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>
9.8 KiB
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
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' |
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)
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)
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)
{
"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
ConfigService.loadBootstrap()fetches (or, in mock mode, reads local JSON undersrc/assets/mock/bootstrap/) and parsesBootstrapConfig.PlatformRuntimeServiceapplies theme tokens as CSS variables, sets branding, and exposes the parsed pages/navigation/footer/static-pages to the rest of the app.- Section Engine / Widget Host render pages from
bootstrap.pageson route match (seedocs/ARCHITECTURE.md). PlatformRuntimeService.reloadFromBootstrap(next)re-applies an entire newBootstrapConfigin-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
localStorageas a draft (projectEditor.draftBootstrap.v1, scoped bytenant.id) so it survives reloads on the same browser. - Publish: runs
ProjectValidator, then callsPlatformRuntimeService.reloadFromBootstrap()for live in-memory preview and flips a localstatusflag — no backend call happens. This is the largest gap covered indocs/BACKEND.md. - Preview: same in-memory re-apply mechanism, without marking the state published.