Skip to content

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.ts is topNavModel and leftNavModel in one file, plus what neither had: navChildren(pathname, ctx) (a section's static children, or the campaign in scope's own pages), a campaignEdit area, and THIRD_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 — useGlobalCommands is 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), and MapSecondLevelNav (895 lines, unmounted since v0.42.0) with StartMenu and MapBreadcrumb.
  • SectionNav (L.4) — the third level, named: a 200px column of NavLinks 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) — CampaignShareButton in the campaign page's PageHeader: 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. SheetShareButton likewise shows the noun to a viewer instead of nothing.
  • GlobalTopNav deleted (T.3), with GlobalTopNavRight, its constants, the dead PlayTopBar and ShareModal. AppShell reserves no row; the rail is full height; the map's overlays start at PLAY_BAR_HEIGHT; the sheet's frame subtracts the footer alone. Z_INDEX.TOP_BAR is released.

The play bar

  • Three columns (P.1) — PlayBar: left, Me / System / Library / Map / Campaign; centre, the ToolPalette embedded 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 standalone MapSwitcher folded 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 what UI.tsx would 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.ts holds the thirteen deletions, and the z-index scale ratchets down (33 bands).

Also in this release

  • Found on the way: SystemContext imported a height constant through the dead PlayTopBar.tsx, which imported the deleted MapSecondLevelNav; 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: MobileTopNav keeps 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/parties route 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.