WAYS OF WORKING · DEMO 17 NINO CHAVEZ

The layout was
inferred, not
designed.

Aisles is a headless storefront that decides its own layout, per visitor, at request time. Same URL, same catalog — the AI infers who's shopping through 31 weighted rules and generates one of four schema-valid layouts to fit. On 2026-08-12 it was pointed at a real BigCommerce catalog for the first time.

Gatherereditorial header, 2-col landscape grid, no quick-add
Huntercategory header, dense 4-col grid, quick-add on
Researcherspecs on every card, no hero, no editorial copy
Gifterhero product, 3-col grid, safe price tier

or scroll to advance · to go back

02 WHAT AISLES IS

One engine, four shoppers it has never met

Every category page runs the same pipeline. Request-time signals (search query, referrer, UTM tag, device, time of day) and behavioral signals (category views, dwell time, cart adds) feed 31 weighted rules. Each rule contributes a log-likelihood adjustment toward one or more of four personas; the engine sums them against a gatherer-biased prior and takes a softmax to get a probability distribution. The primary persona then drives the AI layout call.

Persona
Cold-start prior
Intent
Leads with
gatherer
0.375
browsing to discover
editorial header + hero
hunter
0.250
buying with intent
category header, dense grid
researcher
0.250
comparing the specs
category header, specs shown
gifter
0.125
shopping for someone else
editorial header + hero

The prior leans gatherer on purpose — the exploratory layout is the safest default for a visitor the engine hasn't seen signals from yet. It self-corrects as request and behavioral signals arrive.

03 THE SCHEMA IS THE GUARDRAIL

The AI can't invent a component that isn't in the vocabulary

A model that free-writes a category page can produce anything — including something broken. Aisles constrains it instead: a Zod schema enumerates exactly four component types, and that schema is passed to the model as a structured-output contract, not a suggestion in the prompt.

SectionSchema = discriminatedUnion('component', [
  editorial-header,  // eyebrow + headline + body copy
  hero-product,      // one featured product, specs optional
  product-grid,      // 2/3/4 columns, landscape or square
  category-header,   // title, sort, filter
])
LayoutSchema.sections: 1 to 8, ordered

The model chooses which components appear, how products are ordered, and what the copy says — it cannot emit a fifth component or a malformed prop. If generation fails outright, the documented fallback is Claude Haiku → Claude Sonnet → a static Svelte layout, so a valid page always exists.

04 FOUR LAYOUTS, CONCRETELY

The same prompt reads four different rulebooks

Each persona ships its own layout principles into the prompt. The model isn't told "personalize this" — it's told exactly which components to lead with, whether quick-add appears, and how to sort.

Gatherer

"No quick-add buttons — this shopper wants to browse, not rush… copy should be warm, editorial, magazine-like."

Hunter

"Show quick-add buttons on every card… no editorial copy — no hero product, no lifestyle stories."

Researcher

"Show full specs inline on every card… no quick-add buttons — they're not ready to buy yet, they're evaluating."

Gifter

"Group or call out price tiers… copy should be warm, reassuring, and focused on the recipient's experience."

Every quote above is taken directly from the prompt-construction source — the persona definitions the model actually reads before it writes a single section.

05 THE INTEGRATION DAY

Pointing it at a real store was configuration, not code

Aisles already ran on three fictional brands — Haven, Volt, Ember — sharing no product data, only the inference engine and prompt pipeline. Adding Kibble & Co., the pet-supply storefront from the same BigCommerce sandbox as two earlier sessions in this series, meant writing a brand config entry and nothing else.

The change, committed 2026-08-12

One brand-config entry: 8 categories mapped 1:1 to the live sandbox's category tree, a harvested theme, and store copy. No route, no query, no component touched.

✓ live: aisles.bcsubs.app — fetched and confirmed this session, rendering the same 8 categories as the config. It joins the same live sandbox store as the Kibble & Co. fleet (demo 14) and Ask BC (demo 16).

It worked in one pass because the catalog seam was already generic: loadCategoryProducts() reads its category map from getBrand().categories, and calls BigCommerce's Storefront GraphQL through two functions in a shared client. Add a brand, and the same function resolves a different catalog. Selecting the deployed brand is two environment variables — BRAND_ID for the server, VITE_BRAND_ID for anything the client bundle needs at build time.

06 THE PALETTE HARVEST

The colors came from the storefront, not a color picker

Kibble & Co.'s theme wasn't chosen — it was read off the live storefront's own stylesheet, the "Shelf-Native" theme, where mint is reserved for the Auto-Refill subscription signal and never used decoratively elsewhere.

#3b5bd0 — Cornflowerapp.css:30, --color-accent, "Native Pet cornflower"
#2f49b0 — Cornflower textapp.css:32, --color-accent-text
#37bfa2 — Seafoamapp.css:35, --color-autorefill
#1e2150 — Inkapp.css:26, --color-primary

Every hex value in the brand config traces to a specific line in the storefront's own CSS, not a hand-picked palette. That discipline — harvest the brand, don't invent it — is what keeps a demo built by an agent from drifting into generic-AI color choices.

07 WHAT WASN'T RIGHT THE FIRST TIME

A furniture nav sat on top of a pet-food page

The navigation component called getBrand() directly, in the client. That function reads an environment variable that is only ever set on the server — so on every page, the client silently fell back to the default brand and rendered its category list instead of the active one.

The bug, in production

Kibble & Co.'s catalog rendered correctly below the fold — but the nav bar above it showed Haven's furniture categories: Living Room, Office, Bedroom.

✗ cause: BRAND_ID never reaches the browser bundle, so a client-side getBrand() call always resolves to the default brand, not the one actually deployed.
"Categories come from the server-loaded brand — getBrand() is server-only (BRAND_ID is not exposed to the client bundle, so a client call silently falls back to the default brand)." inline comment added to the fix, src/lib/components/Nav.svelte, commit 09e074b

The fix: flow categories through the page's server load function instead of re-deriving them in the browser. The general lesson travels past this one bug — an env-selected config read in two runtimes is two sources of truth, and the client one fails silently to a default rather than erroring loudly.

08 WHAT TRANSFERS

Four habits that made a real store a same-day integration

One catalog seam

A single function reads the active brand's category map and calls the storefront API. New brand, same function, different data.

Constrain, don't hope

A typed schema passed as a generation contract makes an AI-written layout provably valid, not just usually reasonable.

Harvest the brand

Pull real colors from real CSS with a file and a line number. A palette with no citation is a guess wearing a hex code.

One runtime per config

An env var read in both server and client code needs to resolve identically in both, or the client's silent fallback becomes the bug nobody notices until it ships.

The pattern is reusable. The store is the fixture.

09 CLOSE

Four stores. One page that never stops guessing

Nothing about the URL changes, and nothing about the catalog changes. What changes is which of 31 rules fired, which persona won the softmax, and which four-component layout the model was allowed to write. Adding a real store to that engine took one config file and two environment variables — and still found a real bug the moment it ran in production.

same catalog, four schema-valid layouts harvest the brand, don't invent it one runtime per config, or it lies silently

Correction, 2026-08-13. Eleven hours after this deck published, commit c50e38c moved Kibble & Co. to a preserve contract. That storefront's layout API now answers 503; the other three brands still take the model path. The bounded live-decision path that replaced it, and how the removal happened, are demo 18.

Colophon. Every figure on these slides — the 31-rule count, the four persona priors, the component vocabulary and section limits, the per-persona prompt quotes, the model-fallback chain, the catalog-seam code path, the harvested hex values with their source lines, and the Nav.svelte bug and fix — was read directly from the Aisles repository and its commit history in this session, sources cited in an HTML comment at the top of this file. Kibble & Co. is a fictional demo merchant. An independent demonstration by Nino Chavez — not a BigCommerce product. Demo 17 in the ways-of-working series — the storefront it now renders is demo 14, Kibble & Co.; the commerce agent running against the same sandbox is demo 16, the agent that asks twice.