Wocart

The starter, file by file

This page walks through a project created with npm create wocart-theme (Quick start). The project's own README.md and AGENTS.md say the same things, next to the code.

The project map

Code
src/ theme/ what Wocart Studio reads theme.ts the theme: 12 pages, the header and footer, settings, sections key.ts the permanent theme key and the version locales.ts the theme's languages (ar, fr, en) and its default (ar) sections/ one definition per section or family: fields, data (load), presets documents/ the layout each page starts with (JSON) content/ every word a shopper reads, ar/fr/en side by side labels.ts what Studio calls each page, section, field and option palette.ts the starter's colors, written once (black and white) settings.ts store-wide settings and the default color schemes fields.ts fields several sections share (scheme, style, heading) components/ presentation surface/ Sections.astro, Area.astro, PreviewBridge.astro sections/ one component per home-page section ui/ Section.astro (every section's frame), Photo, ProductCard… pages/ thin routes, plus the manifest, preview and revalidate routes layouts/ StoreLayout.astro: <head>, header, footer, basket, WhatsApp lib/ server-side glue; store.ts is the only SDK client scripts/ everything the browser runs; checkout/ holds the order form's parts styles/ tokens.css is the design system; one stylesheet per area middleware.ts the store, the language, redirects and preview: one SDK call actions/ placing an order and looking one up, through the SDK tests/unit/ contract, guards, Studio, checkout, copy and more public/fonts/ the two self-hosted font families and their license .agents/skills/ two skills for coding agents

How a request becomes a page

  1. The middleware (src/middleware.ts) is one call to the SDK's createStorefrontMiddleware, given src/lib/store.ts's createStore as the one client factory, unavailableStoreConfig as the fallback, the preview route and key, and the context builder. The SDK decides which store, language and redirect the request means (a landing page has no /fr/… address), and a page's notFound() keeps the shopper's language. The context (src/lib/context.ts) is built once and put on Astro.locals.store: the client, the store's config, the language and direction, the theme's words with the merchant's edits (resolveThemeContent), the theme settings, and an href() that keeps the shopper's language. A form posted to the page runs its action before the page renders.
  2. The route (src/pages/…) reads what its address names (a product, a collection, a landing page) and answers notFound(Astro) when it is missing. It reads the store's saved document for its surface (client.pageData.get("product")) and renders the page with src/components/surface/Sections.astro.
  3. Sections.astro picks the document that wins: the store's saved one, or the theme's default from src/theme/documents/ (resolveSurfaceDocument). It renders each section through the SDK's InstanceView, with one read cache for the page.
  4. A section's definition (src/theme/sections/*.ts) reads the data it needs in load, through reads.get(key, …), so two sections that need the same product read it once.
  5. A section's component (src/components/…) renders the resolved field values and the loaded data inside src/components/ui/Section.astro, which applies the merchant's color scheme and spacing.
  6. The layout (src/layouts/StoreLayout.astro) adds the <head>, the header and footer areas (src/components/surface/Area.astro), the basket drawer, the WhatsApp widget and the data the browser scripts read.

The browser side

Everything the browser runs is in src/scripts/. Each script binds through bindEach or onEachPage from @wocart/theme/lifecycle, so it works again when Studio's preview swaps a section.

  • src/scripts/order-form.ts binds every order form and wires its parts in src/scripts/checkout/: delivery (wilaya, commune, delivery type), quote (createQuoter, the store's price for the order), coupon, otp (the phone check), submit and tracking (unfinished checkouts and the Meta Pixel).
  • src/scripts/cart.ts keeps the basket in the shopper's browser and has each line re-priced by the store.
  • src/scripts/reviews.ts runs the review form (createReviewSession).

The browser never calls the store's API with the key. It calls the theme's own routes under src/pages/api/, each a one-line file that mounts one of the SDK's same-origin routes (createStorefrontRoutes in src/theme/api-routes.ts). Their paths are the SDK's STOREFRONT_API_PATHS (storefrontApiPath fills a parameter), and a test checks that every path has its file, no file is unlisted, and no other file writes an /api/ path.

Orders are placed with Astro actions (src/actions/index.ts): placeOrder runs the SDK's executePlaceOrder on placeOrderInput, and orderStatus runs lookupOrder. The browser never decides a price, a fee, a discount or whether an order is valid. The store does.

Studio's routes

RouteFileWhat it does
/wocart-theme.jsonsrc/pages/wocart-theme.json.tsThe manifest, built once at build time (themeManifestEndpoint)
/wocart-styles.csssrc/pages/wocart-styles.css.tsThe style classes for the theme's spacing and widths (themeStylesEndpoint)
/wocart-previewsrc/pages/wocart-preview.astroRenders an unsaved draft for Studio, never cached (previewTarget)
/wocart-revalidatesrc/pages/wocart-revalidate.tsDrops every cached page after a publish

The preview and revalidation routes check a token signed by the platform against WOCART_PREVIEW_KEY, and answer 404 without the key. The middleware opens the preview; src/pages/wocart-revalidate.ts is the SDK's revalidateEndpoint, which drops every page cached under PAGES_CACHE_TAG.

Where each change goes

You want to…Change
Restyle the storesrc/styles/tokens.css (fonts, type, corners, shadows), the starter's colors in src/theme/palette.ts, the default color schemes in src/theme/settings.ts, the fonts in public/fonts/
Change a word a shopper readsIts key in src/theme/content/, in ar, fr and en
Change what a page starts withIts document in src/theme/documents/
Let merchants change something newA field in the section's definition, read by its component
Add a sectionA definition in src/theme/sections/, registered in sections/index.ts; a component; its words; its Studio labels in labels.ts
Add a store-wide choiceA field in src/theme/settings.ts
Change what the browser doessrc/scripts/

Make it your own walks through each one.

The rules, and the tests that keep them

The starter's tests run with npm test and need no network. Each rule below is checked by a test you can run on its own with npx vitest run <file> -t "<name>".

RuleTest
Import @wocart/* through published entry points onlyguards.test.ts "published entry points only"
Only src/lib/store.ts creates an SDK clientguards.test.ts "creates SDK clients in one place only"
A store that cannot be read renders the SDK's unavailable storeguards.test.ts "unavailable store"
Every platform page type is a surface, plus the header and footer areascontract.test.ts "every page the platform defines"
The product and landing pages always hold an order form (validateThemeDefinition checks both)contract.test.ts "is a valid catalog", "guarantees an order form"
Every page renders through the SDK, for a shopper and in Studio's previewcontract.test.ts "every page, rendered through the SDK"
Every word is in ar, fr and en, with each language's plural forms, and every key is readcopy.test.ts
No word states a fact about a store: delivery reach, periods, percentages, opening hours, phone callscopy.test.ts "states no fact about a store"
Platform links come from storefrontPath and localizeHrefguards.test.ts "storefrontPath"
Every WhatsApp link comes from storeWhatsApp() in src/lib/whatsapp.tsguards.test.ts "is reached one way"
Colors come from color schemes; every brand value lives in tokens.csscontract.test.ts "colour scheme"; guards.test.ts "keeps every brand value in tokens.css", "scheme marker"
A photo a merchant picks is an image field, never a URLcontract.test.ts "never type an image URL"
Layout uses logical properties, so right to left needs no rule of its ownguards.test.ts "logical properties"
No colour outside tokens.css and palette.ts: not in code, markup, a data URI or an SVGguards.test.ts "holds no colour outside the tokens and the palette"
Every section has a colour scheme and a style, and renders through Section.astroguards.test.ts "gives every section a colour scheme and a style"
Browser scripts bind through the SDK's lifecycleguards.test.ts "binds every behaviour through the SDK's lifecycle"
Browser code imports no Zod, no schema and no server-only moduleguards.test.ts "the theme's browser code"
Every order form posts to the placeOrder action, with its checkout keyguards.test.ts "gives every order form its checkout key"
A missing resource answers notFound(Astro)guards.test.ts "through the SDK's notFound"
Every cached page carries the pages tag, written as PAGES_CACHE_TAGstudio.test.ts "caches every composed route", "reaches every cached page"
Every same-origin API path is the SDK's, mounted by exactly one fileguards.test.ts "mounts a route file at every API path"
A 404 keeps the shopper's language; /fr/lp/… goes to /lp/…middleware.test.ts
A landing page offers no other languagelanguages.test.ts
A merchant's text edit shows only in its language, for the theme's own wordscopy.test.ts "the merchant's text edits"
Preview and revalidation accept only the platform's signed tokensstudio.test.ts
The theme builds for Cloudflare Workers and for Vercelguards.test.ts "builds for Cloudflare Workers and for Vercel"

When you add a rule of your own, add a test for it, and prove the test by breaking the code once and watching it fail.