Files
marketplaces/docs/EDITOR.md
sdarbinyan 76831b8485 docs: consolidate scattered docs into canonical set
Replace ~35 organically-grown docs (docs/platform/*, docs/backend-platform/*,
one-off sprint reports, Search.md, Diagnostics.md, Content-Management.md,
Backend-Handoff-Sprint16.md, docs/superpowers/*, docs/Project-Editor.md,
untracked docs/total.md) with the six canonical docs declared in
.claude/CLAUDE.md: PROJECT.md, ARCHITECTURE.md, BACKEND.md, FRONTEND.md,
BOOTSTRAP.md, EDITOR.md, plus a new PROJECT-STRUCTURE.md.

- BACKEND.md is a punch list per domain (auth, bootstrap draft/publish,
  static pages, categories, products, orders, dashboard metrics, activity,
  translations, search, product engagement) plus a Known reliability issues
  section on the prod 502/504 root cause.
- ARCHITECTURE.md links to (does not duplicate) the enforced
  docs/architecture/foundation/** ADRs and standards docs.
- docs/ADMIN.md and docs/architecture/foundation/** and docs/context/** are
  left untouched per instructions.
- Updated the one dangling docs/Project-Editor.md reference in
  admin-auth.service.ts to point at docs/BACKEND.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 12:28:41 +04:00

7.3 KiB

EDITOR (Project Editor)

Replaces the old docs/Project-Editor.md (content merged in below and extended with the Sprint 19 field-description/dropdown work).

The Project Editor (src/app/features/project-editor/) edits the tenant's BootstrapConfig (docs/BOOTSTRAP.md) directly — no parallel model. It is out of scope for products, categories, orders, or analytics management (those live under features/admin/*/features/backoffice/*, see docs/ADMIN.md).

src/app/features/project-editor/
  pages/       route container
  sections/    one component per editor tab (see below)
  components/  shared editor UI (save bar, HTML editor)
  models/      ProjectEditorState, EDITOR_SECTION_BOOTSTRAP_KEYS
  services/    ProjectValidator, ProjectEditorDraftStorageService, LocaleSyncService
  facade/      ProjectEditorFacade

Route: /edit/:section or /{lang}/edit/:section.

Facade

ProjectEditorFacade exposes: loadBootstrap(), updateBootstrap(updater), exportBootstrap(), importBootstrap(), preview(), save(), publish(), plus signals bootstrap, status (draft|published), dirty, lastSavedAt, lastPublishedAt, validationIssues, homepageWidgets, homepagePage. Components in sections/* inject this facade directly (an accepted exception to the presentational-component rule, per ADR-006 — these are container/section components, not shared UI).

Sections

Section Component Covers
General general-section marketplace name, domain, description, default/supported languages
Branding branding-section logo, small logo, favicon, marketplace title
Theme theme-section palette colors, theme mode, site layout mode
Header header-section logo/search/categories/languages/cart/profile/wishlist/compare/region toggles
Footer footer-section company info, address, phone, email, copyright, payment icons, social links, static pages list
Homepage homepage-section homepage section list: visibility, order (drag-and-drop), layout strategy, columns
Widgets widgets-section homepage widget configuration — typed editors for hero/categories/product-collection, JSON fallback for everything else
Marketplace Features features-section feature flags, catalog navigation mode, search suggestions/history, recently viewed, reviews/questions/recommendations
Languages languages-section add/remove supported locale, set default locale; syncs translation keys across static pages and nav labels via LocaleSyncService
Navigation navigation-section header nav: add/remove/reorder/edit label/URL/visibility. Flat footer nav: same. Grouped (column-based) footer nav is read-only here — edit via Footer tab.
Preview preview-section export/import JSON, in-memory runtime preview without full reload

Save / publish / draft / reset model

  • Save: save() snapshots the current in-memory bootstrap as "last saved" (lastSavedAt). ProjectEditorDraftStorageService persists the full draft to localStorage (projectEditor.draftBootstrap.v1, scoped by tenant.id) on every updateBootstrap(), save(), and publish() call.
  • Publish: runs ProjectValidator; if clean, calls PlatformRuntimeService.reloadFromBootstrap(), sets status = 'published', sets lastPublishedAt, and becomes the new originalBootstrap baseline used by reset.
  • Draft restore: on loadBootstrap(), if a stored draft exists for the same tenant it loads instead of the fresh fetch, and draftRestored is set (shown as a dismissible banner in the save bar).
  • Reset section: reverts one section's bootstrap keys (per EDITOR_SECTION_BOOTSTRAP_KEYS in models/project-editor.model.ts) to originalBootstrap. Confirmation required.
  • Reset draft: reverts the entire bootstrap to originalBootstrap and clears the persisted local draft. Confirmation required.
  • Per-field reset is not implemented — no per-field default registry exists; only section- and project-level reset.
  • No backend persistence exists for any of this today — see docs/BACKEND.md item 2 for the endpoints needed.

Validation

ProjectValidator (services/project-validator.service.ts) runs on every save-bar render and blocks Publish (not Save) on: missing branding.logoUrl, no supported locales, invalid tenant.websiteBaseUrl, duplicate static-page slugs (falls back to route), empty homepage, a homepage widget with no type, duplicate header nav links, invalid theme colors, missing translations for a supported locale, and layout/section-layout values outside the known enums (PlatformLayoutType, SectionLayoutStrategy).

Admin Authentication (QR reuse)

Admin login shares the exact same Telegram QR/session backend and TelegramLoginComponent as customer login (mode: 'admin' vs 'customer') — only the cookie name/SameSite policy, token storage keys, and guard differ. Backend gap: because both flows hit the same session endpoint, the backend cannot distinguish an admin scan from a customer scan today — real admin authorization must be enforced server-side. Full detail: docs/BACKEND.md item 1.

Field-description / dropdown UX (Sprint 19+)

Every field across the 10 editor section templates now carries a one-line, i18n'd description under its label explaining what it does in plain language (all new copy routed through TranslateService/TranslatePipe, added to Translations + en.ts/ru.ts/hy.ts following the existing builder.* key pattern — see src/app/i18n/translations.ts).

Converted from free-text <input> to <select> (backed by a closed TypeScript union), each option carrying a human label and a short description (via title attribute) instead of the raw enum value:

  • section.layout.strategy (Homepage section) — SectionLayoutStrategy: stack | grid | hero | carousel | split.
  • theme.mode (Theme section) — light | dark | system.
  • layout.type (Theme section, "Site Layout") — PlatformLayoutType: default | sidebar-left | carousel-home | minimal.
  • catalog.navigationMode (Marketplace Features section) — CatalogNavigationModeConfig: default | left-category-navigation | mega-category-layout | top-category-carousel.

Each of these components defines a local readonly options array of { value, labelKey, descriptionKey } (per ADR-006, these are section/container components so this is allowed without a new shared UI library).

Still plain text/checkbox, with a description added, and why: marketplace name, domain, description, logo/favicon/small-logo URLs, palette colors (already <input type="color">, which is the correct native widget), company/address/phone/email, copyright, payment icons/social links (JSON-ish textarea), homepage section columns (a number, not an enum), widget-specific props (hero/categories/product-collection typed fields like layout/height/overlay/autoplay/cardsPerRow — these are widget props strings/booleans, not modeled as TypeScript unions anywhere, so they stay free text/checkbox with a description rather than a fabricated enum), navigation link label/URL, and the widget JSON fallback textarea for any widget type without a dedicated editor. These are genuinely open-ended or already have the correct native input type; converting them to <select> would either be wrong (URLs/colors/free text) or invent an enum that doesn't exist in the schema.