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>
This commit is contained in:
sdarbinyan
2026-07-14 12:28:41 +04:00
parent 94e59ab878
commit 76831b8485
46 changed files with 627 additions and 6857 deletions

72
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,72 @@
# ARCHITECTURE
## Platform principles
- One codebase, unlimited tenants. No tenant-specific implementation code in the frontend.
- Tenant behavior is controlled entirely by configuration loaded at bootstrap (`GET /bootstrap`, tenant resolved server-side by domain).
- Prefer configuration over conditionals, composition over inheritance.
- Authentication, payment, and authorization contracts/behavior are frozen and must not be redesigned as part of platform work (`docs/architecture/foundation/adr/ADR-010-backward-compatibility-for-auth-payment-authorization.md`).
- No circular dependencies; shared/UI layers are feature-agnostic.
These rules are enforced, not aspirational — see `docs/architecture/foundation/README.md` and the ADR set below, plus `npm run arch:check` (import-boundary + circular-dependency checks).
## Architecture Decision Records (source of truth — read directly, do not treat this file as a paraphrase)
All under `docs/architecture/foundation/adr/`:
- **ADR-001** — platform model (multi-tenant, config-driven).
- **ADR-002** — layered feature architecture.
- **ADR-003** — import boundaries and dependency direction.
- **ADR-004** — configuration bootstrap and provider abstraction.
- **ADR-005** — dynamic page/section/widget rendering.
- **ADR-006** — UI component purity and container/facade pattern.
- **ADR-007** — state management and facade boundaries.
- **ADR-008** — theme engine and design-token runtime.
- **ADR-009** — feature flags and capability guards.
- **ADR-010** — backward compatibility for auth/payment/authorization.
Companion standards docs (also `docs/architecture/foundation/`, kept as-is, enforced): `Coding-Standards.md`, `Naming-Conventions.md`, `Dependency-Rules.md`, `Folder-Blueprint.md`, `Import-Boundary-Matrix.md`, `State-Management-Standards.md`, `Configuration-Standards.md`, `Component-Standards.md`, `Service-Standards.md`.
## Layered architecture
```
Component (container) --> Facade --> Domain Service --> Repository/Provider --> Mock | API
```
- **Container/page components** own routing, orchestration, and DI of a facade. They hold no business logic.
- **Presentational components** are `@Input()`/`@Output()`-only: no `HttpClient`, no storage, no environment access, no facade injection (ADR-006). The Project Editor's *sections* (`features/project-editor/sections/*`) are an accepted exception — they are container/section components, not shared presentational UI, so they may inject the facade directly (see `docs/EDITOR.md`).
- **Facades** (`facades/**`, or feature-local `facade/`) are the only thing components talk to. They expose signals/observables and imperative methods; they compose one or more domain services (ADR-007).
- **Domain services** (`core/<domain>/*.service.ts`) convert backend DTOs into domain models via a **mapper**, and expose domain-shaped methods. DTOs never leak past the mapper boundary.
- **Repositories/providers** are swappable via injection tokens (e.g. `PRODUCT_DATA_PROVIDER`, `CATEGORY_REPOSITORY`, `BACKOFFICE_DATA_PROVIDER`, `ADMIN_DASHBOARD_METRICS_GATEWAY`) so mock and real-API implementations can be swapped without touching facades or components — the same pattern used throughout `core/`, `features/admin/*`, and `features/backoffice/*`.
## Bootstrap / configuration engine
- `ConfigService` loads `BootstrapConfig` (see `docs/BOOTSTRAP.md`) once at startup; `PlatformRuntimeService` applies it (theme, branding, runtime state) and can `reloadFromBootstrap()` for in-memory preview without a full page reload.
- The bootstrap is the single source of truth for pages, sections, widgets, theme, navigation, footer, static pages, and feature flags (ADR-004).
- The Project Editor mutates an in-memory draft of the same `BootstrapConfig` — there is no parallel editor-only model.
## Dynamic page / section / widget rendering (ADR-005)
Render pipeline: `page config -> section engine -> section renderer -> widget host -> registered widget component`.
- **Section Engine** (`dynamic-renderer/section-engine/section-engine.service.ts`) builds an ordered page render model from `PageConfig.sections`, applying `order`, `layout` (`SectionLayoutConfig.strategy`: `stack | grid | hero | carousel | split`), and `visibility` (desktop/tablet/mobile).
- **Page Renderer** (`dynamic-renderer/page-renderer/page-renderer.service.ts`) delegates to the Section Engine.
- **Widget Host** (`dynamic-renderer/widget-host/widget-host.service.ts`) resolves each widget's component via the **Widget Manifest** (`widgets/registry/widget-manifest.service.ts`, `widgets/contracts/widget-manifest.contract.ts`) and its data via the **Data Source Resolver** (`widgets/resolvers/data-source-resolver.service.ts`), which delegates to `CategoryFacade`/`ProductFacade` — widgets never call APIs directly.
- Widgets receive only `{ section config, resolved data }` as inputs; they render presentation only, never fetch or mutate.
- Unknown/unregistered widget types render a safe fallback; this is also surfaced in `features/diagnostics` (dev-only, route `/__diagnostics`).
- `dynamic-page-layout.component.ts` (`layouts/containers/`) is the top-level container that composes Section Engine output using `PlatformLayoutConfig.type` (`default | sidebar-left | carousel-home | minimal`).
## Theme engine (ADR-008)
- `ThemeConfig` (`shared/models/config/theme.model.ts`): `themeId`, `mode` (`light | dark | system`), `palette` (12 semantic colors), `typography`, `spacing`, `borderRadiusScale`, `shadows`, `iconSet`.
- Applied as CSS custom properties at runtime; components/widgets consume tokens, never hardcoded brand colors.
- Three tenant theme stylesheets live under `src/styles/themes/*.theme.scss` — see `docs/FRONTEND.md` for the CSS custom property convention.
## Feature flags / capability guards (ADR-009)
- `bootstrap.featureFlags` (typed) plus the broader `bootstrap.features` (`MarketplaceFeaturesConfig`) surface for UI-facing toggles (wishlist, compare, reviews, recommendations, search history, etc.).
- Feature resolution falls back across older config surfaces to preserve behavior as the flag model evolved across sprints — see `docs/BOOTSTRAP.md` for the full field list.
## Diagnostics (dev-only)
`features/diagnostics/` (route `/__diagnostics`, excluded from production) validates bootstrap structure (missing fields, unknown widget types, duplicate ids, unknown layout values, missing translations) and runtime health (widget render failures, missing datasources), scored 0-100. Useful when investigating a bootstrap authored by the Project Editor.

157
docs/BACKEND.md Normal file
View File

@@ -0,0 +1,157 @@
# BACKEND
This is the punch list for backend engineers. Every item below is a frontend behavior that is currently **mocked or local-only**, with the exact gap, the endpoint(s) needed, and which frontend files change once the endpoint exists. Read this file instead of diffing the whole repo against `main`.
General contract rules (apply to everything below): tenant is resolved by request host, never a path/query param (`docs/ARCHITECTURE.md`); bootstrap/config responses must never include secrets; existing auth/payment/authorization contracts are frozen (ADR-010) — nothing here changes them.
## 1. Auth — admin authorization gap (highest priority, security-relevant)
**Current frontend behavior:** Admin login and customer login share **one** Telegram QR/session backend (`{authApiUrl}/users/sessions`) and **one** UI component (`TelegramLoginComponent`, `mode: 'customer' | 'admin'`). The only client-side difference is *where the resulting session id is stored*: `webSessionID` cookie (`SameSite=Lax`) for customers vs. `adminSessionID` cookie (`SameSite=Strict`) for admin, via separate `AuthService` / `AdminAuthService`. Admin API calls attach `AdminWebSessionID` via `adminAuthHeadersInterceptor` (`core/admin-auth/admin-auth-headers.interceptor.ts`).
**The gap:** because both flows hit the identical Telegram session endpoint, the backend has **no concept of "this is an admin session"** at the moment the QR is scanned — it's an ordinary Telegram user session, indistinguishable from a customer's. The frontend only decides where to *store* the resulting id; it cannot and does not decide whether that Telegram user is actually allowed to act as an admin. Any Telegram user who completes the QR flow while the admin login UI is showing gets an `adminSessionID`.
**What's needed:** server-side authorization check keyed off the session id (or a dedicated admin-scoped token) at the point admin API calls arrive with `AdminWebSessionID` — reject non-admin users. This must be enforced server-side; nothing on the frontend can substitute for it.
**Frontend files that would change:** `src/app/core/admin-auth/admin-auth.service.ts`, `admin-auth-headers.interceptor.ts`, `admin-auth.guard.ts` — only if the auth response shape changes (e.g. a role claim to check client-side in addition to the server-side enforcement).
**Also prepared, not wired:** `core/admin-auth/ed25519-verification.model.ts` defines `Ed25519VerificationService` (challenge/verify) for a future non-Telegram admin auth path. Current binding (`NoopEd25519VerificationService`, in `app.config.ts`) throws rather than silently accepting — safe to leave wired until a real challenge/verify endpoint exists.
## 2. Bootstrap draft / publish persistence (second-highest priority)
**Current frontend behavior:** The Project Editor (`docs/EDITOR.md`) edits the same `BootstrapConfig` the storefront consumes.
- Load: `GET /bootstrap` (existing, tenant by host).
- Save: in-memory + `localStorage` only (`ProjectEditorDraftStorageService`, key `projectEditor.draftBootstrap.v1`, scoped by `tenant.id`). Reload in another browser/tab and it's gone.
- Publish: applies the bootstrap in-memory via `PlatformRuntimeService.reloadFromBootstrap()` and flips a local `status` flag. **No backend call.**
**Endpoints needed** (none exist yet; shapes are contracts, adjust to house conventions):
```
GET /builder/bootstrap/draft
-> tenant's current draft BootstrapConfig. 404/empty if none (draft = published).
PUT /builder/bootstrap/draft
body: BootstrapConfig
-> persists the draft for this tenant. Does not affect GET /bootstrap (storefront-facing).
POST /builder/bootstrap/publish
body: BootstrapConfig (or none, if publish always promotes the stored draft)
-> validates, then makes this BootstrapConfig what GET /bootstrap returns. Only endpoint
that affects the live storefront.
POST /builder/bootstrap/validate (optional)
body: BootstrapConfig
-> { code: string, message: string }[], mirroring ProjectValidationIssue shape client-side.
```
Tenant identity: resolved by request host, same as every other endpoint — no `projectId` path param. One domain = one tenant = one draft = one published bootstrap.
**Don't duplicate client validation, enforce it server-side too.** `ProjectValidator` (`features/project-editor/services/project-validator.service.ts`) blocks Publish client-side on: missing `branding.logoUrl`, empty `localization.supportedLocales`, invalid `tenant.websiteBaseUrl` (must be `http(s)://...`), duplicate static-page identifiers (slug, falling back to `route`), empty homepage sections, a homepage widget with no `type`, duplicate header nav links, non-hex `theme.palette` values, missing translations for a supported locale, and unknown layout/section-layout enum values. A malicious or stale client can bypass all of this — if `POST /builder/bootstrap/publish` is a trust boundary, re-run equivalent checks server-side.
**Frontend files that would change:** `features/project-editor/facade/project-editor.facade.ts` (replace local save/publish with the new endpoints, keep the same public method signatures), `services/project-editor-draft-storage.service.ts` (becomes a fallback/offline cache rather than primary store).
## 3. Static pages / `slug` vs `route` inconsistency
**Gap:** `StaticPageConfig` requires `slug: string`, but at least one real bootstrap fixture (`src/assets/mock/bootstrap/bootstrap.json`) only populates `route` (e.g. `/about-us`) and leaves `slug` undefined. The frontend's duplicate-detection was patched to fall back to `route` when `slug` is empty, but the underlying data inconsistency remains.
**Needed:** decide whether `slug` is backend-required or auto-derived from `route`, so both sides agree on one source of truth going forward.
**Frontend files:** `shared/models/config/static-page.model.ts`, `features/content-management/services/*` (`ContentPageService` normalization), `features/project-editor/services/project-validator.service.ts`.
## 4. Static pages / CMS persistence
**Current frontend behavior:** `staticPages` are edited client-side in the Project Editor (create/delete page, toggle footer/header/sitemap visibility, edit slug/icon/order/translations/HTML) and only ever written into the in-memory/`localStorage` bootstrap draft above (item 2) — no dedicated backend for CMS content exists.
**Endpoints needed:**
```
GET /builder/content-pages
PUT /builder/content-pages
POST /builder/content-pages/import
GET /builder/content-pages/export
POST /builder/content-pages/validate
```
Backend must also support content moderation/validation on publish (disallow dangerous tags/attributes) and revision history — the frontend only sanitizes at render time, not at authoring time (`MarketplaceHtmlEditorComponent` emits raw HTML with no sanitization by design; sanitization is a storefront-render concern, not an authoring concern).
**Frontend files:** `features/content-management/facade/*`, `services/*`, `components/html-editor/*`, `features/project-editor/sections/footer-section.component.ts` (static page list editing today lives partly here too).
## 5. Categories
**Current behavior:** `GET /category` (existing) mapped through `CategoryDto -> CategoryMapper -> Category` domain model, exposed via `CategoryFacade`. No editor/CRUD surface — `admin/categories` is a routing placeholder (`BackofficeComingSoonPageComponent`).
**Gap:** no admin write path (create/update/delete/reorder categories) exists anywhere in this codebase.
**Needed:** category CRUD endpoints and, ideally, a bulk reorder/visibility endpoint (category `priority`/`visible` are already read fields — see `docs/BOOTSTRAP.md`/domain report history).
**Frontend files:** would need a new `features/admin/categories/` module mirroring `features/admin/products/` (gateway interface + local/API gateway + facade + pages), plus wiring `/:lang/backoffice/categories` off the current coming-soon placeholder in `app.routes.ts`.
## 6. Products
**Current behavior:** `features/admin/products/` is fully built (list + editor pages) against `AdminProductsLocalGateway` (swappable via an injection token, same pattern as everywhere else) — i.e. it's ready for a real API gateway, but one has never been implemented.
**Needed:** product CRUD endpoints matching the existing storefront product contract (`itemID`, `name`, `price`, `currency`, `categoryID`, `visible`, `discount`, `images`, `badges`, `media`, `specificationGroups`, `variantOptions`, `relatedCollections` — see the Product Engagement / Product Experience 2.0 fields folded from prior sprint reports).
**Frontend files:** implement `AdminProductsApiGateway` alongside the existing `AdminProductsLocalGateway` and rebind the injection token — `features/admin/products/pages/*` and the facade do not change.
## 7. Orders / revenue (does not exist at all)
**Current behavior:** no backend or local data model for orders or revenue exists anywhere in the codebase. `features/backoffice/orders` is an empty placeholder. The Admin Dashboard's Orders and Revenue cards intentionally render a `pending-backend` state ("Awaiting backend integration") rather than fabricated numbers or a generic empty state.
**Needed:** an order domain (creation, lifecycle, line items, totals) and revenue aggregation, plus endpoints to back a dashboard summary (see item 8) and an admin orders list/detail UI.
**Frontend files:** `features/admin/dashboard/facade/admin-dashboard.facade.ts` (card status computation), a new `features/admin/orders/` module once the domain exists.
## 8. Dashboard metrics
**Current behavior:** `AdminDashboardMetricsGateway` (token `ADMIN_DASHBOARD_METRICS_GATEWAY`) defaults to `AdminDashboardMetricsLocalGateway`, which composes `BackofficeDataService.loadCategories()/loadProducts()` client-side into counts. Everything else on the dashboard (marketplace status, theme, languages, last publish/save, bootstrap version, active layout, enabled widgets, system health) is derived from `ProjectEditorFacade` state, not a metrics endpoint.
**Needed:** a `/builder/dashboard/summary`-style endpoint returning real-time counts and trend deltas.
**Frontend files:** implement `AdminDashboardMetricsGateway` (real API version) and rebind the token in DI config — `AdminDashboardFacade` and all dashboard card components are unaffected (`docs/ADMIN.md` has the full architecture).
## 9. Recent Activity (admin dashboard)
**Current behavior:** `AdminDashboardHistoryService` is `localStorage`-backed, scoped per tenant (`adminDashboard.activityHistory.v1`) — same limitation pattern as the draft storage in item 2. It will never show another editor's activity.
**Needed:** a real audit-log endpoint (who changed what, when) that multiple admin sessions can read.
**Frontend files:** `features/admin/dashboard/services/admin-dashboard-history.service.ts`, `facade/admin-dashboard.facade.ts`.
## 10. Translations
**Current behavior:** all UI strings are static, compiled into the three locale files (`src/app/i18n/{en,ru,hy}.ts`) via the `Translations` interface (`translations.ts`) and served by `TranslateService`/`TranslatePipe`. There is no backend-editable translation surface — bootstrap-level translatable content (nav labels, static-page translations, footer copyright) is edited per-tenant through the Project Editor and stored in the bootstrap itself (see item 2), which is separate from the compiled UI-chrome strings.
**Gap:** UI-chrome strings (button labels, section titles, validation messages) require a frontend deploy to change or add a locale — there's no backend-driven UI-string catalog. This may be acceptable (frontend chrome vs. tenant content are different concerns) but is worth an explicit decision if a backend team expects to control all copy.
**Frontend files:** `src/app/i18n/*` (if this becomes backend-driven, it's a new i18n loading mechanism, a larger change than any other item here).
## 11. Search / autocomplete / trending (future-ready, not urgent)
**Current behavior:** in-memory products/categories/tags power autocomplete and suggestions (`features/search/services/search-autocomplete.service.ts`); trending returns `null` (UI hides gracefully); search history is `LocalSearchHistoryRepository` (guest) with a `BackendSearchHistoryRepository` placeholder already defined but unimplemented.
**Endpoints that would let this go live without frontend changes:**
```
GET /search/suggestions?q={term}
GET /catalog/filters?category={id}&q={term}
```
Plus, if authenticated history/wishlist/compare/saved-searches sync is wanted: `GET/POST/DELETE /me/wishlist`, `/me/compare`, `/me/saved-searches`, `/me/recently-viewed`.
**Frontend files:** `features/search/services/search-autocomplete.service.ts`, `search-trending.service.ts`, `search-history.repository.ts` (swap `LocalSearchHistoryRepository` for `BackendSearchHistoryRepository`) — facade/UI unaffected by design.
## 12. Product engagement (rating/reviews/questions) — partially real, confirm contract
**Current behavior:** frontend already expects these endpoints and renders against them: `GET /products/{id}/rating`, `GET /products/{id}/reviews?page&pageSize`, `GET /products/{id}/questions?page&pageSize`, `POST /products/{id}/reviews` (rating/title/text/anonymous), `POST /products/{id}/questions` (text/anonymous). If these already exist, this is a contract-confirmation item, not new work; if not, they block the Reviews/Questions UI (`productPage.reviews`/`questions` config, already toggleable per tenant).
**Frontend files:** none, if the contract matches what's documented in `docs/BOOTSTRAP.md`'s Product Engagement notes.
---
## Known reliability issues
### Production 502/504 Bad Gateway on refresh / back-navigation
**Symptom:** production hits intermittent `502`/`504 Bad Gateway` responses specifically on page refresh and browser back-navigation.
**Root cause (investigation so far):** `environment.production.ts` points the frontend at the backend API via **absolute URLs directly** (`apiUrl: 'https://api.dexarmarket.ru:445'`, `authApiUrl: 'https://users.vitanova.network:456'`), bypassing this repo's `nginx.conf` entirely — that config only proxies `/api` for the `lovero.store` tenant, not `dexarmarket.ru`. So the 502/504 originates from **that backend API's own reverse proxy** (ports 445/456, a separate server not in this repo), not from anything this repo controls.
Refresh and back-navigation both re-fire session-check and bootstrap-load calls on mount (`AdminAuthService.checkSession()`, `ConfigService.loadBootstrap()`, `TelegramSessionApiService`), which is the likely trigger if that backend's app server or reverse proxy is crashing, overloaded, or misconfigured on those specific endpoints.
**This is not fixable from this repo.** It needs DevOps/backend investigation on the reverse proxy and app server fronting `api.dexarmarket.ru:445` and `users.vitanova.network:456` — check upstream health, timeout settings, and concurrent-connection handling around session-check and bootstrap endpoints.

153
docs/BOOTSTRAP.md Normal file
View File

@@ -0,0 +1,153 @@
# BOOTSTRAP
The `BootstrapConfig` model (`src/app/shared/models/config/bootstrap-config.model.ts`) is the single JSON contract that drives the entire storefront for a tenant. `ConfigService` loads it once at startup via `GET /bootstrap` (tenant resolved server-side by request host); `PlatformRuntimeService` applies it and can re-apply an edited in-memory copy for preview.
## Top-level shape
```ts
interface BootstrapConfig {
schemaVersion: string;
generatedAt: string;
tenant: TenantConfig; // required
branding: BrandingConfig; // required
theme: ThemeConfig; // required
company: CompanyConfig; // required
featureFlags: FeatureFlagsConfig; // required
features?: MarketplaceFeaturesConfig; // optional, centralized feature toggles
apiEndpoints: ApiEndpointsConfig; // required
localization: LocalizationConfig; // required
seo: SeoConfig; // required
permissions: PermissionsConfig; // required
header?: HeaderConfig;
catalog?: CatalogConfig;
layout?: PlatformLayoutConfig;
navigation: NavigationConfig; // required
footer?: FooterConfig;
productPage?: ProductPageConfig;
userExperience?: UserExperienceConfig;
pages: PageConfig[]; // required
staticPages?: StaticPagesConfig;
widgetRegistry?: WidgetRegistryConfig;
}
```
## Field-by-field
| Key | Meaning |
|---|---|
| `schemaVersion` | Contract version. Breaking changes require a bump; frontend must stay compatible within a minor line. |
| `generatedAt` | Payload generation timestamp. |
| `tenant` | `id`, `slug`, `host`, `name`, `defaultLocale`, `supportedLocales`, `defaultCurrency`, `websiteBaseUrl`, etc. Tenant is resolved by domain only — never re-derived on the frontend. |
| `branding` | `logoUrl`, `logoCompactUrl`, `faviconUrl`, `brandName`. |
| `theme` | `themeId`, `mode` (`light\|dark\|system`), `palette` (12 semantic colors), `typography`, `spacing`, `borderRadiusScale`, `shadows`, `iconSet`. See `docs/ARCHITECTURE.md` theme engine section. |
| `company` | Legal/contact info used in the footer: `companyName`, `address`, `contacts.phone`/`email`. |
| `featureFlags` | Simple boolean toggles: `wishlist`, `compare`, `reviews`, `comments`, `recommendations`, etc. |
| `features` | Newer centralized feature surface (`MarketplaceFeaturesConfig`) — wishlist/compare/reviews/comments/questions/recommendations/recentlyViewed/searchHistory/recentlySearched/ratings/share/brands/manufacturers/availability/discounts/badges. Resolvers fall back to `featureFlags`/`productPage`/`userExperience`/`catalog` for older bootstraps. |
| `apiEndpoints` | Public endpoint map the frontend's API layer reads (base URLs, paths, timeouts). Never contains secrets. |
| `localization` | `defaultLocale`, `supportedLocales`, optional `currencyByLocale`. |
| `seo` | `default.title`/`default.description` plus per-page SEO overrides. |
| `permissions` | Roles/permissions for admin surfaces (currently minimal; see `docs/BACKEND.md`). |
| `header` | Boolean toggles: `showLogo`, `showSearch`, `showCategories`, `showLanguages`, `showCart`, `showProfile`, `showWishlist`, `showCompare`, `showRegion`. |
| `catalog` | UI/feature config only (no product data) — see Catalog Config below. |
| `layout` | `PlatformLayoutConfig`: `{ type: 'default'|'sidebar-left'|'carousel-home'|'minimal', options?: Record<string, unknown> }`. Global page-chrome mode. |
| `navigation` | `header[]` / `footer[]` link arrays: `id`, label (translatable), `route`, `order`, `visible`. |
| `footer` | Payment icons, social links, copyright (per-locale), static-page references. |
| `productPage` | Feature config only for the product detail page — rating/reviews/questions/tabs/relatedProducts/actions enablement, pagination size, mode. No review/question *data* lives here. |
| `userExperience` | Feature config only for wishlist/compare/recentlyViewed/share/savedSearches — flags and limits, never user-specific lists. |
| `pages` | Array of `PageConfig`: `id`, `key`, `route.path`, `sections: SectionConfig[]`. |
| `staticPages` | CMS-style informational/legal pages — see `staticPages` below. |
| `widgetRegistry` | Pointer/metadata for the widget manifest (see `docs/ARCHITECTURE.md` widget engine). |
### Section config (`shared/models/config/section.model.ts`)
```ts
type SectionLayoutStrategy = 'stack' | 'grid' | 'hero' | 'carousel' | 'split';
interface SectionConfig {
id: string; type: string; order: number;
layout?: { strategy?: SectionLayoutStrategy; columns?: number; gap?: string; align?: 'start'|'center'|'end'|'stretch' };
visibility?: { desktop?: boolean; tablet?: boolean; mobile?: boolean };
widgets: WidgetConfig[];
featureFlag?: string;
visible?: boolean;
}
```
### Widget config (`shared/models/config/widget.model.ts`)
Each widget has `id`, `type`, `version`, optional `title`/`subtitle`/`order`/`padding`/`visibility`/`animation`/`style`/`permissions`/`actions`/`featureFlag`/`visible`, and `props: Record<string, unknown>` (widget-specific). Typed editors exist in the Project Editor for `hero`, `categories`, `product-collection`; everything else edits `props` as raw JSON (see `docs/EDITOR.md`).
### Catalog Config (`shared/models/config/catalog-config.model.ts`)
```ts
type CatalogLayoutModeConfig = 'grid'|'large-grid'|'compact-grid'|'grid-2'|'grid-3'|'grid-4'|'compact'|'list';
type CatalogNavigationModeConfig = 'default'|'left-category-navigation'|'mega-category-layout'|'top-category-carousel';
type CatalogLoadingStrategy = 'pagination'|'loadMore'|'infiniteScroll';
```
Plus `defaultSort`/`availableSorts` (`relevance|latest|price_asc|price_desc|rating|popular|discount`), `enabledFilters: string[]`, and `show*`/`*Enabled` booleans (breadcrumbs, category banner, subcategory chips, ratings, discounts, availability, suggestions, search history). This is feature-configuration only — no product/filter *data* lives in bootstrap.
### `staticPages`
Each entry: `id`, `slug`, `title`, `showInHeader`, `showInFooter`, `showInSitemap`, `icon`, `order`, `visibility`, `requiresAuthentication`, `footerGroup`, `translations[locale] = { title, html, seo }`. Rendered dynamically (no hardcoded page list); HTML is sanitized on render. **Known inconsistency:** the model requires `slug`, but the mock bootstrap only populates `route` for some pages — the frontend's duplicate-slug validator falls back to `route` when `slug` is empty (see `docs/BACKEND.md`).
## Representative example (trimmed)
```json
{
"schemaVersion": "2.1.0",
"generatedAt": "2026-07-05T10:30:00Z",
"tenant": {
"id": "tenant-dexar-ru", "slug": "dexar-ru", "host": "dexarmarket.ru",
"name": "Dexar Market", "defaultLocale": "ru", "supportedLocales": ["ru", "en", "hy"],
"defaultCurrency": "RUB", "websiteBaseUrl": "https://dexarmarket.ru"
},
"branding": { "logoUrl": "/assets/brand/logo.svg", "faviconUrl": "/assets/brand/favicon.ico", "brandName": "Dexar Market" },
"theme": {
"themeId": "dexar-light", "mode": "light",
"palette": { "primary": "#2F6E5D", "secondary": "#8FA9A2", "backgroundPrimary": "#FFFFFF", "textPrimary": "#1F322D" },
"typography": { "primaryFontFamily": "DM Sans, sans-serif", "baseFontSize": 16 }
},
"layout": { "type": "default" },
"catalog": { "layout": "grid-4", "navigationMode": "default", "defaultSort": "relevance", "showRatings": true },
"navigation": {
"header": [ { "id": "nav-home", "label": "Home", "route": "/", "order": 1, "visible": true } ],
"footer": [ { "id": "nav-privacy", "label": "Privacy", "route": "/privacy-policy", "order": 1, "visible": true } ]
},
"pages": [
{
"id": "page-home", "key": "home", "route": { "path": "/", "exact": true },
"sections": [
{
"id": "home-hero", "type": "hero", "order": 1,
"layout": { "strategy": "hero" },
"widgets": [ { "id": "w-hero", "type": "hero", "version": "1.0.0", "props": { "title": "Welcome", "layout": "full-bleed" } } ]
},
{
"id": "home-categories", "type": "categories", "order": 2,
"layout": { "strategy": "grid", "columns": 4 },
"widgets": [ { "id": "w-categories", "type": "categories", "version": "1.0.0", "props": { "columns": 4 } } ]
}
]
}
],
"staticPages": [
{ "id": "static-about", "slug": "about", "title": { "en": "About Us" }, "showInFooter": true, "showInHeader": false, "translations": { "en": { "html": "<h1>About Us</h1>" } } }
]
}
```
## How `ConfigService` / `PlatformRuntimeService` consume it
1. `ConfigService.loadBootstrap()` fetches (or, in mock mode, reads local JSON under `src/assets/mock/bootstrap/`) and parses `BootstrapConfig`.
2. `PlatformRuntimeService` applies theme tokens as CSS variables, sets branding, and exposes the parsed pages/navigation/footer/static-pages to the rest of the app.
3. Section Engine / Widget Host render pages from `bootstrap.pages` on route match (see `docs/ARCHITECTURE.md`).
4. `PlatformRuntimeService.reloadFromBootstrap(next)` re-applies an entire new `BootstrapConfig` in-memory — this is what the Project Editor's Publish action (and Preview) use, without a full browser reload.
## How the Project Editor edits it, and draft/publish/preview
The Project Editor (`docs/EDITOR.md`) edits an in-memory copy of the exact same `BootstrapConfig` — there is no parallel editor model or DTO translation layer. Today:
- **Load**: `GET /bootstrap` (same endpoint the storefront uses).
- **Save**: in-memory snapshot only, persisted to `localStorage` as a draft (`projectEditor.draftBootstrap.v1`, scoped by `tenant.id`) so it survives reloads on the same browser.
- **Publish**: runs `ProjectValidator`, then calls `PlatformRuntimeService.reloadFromBootstrap()` for live in-memory preview and flips a local `status` flag — **no backend call happens**. This is the largest gap covered in `docs/BACKEND.md`.
- **Preview**: same in-memory re-apply mechanism, without marking the state published.

View File

@@ -1,90 +0,0 @@
# Backend Handoff — Sprint 16 (Project Editor)
For backend devs picking up work after the Sprint 16 frontend editor. Frontend
is done; this documents what backend still needs to build for the editor to
be real (not just an in-browser demo).
## What exists today (frontend-only)
The Project Editor (`/edit/:section`, or `/{lang}/edit/:section`) edits the
same `BootstrapConfig` the storefront consumes — no parallel model. Today:
- **Load:** `GET /bootstrap` (existing, tenant resolved by request host).
- **Save:** in-memory only. `ProjectEditorFacade.save()` just snapshots the
current draft as "last saved" in the browser tab. Nothing is persisted.
Reload the page, or open the editor in another tab/browser, and it's gone.
- **Publish:** `ProjectEditorFacade.publish()` applies the bootstrap
in-memory via `PlatformRuntimeService.reloadFromBootstrap` (for live
preview) and flips a local `status` flag to `'published'`. It does not
call any backend endpoint. Nothing is persisted.
This is fine for demoing the editor UI to one person in one browser tab. It
is not usable as a real per-tenant admin panel yet — that's this handoff.
## Endpoints backend needs to add
None of these exist yet. Suggested shapes (adjust to match your existing API
conventions — these are contracts, not prescriptions):
```
GET /builder/bootstrap/draft
-> returns the tenant's current draft BootstrapConfig (may differ from
the published one). 404/empty if no draft exists yet (draft = published).
PUT /builder/bootstrap/draft
body: BootstrapConfig
-> persists the draft for this tenant. Does not affect what GET /bootstrap
(storefront-facing) returns.
POST /builder/bootstrap/publish
body: BootstrapConfig (or no body, if publish always promotes the
current stored draft)
-> validates, then makes this BootstrapConfig the one GET /bootstrap
returns for this tenant. This is the only endpoint that affects the
live storefront.
POST /builder/bootstrap/validate (optional — validation already runs
client-side via ProjectValidator, but a
server-side check prevents a stale/
bypassed client from publishing garbage)
body: BootstrapConfig
-> returns the same shape as the client's ProjectValidationIssue[]:
{ code: string, message: string }[]
```
Tenant identity: same as every other endpoint in this platform — resolved by
request host, not a `projectId` path param (see `docs/backend-platform/
tenant-resolution.md`). There is no multi-project-per-domain concept; each
domain is one tenant with one draft and one published bootstrap.
## What the frontend already validates (don't duplicate logic, just enforce it)
`ProjectValidator` (`src/app/features/project-editor/services/
project-validator.service.ts`) blocks Publish client-side on:
missing `branding.logoUrl`, empty `localization.supportedLocales`, invalid
`tenant.websiteBaseUrl` (must be `http(s)://...`), duplicate static-page
identifiers (slug, falling back to route), empty homepage sections, a
homepage widget with no `type`, duplicate header nav links, and any
non-hex-string `theme.palette` value. A malicious or buggy client could
bypass all of this — if `POST /builder/bootstrap/publish` is meant to be a
trust boundary, re-run equivalent checks server-side before accepting.
## Static pages: `slug` vs `route`
Heads up for whoever owns `StaticPageConfig`: the model requires `slug:
string`, but at least one real bootstrap in this repo
(`src/assets/mock/bootstrap/bootstrap.json`) only populates `route` (e.g.
`/about-us`) and leaves `slug` undefined. The frontend's duplicate-detection
was patched to fall back to `route` when `slug` is empty
(`project-validator.service.ts`), but the underlying data inconsistency is
still there. Worth deciding whether `slug` should be backend-required/
auto-derived from `route` going forward, so both frontend and backend agree
on one source of truth.
## Not in scope for this handoff (already tracked separately)
- Separate admin app/deployment (`admin.<domain>` subdomain) — storefront and
editor still ship in one Angular build today.
- A pre-existing, unrelated crash in `ContentPageService.normalizeSlug`
against legacy-shaped static-page fixture data (frontend bug, not a
backend concern).

View File

@@ -1,357 +0,0 @@
# Catalog Module Report
## Scope
Sprint 5 added a Catalog Module on the frozen platform architecture. No backend APIs, authentication, payment, or bootstrap contracts were changed.
The catalog uses existing domain boundaries:
- Category data: `CategoryFacade` -> `CategoryService` -> Category Repository -> existing `GET /category`
- Product data: `ProductFacade` -> `ProductDataService` -> Product Provider -> existing product/category item endpoints
## Implemented Module
### Catalog Container
- `src/app/features/website/catalog/containers/catalog-container.component.ts`
- `src/app/features/website/catalog/containers/catalog-container.component.html`
- `src/app/features/website/catalog/containers/catalog-container.component.scss`
Responsibilities implemented:
- Reads the route category id.
- Requests category data through `CategoryFacade` only.
- Requests product data through `ProductFacade` only.
- Determines whether the current category has child categories.
- Renders category grid when child categories exist.
- Renders product grid when no child categories exist.
- Supports root catalog entry with root categories.
- Handles loading, empty, and error states.
- Cancels prior category/product data subscriptions when the route changes.
No `HttpClient`, backend DTO, auth, payment, bootstrap, or tenant-specific logic is used in the container.
### Category Grid
- `src/app/features/website/catalog/components/category-grid/category-grid.component.ts`
- `src/app/features/website/catalog/components/category-grid/category-grid.component.html`
- `src/app/features/website/catalog/components/category-grid/category-grid.component.scss`
Reusable category grid implemented with:
- Input: `Category[]`
- Output: selected `Category`
- Responsive grid layout
- Domain model only
- No data fetching
- No backend DTOs
### Product Grid
- `src/app/features/website/catalog/components/product-grid/product-grid.component.ts`
- `src/app/features/website/catalog/components/product-grid/product-grid.component.html`
- `src/app/features/website/catalog/components/product-grid/product-grid.component.scss`
Reusable product grid implemented with:
- Input: `Product[]`
- Output: selected `Product`
- Output: add-to-cart payload
- Output: product preview id
- Responsive grid layout
- Uses existing reusable product card
- No `HttpClient`
- No backend DTOs
### Product Card Compatibility
- `src/app/components/product-card/product-card.component.ts`
- `src/app/components/product-card/product-card.component.html`
Updated the reusable product card to depend on the Product Domain type and added an explicit selected output.
The product card remains input/output-only and does not use services, storage, `HttpClient`, or environment configuration. It displays image, title, price, discount, badges, and stock.
### Catalog State
- `src/app/features/website/catalog/models/catalog-state.model.ts`
Prepared future state architecture for:
- Category
- Search
- Sort
- Price range
- Attributes
- Pagination
- Filters
Backend filtering was intentionally not implemented in this sprint.
## Navigation
Updated routes in `src/app/app.routes.ts`:
- `/catalog`
- `/catalog/:id`
Both routes load the same catalog container. Legacy category URLs redirect to the catalog route:
- `/category/:id` -> `/catalog/:id`
- `/category/:id/items` -> `/catalog/:id`
Home category links now point to `/catalog/:id`.
## Unlimited Category Depth
Unlimited nesting is supported by the Category Domain tree utilities from Sprint 4. The catalog container does not assume a fixed depth. For any category id, it asks `CategoryFacade.getChildren(categoryId)`:
- if children exist, it renders the category grid
- if no children exist, it loads the product grid
This same decision repeats for every category route depth.
## Localization
Added catalog translations in:
- `src/app/i18n/en.ts`
- `src/app/i18n/ru.ts`
- `src/app/i18n/hy.ts`
- `src/app/i18n/translations.ts`
## Validation
Completed checks:
- Unlimited category depth is supported through facade child lookup and recursive category domain tree utilities.
- Product grid is reusable and consumes `Product[]`.
- Category grid is reusable and consumes `Category[]`.
- Catalog components use domain models only.
- Catalog data requests go through facades only.
- DTOs remain isolated outside the catalog module.
- Catalog module has no `HttpClient` usage.
- Product card has no services, storage, `HttpClient`, or environment usage.
- Authentication was not modified.
- Payment was not modified.
- Bootstrap contracts were not modified.
- Backend APIs were not modified.
Build validation passed:
```bash
npm run build
```
## Stop Point
Catalog Module implementation is complete for Sprint 5. Stop here for approval before starting the next module or any Builder/Backoffice work.
## Sprint 10.2 Catalog UX Polish
Sprint 10.2 improves catalog UX and responsiveness without changing facades, business logic, bootstrap flow, runtime architecture, routing, authentication, or payment.
### Empty State Behavior
Two separate states are now rendered in the catalog container:
- Empty category state (`rawProducts.length === 0`):
- hides filter/sort/layout/result controls
- shows dedicated empty category component with icon, category context, friendly message, and "Browse Categories" action
- Filtered empty state (`rawProducts.length > 0 && products.length === 0`):
- shows "no filter match" message
- provides "Clear Filters" action
- keeps filter access available (sidebar on desktop, drawer trigger on tablet/mobile)
### Mobile Filter Drawer
- Desktop keeps visible sticky sidebar filters.
- Tablet and mobile switch to a drawer-based filter UI.
- Drawer includes filter groups, Reset, and Apply actions.
- Apply closes the drawer.
- Accessibility:
- drawer uses dialog semantics (`role="dialog"`, `aria-modal="true"`)
- focus trap is enabled while drawer is open
- `Esc` closes the drawer
### Mobile Sort
- Desktop keeps dropdown sort control.
- Tablet keeps compact dropdown with drawer-based filters.
- Mobile opens a bottom-sheet sort modal.
- Supported mobile sort options:
- Recommended
- Newest
- Price Low -> High
- Price High -> Low
- Rating
- Popularity
### Responsive Grid Modes and Toolbar
- Grid selector uses icon buttons and keeps active-state highlighting.
- Mobile sticky toolbar added with quick actions:
- Filters
- Sort
- Grid cycle
- Grid cycle rotates through supported layouts while preserving existing layout architecture.
### Responsive Spacing and Overflow
Catalog spacing and controls were polished for desktop/tablet/mobile:
- filter/input/button spacing
- sort/reset row behavior (single row on desktop, stacked naturally on mobile)
- card and grid spacing
- search block spacing
- drawer/sheet interaction surfaces
- horizontal overflow prevention
## Sprint UI Polish (Visual Only)
This sprint applies visual and responsive UX polish only. No facade contracts, business logic, API contracts, runtime/bootstrap architecture, or widget contracts were changed.
### Desktop Layout
- Catalog products section uses a cleaner two-column structure with consistent spacing tokens (8/12/16/24/32).
- Filters panel remains sticky on desktop and uses collapsible sections with smooth expand/collapse animation.
- Product cards keep consistent image height/aspect ratio and improved vertical rhythm between image/title/rating/price/actions.
- Product action controls are vertical floating circles in the image top-right zone with fixed spacing and no overlap.
### Tablet Layout
- Sidebar filters transition into drawer interaction for better content width.
- Sort and layout controls retain consistent sizing and spacing.
- Grid/list results avoid horizontal overflow and preserve button/input containment.
### Mobile Layout
- Permanent sidebar is hidden.
- Sticky toolbar provides three entry points: Filters, Sort, Grid.
- Filters open in drawer form with scrollable content and fixed bottom actions.
- Sort and Grid open bottom-sheet style dialogs.
- Focus states and keyboard dismissal (`Esc`) are preserved for all overlays.
### Grid Types
- `grid`
- `large-grid`
- `compact-grid`
- `list`
All grid switch icons are normalized in size and selected state is visually highlighted.
### Filter Drawer and Sections
- Filter groups (Price, Availability, Rating, Brand, etc.) are collapsible.
- Range inputs are stacked vertically (`From`, `To`) with full-width controls and 12px+ spacing.
- Slider remains below price inputs for predictable scan order.
### Product Card Anatomy
- Image area: square ratio, `object-fit: contain`, padded image content.
- Status elements (discount/stock/badges) positioned to avoid action collisions.
- Actions: top-right vertical controls with equal circular dimensions.
- Content: title, optional description, rating, pricing, stock indicator, CTA.
### Animations
- Card hover: subtle elevation + `translateY(-2px)`.
- Button/selector transitions: ~180-200ms.
- Filter group expand/collapse: smooth height/opacity transition.
- Drawer and sheet overlays: subtle slide/fade entrance.
### Empty and Loading States
- Empty results state keeps friendly message and action while hiding non-essential catalog controls when no products are rendered.
- Skeletons for cards/results keep stable heights to reduce layout shift.
### Accessibility Notes
- Added/standardized visible `:focus-visible` outlines for interactive elements.
- Product quick action controls now expose aria labels.
- Modal/drawer interactions continue to use dialog semantics and focus trap.
### Localization and Accessibility
- New strings for empty states, drawer/sheet UI, and toolbar were added to all languages:
- `src/app/i18n/en.ts`
- `src/app/i18n/ru.ts`
- `src/app/i18n/hy.ts`
- `src/app/i18n/translations.ts`
- No hardcoded catalog UX strings were introduced for Sprint 10.2 additions.
## Sprint 11 Search & Discovery Engine
Sprint 11 introduces a reusable, backend-driven Search and Discovery architecture while preserving platform boundaries and existing domain models.
### Search Domain Models
Core search models were added under:
- `src/app/core/search/models/search.model.ts`
- `src/app/core/search/models/search-state.model.ts`
Model coverage includes:
- `SearchQuery`
- `SearchResult`
- `FilterGroup`
- `FilterOption`
- `SortOption`
- `SearchSuggestion`
- `SearchHistory`
- `SearchState`
### Search Entry Point
- `src/app/facades/platform/search.facade.ts`
`SearchFacade` is now the search orchestration entry point for the catalog UX and provides:
- backend catalog loading bridge for search query payloads
- metadata-driven sort option generation
- metadata-driven dynamic filter group generation
- live suggestions generation
- in-memory filter metadata memoization
- reusable filtering, sorting, and pagination helpers
- query param serialization/deserialization for URL synchronization
### History Service
- `src/app/core/search/services/search-history.service.ts`
Search history moved to a reusable core service with:
- recent search tracking
- popular search support
- clear/reset support
### Catalog UI Integration
Catalog UI now consumes Search domain metadata and state:
- `src/app/features/website/catalog/containers/catalog-container.component.ts`
- `src/app/features/website/catalog/components/search-box/search-box.component.ts`
- `src/app/features/website/catalog/components/filters-panel/filters-panel.component.ts`
- `src/app/features/website/catalog/components/sorting-control/sorting-control.component.ts`
Implemented behaviors:
- live suggestions
- recent + popular searches
- keyboard navigation in search box (up/down/enter/escape)
- clear search action
- dynamic filters for checkbox, radio, toggle, range, slider, color, size, rating, availability
- URL query synchronization with `SearchState`
- page reload restore from query params
### Validation
Completed validation for Sprint 11 integration:
- `npm run build` passes successfully
- facades remain the UI data boundary
- no authentication changes
- no payment changes
- no bootstrap/runtime contract changes

View File

@@ -1,104 +0,0 @@
# Catalog UX, Navigation and Loading Strategies - Sprint 16
## Scope
Sprint 16 improves catalog UX without introducing marketplace-specific logic.
Areas covered:
- empty category behavior
- root navigation consistency
- multiple loading strategies
- grid selector completion
- mobile catalog behavior
- future slug routing preparation
- reusable catalog states
- centralized feature flags
- project skills documentation
## Empty Category Behavior
Catalog now distinguishes three category outcomes:
- subcategories exist: show category browser
- products exist: show product list
- neither exist: show dedicated catalog empty state
Empty category state belongs to catalog surface, not product grid.
## Root Navigation
`All Categories` always routes to `/catalog` and shows category browser.
Continue-browsing restoration no longer hijacks this root navigation path.
## Loading Strategies
Configured via `catalog.loadingStrategy`:
- `pagination`
- `loadMore`
- `infiniteScroll`
Single product list component remains source of truth. Strategy changes only affect controls and page-windowing.
## Grid System
Supported layouts:
- `grid-2`
- `grid-3`
- `grid-4`
- `list`
- `compact`
User preference persists locally. Bootstrap default still seeds first render.
Legacy layout aliases normalize to new modes for backward compatibility.
## Mobile Behavior
Mobile catalog uses:
- filter drawer
- sort popup sheet
- grid popup sheet
Inline filter density is avoided.
## Breadcrumb and Slug Preparation
Current URLs remain ID-based.
Routing layer now tolerates future slug-like category tokens by resolving them to internal IDs without changing current public contract.
## Centralized Features
`bootstrap.features` is new central toggle surface for UI features such as:
- wishlist
- compare
- reviews
- comments
- questions
- recommendations
- recentlyViewed
- searchHistory
- recentlySearched
- ratings
- share
- brands
- manufacturers
- availability
- discounts
- badges
Feature resolver falls back to older config surfaces to preserve behavior.
## Project Skills
Added repo skills:
- `.agents/skills/marketplace-architecture/SKILL.md`
- `.agents/skills/ui-standards/SKILL.md`
- `.agents/skills/backend-contract/SKILL.md`
## Future Work
- true backend paging for load-more/infinite strategies
- offline-aware cached catalog data
- explicit slug field on categories
- admin editing surface for centralized feature toggles

View File

@@ -1,155 +0,0 @@
# Category Domain Report
## Scope
Sprint 4 added a complete Category Domain on top of the existing backend API contract. Backend endpoints and payload names were not changed.
Existing backend category fields remain isolated as DTO input:
- `categoryID`
- `parentID`
- `name`
- `icon`
- `priority`
- `visible`
- `categoriesCount`
- `itemCount`
- `names[]`
The UI now consumes category domain models rather than backend-shaped category responses.
## Implemented Files
### DTO
- `src/app/core/categories/dto/category.dto.ts`
Defines `CategoryDto` and `CategoryNameDto` for existing backend category payloads. Compatibility fields for current mock/API variants are accepted only at the DTO boundary.
### Domain Model
- `src/app/core/categories/models/category-domain.model.ts`
Frontend category model exposes:
- `id`
- `parentId`
- `title`
- `icon`
- `priority`
- `visible`
- `itemCount`
- `children[]`
- `translations`
No backend category naming is required by category UI consumers.
### Mapper
- `src/app/core/categories/mappers/category.mapper.ts`
Maps backend DTOs into domain categories, including:
- backend id normalization
- parent id normalization
- title fallback selection
- `names[]` to `translations`
- nested DTO flattening
- visible-category filtering
- priority sorting
- duplicate id de-duplication
### Tree Utilities
- `src/app/core/categories/utils/category-tree.utils.ts`
Supports:
- flat list to tree
- unlimited nesting
- parent lookup
- children lookup
- breadcrumb generation
- leaf detection
- tree flattening for future lazy-loading compatibility
### Repository Abstraction
- `src/app/core/categories/repositories/category.repository.ts`
- `src/app/core/categories/repositories/api-category.repository.ts`
- `src/app/core/categories/category-repository.token.ts`
`CategoryRepository` returns DTOs from the existing `GET /category` API. The injection token uses the existing runtime provider strategy and remains compatible with both mock and API modes. Mock mode continues to work through the existing mock-data interceptor.
### Category Service
- `src/app/core/categories/category.service.ts`
Converts repository DTOs through the mapper and exposes domain methods:
- all categories
- category tree
- root categories
- category by id
- children
- parent
- breadcrumb
- leaf detection
### Category Facade
- `src/app/facades/platform/category.facade.ts`
Exposes observable streams and state for:
- all categories
- category tree
- root categories
- category by id
- selected category
- breadcrumb
- children
### Product Compatibility
- `src/app/core/products/models/product-domain.model.ts`
- `src/app/core/products/providers/api-product-data.provider.ts`
`ProductFacade.getCategories()` now resolves through `CategoryService`, so compatibility category access also returns the new category domain model.
## UI Migration
Updated category-facing UI consumers:
- `src/app/pages/home/home.component.ts`
- `src/app/pages/home/home.component.html`
- `src/app/pages/category/subcategories.component.ts`
- `src/app/pages/category/subcategories.component.html`
- `src/app/pages/category/category.component.ts`
The home page and subcategory page now consume `CategoryFacade` and `Category` domain models. Category route item loading still uses the existing product facade for product lists, without changing product/payment/auth contracts.
## Validation
Completed checks:
- DTOs are isolated under `core/categories/dto`.
- Category mapper exists and is the only category DTO-to-domain conversion point.
- Category UI uses `CategoryFacade` and category domain models.
- Category backend field names are contained to the category DTO/mapper boundary and compatibility internals.
- Components do not use `HttpClient` for category data.
- No authentication changes were made.
- No payment changes were made.
- No bootstrap contract changes were made.
- No backend API contract changes were made.
- Mock/API compatibility is preserved through the repository token and existing mock interceptor.
Build validation passed:
```bash
npm run build
```
## Stop Point
Category Domain implementation is complete for Sprint 4. Stop here for approval before starting the next domain or any Builder/Backoffice work.

View File

@@ -1,154 +0,0 @@
# Static Pages / CMS - Sprint 14
## Scope
Sprint 14 adds configuration-driven CMS support for unlimited marketplace static pages.
Out of scope:
- backend save endpoints
- marketplace-specific content logic
- hardcoded page names
## Feature Architecture
```text
src/app/features/content-management/
facade/
services/
models/
pages/
components/
```
Main pieces:
- `ContentPageService` normalizes bootstrap static page data
- `ContentManagementFacade` exposes normalized pages and validation helpers
- `StaticPagesEditorComponent` integrates CMS editing into Project Editor
## Bootstrap Structure
Static pages live under `bootstrap.staticPages`.
Supported properties per page:
- `id`
- `slug`
- `title`
- `showInFooter`
- `showInHeader`
- `showInSitemap`
- `icon`
- `order`
- `visibility`
- `requiresAuthentication`
- `footerGroup`
- `translations`
- `html`
- `seo`
## Translation Model
Content can be stored in `translations[locale]`:
- `title`
- `html`
- `seo`
Legacy structures remain supported through normalization.
## Dynamic Routing
Frontend uses dynamic static page resolution instead of manual page registration.
Current route surface:
- `/:lang/:staticPath`
- legacy compatibility: `/:lang/page/:key`
Resolver maps route slug to bootstrap-configured page.
## Header Generation
Pages with `showInHeader: true` render in header navigation.
No hardcoded page list required.
## Footer Generation
Footer can render grouped static pages from bootstrap flags and metadata.
Supported grouping:
- Company
n- Customer
- Legal
- Support
- Social
Social links come from footer bootstrap config.
## Rendering
`StaticPageComponent` now:
- resolves content by slug or id
- reloads content when language changes
- updates document title/meta
- supports RTL-ready `dir` switching
- renders backend HTML safely through sanitization
## Editor Integration
Project Editor now includes `Static Pages` section.
Supported actions:
- create page
- delete page
- enable footer/header/sitemap visibility
- edit slug
- edit icon
- edit order
- edit translations
- edit HTML
## Validation
Current validation prevents:
- duplicate slugs
- empty titles
## Future Backend Endpoints
Recommended future endpoints:
- `GET /builder/content-pages`
- `PUT /builder/content-pages`
- `POST /builder/content-pages/import`
- `GET /builder/content-pages/export`
- `POST /builder/content-pages/validate`
## Example Bootstrap
```json
{
"staticPages": {
"about": {
"id": "about",
"slug": "about",
"title": { "en": "About Us", "ru": "О нас" },
"showInFooter": true,
"showInHeader": true,
"showInSitemap": true,
"icon": "info",
"order": 1,
"requiresAuthentication": false,
"footerGroup": "Company",
"translations": {
"en": {
"title": "About Us",
"html": "<h2>About</h2><p>Company story</p>"
}
},
"seo": {
"title": { "en": "About Us" },
"description": { "en": "About our marketplace" },
"canonical": "/about"
}
}
}
}
```

View File

@@ -1,127 +0,0 @@
# Marketplace Diagnostics & Health Engine - Sprint 14
## Scope
Sprint 14 adds a development-only diagnostics feature for marketplace configuration and runtime health.
Constraints respected:
- No runtime behavior changes
- No business logic changes
- No authentication changes
- No payment changes
## Architecture
```text
src/app/features/diagnostics/
components/
diagnostics-page.component.*
services/
diagnostics-logger.service.ts
models/
diagnostics.model.ts
validators/
bootstrap-diagnostics.validator.ts
runtime-diagnostics.validator.ts
diagnostics-health-score.util.ts
facade/
diagnostics.facade.ts
```
## Health Checks
Current checks cover:
- bootstrap loaded
- tenant resolved
- runtime initialized
- theme loaded
- widget manifest loaded
- section engine reachable
- configuration engine initialized
- translations available
- required assets health
## Bootstrap Validation
Current validator detects:
- missing required properties
- unknown widget types
- duplicate page/section/widget ids
- unknown layout values
- invalid feature flags
- broken page definitions
- invalid navigation targets
- missing translations in navigation label keys
- missing branding media
## Runtime Validation
Current validator detects:
- missing datasource declarations for widgets that support them
- widget rendering failures from runtime diagnostics stream
- broken route expectations
- configuration fallback usage in local development
- failed image loading where DOM can observe it
- missing optional data
## Severity Model
Each entry includes:
- code
- severity
- title
- description
- affected component
- suggested resolution
Levels:
- info
- warning
- error
- critical
## Health Score
Simple weighted score:
- critical: -15
- error: -8
- warning: -3
- info: 0
Minimum 0, maximum 100.
## Developer Page
Development-only route:
- `/__diagnostics`
Page shows:
- summary
- health score
- passed checks
- warnings/errors/critical counts
- detailed diagnostics entries
## Logging Abstraction
`DiagnosticsLoggerService` stores diagnostic entries in memory.
`RuntimeDiagnosticsService` now keeps unknown widget events in memory for diagnostics consumption only.
No external logging integration in this sprint.
## Extension Points
Future additions can plug into:
- new validators under `validators/`
- new runtime event collectors
- remote diagnostics export service
- monitoring integrations (Sentry, Datadog, Grafana, OpenTelemetry)
- asset/network checks
- widget render timing checks
## Performance
Production route excluded.
Diagnostics work runs only on diagnostics page access in development mode.

70
docs/EDITOR.md Normal file
View File

@@ -0,0 +1,70 @@
# 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.

54
docs/FRONTEND.md Normal file
View File

@@ -0,0 +1,54 @@
# FRONTEND
Angular 18+, standalone components throughout (no NgModules). See `docs/PROJECT-STRUCTURE.md` for the full `src/app/**` folder tour and `docs/ARCHITECTURE.md` for the layered container/facade/service pattern.
## App structure at a glance
```
src/app/
core/ domain services, DTOs, mappers, repositories (per domain: categories, products, search, admin-auth)
facades/ cross-feature facades (platform/category.facade.ts, platform/search.facade.ts, ...)
features/ feature modules (project-editor, admin/*, backoffice/*, website/catalog, website/product, diagnostics, content-management, search)
shared/ models/config (BootstrapConfig + ~20 sub-configs), shared UI, utils — feature-agnostic
widgets/ widget contracts, registry/manifest, resolvers, ui components
dynamic-renderer/ section-engine, page-renderer, section-renderer, widget-host
layouts/ page-chrome containers (dynamic-page-layout, header/footer shells)
i18n/ translations.ts (interface), en.ts, ru.ts, hy.ts, translate.pipe.ts, TranslateService
pages/ top-level routed pages (home, cart, category, ...)
components/ reusable standalone components used across features (product-card, telegram-login, ...)
guards/ route guards (admin-auth guard, etc.)
```
## Routing (`app.routes.ts`)
- Locale-prefixed routes: `/:lang/...` (lang from `LanguageService.currentLanguage()`), plus root redirects.
- Storefront: `/`, `/catalog`, `/catalog/:id`, `/product/:id` (legacy `/item/:id` and `/category/:id[/items]` redirect for compatibility).
- Static/CMS pages resolve dynamically: `/:lang/:staticPath` (legacy `/:lang/page/:key` kept for compatibility) — no hardcoded page list, resolved from `bootstrap.staticPages`.
- Project Editor: `/edit/:section` or `/{lang}/edit/:section`.
- Admin/backoffice: `/:lang/backoffice/**`, guarded by `adminAuthGuard` (`core/admin-auth/admin-auth.guard.ts`) — dashboard, products (fully wired), categories/static-pages/transactions/orders/media (routed to `BackofficeComingSoonPageComponent` placeholders pending features). See `docs/ADMIN.md`.
- Dev-only diagnostics: `/__diagnostics` (excluded from production).
## i18n system
- `src/app/i18n/translations.ts` defines the `Translations` interface — the single schema every locale file must satisfy (TypeScript enforces this at compile time: a missing key in any locale is a build error).
- `en.ts`, `ru.ts`, `hy.ts` implement that interface, keyed identically and nested by feature area (`header`, `footer`, `home`, `builder`, `dashboard`, ...).
- `TranslateService` resolves the active locale and exposes translated strings; `TranslatePipe` (`| translate`) is the template-facing API — **never hardcode user-facing strings in templates**, always add a key to all three locale files.
- 3 locales: `en`, `ru`, `hy` (Armenian). `LanguageService` tracks the active locale and drives the `/:lang/` route prefix.
- Adding a new UI string: add the key to the `Translations` interface first, then to `en.ts`/`ru.ts`/`hy.ts` in the same position (see `docs/EDITOR.md` for the pattern used by the field-description work).
## Theming
- 3 tenant theme stylesheets: `src/styles/themes/*.theme.scss`.
- Convention: each theme file defines CSS custom properties (`--color-primary`, `--text-primary`, `--border-color`, etc.) that mirror `ThemeConfig.palette`/`typography`/`shadows`/`borderRadiusScale`; components and widgets consume only these custom properties, never hardcoded hex values (ADR-008).
- `theme.mode` (`light | dark | system`) and the palette are runtime-configurable per tenant via bootstrap and editable via the Project Editor's Theme section (`docs/EDITOR.md`).
## State management
- **Signals-based facades, no NgRx.** Every feature/domain exposes a facade (`ProjectEditorFacade`, `CategoryFacade`, `ProductFacade`, `SearchFacade`, `AdminDashboardFacade`, ...) built on Angular signals (`signal`, `computed`, `effect`), following ADR-007.
- Components inject exactly one facade and read/write through it; no direct service or HTTP access from components (ADR-006).
- Local component state (e.g. draft form values) stays in the component; cross-cutting/shared state lives in the facade.
- Persistence for local-only features (Project Editor drafts, admin dashboard activity history) uses scoped `localStorage` keys behind a dedicated service (`ProjectEditorDraftStorageService`, `AdminDashboardHistoryService`) — never raw `localStorage` calls from components/facades.
## Dynamic widget/section rendering from bootstrap JSON
Full detail in `docs/ARCHITECTURE.md` and `docs/BOOTSTRAP.md`. Summary: `page config (bootstrap.pages) -> Section Engine (order/layout/visibility) -> Page Renderer -> Widget Host (resolves component via Widget Manifest + data via Data Source Resolver) -> widget component (props + resolved data only)`. Nothing in this pipeline calls an API directly except the Data Source Resolver, which delegates to `CategoryFacade`/`ProductFacade`.

View File

@@ -1,92 +0,0 @@
# Marketplace MVP Report
## Scope
Sprint 3 delivered the first client-ready marketplace flow on the frozen architecture. The implemented path is configuration-driven and contract-compatible:
Component -> Facade -> ProductDataService -> Provider -> Mock/API
Builder, Backoffice, admin, database, auth contract redesign, payment contract redesign, routing redesign, and dynamic renderer redesign were intentionally left untouched.
## Completed Flow
### Product Data Layer
- Added product domain contracts and provider interfaces.
- Added `ProductDataService` and `PRODUCT_DATA_PROVIDER` abstraction.
- Implemented API-backed product provider over existing `ApiService`.
- Preserved mock/API switching through the existing mock-data interceptor and runtime provider strategy.
Commit: `05d7542 Add product data domain layer`
### Marketplace Facade Migration
- Added `ProductFacade` as the component-facing product API.
- Migrated visible marketplace product/category reads through the facade.
- Kept cart payment and product review submission on existing API contracts.
Commit: `ae3512a Route marketplace data through product facade`
### Reusable Product Card
- Added reusable, input/output-only product card component.
- Integrated the card into category and search product grids.
- Kept routing, add-to-cart, preview/prefetch, price, stock, discount, rating, and description display reusable.
Commit: `d7d73c2 Add reusable marketplace product card`
### Product Detail
- Product detail continues to support gallery, localized title/description, price, discount, attributes, variants, stock, badges, tags, SEO, reviews, and Q&A.
- Added related products through `ProductFacade.getRelatedProducts()`.
- Rendered related products with the reusable product card.
- Added localized related-products labels for English, Russian, and Armenian.
Commit: `b676cec Add related products to detail page`
### Category Navigation
- Preserved existing language-prefixed category routing.
- Added recursive nested-category lookup so API descendants can route correctly even when they are not repeated as root categories.
- Kept legacy flat-category fallback.
- Added localized category/product count labels and visible mixed-content indicators.
Commit: `3974eef Support nested marketplace categories`
### Search
- Search remains facade/provider/API driven.
- Added short-query handling so queries below the backend threshold show a localized prompt instead of stale results or false empty states.
- Trimmed outgoing queries and cleared stale errors before new searches.
- Fixed the no-results icon path.
Commit: `3f5aa2a Polish marketplace search states`
### Cart Integration
- Preserved existing cart persistence, Telegram auth gate, delivery selection, QR/card payment creation, payment polling, and post-payment email/phone handling.
- Made cart add/update/remove operations variant-aware by product, color, and size.
- Prevented separate variants of the same product from merging or being removed together.
- Payment payload continues to include variant details in item names.
Commit: `01d2b26 Support variant-aware cart lines`
## CMS Readiness
- Hardcoded informational/legal pages remain disabled for backend CMS ownership.
- Header/footer/static route placeholders are marked with `TODO(CMS)` comments.
- Cart legal labels remain non-linked placeholders pending backend-configured legal document links.
## Validation
Each implementation phase was built before commit with:
```bash
npm run build
```
Final validation also passed with the same build command after the Marketplace MVP report was added.
## Current Approval Boundary
Marketplace MVP flow is complete for the requested Sprint 3 scope. Stop here and wait for approval before starting Builder or Backoffice work.

60
docs/PROJECT-STRUCTURE.md Normal file
View File

@@ -0,0 +1,60 @@
# PROJECT STRUCTURE
Folder-by-folder tour of `src/app/**`, then one worked example (the Sprint 19 admin dashboard) followed as a literal file-by-file walk-through, ending with a checklist for adding your own feature.
Standards referenced below are enforced, not suggestions: `docs/architecture/foundation/Folder-Blueprint.md`, `Naming-Conventions.md`, `Dependency-Rules.md`, `Import-Boundary-Matrix.md`.
## Top-level folders
| Folder | What belongs here | Why |
|---|---|---|
| `core/` | Per-domain: DTOs, mappers, domain models, repositories, domain services (e.g. `core/categories/`, `core/products/`, `core/search/`, `core/admin-auth/`). | Isolates backend-shaped data (DTOs) from the rest of the app. Only the mapper inside a domain's `core/<domain>/` folder is allowed to see both DTO and domain model shapes (ADR-003 import boundaries). |
| `facades/` | Cross-feature facades not owned by a single feature, e.g. `facades/platform/category.facade.ts`, `facades/platform/search.facade.ts`. | The only thing components are allowed to inject for data/state (ADR-006/007). Feature-local facades instead live inside that feature's own `facade/` folder (see `features/project-editor/facade/`, `features/admin/dashboard/facade/`). |
| `features/` | One folder per feature/domain: `project-editor/`, `admin/<subfeature>/`, `backoffice/<subfeature>/`, `website/catalog/`, `website/product/`, `search/`, `content-management/`, `diagnostics/`. | Organized by feature, not by file type — a feature's models/services/facade/components/pages all live together (`docs/architecture/foundation/Folder-Blueprint.md`). |
| `shared/` | `shared/models/config/*` (the `BootstrapConfig` and ~20 sub-configs), reusable presentational UI, utils. | Feature-agnostic by contract — `shared/` must never import from `features/` (Import-Boundary-Matrix). |
| `widgets/` | `contracts/` (widget manifest contract), `registry/` (manifest service), `resolvers/` (data-source resolver), `ui/` (widget components). | The dynamic rendering engine — see `docs/ARCHITECTURE.md`. |
| `dynamic-renderer/` | `section-engine/`, `page-renderer/`, `section-renderer/`, `widget-host/`. | The page-composition pipeline that turns bootstrap JSON into rendered pages. |
| `layouts/` | Page-chrome containers, e.g. `layouts/containers/dynamic-page-layout.component.ts`. | Top-level layout composition, one level above pages. |
| `i18n/` | `translations.ts` (interface), `en.ts`/`ru.ts`/`hy.ts`, `translate.pipe.ts`, `TranslateService`. | Single source of truth for all user-facing copy — see `docs/FRONTEND.md`. |
| `pages/` | Top-level routed pages not part of a larger feature module (`home`, `cart`, `category`). | Simpler routed pages that don't warrant a full `features/` module. |
| `components/` | Reusable standalone components shared across features/pages (`product-card`, `telegram-login`). | Presentational, input/output-only (ADR-006) — no facade/HttpClient/storage access. |
| `guards/` | Route guards. | Kept separate from `core/admin-auth/` because `admin-auth.guard.ts` is domain-specific; generic guards live here. |
## Worked example, end to end: the Sprint 19 admin dashboard
`src/app/features/admin/dashboard/` — read in the order a new engineer would build it.
1. **Model**`models/admin-dashboard.model.ts`. Plain interfaces/types for card data, card status (`loading|empty|error|pending-backend|ready`), health-check entries. No behavior, no imports from Angular DI.
2. **Gateway interface**`services/admin-dashboard-metrics.gateway.interface.ts`. An abstract contract (`AdminDashboardMetricsGateway`) for "however we get category/product counts" — deliberately decoupled from *how* (local computation vs. real API) so the facade never knows which implementation is active.
3. **Gateway implementation**`services/admin-dashboard-metrics.local.gateway.ts`. `AdminDashboardMetricsLocalGateway implements AdminDashboardMetricsGateway`, composing `BackofficeDataService.loadCategories()/loadProducts()` (already used elsewhere) into counts. A future `AdminDashboardMetricsApiGateway` would implement the same interface against a real endpoint (`docs/BACKEND.md` item 8) — nothing above this layer changes when that happens.
4. **DI token**`services/admin-dashboard-metrics-gateway.token.ts`. `const ADMIN_DASHBOARD_METRICS_GATEWAY = new InjectionToken<AdminDashboardMetricsGateway>(...)`, bound to the local gateway by default in `app.config.ts`. This is the swap point: rebinding this token to a real API gateway is the *only* change needed to go from mock to real data.
5. **Supporting service**`services/admin-dashboard-history.service.ts`. `localStorage`-backed activity log, scoped per tenant — a second, narrower concern (recent activity) that doesn't belong in the metrics gateway.
6. **Facade**`facade/admin-dashboard.facade.ts`. `AdminDashboardFacade` is the *only* thing the components below are allowed to inject. It composes `ProjectEditorFacade` (existing — bootstrap/status/validation), `ADMIN_DASHBOARD_METRICS_GATEWAY` (via the token, not the concrete class), and `AdminDashboardHistoryService`, and exposes computed signals per card (status + value) plus the health-check list and quick-actions list.
7. **Presentational components**`components/admin-dashboard-card.component.*`, `admin-dashboard-quick-actions.component.*`, `admin-dashboard-activity.component.*`, `admin-dashboard-health.component.*`. Each takes only `@Input()`s (card data, health entries, quick-action list) — no `HttpClient`, no `localStorage`, no route access, no facade injection. This is what makes them independently testable and reusable.
8. **Page container**`pages/admin-dashboard-page.component.*`. Injects `AdminDashboardFacade`, computes per-card status from bootstrap-loaded/metrics-error/empty conditions, prefixes `routerLink`s with the current locale (`LanguageService.currentLanguage()`), and passes plain data down to the presentational components above. This is the only place in the feature that knows about routing or the facade.
9. **Route wiring**`app.routes.ts`. `/:lang/backoffice/dashboard -> AdminDashboardPageComponent`, guarded by `adminAuthGuard`; `/:lang/backoffice` (empty path) redirects to `dashboard`.
Full narrative and known gaps: `docs/ADMIN.md`.
## Steps to add a new feature (derived from the example above)
1. Decide: does this belong in `features/<area>/<feature>/`, or is it simple enough for `pages/`? Route-guarded, multi-component admin/backoffice work goes in `features/admin/*` or `features/backoffice/*`.
2. Define the domain model(s) first (`models/*.model.ts`) — no behavior, no DI.
3. If the feature needs data that might later come from a real backend, define a gateway/repository **interface** before writing any implementation.
4. Implement a local/mock gateway against existing data sources where possible (reuse, don't duplicate — check `core/*` and other features' services first).
5. Create an `InjectionToken` for the gateway and bind it to the local implementation in `app.config.ts` (or the relevant provider scope). This is the seam a backend integration will use later — never inject the concrete class directly from a facade or component.
6. Write the facade. It is the only consumer of the gateway token, and the only thing components inject.
7. Build presentational components as `@Input()`/`@Output()`-only — verify none of them import `HttpClient`, storage, or a facade.
8. Build the container/page component that injects the facade and wires routing.
9. Add routes in `app.routes.ts`, with `adminAuthGuard` (or the relevant guard) if it's an admin surface.
10. Add every new user-facing string to `i18n/translations.ts` (interface) then `en.ts`/`ru.ts`/`hy.ts` — never hardcode copy in a template.
11. Document backend gaps (if any) in `docs/BACKEND.md` using the same "current behavior / gap / endpoint needed / files that change" structure as the existing entries.
12. Run `npm run arch:check` (import boundaries + circular dependencies) and `npx tsc -p tsconfig.app.json --noEmit` before committing.

60
docs/PROJECT.md Normal file
View File

@@ -0,0 +1,60 @@
# PROJECT
## What this is
A configuration-driven, multi-tenant SaaS marketplace platform (Angular 18+, standalone components). One frontend codebase serves unlimited tenants ("marketplaces"). Tenant identity, theme, navigation, page/section/widget composition, and static content are all resolved from a per-tenant `bootstrap.json` fetched at runtime — no tenant-specific code paths exist in the frontend. See `docs/ARCHITECTURE.md` and `docs/BOOTSTRAP.md` for the mechanics.
Every tenant conceptually has three surfaces on this one codebase:
- **Website** — the public storefront (catalog, product pages, cart, static pages).
- **Builder** (Project Editor) — an in-app editor that edits the tenant's `BootstrapConfig` (see `docs/EDITOR.md`).
- **Backoffice** (Admin) — an admin area for products, and (as of Sprint 19) a dashboard; more domains are placeholders pending backend (see `docs/ADMIN.md`).
## Tenant / marketplace model
- Tenant is resolved **only by request domain/host** — never by query param, localStorage, or hardcoded ID.
- The frontend loads `GET /bootstrap` (tenant resolved server-side by host) and renders entirely from that JSON: theme, layout, navigation, pages, sections, widgets, static pages, feature flags.
- Widgets never call APIs directly; they receive resolved data through facades/resolvers.
- New tenants are onboarded by domain + bootstrap config + backend data, not by forking the frontend.
- Full contract: `docs/BOOTSTRAP.md`.
## Doc index
- **[ARCHITECTURE.md](ARCHITECTURE.md)** — layered architecture, container/facade/service pattern, bootstrap/theme/widget engines, links to the enforced ADRs.
- **[BACKEND.md](BACKEND.md)** — the backend punch list: every mocked/local-only feature, its gap, and the endpoint needed to make it real. Start here if you're a backend engineer picking up this project.
- **[FRONTEND.md](FRONTEND.md)** — app structure, routing, i18n, theming, state management (signals/facades, no NgRx), dynamic rendering.
- **[BOOTSTRAP.md](BOOTSTRAP.md)** — the `BootstrapConfig` model, field-by-field, with a representative example JSON.
- **[EDITOR.md](EDITOR.md)** — the Project Editor: every section, the save/publish/draft/reset model, and the field-description/dropdown UX.
- **[PROJECT-STRUCTURE.md](PROJECT-STRUCTURE.md)** — folder-by-folder tour of `src/app/**` with a worked "add a new feature" example (admin dashboard).
- **[ADMIN.md](ADMIN.md)** — Sprint 19 admin dashboard: routing, architecture, data sources, known gaps.
- `docs/architecture/foundation/**` — the enforced ADRs (ADR-001…ADR-010) and standards docs (Coding-Standards, Naming-Conventions, Dependency-Rules, Folder-Blueprint, Import-Boundary-Matrix, State-Management-Standards, Configuration-Standards, Component-Standards, Service-Standards). These are governance, not narrative — read them directly; `ARCHITECTURE.md` only links to them.
- `docs/context/**` — Barry Cache's own source-backed memory system. Infrastructure, not project documentation; do not edit by hand.
## How to run it
From `package.json`:
```bash
npm install
npm run start # ng serve
npm run start:dexar # ng serve --configuration=development --port 4200
npm run build # ng build
npm run build:dexar # ng build --configuration=production
npm run watch # ng build --watch --configuration development
npm run arch:check # boundary + circular-dependency checks (tools/architecture/check-boundaries.mjs, madge)
```
Barry Cache (repo memory, optional but recommended before/after non-trivial work):
```bash
npm run barry -- resume --task "<task>"
npm run barry -- validate
```
See root `CLAUDE.md` for the full Barry Cache workflow and memory policy.
## Current status (this sprint)
- **Sprint 19** shipped the production Admin Dashboard (`src/app/features/admin/dashboard/`) as the default `/:lang/backoffice` landing page, wired the previously-unrouted `admin/products` feature into routing, and added `lastPublishedAt` tracking to `ProjectEditorFacade`. See `docs/ADMIN.md`.
- **Sprint 18** added Project Editor autosave (localStorage draft), section/draft reset, admin QR-login reuse (shared Telegram session API/component, separate cookie/guard), and Ed25519 verification scaffolding (no crypto implemented yet — fails closed). See `docs/EDITOR.md`.
- Draft/publish for the Project Editor is still **frontend-only** (localStorage), with no backend persistence. This is the single largest backend gap — see `docs/BACKEND.md`.
- This documentation set (`docs/PROJECT.md`, `ARCHITECTURE.md`, `BACKEND.md`, `FRONTEND.md`, `BOOTSTRAP.md`, `EDITOR.md`, `PROJECT-STRUCTURE.md`) replaces ~35 previously scattered files under `docs/platform/`, `docs/backend-platform/`, and various one-off sprint reports, which have been consolidated and removed.

View File

@@ -1,272 +0,0 @@
# Product Details Report
## Scope
Sprint 6 implemented the product details route and UI using the frozen architecture and the existing Product Domain. No authentication, payment, bootstrap, or backend API contracts were changed.
The route and UI consume ProductFacade only. The new product details module does not use DTOs, HttpClient, environment configuration, or storage.
## Implemented Route
- `/product/:id`
Legacy `/item/:id` now redirects to `/product/:id` for compatibility.
## Implemented Module
### Product Details Container
- `src/app/features/website/product/containers/product-details-container.component.ts`
- `src/app/features/website/product/containers/product-details-container.component.html`
- `src/app/features/website/product/containers/product-details-container.component.scss`
Responsibilities implemented:
- Reads the route id.
- Loads the product through `ProductFacade` only.
- Handles loading, error, and missing-product states.
- Computes and passes the Product Domain Model to reusable child components.
- Loads related products from the same category through `ProductFacade`.
- Supports variant selection and computed price/stock updates.
### Reusable Components
#### Product Gallery
- `src/app/features/website/product/components/product-gallery/product-gallery.component.ts`
- `src/app/features/website/product/components/product-gallery/product-gallery.component.html`
- `src/app/features/website/product/components/product-gallery/product-gallery.component.scss`
Features:
- Main image
- Thumbnail list
- Video-aware media rendering
- Future multi-image support through the existing media array shape
#### Product Information
- `src/app/features/website/product/components/product-information/product-information.component.ts`
- `src/app/features/website/product/components/product-information/product-information.component.html`
- `src/app/features/website/product/components/product-information/product-information.component.scss`
Features:
- Title
- Price
- Discount
- Badges
- Stock
- Add-to-cart output
#### Variant Selector
- `src/app/features/website/product/components/variant-selector/variant-selector.component.ts`
- `src/app/features/website/product/components/variant-selector/variant-selector.component.html`
- `src/app/features/website/product/components/variant-selector/variant-selector.component.scss`
Features:
- Color selection
- Size selection
- Fallback display for single-variant products
#### Delivery Information
- `src/app/features/website/product/components/delivery-information/delivery-information.component.ts`
- `src/app/features/website/product/components/delivery-information/delivery-information.component.html`
- `src/app/features/website/product/components/delivery-information/delivery-information.component.scss`
Features:
- Digital delivery state
- Delivery option list
- Currency display
#### Product Description
- `src/app/features/website/product/components/product-description/product-description.component.ts`
- `src/app/features/website/product/components/product-description/product-description.component.html`
- `src/app/features/website/product/components/product-description/product-description.component.scss`
Features:
- Simple description
- Structured specification fields
- Plain description fallback
#### Related Products
- `src/app/features/website/product/components/related-products/related-products.component.ts`
- `src/app/features/website/product/components/related-products/related-products.component.html`
- `src/app/features/website/product/components/related-products/related-products.component.scss`
Features:
- Uses existing ProductFacade results
- Excludes the current product
- Reuses the shared product card
## Product Domain Usage
All product details UI code uses Product Domain models and ProductFacade. Backend DTOs remain isolated to the domain/provider layer.
The container derives all view state locally from the product model:
- selected color
- selected size
- effective price
- effective currency
- effective remaining stock
- gallery selection
- related products
## Navigation Updates
Updated links to use the new route surface:
- product card navigation
- carousel item links
- cart item links
- category item links
- catalog module product navigation
## Validation
Completed checks:
- Product loads through `ProductFacade`.
- Variant changes update displayed price.
- Stock changes correctly when variant selection changes.
- Gallery works with primary and thumbnail media.
- Related products work through the same-category product query.
- Components are reusable and input/output-only.
- No DTO usage in the new product details module.
- No `HttpClient` in the new product details UI.
- No storage or environment usage in the new product details UI.
- Build passes.
Build validation:
```bash
npm run build
```
## Stop Point
Product Details Module implementation is complete for Sprint 6. Stop here for approval before starting any further module work.
## Sprint 11 - Product Experience 2.0
Sprint 11 extends Product Details UX with reusable modules and config-driven behavior. Architecture, Widget Manifest, Section Engine, bootstrap loading, authentication, and payment logic remain unchanged.
### New Reusable Modules
- Product Actions module
- `Add to Cart`, `Buy Now`, `Wishlist`, `Compare`, `Share`, `Notify Me`
- `productPage.actions` controls visibility of each action
- Product Gallery upgrade
- Media renderer by type (`image`, `video`, `pdf`, `manual`, `warranty`)
- Thumbnail strip + active media
- Mobile swipe support
- Zoom/fullscreen extension events (`zoomRequested`, `fullscreenRequested`)
- Product Specifications upgrade
- Grouped attributes via `specificationGroups`
- Translated labels via `labels` map
- Backward-compatible fallback to `descriptionFields` and `attributes`
- Variant Selector upgrade
- Dynamic option groups via `variantOptions`
- No hardcoded variant keys required
- Backward-compatible color/size fallback
- Reviews upgrade
- Existing pagination preserved
- Optional `load-more` mode added via config
- Review form adds explicit validation and success state
- Questions & Answers upgrade
- Existing list/answers/accepted-answer behavior preserved
- Submission can be toggled off while component remains future-ready
- Related Products upgrade
- Supports multiple backend-provided collections
- Falls back to previous single related collection behavior
### Config Additions (backward-compatible)
`productPage` supports new optional keys:
```json
{
"reviews": {
"mode": "pages"
},
"questions": {
"allowSubmission": true
},
"actions": {
"enabled": true,
"addToCart": true,
"buyNow": true,
"wishlist": true,
"compare": true,
"share": true,
"notifyMe": true
}
}
```
### Product JSON Extension Example
```json
{
"itemID": 7812,
"name": "Laptop Pro 14",
"media": [
{ "type": "image", "url": "https://cdn.example.com/items/7812/main.jpg" },
{ "type": "video", "url": "https://cdn.example.com/items/7812/demo.mp4" },
{ "type": "pdf", "url": "https://cdn.example.com/items/7812/spec.pdf" },
{ "type": "manual", "url": "https://cdn.example.com/items/7812/manual.pdf" },
{ "type": "warranty", "url": "https://cdn.example.com/items/7812/warranty.pdf" }
],
"specificationGroups": [
{
"key": "display",
"label": "Display",
"labels": { "en": "Display", "ru": "Экран", "hy": "Էկրան" },
"attributes": [
{ "key": "size", "value": "14", "unit": "inch", "labels": { "en": "Size", "ru": "Диагональ", "hy": "Չափ" } },
{ "key": "resolution", "value": "2880x1800", "labels": { "en": "Resolution", "ru": "Разрешение", "hy": "Լուծաչափ" } }
]
}
],
"variantOptions": [
{
"key": "storage",
"label": "Storage",
"options": [
{ "value": "256GB" },
{ "value": "512GB" }
]
}
],
"relatedCollections": [
{
"id": "frequently-bought-together",
"title": "Frequently bought together",
"titles": { "ru": "Покупают вместе", "hy": "Հաճախ գնում են միասին" },
"products": [9021, 9022, 9023]
},
{
"id": "similar-products",
"title": "Similar products",
"products": [9030, 9031]
}
]
}
```
### Extension Points
- Gallery overlays can subscribe to `zoomRequested` and `fullscreenRequested` without changing gallery internals.
- Variant logic can add new option groups from backend by extending `variantOptions` only.
- Specifications can add locale labels without frontend refactor.
- Related collections can add campaign-specific blocks without changing section engine.
- Action visibility and review mode can be tuned from config without component rewrites.

View File

@@ -1,307 +0,0 @@
# Marketplace Project Editor - Sprint 13 (updated Sprint 18)
## Scope
Sprint 13 introduces first version of Marketplace Project Editor.
Editor only manages bootstrap configuration.
Out of scope:
- products
- categories
- orders
- analytics management
## Architecture
```text
src/app/features/project-editor/
pages/
components/
sections/
models/
services/
facade/
```
Editor uses strongly typed bootstrap models and isolated feature state.
Components do not call APIs directly.
## Facade API
`ProjectEditorFacade` exposes:
- `loadBootstrap()`
- `updateBootstrap()`
- `exportBootstrap()`
- `importBootstrap()`
- `preview()`
Current source of truth:
- existing bootstrap provider via `ConfigService`
Future backend integration:
- replace local import/export/save flow with builder endpoints
- keep component contracts unchanged
## Supported Sections
- General
- marketplace name
- domain
- description
- default language
- supported languages
- Branding
- logo
- small logo
- favicon
- marketplace title
- Theme
- palette tokens via color pickers
- Header
- logo/search/categories/languages/cart/profile/wishlist/compare/region toggles
- Footer
- company information
- address
- phone
- email
- copyright
- payment icons
- social links
- static pages
- Homepage
- home page sections list
- visibility
- order
- layout
- drag-and-drop ordering
- Widgets
- homepage widget configuration
- typed shortcuts for hero/categories/product collection widgets
- JSON fallback for other widget props
- Marketplace Features
- feature flags
- search suggestions/history
- recently viewed
- reviews/questions/recommendations
- Preview
- export JSON
- import JSON
- runtime preview without full browser refresh
- Languages (Sprint 16)
- add/remove supported locale
- set default locale
- generically syncs translation keys across static pages and navigation labels (`LocaleSyncService`)
- Navigation (Sprint 16)
- header navigation: add/remove/reorder/edit label/URL/visibility
- flat footer navigation: same actions
- grouped footer navigation (column-based) is read-only in this tab for now
## Preview Strategy
Preview updates bootstrap snapshot in memory and re-applies:
- theme
- branding
- runtime state
- route-based page rendering on next Angular navigation
This keeps bootstrap engine intact while enabling fast local preview.
## Widget Configuration
Current widget editor supports explicit fields for:
- Hero
- layout
- height
- overlay
- autoplay
- Categories
- layout
- columns
- Product Collection
- layout
- cards per row
- filters
- badges
- rating
- price
Other widgets use JSON props fallback until dedicated editors are added.
## Draft / Publish (Sprint 16, autosave added Sprint 18)
There is still no backend draft/publish API. This sprint models it client-side
in `ProjectEditorFacade`:
- `status: 'draft' | 'published'` and `dirty` (diffed against the
last-saved snapshot) live in facade state.
- `save()` snapshots the current in-memory bootstrap as "last saved" and
timestamps it (`lastSavedAt`).
- `publish()` runs `ProjectValidator`, and if there are no issues, applies
the bootstrap via `PlatformRuntimeService.reloadFromBootstrap`, marks
status `published`, and becomes the new `originalBootstrap` snapshot used
by reset.
**Backend gap, not yet implemented:** real persistence needs
`PUT /builder/bootstrap/draft` and `POST /builder/bootstrap/publish`
endpoints so drafts/publishes survive a reload and are shared across editors.
### Autosave (Sprint 18)
`ProjectEditorDraftStorageService` (`services/project-editor-draft-storage.service.ts`)
persists the full bootstrap draft to `localStorage` (key
`projectEditor.draftBootstrap.v1`, scoped by `tenant.id`) on every
`updateBootstrap()` call, `save()`, and `publish()`. On `loadBootstrap()`, if a
stored draft exists for the same tenant it is loaded instead of the
freshly-fetched bootstrap and `draftRestored` is set true (surfaced in the
save bar as a dismissible notice). The published/loaded bootstrap is never
overwritten automatically — only explicit `publish()` calls change what the
runtime actually serves; the localStorage draft is a separate, purely local
concern that survives refreshes and browser restarts.
Status indicators in `ProjectEditorSaveBarComponent`:
- **Unsaved changes** - shown while `dirty()` is true.
- **Last saved: HH:MM:SS** - shown once not dirty and `lastSavedAt` is set.
- **Draft restored** banner - shown once after a local draft is loaded from
a previous session, dismissible.
### Reset (Sprint 18)
- **Reset section** - button above the active section (shown only for
sections with a bootstrap-key mapping in `EDITOR_SECTION_BOOTSTRAP_KEYS`,
`models/project-editor.model.ts`). Reverts that section's bootstrap keys
to `originalBootstrap` (the last loaded/published snapshot). Confirmation
required.
- **Reset draft** - button in the save bar. Reverts the entire bootstrap to
`originalBootstrap` and clears the persisted local draft. Confirmation
required.
- Per-field reset is **not implemented** - the bootstrap schema has no
registry of per-field defaults, so only section- and project-level reset
exist. Adding field-level reset would require either a default-value
registry per field or storing per-field undo history; deferred.
## Validation
`ProjectValidator` (`services/project-validator.service.ts`) runs on every
render of the save bar: missing logo, no languages, invalid marketplace URL,
duplicate static-page slugs, empty homepage, a homepage widget with no
`type`, duplicate header navigation links, invalid theme colors. Publish is
blocked while any issue is present; Save is not.
## Rich HTML editing
Static page HTML is edited via `MarketplaceHtmlEditorComponent`
(`components/html-editor/`), a `contentEditable` + toolbar component with no
external dependency. It emits raw HTML on every change and never sanitizes —
sanitization remains a storefront-render concern.
## QR Login Reuse (Sprint 18, corrected)
There is exactly **one** Telegram QR/session backend
(`{authApiUrl}/users/sessions`) and exactly **one** QR login component/UI.
Nothing about the QR flow is duplicated for admin:
- `TelegramSessionApiService` (`services/telegram-session-api.service.ts`) is
the single place that calls `POST/GET/DELETE {authApiUrl}/users/sessions...`
and normalizes the response into `AuthSession`. It holds no state and
writes no cookies - it's a pure API wrapper.
- `QrLoginEngine<TSession>` (`shared/qr-login/qr-login.engine.ts`) is the
QR/polling/expiry/"return from Telegram app" state machine (extracted from
the original `TelegramLoginComponent`), driven by a small
`QrLoginAdapter<TSession>` (`shared/qr-login/qr-login.model.ts`).
- `TelegramLoginComponent` (`components/telegram-login/`) is **the same
component for both customer and admin login** - not two components. It
takes a `mode: 'customer' | 'admin'` input; `ngOnInit` picks
`AuthService` or `AdminAuthService` accordingly and builds the
`QrLoginAdapter` from whichever one, but the QR image, polling loop,
timeouts, and dialog markup are identical either way. Customer usage is
unchanged (`<app-telegram-login />` on the cart page, `mode` defaults to
`'customer'`); admin usage is `<app-telegram-login mode="admin" />`,
mounted once globally in `app.html`.
An earlier version of this sprint's work built a separate
`AdminAuthService`/`AdminLoginComponent` pair that called its own
`adminAuthApiUrl` placeholder endpoint. That was wrong: there is no separate
admin backend, and inventing one client-side would have meant testing against
an endpoint that doesn't exist. It was replaced with the shared-API approach
described above.
## Admin Authentication (Sprint 18, corrected)
Only the **storage** is separate between customer and admin - the QR/session
API and UI component are shared (see above), by design, since one Telegram
QR/session backend serves both. What stays separate is everything needed so
that scanning the admin QR can never authenticate the customer session (or
vice versa):
| | Customer (`AuthService`) | Admin (`AdminAuthService`, `core/admin-auth/`) |
|---|---|---|
| Cookie | `webSessionID` (`SameSite=Lax`) | `adminSessionID` (`SameSite=Strict`) |
| Token storage | `web_session_id` (localStorage, anonymous API attribution only, unrelated to auth) | `adminToken` / `adminRefreshToken` (localStorage, reserved for a future JWT pair - unused today) |
| Signals | `session`, `status`, `showLoginDialog` on `AuthService` | `session`, `status`, `showLoginDialog` on `AdminAuthService` |
| Guard | none yet for customer routes | `adminAuthGuard` (`core/admin-auth/admin-auth.guard.ts`) |
| Interceptor | `apiHeadersInterceptor` | `adminAuthHeadersInterceptor` (`core/admin-auth/admin-auth-headers.interceptor.ts`), self-guards on `/admin/` in the request URL, sets `AdminWebSessionID` + `Authorization: Bearer <adminToken>` when present |
| Session/QR API | `TelegramSessionApiService` | same `TelegramSessionApiService` instance/endpoint |
| Login UI | `TelegramLoginComponent` (`mode="customer"`, default) | same `TelegramLoginComponent` (`mode="admin"`) |
**Backend gap this creates, and why it matters:** because admin login goes
through the exact same Telegram session API as customer login, the backend
has **no concept of "this is an admin session"** at the point the QR is
scanned - it's just a regular Telegram user session, identical in shape to a
customer's. The frontend only decides *where to store* the resulting session
id (admin cookie vs. customer cookie); it cannot and does not decide whether
that Telegram user is actually allowed to act as an admin. **Real admin
authorization must be enforced server-side**, at the point admin API calls
are made with the `AdminWebSessionID` header - the backend must check the
authenticated user against an admin/role list and reject non-admins, since
nothing on the frontend prevents any Telegram user from completing the QR
flow while `mode="admin"` is showing. This needs a backend decision (role
check keyed off the session id, or a dedicated admin-scoped token issuance)
before admin login can be considered secure, not just "separate storage."
### Login test mode
`?login=true` and `?adminLogin=true` query params (handled once in
`App.ngOnInit` via `openLoginDialogsFromTestModeQueryParams()`, `app.ts`) call
`AuthService.requestLogin()` / `AdminAuthService.requestLogin()` respectively,
for manual testing. This only sets the same signal a normal "please log in"
action would set - it does not bypass authentication or change any other
behavior, so it is safe in all environments. `TelegramLoginComponent` in
customer mode is currently mounted only on the cart page, so `?login=true`
only shows a dialog there; the admin-mode instance is mounted globally so
`?adminLogin=true` works from any route.
### Ed25519 prep
`core/admin-auth/ed25519-verification.model.ts` defines
`Ed25519VerificationService` (abstract, injectable) with
`requestChallenge()` / `verify(signedResponse)` and the
`Ed25519Challenge` / `Ed25519SignedResponse` / `Ed25519VerificationResult`
shapes (nonce, timestamp, payload, public key, signature). No crypto is
implemented. The current DI binding,
`NoopEd25519VerificationService` (registered in `app.config.ts`), fails
closed (throws) rather than silently accepting anything, so it's safe to wire
into a real login path today - it will error loudly instead of pretending to
verify a signature. Swap the DI binding for a real implementation once the
backend ships challenge/verify endpoints; nothing else needs to change.
## Known gaps / deferred (Sprint 18)
Full field-by-field coverage of every supported bootstrap property (with
bilingual EN/RU labels, description, and validation state per field) was not
completed in this pass - the bootstrap schema is large (theme typography/
spacing/shadows, full company address, per-locale footer copyright, grouped
footer navigation editing, sidebar navigation, per-page SEO map, catalog/
product-page/user-experience sub-fields, permissions, API endpoints) and
several concepts named in the sprint brief (payments, delivery/shipping,
checkout, unified search config) have **no corresponding model in
`shared/models/config` at all** - they would need new bootstrap schema before
an editor could expose them. See the section-by-section gap list gathered
during Sprint 18 investigation for the full inventory; treat as a follow-up
sprint rather than something silently skipped.
## Constraints
- runtime bootstrap engine not replaced
- configuration stays source of truth
- no hardcoded marketplace values
- no business domain management mixed into editor

View File

@@ -1,188 +0,0 @@
# Search Intelligence Engine - Sprint 12
## Scope
Sprint 12 introduces a standalone Search Feature architecture reusable across marketplaces.
Constraints respected:
- No authentication changes
- No payment changes
- No runtime bootstrap changes
- No Widget Manifest changes
- No Section Engine changes
- No Product/Catalog business rule changes
## Architecture
```text
src/app/features/search/
components/
search-bar/
trending-searches/
empty-results/
services/
search-autocomplete.service.ts
search-history.service.ts
search-history.repository.ts
search-trending.service.ts
search-cache.service.ts
search-analytics.service.ts
facade/
search.facade.ts
models/
search.model.ts
search-state.model.ts
store/
search.store.ts
utils/
search-query-key.util.ts
```
Legacy compatibility kept:
- `src/app/facades/platform/search.facade.ts` now re-exports feature facade
- `src/app/core/search/models/*` re-export feature models
- `src/app/core/search/services/search-history.service.ts` re-exports feature history service
## Facade API
Search UI communicates through `SearchFacade`:
- `search(query)`
- `loadCatalog(query)`
- `suggestions(query, products, categories, limit)`
- `autocomplete(query, products, categories, limit)`
- `getSearchHistory()`
- `pushSearchHistory(term, maxHistory)`
- `clearSearchHistory()`
- `trending()`
- existing helpers reused by catalog: filters, sorting, pagination, query params
## State Model
Managed in `SearchStore`:
- current query
- loading
- results
- suggestions
- recent searches
- popular searches
- selected filters
- current sort
- current page
- total results
Compatibility aliases preserved for existing catalog integration.
## Suggestion Model
Each suggestion includes:
- `type`
- `title`
- `subtitle`
- `icon`
- `target`
Supported suggestion types:
- product
- category
- brand
- collection
- seller
- static-page
- ai
## Autocomplete
Behavior:
- Debounced typing (`debounceTime`)
- Previous request cancellation (`switchMap`)
- Distinct query suppression (`distinctUntilChanged`)
- Suggestion state controlled by facade/store
Current source:
- In-memory products/categories/tags
Future-ready for backend endpoint:
- autocomplete service can switch to API provider without UI changes
## Search History
Abstraction:
- `SearchHistoryRepository`
- `LocalSearchHistoryRepository` for guest users
- `BackendSearchHistoryRepository` placeholder for logged users
Behavior:
- Newest first
- Configurable max length
- Clear history support
## Trending Searches
`SearchTrendingService` contract introduced.
Current behavior:
- Returns `null` when endpoint unavailable
- UI hides trending block gracefully
## Empty Results UX
Reusable empty state supports:
- no results messaging
- popular categories
- popular searches
- recommended products
- reset filters action
## Filters and Sorting Reuse
No duplicated filter/sort engines.
Search facade reuses existing catalog filter metadata generation,
filter application, and sort application pathways.
## Search Bar UX
Reusable `SearchBarComponent` supports:
- ESC closes suggestions/overlay
- arrow navigation
- Enter opens highlighted suggestion
- mouse selection
- loading indicator
- clear button
- mobile fullscreen overlay with large touch targets
## Performance
Implemented:
- debounce
- switch-map cancellation
- duplicate suppression
- query-result cache for repeated searches
## Analytics Architecture
`SearchAnalyticsService` provides event factory only.
Event shape:
- query
- tenant
- language
- timestamp
- result count
No analytics transport implementation in Sprint 12.
## Configuration and Extension Points
Extension points:
- replace history repository with backend endpoint
- replace trending provider with backend endpoint
- replace autocomplete provider with API or AI provider
- enrich suggestion mapper with static pages/sellers/collections sources
## Responsiveness and Accessibility
- Desktop suggestion dropdown behavior
- Mobile fullscreen overlay behavior
- ARIA labels and keyboard navigation
- touch target sizing in mobile mode

View File

@@ -1,75 +0,0 @@
# Section Engine Report
## Scope
Sprint 7 introduced the generic section engine used by the homepage runtime path. The implementation stays within the frozen frontend architecture and does not change backend APIs, auth, payment, or bootstrap contracts.
The section engine now treats the homepage as a section collection rendered through the existing dynamic page layout pipeline:
- page config -> section engine -> section renderer -> widget host -> registered widget components
## Implemented Changes
### Section Metadata
- `src/app/shared/models/config/section.model.ts`
Added explicit section metadata for:
- layout strategy
- layout columns/gap/alignment
- desktop/tablet/mobile visibility flags
### Render Nodes
- `src/app/dynamic-renderer/section-renderer/section-renderer.model.ts`
- `src/app/dynamic-renderer/section-renderer/section-renderer.service.ts`
Section render nodes now carry layout and visibility metadata forward from the shared section config.
### Section Engine
- `src/app/dynamic-renderer/section-engine/section-engine.service.ts`
Added the section engine orchestration service to build ordered page render models from section definitions.
### Page Renderer
- `src/app/dynamic-renderer/page-renderer/page-renderer.service.ts`
Delegates page model assembly to the section engine.
### Dynamic Layout
- `src/app/layouts/containers/dynamic-page-layout.component.ts`
The layout container now reads section layout strategy and visibility metadata when rendering sections.
### Homepage Migration
- `src/app/pages/home/home.component.ts`
- `src/app/pages/home/home.component.html`
- `src/assets/mock/bootstrap/homepage.json`
The homepage now loads the runtime page model and renders its section collection instead of hardcoding the hero and product carousel blocks directly in the template.
The existing category area remains on the homepage and still uses the category domain/facade path.
## Validation
Completed checks:
- Section layout and visibility metadata compile cleanly.
- Page renderer delegates through the new section engine service.
- Homepage composes the runtime page model through `DynamicPageLayoutComponent`.
- The workspace build passes.
Build validation:
```bash
npm run build
```
## Stop Point
Sprint 7 section-engine groundwork is complete for the current homepage flow. Stop here and wait for approval before extending the section system to additional website pages or adding new widget/data resolvers.

View File

@@ -1,91 +0,0 @@
# Widget Manifest Report
## Scope
Sprint 8 introduced a generic widget manifest and data-source engine for the runtime homepage path. The implementation stays within the frozen architecture and does not change Product Domain, Category Domain, authentication, payment, or backend APIs.
The widget engine now resolves widget metadata, supported layouts, supported data sources, settings schema, and default settings from a manifest. Widgets receive only the section config and resolved data.
## Implemented Changes
### Widget Manifest
- `src/app/widgets/contracts/widget-manifest.contract.ts`
- `src/app/widgets/registry/widget-manifest.service.ts`
- `src/assets/mock/bootstrap/widget-manifest.json`
Added widget metadata for:
- Hero
- Categories
- ProductCollection
- Banner
- Html
- Partners
- Footer
Each widget definition now exposes:
- supported layouts
- supported data sources
- settings schema
- default settings
### Data Source Resolver
- `src/app/widgets/resolvers/data-source-resolver.service.ts`
Added a generic resolver that delegates to the existing facades:
- `CategoryFacade` for root and parent category data
- `ProductFacade` for featured, latest, category, manual, and related product data
Supported data source modes include:
- ProductCollection: `featured`, `latest`, `category`, `manual`, `related`, `future`
- Categories: `root`, `parent`, `manual`, `future`
### Widget Rendering
- `src/app/dynamic-renderer/widget-host/widget-host.service.ts`
- `src/app/layouts/containers/dynamic-page-layout.component.ts`
- `src/app/widgets/ui/hero-widget.component.ts`
- `src/app/widgets/ui/categories-widget.component.ts`
- `src/app/widgets/ui/product-carousel-widget.component.ts`
- `src/app/widgets/ui/footer-navigation-widget.component.ts`
Widgets now receive section config plus resolved data only. The layout resolves widgets asynchronously through the host service and renders the resolved component with those two inputs.
### Homepage Bootstrap
- `src/assets/mock/bootstrap/bootstrap.json`
- `src/assets/mock/bootstrap/homepage.json`
- `src/app/pages/home/home.component.ts`
- `src/app/pages/home/home.component.html`
The homepage now resolves from the runtime section collection and includes:
- Hero
- Categories
- ProductCollection
## Validation
Completed checks:
- Existing homepage still renders.
- ProductCollection works through the data-source resolver.
- Categories work through the data-source resolver.
- Widget metadata exists in the manifest.
- Data-source resolution is isolated from widget components.
- Build passes.
Build validation:
```bash
npm run build
```
## Stop Point
Sprint 8 widget-manifest and data-source-engine work is complete. Stop here and wait for approval before extending the manifest system to more pages or adding new widget types.

View File

@@ -1,103 +0,0 @@
# Backend Platform Architecture
## 1. System Overview
This platform is a domain-based multi-tenant SaaS marketplace.
- Frontend (Angular) is configuration-driven.
- Backend (Node.js) is tenant-aware and resolves tenant by request domain.
- UI composition is delivered by `GET /bootstrap` from the backend CONFIG DOMAIN.
- Business operations are delivered by existing BUSINESS DOMAIN APIs (`/auth`, `/items`, `/categories`, `/orders`, `/cart`, `/payments`).
Core architectural rule:
- No projectName-based behavior.
- No environment-based business branching.
- Runtime behavior is tenant-driven by domain + tenant configuration.
## 2. Tenant Resolution by Domain
Tenant identity is resolved from the incoming host:
- `shop-a.example.com` -> tenant A
- `shop-b.example.com` -> tenant B
Resolution output is attached to request context and used by:
- Config domain (`/bootstrap`, `/pages/:slug`)
- Business APIs (data isolation and policy checks)
## 3. CONFIG DOMAIN vs BUSINESS DOMAIN
### CONFIG DOMAIN
Purpose: return public runtime configuration for frontend composition.
Includes:
- tenant metadata (public)
- theme
- layout mode
- widget registry metadata
- page/section/widget structure
- footer/static pages metadata
- supported locales/currencies
- endpoint mapping (public)
### BUSINESS DOMAIN
Purpose: transactional and catalog operations.
Includes:
- authentication/session
- products/items
- categories
- cart
- orders
- payments
Boundary rule:
- BUSINESS APIs do not return UI layout/theme/widget composition.
- CONFIG APIs do not return transactional business state.
## 4. Bootstrap API Role
`GET /bootstrap` initializes frontend runtime.
Backend responsibilities:
1. Resolve tenant from domain.
2. Load tenant config aggregate.
3. Return versioned, public bootstrap payload.
4. Never leak secrets in bootstrap.
Frontend responsibilities:
1. Load bootstrap at startup.
2. Render based on config only.
3. Use business APIs only for domain data/actions.
## 5. Existing API Domains (Unchanged)
- `/auth`
- `/items`
- `/categories`
- `/orders`
- `/cart`
- `/payments`
These APIs remain authoritative for business workflows and must not be rewritten for UI composition.
## 6. Data Flow Diagram (Text)
```text
Browser Request
-> Edge/Ingress (Host preserved)
-> Node.js API Gateway
-> Tenant Resolver Middleware (host -> tenant)
-> Request Context Enrichment (tenantId, locale, policy)
-> Route Dispatch
-> /bootstrap (CONFIG DOMAIN) -> Config Services -> Response JSON
-> /items|/orders|... (BUSINESS DOMAIN) -> Business Services -> Response JSON
<- Tenant-scoped response
```
## 7. Request Lifecycle (Browser -> Backend -> Tenant -> Response)
1. Browser sends request with `Host` header.
2. Backend middleware resolves tenant by domain.
3. Backend validates tenant status (active, allowed, mapped).
4. Tenant context is attached to request (`req.ctx.tenant`).
5. Route handler executes with tenant-scoped repositories/services.
6. Response is returned with tenant-scoped data.
## 8. Scalability Notes (10100+ Tenants)
- Keep tenant config in low-latency cache with invalidation.
- Use stateless API instances; tenant context is per request.
- Enforce strict tenant filters at repository/query layer.
- Monitor by tenant dimensions (latency, errors, saturation).
- Apply rate limits and abuse controls per tenant/domain.

View File

@@ -1,151 +0,0 @@
# Bootstrap API Specification
## 1. Endpoint
- Method: `GET`
- Path: `/bootstrap`
- Auth: public (or optional lightweight token), tenant-scoped by domain
## 2. Domain-Based Request Flow
1. Receive request with host.
2. Resolve tenant by host.
3. Load tenant config aggregate from CONFIG DOMAIN.
4. Build versioned bootstrap payload.
5. Return public config JSON.
Failure responses:
- `404` unknown tenant domain
- `403` tenant inactive/suspended
- `500` config assembly failure
## 3. Production-Like Sample Response
```json
{
"schemaVersion": "2.1.0",
"generatedAt": "2026-07-05T10:30:00Z",
"tenant": {
"id": "a95c2f1b-58c1-4d8b-b35b-82e5bdf14321",
"slug": "alpha-market",
"code": "ALPHA",
"host": "shop.alpha.example.com",
"name": "Alpha Marketplace",
"defaultLocale": "en",
"supportedLocales": ["en", "ru", "hy"],
"defaultCurrency": "USD",
"supportedCurrencies": ["USD", "EUR", "AMD"],
"timezone": "UTC"
},
"theme": {
"themeId": "alpha-light",
"mode": "light",
"palette": {
"primary": "#2F6F6D",
"secondary": "#9FB8B6",
"accent": "#B5D7D4",
"textPrimary": "#1E3C38",
"textSecondary": "#5E7471",
"backgroundPrimary": "#FFFFFF",
"backgroundSecondary": "#F6F8F8",
"border": "#D7E0DF"
}
},
"layout": {
"type": "sidebar-left",
"options": {
"sidebarSticky": true,
"heroEnabled": true
}
},
"widgetRegistry": {
"manifestUrl": "/config/widgets/manifest.json"
},
"pages": [
{
"id": "page-home",
"key": "home",
"route": { "path": "/", "exact": true },
"layout": { "type": "carousel-home" },
"sections": [
{
"id": "sec-hero",
"type": "hero",
"order": 1,
"widgets": [
{
"id": "w-hero-main",
"type": "hero",
"version": "1.0.0",
"order": 1,
"padding": "0.5rem 0",
"visibility": { "desktop": true, "tablet": true, "mobile": true },
"props": { "title": "Welcome", "subtitle": "B2B Catalog" }
}
]
}
]
}
],
"footer": {
"paymentIcons": [
{ "src": "/assets/payments/visa.svg", "alt": "Visa", "width": 40, "height": 28 },
{ "src": "/assets/payments/mastercard.svg", "alt": "Mastercard", "width": 40, "height": 28 }
],
"copyrightText": {
"en": "© 2026 Alpha Marketplace. All rights reserved.",
"ru": "© 2026 Alpha Marketplace. Все права защищены.",
"hy": "© 2026 Alpha Marketplace. Բոլոր իրավունքները պաշտպանված են:"
},
"legalPageKeys": ["about-us", "privacy-policy", "terms-of-service"]
},
"localization": {
"defaultLocale": "en",
"supportedLocales": ["en", "ru", "hy"],
"currencyByLocale": {
"en": "USD",
"ru": "USD",
"hy": "AMD"
}
},
"apiEndpoints": {
"bootstrap": { "path": "/bootstrap", "method": "GET", "timeoutMs": 5000 },
"website": {
"items": { "path": "/items", "method": "GET" },
"categories": { "path": "/categories", "method": "GET" },
"cart": { "path": "/cart", "method": "GET" },
"orders": { "path": "/orders", "method": "POST" },
"payments": { "path": "/payments", "method": "POST" }
}
}
}
```
## 4. Field-by-Field Meaning
- `schemaVersion`: bootstrap contract version used by frontend parser.
- `generatedAt`: payload generation timestamp.
- `tenant`: public tenant identity and locale/currency defaults.
- `theme`: UI tokens; no business logic.
- `layout.type`: global layout mode. Supported: `default`, `sidebar-left`, `carousel-home`, `minimal`.
- `widgetRegistry.manifestUrl`: source for widget definitions/components mapping metadata.
- `pages`: route-driven composition graph.
- `footer`: footer links/icons/legal references.
- `localization`: supported locales and currency mapping.
- `apiEndpoints`: public endpoint mapping for frontend clients.
## 5. Versioning Strategy
Use semantic versioning in `schemaVersion`:
- Patch (`2.1.1`): non-breaking metadata additions.
- Minor (`2.2.0`): additive fields/sections with backward compatibility.
- Major (`3.0.0`): breaking structural changes.
Operational rules:
- Keep old parser compatibility for at least one minor line.
- Publish migration notes for any major bump.
- Validate payload against schema before release.
## 6. Security Rules
Never include in bootstrap:
- private keys
- internal credentials
- admin secrets
- payment signing material
Bootstrap is strictly public runtime configuration.

View File

@@ -1,242 +0,0 @@
# Business APIs
## 1. Scope
Business APIs provide transactional and catalog capabilities.
They must remain tenant-aware and must not return UI/layout/theme/widget configuration.
## 2. Immutable Rule
These APIs MUST NOT return:
- page composition
- layout mode
- widget metadata
- theme/footer/static page config
That data belongs to CONFIG DOMAIN (`/bootstrap`, `/pages/:slug`).
## 3. API Catalog
## /auth
Purpose:
- session creation/validation
- login/logout flows
- token refresh where applicable
High-level response shape:
- session/token metadata
- user identity claims
- permission scopes
Tenant rule:
- auth sessions are tenant-scoped by request context.
Must not change:
- authentication contract and downstream payment/auth integrations.
## /items
Purpose:
- list/fetch product items
- search/filter/sort
- item details and availability
High-level response shape:
- item arrays / item objects
- pagination metadata
- stock/price fields
Tenant rule:
- only items visible to tenant catalog policy.
Must not change:
- item identifiers/price semantics relied on frontend checkout/cart logic.
## /searchitems
Purpose:
- keyword-based product search for catalog/search pages.
High-level response shape:
- items array
- total count
Tenant rule:
- results must respect tenant catalog visibility and pricing policies.
## /search/suggestions (future-ready)
Purpose:
- return instant search suggestions for typed keywords.
High-level response shape:
- suggestion strings or objects with label/value and optional popularity/count.
Tenant rule:
- suggestions generated only from tenant-visible catalog corpus.
## /catalog/filters (future-ready)
Purpose:
- provide dynamic filter definitions and options for current search/category context.
High-level response shape:
- filter definitions
- option counts
- optional min/max ranges
Tenant rule:
- filter options/counts must be tenant-scoped and inventory-aware.
## /products/{id}/rating
Purpose:
- return product rating aggregate for engagement UI.
High-level response shape:
- average rating
- total reviews
- star distribution (5..1)
Tenant rule:
- aggregate must be computed only from tenant-visible reviews.
## /products/{id}/reviews
Purpose:
- paginated review feed for product page.
High-level response shape:
- review list
- page/pageSize/total metadata
Tenant rule:
- only tenant-allowed and moderation-approved reviews.
## /products/{id}/questions
Purpose:
- paginated questions and answers feed for product page.
High-level response shape:
- question list with answers
- page/pageSize/total metadata
Tenant rule:
- only tenant-visible questions/answers.
## /products/{id}/reviews (POST)
Purpose:
- create review (rating/title/text/anonymous).
Must not change:
- request validation semantics expected by frontend engagement form.
## /products/{id}/questions (POST)
Purpose:
- create product question (text/anonymous).
Must not change:
- acknowledgement contract expected by frontend engagement form.
## /me/wishlist (future-ready)
Purpose:
- authenticated wishlist synchronization across devices.
High-level response shape:
- wishlist product references
- optional addedAt metadata
Tenant rule:
- wishlist entries must remain tenant-scoped.
## /me/compare (future-ready)
Purpose:
- optional compare list synchronization for authenticated users.
High-level response shape:
- compared product references
- optional addedAt metadata
Tenant rule:
- compare list must be isolated by tenant + user.
## /me/saved-searches (future-ready)
Purpose:
- persist and restore saved search presets.
High-level response shape:
- saved query/filter/sort presets
- timestamps and id
Tenant rule:
- saved searches must be tenant-scoped and user-scoped.
## /me/recently-viewed (future-ready)
Purpose:
- synchronize recently viewed product history for authenticated users.
High-level response shape:
- product references + viewedAt metadata
Tenant rule:
- history must remain tenant-scoped and privacy-safe.
## /categories
Purpose:
- category tree retrieval
- category filtering metadata
High-level response shape:
- hierarchical or flat category collections
- visibility and ordering metadata
Tenant rule:
- category graph resolved per tenant catalog configuration.
Must not change:
- category IDs and parent linkage semantics consumed by frontend domain mapping.
## /orders
Purpose:
- create and track orders
- lifecycle state transitions
High-level response shape:
- order id
- status
- totals and line-items
Tenant rule:
- order creation/query restricted to tenant context.
Must not change:
- order status lifecycle contract integrated with payment and notification flows.
## /cart
Purpose:
- cart synchronization and server-side cart state where applicable
High-level response shape:
- cart items
- totals
- selected delivery/payment metadata
Tenant rule:
- cart state must be isolated by tenant + session/user.
Must not change:
- cart schema expected by checkout and payment request builders.
## /payments
Purpose:
- payment intent/QR/card flow initiation
- payment status querying
High-level response shape:
- payment id/reference
- redirect/QR links
- status fields
Tenant rule:
- payment credentials/routes resolved per tenant context on backend.
Must not change:
- existing payment provider contracts and callback/status semantics.
## 4. Governance for All Business APIs
- tenant derived from request context only
- strict repository-level tenant filtering
- no UI config payloads
- backward compatibility for existing frontend business flows

View File

@@ -1,75 +0,0 @@
# Config Domain
## 1. Purpose
CONFIG DOMAIN provides runtime UI configuration for a tenant.
It enables one frontend build to serve many tenants by changing configuration, not code.
Primary outputs:
- `/bootstrap`
- `/pages/:slug` (static content domain)
## 2. Ownership
Config domain owns:
- tenant public runtime metadata
- theme and visual tokens
- layout mode and page structure
- widget registry metadata pointers
- footer metadata and legal-page mapping
- feature flags and localization mappings
Business domain owns:
- items, categories, cart, orders, payments, auth
## 3. Layout Engine Responsibility
Backend returns layout intent (e.g., `layout.type`) and page graph.
Frontend layout engine composes UI from this graph.
Supported modes:
- default
- sidebar-left
- carousel-home
- minimal
No tenant-specific UI branching in component code.
## 4. Widget Registry Concept
Backend provides `widgetRegistry.manifestUrl`.
Frontend reads manifest and resolves approved widget keys.
Benefits:
- controlled extensibility
- unknown widget safe fallback
- decoupled rollout of widget metadata
## 5. Footer + Static Page System
Footer metadata includes:
- columns and links
- payment icons
- legal page references
- localized copyright
Static pages provide multilingual HTML per slug.
Frontend renders through safe sanitization path.
## 6. Feature Flags
Feature flags in bootstrap:
- enable/disable capabilities at tenant scope
- support gradual rollout
- avoid deployment-based behavior switches
## 7. Config Data vs Business Data
Config data:
- shapes the interface
- relatively low-frequency changes
- public-safe payloads
Business data:
- transactional/catalog state
- high-frequency updates
- operational integrity requirements
## 8. Why Separation Matters
- scalability: independent lifecycle for UI config and business operations
- safety: prevents leaking operational logic into UI composition
- maintainability: clear boundaries and lower coupling
- multi-tenant readiness: behavior changes per tenant without code fork

View File

@@ -1,66 +0,0 @@
# Backend Platform Deployment
## 1. Local Development Setup
Recommended local flow:
1. Start backend API (Node.js) with local tenant mappings.
2. Start frontend Angular app with proxy to backend.
3. Use local domains/hosts file entries for tenant simulation.
Example hosts mapping:
- `alpha.local` -> localhost
- `beta.local` -> localhost
## 2. Environment Variables (Backend)
Infrastructure-focused variables only:
- `PORT`
- `NODE_ENV`
- `DB_URL`
- `REDIS_URL`
- `TENANT_CACHE_TTL_SECONDS`
- `TRUST_PROXY`
- `ALLOWED_HOSTS`
- `LOG_LEVEL`
Guideline:
- do not use environment variables for tenant business behavior branching.
- tenant behavior comes from tenant config data resolved by domain.
## 3. Frontend Proxy Setup
Frontend proxy should route API calls to Node backend:
- `/bootstrap`
- `/pages/*`
- `/items`, `/categories`, `/cart`, `/orders`, `/payments`, `/auth`
Proxy keeps browser-side calls same-origin in local development.
## 4. Production Deployment Flow
1. Deploy stateless Node API instances.
2. Configure ingress/load balancer to preserve host headers.
3. Route all tenant domains to same backend/frontend runtime.
4. Resolve tenant by domain per request.
5. Serve tenant-specific bootstrap + tenant-scoped business responses.
## 5. Multi-Tenant Domain Mapping Strategy
Maintain authoritative mapping table:
- host -> tenantId
- tenant status
- locale/currency defaults
Operational controls:
- admin tooling for host assignment
- cache invalidation on mapping updates
- audit logs for domain changes
## 6. Scaling Considerations (10100+ Tenants)
- horizontal scale backend instances
- distributed cache for tenant + config hot paths
- query/index optimization with tenant-partitioning strategy
- per-tenant rate limiting and quotas
- observability dimensions: tenantId, host, endpoint, latency, error-rate
## 7. Reliability Checklist
- health/readiness probes
- circuit breakers for downstream services
- timeout + retry policy by endpoint class
- graceful degradation for config fetch failures
- rollback strategy for bad config releases

View File

@@ -1,62 +0,0 @@
# Static Pages System
## 1. Endpoint
- Method: `GET`
- Path: `/pages/:slug`
- Scope: tenant-aware by request domain
Supported slugs (example set):
- `about-us`
- `privacy-policy`
- `terms-of-service`
- `returns-policy`
## 2. Storage Model
Pages are stored per tenant:
- key: tenantId + slug
- multilingual content map (locale -> HTML)
- optional SEO metadata per locale
- status (published/draft)
## 3. Example Response
```json
{
"slug": "about-us",
"title": {
"en": "About Us",
"ru": "О компании",
"hy": "Մեր մասին"
},
"content": {
"en": "<h1>About Us</h1><p>...</p>",
"ru": "<h1>О компании</h1><p>...</p>",
"hy": "<h1>Մեր մասին</h1><p>...</p>"
},
"seo": {
"title": { "en": "About Us" },
"description": { "en": "Company information" }
},
"updatedAt": "2026-07-05T09:00:00Z"
}
```
## 4. Frontend Rendering Rule
Frontend renders static pages via safe HTML flow only:
- resolve locale-specific HTML
- sanitize before binding
- bind to template as trusted/safe HTML only in static page component context
## 5. Security Considerations
Backend requirements:
- content moderation/validation pipeline
- disallow dangerous tags/attributes at content publishing stage
- maintain revision history and audit trail
Frontend requirements:
- enforce sanitizer before rendering
- no raw HTML injection in arbitrary components
Platform requirements:
- tenant isolation on page retrieval
- cache with tenant+slug key
- return 404 for missing slug in tenant scope

View File

@@ -1,85 +0,0 @@
# Tenant Resolution
## 1. Goal
Resolve tenant from request domain and enforce strict tenant isolation for all config and business endpoints.
## 2. Middleware Flow
1. Parse request host (`Host`/`X-Forwarded-Host` as trusted by ingress policy).
2. Normalize host (lowercase, strip port, normalize punycode if needed).
3. Resolve tenant record from host mapping store.
4. Validate tenant status (`active`, not suspended/expired).
5. Attach tenant context to request.
6. Continue to route handlers with tenant-scoped services.
## 3. Request Context Attachment
Attach immutable context object, e.g.:
- `req.ctx.tenantId`
- `req.ctx.tenantSlug`
- `req.ctx.host`
- `req.ctx.defaultLocale`
- `req.ctx.allowedLocales`
All downstream services must read tenant from context, not from query params.
## 4. Pseudocode Middleware Example
```ts
async function tenantResolver(req, res, next) {
const host = normalizeHost(req.headers['x-forwarded-host'] || req.headers.host);
if (!host) return res.status(400).json({ error: 'INVALID_HOST' });
const cacheKey = `tenant:host:${host}`;
let tenant = await cache.get(cacheKey);
if (!tenant) {
tenant = await tenantRepository.findByHost(host);
if (tenant) await cache.set(cacheKey, tenant, { ttlSeconds: 300 });
}
if (!tenant) return res.status(404).json({ error: 'TENANT_NOT_FOUND' });
if (!tenant.active) return res.status(403).json({ error: 'TENANT_INACTIVE' });
req.ctx = {
...(req.ctx || {}),
tenantId: tenant.id,
tenantSlug: tenant.slug,
host,
defaultLocale: tenant.defaultLocale,
allowedLocales: tenant.supportedLocales
};
return next();
}
```
## 5. Caching Strategy
Recommended:
- L1 in-memory cache per API instance for hot host lookups.
- L2 distributed cache (Redis) for cross-instance consistency.
- Cache key by host.
- Short TTL (60-300s) + explicit invalidation on tenant changes.
Do not cache authorization decisions globally; only cache tenant mapping metadata.
## 6. Security Rules
- Never trust tenant from client payload.
- Always derive tenant from validated host/context.
- Enforce tenant filter at repository layer for every query.
- Reject cross-tenant IDs even if resource exists globally.
- Emit audit logs for tenant mismatch attempts.
## 7. Edge Cases
### Unknown Domain
- Behavior: return `404 TENANT_NOT_FOUND`.
- Optional: redirect only if explicit global fallback policy exists.
### Inactive Tenant
- Behavior: return `403 TENANT_INACTIVE`.
- Optional: include support contact metadata in response.
### Host Header Poisoning
- Use trusted proxy chain rules.
- Ignore untrusted forwarded host headers.
### Local Development Domains
- Keep explicit local host mapping table.
- No projectName shortcuts for tenant selection.

View File

@@ -1,746 +0,0 @@
# 1. SYSTEM OVERVIEW
Платформа является полностью configuration-driven SaaS-решением для запуска и масштабирования multi-tenant маркетплейсов.
Ключевые принципы:
- Поведение витрины определяется конфигурацией, а не кастомным кодом под каждого клиента.
- Каждый домен однозначно резолвится в конкретный tenant.
- UI формируется только на основе bootstrap JSON.
- Во frontend отсутствуют hardcoded правила по layout, страницам и tenant-ветвлению.
Это позволяет запускать новые магазины без форка frontend-приложения: меняется конфигурация и данные, а не архитектура продукта.
# 2. BOOTSTRAP FLOW
Стандартный поток инициализации:
1. Пользователь открывает домен магазина.
2. Backend определяет tenant по домену.
3. Backend возвращает tenant-specific bootstrap JSON.
4. Frontend валидирует конфигурацию.
5. Frontend динамически строит:
- тему,
- навигацию,
- страницы,
- секции,
- виджеты,
- статические страницы.
6. Данные каталога и товаров подгружаются через API-контракты, указанные в bootstrap.
Итог: один frontend runtime обслуживает множество магазинов, различающихся конфигурацией.
# 3. FULL BOOTSTRAP JSON EXAMPLE
Ниже приведен полный production-grade пример bootstrap JSON с явно именованными сущностями.
```json
{
"schemaVersion": "1.0.0",
"generatedAt": "2026-07-05T10:00:00Z",
"tenant": {
"id": "tenant-dexar-ru",
"name": "Dexar Market RU",
"domain": "dexarmarket.ru",
"slug": "dexar-ru",
"defaultLocale": "ru",
"supportedLocales": ["ru", "en", "hy"],
"defaultCurrency": "RUB",
"supportedCurrencies": ["RUB", "USD", "EUR", "AMD"],
"timezone": "Europe/Moscow"
},
"api": {
"baseUrl": "https://api.dexarmarket.ru",
"endpoints": {
"bootstrap": "/bootstrap",
"categories": "/categories",
"products": "/products",
"productDetails": "/products/{id}",
"search": "/search",
"cart": "/cart"
},
"timeouts": {
"defaultMs": 10000,
"catalogMs": 12000,
"productMs": 12000
}
},
"theme": {
"themeId": "dexar-light",
"colors": {
"primary": "#2F6E5D",
"secondary": "#8FA9A2",
"accent": "#CBE4DA",
"textPrimary": "#1F322D",
"textSecondary": "#5F6E6A",
"backgroundPrimary": "#FFFFFF",
"backgroundSecondary": "#F6F8F7",
"border": "#D5DDDB",
"success": "#1FA97A",
"warning": "#D9941A",
"danger": "#D64545"
},
"typography": {
"fontFamily": "DM Sans, sans-serif",
"headingFontFamily": "DM Sans, sans-serif",
"baseFontSize": 16,
"scale": {
"h1": 40,
"h2": 32,
"h3": 24,
"body": 16,
"caption": 14
}
},
"radius": {
"sm": "8px",
"md": "12px",
"lg": "16px"
},
"shadows": {
"sm": "0 2px 8px rgba(0,0,0,0.08)",
"md": "0 6px 18px rgba(0,0,0,0.12)",
"lg": "0 14px 36px rgba(0,0,0,0.16)"
}
},
"layoutProfile": "default",
"layoutProfiles": {
"default": {
"description": "Стандартный storefront layout с верхней навигацией",
"pageContainer": {
"maxWidth": 1280,
"paddingX": 16,
"paddingY": 24
},
"sectionSpacing": 24,
"grid": {
"gap": 16,
"columnsDesktop": 4,
"columnsTablet": 2,
"columnsMobile": 1
},
"regions": ["header", "content", "footer"]
},
"side-menu-layout": {
"description": "Layout с левой боковой навигацией",
"pageContainer": {
"maxWidth": 1360,
"paddingX": 16,
"paddingY": 24
},
"sectionSpacing": 24,
"grid": {
"gap": 16,
"columnsDesktop": 3,
"columnsTablet": 2,
"columnsMobile": 1
},
"regions": ["header", "side", "content", "footer"]
},
"grid-layout": {
"description": "Плиточная витрина с усиленным grid-представлением",
"pageContainer": {
"maxWidth": 1440,
"paddingX": 20,
"paddingY": 24
},
"sectionSpacing": 20,
"grid": {
"gap": 20,
"columnsDesktop": 5,
"columnsTablet": 3,
"columnsMobile": 2
},
"regions": ["header", "content", "footer"]
},
"landing-page-layout": {
"description": "Промо-лендинг с акцентом на hero и banner секции",
"pageContainer": {
"maxWidth": 1200,
"paddingX": 16,
"paddingY": 32
},
"sectionSpacing": 32,
"grid": {
"gap": 24,
"columnsDesktop": 2,
"columnsTablet": 1,
"columnsMobile": 1
},
"regions": ["header", "content", "footer"]
}
},
"navigation": {
"header": [
{
"id": "nav-logo",
"type": "logo",
"label": "Dexar",
"route": "/",
"order": 1,
"visible": true
},
{
"id": "nav-side-menu",
"type": "side-menu",
"label": "Меню",
"route": "/catalog",
"order": 2,
"visible": true
},
{
"id": "nav-category-menu",
"type": "category-menu",
"label": "Категории",
"route": "/catalog",
"order": 3,
"visible": true
},
{
"id": "nav-search",
"type": "search",
"label": "Поиск",
"route": "/search",
"order": 4,
"visible": true
},
{
"id": "nav-language",
"type": "language-switcher",
"label": "Язык",
"order": 5,
"visible": true
},
{
"id": "nav-currency",
"type": "currency-switcher",
"label": "Валюта",
"order": 6,
"visible": true
},
{
"id": "nav-cart",
"type": "cart",
"label": "Корзина",
"route": "/cart",
"order": 7,
"visible": true
}
],
"footer": [
{
"id": "footer-about",
"type": "footer-links",
"label": "О компании",
"route": "/about",
"order": 1,
"visible": true
},
{
"id": "footer-terms",
"type": "footer-links",
"label": "Условия",
"route": "/terms",
"order": 2,
"visible": true
},
{
"id": "footer-privacy",
"type": "footer-links",
"label": "Конфиденциальность",
"route": "/privacy",
"order": 3,
"visible": true
}
]
},
"widgetManifest": [
{
"type": "hero-widget",
"version": "1.0.0",
"component": "HeroWidgetComponent",
"dataSource": "static",
"enabled": true
},
{
"type": "category-widget",
"version": "1.0.0",
"component": "CategoryWidgetComponent",
"dataSource": "categories",
"enabled": true
},
{
"type": "product-grid-widget",
"version": "1.0.0",
"component": "ProductGridWidgetComponent",
"dataSource": "products",
"enabled": true
},
{
"type": "product-carousel-widget",
"version": "1.0.0",
"component": "ProductCarouselWidgetComponent",
"dataSource": "products",
"enabled": true
},
{
"type": "cart-widget",
"version": "1.0.0",
"component": "CartWidgetComponent",
"dataSource": "cart",
"enabled": true
},
{
"type": "side-menu-widget",
"version": "1.0.0",
"component": "SideMenuWidgetComponent",
"dataSource": "navigation",
"enabled": true
}
],
"catalog": {
"layout": "grid",
"navigationMode": "default",
"defaultSort": "relevance",
"availableSorts": ["relevance", "latest", "price_asc", "price_desc", "rating", "popular", "discount"],
"enabledFilters": ["price", "availability", "rating", "brand", "category", "subcategory", "discount", "new", "color", "size", "attributes"],
"showBreadcrumbs": true,
"showCategoryBanner": true,
"showSubcategoryChips": true,
"showRatings": true,
"showDiscounts": true,
"showAvailability": true,
"suggestionsEnabled": true,
"searchHistoryEnabled": true
},
"productPage": {
"rating": { "enabled": true },
"reviews": {
"enabled": true,
"pageSize": 5,
"showSummary": true
},
"questions": {
"enabled": true,
"pageSize": 5
},
"tabs": {
"enabled": true,
"items": ["description", "specifications", "reviews", "questions", "delivery", "warranty"]
},
"relatedProducts": { "enabled": true }
},
"pages": [
{
"id": "page-home",
"key": "home",
"title": "Главная",
"route": { "path": "/", "exact": true },
"layoutProfile": "default",
"sections": [
{
"id": "home-hero",
"type": "hero",
"order": 1,
"widgets": [
{
"id": "widget-home-hero",
"type": "hero-widget",
"version": "1.0.0",
"props": {
"title": "Маркетплейс нового поколения",
"subtitle": "Запущен на configuration-driven SaaS платформе",
"ctaText": "Перейти в каталог"
}
}
]
},
{
"id": "home-categories",
"type": "categories",
"order": 2,
"widgets": [
{
"id": "widget-home-categories",
"type": "category-widget",
"version": "1.0.0",
"dataSource": {
"name": "categories",
"params": { "rootOnly": true, "limit": 12 }
}
}
]
},
{
"id": "home-featured",
"type": "featured-products",
"order": 3,
"widgets": [
{
"id": "widget-home-featured-carousel",
"type": "product-carousel-widget",
"version": "1.0.0",
"dataSource": {
"name": "products",
"params": { "preset": "featured", "limit": 10 }
}
}
]
},
{
"id": "home-banner",
"type": "banner",
"order": 4,
"widgets": [
{
"id": "widget-home-banner",
"type": "hero-widget",
"version": "1.0.0",
"props": {
"title": "Летняя распродажа",
"subtitle": "Скидки до 30%",
"ctaText": "Смотреть предложения"
}
}
]
},
{
"id": "home-footer-links",
"type": "footer-links",
"order": 5,
"widgets": [
{
"id": "widget-home-footer-links",
"type": "side-menu-widget",
"version": "1.0.0",
"dataSource": { "name": "navigation", "params": { "zone": "footer" } }
}
]
}
]
},
{
"id": "page-catalog",
"key": "catalog",
"title": "Каталог",
"route": { "path": "/catalog", "exact": true },
"layoutProfile": "side-menu-layout",
"sections": [
{
"id": "catalog-sidebar",
"type": "sidebar-categories",
"order": 1,
"widgets": [
{
"id": "widget-catalog-side-menu",
"type": "side-menu-widget",
"version": "1.0.0",
"dataSource": { "name": "categories", "params": { "tree": true } }
}
]
},
{
"id": "catalog-grid",
"type": "product-grid",
"order": 2,
"widgets": [
{
"id": "widget-catalog-product-grid",
"type": "product-grid-widget",
"version": "1.0.0",
"dataSource": {
"name": "products",
"params": { "sort": "priority_desc", "pageSize": 20 }
}
}
]
}
]
},
{
"id": "page-product",
"key": "product",
"title": "Карточка товара",
"route": { "path": "/product/:id", "exact": true },
"layoutProfile": "default",
"sections": [
{
"id": "product-main-grid",
"type": "product-grid",
"order": 1,
"widgets": [
{
"id": "widget-product-main",
"type": "product-grid-widget",
"version": "1.0.0",
"dataSource": {
"name": "productDetails",
"params": { "fromRoute": "id" }
}
}
]
},
{
"id": "product-recommendations",
"type": "product-carousel",
"order": 2,
"widgets": [
{
"id": "widget-product-recommendations",
"type": "product-carousel-widget",
"version": "1.0.0",
"dataSource": {
"name": "products",
"params": { "preset": "related", "limit": 12 }
}
}
]
},
{
"id": "product-cart",
"type": "featured-products",
"order": 3,
"widgets": [
{
"id": "widget-product-cart",
"type": "cart-widget",
"version": "1.0.0",
"dataSource": { "name": "cart", "params": {} }
}
]
}
]
}
],
"staticPages": [
{
"id": "static-about",
"key": "about",
"title": "О компании",
"route": { "path": "/about", "exact": true },
"content": {
"source": "cms",
"contentType": "html",
"value": "<h1>О компании</h1><p>Dexar Market - платформа маркетплейса для B2B/B2C продаж.</p>"
},
"visible": true
},
{
"id": "static-terms",
"key": "terms",
"title": "Условия использования",
"route": { "path": "/terms", "exact": true },
"content": {
"source": "cms",
"contentType": "html",
"value": "<h1>Условия использования</h1><p>Правила работы сервиса и обязательства сторон.</p>"
},
"visible": true
},
{
"id": "static-privacy",
"key": "privacy",
"title": "Политика конфиденциальности",
"route": { "path": "/privacy", "exact": true },
"content": {
"source": "cms",
"contentType": "html",
"value": "<h1>Политика конфиденциальности</h1><p>Порядок обработки персональных данных.</p>"
},
"visible": true
}
],
"features": {
"multiLanguage": true,
"multiCurrency": true,
"regionSelector": true,
"guestCheckout": true,
"searchEnabled": true,
"recommendationsEnabled": true
}
}
```
# 4. FEATURE REGISTRY TABLE
Ниже перечислены поддерживаемые возможности платформы в удобном формате: что это, где применяется и как выглядит в JSON.
## 4.1 Layout Features
- `default` (type: layout): базовый профиль витрины с верхней навигацией.
- `side-menu-layout` (type: layout): профиль с боковым меню категорий и контентной зоной.
- `grid-layout` (type: layout): плиточный профиль для плотного товарного листинга.
- `landing-page-layout` (type: layout): профиль лендинга с акцентом на hero/banner.
Пример использования layout:
```json
{
"layoutProfile": "side-menu-layout",
"layoutProfiles": {
"default": { "sectionSpacing": 24 },
"side-menu-layout": { "regions": ["header", "side", "content", "footer"] },
"grid-layout": { "grid": { "columnsDesktop": 5 } },
"landing-page-layout": { "sectionSpacing": 32 }
}
}
```
## 4.2 Navigation Features
- `logo` (type: navigation): блок логотипа в header.
- `side-menu` (type: navigation): триггер бокового меню.
- `category-menu` (type: navigation): навигация по категориям.
- `cart` (type: navigation): переход к корзине.
- `search` (type: navigation): точка входа в поиск.
- `language-switcher` (type: navigation): переключение языка.
- `currency-switcher` (type: navigation): переключение валюты.
Пример использования navigation:
```json
{
"navigation": {
"header": [
{ "type": "logo", "route": "/" },
{ "type": "side-menu", "route": "/catalog" },
{ "type": "category-menu", "route": "/catalog" },
{ "type": "search", "route": "/search" },
{ "type": "language-switcher" },
{ "type": "currency-switcher" },
{ "type": "cart", "route": "/cart" }
]
}
}
```
## 4.3 Section Features
- `hero` (type: section): главная промо-секция страницы.
- `categories` (type: section): блок категорий.
- `product-grid` (type: section): сетка товаров.
- `product-carousel` (type: section): карусель товаров.
- `sidebar-categories` (type: section): боковая колонка категорий.
- `featured-products` (type: section): выделенный блок рекомендованных товаров.
- `banner` (type: section): баннерная секция.
- `footer-links` (type: section): секция ссылок в футере.
Пример использования sections:
```json
{
"sections": [
{ "type": "hero", "order": 1 },
{ "type": "categories", "order": 2 },
{ "type": "featured-products", "order": 3 },
{ "type": "banner", "order": 4 },
{ "type": "footer-links", "order": 5 }
]
}
```
## 4.4 Widget Features
- `hero-widget` (type: widget): виджет hero-контента.
- `category-widget` (type: widget): виджет списка/сетки категорий.
- `product-grid-widget` (type: widget): виджет товарной сетки.
- `product-carousel-widget` (type: widget): виджет товарной карусели.
- `cart-widget` (type: widget): виджет корзины.
- `side-menu-widget` (type: widget): виджет бокового меню.
Пример использования widgets:
```json
{
"widgets": [
{ "type": "hero-widget", "version": "1.0.0" },
{ "type": "category-widget", "version": "1.0.0" },
{ "type": "product-grid-widget", "version": "1.0.0" },
{ "type": "product-carousel-widget", "version": "1.0.0" },
{ "type": "cart-widget", "version": "1.0.0" },
{ "type": "side-menu-widget", "version": "1.0.0" }
]
}
```
## 4.5 Feature Flags
- `multiLanguage` (type: feature): включает мультиязычность storefront.
- `multiCurrency` (type: feature): включает мультивалютный режим.
- `regionSelector` (type: feature): включает выбор региона.
- `guestCheckout` (type: feature): разрешает checkout без авторизации.
- `searchEnabled` (type: feature): включает поиск по каталогу.
- `recommendationsEnabled` (type: feature): включает рекомендательные блоки.
Пример использования feature flags:
```json
{
"features": {
"multiLanguage": true,
"multiCurrency": true,
"regionSelector": true,
"guestCheckout": true,
"searchEnabled": true,
"recommendationsEnabled": true
}
}
```
# 5. LAYOUT ENGINE EXPLANATION
Layout Engine применяет выбранный профиль layoutProfile для каждой страницы и определяет:
- контейнер страницы (ширина, внутренние отступы);
- интервалы между секциями;
- grid-параметры (колонки и gap);
- доступные regions (header/content/side/footer).
Как отличается side-menu-layout от default:
- default: акцент на центральный контент и верхнюю навигацию;
- side-menu-layout: добавляется регион side для боковой навигации и фильтров, контентный поток меняется на двухзонный.
Позиционирование виджетов:
- виджеты размещаются по секциям и регионам, заданным конфигурацией страницы;
- порядок и тип секций контролируются order и type;
- frontend не содержит hardcoded матриц layout.
Ключевой принцип: layout полностью декларативен, а не зашит в Angular-компоненты страниц.
# 6. STRICT RULES
## DO NOT
- Do NOT hardcode tenant logic in frontend.
- Do NOT define layout in Angular components.
- Do NOT call APIs inside widgets.
- Do NOT add project-specific conditions.
- Do NOT duplicate config logic across JSON files.
Дополнительные обязательные ограничения:
- Нельзя смешивать обязанности модулей конфигурации (theme, navigation, pages, features).
- Нельзя добавлять новые обязательные поля без повышения schemaVersion.
- Нельзя нарушать domain-to-tenant резолвинг альтернативными источниками истины.
# 7. EXTENSIBILITY MODEL
Платформа расширяется конфигурационно без изменений бизнес-логики frontend:
1. Новый виджет:
- Добавляется в widgetManifest.
- Привязывается к section через widgets[].type.
- Контент/данные подаются через props/dataSource.
2. Новый layout:
- Добавляется в layoutProfiles.
- Назначается страницам через pages[].layoutProfile.
3. Новая страница:
- Добавляется в pages с route, sections и widgets.
- Сразу участвует в runtime-рендеринге.
4. Контентные изменения:
- Меняются только JSON-конфигурации и backend-данные.
- Изменения контента не требуют модификации frontend-кода при сохранении контрактов.
Итоговая модель масштабирования:
- Tenant onboarding выполняется через домен, bootstrap и данные.
- Продукт расширяется через registry-подход.
- Архитектура остается стабильной при росте количества магазинов.

View File

@@ -1,93 +0,0 @@
# 00. Обзор платформы
## Master Summary
Платформа представляет собой многоарендный SaaS-конструктор маркетплейсов, в котором витрина, структура страниц, виджеты, темы и навигация формируются из конфигурации, а не из кастомного кода под каждого клиента. Каждый магазин (tenant) определяется строго по доменному имени, после чего frontend загружает bootstrap.json и строит UI динамически.
### Что это за платформа
- Конфигурационно-управляемая marketplace-платформа для запуска нескольких магазинов на единой кодовой базе.
- Визуальная и функциональная сборка витрины выполняется через bootstrap.json и связанные JSON-модули.
- Backend предоставляет данные домена: категории, товары, остатки, цены, медиа и справочники.
### Как создается новый маркетплейс
1. Регистрируется домен нового клиента и на backend настраивается tenant-конфигурация.
2. Готовится bootstrap.json (страницы, секции, виджеты, тема, маршруты, feature flags).
3. Подключаются API-эндпоинты каталога, категорий и карточек товаров.
4. Выполняется smoke-проверка: tenant resolution, загрузка bootstrap, рендер главной, каталог, карточка товара.
### Что должен сделать клиент для запуска нового магазина
- Предоставить домен и бренд-материалы (логотип, цвета, шрифты, иконки).
- Утвердить структуру страниц и навигации.
- Подтвердить каталогные правила (категории, витрины, карточки, фильтры).
- Подтвердить статический контент (о компании, политика, доставка, возвраты).
### Что обязаны реализовать backend-команды
- Доменную идентификацию tenant и выдачу tenant-aware bootstrap-конфигурации.
- API для категорий, товаров, карточек и связанных коллекций.
- Гарантированную стабильность контрактов JSON и версионирование schemaVersion.
- SLA по доступности и времени ответа, достаточные для runtime-инициализации UI.
## Назначение документа
Документ описывает бизнес-границы платформы, обязательные принципы архитектуры и процесс запуска нового tenant без изменения frontend-кода.
## Обязательные JSON-поля платформенного bootstrap
- schemaVersion: версия контракта конфигурации.
- tenant: идентификатор и параметры арендатора.
- theme: токены темы (цвета, типографика, радиусы, тени).
- pages: список страниц с секциями и виджетами.
- apiEndpoints: карта backend-эндпоинтов.
## Опциональные JSON-поля
- featureFlags: флаги включения функциональности.
- localization: список языков и словарей.
- seo: SEO-конфигурация страниц.
- permissions: роли и разрешения для административных зон.
## Строгие правила
- Нельзя хардкодить tenant-логику во frontend.
- Tenant определяется только по домену.
- UI генерируется из bootstrap.json; ручная сборка страниц запрещена.
- Виджеты не вызывают API напрямую.
- Layout управляется только конфигурацией.
- Изменения контрактов выполняются только через версионирование schemaVersion.
## Пример JSON (сокращенно)
```json
{
"schemaVersion": "1.0.0",
"tenant": {
"id": "tenant-acme",
"slug": "acme",
"host": "shop.acme.com",
"defaultLocale": "ru"
},
"theme": {
"themeId": "acme-light",
"palette": {
"primary": "#1F6B5C",
"backgroundPrimary": "#FFFFFF"
}
},
"pages": [
{
"id": "home",
"route": { "path": "/" },
"sections": []
}
]
}
```
## Ответственность Frontend
- Разрешить tenant по домену и загрузить bootstrap-конфигурацию.
- Валидировать обязательные поля и безопасно обрабатывать отсутствие опциональных.
- Построить страницы, секции и виджеты без tenant-specific условных веток.
## Ответственность Backend
- Возвращать валидный bootstrap JSON для каждого tenant.
- Поддерживать согласованные API-контракты каталога.
- Обеспечивать обратную совместимость либо явно повышать schemaVersion.
## Project Editor
- Внутренний Project Editor редактирует только bootstrap-конфигурацию маркетплейса.
- Editor не управляет товарами, категориями, заказами или аналитикой.
- Первичная версия поддерживает секции general, branding, theme, header, footer, homepage, widgets, marketplace features и preview.

View File

@@ -1,81 +0,0 @@
# 01. Архитектура платформы
## Назначение
Документ определяет архитектурную модель многоарендной платформы маркетплейсов и обязательные границы между конфигурацией, frontend-runtime и backend-данными.
## Поведение системы
- Платформа использует единую frontend-кодовую базу для всех tenants.
- При старте приложение определяет tenant по домену.
- Затем загружается bootstrap-конфигурация.
- На основе конфигурации рендерятся страницы, секции и виджеты.
- Доменный контент (товары, категории, остатки) подгружается через backend API.
## Архитектурные слои
1. Tenant Resolution Layer: определение tenant из host.
2. Bootstrap Layer: загрузка конфигурации UI и маршрутов.
3. Section/Layout Engine: построение структуры страницы.
4. Widget Engine: отрисовка и наполнение reusable виджетов.
5. Domain Data Layer: доступ к API продуктов и категорий.
## Обязательные JSON-секции
- tenant
- layout
- pages
- sections
- widgets
- widgetRegistry
- theme
- apiEndpoints
- footer
- staticPages
## Опциональные JSON-секции
- featureFlags
- localization
- seo
- permissions
- integrations
## Строгие правила
- Запрещено смешивать layout-логику и data-fetch в виджетах.
- Запрещено tenant-specific ветвление в компонентах frontend.
- Backend может отдавать HTML-контент только для статических страниц (about/privacy/terms) через контролируемый контракт.
- Такой контент рендерится только через безопасную sanitization-цепочку.
- Запрещено добавлять новые обязательные поля без обновления schemaVersion.
## Пример архитектурного bootstrap-фрагмента
```json
{
"tenant": {
"id": "tenant-novo",
"host": "novo.marketplace.com"
},
"apiEndpoints": {
"catalog": { "baseUrl": "https://api.marketplace.com" }
},
"pages": [
{
"id": "home",
"sections": [
{
"id": "hero-1",
"type": "hero",
"widgets": [
{ "id": "w-hero", "type": "hero", "dataSource": { "kind": "static" } }
]
}
]
}
]
}
```
## Ответственность Frontend
- Следовать слоям архитектуры без cross-layer обходов.
- Выполнять fail-safe рендер при частично валидной конфигурации.
- Логировать нарушения контрактов конфигурации.
## Ответственность Backend
- Отдавать данные строго по контракту API.
- Гарантировать tenant-aware ответы.
- Поддерживать прогнозируемую схему и документацию изменений.

View File

@@ -1,179 +0,0 @@
# 02. Спецификация bootstrap.json
## Назначение
Bootstrap JSON является главным конфигурационным документом витрины. Он определяет структуру страниц, секций, виджетов, тему и подключение источников данных.
## Поведение системы
- Frontend загружает bootstrap.json на старте runtime.
- Конфигурация валидируется по обязательным полям.
- После валидации строится UI без хардкода tenant-логики.
## Обязательные свойства
- schemaVersion: string
- tenant: object
- theme: object
- layout: object
- pages: array
- apiEndpoints: object
### Обязательные свойства tenant
- id: string
- slug: string
- host: string
- defaultLocale: string
- supportedLocales: string[]
### Обязательные свойства страницы
- id: string
- key: string
- route.path: string
- sections: array
### Обязательные свойства секции
- id: string
- type: string
- order: number
- widgets: array
### Обязательные свойства виджета
- id: string
- type: string
- version: string
### Поддерживаемые layout.type
- default
- sidebar-left
- carousel-home
- minimal
## Опциональные свойства
- featureFlags
- localization
- seo
- permissions
- branding
- navigation
- footer
- catalog
- productPage
- userExperience
- staticPages
- widgetRegistry
- visibility
- layout
- dataSource
## Строгие правила
- Поля обязательной схемы не могут быть null.
- route.path должен быть уникальным в рамках tenant.
- id страниц, секций и виджетов должен быть уникальным в своей области.
- В одной секции порядок order не может дублироваться.
- Виджеты не содержат backend URL в props; URL управляются только apiEndpoints.
- Для виджетов допустимы metadata поля: order, padding, visibility.desktop/tablet/mobile.
- Для footer links/legal/payout icons источник истины — bootstrap JSON.
- Для staticPages контент поддерживается в формате multilingual HTML и рендерится только через safe sanitizer.
- Для productPage допускаются только feature-конфиги (enabled/pageSize/tabs/showSummary), без доменных данных отзывов и вопросов.
- Для catalog допускаются только UI/feature-конфиги (layout/sorts/filters/visibility toggles), без товарных данных.
### Catalog Config (опционально)
- catalog.layout: grid | large-grid | compact-grid | list
- catalog.navigationMode: default | left-category-navigation | mega-category-layout | top-category-carousel
- catalog.defaultSort: relevance | latest | price_asc | price_desc | rating | popular | discount
- catalog.availableSorts: string[]
- catalog.enabledFilters: string[]
- catalog.showBreadcrumbs: boolean
- catalog.showCategoryBanner: boolean
- catalog.showSubcategoryChips: boolean
- catalog.showRatings: boolean
- catalog.showDiscounts: boolean
- catalog.showAvailability: boolean
- catalog.suggestionsEnabled: boolean
- catalog.searchHistoryEnabled: boolean
### Product Engagement Config (опционально)
- productPage.rating.enabled: boolean
- productPage.reviews.enabled: boolean
- productPage.reviews.pageSize: number
- productPage.reviews.showSummary: boolean
- productPage.questions.enabled: boolean
- productPage.questions.pageSize: number
- productPage.tabs.enabled: boolean
- productPage.tabs.items: array (description/specifications/reviews/questions/delivery/warranty)
- productPage.relatedProducts.enabled: boolean
### User Experience Config (опционально)
- userExperience.wishlist.enabled: boolean
- userExperience.wishlist.headerBadgeEnabled: boolean
- userExperience.compare.enabled: boolean
- userExperience.compare.maxItems: number
- userExperience.compare.hideIdenticalDefault: boolean
- userExperience.compare.highlightDifferencesDefault: boolean
- userExperience.recentlyViewed.enabled: boolean
- userExperience.recentlyViewed.maxItems: number
- userExperience.recentlyViewed.widgetEnabled: boolean
- userExperience.share.enabled: boolean
- userExperience.continueBrowsing.enabled: boolean
- userExperience.savedSearches.enabled: boolean
- userExperience.savedSearches.maxItems: number
Правило:
- Для userExperience допускаются только feature-конфиги (flags/limits/default behaviors), без пользовательских списков (wishlist/compare/recentlyViewed/saved searches) и без product payloads.
## Пример полного минимального bootstrap
```json
{
"schemaVersion": "1.0.0",
"tenant": {
"id": "tenant-default",
"slug": "default",
"host": "default.marketplace.com",
"defaultLocale": "ru",
"supportedLocales": ["ru", "en"]
},
"theme": {
"themeId": "default-light",
"palette": {
"primary": "#497671",
"textPrimary": "#1e3c38",
"backgroundPrimary": "#ffffff"
}
},
"apiEndpoints": {
"catalog": { "baseUrl": "https://api.marketplace.com" },
"bootstrap": { "path": "/bootstrap" }
},
"pages": [
{
"id": "page-home",
"key": "home",
"route": { "path": "/", "exact": true },
"sections": [
{
"id": "section-hero",
"type": "hero",
"order": 1,
"widgets": [
{ "id": "widget-hero", "type": "hero", "version": "1.0.0" }
]
}
]
}
]
}
```
## Ответственность Frontend
- Валидировать обязательные поля до рендера.
- Применять значения опциональных полей только при наличии.
- Прекращать инициализацию при критической невалидности схемы.
## Ответственность Backend
- Отдавать tenant-specific bootstrap.json.
- Поддерживать schemaVersion и changelog контракта.
- Не включать frontend-специфические runtime-хуки в JSON.
## Project Editor Coverage
- Marketplace Project Editor изменяет bootstrap-конфигурацию без редактирования доменных сущностей.
- Editor работает со строго типизированными разделами: general, branding, theme, header, footer, homepage, widgets, marketplace features.
- Под preview допускается in-memory override bootstrap snapshot без полной перезагрузки браузера.
- Будущие builder endpoints должны повторять контракты BootstrapConfig без tenant-specific веток во frontend.

View File

@@ -1,68 +0,0 @@
# 03. Тема и дизайн-система
## Назначение
Тема задает визуальные токены бренда tenant: цвета, типографику, радиусы, тени и базовые параметры визуальной консистентности.
## Поведение системы
- Theme токены загружаются из bootstrap.json.
- Frontend применяет токены через CSS-переменные.
- Компоненты и виджеты используют только токены, а не hardcoded brand-значения.
## Обязательные свойства JSON
- theme.themeId
- theme.palette.primary
- theme.palette.textPrimary
- theme.palette.backgroundPrimary
- theme.typography.primaryFontFamily
- theme.typography.baseFontSize
## Опциональные свойства JSON
- theme.palette.secondary
- theme.palette.accent
- theme.borderRadiusScale
- theme.shadows
- theme.iconSet
- theme.mode
## Строгие правила
- Нельзя хардкодить tenant-цвета в компонентах.
- Нельзя задавать типографику вне theme токенов для брендовых элементов.
- Нельзя смешивать несколько themeId одновременно для одной витрины.
- При отсутствии опционального токена используется системный fallback.
## Пример theme JSON
```json
{
"theme": {
"themeId": "novo-light",
"mode": "light",
"palette": {
"primary": "#2F6E5D",
"secondary": "#8FA9A2",
"accent": "#B9D9CF",
"textPrimary": "#1F322D",
"backgroundPrimary": "#FFFFFF"
},
"typography": {
"primaryFontFamily": "DM Sans, sans-serif",
"headingFontFamily": "DM Sans, sans-serif",
"baseFontSize": 16
},
"borderRadiusScale": {
"sm": "8px",
"md": "12px",
"lg": "16px"
}
}
}
```
## Ответственность Frontend
- Маппить токены в CSS custom properties.
- Применять fallback токены для опциональных полей.
- Обеспечивать визуальную консистентность между страницами и виджетами.
## Ответственность Backend
- Выдавать валидный theme объект для каждого tenant.
- Контролировать полноту обязательных токенов.
- Поддерживать совместимость theme-контракта между версиями.

View File

@@ -1,67 +0,0 @@
# 04. Layout Engine
## Назначение
Layout Engine отвечает за композицию страницы из секций по данным конфигурации и управляет только структурой и позиционированием, без доменной бизнес-логики.
## Поведение системы
- Engine читает page.sections.
- Секции сортируются по order.
- Для каждой секции применяется layout-стратегия.
- Виджеты размещаются внутри секции согласно layout-параметрам.
## Обязательные свойства JSON
- section.id
- section.type
- section.order
- section.widgets
## Опциональные свойства JSON
- section.layout.strategy
- section.layout.columns
- section.layout.gap
- section.layout.align
- section.visibility.desktop/tablet/mobile
- section.featureFlag
## Строгие правила
- Layout определяется только конфигурацией.
- Виджет не может переопределять секционный grid/columns на уровне страницы.
- Если section.visible=false, секция не рендерится.
- Секция должна рендериться в стандартном каркасе: section + page container.
## Пример JSON секции
```json
{
"id": "section-featured-products",
"type": "product-collection",
"order": 2,
"layout": {
"strategy": "grid",
"columns": 4,
"gap": "16px",
"align": "stretch"
},
"visibility": {
"desktop": true,
"tablet": true,
"mobile": true
},
"widgets": [
{
"id": "widget-featured",
"type": "product-carousel",
"version": "1.0.0"
}
]
}
```
## Ответственность Frontend
- Корректно применять сортировку и layout-параметры.
- Гарантировать единые отступы и контейнеры секций.
- Безопасно деградировать при частично некорректном layout.
## Ответственность Backend
- Отдавать корректные layout-атрибуты в bootstrap.
- Не смешивать контентные данные с layout-инструкциями.
- Поддерживать непротиворечивость секций внутри страницы.

View File

@@ -1,63 +0,0 @@
## Project Editor Widget Support
- Project Editor предоставляет typed editing для homepage widgets.
- Hero: layout, height, overlay, autoplay.
- Categories: layout, columns.
- Product collection: layout, cards per row, filters, badges, rating, price.
- Остальные виджеты редактируются через JSON props fallback до появления специализированных editors.
# 05. Widget System
## Назначение
Widget System предоставляет переиспользуемые UI-блоки для сборки страниц из конфигурации без дублирования логики и без tenant-specific кода.
## Поведение системы
- Widget Engine выбирает компонент по widget.type и version.
- Виджет получает входные props и resolved data.
- В случае отсутствия регистрации используется fallback unknown-widget.
## Обязательные свойства JSON
- widget.id
- widget.type
- widget.version
## Опциональные свойства JSON
- widget.props
- widget.dataSource
- widget.featureFlag
- widget.visible
- widget.events
## Строгие правила
- Виджеты не вызывают API напрямую.
- Виджеты не управляют page-level margin/padding/layout.
- Виджет может управлять только внутренней разметкой и презентацией.
- Любой новый widget.type должен быть зарегистрирован в реестре.
## Пример JSON виджета
```json
{
"id": "widget-categories-main",
"type": "categories",
"version": "1.0.0",
"props": {
"title": "Категории",
"emptyMessage": "Категории скоро появятся"
},
"dataSource": {
"name": "categories",
"params": {
"rootOnly": true,
"limit": 12
}
}
}
```
## Ответственность Frontend
- Разрешать тип виджета через registry/manifest.
- Передавать только подготовленные данные в компонент виджета.
- Блокировать прямые API-вызовы из слоя UI-виджета.
## Ответственность Backend
- Поставлять данные в форматах, ожидаемых data resolvers.
- Обеспечивать консистентность ID и ссылок между сущностями.
- Не внедрять frontend-специфичные инструкции в props виджетов.

View File

@@ -1,121 +0,0 @@
# 06. API-контракты
## Назначение
Документ определяет стабильные контракты API для данных маркетплейса. Backend предоставляет только данные, frontend отвечает за представление.
## Поведение системы
- API base URL определяется tenant-конфигурацией.
- Frontend отправляет запросы через единый API слой и интерсепторы.
- Ответы маппятся в доменные модели frontend.
## Обязательные свойства JSON (ответы API)
- status или корректный HTTP status code
- data (основная полезная нагрузка)
- id для доменных сущностей
## Опциональные свойства JSON
- meta (pagination, total, filters)
- errors (детализация ошибок)
- warnings
## Строгие правила
- Backend не должен отдавать HTML для витрины.
- Контракты должны быть обратно совместимы в пределах одной major-версии.
- В ответах на списки должна поддерживаться пагинация.
- Ошибки API должны быть машиночитаемыми и локализуемыми на frontend.
## Product Engagement API (ожидаемый контракт)
- GET /products/{id}/rating
- возвращает агрегированную оценку и распределение по звездам.
- GET /products/{id}/reviews
- поддерживает пагинацию (page/pageSize).
- GET /products/{id}/questions
- поддерживает пагинацию (page/pageSize).
- POST /products/{id}/reviews
- принимает rating/title/text/anonymous.
- POST /products/{id}/questions
- принимает text/anonymous.
Правило:
- Feature UI не вызывает API напрямую; запросы идут через ProductFacade -> domain service -> provider/repository.
## Advanced Catalog/Search Expectations
- Suggestions endpoint (future-ready):
- GET /search/suggestions?q={term}
- response: suggestion strings with optional popularity/count metadata.
- Dynamic filter metadata endpoint (future-ready):
- GET /catalog/filters?category={id}&q={term}
- response: filter definitions/options that frontend can render without hardcoded filter schema.
- Sort extension contract:
- Backend may introduce new sort IDs via bootstrap `catalog.availableSorts`.
- Frontend must render unknown sort keys safely if label mapping is provided.
## User Experience API Expectations (architecture-ready)
- Wishlist (authenticated mode, future-ready):
- GET /me/wishlist
- POST /me/wishlist
- DELETE /me/wishlist/{itemId}
- Compare list (optional sync for authenticated mode):
- GET /me/compare
- POST /me/compare
- DELETE /me/compare/{itemId}
- Saved searches:
- GET /me/saved-searches
- POST /me/saved-searches
- DELETE /me/saved-searches/{id}
- Recently viewed sync (optional):
- GET /me/recently-viewed
- POST /me/recently-viewed
Правила:
- Guest mode может хранить UX данные локально (local storage) без backend-запросов.
- UI не вызывает HttpClient напрямую: Feature -> Facade -> Repository/Provider.
## Пример API ответа: категории
```json
{
"data": [
{
"id": 101,
"title": "Смартфоны",
"parentId": null,
"priority": 1,
"visible": true
}
],
"meta": {
"total": 1
}
}
```
## Пример API ответа: товары
```json
{
"data": {
"items": [
{
"itemID": 5001,
"name": "Phone X",
"price": 49990,
"currency": "RUB",
"categoryID": 101,
"visible": true
}
],
"total": 1,
"skip": 0,
"count": 20
}
}
```
## Ответственность Frontend
- Маппинг API DTO в доменные модели.
- Центральная обработка ошибок и retry-стратегий.
- Кеширование и переиспользование данных без нарушения актуальности.
## Ответственность Backend
- Гарантировать SLA и стабильность контрактов.
- Возвращать tenant-correct данные.
- Поддерживать фильтрацию, пагинацию и сортировку для каталога.

View File

@@ -1,55 +0,0 @@
# 07. Tenant System
## Назначение
Tenant System обеспечивает запуск нескольких независимых магазинов на единой платформе через доменное разделение и конфигурационный bootstrap.
## Поведение системы
- Tenant определяется только по hostname запроса.
- По tenant выбираются конфигурации bootstrap, тема, локализация и API base.
- Frontend не хранит статических tenant-switch правил в коде.
## Обязательные свойства JSON
- tenant.id
- tenant.slug
- tenant.host
- tenant.defaultLocale
- tenant.supportedLocales
- tenant.defaultCurrency
## Опциональные свойства JSON
- tenant.timezone
- tenant.brandName
- tenant.websiteBaseUrl
- tenant.builderBaseUrl
- tenant.backofficeBaseUrl
## Строгие правила
- Нельзя определять tenant через query params или localStorage как источник истины.
- Нельзя хардкодить tenant ID внутри компонентов.
- Один домен может быть связан только с одним активным tenant в момент запроса.
- При отсутствии tenant-конфигурации runtime должен завершаться контролируемой ошибкой.
## Пример tenant JSON
```json
{
"tenant": {
"id": "tenant-lavero",
"slug": "lavero",
"host": "lavero.marketplace.com",
"defaultLocale": "ru",
"supportedLocales": ["ru", "en", "hy"],
"defaultCurrency": "RUB",
"timezone": "Europe/Moscow"
}
}
```
## Ответственность Frontend
- Резолвить tenant на старте приложения.
- Использовать tenant-параметры для формирования маршрутов, локали и API-слоя.
- Исключать fallback на чужой tenant без явной backend-политики.
## Ответственность Backend
- Поддерживать доменно-tenant маппинг.
- Возвращать корректную tenant-конфигурацию и bootstrap.
- Контролировать изоляцию данных между tenants.

View File

@@ -1,88 +0,0 @@
# 08. Каталогный домен
## Назначение
Каталогный домен описывает правила формирования витрин, листингов и поисковых выборок для tenant-магазина.
## Поведение системы
- Каталог строится из API-данных и bootstrap-конфигурации.
- Bootstrap определяет структуру страниц каталога и виджеты.
- API возвращает содержимое: товары, категории, метаданные фильтров.
## Обязательные JSON-свойства каталога
- catalog.defaultSort
- catalog.availableSorts
- catalog.enabledFilters
- catalog.layout
## Опциональные свойства
- catalog.navigationMode
- catalog.loadingStrategy
- catalog.showBreadcrumbs
- catalog.showCategoryBanner
- catalog.showSubcategoryChips
- catalog.showRatings
- catalog.showDiscounts
- catalog.showAvailability
- catalog.suggestionsEnabled
- catalog.searchHistoryEnabled
- catalog.features (via bootstrap.features)
- catalog.facets (future backend-driven)
## Строгие правила
- Каталог не содержит tenant-specific условий в frontend-коде.
- Сортировка и фильтры должны быть согласованы между frontend и backend.
- Видимость товаров контролируется данными backend, а не frontend-хардкодом.
- Пустая категория рендерит отдельный catalog empty-state, а не product grid empty-state.
- Корневой переход `All Categories` всегда возвращает category browser `/catalog`.
- Loading strategy выбирается конфигурацией без дублирования list logic.
## Reusable Domain Models
- SearchCriteria
- FilterDefinition
- FilterOption
- SortDefinition
- CatalogView
- SearchResult
## Пример catalog JSON (bootstrap fragment)
```json
{
"catalog": {
"layout": "grid-4",
"loadingStrategy": "pagination",
"defaultSort": "relevance",
"availableSorts": ["relevance", "latest", "price_asc", "price_desc", "rating", "popular", "discount"],
"enabledFilters": ["price", "availability", "rating", "brand", "category", "subcategory", "discount", "new", "color", "size", "attributes"],
"showBreadcrumbs": true,
"showCategoryBanner": true,
"showSubcategoryChips": true
}
}
```
## Loading Strategies
- `pagination`
- `loadMore`
- `infiniteScroll`
## Grid Modes
- `grid-2`
- `grid-3`
- `grid-4`
- `list`
- `compact`
Legacy aliases still normalize safely:
- `grid`
- `large-grid`
- `compact-grid`
## Ответственность Frontend
- Отобразить листинг, фильтры, сортировки и пагинацию.
- Синхронизировать состояние каталога с URL.
- Стабильно обрабатывать пустые и частично заполненные наборы данных.
## Ответственность Backend
- Возвращать согласованные данные для листингов и фильтров.
- Гарантировать корректные totals/pagination.
- Поддерживать стабильные ключи сортировки и фильтрации.

View File

@@ -1,49 +0,0 @@
# 09. Домен категорий
## Назначение
Категорийный домен описывает иерархию каталога, правила вложенности и отображения категорий.
## Поведение системы
- Frontend получает плоский список или дерево категорий из backend.
- Для витрины строится дерево root -> children.
- Выбор категории влияет на выборку товаров и хлебные крошки.
## Обязательные JSON-свойства категории
- id
- title
- parentId (null для корня)
- visible
- priority
## Опциональные свойства
- icon
- image
- itemCount
- seo
- translations
## Строгие правила
- id категории должен быть уникальным в tenant.
- Циклические ссылки parentId запрещены.
- Невидимые категории не отображаются в публичной витрине.
- Порядок показа определяется priority, затем id.
## Пример JSON категорий
```json
{
"data": [
{ "id": 1, "title": "Электроника", "parentId": null, "visible": true, "priority": 1 },
{ "id": 2, "title": "Смартфоны", "parentId": 1, "visible": true, "priority": 1 }
]
}
```
## Ответственность Frontend
- Корректно строить дерево категорий и breadcrumbs.
- Переходить в каталог категории по маршруту конфигурации.
- Не показывать скрытые категории.
## Ответственность Backend
- Поддерживать целостность иерархии категорий.
- Возвращать категории в tenant-контексте.
- Отдавать метрики itemCount при их поддержке.

View File

@@ -1,131 +0,0 @@
# 10. Домен товаров
## Назначение
Товарный домен определяет контракт карточки товара, листингов, ценовых и складских атрибутов.
## Поведение системы
- Товары загружаются по API для листинга, карточки и связанных коллекций.
- Frontend отображает только те поля, которые есть в контракте.
- Бизнес-правила доступности товара приходят из backend.
## Обязательные JSON-свойства товара
- itemID
- name
- price
- currency
- categoryID
- visible
## Опциональные свойства
- discount
- images
- badges
- simpleDescription
- attributes
- stockStatus
- rating
- media
- specificationGroups
- variantOptions
- relatedCollections
## Product Engagement Models
- RatingSummary
- average: number
- totalReviews: number
- distribution: [{ stars, count, share }]
- Review
- id, rating, title, text, author, anonymous, verifiedPurchase, createdAt
- likes/dislikes placeholders
- photos: string[] (reserved for future uploads)
- Question
- id, text, author, createdAt, likes, dislikes
- answers: Answer[]
- Answer
- id, text, author, createdAt
- isOfficialSeller, isAccepted
## Product Experience 2.0 Optional Contracts
- media: `[{ type, url, thumbnailUrl?, alt?, title?, labels? }]`
- type: `image | video | pdf | manual | warranty`
- frontend renderer picks viewer by `type`
- specificationGroups: `[{ key, label?, labels?, attributes: [{ key, value, label?, labels?, unit? }] }]`
- supports grouped specifications and translated labels
- variantOptions: `[{ key, label?, labels?, options: [{ value, label?, labels?, available? }] }]`
- supports arbitrary variant groups (`color`, `size`, `storage`, etc.)
- relatedCollections: `[{ id, title, titles?, products: number[] }]`
- supports multiple related collections from backend
## Product Page Config Extensions
`productPage` optional config additions:
- reviews.mode: `pages | load-more`
- questions.allowSubmission: `boolean`
- actions: `{ enabled, addToCart, buyNow, wishlist, compare, share, notifyMe }`
## Строгие правила
- Цена и валюта должны передаваться как валидная пара.
- Скрытые товары не участвуют в публичных витринах.
- categoryID должен ссылаться на существующую категорию.
- Виджет не изменяет товарные данные, только отображает.
- Frontend feature-слой работает только через ProductFacade.
- DTO/API shape не импортируется в feature components.
- reviews/questions не хранятся в bootstrap, только их feature-конфиг.
## Пример JSON товара
```json
{
"itemID": 7812,
"name": "Laptop Pro 14",
"price": 129990,
"currency": "RUB",
"categoryID": 55,
"visible": true,
"discount": 10,
"images": [
{ "url": "https://cdn.example.com/items/7812/main.jpg", "isMain": true }
],
"badges": ["featured", "new"],
"media": [
{ "type": "image", "url": "https://cdn.example.com/items/7812/main.jpg" },
{ "type": "video", "url": "https://cdn.example.com/items/7812/demo.mp4" },
{ "type": "pdf", "url": "https://cdn.example.com/items/7812/spec.pdf" }
],
"specificationGroups": [
{
"key": "display",
"labels": { "en": "Display", "ru": "Экран" },
"attributes": [
{ "key": "size", "value": "14", "unit": "inch" },
{ "key": "resolution", "value": "2880x1800" }
]
}
],
"variantOptions": [
{
"key": "storage",
"options": [{ "value": "256GB" }, { "value": "512GB" }]
}
],
"relatedCollections": [
{
"id": "similar-products",
"title": "Похожие товары",
"products": [9030, 9031]
}
]
}
```
## Ответственность Frontend
- Показывать корректную цену, скидку, бейджи и доступность.
- Поддерживать переход из листинга в карточку товара.
- Учитывать locale/currency из tenant-конфигурации.
## Ответственность Backend
- Возвращать актуальные цены и доступность.
- Стабильно поддерживать идентификаторы товаров.
- Предоставлять медиа и атрибуты в согласованном формате.
- Поддерживать обратную совместимость: новые поля опциональны, старые payload остаются валидными.

View File

@@ -1,61 +0,0 @@
## CMS Navigation Integration
- Static pages with `showInHeader: true` may appear in header navigation automatically.
- Static pages with `showInFooter: true` may be grouped into footer sections using `footerGroup`.
- Social links remain footer-config driven and can coexist with CMS page groups.
- Navigation stays configuration-driven; no marketplace-specific page names are hardcoded in frontend.
## Catalog Routing Preparation
- Catalog root `/catalog` renders category browser.
- Category detail keeps current ID routes while resolving future slug tokens in routing layer.
- Breadcrumb root action must always navigate to `/catalog`, not all-products grid.
# 11. Система навигации
## Назначение
Навигационная система управляет маршрутами витрины, меню и ссылками на основе bootstrap-конфигурации.
## Поведение системы
- Frontend строит маршруты из конфигурации pages и navigation.
- Языковой префикс маршрута задается локализационной конфигурацией.
- Для категорий/товаров используются конфигурируемые route templates.
## Обязательные JSON-свойства
- pages[].route.path
- pages[].id
- navigation.header или navigation.footer (минимум один набор)
## Опциональные свойства
- route.exact
- route.redirectTo
- navigation.icon
- navigation.order
- navigation.visible
## Строгие правила
- Маршруты страниц должны быть уникальны в рамках tenant.
- Ссылка меню должна ссылаться на существующий маршрут или валидный внешний URL.
- Нельзя хардкодить статические tenant-пути в компонентах.
## Пример navigation JSON
```json
{
"navigation": {
"header": [
{ "id": "nav-home", "label": "Главная", "route": "/", "order": 1 },
{ "id": "nav-catalog", "label": "Каталог", "route": "/catalog", "order": 2 }
],
"footer": [
{ "id": "nav-privacy", "label": "Политика", "route": "/privacy-policy", "order": 1 }
]
}
}
```
## Ответственность Frontend
- Строить меню и роутинг из конфигурации.
- Соблюдать локализацию маршрутов.
- Обрабатывать недоступные маршруты через fallback-страницу.
## Ответственность Backend
- Возвращать валидную карту маршрутов/навигации в bootstrap.
- Поддерживать актуальность ссылок на статические страницы.
- Контролировать tenant-специфичность навигации.

View File

@@ -1,58 +0,0 @@
## CMS Upgrade
- Static pages now support unlimited bootstrap-driven entries under `staticPages`.
- Each page may define slug, visibility, header/footer/sitemap flags, icon, order, translations, HTML and SEO metadata.
- Frontend resolves pages dynamically from bootstrap instead of hardcoded page names.
- Header and footer may consume the same static page registry without duplicating page definitions.
# 12. Система статических страниц
## Назначение
Система статических страниц управляет юридическими и информационными страницами (о компании, политика, доставка, возврат) через конфигурацию.
## Поведение системы
- Список страниц и маршруты берутся из bootstrap/pages.
- Контент может храниться как HTML/Markdown/структурированный JSON.
- Frontend рендерит контент безопасно, с tenant-aware навигацией.
## Обязательные JSON-свойства
- page.id
- page.key
- page.route.path
- page.type = "static"
- page.content.source
## Опциональные свойства
- page.seoKey
- page.visible
- page.translations
- page.lastUpdated
## Строгие правила
- Запрещено хардкодить список статических страниц во frontend.
- Контент должен быть изолирован по tenant.
- HTML-контент должен проходить sanitation на frontend и/или backend.
## Пример JSON статической страницы
```json
{
"id": "page-privacy",
"key": "privacy-policy",
"type": "static",
"route": { "path": "/privacy-policy", "exact": true },
"content": {
"source": "cms",
"contentType": "html",
"value": "<h1>Политика конфиденциальности</h1><p>...</p>"
},
"visible": true
}
```
## Ответственность Frontend
- Рендерить статические страницы по конфигурации маршрутов.
- Безопасно обрабатывать HTML-контент.
- Поддерживать локализованные версии страницы.
## Ответственность Backend
- Поставлять tenant-specific статический контент.
- Поддерживать версионирование и аудит контента.
- Гарантировать валидность route/content связки.

View File

@@ -1,100 +0,0 @@
# 13. Требования к backend
## Назначение
Документ фиксирует минимальный набор backend-возможностей для стабильной работы конфигурационно-управляемой multi-tenant платформы.
## Функциональные требования
- Tenant resolution по домену.
- Выдача bootstrap.json для tenant.
- API категорий, товаров, карточек, поисковых выборок.
- Выдача навигации, статических страниц и feature flags.
- Product Engagement API для рейтинга, отзывов и вопросов.
- Advanced Search API для keyword/suggestions/filter metadata/sorting.
- User Experience API (future-ready): wishlist/compare/saved-searches/recently-viewed sync for authenticated users.
### Контракт статических страниц
Backend должен поддерживать формат:
```json
{
"slug": "about-us",
"content": {
"en": "<html>",
"ru": "<html>",
"hy": "<html>"
}
}
```
## Обязательные JSON-контракты
- Bootstrap контракт со schemaVersion.
- Категории: id/title/parentId/visible/priority.
- Товары: itemID/name/price/currency/categoryID/visible.
- Product Engagement:
- RatingSummary (average, totalReviews, distribution)
- Review (id, rating, text, createdAt, author)
- Question (id, text, createdAt, answers)
- Унифицированный формат ошибок API.
## Обязательные Product Engagement endpoints
- GET /products/{id}/rating
- GET /products/{id}/reviews?page={n}&pageSize={n}
- GET /products/{id}/questions?page={n}&pageSize={n}
- POST /products/{id}/reviews
- POST /products/{id}/questions
## Обязательные Catalog/Search endpoints (current + future-ready)
- GET /searchitems
- GET /category/{id}
- GET /items/randomitems
- GET /search/suggestions?q={term} (future-ready)
- GET /catalog/filters?category={id}&q={term} (future-ready)
## User Experience endpoints (future-ready)
- GET /me/wishlist
- POST /me/wishlist
- DELETE /me/wishlist/{itemId}
- GET /me/compare
- POST /me/compare
- DELETE /me/compare/{itemId}
- GET /me/saved-searches
- POST /me/saved-searches
- DELETE /me/saved-searches/{id}
- GET /me/recently-viewed
- POST /me/recently-viewed
## Catalog bootstrap contract expectations
- Backend should populate `catalog.availableSorts` and `catalog.enabledFilters`.
- Backend should not include product list or filter results inside bootstrap.
- Bootstrap remains feature-configuration only.
## Опциональные JSON-контракты
- Персонализированные рекомендации.
- Расширенные facets/filters.
- SEO-объекты и контентные блоки.
## Строгие правила
- Backend не должен возвращать frontend-specific разметку приложения (кроме контента статических страниц по согласованному контракту).
- Любое breaking change требует новой версии контракта.
- Данные tenants должны быть полностью изолированы.
- SLA bootstrap и catalog API должны обеспечивать запуск витрины без деградации UX.
- Bootstrap не должен содержать секреты: private keys, admin credentials, signing tokens.
## Пример JSON ошибки API
```json
{
"error": {
"code": "CATEGORY_NOT_FOUND",
"message": "Category does not exist",
"details": { "categoryId": 999 }
}
}
```
## Ответственность Frontend
- Корректно интерпретировать ошибки и показывать пользовательские сценарии восстановления.
- Не обходить публичные backend-контракты прямыми вызовами внутренних сервисов.
## Ответственность Backend
- Обеспечить мониторинг, логирование и трассировку критических endpoint.
- Поддерживать тестируемые и документированные контракты.
- Обеспечить безопасность, rate limiting и контроль доступа.

View File

@@ -1,54 +0,0 @@
# 14. Модель деплоя
## Назначение
Модель деплоя описывает запуск платформы в SaaS-режиме для нескольких tenants с общей frontend-сборкой и tenant-aware backend-конфигурацией.
## Поведение системы
- Одна frontend-сборка обслуживает несколько доменов.
- Tenant определяется на runtime по host.
- Backend/edge отдает соответствующий bootstrap и API конфигурацию.
- UI-режимы layout/widgets/footer/static pages переключаются только через bootstrap без перекомпиляции frontend.
## Обязательные параметры деплоя (JSON/env)
- supportedHosts
- defaultTenantPolicy
- apiGatewayBaseUrl
- bootstrapEndpoint
- observability (logs/metrics/traces)
## Опциональные параметры
- CDN policy
- региональные endpoint
- feature rollouts
- fallback tenant (только по утвержденной политике)
## Строгие правила
- Нельзя собирать отдельный frontend-бандл под каждый tenant как основной процесс.
- Нельзя использовать ручные правки frontend для запуска нового клиента.
- Деплой должен поддерживать zero-downtime обновления.
- Конфигурация окружений должна быть отделена от бизнес-данных tenants.
- В bootstrap и публичных API запрещено хранить секреты.
## Пример deployment-конфигурации (сокращенно)
```json
{
"environment": "production",
"supportedHosts": ["store-a.com", "store-b.com"],
"bootstrapEndpoint": "https://api.platform.com/bootstrap",
"apiGatewayBaseUrl": "https://api.platform.com",
"observability": {
"logs": true,
"metrics": true,
"traces": true
}
}
```
## Ответственность Frontend
- Корректно работать в multi-host режиме без перекомпиляции.
- Логировать runtime-ошибки tenant resolution/bootstrap.
## Ответственность Backend/DevOps
- Обеспечить маршрутизацию доменов на единый frontend runtime.
- Поддерживать tenant-aware конфигурацию на edge/API уровне.
- Обеспечить CI/CD с валидацией контрактов и smoke-тестами tenants.

View File

@@ -1,54 +0,0 @@
# 15. Правила и ограничения платформы
## Назначение
Документ фиксирует обязательные ограничения платформы для всех команд: frontend, backend, QA, DevOps и интеграционных партнеров.
## Базовые неизменяемые принципы
- Платформа полностью configuration-driven.
- Никакой hardcoded tenant/project логики во frontend.
- Tenant определяется только по домену.
- UI строится из bootstrap.json.
- Виджеты переиспользуемы и не обращаются к API напрямую.
- Backend предоставляет данные, а не layout.
- Layout управляется только конфигурацией.
## Обязательные правила JSON-модульности
- Bootstrap: структура страниц и подключение систем.
- Theme JSON: только визуальные токены.
- Navigation JSON: только маршруты и меню.
- Catalog/Product/Category API JSON: только доменные данные.
- Запрещено смешивать зоны ответственности между JSON-модулями.
## Что разрешено
- Добавлять новые виджеты через registry/manifest.
- Расширять опциональные поля с сохранением обратной совместимости.
- Добавлять новые секции/страницы через конфигурацию.
## Что запрещено
- Хардкод tenant-веток в компонентах.
- Прямые API-вызовы из widget UI слоя.
- Дублирование layout-правил в каждом виджете.
- Breaking изменения контрактов без schemaVersion.
## Пример policy JSON
```json
{
"platformPolicy": {
"configurationDriven": true,
"tenantResolution": "domain-only",
"widgetsCanCallApiDirectly": false,
"layoutControlledBy": "configuration",
"backendProvides": ["products", "categories", "items"]
}
}
```
## Ответственность Frontend
- Соблюдать архитектурные ограничения и слоистость.
- Не вводить локальные обходы конфигурации.
- Проводить регрессионные проверки на multi-tenant сценариях.
## Ответственность Backend
- Строго следовать контрактам данных.
- Поддерживать tenant isolation и аудируемость изменений.
- Предоставлять стабильные и документированные API.

View File

@@ -1,175 +0,0 @@
# 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").