From 2cbb62a9ccbe85d1537109dc6c9617ca658d4fea Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Fri, 24 Jul 2026 10:12:14 +0400 Subject: [PATCH] fix(builder): release-candidate walkthrough fixes - Legacy no-lang-prefix URLs with a query string (e.g. the dev ?devBypassAdmin=true bypass itself, or any bookmarked/shared deep link into the Builder) got their query string percent-encoded into the path instead of preserved (language.guard.ts) - router.createUrlTree([...]) treats a single array element as a literal path segment, so `/edit/branding?devBypassAdmin=true` became `/ru/edit/branding%3FdevBypassAdmin%3Dtrue`, a 0-result route. Switched to router.parseUrl() on the full redirect string so path, query params, and fragment are parsed and preserved correctly. This guard runs on every top-level route in the app (not just Builder), so this was silently breaking any legacy URL with a query string app-wide. - "Reset draft" (Sbrosit' chernovik) left the save-bar showing "unsaved changes" immediately after the reset, even though the reset already discarded everything and cleared the persisted localStorage draft (project-editor.facade.ts resetDraft()) - it updated the in-memory bootstrap and cleared the draft but never resynced lastSavedBootstrap, which the dirty computed diffs against. Now resetDraft() also resets lastSavedBootstrap to match, so the status bar correctly reads as clean right after a full discard. Verified live via browser walkthrough of every Builder section (General, Branding, Theme, Header, Footer, Homepage, Widgets, Static Pages, Languages, Features, Navigation, Preview) plus save/publish/undo/redo/ reset-section/reset-draft/draft-restore flows, the media picker dialog, and the Homepage block / Footer column keyboard-fallback reorder buttons (WCAG 2.1.1 fallback added in the prior a11y pass) - all functioned correctly end-to-end, no console errors, no untranslated i18n keys, no unexpected 4xx/5xx, no layout overflow at desktop or mobile widths. Investigated and flagged, not fixed (needs a design decision, not a bug fix): the static page's actual body content editor (app-marketplace-html-editor, per-locale) is not on the page editor's "Content" tab at all - it only has title/hero-image/thumbnail fields. The real WYSIWYG/HTML editor is nested inside a collapsed
disclosure under the "Advanced" tab, labeled "Source HTML (advanced)" as if it were a raw-HTML power-user fallback, when it is in fact the only way to edit a static page's body content. Functions correctly once found/expanded; the placement/labeling just doesn't match the "Content" tab a merchant would expect it under, and moving it is a navigation change beyond this pass's fix-what's-broken scope. npx tsc --noEmit and npm run build both green. Co-Authored-By: Claude Sonnet 5 --- .../project-editor/facade/project-editor.facade.ts | 12 +++++++++++- src/app/guards/language.guard.ts | 9 ++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/app/features/project-editor/facade/project-editor.facade.ts b/src/app/features/project-editor/facade/project-editor.facade.ts index 7390587..489376d 100644 --- a/src/app/features/project-editor/facade/project-editor.facade.ts +++ b/src/app/features/project-editor/facade/project-editor.facade.ts @@ -461,7 +461,17 @@ export class ProjectEditorFacade { } const reset = this.normalize(JSON.parse(JSON.stringify(original)) as BootstrapConfig); this.draftStorage.clear(); - this.state.update(state => ({ ...state, bootstrap: reset, lastSavedAt: null, draftRestored: false })); + this.state.update(state => ({ + ...state, + bootstrap: reset, + // Reset also clears the persisted draft, so there is nothing pending + // to save at this point - keep `lastSavedBootstrap` in sync too, + // otherwise `dirty` (computed against it) immediately flags + // "unsaved changes" right after a full discard. + lastSavedBootstrap: JSON.parse(JSON.stringify(reset)), + lastSavedAt: null, + draftRestored: false + })); this.clearHistory(); } diff --git a/src/app/guards/language.guard.ts b/src/app/guards/language.guard.ts index aa51706..b8117be 100644 --- a/src/app/guards/language.guard.ts +++ b/src/app/guards/language.guard.ts @@ -24,9 +24,12 @@ export const languageGuard: CanActivateFn = async (route, state) => { if (langObj && !langObj.enabled) { // Known but disabled language — redirect to default lang, keep the rest of the path const pathAfterLang = state.url.slice(1 + lang.length); - return router.createUrlTree([`/${defaultLang}${pathAfterLang}`]); + return router.parseUrl(`/${defaultLang}${pathAfterLang}`); } - // Not a recognized language code — treat as a legacy URL without lang prefix - return router.createUrlTree([`/${defaultLang}${state.url}`]); + // Not a recognized language code — treat as a legacy URL without lang prefix. + // Use parseUrl (not createUrlTree with a single path-segment array entry) so + // the query string/fragment on state.url are preserved as real query params + // instead of being percent-encoded into the path segment. + return router.parseUrl(`/${defaultLang}${state.url}`); };