docs: add platform vision ADR and Sprint 16 project editor design spec
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.
This commit is contained in:
67
docs/context/adrs/ADR-0001-marketplace-platform-vision.md
Normal file
67
docs/context/adrs/ADR-0001-marketplace-platform-vision.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
id: ADR-0001
|
||||
title: Multi-tenant marketplace platform vision and config-driven architecture
|
||||
status: active
|
||||
date: 2026-07-13
|
||||
tags: ["architecture", "philosophy", "multi-tenant", "bootstrap"]
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
This is not a single marketplace — it is a multi-tenant platform powering unlimited
|
||||
marketplaces (e.g. electronics.example.com, books.example.com) from one codebase.
|
||||
Every marketplace is configured from the backend via a bootstrap configuration
|
||||
(`GET /bootstrap`). No marketplace-specific code may exist in the frontend.
|
||||
|
||||
## Decision
|
||||
|
||||
- The frontend (Angular 20, standalone components, Signals, RxJS, SCSS) is a pure
|
||||
renderer. It owns render, navigation, interaction, validation, animations only.
|
||||
- The backend (ASP.NET Core REST API) owns branding, pages, layouts, languages,
|
||||
homepage, navigation, categories, products, footer, static pages, payment
|
||||
configuration, and enabled features.
|
||||
- Flow: Bootstrap → Runtime Provider → Configuration Store → Renderer → Widgets.
|
||||
Nothing depends on build-time environments; everything depends on runtime
|
||||
configuration.
|
||||
- Bootstrap contains only data needed before the app starts (name, logo, colors,
|
||||
languages, footer pages, homepage layout, navigation, enabled widgets). It must
|
||||
never contain products, orders, cart, or users.
|
||||
- Widgets never own page spacing — only their own internal layout. The renderer
|
||||
owns sections, spacing, and page width.
|
||||
- Homepage is composed from a configurable, ordered list of sections (Section
|
||||
Engine): Hero, Categories, Featured Products, Banner, Latest Products, Custom
|
||||
HTML, Newsletter, etc.
|
||||
- All layouts (homepage, PLP, etc.) must be backend-configurable without frontend
|
||||
changes.
|
||||
- All user-facing text is translatable via a `translations.{lang}` shape, not a
|
||||
flat `title` field. Adding/removing a supported language must automatically
|
||||
expose/remove translation fields across all translatable objects, generically —
|
||||
never per-field hardcoding.
|
||||
- Static pages (About Us, Privacy, Terms, Contacts, Return Policy, Delivery,
|
||||
custom pages) are backend-delivered HTML, multilingual, and drive the footer.
|
||||
- Admin and storefront share a domain but are fully separate applications: the
|
||||
marketplace bundle never ships admin code and vice versa. Bootstrap is public;
|
||||
Admin is protected by JWT + roles/permissions + tenant isolation (Super Admin,
|
||||
Marketplace Admin, Moderator, Editor, Support, Customer).
|
||||
|
||||
## Coding rules
|
||||
|
||||
- Never hardcode marketplace data or introduce marketplace-specific conditionals.
|
||||
- Never use environment flags to drive UI — everything is config-driven.
|
||||
- Keep components small; prefer composition and reusable widgets; never
|
||||
duplicate layouts.
|
||||
- Business logic lives in services/facades, not components.
|
||||
- Prefer Signals and standalone components.
|
||||
- Every new feature ships with docs: frontend docs, backend contract, bootstrap
|
||||
updates, API examples, migration notes if needed.
|
||||
|
||||
## Guiding question
|
||||
|
||||
Before implementing anything: "Will this still make sense after 50 marketplaces
|
||||
and 100 developers?" If not, redesign before coding.
|
||||
|
||||
## Consequences
|
||||
|
||||
Any feature (including the Sprint 16 Project Editor) must edit the same Bootstrap
|
||||
model the storefront consumes — no parallel/duplicate configuration models are
|
||||
permitted anywhere in the platform.
|
||||
6
docs/context/features/platform-vision/FACTS.jsonl
Normal file
6
docs/context/features/platform-vision/FACTS.jsonl
Normal file
@@ -0,0 +1,6 @@
|
||||
{"id":"PV-20260713T000000Z-0001","subject":"platform","predicate":"is-architected-as","object":"multi-tenant marketplace platform powering unlimited marketplaces from one codebase, driven entirely by backend bootstrap configuration","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"decision","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["architecture","multi-tenant"]}
|
||||
{"id":"PV-20260713T000000Z-0002","subject":"frontend","predicate":"must-not","object":"contain marketplace-specific code, hardcoded marketplace data, or environment-flag-driven UI","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["frontend","constraint"]}
|
||||
{"id":"PV-20260713T000000Z-0003","subject":"bootstrap","predicate":"must-only-contain","object":"data needed before app start (branding, languages, homepage layout, navigation, enabled widgets, footer pages) and must never contain products, orders, cart, or users","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["bootstrap","constraint"]}
|
||||
{"id":"PV-20260713T000000Z-0004","subject":"translatable-fields","predicate":"must-be-modeled-as","object":"generic translations.{lang} map so adding/removing a language automatically exposes/removes translation fields across all translatable objects","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["i18n","constraint"]}
|
||||
{"id":"PV-20260713T000000Z-0005","subject":"admin-app","predicate":"is-isolated-from","object":"marketplace storefront bundle: admin code never ships to storefront and vice versa, though they may share a domain","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["admin","security"]}
|
||||
{"id":"PV-20260713T000000Z-0006","subject":"widgets","predicate":"must-not-own","object":"page spacing or page width; the renderer owns sections, spacing, and page width, widgets own only their internal layout","src":["docs/context/adrs/ADR-0001-marketplace-platform-vision.md"],"status":"active","kind":"constraint","updated_at":"2026-07-13T00:00:00Z","confidence":"high","tags":["widgets","layout"]}
|
||||
@@ -0,0 +1,175 @@
|
||||
# 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").
|
||||
Reference in New Issue
Block a user