Skip to content
weLabsweLabsStorefront Playbook
Live demoQuickstart

Developers

Decision records

Every architecture decision record in the storefront repository — its number, what it decided, and whether it is built or only designed.

Hard-to-reverse decisions are recorded as ADRs in docs/adr/ of the storefront repository (retail-store-frontend). Each has a context, a decision, the alternatives considered and the consequences, and each is marked accepted with a date. Read the full record before changing anything it covers. This page summarises all 26 documents.

ADR Title Decision Status
0001 Keep the capability-split Port; reject a single Provider interface The Port stays split into Capability interfaces composed into one adapter contract. From a reference repo it adopts the ideas, not the shape: a contract suite, a fake adapter as the reference implementation, and lint-enforced dependency direction. A flattened interface would be a 60-plus-method god-interface every backend must stub. Built
0002 Canonical model everywhere: no app-side data layer The lib/data accessor layer, its bridge and the “UI type” dialect are deleted. Components and Server Actions consume canonical types from the Port; money stays in minor units until the display edge; only the Composition Root names an adapter; the Fake Adapter replaces fixtures, so there is one code path. Built
0003 Identity goes behind the commerce Port Sign-in, registration, token refresh, revocation, OAuth and profile become the Identity capability, tokens in and tokens out. Auth.js session mechanics (cookie, TTLs, remember-me, single-flight refresh) stay in the app and never cross the Port. Built
0004 Template Families as an agency shelf Template Families cover only the look-bearing Page Types (Home, Listing, Product) plus a Shell Preset; they are packaged (templates, storefront-ui); chrome stays data; a family is chosen per deployment by env at build time. Built The machinery ships with one family, default.
0005 Bilingual deployments Locales are deployment config with the choice in the URL (Default Locale unprefixed); each layer owns its message catalogs, merged over English; preset data uses Translatable Markers; the Port carries the active Locale; styling is RTL-first with logical properties only. Built
0006 Sub-Order reads use store credentials with a response-side ownership assert Under Multivendor, Sub-Orders are read with the store’s keys and then every returned order’s customer_id must equal the verified session’s customer; any mismatch reads as not found. Designed only
0007 Multivendor Plugins are separate packages injected into the WooCommerce adapter One package per Multivendor Plugin (Dokan first), selected by WOO_MULTIVENDOR in the Composition Root and injected as a strategy that enriches Woo mappers while the raw payload is in hand. capabilities.multivendor would be Boolean(strategy). Designed only No flag, package or route exists.
0009 Additive third-party plugins ship with the Woo Adapter and activate on data presence Plugins are split by what they are. A purely additive plugin (PDF invoicing first) gets its own pure dialect package that adapter-woocommerce depends on, no Capability flag and no configuration: it activates when its data arrives. Documents are proxied through the Port, never linked. Built
ADR Title Decision Status
0006 Server-driven brand querying Add queryBrands(query) and brandFacets(query) to the Port so search, sort, filter and paging run on the backend, with no N+1 and no cap at 100 brands. listBrands() stays for callers that need the whole set. Built
0008 Theme data carries light and dark token maps, injected as a stylesheet StorefrontTheme carries cssVariables and cssVariablesDark. The root layout injects a <style id="rs-theme"> with doubled selectors (:root:root, html.dark.dark) instead of inline styles on <html>, so both Color Schemes survive the Port. Built
0009 Free-shipping progress is computed by the backend and rides the cart The Companion computes the per-zone remaining amount exactly as WooCommerce’s own free-shipping rule does and adds it to every Store API cart response. Cart.freeShipping is optional, not a Capability; the storefront only renders it and hides it when unknown. Built
0010 Dates render in the store’s timezone, not the visitor’s next-intl’s timeZone comes from the backend’s getStoreTimezone(), on server and client alike. Every date is a store event; where it can cost the shopper, the offset is labelled. Built
0011 Product videos are a sibling list to images, with a poster the backend guarantees Product.videos sits beside images, each video with a merchant-chosen 1-based position and a backend-guaranteed poster. ProductSummary never carries video; there is no Capability flag. Built
0012 Trust badges resolve backend-side; most specific level wins The backend resolves product, then Primary Category chain, then global. The most specific level that says anything wins as a whole list, in one of three modes: default, off or custom. Built
0013 Payment & Security card: resolved like trust badges, blank parts fall back per part Same walk and modes as trust badges; inside custom, a blank title, image or description falls back to the storefront’s translated default for that part. Built
0014 Review media is uploaded through the Port, one file at a time, ahead of the review Each file uploads first (so the form shows progress) and returns an id the review submission names. A reviewMedia capability plus a Review Media Policy (switch, counts, sizes, types). Built
0015 Review threads are WordPress child comments Two-level replies, a Seller Reply badge for store managers, held Shopper replies shown only to their author, a helpful toggle and private reports, listed with the review and written through the Port. Built
0016 Policy pages are backend pages, read through the Port, with no local copy Returns, shipping, privacy, terms and cookies are block-editor pages the Store Owner picks per policy and language, read with getPolicyPage. A missing page is a 404, and its links disappear. Built
0017 Header/footer scripts are raw HTML from the backend, placed by box Head, Body start and Footer boxes are injected as pasted, by position. Consent is the pasted tool’s job; SPA page views are pushed to dataLayer and fbq; scripts are kept off Payment Pages. Built
0018 The Address Book lives in companion user meta; WooCommerce’s fields mirror its defaults Up to 10 entries with Default Billing and Default Shipping roles; the book is the source of truth and WooCommerce’s billing and shipping fields mirror its defaults. An addressBook capability, with the legacy pair as the fallback. Built
0021 Product Preview: a one-time Backend link, a per-product session, a separate route The editor’s Preview button mints a signed one-time link (12 hours) that the storefront trades for a 1-hour session bound to one product, rendered on a separate read-only route that never reaches caches, listings or search. Built
0023 SEO: the storefront prints the tags and builds the structured data; the Backend supplies the words SEO Fields and SEO Patterns come from Yoast SEO, Rank Math or the Companion; the storefront resolves field, then pattern, then its own default, and prints every tag and JSON-LD block from one PageSeo. Private pages are noindex. Built
ADR Title Decision Status
0022 Embedded-only payment, and the Processor as a canonical concept Payment happens on the storefront checkout only. The Port names the Processor (stripe, paypal, demo); gateway ids stay opaque; public config comes from the backend; tokens are opaque; form versus button interaction; prepare, confirm and lost-placement recovery; Gateway Dialects on WooCommerce. The redirect kind was removed. Built
0023 The Shopper’s address reaches the Backend signed The storefront signs the Shopper’s IP with HMAC-SHA256 (X-RS-Shopper-Address, X-RS-Shopper-Signature) using a secret shared with wp-config.php, so the backend’s rate limits count Shoppers instead of the storefront server. Built
0024 A declined payment does not say why The Companion replaces every Store API decline reason with one generic message (keeping the code); the storefront shows its own wording; the real reason stays in order notes and plugin logs. Built
0025 Express Checkout: wallet buttons outside the checkout form Apple Pay, Google Pay and Link through Stripe on product, cart and checkout; getExpressCheckout plus the existing placeOrder; the backend enforces the displayed amount; the storefront owns the button look; a demo wallet in Demo Mode. Withdraws ADR 0022’s “express checkout is out of scope”. Built Real-wallet hand checks (issue #207) are pending. Present Apple Pay and Google Pay; the Link button must not ship until #207 is done.

The Companion keeps a separate, shorter set in Architectur-docs/decisions/ of its repository:

Number Topic
0001 Build our own JWT layer instead of the JWT Auth plugin
0003 SEO: supply data to the storefront, mirror the active SEO plugin, print nothing
0006 Storefront settings as WordPress abilities, for agents acting for the Store Owner
0007 Item SEO abilities write the SEO source in charge

Write one when a decision is hard to reverse, surprising without its context, or the result of a real trade-off. Number it after the last one, follow the existing shape (Status, Context, Decision, Alternatives considered, Consequences), and when it withdraws part of an earlier ADR, say so in both status lines. Update CONTEXT.md in the same change if the vocabulary moves.

Storefront Playbook · Built by weLabsFeaturesFAQTalk to us