v0.97.0 — Navigation: left rail and play bar
Shipped 2026-09-20 — merged to main as aa1fc7ff, tagged v0.97.0.
Built on version/0.97.0, fifteen commits. Followed by three hotfixes
from first use: v0.97.1, v0.97.2, v0.97.3. The seventh of the
releases the near-term roadmap sequences from the
August 2026 heuristic evaluation; the roadmap page is
navigation-left-rail-and-play-bar,
part one of the navigation refactor.
The design reference is Style Guide 3.0.0; the
help page is Core Concepts → Getting around.
The third navigation pass, and it reverses the central decision of the
second: the global top bar (v0.42.0) is gone, and a left rail wide enough
for labels is back, with the second level embedded under the active item
and collapsed to icons on /play. Search, help, connectivity, notifications
and profile moved to the rail's foot; Share moved to the page header's CTA
slot; /play got a three-column play bar with the palette in the centre and
the map's name in a nub. Decisions N-D1 (collapse to icons on /play)
and N-D2 (the evaluation's Proposal One for the play bar) were taken
at their recommendations.
The rail
- One route model (L.3) —
navModel.tsistopNavModelandleftNavModelin one file, plus what neither had:navChildren(pathname, ctx)(a section's static children, or the campaign in scope's own pages), acampaignEditarea, andTHIRD_LEVEL_AREAS. LeftRail(L.1) replaces v0.48.0's double-navbar. One column, 220px wide with icon and label by default, 60px collapsed — forced on/play, the user's persisted choice elsewhere, a toggle in the foot. The foot carries Search (⌘/Ctrl+P, the global palette —useGlobalCommandsis now its own hook), Help, connectivity (the dot, the connected players, Invite Players), Notifications, the impersonation indicator and Profile, each opening to the right.- The children embed (L.2): under the active item, indented — Characters
→ Characters / Parties / Factions; Admin → its pages; a campaign in scope →
Overview / Maps / Characters / Edit, headed by its name. The two
second-level strips retired:
CharacterSecondLevelNav(the mobile Characters strip reads the same children until v0.98.0), andMapSecondLevelNav(895 lines, unmounted since v0.42.0) withStartMenuandMapBreadcrumb. SectionNav(L.4) — the third level, named: a 200px column ofNavLinks with a rule on its right, in-page state. Edit Campaign (15 sections) and the system page (11) render it from a declared list instead of two hand-rolled copies; a guard fails a third.
Removing the top bar
- Breadcrumbs (T.1) render in the page header only. The bar's sections
became
NavSections.tsx, whose one consumer is the mobile bar; a guard holds one breadcrumb per route. - Share in the CTA slot (T.2) —
CampaignShareButtonin the campaign page'sPageHeader: the verb (IconShare2, "Share", the campaign Share modal) for whoever may share, the noun (IconShare, "Who has access", the read-only access panel) for everyone else who can see the page.SheetShareButtonlikewise shows the noun to a viewer instead of nothing. GlobalTopNavdeleted (T.3), withGlobalTopNavRight, its constants, the deadPlayTopBarandShareModal.AppShellreserves no row; the rail is full height; the map's overlays start atPLAY_BAR_HEIGHT; the sheet's frame subtracts the footer alone.Z_INDEX.TOP_BARis released.
The play bar
- Three columns (P.1) —
PlayBar: left, Me / System / Library / Map / Campaign; centre, theToolPaletteembedded and planned against the width it really has; right, Chat / Initiative / Combat Log / Transcript. Each side column is the width of the sidebar it opens. - The nub (P.2) —
MapNub: the dot, the map's name and the connected count, hanging centred from the bar; click for the only map switcher, the connected-players panel and Invite Players; a thin line while a token or drawing is dragged. The standaloneMapSwitcherfolded into it. - The rail on play (P.3) is icon-only and the palette's overflow planner
subtracts it; sidebars open and close from the bar (P.4) through
layoutStore, with a second click on the chat view already showing collapsing the panel rather than closing whatUI.tsxwould reopen.
Finishing
- Style Guide 3.0.0 (F.1) — a major bump; 2.2.0 archived verbatim.
- Guards (F.2) — nine deleted with their subjects, six renamed, about
thirty-five re-anchored;
retiredChrome.guard.test.tsholds the thirteen deletions, and the z-index scale ratchets down (33 bands).
Also in this release
- Found on the way:
SystemContextimported a height constant through the deadPlayTopBar.tsx, which imported the deletedMapSecondLevelNav; Vite pre-transformed the chain and the app was blank between L.2 and T.2. It reads the constants module now. - Mobile is untouched by design:
MobileTopNavkeeps its breadcrumb sections and the floating palette until the mobile play shell (v0.98.0). - The spec's "Parties" child under a campaign is not listed: no
/campaign/:id/partiesroute exists.
Decisions
N-D1 and N-D2 at their recommendations. Two more made here: the chat panel
collapses rather than closes from the bar, because the desktop map has
always reopened a closed chat; and the connectivity popover lives in the
rail's foot on every route as well as in the nub on /play, so "invite
someone" is never behind a page change.