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