Basefox v0 · Mockup 1 completion · 26 Aug 2026

Tab shell, hamburger drawer, and the Saved & Alerts features behind them

The three elements deferred from the Section A follow-up, now built. Two of them needed features rather than chrome.

58Swift tests pass
60Pipeline tests pass
5Tabs, own stacks
2New boards

What the mockup was actually asking for

The tab bar reads as chrome, but three of its five tabs had nothing behind them. Saved and Alerts needed a data model, persistence, and read state before a tab could point anywhere, and the drawer needed two destinations that did not exist. So this is mostly feature work with a tab bar on top.

Home — hamburger, See all, five tabs, unread badge
Home — hamburger, See all, five tabs, unread badge
The drawer — jump-to only, nothing configurable
The drawer — jump-to only, nothing configurable
Categories — the full taxonomy plus occasions
Categories — the full taxonomy plus occasions
Saved — empty state that says how to fill it
Saved — empty state that says how to fill it
Alerts — unread dots, status pills, two lenses
Alerts — unread dots, status pills, two lenses
More — settings only, sharing one view with the sheet
More — settings only, sharing one view with the sheet
Exchange events — where “See all” lands
Exchange events — where “See all” lands
The Foxhole, whole, reachable from the drawer
The Foxhole, whole, reachable from the drawer

The three elements

1. The tab bar

2. The hamburger drawer

Built to the split you chose: the drawer navigates, More configures. The drawer holds every category with its brand count, the occasions, the exchange-events board, the Foxhole, Saved, and Alerts. Nothing in it changes a setting, and nothing in More pushes a board, so no row appears in both places.

The header avatar still opens the profile controls as a quick-edit sheet. That sheet and the More tab render the same ProfileFormSections view, so the two entry points cannot drift apart.

3. “See all”

It now pushes an exchange-events board. Home shows live events only, because Home is about right now; this board also carries what has been announced but has not opened, which is the reason for it to exist rather than just scrolling Home's row.

The features underneath

FeatureDecisionWhy
Saved Saves a brand, never a route A route is a door that can close between sweeps. The brand board always shows whichever doors are open today, so a saved brand cannot go stale into a dead link.
Alerts Read state stamps the moment of the visit Stamping the newest change date instead would hide anything published between a sweep and the next visit.
Alerts lens “What I follow” = saved brands + watched categories Reuses the watched-categories setting that already existed rather than inventing a second follow list.
More Also carries dataset freshness and a manual refresh The freshness posture applies to Basefox too. The footer line said it; now there is somewhere to act on it.

Two bugs this surfaced

The unread badge cleared itself before anyone saw it

Every tab stays mounted so its scroll position survives a switch, which means onAppear fires at launch for tabs nobody has looked at. AlertsView.onAppear was stamping the feed read on the first frame, so the badge never appeared. Read state now keys off the selected tab instead. Caught it because the first screenshot showed no badge with four unread changes sitting in the dataset.

A field report was wearing a verification badge

The Foxhole board says these carry no verification badge, then rendered “✓ verified Jul 28” on the card below it. No sweep can confirm an in-store discount, so a field-tier route now reads “reported Jul 28” with the field marker. This changes the route card everywhere, including inside category boards, which is the correct blast radius: the green check should mean one thing.

What changed, by file

AreaFilesChange
ShellAppShell.swift new, LarchApp.swiftFive tabs, hand-drawn bar with badge, shared AppHeader
NavigationNavigation.swiftTab; per-tab paths; push(_:on:), select(_:); two new destinations
DrawerNavDrawer.swift newSlide-over with scrim, swipe-to-close, jump-to rows
TabsCategoriesView, SavedView, AlertsView, MoreView newThe four non-Home roots
BoardsEventsBoardView, FoxholeView newDestinations for “See all” and the drawer
StateModels.swift, ProfileStore.swiftsavedBrands, lastSeenAlertsAt, hand-written Codable so old profiles still load
FixesComponents.swift, ProfileSheet.swift, RouteBoardView.swiftField-report footline; sections extracted for reuse; save affordance
TestsShellTests.swift new13 tests: per-tab stacks, saved brands, read state, legacy profile decoding
Specbasefox-spec-v03.mdNew §Shell paragraph; screens 6–9; Profile updated

Review

What holds up

Where it is thin

Confidence

85/ 100 — that this is the right structure for the shell

High on the mechanics: per-tab stacks, the drawer/More split you picked, brand-level saving, and visit-time read stamping all have a clear right answer and are tested. Lower on two things only usage settles — whether five tabs plus a drawer is one surface too many for an app this size, and whether “What I follow” is the lens people actually want versus filtering by change kind.

Back to the cards

The queue from last time is unchanged: the 13 asset files for tiers 1 and 2, offer expiry for the Ending Soon card, and the “10% OFF” versus “−10%” discount vernacular. Two new candidates joined it — the route board and occasion board still hand-roll their rows instead of composing cards.