Skip to content

ADR-013: Storefronts are themes on a public Storefront API

Status: accepted 4 October 2026 (storefront platform plan, improvement-plans/Storefront-Platform-Plan.md). Implemented so far:

  • Phase A: the API.
  • Phase B: versioned configuration, theme manifests and Settings → Storefront.
  • Phase C: the Astro runtime, the SDK, and the Bloom and Noir themes, running locally.

Still to do:

  • Phase D: the production cutover. It was prepared with the files in deploy/storefront/ and a runbook in operations, and went live on 5 October 2026: shop subdomains are served by the Astro storefront.
  • Phase E: the internal MCP server (storefront agent), done 5 October 2026.

The storefront was a set of Inertia pages inside the Laravel app, with layout and copy fixed in code. A review found invented reviews and delivery promises, broken actions, raw stock shown to customers, and no way for a shop to describe itself. The owner wants:

  • Shops that choose a theme from a catalogue.
  • An internal AI agent that can adjust a shop’s storefront.
  • Eventually, a public SDK with a theme marketplace, where theme code must be treated as untrusted.
  1. A public, versioned Storefront API (/storefront-api/v1, specified in docs/storefront-api/openapi.yaml) is the only data a storefront theme receives.
    • Responses are public data only: no costs, stock counts or SKUs.
    • Availability is a label (available, few_left, sold_out); the one number is max_per_bouquet, capped at 99.
    • Prices are exact decimal strings computed by the server.
    • Catalogue answers are cacheable for 30 seconds; the cart never is.
    • Writes accept JSON only, and there are no CORS grants, so the cart cookie (httpOnly, host-only, SameSite=Lax, stored hashed) needs no CSRF token.
    • Rate limits apply per client.
    • A contract test fails if a route is undocumented or a response differs from the specification, including any undocumented field.
  2. Storefronts render in Astro (server-side on Node, cached per shop), on the same {slug}.flowery.com.ua host. nginx sends the control panel, authentication and the API to Laravel and everything else to Astro.
  3. A theme is a package with a manifest (templates, section types, a settings schema in a small typed DSL). It may import only the storefront SDK and its own files.
  4. Each shop’s storefront configuration is a versioned document (profile, theme, settings, sections) with draft, preview, publish and revert. The settings page and an internal MCP server edit the same draft through one validated service. Raw HTML, CSS or JS is never accepted.
  • One contract serves first-party themes, the internal agent’s previews, and later third-party themes and other clients.
  • The Inertia storefront was removed after the Astro storefront went live (5 October 2026). Laravel keeps only a “back in a moment” page for the moments the storefront process is down.
  • Gate before any third-party theme goes live:
    • The control panel and staff session must move off the storefront origin (for example to app.flowery.com.ua, with a host-only session cookie), because the current parent-domain session cookie (ADR-006) would expose staff sessions to theme JavaScript on any shop subdomain.
    • Third-party themes run in an isolated runtime with only the public API.
  • Per-IP rate limits assume the real client address reaches Laravel. When Astro calls the API server-side, it forwards the visitor’s address with a shared runtime key; Laravel trusts the forwarded address only with that key (phase C).
  • Theme boundaries are enforced by a check that runs in CI (npm run themes:check). This is a review aid for first-party themes, not a sandbox; untrusted themes still need the isolated runtime above.