Skip to content

Put the document's title and views on its sheet, and zoom children by default - #507

Merged
MaggieAppleton merged 7 commits into
mainfrom
design-doc-two-bar-header
Oct 12, 2026
Merged

MaggieAppleton merged 7 commits into
mainfrom
design-doc-two-bar-header

Conversation

@MaggieAppleton

@MaggieAppleton MaggieAppleton commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Direction D from the doc-header jig: the document's identity moves onto its own sheet, so expanding never hides the title, and children zoom in place by default.

What changes

Two bars inside the sheet (split workspace). The ground-level room header over Chat is gone.

  • Title bar (40px): ghost expand / "Chat" toggle | project / Title ▾ (rename and the document actions menu, moved from the ground header) … status | people. Full-height dividers, 8px insets and 8px between groups.
  • Views bar (40px): Document · Decisions · Build, left-aligned at the 8px inset, sitting on the paper with no rule beneath. Tabs keep their tighter 6px rhythm.
  • Expand / contract is width only: Chat collapses, the title stays put and un-truncates.
  • Chat has no top bar now. It starts at the sheet's top margin, or below the floating Show sidebar trigger when that is showing.
  • Narrow sheets (container queries on the title bar): the project name stays, the title truncates first, then status labels hide ([data-status-label], for the sync slot in Add Build plan and live sync status to the document toolbar #453 to opt into), then, below 26rem, presence drops to one face and "+N". A 1280px laptop with the sidebar and Chat keeps its faces.

Zoom is the default child motion; Lift stays behind the existing toggle (localStorage["chopin:dev:child-motion"] = "lift" in dev or with VITE_CHILD_MOTION_TOGGLE=on).

  • Zoom: the title bar becomes ← project / Root › Child ▾. The child's title, rename and actions belong to the child room and are portalled into the root's path. The views bar folds shut, because a child has no views. If the root is on Decisions or Build, the views stay.
  • Lift: the child sheet rises over the root's views bar. The root's title bar stays visible above it as the stack, and a click anywhere on it returns. The child gets its own title bar (icon | title ▾ … ×). Children show no sync or faces.
  • Compact and phones keep the stack bar as before.

Folded in from #497 (closed; its commits are the first two here)

  • The panel bar is a fixed 40px (--panel-header-height) with no vertical padding, so dividers run its full height.
  • 8px inline insets (--panel-toolbar-padding-inline), with compact 24px controls centred in the bar.
  • The restore toggle is a ghost button: no resting fill or outline.
  • On coarse pointers the bar grows to --control-target-coarse + edge, so 44pt hit areas aren't clipped.
#497 before #497 after
Bar before 497 Bar after 497

Screenshots (1440×900 unless noted)

Before After
Root before Root after
Expanded before Expanded after
Long title before Long title after
Child zoom before Child zoom after
Child lift before Child lift after, flag on
Phone before Phone after
Phone child before Phone child after
After: other states
Long title, expanded Long expanded
1100px, sidebar open Narrow
1100px, sidebar open, long title: the project stays and the title truncates Narrow long
Zoom child, expanded Zoom expanded
flowchart TD
  R[Root RoomWorkspace] -->|title prop| T[Title bar: toggle / DocumentTitle / status / people]
  R -->|controls| V[Views bar]
  R -->|ChildTitleContext: motion + slot| C[Child RoomWorkspace]
  C -->|Zoom: createPortal DocumentTitle| S[Slot at end of root path]
  C -->|Lift: own title bar| L[Child sheet]
  W[Workspace] -->|Zoom on Document| F[Views bar data-folded]
Loading

Implementation notes

  • DocumentTitle is extracted from Header. Header remains the compact ground header. The rename focus hand-off still searches .workspace-root and works from the sheet.
  • The title bar doesn't scroll horizontally; only the views bar keeps overflow-x: auto. The inline rename field fits inside the title slot.
  • The status divider only appears when a visible notice is present; status keeps its live regions mounted.
  • E2E selectors move from getByRole("banner") to the Document: … group in split tests. Lift tests pin Lift. The compact Zoom view-switch test runs at 600px. Paper-layout asserts both bars are 40px.

Follow-ups (not in this PR)

  • Phone D: title always shown with a project · sync subtitle, two faces and ⋯; child: back arrow and "in Root". Phones are unchanged here and keep the stack bar.
  • Add Build plan and live sync status to the document toolbar #453 (Build plan / sync slot) should render into status and mark its labels with data-status-label so they fold at narrow widths.
  • The loading placeholders (empty-workspace, workspace-notice) still reserve a ground header row in split.

Design-contract review

Renewed in a dedicated commit, as Maggie approved for this pass. The reviewed dynamic owners keep the same data flow:

  • apps/web/src/workspace.tsx: the SurfaceToggle feedback class and the Chat pane presence class.
  • apps/web/src/navigation-shell.tsx: the sidebar frame motion class. Only the Undo focus fallback selector changed, from header button[…] to the Document group.

Review fixes

  • Zoom child rename: pressing Enter used to drop focus to <body>, because the rename rekeys the child's card. The remounted title now takes the caret back. Covered by e2e.
  • Root on Decisions or Build with a Zoom child open: the child's title gives way to a crumb back to Document (aria-current="page"), as the stack bar did. Rename and actions never act on a document the pane isn't showing. Covered by e2e.
  • Zoom accessibility: the arrow is now a pointer target only (aria-hidden, tabindex=-1). The root crumb is the single Back stop, named "Back to ". The child's Document: group sits beside the root's, not inside it.
  • Lift: the covered root's title now dims with the rest of its bar.

Verification

  • bun run types ✅, bun test apps/web ✅ (the only local failures are from untracked design-jig files), dprint/oxlint/tokens/design-record/impeccable ✅.
  • Checked in headless Chromium on a fake-GitHub server: Zoom child rename, the actions menu and the root crumb from the title bar; Lift return; expand/contract; and the narrow container rules.
  • Local E2E skipped because port 8797 is held by another session. Relying on PR CI.

🤖 Generated with Claude Code

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Review: Changes needed

Direction D lands well and matches the jig: title, actions, status and faces stay on the sheet in every state; expanding is width-only; the long title truncates before the project; Zoom's ← score / Root › Child ▾ reads cleanly; Lift (flag) gives the child the same title-bar anatomy; compact (640px) and phone (390px) are unchanged. Checked in headless Chromium on a fake-GitHub server at 1440, 1100 ± sidebar, 800, 640 and 390, Zoom and Lift (built with VITE_CHILD_MOTION_TOGGLE=on), two people present.

Blocking

  1. The PR conflicts with main, so CI has not run on the head commit. 6cae209e (Planner attachment line) adds planner={…} to <Chat> in room-workspace.tsx, which this PR re-indents. It's a single, mechanical hunk. The only CI run is on c4a51bce, before the e2e update in adb799bf: lint/tests failed only on the review hashes, but e2e failed broadly there. Rebase, push, and get a green e2e before merge. (I couldn't run e2e locally because 8788/8789/8792/8797 are held by other sessions.)
  2. Renaming a Zoom child drops focus to <body>. In split Zoom, Rename Source review, type, Enter: document.activeElement is BODY at 50, 200 and 800ms. The same steps on the root keep focus on Rename …. The child's .child-document is replaced after the save, probably because the slug changes and the child route reloads. DocumentTitle's refocus effect (room-workspace.tsx:208-213) then runs on an unmounted node. Escape, F2 and the actions menu all return focus correctly. Lift doesn't show it unless the title changes. Fix by keeping the child room mounted across a slug change, or by restoring focus to the new child's Rename button after the remount. Add an e2e for Enter-commit focus on a Zoom child.

Should fix

  1. Root on Decisions/Build with a Zoom child open: the title bar reads Offline sync › Source review ▾ while the pane shows the root's Decisions. Rename/Actions in that bar target the child, so the bar names a document you aren't looking at. On main, the stack bar's child crumb became a button back to Document. Off Document, either make the child crumb return to Document (aria-current only on Document), or drop the child segment and leave the root current.
  2. The covered Lift title bar doesn't dim. navigation.css sets [data-child-covered] [data-document-title-bar] { color: secondary }, but .document-title-bar .title-control { color: var(--color-text-primary) } (theme.css) overrides it for the title. The root's title stays full-ink above the lifted child. Scope the primary colour with :not([data-child-covered] *) or similar.
  3. Duplicate tab stop in Zoom. The order is Expand, Back to Offline sync, Offline sync (crumb), Rename Source review, Actions for…, then the editor. The arrow and the crumb do the same thing, and the crumb's accessible name ("Offline sync") doesn't say it navigates. Consider tabIndex={-1} on the arrow (keep it for pointer), or label the crumb Back to Offline sync. The child's Document: group is also nested inside the root's Document: group (the portal slot sits inside it). That's fine for SR, but put the slot after the group if you want flatter announcements.

Verified OK

  • Design-contract hashes (workspace.tsx 196/206/728, navigation-shell.tsx 1008): bun run ci fails only on these. feedback (SurfaceToggle), chatPresence (inputs unchanged: presentation.chatVisible, the same durations, immediately), sidebarMotion and sidebarPresence are byte-identical in data flow. Only surrounding code changed, so they're safe to renew.
  • Undo focus fallback: archive from the title bar at 1100 with no sidebar, then Undo. Focus lands on Actions for Offline sync through the new [aria-label^='Document:'] selector.
  • Fast sibling switching (c1→c2 via the sidebar): for about 150ms the exiting root frame still holds the old child's portal, but frame-by-frame screenshots show no doubled or stale title. It settles on the new child.
  • Sidebar trigger clearance at 1100: Chat starts below the floating trigger. The sidebar overlay at 1100 is fine.
  • Zoom crumb and Back return to the root. Lift: clicking the covered root bar returns, × returns, and the child's rename/actions work.
  • Remaining getByRole("banner") e2e uses are all phone/compact.

Nits: the loading placeholders still reserve a ground-header row in split (acknowledged follow-up). [data-document-title-bar] > :not(.workspace-tab-row-return) { position: relative } now applies outside Lift too, which is harmless.

MaggieAppleton and others added 7 commits October 12, 2026 11:10
The bar drops its vertical padding and centres 24px controls in 40px, so
the divider after the surface toggle spans the bar's full height. The
restore toggle loses its resting fill and outline and uses the ghost tier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bar clips overflow, which cut the coarse-pointer hit-area expansion
to the 40px bar. Touch devices now get a bar one coarse target tall plus
its hairline; desktop stays at 40px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m the default child motion

The ground-level room header over Chat goes away in the split workspace.
The sheet carries two 40px bars: the Chat toggle, project / title with
rename and actions, status and the people here; then Document, Decisions
and Build on the paper. Expanding only changes the sheet's width, so the
title never disappears. Container queries fold status labels and then all
but one face as the bar narrows.

Children open with Zoom by default: the child's title, rename and actions
join the end of the root's path in the title bar, behind a back arrow and
a root crumb, and the views fold away. Lift stays behind the development
toggle; its child sheet rises over the views under the root's title bar
and carries a title bar of its own. Compact and phones keep the stack bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Specs that found the title, status or expand control through the old
banner or the views bar now use the Document group and title bar. Zoom
covers the root's Document view rather than the whole swap stack, Undo
sits one stop past the sheet's expand control, and the compact header
keeps its icon inside the title group. Presence folds to one face only
below 26rem, so a 1280px laptop with the sidebar and Chat keeps faces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t path, a11y and Lift dimming

- A Zoom child's rename rekeys its card, so the remounted title takes the
  caret back when the save started from its field.
- With the root on Decisions or Build, the child's title gives way to a
  crumb that brings Document back, as the stack bar did, so rename and
  actions never act on a document that isn't on screen.
- The back arrow is a pointer target only; the root crumb is the single
  Back stop, named "Back to <root>". The child's Document group sits
  beside the root's instead of inside it.
- A covered Lift root's title dims with the rest of its bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reviewed dynamic owners in workspace.tsx (SurfaceToggle feedback,
Chat pane presence) and navigation-shell.tsx (sidebar frame motion) keep
the same data flow; the files changed around them for the sheet's title
bar and the Undo focus fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecisions

Zoom hides the child off Document, so a blank root with open decisions
that routed itself to Decisions on arrival hid the child its reader had
just opened. The opening route now waits while a child is open; choosing
a view still applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant