Created and presented by Nino Chavez.

Session S17 · 9 chaptersScroll to read · use ← → to step between chaptersSkip to the record

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
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.

What happened

The Layout Was Inferred, Not Designed

Aisles is a headless storefront that decides its own layout per shopper — 31 weighted rules feed a Bayesian inference engine that picks one of four personas, then an AI model generates the category page from a four-component vocabulary a Zod schema won't let it leave. Same URL, same catalog, four schema-valid layouts. On 2026-08-12 it was pointed at a real BigCommerce store for the first time — a brand-config entry, two environment variables, a harvested palette with file-line citations, and one honest bug: a client-side lookup that silently rendered furniture navigation over a pet-supply catalog.

Collection
Ways of Working
Record
S17
Format
Session
01 / Reader

Who this is for

Anyone evaluating whether AI-generated UI can be made safe to ship, and anyone deciding how much of a personalization engine should be inferred versus hand-coded per segment.

02 / Evidence

What the session shows

The full pipeline read from source — 31 rules, a gatherer-biased Bayesian prior, and a softmax over four named personas (gatherer, hunter, researcher, gifter); the Zod schema that makes an AI-written layout provably valid instead of usually reasonable; the per-persona prompt language that actually reaches the model; the same-day integration against a live BigCommerce sandbox that took one config file because the catalog seam was already generic; a palette harvested from the storefront's own CSS with file and line citations; and the client-side brand-resolution bug the integration surfaced, with its fix.

03 / Practice

What to reuse

Build one catalog function that reads the active brand's config instead of one function per brand. Pass a typed schema to the model as a generation constraint, not a suggestion in the prompt. Harvest brand colors from the real source with a citation, never invent them. When a config is read in both server and client code, make sure it resolves identically in both — the client's silent fallback is the bug that ships.