Records the multi-tenant marketplace platform architecture as ADR-0001 (bootstrap-driven, config-only frontend) with a source-backed fact pack, and writes the approved Sprint 16 design for extending the existing project editor with Languages/Navigation tabs, an HTML editor, and a client-side draft/publish flow.
7.8 KiB
Sprint 16 — Marketplace Project Editor MVP
Status: approved for planning Date: 2026-07-13 Related: ADR-0001, Project-Editor.md
Goal
A client can open the editor for their marketplace, edit settings, save a
draft, preview, and publish — without touching JSON by hand. The editor edits
the same BootstrapConfig the storefront consumes. No parallel/duplicate
configuration model is introduced anywhere in this work.
Current state (as of this sprint)
A working editor already exists at /builder
(src/app/features/project-editor/): ProjectEditorPageComponent + signal-based
ProjectEditorFacade (no NgRx), sections for general, branding, theme, header,
footer, homepage (already has drag-and-drop reordering), widgets,
static-pages (via StaticPagesEditorComponent), features, preview. Static
page HTML is edited via a plain <textarea>. There is no Languages tab, no
Navigation tab, no Draft/Publish distinction, and no ProjectValidator. There
is no separate admin app — admin-ish features are ordinary lazy routes in the
same Angular build as the storefront. There is no projectId / multi-project
concept: each domain (tenant) resolves to its own bootstrap via the existing
tenant-resolution mechanism, so "a project" is implicitly the current domain's
tenant.
This sprint extends the existing editor rather than rebuilding it.
Routing
Single Angular build (no separate admin app this sprint — deferred, see Out of scope). Flat, non-parameterized routes, since there is no project id:
/edit → redirect to /edit/general
/edit/general
/edit/branding
/edit/theme (existing, kept)
/edit/languages (new)
/edit/navigation (new)
/edit/header (existing, kept)
/edit/homepage
/edit/footer
/edit/static-pages
/edit/widgets (existing, kept)
/edit/features (existing, kept)
/edit/preview
/builder and /builder/* redirect to the equivalent /edit/* route for
backward compatibility. Actual subdomain hosting (admin.marketplace.com)
and a physically separate Angular deployment are a future ADR/sprint — this
sprint only makes the route shape subdomain-ready (flat, no id segment).
Tabs become real child routes (deep-linkable, back-button works) instead of
today's in-component signal switch. ProjectEditorFacade is unchanged by
this — it already loads/holds bootstrap independent of which tab is active.
Languages tab (new)
New LocaleSyncService (in features/project-editor/services/). It knows
both translation shapes already used in the codebase:
- index-signature maps:
{ [locale: string]: string }(LocalizedTextContent/LocalizedHtmlContent) - keyed translation records:
Record<string, { title?, html?, seo? }>(e.g.StaticPageTranslationConfig)
addLocale(code) / removeLocale(code) walk the known translatable slices of
the in-memory bootstrap draft (static pages, footer nav labels, homepage
widget text props) and add/remove that locale's key generically — not one
if per field. setDefaultLocale(code) updates
localization.defaultLocale (already exists). The tab lists supported
locales, lets you add/remove, and pick the default; removing the current
default is blocked by ProjectValidator (see below).
Navigation tab (new)
Visual nested list over header + footer navigation
(src/app/shared/models/config/navigation.model.ts). Add link, remove link,
reorder (buttons, matching the homepage tab's existing pattern — no new
drag-and-drop library), edit label (multilingual, via the same translation
shape as everywhere else), edit URL, "open in new tab" toggle, visibility
toggle, and nested children. Backed by new facade actions on
ProjectEditorFacade (addNavLink, removeNavLink, reorderNavLink,
updateNavLink) — no new store, same facade.
Rich HTML editor (new)
New MarketplaceHtmlEditorComponent
(features/project-editor/components/html-editor/): native
contentEditable + a toolbar (bold, italic, underline, bullet/numbered
lists, link, image, table, headings, code view, preview). No new npm
dependency (no Quill/TipTap). Emits raw HTML on change; does not sanitize —
sanitization remains a storefront-render concern, unchanged. Replaces the
<textarea> currently in static-pages-editor.component.html, and is reused
anywhere else HTML is edited going forward (e.g. footer custom content).
Draft / Publish
No backend draft/publish API exists yet — confirmed against
docs/Project-Editor.md, which only lists recommended future endpoints
(PUT /builder/bootstrap, POST /builder/bootstrap/{preview,validate,import},
GET /builder/bootstrap/export), none of which distinguish draft from
published. This sprint models it client-side only:
ProjectDraft(new, inmodels/):{ bootstrap: BootstrapConfig, status: 'draft' | 'published', dirty: boolean, lastSavedAt?: string }.- Save persists the draft using the same local mechanism the editor
already uses for export/import (no new backend call this sprint) and
clears
dirty. - Publish runs
ProjectValidator(must pass), then callsConfigService.applyBootstrapOverride()(the mechanism already used for live preview) and setsstatus = 'published'. - Documented gap for backend: real draft/publish persistence needs
PUT /builder/bootstrap/draftandPOST /builder/bootstrap/publishendpoints. Not implemented this sprint; called out explicitly so backend work can be scheduled.
Validation (new)
New ProjectValidator (in services/), pure functions run against the
current draft. Checks: missing logo, no languages configured, invalid
marketplace URL, duplicate static-page slugs, empty homepage (no sections),
homepage section referencing a missing/unregistered widget, duplicate
navigation links (same URL+label pair), invalid color values (theme tab).
Runs on every Save (surfaces warnings, does not block) and on Publish
(blocks with a clear list of failures). Errors surface inline in the
relevant tab plus a summary list.
Dirty-state
ProjectEditorFacade tracks dirty via a diff against the last-saved
snapshot. A CanDeactivate route guard plus a beforeunload listener warn
before navigating away or closing the tab with unsaved changes.
UI
Card-based layout per tab, sticky Save/Publish bar (shows dirty state and validation summary), responsive down to tablet width — matching the existing editor's current visual style, no new design system.
Architecture summary
Reused as-is: ProjectEditorFacade, ProjectEditorIoService,
ProjectEditorPreviewService, existing section components.
New: LocaleSyncService, ProjectValidator, MarketplaceHtmlEditorComponent,
ProjectDraft model, Navigation tab section + facade actions, Languages tab
section + facade actions, CanDeactivate dirty-guard.
No ProjectEditorStore/ProjectSerializer as separate classes — the
existing facade + ProjectEditorIoService already cover that responsibility
(store = facade signals; serializer = IO service); introducing parallel
classes would duplicate what's there, which the platform's own coding rules
forbid.
Out of scope (this sprint)
- Separate Angular app / actual subdomain deployment for admin.
- Real backend draft/publish persistence (documented as a gap above).
- Drag-and-drop for the new Navigation tab (buttons only, like homepage's existing pattern predates this — homepage itself already has drag-and-drop from a prior sprint and is left as-is).
- Products, Orders, Users, Dashboards, Analytics (future sprints per the platform roadmap).
Documentation
This spec plus updates to docs/Project-Editor.md (new tabs, draft/publish
model, new services) and a fact-pack update under
docs/context/features/project-editor/FACTS.jsonl are part of the
deliverable, per the platform's documentation rule ("every new feature must
include documentation").