Renamed the experience consistently to Marketplace Builder everywhere
visible (page title/subtitle, breadcrumbs, sidebar, dashboard quick
action/shortcut copy) - internal ProjectEditor class/selector names
kept as-is to avoid regressions. builder.title/subtitle no longer say
'bootstrap-config editing surface' to end users.
New business-oriented IA (builder-groups.model.ts): 12 existing
ProjectEditorSectionIds regrouped into 8 groups (Marketplace, Branding
and Design, Homepage, Content, Languages, Marketplace Features,
Navigation and Search, Preview) - every existing section mapped to
exactly one group, none dropped, no group points at a page that
doesn't exist.
New Builder landing page at /edit (was a redirect straight into the
General form): readiness percent, recommended next step, one overview
card per group (purpose + real complete/in-progress/not-started/unknown
status, icon+text never color-alone), a Marketplace Readiness
checklist, quick links. All derived from real facade/schema signals
(required-field fill state, modifiedSections, staticPages, catalog
counts via BackofficeDataService) or explicitly marked unknown
(the "preview reviewed" check has no tracking - shown as unknown,
never guessed).
Section pages (/edit/:section) now share one consistent header:
breadcrumb (Builder > Group > Section), draft/published + modified
badges, and a contextual help panel (what is this / where visible /
what happens if you skip it) sourced per group. Sidebar nav rewritten
as grouped, icon-led sections with per-section and per-group status
indicators; layout converted from a top pill-tab bar to a responsive
sidebar (desktop 280px, tablet 72px icon rail, mobile drawer with
Escape-to-close), matching the Sprint 1 admin shell pattern. Section
form components themselves untouched.
Routes: 'edit' is now its own landing route instead of redirecting to
'edit/general'; /builder and /project-editor redirect to 'edit'.
Sprint 1/2 destinations that pointed at edit/general now point at the
new edit landing page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
importBootstrap() replaced state.bootstrap directly, bypassing the same
updateBootstrap() pipeline every other edit goes through - so an import
never got a draftStorage.save() (lost on refresh before an explicit Save)
and never became an undo-able history step (Undo silently skipped over it).
Verified live via window.ng.getComponent(): exported the current bootstrap,
mutated branding.brandName, imported it back - draftStorage's localStorage
key changed and contained the new value; clicking Undo correctly reverted
brandName to the pre-import value.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Independent review pass over the Static Pages Module sprint (M1-M7),
including live browser E2E per the task's own verification checklist.
- ProjectEditorFacade.normalize() now backfills enabled/status on
record-format static pages (defaulting missing values to enabled+published,
same rule ContentPageService.normalizePage applies for display - mirrored
rather than imported, to avoid a project-editor <-> content-management
circular dependency since ContentManagementFacade already depends on this
facade). Found live: exporting a page that predates this sprint and was
never touched/re-saved in the current session produced JSON missing
enabled/status entirely - the editor UI and storefront resolver both
normalize-on-read so nothing was actually broken live, but Export/Import
fidelity should match what the editor shows. Verified fixed live (export
now includes "enabled":true,"status":"published" for an untouched legacy
page) and via the full gate.
- Added the missing adminCategories.chooseImage i18n key (interface +
en/ru/hy). Found live: the media-picker "choose image" button rendered as
the literal string "adminCategories.chooseImage" - a pre-existing,
repo-wide bug (5 templates reference this key; none of the locale files
ever defined it) that I propagated into a 3rd/4th/5th... well, 2 new
occurrences by copying the existing branding-section/footer-section
pattern into static-pages-editor. Fixed the actual defect (missing
translation) rather than renaming the key, which would have required
touching 2 unrelated admin components outside this sprint's scope.
Live-verified this pass: Static Pages editor renders with all new fields;
create page works (page count 4->confirmed); device preview toggles
desktop/tablet/mobile widths correctly; navigation "Insert page link"
creates a real type:'staticPage' nav item end-to-end (confirmed in the
exported JSON); export includes all Sprint X+2 fields after the fix; no
console errors throughout.
Gate: tsc --noEmit, npm test (57/57), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 6 of the Static Pages Module sprint.
- ProjectEditorFacade.addStaticPageNavLink(target, pageId): creates a
NavigationItemConfig { type: 'staticPage', key: pageId }. This shape was
already understood end-to-end by the resolvers (StaticPageResolverService/
FooterResolverService derive label+route from the linked page - see
footer-resolver.service.ts resolveGroupItem/resolveLegacyItem) - the only
gap was that the editor UI never exposed a way to create it.
- navigation-section: "Insert page link" control (page picker + button) next
to both header and footer "Add link". Rows for a static-page link show a
"Linked to page" indicator instead of the raw label/URL inputs (those
fields don't apply - the resolver derives them dynamically). labelOf()
falls back to the page id for the row heading since a static-page link has
no label of its own.
- i18n: builder.insertPageLink, builder.linkedToPage in interface + en/ru/hy.
Verified (no rebuild needed) that pages already participate in preview(),
exportBootstrap()/importBootstrap(), and draft/publish gating - all flow
through bootstrap.staticPages and the M1-M2 additive fields untouched here.
Gate: tsc --noEmit, npm test (57/57), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 4 of the Static Pages Module sprint.
- heroImage/thumbnail now wire through the existing MediaPickerComponent
(same media-field-row + "choose image" pattern as branding-section), not
plain URL text alone. gallery stays a lightweight CSV field ("future
ready" per the brief - no dedicated multi-upload UI this sprint).
- ProjectEditorFacade: add originalStaticPages, a narrow computed exposing
the originally loaded/published staticPages snapshot (mirrors the facade's
existing pattern of small single-purpose computeds).
- StaticPagesEditorComponent: isModified(page) diffs a page against its
normalized original snapshot, reusing ContentManagementFacade.pages() for
normalization rather than reimplementing it. Renders as an amber
"unsaved changes" badge per page.
Draft/published status UI, publish/unpublish actions, and the plain-text
media fields landed already in M2; this milestone completes M4's remaining
scope (visual media picker + modified indicator) without duplicating that
work.
Gate: tsc --noEmit, npm test (57/57), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Independent review pass over the Configuration Engine sprint (M1-M6).
- Facade: a fresh edit burst now clears the redo (future) stack immediately,
not just once its debounced commit lands ~300ms later. Previously, editing
right after an undo left canRedo() true for that window; clicking Redo
during it would have silently discarded the new edit and jumped back to
the stale future snapshot. Reordered two interspersed imports/interface
for readability while in the file.
- footer-section: removed a local HTTP_URL regex + duplicate isValidUrl
logic (its "shared/ui can't import features" justification didn't apply -
this file already lives in features/project-editor/sections/, the same
feature as schema/validators/). Now calls isValidHttpUrl from
schema/validators/primitives, closing a validator duplication the sprint's
"no duplicated validators" requirement was meant to catch.
Gate: tsc --noEmit, npm test (33/33), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 5 of the Configuration Engine sprint.
- Facade: changeSummary computed - per modified field, before/after values
(schema label + stringified diff vs originalBootstrap), reusing
modifiedFields from M4.
- preview-section: new "changes since last publish" card ahead of the
existing export/import/live-preview card - validation issue list
(warning/error styled) plus a before/after change table. Reuses the
existing, non-destructive ProjectEditorPreviewService.preview() call.
- i18n: previewChangesTitle/NoIssues/NoChanges/ChangeField/Before/After in
interface + en/ru/hy.
Gate: tsc --noEmit, npm test (33/33), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 4 of the Configuration Engine sprint.
- Add schema/history.util: pure undo/redo reducer (commit/undo/redo, depth cap)
with full spec coverage.
- Facade: debounced snapshot history (~300ms coalesce so a typing burst = one
undo step); undo()/redo() route through the draft-save path so autosave never
desyncs; canUndo/canRedo; history cleared on load/publish/resetDraft.
modifiedFields (schema-diff vs original) + modifiedSections computeds.
- save-bar: Undo/Redo buttons. Page: Ctrl/Cmd+Z / Shift+Z / Y shortcuts
(skipped while a text field is focused so native text undo is preserved);
beforeunload guard already present.
- nav: amber modified-field dot per section (when no blocking badge).
- i18n: builder.undo / builder.redo in interface + en/ru/hy.
Gate: tsc --noEmit, npm test (33/33), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 3 of the Configuration Engine sprint.
- Facade fieldError(key) accessor over issuesByField for inline field errors.
- Bind [error] on schema-backed fields: theme palette colours, general
name/domain, branding logo (translated via each section).
- project-editor-nav: per-section blocking-issue count badge (issuesBySection).
- save-bar: Publish now disabled on hasBlockingIssues() (errors only, so new
warnings no longer block); issue list tags warning vs error severity.
Editor verified rendering at /ru/edit/theme with the new nav + save bar;
validation logic covered by the 25 unit tests.
Gate: tsc --noEmit, npm test (25/25), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Milestone 2 of the Configuration Engine sprint.
- Add schema/validators/primitives: pure isValidHexColor/HttpUrl/Email,
validateJson, validateCss, extractStyleBlocks, normalizeRoute. One function
per concern, no duplicated validator logic.
- Refactor ProjectValidator to compose the primitives and tag every issue with
section + fieldKey + severity ('error' blocks publish, 'warning' advisory).
Preserves all existing codes/messages; adds duplicate-routes, invalid-css,
invalid-widget-config checks.
- Facade: issuesByField, issuesBySection, blockingIssues, hasBlockingIssues;
publish() now gates on severity==='error' instead of any issue.
- i18n: add validationInvalidJson/Css/DuplicateRoutes/InvalidWidgetConfig to
the Translations interface + en/ru/hy.
- Specs: primitives + ProjectValidator (25 passing total).
Gate: tsc --noEmit, npm test (25/25), arch:check, build all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- navigation-section: add LocaleTabs; label input now reads/writes the
active locale's translation via a new editableLabel() helper instead of
always the default locale. facade.updateNavLinkLabel() gained an optional
locale param (defaults to current default locale, so existing callers
are unaffected) and correctly promotes a plain-string label into a
per-locale map when writing a non-default locale.
- languages-section: wrap in SectionCard, add LocaleTabs for consistency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Project editor: persist draft to localStorage, restore on reload,
last-saved/draft-restored status indicators, section/whole-draft reset
with confirmation.
- Extract shared QR/polling/expiry engine from TelegramLoginComponent
(shared/qr-login) and reuse it for a new admin login flow.
- Admin authentication kept fully separate from customer session:
own cookie/localStorage keys, signals, guard, and header interceptor
(core/admin-auth).
- ?login=true / ?adminLogin=true open the respective login dialog for
manual testing.
- Ed25519 challenge/verify interfaces (fail-closed no-op binding) ready
for backend delivery.
- Document autosave/reset/admin-auth/QR-reuse/Ed25519 model and the
remaining full-field-coverage gap in docs/Project-Editor.md.
updateLabel() previously overwrote NavigationItemConfig.label with a bare
string via updateNavLink, destroying every other locale's translation
whenever a localized label object was edited. Add a facade method
updateNavLinkLabel() that inspects the existing label shape: plain
strings are replaced as before, but localized objects only have the
current default locale's key overwritten, leaving other locales intact.
Wires ProjectValidator into ProjectEditorFacade and extends ProjectEditorState
with status ('draft'|'published') and lastSavedBootstrap. Adds dirty computed
(diffed against lastSavedBootstrap), validationIssues computed, and save()/publish()
methods. publish() calls PlatformRuntimeService.reloadFromBootstrap() and refuses
when validation issues exist. loadBootstrap() seeds lastSavedBootstrap so a
freshly-loaded bootstrap is not dirty.