Basefox v0 · Section A follow-up · 25 Aug 2026

Card system, rebuilt Home, and product-aware search

Acting on the two notes from the Section A test pass: the Home redesign (mockup 1), the card system (mockup 2), and the search-intent problem.

45Swift tests pass
60Pipeline tests pass
6×3Card types × densities
0 / 13Brand & campaign assets

What shipped

1. The card system is real code, not a picture

All six types and three densities from mockup 2 exist as one component. The five-part anatomy (eyebrow, headline, descriptor, visual, status) is the same shape at every density, and boards now compose cards instead of hand-rolling rows.

TypeLeads withWhat triggers it in the dataLive in v0?
A · Brand offerthe valuea usable best routeyes
B · Exchange eventthe eventa live ExchangeEventyes
C · Category eventthe categorya live event seen from a category boardyes
D · New benefitwhat’s newa newRoute change inside 14 daysyes
E · Ending soonurgencyendsOn or event ends inside 3 daysevents only
F · Fallbackwhat we do knowno usable route, or a verified negativeyes

Type E fires from exchange events today. It cannot fire from a brand offer yet, because nothing extracts an offer expiry — see the gap below.

2. The visual asset hierarchy, with the fallback actually built

Four tiers, best first. The app asks for the best it could have and takes the best that exists, so a missing file costs richness and never correctness.

TierSourceWhere it comes fromShips today
1 · Campaignfrom sourceregistry/assets/events/no files
2 · Brandcurated by Basefoxregistry/assets/brands/no files
3 · CategoryBasefox librarySF Symbol per categoryyes
4 · GenericBasefox abstractdrawn in-appyes

larchpipe assets sync copies whatever is in the registry into the app’s asset catalog, names what is still missing, and flags files nothing references. It reports 0 of 13 today and is safe to run against an empty directory.

3. Merchant signature accents

Sampled from your card-system mockup and assigned per brand in brands.yaml. Every accent comes from this fixed set, so no card can introduce a colour the rest of the app does not already speak.

#2A3C22
#7D8668
#CD5019
#3D5169
#D6BD9A
#AAA7A3
#B42A10
#3E4C35

4. Home, rebuilt on the cards

Rebuilt Home screen
Home — hero event cards, glyph tiles, compact change feed
Search results for hiking boots
“hiking boots” → two category boards, each naming the term
Footwear category board with filter bar
Category board — event card, filter bar, running count

5. Search now answers a product query

Your instinct was right, and the old behaviour proved it: typing “hiking boots” matched nothing and dropped to the empty state. The index still holds brands and categories only. A new productTerms lexicon bridges the two.

6. Sharp filters on the category board

The old board had four mutually-exclusive chips that mixed sorting with filtering, so choosing “Online only” silently discarded the discount ranking. They are now two separate controls, and the filters combine.

The gap you should know about

Nothing produces an image, and the card system is largely image-driven

Before this work, grep for image/logo/asset/photo across /pipeline, /registry, and the spec returned zero hits. The “Visual Asset Hierarchy” box in your mockup described a system with no supply of any of its four tiers.

I built tiers 3 and 4 so the app is complete and intentional-looking today, and wired tiers 1 and 2 end to end. What remains is the licensed material itself: 10 brand lockups and 3 campaign heroes. Specs and filenames are in registry/assets/README.md; drop the files in, run larchpipe assets sync, rebuild, and every card upgrades on its own.

Card type E cannot fire from a brand offer yet

Route.endsOn now exists in all four halves of the contract, but no source extracts it — the Tier-2 extractor never asked for an expiry date. I left it null across the bundled snapshot rather than inventing dates in shipped sample data. Teaching the extractor to pull expiry is the next step if “Ends tomorrow” matters for launch.

Three things in mockup 1 I did not build

What changed, by file

AreaFilesChange
Contractbrands.yaml, categories.yaml, events.yaml, models.py, registry.py, dataset.schema.json, Models.swift, dataset.jsonSix new fields carried through all four mirrors: accent, logo, glyph, productTerms, hero, endsOn
Card systemViews/Cards/ (new: CardModel, CardView, CardBuilders, AssetResolver)The six types, three densities, the anatomy, and the four-tier resolver
HomeHomeView.swiftHero event cards, circular glyph tiles, compact change feed, one card tap handler
SearchDomain.swiftCategoryMatch with the matched term; scored name/synonym/product-term ranking
FiltersBoardFilter.swift, FilterBar.swift, CategoryBoardView.swift (new + rewired)Sort split from filters; route-level filtering with best-route re-pick
Assetslarchpipe/assets.py, cli.py, registry/assets/larchpipe assets sync plus the file spec
TestsCardSystemTests.swift, test_assets.py27 new Swift tests, 11 new pipeline tests
Specbasefox-spec-v03.mdNew §Card system; §Screens, §Design system, §Data model, and the search order updated

One tidy-up worth naming: FlowChips was left with no callers after the category chips became tiles, so it is gone. FlowLayout survived and moved to Components.swift with the other shared primitives.

Review

What this buys

Where it is thin

Confidence

82/ 100 — that this is the right shape for the card system and search

High on the parts with a clear right answer: the four-tier resolver, splitting sort from filters, filtering at the route level, and keeping the contract mirrored. Lower on two judgement calls that only a real asset will settle — the single stacked hero variant, and how a licensed hero image behaves in a short band. The lexicon’s coverage is a guess until someone uses it.

Decisions waiting on you