Migrate to Vue 3 Composition API and TypeScript#3751
Open
Vondry wants to merge 38 commits into
Open
Conversation
5304b92 to
36f5bda
Compare
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.9 to 2.0.10. - [Release notes](https://github.com/chimurai/http-proxy-middleware/releases) - [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.10/CHANGELOG.md) - [Commits](chimurai/http-proxy-middleware@v2.0.9...v2.0.10) --- updated-dependencies: - dependency-name: http-proxy-middleware dependency-version: 2.0.10 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> # Conflicts: # package-lock.json
Bumps [undici](https://github.com/nodejs/undici) from 6.26.0 to 6.27.0. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](nodejs/undici@v6.26.0...v6.27.0) --- updated-dependencies: - dependency-name: undici dependency-version: 6.27.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [launch-editor](https://github.com/vitejs/launch-editor) from 2.14.0 to 2.14.1. - [Commits](vitejs/launch-editor@v2.14.0...v2.14.1) --- updated-dependencies: - dependency-name: launch-editor dependency-version: 2.14.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
The Relation API resource is readable with `api:get` (PUBLIC_ACCESS) and embeds both `fromContent` and `toContent`, but ContentExtension only filtered the Content and Field resources. Anonymous users could therefore enumerate relations involving unpublished, draft or viewless content, leaking its existence, IDs and the relationship graph. Apply the same published/non-viewless filter to Relation queries, requiring both ends of the relation to be publicly visible. Add tests asserting the collection and item queries constrain both ends; they fail if the filter is removed.
Bind content-type slugs as query parameters instead of interpolating them into raw SQL strings. The split() helper now returns an array of slugs bound via ArrayParameterType::STRING rather than a pre-quoted SQL fragment. Add regression tests proving the slugs are passed as bound parameters and never reach the SQL string.
The /embed endpoint fetched any user-supplied URL server-side via the embed library after only a CSRF check, with the same SSRF exposure as the upload-from-URL endpoint. Guard it with UrlSafetyChecker before the fetch. Add controller tests for both /embed and /upload-url asserting that internal/private/metadata hosts and disallowed schemes are rejected with a 400. These tests fail if the UrlSafetyChecker guard is removed.
The /upload-url endpoint fetched any user-supplied URL server-side via Filesystem::copy() after only a format/scheme check, allowing requests to internal services and cloud metadata endpoints (e.g. 169.254.169.254). Add UrlSafetyChecker, which rejects non-http(s) schemes and any URL whose host resolves to a private, reserved, loopback or link-local address (including IPv4-mapped IPv6), and call it before fetching the URL. Add unit tests covering allowed public URLs and blocked schemes, private ranges, loopback, link-local/metadata and malformed input.
Enable strict TypeScript and replace loose Record<string, any> typings with explicit interfaces and shared type modules across the Vue editor, listing, sidebar and toolbar components. Includes hardened async/error handling (BrowserError class, shared upload-error helper, safe DOM lookups), tightened lint/tsconfig, and the accompanying unit-test type-safety updates.
Image.vue built its reactive extra-field map from every key of the extra-data payload. In an imagelist the parent passes the whole ImageField record, so base keys (filename, media, thumbnail, ...) were coerced into the map. Key the map off the declared extra fields instead, normalizing the object/array/whole-record inputs to a single lookup.
getStatusLink() returns null for new content or when the user lacks CONTENT_VIEW permission, so the status-change links rendered a dead "&status=..." relative href. Guard each on extras.statusLink so they hide entirely, which is also the correct authorization behavior.
normalizeError checked `instanceof Error` and returned Axios's generic "Request failed with status code N" before extracting the server-provided message. Since AxiosErrors are Error instances, the response.data message was unreachable. Extract the response message first, then fall back to error.message.
fixSelectedItems used a truthiness check (`if (props.value)`) that discarded a valid numeric key of 0. Use an explicit nullish check. Also, addTag wrapped the new tag in an array in the single-select branch; bind the object directly so the non-multiple v-model stays a single object.
getLocale() returns undefined for English/unresolved locales, and passing locale: undefined makes flatpickr throw "invalid locale undefined". Only include the locale key when one is resolved; flatpickr then uses its English default.
The test emitted the kebab-case "update:model-value", which vue-multiselect does not declare, producing a Vue emit warning. Emit the declared camelCase event.
fixSelectedItems always assigned an array, so single-select mode left addTag pushing into it and accumulating multiple tags. Bind the shape vue-multiselect expects: a single object (or null) when multiple is false.
Align each component's props with its real usage (Twig templates as the source of truth) so passed attributes bind as props instead of falling through onto the root element: - Image: title (imagelist/simple_image) - File: thumbnail, alt (file.html.twig, Filelist) - Textarea: label - Password: label, label_class (kept underscore to match the emitted attribute) - listing Row _Actions: size
- Fix theme/skeleton/assets/favicon/manifest.json 404 by explicitly defining 'bolt' package in favicon twig partials. - Fix Vue warnings on cropX/cropY/cropZoom props by coercing them to strings in twig and allowing number in Text.vue. - Fix Vibrant.js getSwatches/getHex TypeError for node-vibrant v4 compatibility. - Fix bolt_logo_dashboard.svg 404 by converting absolute path to relative in Toolbar.vue to enable Webpack processing. - Add test coverage for Toolbar logo src.
- Integrate eslint-plugin-vuejs-accessibility - Map orphaned labels to inputs via id - Replace non-semantic empty anchors with buttons - Remove redundant click handlers on labels - Convert static text labels to spans to prevent screen reader confusion - Adjust eslint rules to allow valid Bootstrap label-input sibling structures
- Revert removal of autofocus from Text.vue and suppress a11y rule - Remove fragment-creating a11y suppression comments from root nodes of File.vue and Image.vue - Suppress a11y rules for File.vue and Image.vue directly in eslint.config.mjs
fixRequired() ran on mount and called input.reportValidity(), which popped the native 'please fill in this field' bubble on page load for every empty required date field, before any user interaction. Only report validity on subsequent updates/value changes; still apply the required attribute on mount.
Investigate the two remaining review candidates with executable tests: - Bug 2 (Slug/Text eventBus scoping): add integration tests proving the supported top-level title->slug flow works end-to-end, and that a field nested in a collection (fields[blocks][hash][title]) does not drive the page's top-level slug. The latter fails if fieldKey is changed to extract the innermost segment, confirming the current first-segment scoping is the correct design and nested auto-slug was never a supported behaviour. - Bug 3 (Collection move/reorder): add tests proving the DOM submission order (hash-named inputs) survives a manual move followed by an add or a remove. The claimed 'reorder snaps back' failure does not occur, because Vue's keyed diff never relocates the jQuery-moved nodes. Both candidates pass on current code; these lock in the correct behaviour.
4f7e067 to
3778f43
Compare
This was referenced Jul 8, 2026
b420363 to
ccafced
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR modernizes the Bolt admin frontend assets for Vue 3, TypeScript, stricter quality checks, and stronger UI test coverage.
Main Changes
Vue 3 Migration
assets/js/app/to Vue 3 Composition API with<script setup lang="ts">.useFieldValue.ts.mittevent bus for editor title/slug synchronization.TypeScript
.jsto.tswhere useful.strictnoImplicitReturnsnoFallthroughCasesInSwitchallowUnreachableCode: falseallowUnusedLabels: falsenoImplicitOverrideisolatedModulesvue-tsc --noEmittype checking.Build Tooling
webpack.config.mjs..tsand Vue SFC files.Testing
95%85%95%95%CI
npm run typecheck.Linting And Code Quality
@typescript-eslint/no-explicit-anyan error.eslint-plugin-vuejs-accessibilityto enforce accessibility checks in Vue components.Accessibility
aria-labels, screen-reader text, and keyboard roles across Vue components.Shared Utilities
useFieldValue.ts.serverFileBrowser.ts.Performance And Bug Fixes
Editor Fields
0values.Listing