# Frontend Default Bootstrap Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** When a marketplace has no published revision, the frontend renders a built-in, all-features-on generic placeholder instead of a broken/empty page — decided from one explicit `published: boolean` field on the `/bootstrap` response, not from HTTP status. **Architecture:** Add `published` to `BootstrapConfig`. Add one new frontend-only constant `DEFAULT_BOOTSTRAP: BootstrapConfig`, composed from existing `DEFAULT_HEADER_CONFIG` / `DEFAULT_MARKETPLACE_FEATURES_CONFIG` / `DEFAULT_PLATFORM_MODULES_CONFIG` plus a hardcoded generic shell for the sections with no existing default (`tenant`, `branding`, `theme`, `company`, `featureFlags`, `apiEndpoints`, `localization`, `seo`, `permissions`, `navigation`, `footer`, `pages`, `staticPages`). `ConfigService.loadBootstrap()` swaps its cached snapshot to `DEFAULT_BOOTSTRAP` whenever the fetched response has `published === false`. No provider changes. **Tech Stack:** Angular 22, RxJS, Jasmine/Karma (existing `.spec.ts` pattern in this repo). ## Global Constraints - `published` missing/undefined on a response must be treated as `true` (backward compatible — matches the existing pattern for `modules`/ADR-011). - Fallback is a whole-object swap — no field-level merging with the real response. - Fallback triggers only on the explicit `published: false` signal, never on HTTP failure (existing `catchError` behavior in `ConfigService` is untouched). - `DEFAULT_BOOTSTRAP.featureFlags` and `.features` must have every flag `true`. - Reuse `DEFAULT_HEADER_CONFIG`, `DEFAULT_MARKETPLACE_FEATURES_CONFIG`, `DEFAULT_PLATFORM_MODULES_CONFIG` as-is — do not redefine their values inline. --- ### Task 1: Add `published` to the `BootstrapConfig` contract **Files:** - Modify: `src/app/shared/models/config/bootstrap-config.model.ts` - Modify: `src/assets/mock/bootstrap/bootstrap.json` (add `"published": true` so the existing mock keeps behaving as "already live") **Interfaces:** - Produces: `BootstrapConfig.published: boolean` — consumed by Task 3 (`ConfigService`). - [ ] **Step 1: Add the field to the interface** In `src/app/shared/models/config/bootstrap-config.model.ts`, add `published` right after `generatedAt`: ```ts export interface BootstrapConfig { schemaVersion: string; generatedAt: string; published: boolean; tenant: TenantConfig; branding: BrandingConfig; theme: ThemeConfig; company: CompanyConfig; featureFlags: FeatureFlagsConfig; features?: MarketplaceFeaturesConfig; apiEndpoints: ApiEndpointsConfig; localization: LocalizationConfig; seo: SeoConfig; permissions: PermissionsConfig; header?: HeaderConfig; catalog?: CatalogConfig; layout?: PlatformLayoutConfig; navigation: NavigationConfig; footer?: FooterConfig; productPage?: ProductPageConfig; userExperience?: UserExperienceConfig; pages: PageConfig[]; staticPages?: StaticPagesConfig; widgetRegistry?: WidgetRegistryConfig; modules?: PlatformModulesConfig; seller?: SellerConfig; } ``` - [ ] **Step 2: Update the mock fixture** In `src/assets/mock/bootstrap/bootstrap.json`, add `"published": true,` as the line right after `"generatedAt": "2026-07-03T00:00:00Z",` (line 3). - [ ] **Step 3: Compile check** Run: `npx tsc --noEmit -p tsconfig.json` Expected: no new errors referencing `bootstrap-config.model.ts` or `bootstrap.json` (the mock file isn't type-checked, but any TS consumer that builds a `BootstrapConfig` object literal without `published` will now fail — confirms the field is wired through). - [ ] **Step 4: Commit** ```bash git add src/app/shared/models/config/bootstrap-config.model.ts src/assets/mock/bootstrap/bootstrap.json git commit -m "feat: add published field to BootstrapConfig contract" ``` --- ### Task 2: Add the `DEFAULT_BOOTSTRAP` constant **Files:** - Create: `src/app/shared/models/config/default-bootstrap.const.ts` - Modify: `src/app/shared/models/config/index.ts` (export the new file) - Test: `src/app/shared/models/config/default-bootstrap.const.spec.ts` **Interfaces:** - Consumes: `BootstrapConfig` (Task 1), `DEFAULT_HEADER_CONFIG` from `./header-config.model`, `DEFAULT_MARKETPLACE_FEATURES_CONFIG` from `./features-config.model`, `DEFAULT_PLATFORM_MODULES_CONFIG` from `./platform-modules.model`. - Produces: `DEFAULT_BOOTSTRAP: BootstrapConfig` — consumed by Task 3 (`ConfigService`). - [ ] **Step 1: Write the failing test** Create `src/app/shared/models/config/default-bootstrap.const.spec.ts`: ```ts import { DEFAULT_BOOTSTRAP } from './default-bootstrap.const'; describe('DEFAULT_BOOTSTRAP', () => { it('is marked unpublished', () => { expect(DEFAULT_BOOTSTRAP.published).toBe(false); }); it('has every feature flag turned on', () => { Object.values(DEFAULT_BOOTSTRAP.featureFlags).forEach(value => { expect(value).toBe(true); }); }); it('has every optional MarketplaceFeaturesConfig flag turned on', () => { expect(DEFAULT_BOOTSTRAP.features).toBeDefined(); Object.values(DEFAULT_BOOTSTRAP.features!).forEach(value => { expect(value).toBe(true); }); }); it('has at least one page with a hero section', () => { expect(DEFAULT_BOOTSTRAP.pages.length).toBeGreaterThan(0); const heroSection = DEFAULT_BOOTSTRAP.pages[0].sections.find(s => s.type === 'hero'); expect(heroSection).toBeDefined(); }); it('has a generic brand name, not a real tenant name', () => { expect(DEFAULT_BOOTSTRAP.branding.brandName).toBe('Marketplace'); }); }); ``` - [ ] **Step 2: Run test to verify it fails** Run: `ng test --include='**/default-bootstrap.const.spec.ts' --watch=false` Expected: FAIL — `Cannot find module './default-bootstrap.const'` - [ ] **Step 3: Write the constant** Create `src/app/shared/models/config/default-bootstrap.const.ts`: ```ts import { BootstrapConfig } from './bootstrap-config.model'; import { DEFAULT_HEADER_CONFIG } from './header-config.model'; import { DEFAULT_MARKETPLACE_FEATURES_CONFIG } from './features-config.model'; import { DEFAULT_PLATFORM_MODULES_CONFIG } from './platform-modules.model'; /** * Whole-object fallback rendered whenever the backend reports * `published: false` for the resolved marketplace (no published revision * yet). Every feature flag is on so it doubles as a full-surface product * demo. See docs/superpowers/specs/2026-08-22-frontend-default-bootstrap-design.md. */ export const DEFAULT_BOOTSTRAP: BootstrapConfig = { schemaVersion: '1.0.0', generatedAt: new Date(0).toISOString(), published: false, tenant: { id: 'tenant-default-unpublished', slug: 'default', code: 'DEFAULT', host: 'default.local', name: 'Marketplace', websiteBaseUrl: 'https://marketplace.local', builderBaseUrl: 'https://builder.marketplace.local', backofficeBaseUrl: 'https://backoffice.marketplace.local', defaultLocale: 'en', supportedLocales: ['en'], defaultCurrency: 'USD', supportedCurrencies: ['USD'], timezone: 'UTC', }, branding: { brandName: 'Marketplace', legalName: 'Marketplace', slogan: 'Your store, coming soon', logoUrl: '/icons/icon-192x192.png', logoCompactUrl: '/icons/icon-192x192.png', faviconUrl: '/favicon.ico', appIconUrl: '/icons/icon-192x192.png', supportEmail: 'support@marketplace.local', }, theme: { themeId: 'default-light', mode: 'light', palette: { primary: '#497671', secondary: '#a1b4b5', accent: '#a7ceca', success: '#10b981', warning: '#f59e0b', danger: '#ef4444', info: '#3b82f6', textPrimary: '#1e3c38', textSecondary: '#667a77', backgroundPrimary: '#ffffff', backgroundSecondary: '#f5f5f5', border: '#d3dad9', }, typography: { primaryFontFamily: 'DM Sans, sans-serif', headingFontFamily: 'DM Sans, sans-serif', baseFontSize: 16, }, spacing: { unit: 4, scale: [0, 4, 8, 12, 16, 24, 32, 48] }, borderRadiusScale: { sm: '8px', md: '12px', lg: '16px', xl: '22px' }, shadows: { sm: '0 2px 8px rgba(0,0,0,0.1)', md: '0 4px 12px rgba(0,0,0,0.15)', lg: '0 12px 32px rgba(73,118,113,0.2)', }, iconSet: 'default', }, company: { companyName: 'Marketplace', address: { country: '', city: '' }, contacts: { email: 'support@marketplace.local' }, }, featureFlags: { wishlist: true, compare: true, reviews: true, questions: true, comments: true, recommendations: true, blog: true, chat: true, analytics: true, notifications: true, coupons: true, loyalty: true, giftCards: true, invoices: true, }, features: DEFAULT_MARKETPLACE_FEATURES_CONFIG, apiEndpoints: { bootstrap: { path: '/bootstrap', method: 'GET', timeoutMs: 10000 }, website: {}, builder: {}, backoffice: {}, }, localization: { defaultLocale: 'en', supportedLocales: ['en'], currencyByLocale: { en: 'USD' }, dictionaries: [{ locale: 'en', dictionaryUrl: '/assets/i18n/en.json', version: '1.0.0' }], }, seo: { default: { title: 'Marketplace', description: 'Your store, coming soon', robots: 'noindex,nofollow' }, byPageKey: { home: { title: 'Marketplace - Home', description: 'Your store, coming soon', robots: 'noindex,nofollow' }, }, }, permissions: { definitions: [], roles: [] }, header: DEFAULT_HEADER_CONFIG, navigation: { header: [ { id: 'nav-home', labelKey: 'nav.home', route: '/', icon: 'home', order: 1 }, { id: 'nav-search', labelKey: 'nav.search', route: '/search', icon: 'search', order: 2 }, { id: 'nav-cart', labelKey: 'nav.cart', route: '/cart', icon: 'cart', order: 3 }, ], footer: [ { id: 'footer-about', labelKey: 'nav.about', route: '/about-us', order: 1 }, { id: 'footer-contacts', labelKey: 'nav.contacts', route: '/contacts', order: 2 }, ], }, footer: { paymentIcons: [], copyrightText: { en: '© 2026 Marketplace. All rights reserved.' }, legalPageKeys: ['about-us', 'privacy-policy', 'terms-of-service'], }, staticPages: { 'about-us': { route: '/about-us', title: { en: 'About Us' }, html: { en: '
This marketplace has not published its storefront yet.
' }, }, 'privacy-policy': { route: '/privacy-policy', title: { en: 'Privacy Policy' }, html: { en: 'Placeholder content until publish.
' }, }, 'terms-of-service': { route: '/terms-of-service', title: { en: 'Terms of Service' }, html: { en: 'Placeholder content until publish.
' }, }, }, pages: [ { id: 'page-home', key: 'home', title: 'Home', route: { path: '/', exact: true }, layout: { type: 'default' }, seoKey: 'home', visible: true, sections: [ { id: 'section-hero', type: 'hero', order: 1, layout: { strategy: 'hero', columns: 1, gap: '1.5rem', align: 'stretch' }, visibility: { desktop: true, tablet: true, mobile: true }, visible: true, widgets: [ { id: 'widget-hero-main', type: 'hero', version: '1.0.0', order: 1, padding: '0.5rem 0', visibility: { desktop: true, tablet: true, mobile: true }, visible: true, props: { title: { en: 'Welcome to Marketplace' }, subtitle: { en: 'This storefront has not been published yet' }, ctaLabel: { en: 'Learn more' }, }, }, ], }, { id: 'section-categories', type: 'categories', order: 2, layout: { strategy: 'grid', columns: 1, gap: '1.5rem', align: 'stretch' }, visibility: { desktop: true, tablet: true, mobile: true }, visible: true, widgets: [ { id: 'widget-categories-root', type: 'categories', version: '1.0.0', order: 1, padding: '0.25rem 0', visibility: { desktop: true, tablet: true, mobile: true }, visible: true, props: { title: 'Categories', source: 'root', emptyMessage: 'No categories available' }, }, ], }, ], }, ], modules: DEFAULT_PLATFORM_MODULES_CONFIG, }; ``` - [ ] **Step 4: Export it from the barrel file** In `src/app/shared/models/config/index.ts`, add one line (alphabetical position, after `catalog-config.model`): ```ts export * from './default-bootstrap.const'; ``` - [ ] **Step 5: Run test to verify it passes** Run: `ng test --include='**/default-bootstrap.const.spec.ts' --watch=false` Expected: PASS (5 specs) - [ ] **Step 6: Commit** ```bash git add src/app/shared/models/config/default-bootstrap.const.ts src/app/shared/models/config/default-bootstrap.const.spec.ts src/app/shared/models/config/index.ts git commit -m "feat: add DEFAULT_BOOTSTRAP placeholder config" ``` --- ### Task 3: Swap to `DEFAULT_BOOTSTRAP` in `ConfigService` when unpublished **Files:** - Modify: `src/app/core/config/config.service.ts` - Test: `src/app/core/config/config.service.spec.ts` (new file — none exists today) **Interfaces:** - Consumes: `DEFAULT_BOOTSTRAP` (Task 2), `BootstrapConfig.published` (Task 1), existing `CONFIG_PROVIDER` token / `ConfigProvider.loadBootstrap()`. - Produces: no new public method — `loadBootstrap()` and `getBootstrapSnapshot()` keep their existing signatures; behavior changes only in which object ends up cached. - [ ] **Step 1: Write the failing tests** Create `src/app/core/config/config.service.spec.ts`: ```ts import { TestBed } from '@angular/core/testing'; import { of } from 'rxjs'; import { ConfigService } from './config.service'; import { CONFIG_PROVIDER } from './config-provider.token'; import { ConfigProvider } from './config-provider.interface'; import { BootstrapConfig, DEFAULT_BOOTSTRAP } from '../../shared/models/config'; function makeRealBootstrap(overrides: Partial