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.
176 lines
7.8 KiB
Markdown
176 lines
7.8 KiB
Markdown
# Sprint 16 — Marketplace Project Editor MVP
|
|
|
|
Status: approved for planning
|
|
Date: 2026-07-13
|
|
Related: [ADR-0001](../../context/adrs/ADR-0001-marketplace-platform-vision.md), [Project-Editor.md](../../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, in `models/`): `{ 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 calls
|
|
`ConfigService.applyBootstrapOverride()` (the mechanism already used for
|
|
live preview) and sets `status = 'published'`.
|
|
- **Documented gap for backend**: real draft/publish persistence needs
|
|
`PUT /builder/bootstrap/draft` and `POST /builder/bootstrap/publish`
|
|
endpoints. 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").
|