changes
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

This commit is contained in:
sdarbinyan
2026-07-19 15:28:35 +04:00
parent 71d5f4d320
commit d853ecb1da
23 changed files with 2189 additions and 1107 deletions

124
.impeccable/design.json Normal file
View File

@@ -0,0 +1,124 @@
{
"schemaVersion": 2,
"generatedAt": "2026-07-17T00:00:00Z",
"title": "Design System: Marketplaces Platform",
"extensions": {
"colorMeta": {
"primary": { "role": "primary", "displayName": "Muted Pine", "canonical": "#497671", "tonalRamp": ["#182927", "#243d3a", "#2f4f4b", "#3d635f", "#497671", "#6b918d", "#93b3af", "#c3d6d3"] },
"secondary": { "role": "secondary", "displayName": "Sage Grey", "canonical": "#a1b4b5", "tonalRamp": ["#2c3838", "#3f5150", "#556c6b", "#6c8583", "#8da3a4", "#a1b4b5", "#c0cfcf", "#e2eaea"] },
"accent": { "role": "tertiary", "displayName": "Pale Mint", "canonical": "#a7ceca", "tonalRamp": ["#243936", "#33514d", "#456a65", "#5a857f", "#7fa9a3", "#a7ceca", "#c6e0dd", "#e6f2f0"] },
"text-primary": { "role": "neutral", "displayName": "Deep Pine Ink", "canonical": "#1e3c38", "tonalRamp": ["#0f1e1c", "#1e3c38", "#2c5651", "#3d716b", "#5a8d87", "#84aca7", "#b1cbc8", "#dfeae9"] },
"bg-secondary": { "role": "neutral", "displayName": "Soft Grey", "canonical": "#f5f5f5", "tonalRamp": ["#2b2b2b", "#4a4a4a", "#6e6e6e", "#949494", "#b8b8b8", "#d7d7d7", "#eaeaea", "#f5f5f5"] },
"border": { "role": "neutral", "displayName": "Divider Grey", "canonical": "#d3dad9", "tonalRamp": ["#333938", "#4a5251", "#636d6c", "#7f8a89", "#9da8a7", "#bcc5c4", "#d3dad9", "#eef1f1"] }
},
"typographyMeta": {
"display": { "displayName": "Display", "purpose": "Page-level and storefront hero titles; ceiling ~2.75rem." },
"headline": { "displayName": "Headline", "purpose": "Section headings and admin page titles." },
"title": { "displayName": "Title", "purpose": "Card titles, editor section labels." },
"body": { "displayName": "Body", "purpose": "Default reading text; cap prose at 65-75ch." },
"label": { "displayName": "Label", "purpose": "Badges and tags only; tracked uppercase." }
},
"shadows": [
{ "name": "shadow-sm", "value": "0 2px 8px rgba(0,0,0,0.1)", "purpose": "Resting cards, inputs, low panels. Default ambient layer." },
{ "name": "shadow-md", "value": "0 4px 12px rgba(0,0,0,0.15)", "purpose": "Hover state for cards and buttons; raised toolbars." },
{ "name": "shadow-lg", "value": "0 12px 32px rgba(73,118,113,0.2)", "purpose": "Structural float: modals, dropdowns, save bar. Brand-tinted." }
],
"motion": [
{ "name": "transition-fast", "value": "120ms ease", "purpose": "Button and small-control state changes." },
{ "name": "transition-normal", "value": "180ms ease", "purpose": "Card hover lift, transforms." },
{ "name": "transition-slow", "value": "300ms ease", "purpose": "Default for links/inputs/textareas." }
],
"breakpoints": [
{ "name": "sm", "value": "640px" },
{ "name": "md", "value": "900px" },
{ "name": "lg", "value": "1200px" },
{ "name": "container", "value": "1280px" }
]
},
"components": [
{
"name": "Primary Button",
"kind": "button",
"refersTo": "button-primary",
"description": "The default confident action. Muted Pine fill, lifts on hover.",
"html": "<button class=\"ds-btn-primary\">Save changes</button>",
"css": ".ds-btn-primary { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; background: #497671; color: #fff; border: 1px solid #497671; border-radius: 12px; padding: 0.625rem 1rem; font-weight: 600; line-height: 1.2; cursor: pointer; transition: background-color 180ms ease, transform 180ms ease, box-shadow 180ms ease; } .ds-btn-primary:hover { background: #3d635f; border-color: #3d635f; transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .ds-btn-primary:active { transform: translateY(0); } .ds-btn-primary:focus-visible { outline: 2px solid #497671; outline-offset: 2px; }"
},
{
"name": "Ghost Button",
"kind": "button",
"refersTo": "button-ghost",
"description": "Low-emphasis action. Transparent with a divider border until hover.",
"html": "<button class=\"ds-btn-ghost\">Cancel</button>",
"css": ".ds-btn-ghost { display: inline-flex; align-items: center; justify-content: center; background: transparent; color: #1e3c38; border: 1px solid #d3dad9; border-radius: 12px; padding: 0.625rem 1rem; font-weight: 600; cursor: pointer; transition: background-color 180ms ease, border-color 180ms ease; } .ds-btn-ghost:hover { background: rgba(73,118,113,0.08); border-color: #497671; } .ds-btn-ghost:focus-visible { outline: 2px solid #497671; outline-offset: 2px; }"
},
{
"name": "Card",
"kind": "card",
"refersTo": "card",
"description": "Resting surface with a soft ambient shadow that lifts on hover.",
"html": "<div class=\"ds-card\"><h3 class=\"ds-card-title\">Product title</h3><p class=\"ds-card-body\">Supporting copy sits in Muted Pine Grey at a comfortable line height.</p></div>",
"css": ".ds-card { background: #ffffff; border: 1px solid #d3dad9; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 16px; transition: transform 180ms ease, box-shadow 180ms ease; } .ds-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .ds-card-title { margin: 0 0 6px; font-size: 1.125rem; font-weight: 600; color: #1e3c38; line-height: 1.3; } .ds-card-body { margin: 0; font-size: 1rem; font-weight: 400; color: #667a77; line-height: 1.6; }"
},
{
"name": "Text Input",
"kind": "input",
"refersTo": "input",
"description": "Editor/admin field with a divider stroke and brand focus outline.",
"html": "<label class=\"ds-field\"><span class=\"ds-field-label\">Store name</span><span class=\"ds-field-desc\">Shown in the storefront header.</span><input class=\"ds-input\" type=\"text\" placeholder=\"My marketplace\" /></label>",
"css": ".ds-field { display: grid; gap: 6px; color: #1e3c38; font-weight: 600; } .ds-field-label { font-size: 1rem; } .ds-field-desc { font-weight: 400; font-size: 12px; line-height: 1.4; color: #667a77; } .ds-input { width: 100%; padding: 10px 12px; border: 1px solid #d3dad9; border-radius: 10px; background: #fff; color: #1e3c38; font: inherit; } .ds-input:focus-visible { outline: 2px solid #497671; outline-offset: 2px; } .ds-input::placeholder { color: #828e8d; }"
},
{
"name": "Badge",
"kind": "chip",
"refersTo": "badge",
"description": "Uppercase status marker overlaid on product media.",
"html": "<span class=\"ds-badge ds-badge-sale\">Sale</span>",
"css": ".ds-badge { display: inline-block; padding: 2px 8px; border-radius: 8px; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; color: #fff; line-height: 1.4; } .ds-badge-sale { background: #f44336; }"
},
{
"name": "Tag",
"kind": "chip",
"refersTo": "badge",
"description": "Low-emphasis metadata pill in brand tint.",
"html": "<span class=\"ds-tag\">Digital</span>",
"css": ".ds-tag { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 0.72rem; color: #497671; background: rgba(73,118,113,0.08); border: 1px solid rgba(73,118,113,0.15); }"
}
],
"narrative": {
"northStar": "The Operator's Workbench",
"overview": "This is a tool before it is a brand. The platform chrome is a dependable workbench an operator returns to session after session to build and run a marketplace: state is always legible, controls map to what they change, and nothing competes with the work. The palette is a calm Muted Pine teal-green, warm enough to feel like commerce, quiet enough to disappear behind a tenant's own theme. The system is configuration-first: every storefront is themed per tenant from a runtime bootstrap, so the platform's identity stays neutral and the tenant's leads. Components are tactile and confident; depth is real but restrained, with structural elevation reserved for things that genuinely float.",
"keyCharacteristics": [
"Quiet, neutral chrome so per-tenant themes lead the storefront.",
"Muted Pine teal-green primary; retail-warm but low-drama.",
"Tactile, confident components with decisive states.",
"Legible state above decoration in every tool surface.",
"WCAG 2.2 AA; contrast holds across tenant themes, not just the default."
],
"rules": [
{ "name": "The Quiet Chrome Rule", "body": "The platform's own surfaces stay neutral so tenant themes carry storefront identity. Never introduce a platform-branded color that would fight a tenant's palette.", "section": "colors" },
{ "name": "The Variable-Only Rule", "body": "Components and widgets consume CSS custom properties only. A hardcoded hex in a component is a bug (ADR-008) that breaks per-tenant theming.", "section": "colors" },
{ "name": "The One Family Rule", "body": "DM Sans in multiple weights carries the entire system. Do not pair a second sans; do not add a display serif. Contrast is weight and size.", "section": "typography" },
{ "name": "The Uppercase-Is-Earned Rule", "body": "Tracked uppercase lives on badges/tags exclusively. It is forbidden as a section eyebrow.", "section": "typography" },
{ "name": "The Lift-on-Intent Rule", "body": "Resting surfaces carry at most shadow-sm. shadow-md is a response to hover/focus; shadow-lg means the element floats above the page.", "section": "elevation" }
],
"dos": [
"Do consume theme CSS custom properties, never hardcode hex in a component (ADR-008).",
"Do keep platform chrome neutral so tenant themes lead the storefront.",
"Do carry hierarchy with DM Sans weight and size; one family only.",
"Do keep resting surfaces on shadow-sm; reserve shadow-lg for genuinely floating elements.",
"Do make state unambiguous in every tool surface.",
"Do give every hover/transform a prefers-reduced-motion fallback.",
"Do hold 4.5:1 body-text contrast across every tenant theme, not just Dexar."
],
"donts": [
"Don't ship dated enterprise admin: cluttered gray dashboards, tiny dense tables, 2010-era Bootstrap backoffice.",
"Don't ship generic AI-SaaS template: cream/violet gradient landings, hero-metric card rows, tracked-uppercase eyebrows, identical card grids.",
"Don't ship consumer-toy UI: bubbly rounded-everything, mascots, candy colors, gamified surfaces.",
"Don't use tracked uppercase anywhere except badges/tags.",
"Don't exceed ~2.75rem on display headings.",
"Don't add a second type family or a display serif.",
"Don't let platform-branded color fight a tenant's palette."
]
}
}

View File

@@ -0,0 +1,6 @@
{
"files": ["src/index.html"],
"insertBefore": "</body>",
"commentSyntax": "html",
"cspChecked": true
}

234
DESIGN.md Normal file
View File

@@ -0,0 +1,234 @@
---
name: Marketplaces Platform
description: Config-driven multi-tenant marketplace platform — quiet chrome, tenant-led storefronts.
colors:
primary: "#497671"
primary-hover: "#3d635f"
secondary: "#a1b4b5"
secondary-hover: "#8da3a4"
accent: "#a7ceca"
accent-hover: "#91b9b5"
text-primary: "#1e3c38"
text-secondary: "#667a77"
text-light: "#828e8d"
bg-primary: "#ffffff"
bg-secondary: "#f5f5f5"
bg-tertiary: "#f0f0f0"
border: "#d3dad9"
border-dark: "#677b78"
success: "#10b981"
warning: "#f59e0b"
error: "#ef4444"
info: "#3b82f6"
typography:
display:
fontFamily: "DM Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif"
fontSize: "clamp(2rem, 4vw, 2.75rem)"
fontWeight: 700
lineHeight: 1.25
letterSpacing: "normal"
headline:
fontFamily: "DM Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif"
fontSize: "clamp(1.5rem, 3vw, 2rem)"
fontWeight: 700
lineHeight: 1.25
letterSpacing: "normal"
title:
fontFamily: "DM Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif"
fontSize: "1.125rem"
fontWeight: 600
lineHeight: 1.3
letterSpacing: "normal"
body:
fontFamily: "DM Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif"
fontSize: "1rem"
fontWeight: 400
lineHeight: 1.6
letterSpacing: "normal"
label:
fontFamily: "DM Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif"
fontSize: "0.7rem"
fontWeight: 600
lineHeight: 1.4
letterSpacing: "0.4px"
rounded:
sm: "8px"
md: "12px"
lg: "13px"
xl: "22px"
field: "10px"
spacing:
xs: "4px"
sm: "8px"
md: "16px"
lg: "24px"
xl: "32px"
components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "#ffffff"
rounded: "{rounded.md}"
padding: "0.625rem 1rem"
button-primary-hover:
backgroundColor: "{colors.primary-hover}"
textColor: "#ffffff"
rounded: "{rounded.md}"
button-secondary:
backgroundColor: "{colors.secondary}"
textColor: "#ffffff"
rounded: "{rounded.md}"
padding: "0.625rem 1rem"
button-ghost:
backgroundColor: "transparent"
textColor: "{colors.text-primary}"
rounded: "{rounded.md}"
padding: "0.625rem 1rem"
card:
backgroundColor: "{colors.bg-primary}"
rounded: "{rounded.md}"
padding: "16px"
input:
backgroundColor: "{colors.bg-primary}"
textColor: "{colors.text-primary}"
rounded: "{rounded.field}"
padding: "10px 12px"
badge:
textColor: "#ffffff"
rounded: "{rounded.sm}"
padding: "2px 8px"
---
# Design System: Marketplaces Platform
## 1. Overview
**Creative North Star: "The Operator's Workbench"**
This is a tool before it is a brand. The platform chrome — the Project Editor, the Admin backoffice, the shared UI primitives — is a dependable workbench an operator returns to session after session to build and run a marketplace. It rewards precision and speed: state is always legible (draft vs published, saved vs unsaved, safe vs destructive), controls map visibly to what they change, and nothing on screen competes with the work. The palette is a calm Muted Pine teal-green, warm enough to feel like commerce, quiet enough to disappear behind a tenant's own theme.
The system is deliberately configuration-first. Every storefront is themed per tenant from a runtime `bootstrap.json`, so the platform's own identity stays neutral by design — the tenant's colors, type, and layout carry the storefront's character, and the workbench chrome recedes. Where components do appear, they are tactile and confident: solid fills, decisive hover lift, honest disabled and error states. Depth is real but restrained — surfaces sit on soft tonal shadows at rest, and structural elevation is reserved for things that genuinely float (modals, dropdowns, the save bar).
This system explicitly rejects three looks. It is **not dated enterprise admin** — no cluttered gray dashboards, no tiny dense tables, no 2010-era Bootstrap backoffice. It is **not a generic AI-SaaS template** — no cream/violet gradient landings, no hero-metric card rows, no tracked-uppercase eyebrows on every section, no identical icon-heading-text grids. It is **not a consumer toy** — no bubbly rounded-everything, no mascots, no candy colors, no gamified UI.
**Key Characteristics:**
- Quiet, neutral chrome so per-tenant themes lead the storefront.
- Muted Pine teal-green primary; retail-warm but low-drama.
- Tactile, confident components with decisive states.
- Legible state above decoration in every tool surface.
- WCAG 2.2 AA; contrast holds across tenant themes, not just the default.
## 2. Colors
A grounded teal-green core over cool near-white neutrals; retail warmth without shouting. The tokens below are the canonical Dexar theme — the platform default. Tenant themes (Lavero, Novo, and future tenants) override these same CSS custom properties, so components must consume the variables, never hardcode hex (ADR-008).
### Primary
- **Muted Pine** (#497671): The core brand teal-green. Primary buttons, active nav, focus outlines, links, key accents. On hover it deepens to **Pine Deep** (#3d635f). Grounded and natural — the color of the workbench itself.
### Secondary
- **Sage Grey** (#a1b4b5): Muted blue-grey-green for secondary actions and supporting surfaces; hover **Sage Grey Deep** (#8da3a4). Quieter than primary, never competes.
### Tertiary
- **Pale Mint** (#a7ceca): Soft light accent (#91b9b5 on hover) for gentle highlights, hero gradient stops, and low-emphasis fills.
### Neutral
- **Deep Pine Ink** (#1e3c38): Primary text. Tinted toward the brand hue, not pure black — carries 4.5:1+ on white.
- **Muted Pine Grey** (#667a77): Secondary text, captions, field descriptions.
- **Faint Pine Grey** (#828e8d): Light/tertiary text, placeholders — reserve for large or non-essential text.
- **White** (#ffffff): Primary surface (cards, inputs, panels).
- **Soft Grey** (#f5f5f5): App background, secondary surface.
- **Faint Grey** (#f0f0f0): Tertiary surface, subtle fills.
- **Divider Grey** (#d3dad9): Borders, dividers, input strokes.
- **Border Deep** (#677b78): Stronger borders where a divider needs weight.
### Status
- **Success** (#10b981), **Warning** (#f59e0b), **Error** (#ef4444), **Info** (#3b82f6): Standard semantic set, consistent across all themes. Error text darkens to #991b1b on light backgrounds for AA.
### Named Rules
**The Quiet Chrome Rule.** The platform's own surfaces stay neutral so tenant themes carry storefront identity. Never introduce a platform-branded color that would fight a tenant's palette.
**The Variable-Only Rule.** Components and widgets consume CSS custom properties (`--primary-color`, `--text-primary`, `--border-color`) only. A hardcoded hex in a component is a bug (ADR-008) — it breaks per-tenant theming.
## 3. Typography
**Display / Body Font:** DM Sans (with `-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif` fallback)
**Label Font:** DM Sans (same family, tracked and uppercased for badges)
**Character:** One family, four weights (400/500/600/700). DM Sans is a low-contrast geometric-humanist sans — clean, legible at dense sizes, neutral enough to sit behind tenant content. Hierarchy comes from weight and size, never a second display face.
### Hierarchy
- **Display** (700, clamp(2rem, 4vw, 2.75rem), 1.25): Page-level headings, storefront hero titles. Never exceeds ~2.75rem — the workbench does not shout.
- **Headline** (700, clamp(1.5rem, 3vw, 2rem), 1.25): Section headings, admin page titles.
- **Title** (600, 1.125rem, 1.3): Card titles, editor section labels, form group headings.
- **Body** (400, 1rem, 1.6): Default reading text. Cap prose at 6575ch.
- **Label** (600, 0.7rem, 1.4, letter-spacing 0.4px, uppercase): Badges and tags only — the one place tracked uppercase is legitimate.
### Named Rules
**The One Family Rule.** DM Sans in multiple weights carries the entire system. Do not pair a second sans; do not add a display serif. Contrast is weight and size.
**The Uppercase-Is-Earned Rule.** Tracked uppercase lives on badges/tags exclusively. It is forbidden as a section eyebrow — that is a named anti-reference.
## 4. Elevation
A hybrid: soft tonal shadows give resting surfaces gentle separation from the background, while structural elevation is reserved for elements that genuinely float — modals, dropdowns, the sticky save bar. On top of that, interactive surfaces lift on hover (a 12px translate plus a stronger shadow). Depth is present and purposeful, never heavy.
### Shadow Vocabulary
- **shadow-sm** (`0 2px 8px rgba(0,0,0,0.1)`): Resting cards, inputs, low panels. The default ambient layer.
- **shadow-md** (`0 4px 12px rgba(0,0,0,0.15)`): Hover state for cards and buttons; raised toolbars.
- **shadow-lg** (`0 12px 32px rgba(73,118,113,0.2)`): Structural float — modals, dropdowns, popovers, the save bar. Tinted with the brand hue.
### Named Rules
**The Lift-on-Intent Rule.** Resting surfaces carry at most `shadow-sm`. `shadow-md` is a response to hover/focus; `shadow-lg` means the element floats above the page. Never use `shadow-lg` as decoration on a static card.
## 5. Components
### Buttons
- **Shape:** Gently curved (12px radius, `{rounded.md}`); editor action buttons use 10px (`{rounded.field}`).
- **Primary:** Muted Pine fill (#497671), white text, padding `0.625rem 1rem`, weight 600700. Tactile and confident.
- **Hover / Focus:** Background deepens to #3d635f, `translateY(-1px)` lift with `shadow-sm`; focus-visible shows a 2px Muted Pine outline offset 2px. `:active` returns to `translateY(0)`.
- **Secondary:** Sage Grey (#a1b4b5) fill, white text; hover #8da3a4.
- **Ghost:** Transparent, Deep Pine Ink text, Divider Grey border; hover fills `rgba(73,118,113,0.08)` and border shifts to Muted Pine.
- **Disabled:** `opacity: 0.6`, no lift, no shadow, `cursor: not-allowed`.
### Cards / Containers
- **Corner Style:** 12px (`{rounded.md}`).
- **Background:** White (#ffffff) on Soft Grey (#f5f5f5) page.
- **Border:** 1px Divider Grey (#d3dad9).
- **Shadow Strategy:** `shadow-sm` at rest → `shadow-md` on hover with `translateY(-2px)` (product cards add a subtle `scale(1.01)`). See Elevation.
- **Internal Padding:** 16px (`{spacing.md}`).
- **Nested cards:** Editor sub-cards use `#fbfcfc` fill with the same 12px radius and 1px border.
### Inputs / Fields
- **Style:** White fill, 1px Divider Grey border, 10px radius (`{rounded.field}`), padding `10px 12px`, inherits body font.
- **Focus:** 2px Muted Pine focus-visible outline, offset 2px (global rule).
- **Field description:** 12px, Muted Pine Grey (#667a77), sits under the label at weight 400.
- **Error:** Error text #991b1b; color input controls get a 44px min-height touch target.
### Navigation
- Neutral chrome, DM Sans, weight 600 for active items. Default text is Deep Pine Ink; active/hover carries Muted Pine. Header uses a low-tint `--bg-header` wash (brand hue at ~10% alpha). Mobile collapses to a menu; `body.platform-menu-open` locks scroll.
### Badges & Tags (signature)
- **Badge:** Uppercase Label type (0.7rem, 600, 0.4px tracking), white text, 8px radius, `2px 8px` padding, solid semantic fills (new #4caf50, sale #f44336, hot #ff5722, limited #ff9800, bestseller #2196f3, featured #607d8b). Absolutely-positioned overlay top-left on product media.
- **Tag:** Pill (12px radius), Muted Pine text on `rgba(73,118,113,0.08)` fill with a faint brand border. Low-emphasis metadata.
### Save Bar (signature)
- Sticky, structurally elevated (`shadow-lg`), always states current state (unsaved changes / saving / published). The clearest expression of the Operator's Workbench: the operator always knows where the work stands.
## 6. Do's and Don'ts
### Do:
- **Do** consume theme CSS custom properties (`--primary-color`, `--text-primary`, `--border-color`) — never hardcode hex in a component (ADR-008).
- **Do** keep platform chrome neutral so tenant themes lead the storefront (The Quiet Chrome Rule).
- **Do** carry hierarchy with DM Sans weight and size; one family only.
- **Do** keep resting surfaces on `shadow-sm`; reserve `shadow-lg` for genuinely floating elements.
- **Do** make state unambiguous — draft vs published, saved vs unsaved, safe vs destructive — in every tool surface.
- **Do** give every hover/transform a `prefers-reduced-motion: reduce` fallback (handled globally in `styles.scss`).
- **Do** hold 4.5:1 body-text contrast across every tenant theme, not just Dexar.
### Don't:
- **Don't** ship dated enterprise admin: no cluttered gray dashboards, tiny dense tables, or 2010-era Bootstrap backoffice.
- **Don't** ship generic AI-SaaS template: no cream/violet gradient landings, hero-metric card rows, tracked-uppercase eyebrows on every section, or identical icon-heading-text card grids.
- **Don't** ship consumer-toy UI: no bubbly rounded-everything, mascots, candy colors, or gamified surfaces.
- **Don't** use tracked uppercase anywhere except badges/tags (The Uppercase-Is-Earned Rule).
- **Don't** exceed ~2.75rem on display headings — the workbench does not shout.
- **Don't** add a second type family or a display serif.
- **Don't** let platform-branded color fight a tenant's palette.

49
PRODUCT.md Normal file
View File

@@ -0,0 +1,49 @@
# Product
## Register
product
## Platform
web
## Users
Primary users are tenant operators — merchants and admins who build and run their own marketplace through the Project Editor (builder) and the Admin/backoffice. They are task-focused power users: configuring theme, layout, navigation, pages, products, and static content, then publishing. Their context is repeated, deliberate work sessions where speed, clarity, and confidence that a change did what they expected matter more than delight.
Secondary users are end shoppers browsing a tenant storefront — catalog, product pages, cart, static pages. They arrive casually, judge fast, and are conversion-driven. Every storefront is themed per tenant, so shoppers should experience the tenant's identity, not the platform's.
Operators come first; shoppers second. The tool must be genuinely good to work in, and the storefront it produces must convert.
## Product Purpose
A configuration-driven, multi-tenant marketplace platform. One Angular frontend serves unlimited tenants: identity, theme, navigation, page/section/widget composition, and static content all resolve at runtime from a per-tenant `bootstrap.json`, with the tenant chosen by request host. No tenant-specific code paths exist. A new marketplace is onboarded by domain plus config plus backend data — never by forking the frontend. Success is an operator standing up and running a complete, on-brand storefront end to end without writing code, and a shopper on that storefront never sensing the platform underneath.
## Positioning
Launch and run a full marketplace with no code: from one runtime config a tenant gets a brandable storefront, an admin backoffice, and a visual editor, onboarded by domain alone. The frontend renders entirely from bootstrap JSON, so tenant identity is fully configurable and the data backend can change without touching the app. The single claim every surface reinforces: everything you see is config, not custom code.
## Brand Personality
Precise, calm, trustworthy. The platform chrome behaves like commerce infrastructure: confident, low-drama, and out of the way. It states what happened plainly, makes destructive and publishing actions unambiguous, and never competes for attention with the tenant's own branding. Voice is direct and operator-literate, not salesy.
## Anti-references
Not dated enterprise admin: no cluttered gray dashboards, tiny dense tables, or 2010-era Bootstrap backoffice. Not generic AI-SaaS template: no cream/violet gradient landings, hero-metric card rows, tracked-uppercase eyebrows on every section, or identical icon-heading-text card grids. Not consumer toy: no bubbly rounded-everything, mascots, candy colors, or gamified UI.
## Design Principles
Config, not custom — the UI's job is to make an entirely configuration-driven system feel direct and predictable; every editor control maps visibly to what it changes.
Quiet chrome, tenant identity leads — the platform's own shell stays neutral so per-tenant themes carry the storefront's character; the platform never imposes an identity over the tenant's.
Operator-first clarity — density, task speed, and unambiguous state (draft vs published, saved vs unsaved, destructive vs safe) win over decoration in the tooling surfaces.
Trust through precision — plain confirmation of what happened, honest empty/error states, and no surprises around publish, reset, or delete.
Practice what you preach — the editor and admin should feel as considered as the storefronts they produce; the tool is itself a demonstration of the platform's quality.
## Accessibility & Inclusion
WCAG 2.2 AA. Body text meets 4.5:1 contrast, all interactive flows are keyboard-navigable with visible focus states, and every animation has a `prefers-reduced-motion` alternative. Because storefront palettes are tenant-configurable, contrast must hold across themes, not just the default one.

View File

@@ -297,7 +297,7 @@ implementation) + `features/backoffice/media/` + the shared
container/facade/service split as the rest of `admin/*`: container/facade/service split as the rest of `admin/*`:
- **No real data source exists for orders anywhere in this repo** (already - **No real data source exists for orders anywhere in this repo** (already
called out in Sprint 19's dashboard gap and `docs/BACKEND.md` item 7) - called out in Sprint 19's dashboard gap and `docs/backend/BACKEND-INTEGRATION.md#611-backoffice--orders-planned`) -
`AdminOrdersLocalGateway` seeds 24 deterministic synthetic orders in `AdminOrdersLocalGateway` seeds 24 deterministic synthetic orders in
memory (cycling through all statuses/customers) rather than reading from memory (cycling through all statuses/customers) rather than reading from
`BackofficeDataService`, since there is nothing there to read. This is `BackofficeDataService`, since there is nothing there to read. This is
@@ -355,14 +355,14 @@ routes at all, this is a net-new admin section.
creates the local record. creates the local record.
- **Passwordless login**: already existed before this sprint - - **Passwordless login**: already existed before this sprint -
`AdminAuthService`'s Telegram QR flow (`docs/ADMIN.md`'s existing admin `AdminAuthService`'s Telegram QR flow (`docs/ADMIN.md`'s existing admin
login section, `docs/BACKEND.md` item 1). This sprint's Users page links login section, `docs/backend/BACKEND-INTEGRATION.md#25-the-admin-authorization-gap-critical--security-relevant-unresolved`). This sprint's Users page links
to it via a hint, doesn't reimplement it. to it via a hint, doesn't reimplement it.
- **Session manager / device manager**: per-user session list (device, IP, - **Session manager / device manager**: per-user session list (device, IP,
last active, current-session badge) with per-session revoke, mocked last active, current-session badge) with per-session revoke, mocked
(`AdminUsersLocalGateway.loadSessions()` fabricates 2 sessions per user (`AdminUsersLocalGateway.loadSessions()` fabricates 2 sessions per user
on first view) - the real `AdminAuthService`/session-cookie flow only on first view) - the real `AdminAuthService`/session-cookie flow only
ever tracks the *current* browser's session, so multi-device session ever tracks the *current* browser's session, so multi-device session
listing has no real backend counterpart yet (see `docs/BACKEND.md` item 1). listing has no real backend counterpart yet (see `docs/backend/BACKEND-INTEGRATION.md#25-the-admin-authorization-gap-critical--security-relevant-unresolved`).
- **Audit**: per-user audit log (role/status changes), same dialog pattern - **Audit**: per-user audit log (role/status changes), same dialog pattern
as Sprint 24's per-transaction audit - not the system-wide security/audit as Sprint 24's per-transaction audit - not the system-wide security/audit
log planned for Sprint 26. log planned for Sprint 26.
@@ -492,7 +492,7 @@ final result, not just the later commit.
multi-tenant platform - locales/categories/products/static pages are only multi-tenant platform - locales/categories/products/static pages are only
known at runtime per tenant, not enumerable client-side at build time. A known at runtime per tenant, not enumerable client-side at build time. A
real per-tenant sitemap needs a backend/build-time generator - see real per-tenant sitemap needs a backend/build-time generator - see
`docs/BACKEND.md` item 17. `docs/backend/BACKEND-INTEGRATION.md#619-sitemap-future--static-baseline-only-today`.
- **Responsive**: spot-checked the admin backoffice and customer-facing - **Responsive**: spot-checked the admin backoffice and customer-facing
marketplace at mobile/tablet/desktop widths. `shared/ui/table` already marketplace at mobile/tablet/desktop widths. `shared/ui/table` already
wraps every admin table in `overflow-x: auto` (no changes needed); the wraps every admin table in `overflow-x: auto` (no changes needed); the

View File

@@ -41,7 +41,7 @@ Component (container) --> Facade --> Domain Service --> Repository/Provide
## Bootstrap / configuration engine ## 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. - `ConfigService` loads `BootstrapConfig` (see `docs/backend/BACKEND-INTEGRATION.md#4-bootstrap`) 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 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. - The Project Editor mutates an in-memory draft of the same `BootstrapConfig` — there is no parallel editor-only model.
@@ -65,7 +65,7 @@ Render pipeline: `page config -> section engine -> section renderer -> widget ho
## Feature flags / capability guards (ADR-009) ## 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.). - `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. - Feature resolution falls back across older config surfaces to preserve behavior as the flag model evolved across sprints — see `docs/backend/BACKEND-INTEGRATION.md#4-bootstrap` for the full field list.
## Diagnostics (dev-only) ## Diagnostics (dev-only)

View File

@@ -1,50 +0,0 @@
# Backend Handoff — B2B branch vs `main`
Audience: backend developer. This is the "what changed and what you need to build" summary for the `B2B` branch compared to `main`. It frames the detailed punch list in [`BACKEND.md`](BACKEND.md) — read that file for exact endpoint shapes and the frontend files that change once each endpoint exists.
## TL;DR
`B2B` is **~138 commits ahead of `main`**. Almost all of it is **frontend that has been built ahead of the backend**: a full admin backoffice, a project editor, a media manager, and a design-system component library. Every data-writing feature runs today against an **in-memory / `localStorage` mock gateway** that is already designed to be swapped for a real API via an injection token — the UI, facades, and pages do **not** change when you wire a real backend; you implement one gateway/provider class per domain and rebind its token.
**You are not changing any existing contract.** Existing auth, payments, `GET /bootstrap`, and `GET /category` are frozen. Everything below is *new* backend surface the frontend is waiting on.
## What `B2B` adds over `main` (feature-level)
| Area | What's new on the frontend | Backend today | Priority |
|---|---|---|---|
| Design system | Reusable primitives (Button, Input, Card, Badge, Dialog, Table, Pagination, Skeleton, EmptyState, FormField) + editor primitives (Toggle, Select, ColorPicker, SectionCard, LocaleTabs, KeyValueEditor) | n/a (pure UI) | — |
| Project Editor | Full tenant `BootstrapConfig` editor (11 sections, draft/publish, validation, per-locale content, HTML editor for static pages) | **mock**: draft/publish is `localStorage` only, no persistence | **High** |
| Media manager | Upload / grid / delete + reusable media picker | **mock**: IndexedDB adapter, nothing server-side | High |
| Categories admin | List + create/edit, hierarchy, drag reorder, soft delete/restore, draft/publish, SEO/translations | **mock**: in-memory, no write path | High |
| Products admin | List + editor (variants, barcode, archive, related products, bulk actions) | **mock**: in-memory gateway | High |
| Orders admin | List/detail, status changes, refund, cancel, notes, CSV, invoice | **mock**: 24 synthetic orders | High |
| Transactions admin | List, retry, fraud flag, audit log, CSV | **mock**: derived from mock orders | Medium |
| Users / roles admin | Users, roles, invitations, sessions, audit log | **mock**: synthetic; ties to auth gap below | High |
| Dashboard metrics | Counts, status, health, activity feed | **partial**: counts composed client-side; activity is `localStorage` | Medium |
| Monitoring | Health (real) + event/queue/webhook feeds | **mock** except Health | Medium |
| Analytics | Revenue/orders/top-products (from mock orders) + visitors/funnels/heatmaps | **no data source** for traffic/funnels | Low |
| SEO | Tenant-driven meta tags, `sitemap.xml` / `robots.txt` baseline | **static baseline** only, not per-tenant | Low |
## The two things to do first
1. **Admin authorization (security).** Admin and customer login currently share **one** Telegram QR session backend, so the server has no concept of "this is an admin session." Any Telegram user who completes the QR flow on the admin login screen gets an `adminSessionID`. **Server-side authorization keyed off the session id is required** — nothing on the frontend can substitute. See [`BACKEND.md`](BACKEND.md) §1.
2. **Bootstrap draft / publish persistence.** The Project Editor edits the same `BootstrapConfig` the storefront consumes, but Save is `localStorage`-only and Publish is in-memory — no backend call. Needs `GET/PUT /builder/bootstrap/draft`, `POST /builder/bootstrap/publish`, optional `POST /builder/bootstrap/validate`, and **server-side re-validation** (the client validator is not a trust boundary). See [`BACKEND.md`](BACKEND.md) §2.
## The pattern for every mocked domain
For categories, products, orders, transactions, users, monitoring, dashboard metrics:
1. A `*LocalGateway` (or `*Provider`) implements a gateway interface and is bound via an Angular injection token.
2. You implement a `*ApiGateway` against the **same interface**, hitting real endpoints matching the domain model in `features/admin/<area>/models/*.model.ts`.
3. You rebind the token in DI config. **Facades and page components do not change.**
Model shapes to build against are named per area in [`BACKEND.md`](BACKEND.md) §5§16.
## Known production issue not fixable here
Intermittent `502`/`504` on refresh / back-navigation in production originates from the **backend API's own reverse proxy** (`api.dexarmarket.ru:445`, `users.vitanova.network:456`) — the frontend calls those absolute URLs directly, bypassing this repo's nginx. Needs DevOps/backend investigation of upstream health and timeouts around session-check + bootstrap endpoints. See [`BACKEND.md`](BACKEND.md) "Known reliability issues".
## Verifying the frontend locally without a backend
`environment.ts` ships `useMockBootstrapOnLocal: true` and mock gateways are the default bindings, so `npm run dexar` runs the whole thing offline. Admin routes are reachable in dev via `?devBypassAdmin=true`. Flipping a domain to a real API is the gateway-swap above — no mock removal needed to start.

View File

@@ -1,54 +0,0 @@
# Backend Implementation Prompt — B2B branch
Copy-paste this to a backend developer or an AI agent. It is self-contained; pair it with [`BACKEND-INTEGRATION.md`](BACKEND-INTEGRATION.md) (contract detail) and [`BACKEND.md`](BACKEND.md) (per-domain models).
---
## Prompt
You are implementing the backend for the **`B2B` branch** of an Angular marketplace/storefront + admin builder. The frontend is already built and runs offline against mocks. Your job is to expose real HTTP endpoints the frontend already calls. **Do not touch authentication (Telegram QR session) or payments — those contracts are frozen and out of scope.**
### Context you must honor
- **Single build, multi-tenant.** Base URL is resolved per-tenant at runtime. Serve each tenant at `https://<tenant>.api.dexarmarket.ru:445` (template) or get it registered in `environment.tenantApiBaseUrls`. Default tenant → `https://api.dexarmarket.ru:445`.
- Every storefront request carries headers: `X-Region` (`Moscow`/`ST. Petersburg`/`Yerevan`), `X-Language` (`RU`/`EN`/`AM`), `Currency` (`RUB`/`USD`/`AMD`), `WebSessionID` (32-char hex). Localize and session-scope responses off these.
- The frontend normalizes both legacy and "backOffice" field variants, but you should match the documented models to avoid surprises.
### Part A — Keep existing storefront endpoints working (already in `main`, do not change shapes)
- `GET /ping`
- `GET /category`
- `GET /category/:id?count=&skip=`
- `GET /items/:id`
- `GET /searchitems?search=&count=&skip=&categoryIDs=&minPrice=&maxPrice=&tag=&sort=``{ items, total }`
- `GET /items/randomitems?count=&category=`
- `POST /websession/:sessionId` (cart lines)
- `POST /items/:id/callback` (review), `POST /items/:id/questiion` (question — keep the typo), `POST /purchase-email`
### Part B — New: `GET /bootstrap` (blocking dependency for prod)
Return one JSON document of type `BootstrapConfig` per tenant: `tenant, branding, theme, company, featureFlags, apiEndpoints, localization, seo, permissions, navigation, footer, pages, staticPages, widgetRegistry`.
- Schema: `src/app/shared/models/config/bootstrap-config.model.ts`
- Reference payload to match field-for-field: `src/assets/mock/bootstrap/bootstrap.json`
- Must be cacheable; frontend loads it once at startup.
### Part C — New: Builder (Project Editor persistence) — highest priority
The editor edits the same `BootstrapConfig` the storefront reads. Implement:
- `GET /builder/bootstrap/draft` → current draft
- `PUT /builder/bootstrap/draft` ← save draft (whole document)
- `POST /builder/bootstrap/publish` → promote draft to the live `GET /bootstrap`
- `POST /builder/bootstrap/validate` (optional endpoint) — **but server-side validation on publish is mandatory**; treat the client validator as untrusted.
### Part D — New: Backoffice (admin) CRUD, per domain
For each domain — categories, products, orders, transactions, users/roles, dashboard metrics, monitoring, media — expose REST endpoints under `/backoffice/<domain>` matching the interfaces the frontend mock gateways implement. Model shapes and required operations per domain: `BACKEND.md` §5§16 and `features/admin/<domain>/models/*.model.ts` / `*-gateway.interface.ts`. The frontend swaps mock→api by rebinding one DI token per domain; your endpoints must satisfy the same interface (list/get/create/update/soft-delete/restore/draft-publish where the interface declares them).
### Constraints
- No changes to auth or payment endpoints.
- Preserve existing storefront request/response shapes exactly.
- Populate `apiEndpoints.website / builder / backoffice` in the bootstrap document as you add endpoints, so the frontend can discover them.
- Add server-side authorization: admin/builder sessions must be distinguishable from customer sessions server-side (the frontend cannot enforce this).
### Deliverables
1. `GET /bootstrap` per tenant.
2. Builder draft/publish/validate with server-side re-validation.
3. Backoffice CRUD per domain against the documented interfaces.
4. OpenAPI/spec for B, C, D. Storefront (A) is already specified by the client normalizers — do not deviate.
Ask before assuming any field you cannot find in the referenced models or `bootstrap.json`.

View File

@@ -1,120 +0,0 @@
# Backend Integration — B2B branch
How the `B2B` frontend **sends** and **gets** data, and how that differs from `main`.
Scope excludes **login/auth** and **payments** — those contracts are frozen and untouched here.
Source of truth in code:
- `src/app/services/api.service.ts` — storefront reads/writes
- `src/app/core/config/api-config.service.ts` — base-URL resolution
- `src/app/interceptors/api-base-url.interceptor.ts` + `api-headers.interceptor.ts` — request rewriting + headers
- `src/app/core/config/config.service.ts` + `core/bootstrap/providers/*` — bootstrap load
- `src/app/shared/models/config/*` — bootstrap/endpoint models
---
## 1. What changed vs `main` (one screen)
| Concern | `main` | `B2B` |
|---|---|---|
| API base URL | Hardcoded `environment.apiUrl` (`/api`) | Resolved per-tenant at runtime by `ApiConfigService` |
| Multi-tenant | One build per brand (`environment.lavero.ts`, `index.novo.html`, …) | **Single build**, tenant resolved from host; per-brand env/html files deleted |
| Config source | Compiled into `environment.ts` (brand, theme, logo, phones) | Fetched at runtime via **`GET /bootstrap`** → `BootstrapConfig` |
| Data providers | `ApiService` called directly everywhere | Same endpoints, now behind **repository/provider interfaces + DI tokens** (swap mock↔api without touching UI) |
| Mock strategy | `mockDataInterceptor` intercepts HTTP | Provider/gateway pattern picks mock vs api per domain (`RuntimeProviderStrategyService`) |
| Interceptor chain | `[mockData, apiHeaders, cache]` | `[mockData, **apiBaseUrl**, apiHeaders, adminAuth, cache]` |
| Storefront read/write endpoints | see §3 | **unchanged in shape** |
**Bottom line for backend:** the storefront read/write contract (categories, items, search, cart, reviews) is the **same as `main`**. What is *new* is (a) a `GET /bootstrap` document the frontend now depends on, and (b) two whole endpoint namespaces (`builder`, `backoffice`) that are declared but **empty** and served by client-side mocks today.
---
## 2. How a request is built (B2B pipeline)
Every storefront call goes through this chain:
1. Code calls `this.http.get('/api/category')` (or `${apiConfig.getBaseUrl()}/category`).
2. **`apiBaseUrlInterceptor`** — if the URL starts with `/api`, rewrites it to the resolved tenant base via `ApiConfigService.toApiUrl()`.
3. **`apiHeadersInterceptor`** — on any API request, attaches:
- `X-Region``Moscow` / `ST. Petersburg` / `Yerevan` (from region id)
- `X-Language``RU` / `EN` / `AM`
- `Currency``RUB` (default) / `USD` / `AMD`
- `WebSessionID` — 32-char hex; from auth session if present, else a persisted anonymous id (`localStorage: web_session_id`)
4. Request goes out to the resolved absolute base.
### Base-URL resolution (`ApiConfigService.getBaseUrl`)
Priority order:
1. localhost → `environment.localhostApiUrl` (`/api`, proxied by dev server)
2. `environment.tenantApiBaseUrls[tenantKey]` (e.g. `default`/`dexarmarket``https://api.dexarmarket.ru:445`)
3. `environment.tenantApiTemplate``https://{tenant}.api.dexarmarket.ru:445`
4. (opt-in) `bootstrap.apiEndpoints.website.baseUrl` / `bootstrap.tenant.apiBaseUrl` — only if `allowBootstrapApiOverride: true` **and** absolute
5. fallback `environment.apiUrl`
> Backend impact: for a **new tenant**, either add it to `tenantApiBaseUrls`, or serve it at `https://<tenant>.api.dexarmarket.ru:445` so the template matches. No frontend rebuild needed if the template host pattern holds.
---
## 3. GET — reads (storefront, real HTTP today)
Identical shapes to `main`. `ApiService` normalizes legacy **and** backOffice response formats (see `normalizeItem`/`normalizeCategory` — dual field names, `0x`-hex colours, `names[]`→translations, `imgs[]`→photos, Go-typo tolerance like `valuue`).
| Method | Endpoint | Notes |
|---|---|---|
| `getCategories()` | `GET /category` | retry ×2 backoff |
| `getCategoryItems(id,count,skip)` | `GET /category/:id?count=&skip=` | |
| `getItem(id)` | `GET /items/:id` | rating/reviews/questions derived from item |
| `searchItems(q,count,skip,opts)` | `GET /searchitems?search=&count=&skip=&categoryIDs=&minPrice=&maxPrice=&tag=&sort=` | returns `{items,total}` |
| `getRandomItems(count,category?)` | `GET /items/randomitems?count=&category=` | featured/related |
| `ping()` | `GET /ping` | health |
`GET /bootstrap`**new, required in prod.** Returns the full `BootstrapConfig` (tenant, branding, theme, company, featureFlags, `apiEndpoints`, localization, seo, permissions, navigation, footer, pages, staticPages, widgetRegistry). Loaded once, cached (`ConfigService.loadBootstrap`, `shareReplay(1)`). Shape: `src/app/shared/models/config/bootstrap-config.model.ts`; canonical example: `src/assets/mock/bootstrap/bootstrap.json`.
On **localhost** (or `useMockData:true`) the `MockBootstrapProvider` serves `bootstrap.json` instead of hitting `/bootstrap`, so the app runs fully offline (`npm run dexar`).
---
## 4. POST — writes (storefront, real HTTP today)
Unchanged from `main` (payment writes omitted per scope):
| Method | Endpoint | Body |
|---|---|---|
| `addToCart(sessionId, items)` | `POST /websession/:sessionId` | `[{itemID,quantity,colour?,size?,price?}]` |
| `submitReview({...})` | `POST /items/:id/callback` | `{rating,comment,sessionID,timestamp}` |
| `submitQuestion({...})` | `POST /items/:id/questiion` | `{question,sessionID,timestamp}`**note existing path typo, keep it** |
| `submitPurchaseEmail({...})` | `POST /purchase-email` | `{email,phone?,telegramUserId,items[]}` |
---
## 5. New backend surface the frontend is waiting on
Declared in the bootstrap model (`apiEndpoints.builder`, `apiEndpoints.backoffice`) but currently `{}` empty and served by **localStorage / in-memory gateways**. UI is done; wiring a real API = implement one gateway/provider against the existing interface and rebind its DI token — no page/component changes.
### 5a. Builder (Project Editor) — **highest priority**
The editor edits the same `BootstrapConfig` the storefront consumes. Today `ProjectEditorIoService` only `JSON.stringify`s it (export) — **no HTTP, no persistence**. Needed:
- `GET /builder/bootstrap/draft` → current draft `BootstrapConfig`
- `PUT /builder/bootstrap/draft` ← save draft
- `POST /builder/bootstrap/publish` → promote draft to the live `GET /bootstrap` document
- `POST /builder/bootstrap/validate` (optional) → **server-side re-validation is mandatory**; the client validator is not a trust boundary
### 5b. Backoffice (admin) — mock gateways awaiting real API
Each domain has a `*Gateway`/`*Provider` interface + a `*LocalGateway` mock bound via token. Implement `*ApiGateway` against the same interface, endpoints under `/backoffice/...`, models in `features/admin/<area>/models/*.model.ts`:
categories, products, orders, transactions, users/roles, dashboard metrics, monitoring feeds, media.
Provider selection is centralized in `RuntimeProviderStrategyService` (`getBackofficeProviderMode`, `getProductProviderMode`, `getCategoryProviderMode`): returns `mock` only when `environment.useMockData`, else `api`.
> Full per-domain punch list and model shapes: [`BACKEND.md`](BACKEND.md) §5§16. Handoff framing: [`BACKEND-DIFF-VS-MAIN.md`](BACKEND-DIFF-VS-MAIN.md).
---
## 6. Contract rules for the backend
- **Do not change** existing storefront read/write shapes (categories, items, search, cart, reviews, questions) — frontend `main` and `B2B` both depend on them.
- **Serve `GET /bootstrap`** matching `bootstrap-config.model.ts`. Field-name tolerance exists on the client, but match the model to avoid normalization surprises.
- Respect request headers `X-Region`, `X-Language`, `Currency`, `WebSessionID` for localization/session-scoped responses.
- New namespaces `builder/*` and `backoffice/*` are yours to define; keep them consistent with the declared `apiEndpoints` block so they can be published via bootstrap.
- **Out of scope here:** auth (Telegram QR session) and payments (`/qr`, `/card`, `/cart` payment) — unchanged, see existing docs.
---
## 7. Known reliability issue (not fixable in frontend)
Intermittent `502`/`504` on refresh/back-nav in prod originate from the upstream reverse proxy at `api.dexarmarket.ru:445` / `users.vitanova.network:456` — the frontend calls those absolute URLs directly. Needs DevOps/backend investigation of upstream health/timeouts around bootstrap + session-check.

View File

@@ -1,197 +0,0 @@
# 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 (Sprint 20):** `GET /category` (existing) still backs the public storefront via `CategoryDto -> CategoryMapper -> Category -> CategoryFacade`, unchanged. A full admin editor now exists at `features/admin/categories/` (list + create/edit, hierarchy, drag-and-drop reorder, soft delete/restore, draft/publish, SEO/translations — see `docs/ADMIN.md` "Sprint 20") but it runs entirely against `AdminCategoriesLocalGateway`, an in-memory cache seeded once from `BackofficeDataService.loadCategories()` (`CategoryCardConfig`, no hierarchy) — nothing persists across a page reload.
**Gap:** no admin write path (create/update/delete/reorder categories) exists on the backend. `AdminCategory` also carries fields the current `CategoryDto`/`CategoryCardConfig` don't have yet: `parentId` (hierarchy), `slug`, `icon`, `imageUrl`, `status` (draft/published), `deletedAt` (soft delete), `seo`, per-locale `translations`.
**Needed:** category CRUD endpoints matching the `AdminCategory` shape (`src/app/features/admin/categories/models/admin-category.model.ts`) plus a bulk reorder endpoint (`order` field) and a slug-uniqueness check (`GET /admin/categories/slug-taken?slug=...`, mirrors `AdminCategoriesGateway.isSlugTaken`).
**Frontend files:** implement `AdminCategoriesApiGateway` against `AdminCategoriesGateway` (`services/admin-categories-gateway.interface.ts`) and rebind via an injection token (same swap pattern as `AdminDashboardMetricsGateway`/`BACKOFFICE_DATA_PROVIDER`) — the facade and pages don't change. Also still open: wire `admin/products`' category dropdown to `AdminCategoriesGateway` instead of its own `AdminProductsGateway.loadCategories()` (Sprint 21).
## 6. Products
**Current behavior (Sprint 21):** `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. Sprint 21 added `barcode`, `archived` (soft archive/restore), `variants` (`{name, price, quantity}[]`), `relatedProductIds`, and wired the category dropdown to `AdminCategoriesGateway` (see item 5) instead of its own seed.
**Needed:** product CRUD endpoints matching the current `AdminProduct` shape (`src/app/features/admin/products/models/admin-product.model.ts`) — `itemID`, `name`, `price`, `currency`, `categoryID`, `visible`, `archived`, `discount`, `images`, `badges`, `media`, `specifications`/`attributes`, `variants`, `relatedProductIds`, plus a bulk endpoint matching `PATCH /items/bulk`-style semantics for `applyBulkVisibility`/`applyBulkDelete`.
**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
**Current behavior (Sprint 23):** `features/admin/orders/` now exists as an admin CRUD-ish surface (list/detail, status changes, refund request, cancel, notes, CSV export, print invoice) but runs entirely against `AdminOrdersLocalGateway`, which fabricates 24 synthetic in-memory orders — there is still no real order data anywhere in this system. The dashboard's Orders/Revenue cards (Sprint 19) still correctly render `pending-backend` rather than reading from this mock (they're intentionally not wired to it — the mock is order-management scaffolding, not a real metrics source).
**Needed:** a real order domain — `AdminOrder` shape is in `src/app/features/admin/orders/models/admin-order.model.ts`. At minimum: order CRUD, status transitions with a timeline/audit trail, payment status, refund workflow, and a revenue aggregation endpoint for the dashboard cards.
**Frontend files:** implement `AdminOrdersApiGateway` against `AdminOrdersGateway` (`services/admin-orders-gateway.interface.ts`) and rebind via an injection token — facade and pages don't change.
## 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.
---
## 13. Transactions (Sprint 24, mock/local)
**Current behavior:** `features/admin/transactions/` exists (list, retry-failed, fraud flag, per-transaction audit log, CSV export) against `AdminTransactionsLocalGateway`, which derives one synthetic transaction per seeded mock order from item 7's `AdminOrdersLocalGateway` — no real payment/transaction data exists.
**Needed:** a real payments/transactions domain (card, QR, cash-on-delivery), linked to orders, with retry semantics matching whatever the actual payment provider supports, and fraud-flag persistence.
**Frontend files:** implement `AdminTransactionsApiGateway` against `AdminTransactionsGateway` (`services/admin-transactions-gateway.interface.ts`) and rebind via an injection token.
## 14. Users, roles & permissions (Sprint 25, mock/local)
**Current behavior:** `features/admin/users/` (users, built-in roles, invitations, per-user mock sessions, per-user audit log) against `AdminUsersLocalGateway` — fully synthetic, no backend. Passwordless login itself is real (`AdminAuthService`, Telegram QR, `docs/BACKEND.md` item 1) — only the roles/permissions/invitations/multi-session-listing layer on top is mocked.
**Needed:** a real user/role domain tied to the eventual server-side admin-authorization enforcement in item 1 — role assignment, a real permission catalog, invitation emails, and genuine multi-device session listing (the current `AdminAuthService` only ever knows about the current browser's session).
**Frontend files:** implement `AdminUsersApiGateway` against `AdminUsersGateway` (`services/admin-users-gateway.interface.ts`) and rebind via an injection token.
## 15. Monitoring (Sprint 26, mock/local except Health)
**Current behavior:** `features/admin/monitoring/` — Health section reads real data (`AdminDashboardFacade.healthChecks`, unchanged from Sprint 19). Everything else (audit/security/login/failed-login/API/error/warning event feed, queue depths, webhook deliveries) is synthetic, seeded once in `AdminMonitoringLocalGateway` — no logging, queue, or webhook infrastructure exists anywhere in this system.
**Needed:** real structured logging with a query API (by category/level/actor/time range), real queue introspection (whatever job runner ships), and real webhook delivery tracking once webhooks exist as a feature at all.
**Frontend files:** implement `AdminMonitoringApiGateway`-equivalent methods against a to-be-defined `AdminMonitoringGateway` interface (`AdminMonitoringLocalGateway` currently has no interface extracted — add one when a real implementation is built, mirroring the pattern used everywhere else in `admin/*`).
## 16. Analytics - visitors/funnels/heatmaps (Sprint 27, no backend at all)
**Current behavior:** `features/admin/analytics/` computes real revenue/orders/top-products aggregations from the existing mock order data (see item 7), but visitor traffic, conversion funnels, and heatmaps have zero data source anywhere in this system (no analytics/tracking pipeline, no event collection) — these render `pending-backend` badges rather than fabricated numbers.
**Needed:** a traffic/event tracking pipeline (page views, sessions, conversion events) and a funnel/heatmap aggregation service, before this section of the Analytics page can show anything real.
**Frontend files:** `features/admin/analytics/pages/admin-analytics-page.component.html` currently renders the pending-backend badge inline (no gateway method exists for this yet, unlike every other mocked domain in this doc).
## 17. Sitemap generation (Sprint 28, static baseline only)
**Current behavior:** `public/sitemap.xml` (new, Sprint 28) lists only the statically-known top-level marketplace routes (home/catalog/search/wishlist/compare) for the default `ru` locale segment, referenced from `public/robots.txt`'s `Sitemap:` directive.
**Gap:** this is a multi-tenant, config-driven platform (`docs/ARCHITECTURE.md`) — supported locales, categories, products, and static pages are all resolved at runtime from each tenant's bootstrap config, not enumerable from the frontend at build time. A real per-tenant sitemap covering `/:lang/product/:id`, `/:lang/catalog/:categoryId`, and `/:lang/:staticPath` needs a backend/build-time job that reads the same bootstrap data source (categories/products/static pages, items 5/6/3-4 above) per tenant/domain and regenerates or serves this file dynamically — not something the SPA can produce correctly on its own.
**Needed:** a build-time or server-side sitemap generator with access to the real per-tenant product/category/static-page lists and domain, likely alongside whatever eventually serves `bootstrap.json` per-domain server-side (item 2).
## 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.

View File

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

@@ -2,7 +2,7 @@
Replaces the old `docs/Project-Editor.md` (content merged in below and extended with the Sprint 19 field-description/dropdown work). 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`). The Project Editor (`src/app/features/project-editor/`) edits the tenant's `BootstrapConfig` (`docs/backend/BACKEND-INTEGRATION.md#4-bootstrap`) 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/ src/app/features/project-editor/
@@ -46,7 +46,7 @@ Route: `/edit/:section` or `/{lang}/edit/:section`. `/backoffice/static-pages` (
- **Reset section**: reverts one section's bootstrap keys (per `EDITOR_SECTION_BOOTSTRAP_KEYS` in `models/project-editor.model.ts`) to `originalBootstrap`. Confirmation required. - **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. - **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. - **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. - **No backend persistence exists for any of this today** — see `docs/backend/BACKEND-INTEGRATION.md#67-builder--bootstrap-draftpublishvalidate-planned-highest-priority` for the endpoints needed.
## Configuration schema, form engine, and validation architecture (Sprint X+1) ## Configuration schema, form engine, and validation architecture (Sprint X+1)
@@ -88,7 +88,7 @@ No changes to `ProjectEditorIoService` (export/import), `ProjectEditorDraftStora
## Admin Authentication (QR reuse) ## 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. 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/BACKEND-INTEGRATION.md#25-the-admin-authorization-gap-critical--security-relevant-unresolved`.
## Design system primitives (post-Sprint 30 redesign) ## Design system primitives (post-Sprint 30 redesign)
@@ -125,7 +125,7 @@ Interaction feedback + motion applied consistently, all gated behind `prefers-re
**HTML-mode validation (Sprint X+2):** switching from raw-HTML back to the visual surface now runs `schema/validators/primitives.validateHtml` (stack-based tag-balance check) first; a malformed edit (unclosed/mismatched tag) stays in code mode with an inline error instead of silently corrupting the visual editor. **HTML-mode validation (Sprint X+2):** switching from raw-HTML back to the visual surface now runs `schema/validators/primitives.validateHtml` (stack-based tag-balance check) first; a malformed edit (unclosed/mismatched tag) stays in code mode with an inline error instead of silently corrupting the visual editor.
**Caveat:** implemented on the deprecated `document.execCommand` API. It works in all current browsers today but is a legacy web API with no modern drop-in replacement; if a future browser drops it, this component needs a rewrite (e.g. a maintained rich-text library). By design it emits **raw, unsanitized** HTML — sanitization is a storefront-render concern, not an authoring one (see `docs/BACKEND.md` item 4 on server-side content moderation on publish; `StaticPageComponent` and `StaticPagePreviewComponent` both run content through `DomSanitizer` before render). **Caveat:** implemented on the deprecated `document.execCommand` API. It works in all current browsers today but is a legacy web API with no modern drop-in replacement; if a future browser drops it, this component needs a rewrite (e.g. a maintained rich-text library). By design it emits **raw, unsanitized** HTML — sanitization is a storefront-render concern, not an authoring one (see `docs/backend/BACKEND-INTEGRATION.md#68-builder--content-pages--cms-planned` on server-side content moderation on publish; `StaticPageComponent` and `StaticPagePreviewComponent` both run content through `DomSanitizer` before render).
## Field-description / dropdown UX (Sprint 19+) ## Field-description / dropdown UX (Sprint 19+)

View File

@@ -51,4 +51,4 @@ src/app/
## Dynamic widget/section rendering from bootstrap JSON ## 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`. Full detail in `docs/ARCHITECTURE.md` and `docs/backend/BACKEND-INTEGRATION.md#4-bootstrap`. 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

@@ -28,7 +28,7 @@ Standards referenced below are enforced, not suggestions: `docs/architecture/fou
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. 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. 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/BACKEND-INTEGRATION.md#615-backoffice--dashboard-metrics--recent-activity-planned`) — 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. 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.
@@ -56,5 +56,5 @@ Full narrative and known gaps: `docs/ADMIN.md`.
8. Build the container/page component that injects the facade and wires routing. 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. 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. 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. 11. Document backend gaps (if any) in `docs/backend/BACKEND-INTEGRATION.md` (§6, endpoints by domain) using the same CURRENT/PLANNED/FUTURE tagging as the existing entries.
12. Run `npm run arch:check` (import boundaries + circular dependencies) and `npx tsc -p tsconfig.app.json --noEmit` before committing. 12. Run `npm run arch:check` (import boundaries + circular dependencies) and `npx tsc -p tsconfig.app.json --noEmit` before committing.

View File

@@ -2,7 +2,7 @@
## What this is ## 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. 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/backend/BACKEND-INTEGRATION.md#4-bootstrap` for the mechanics.
Every tenant conceptually has three surfaces on this one codebase: Every tenant conceptually has three surfaces on this one codebase:
- **Website** — the public storefront (catalog, product pages, cart, static pages). - **Website** — the public storefront (catalog, product pages, cart, static pages).
@@ -15,14 +15,13 @@ Every tenant conceptually has three surfaces on this one codebase:
- 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. - 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. - 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. - New tenants are onboarded by domain + bootstrap config + backend data, not by forking the frontend.
- Full contract: `docs/BOOTSTRAP.md`. - Full contract: `docs/backend/BACKEND-INTEGRATION.md#4-bootstrap`.
## Doc index ## Doc index
- **[ARCHITECTURE.md](ARCHITECTURE.md)** — layered architecture, container/facade/service pattern, bootstrap/theme/widget engines, links to the enforced ADRs. - **[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. - **[backend/BACKEND-INTEGRATION.md](backend/BACKEND-INTEGRATION.md)** — the single canonical backend/API spec: architecture, auth, security, bootstrap, conventions, every endpoint (CURRENT/PLANNED/FUTURE), DTOs, state machines, validation, media, errors, localization, caching. 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. - **[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. - **[EDITOR.md](EDITOR.md)** — the Project Editor: every section, the save/publish/draft/reset model, and the field-description/dropdown UX.
- **[StaticPages.md](StaticPages.md)** — the Static Pages module: full field reference, per-page draft/publish gating, CRUD/search/filter/bulk, rich text, device preview, nav integration (Sprint X+2). - **[StaticPages.md](StaticPages.md)** — the Static Pages module: full field reference, per-page draft/publish gating, CRUD/search/filter/bulk, rich text, device preview, nav integration (Sprint X+2).
- **[PROJECT-STRUCTURE.md](PROJECT-STRUCTURE.md)** — folder-by-folder tour of `src/app/**` with a worked "add a new feature" example (admin dashboard). - **[PROJECT-STRUCTURE.md](PROJECT-STRUCTURE.md)** — folder-by-folder tour of `src/app/**` with a worked "add a new feature" example (admin dashboard).
@@ -57,5 +56,5 @@ See root `CLAUDE.md` for the full Barry Cache workflow and memory policy.
- **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 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`. - **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`. - 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/BACKEND-INTEGRATION.md#67-builder--bootstrap-draftpublishvalidate-planned-highest-priority`.
- 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. - This documentation set (`docs/PROJECT.md`, `ARCHITECTURE.md`, `backend/BACKEND-INTEGRATION.md`, `FRONTEND.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,6 +1,6 @@
# Static Pages (Project Editor module) # Static Pages (Project Editor module)
Sprint X+2. Full-featured CRUD editor for tenant static content (About, Privacy, Terms, Contacts, custom pages, etc.), living inside the Project Editor at `/edit/static-pages`. Edits `bootstrap.staticPages` directly — the same model the storefront renders from (`docs/BOOTSTRAP.md`), no parallel content store. Sprint X+2. Full-featured CRUD editor for tenant static content (About, Privacy, Terms, Contacts, custom pages, etc.), living inside the Project Editor at `/edit/static-pages`. Edits `bootstrap.staticPages` directly — the same model the storefront renders from (`docs/backend/BACKEND-INTEGRATION.md#46-staticpages`), no parallel content store.
`/backoffice/static-pages` (Admin dashboard) redirects here rather than hosting a second CRUD UI over the same data. `/backoffice/static-pages` (Admin dashboard) redirects here rather than hosting a second CRUD UI over the same data.

View File

@@ -1,451 +0,0 @@
# Backend Platform API Specification
Status: Draft for implementation handoff
Date: 2026-07-03
Scope: Marketplace Platform (Website + Builder + Backoffice)
## 1. Tenant Resolution
### Mechanism
- Backend resolves tenant from HTTP Host header.
- Frontend never sends tenant id or project key.
- Tenant resolution occurs before authorization and route handling.
### Resolution Rules
1. Exact host match in tenant registry.
2. Alias host fallback.
3. Unknown host returns 404 tenant_not_found.
### Validation
- Host must be present.
- Host must map to active tenant.
- Suspended tenant returns 403 tenant_suspended.
## 2. Bootstrap API
### Endpoint
- Method: GET
- Path: /bootstrap
### Purpose
- Return complete tenant runtime configuration for frontend bootstrap.
### Authorization
- Public for website runtime.
- Optional authenticated extensions for builder/backoffice context may be included via claims.
### Request
- Headers: Host (required)
- Query: locale (optional)
### Response
- 200 with BootstrapConfig payload.
### Validation
- Schema version required.
- Required segments: tenant, branding, theme, featureFlags, navigation, pages.
### Example Response
```json
{
"schemaVersion": "1.0.0",
"generatedAt": "2026-07-03T00:00:00Z",
"tenant": { "id": "tenant-default-001", "slug": "default", "host": "default.local" },
"branding": { "brandName": "Marketplace", "logoUrl": "/icons/icon-192x192.png", "faviconUrl": "/icons/icon-192x192.png" },
"theme": { "themeId": "default-light", "mode": "light", "palette": { "primary": "#497671" }, "typography": { "primaryFontFamily": "DM Sans, sans-serif", "baseFontSize": 16 }, "spacing": { "unit": 4, "scale": [0,4,8] }, "borderRadiusScale": { "md": "12px" }, "shadows": { "md": "0 4px 12px rgba(0,0,0,0.15)" }, "iconSet": "default" },
"featureFlags": { "wishlist": true, "reviews": true },
"navigation": { "header": [], "footer": [] },
"pages": []
}
```
## 3. Configuration API
### Endpoint
- Method: GET
- Path: /configuration
### Purpose
- Return complete editable configuration model for Builder.
### Authorization
- Required: builder.read
### Request
- Headers: Authorization bearer token
### Response
- 200 configuration aggregate for tenant.
### Validation
- Caller must belong to tenant context.
- Caller role must include builder permissions.
### Example Response
```json
{
"tenantId": "tenant-default-001",
"branding": { "brandName": "Marketplace" },
"theme": { "themeId": "default-light" },
"navigation": { "header": [], "footer": [] },
"pages": []
}
```
## 4. Website API
### Endpoint
- Method: GET
- Path: /website/pages/{pageKey}
### Purpose
- Return website page composition for runtime rendering.
### Authorization
- Public.
### Request
- Path: pageKey required
### Response
- 200 PageConfig with sections/widgets.
### Validation
- pageKey must exist for tenant.
- hidden pages return 404.
### Example Response
```json
{
"id": "page-home",
"key": "home",
"layout": "default-public",
"sections": [
{
"id": "section-hero",
"type": "hero",
"order": 1,
"widgets": [
{ "id": "widget-hero-main", "type": "hero", "version": "1.0.0", "props": { "title": "Welcome" } }
]
}
]
}
```
## 5. Builder API
### Endpoint
- Method: PUT
- Path: /builder/configuration
### Purpose
- Save tenant configuration from Builder Sandbox.
### Authorization
- Required: builder.write
### Request
- Body: full or partial configuration document.
### Response
- 200 updated configuration metadata.
### Validation
- JSON schema validation.
- Widget types must be registered and supported.
- Route/path uniqueness checks.
### Example Request
```json
{
"branding": { "brandName": "Updated Brand" },
"featureFlags": { "chat": true },
"pages": []
}
```
### Example Response
```json
{
"version": 42,
"updatedAt": "2026-07-03T12:00:00Z",
"updatedBy": "user-100"
}
```
## 6. Backoffice API
### Endpoint
- Method: GET
- Path: /backoffice/dashboard
### Purpose
- Return dashboard aggregates for backoffice operations.
### Authorization
- Required: backoffice.read
### Request
- Optional query filters by date range.
### Response
- KPIs and entity counts.
### Validation
- Caller must belong to tenant.
### Example Response
```json
{
"ordersToday": 14,
"openOrders": 38,
"products": 1024,
"customers": 5600,
"inventoryAlerts": 12
}
```
## 7. Products API
### Endpoint
- GET /products
- GET /products/{id}
- POST /products
- PUT /products/{id}
- DELETE /products/{id}
### Purpose
- Product catalog management for backoffice.
### Authorization
- Read: backoffice.products.read
- Write: backoffice.products.write
### Request
- Supports paging/filter/sort on GET /products.
### Response
- Product entities aligned to UI contracts.
### Validation
- SKU unique per tenant.
- Price and currency required.
- Visibility and status must be valid enum values.
### Example Product
```json
{
"id": "prod-001",
"sku": "SKU-001",
"title": "Wireless Headphones",
"price": { "amount": 149990, "currency": "RUB" },
"stockStatus": "in_stock",
"visible": true
}
```
## 8. Categories API
### Endpoint
- GET /categories
- POST /categories
- PUT /categories/{id}
- DELETE /categories/{id}
### Purpose
- Category tree management.
### Authorization
- Read: backoffice.categories.read
- Write: backoffice.categories.write
### Validation
- Category id unique.
- Parent relation must not create cycles.
### Example Category
```json
{
"id": "cat-001",
"title": "Electronics",
"parentId": null,
"itemsCount": 120,
"visible": true
}
```
## 9. Orders API
### Endpoint
- GET /orders
- GET /orders/{id}
- PATCH /orders/{id}/status
### Purpose
- Order lifecycle tracking and updates.
### Authorization
- Read: backoffice.orders.read
- Write: backoffice.orders.write
### Validation
- Status transition must be legal according to state machine.
### Example Response
```json
{
"id": "ord-1001",
"status": "processing",
"total": { "amount": 9900, "currency": "RUB" },
"createdAt": "2026-07-03T10:20:00Z"
}
```
## 10. Media API
### Endpoint
- POST /media/upload
- GET /media/{id}
- DELETE /media/{id}
### Purpose
- Media asset management for products/widgets/pages.
### Authorization
- Required: backoffice.media.write for upload/delete.
### Validation
- File size/type constraints.
- Malware scan required before publish.
### Example Response
```json
{
"id": "media-001",
"url": "https://cdn.example.com/tenant-default/media-001.jpg",
"mimeType": "image/jpeg"
}
```
## 11. Localization API
### Endpoint
- GET /localization/dictionaries/{locale}
- PUT /localization/dictionaries/{locale}
### Purpose
- Localization dictionary retrieval and updates.
### Authorization
- Read: builder.localization.read
- Write: builder.localization.write
### Validation
- Locale must be supported by tenant.
- Keys must be unique.
### Example Response
```json
{
"locale": "ru",
"version": "1.0.3",
"entries": {
"nav.home": "Главная",
"nav.cart": "Корзина"
}
}
```
## 12. Permissions API
### Endpoint
- GET /permissions
- GET /roles
- PUT /roles/{role}
### Purpose
- Permission definitions and role bindings.
### Authorization
- Required: security.admin
### Validation
- Role names unique.
- Permission keys must exist in definitions.
### Example Response
```json
{
"definitions": [
{ "key": "builder.pages.edit" },
{ "key": "backoffice.products.read" }
],
"roles": [
{ "role": "builder_admin", "permissions": ["builder.pages.edit"] }
]
}
```
## 13. Feature Flags API
### Endpoint
- GET /feature-flags
- PUT /feature-flags
### Purpose
- Tenant capability toggles for optional modules.
### Authorization
- Read: builder.features.read
- Write: builder.features.write
### Validation
- Flag keys must be from allowed registry.
- Non-boolean values rejected.
### Example Response
```json
{
"wishlist": true,
"compare": true,
"reviews": true,
"blog": false,
"chat": false,
"analytics": true,
"notifications": true,
"coupons": true,
"loyalty": false,
"giftCards": false,
"invoices": true
}
```
## Error Model (Common)
### Structure
```json
{
"code": "validation_error",
"message": "Validation failed",
"details": [
{ "field": "pages[0].route.path", "message": "Path already exists" }
],
"traceId": "trc-123"
}
```
### Common Codes
- tenant_not_found
- tenant_suspended
- unauthorized
- forbidden
- validation_error
- conflict
- not_found
- internal_error
## Contract Compatibility Note
Authentication, payment, and authorization behavior and contracts in the current system are preserved as-is.
This document defines platform APIs around those stable integrations without changing their existing payload contracts.

View File

@@ -1,36 +1,11 @@
# ADR-004: Configuration Bootstrap and Provider Abstraction # ADR-004: Configuration Bootstrap and Provider Abstraction
Status: Accepted Status: Superseded
Date: 2026-07-03 Date: 2026-07-03
Superseded by: `docs/backend/BACKEND-INTEGRATION.md` §4 (Bootstrap) and §14 (Backend replacement pattern)
## Context ## Original decision (preserved for history)
Configuration must initially come from mock JSON and later from backend API without changing consumers. Configuration must initially come from mock JSON and later from backend API without changing consumers. `ConfigService` is the only configuration entrypoint; consumers depend on typed selectors only; provider implementation is swappable (`MockBootstrapProvider` / `ApiBootstrapProvider`); the frontend calls `GET /bootstrap` when the API provider is enabled and never passes a tenant id.
## Decision This decision remains in effect. The full, verified contract — endpoint, caching, field-by-field DTO reference, and the generalized mock↔API provider-swap pattern this ADR introduced (now used by every admin domain, not just bootstrap) — lives in `docs/backend/BACKEND-INTEGRATION.md`. Read that document for current, code-verified detail; this file is kept only so ADR-numbered references in `docs/architecture/foundation/README.md` continue to resolve.
Introduce configuration provider abstraction behind ConfigService.
- ConfigService is the only configuration entrypoint.
- Consumers depend on typed ConfigService selectors only.
- Provider implementation is swappable:
- MockBootstrapProvider
- ApiBootstrapProvider
- Frontend calls GET /bootstrap when API provider is enabled.
- Frontend does not pass tenant id.
## Consequences
Positive:
- Source-agnostic configuration usage.
- Mock-to-backend transition with minimal change surface.
Negative:
- Requires strict prohibition of direct JSON imports in components/services.
## Compliance Requirements
- No code outside ConfigService may load bootstrap JSON.
- No page/widget/component may access configuration files directly.

View File

@@ -1,35 +1,11 @@
# ADR-010: Backward Compatibility for Authentication, Payment, and Authorization # ADR-010: Backward Compatibility for Authentication, Payment, and Authorization
Status: Accepted Status: Superseded
Date: 2026-07-03 Date: 2026-07-03
Superseded by: `docs/backend/BACKEND-INTEGRATION.md` §2 (Authentication), §2.8 (Payments), §2.5 (admin authorization gap)
## Context ## Original decision (preserved for history)
Authentication and payment flows are proven and contract-sensitive. Platform refactoring must not break existing integrations. Authentication and payment flows are proven and contract-sensitive. Platform refactoring must not break existing integrations. Freeze behavior and contracts for authentication flow, payment API interactions, and authorization logic. Allow only encapsulation and integration-layer isolation, not contract redesign.
## Decision This decision remains in effect. The full, verified contract — the Telegram QR/session flow, cookie policy, the frozen payment endpoint shapes, and the still-unresolved admin-authorization gap this ADR's constraint interacts with — lives in `docs/backend/BACKEND-INTEGRATION.md` §2 and §2.5§2.8. Read that document for current, code-verified detail; this file is kept only so ADR-numbered references in `docs/architecture/foundation/README.md` continue to resolve.
Freeze behavior and contracts for:
- Authentication flow.
- Payment API interactions.
- Authorization logic.
Allow only encapsulation and integration-layer isolation, not contract redesign.
## Consequences
Positive:
- Prevents regressions in critical commerce and access flows.
- Enables architecture modernization around stable core behavior.
Negative:
- Some suboptimal legacy internals may remain until controlled replacement strategy is approved.
## Compliance Requirements
- Existing auth/payment request/response contracts remain unchanged.
- Behavior-equivalent wrappers/adapters are allowed.
- Any change requires explicit ADR and compatibility test evidence.

File diff suppressed because it is too large Load Diff

View File

@@ -16,5 +16,5 @@ Crawl-delay: 1
# Static baseline sitemap (home/catalog/search/wishlist/compare only) - see # Static baseline sitemap (home/catalog/search/wishlist/compare only) - see
# public/sitemap.xml's own header comment for what this does and does not # public/sitemap.xml's own header comment for what this does and does not
# cover (no per-tenant product/category/static-page URLs yet - needs a # cover (no per-tenant product/category/static-page URLs yet - needs a
# backend/build-time generator, documented in docs/BACKEND.md). # backend/build-time generator, documented in docs/backend/BACKEND-INTEGRATION.md#619-sitemap-future--static-baseline-only-today).
Sitemap: /sitemap.xml Sitemap: /sitemap.xml

View File

@@ -12,7 +12,7 @@
/:lang/product/:id, /:lang/catalog/:categoryId, and /:lang/:staticPath /:lang/product/:id, /:lang/catalog/:categoryId, and /:lang/:staticPath
needs a backend/build-time job that reads the same bootstrap data source needs a backend/build-time job that reads the same bootstrap data source
and regenerates this file (or serves it dynamically) per tenant/domain - and regenerates this file (or serves it dynamically) per tenant/domain -
see docs/BACKEND.md. Until that exists, this static file is a reasonable see docs/backend/BACKEND-INTEGRATION.md#619-sitemap-future--static-baseline-only-today. Until that exists, this static file is a reasonable
floor, not the full picture. floor, not the full picture.
--> -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

View File

@@ -13,7 +13,7 @@ import { environment } from '../../../environments/environment';
* never authenticates the customer session or vice versa: distinct cookie * never authenticates the customer session or vice versa: distinct cookie
* name, distinct signals, distinct guard/interceptor. * name, distinct signals, distinct guard/interceptor.
* *
* Backend gap this creates (see docs/BACKEND.md, item 1): since the session * Backend gap this creates (see docs/backend/BACKEND-INTEGRATION.md §2.5): since the session
* API itself has no concept of "admin", the frontend cannot tell an admin * API itself has no concept of "admin", the frontend cannot tell an admin
* Telegram session from a regular one. Actual admin authorization must be * Telegram session from a regular one. Actual admin authorization must be
* enforced server-side when admin API calls are made with the resulting * enforced server-side when admin API calls are made with the resulting