Repository navigation
Give phones a bottom tab bar and a collapsing top bar - #475
Conversation
|
Review: Changes needed The foundation is solid. The phone class is right: 1440, 1280, a 1024 touch tablet and a 1180×820 touch iPad all keep their split layouts, while iPhone 15 Pro, SE and landscape get the phone layout. Rotating the device in place keeps the phone layout and the active tab. Scroll position survives tab switches (Document at 700px and Decisions at 150px both came back intact). Tapping the current tab returns to the top. The tab bar has the right semantics: Two must-fix issues, both in the collapsing top bar. Must fix
Should fix (this PR or the next slice)
Nice to have
🤖 Generated with Claude Code |
|
Review: Looks good I re-checked on
CI: e2e and container pass. The validation job fails only on the reviewed design-contract hashes listed in the PR body. Nice to have, none blocking:
🤖 Generated with Claude Code |
3fc7b54 to
6a8c250
Compare
Phone class (primary coarse pointer, width <= 600 or height <= 500) now keeps the compact layout in landscape and replaces the top segmented tabs with one floating bottom bar: Chat, Document, Decisions, Build. Panes run full-bleed under a translucent 44px top bar that holds the sidebar trigger, title and document actions, and tucks away while reading down. Destinations slide by tab order, crossfade under reduced motion, and the bottom bar steps aside for the software keyboard. Desktop and tablets are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hidden top bar now leaves the screen entirely, with a glass strip kept under the status bar, and comes back when focus moves into it. Its small title fades in only once the document's heading has scrolled under it. Landscape phones get a shorter top bar and a one-line tab bar that keep 44px targets. Child documents keep the sidebar trigger in the parent's bar, tapping Chat again returns to the newest message, rubber-banding no longer toggles the bar, and tabs use the shared press scale. Phones keep the 24px document gutter so margin comment markers keep their lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measure the top bar title at most once a frame, mark the current tab with aria-current alone, keep the landscape Decisions count beside its label, and show the status-bar glass strip only while the top bar is tucked away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t.ts and workspace.tsx - apps/web/src/navigation-shell.tsx: wraps the shell in the sidebar trigger claim context; the sidebar motion classes and width style are unchanged, only re-indented. - apps/web/src/viewport.ts: adds a boolean data-keyboard-open attribute; the --app-* property writes are unchanged. - apps/web/src/workspace.tsx: the phone branch chooses between literal frame and motion classes and the tab-slide motion contract; the paper layout's expand control and other reviewed sites are main's, unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- chat-phone: the phone top bar carries no presence, so the picker's own wait for cy replaces the presence-group check. - comment-sheet: the selection bar docks just above the tab bar (it already reads --phone-bottom-chrome), so measure against the tab bar, and seat the low passage where the bar now docks. - decisions-phone: phone tabs mark the current view with aria-current. - sidecar: poll the reply frame until it has grown around Send, instead of measuring once mid-growth (flaky in CI). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is open Nested documents now open as cards on the root sheet, with the stack bar carrying the way back, so the phone top bar no longer swaps in the child's title and a Return button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nested documents put a stack bar with the way back at the top of the document panel; on phones that is under the translucent top bar, and the child card covered it. Place it below the top bar, above the card, and let it follow the bar when it tucks away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tucked stack bar now leaves the screen and hides, so nothing shows through the glass top bar, and the child card starts at the top edge like other phone panes, with its document inset below both bars, so no second band shows between them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1bae625 to
a96ebe6
Compare
Why
On a phone the workspace was desktop-shaped: top segmented tabs out of the thumb zone with Build as a 20px link (M-15, M-16), ~113px of chrome plus card margins that never collapse (M-06), a separately fixed 28px sidebar trigger that ignored the top safe area (M-04, M-07), a directionless crossfade between views (M-17), and landscape phones (734×343) fell into the desktop split with 24px tabs and a 4px resize handle (M-40).
What changed
isPhone()/usePhone()in@chopin/editor/pointer= primary coarse pointer AND (width ≤ 600 OR height ≤ 500), mirrored as:root[data-phone]and.workspace-root[data-workspace-phone]. A phone always gets the compact workspace, so landscape stays compact. Desktop, touch laptops and tablets keep their layouts.phone-workspace.tsx): one floating glass bar in the thumb zone with Chat · Document · Decisions · Build (icons + small labels, Decisions count badge, Chat unread/working dot, Document activity dot), 52px tabs in a 60px bar, safe-area aware, sliding active indicator,:activepress feedback. Tapping the current tab scrolls that pane to the top (Chat: to the newest message). Samenav "Workspace view"+aria-currentsemantics as the old compact tabs. Build disabled state keeps the existingaria-describedbyreason. The bar steps aside while the software keyboard is up (new:root[data-keyboard-open]from the existing visualViewport adapter). Landscape centres it at a 30rem max width.WorkspacetakesphoneAction?: ReactNode, rendered after the bar; style it with.phone-tab-action(60px glass circle, already defined). Verified at 375 and 320 with a placeholder: every tab stays ≥ 44px (58×52 at 320).phone-header.tsx): 44px translucent bar with backdrop blur and safe-area-inset-top; the existing sidebar trigger now lives in the bar (the shell's floating trigger steps aside via a smallSidebarTriggerclaim context), a small centred title that fades in only once the document's H1 has scrolled under the bar (always shown on Chat, Decisions and Build), and the existing document actions menu behind ⋯. It hides entirely on scroll down (a glass strip stays under the status bar) and returns on scroll up or when focus moves into it (transform only, 250ms ease-out; never hides under reduced motion; rubber-banding is ignored), listening to the active pane's scroller (Document, Decisions, Build; Chat keeps its bar). On a child document the parent's bar keeps both the sidebar trigger and the way back.responsive-comments.e2e.ts). DESIGN.md Layout updated.--tab-slide-ease: cubic-bezier(.32,.72,0,1)), crossfade under reduced motion. Scroll positions are kept per pane.packages/visuals/theme.css:--color-glass,--glass-filter,--phone-bar-height,--phone-tab-bar-height,--phone-tab-bar-gap,--phone-tab-row-max,--tab-slide-dur,--tab-slide-ease,--phone-bar-dur.Not in this PR: M-18 skeleton (not trivial here; skipped). M-02 window scrolling (
#rootstays fixed; follow-up). Overlaps with open #465 (workspace.tsx layout); phone changes are kept behind the phone condition and in new files to keep the rebase small.Screenshots
BEFORE (main, 8811) left, AFTER right, iPhone 15 Pro emulation.
Document

Chat

Decisions

Build

Landscape: before at 734×343 (desktop split), after at 852×393 with simulated side insets (compact phone chrome)

Top bar with simulated iPhone insets (59px top, 34px bottom): at rest with no small title (left), hidden while reading down with the glass strip under the status bar (middle), back on scroll up with the title faded in (right)

320px with a placeholder action button in the slice-3 slot

Desktop 1440×900 is unchanged (ImageMagick AE 0 at 2% fuzz; 5 anti-aliased pixels differ unfuzzed).
Testing
bun test apps/web packages/editor: 1313 pass, 0 fail (new unit tests forisPhoneandkeyboardOpen).bun run types: pass.bun run ci: dprint, oxlint, tokens, design record pass; design contract fails only on the reviewed-hash pins below.e2e/phone-workspace.e2e.ts: full-bleed chrome and in-bar controls, tab navigation (Chat/Document/Decisions/Build, scroll kept, reselect scrolls to top), landscape stays compact, top bar hides/returns, keyboard hides the tab bar (visual-viewport fake), desktop and touch tablet unaffected.e2e/responsive-workspace.e2e.tsupdated deliberately for phone chrome (full-bleed frame, tab bar hidden under a keyboard-sized viewport).e2e) passes on the latest commit (run 38121945859). Local e2e could not run because another worktree held the e2e database ports. Existing phone-width touch tests were updated where they asserted old chrome: tab bar placement and keyboard hiding (responsive-workspace), the comment backdrop covering the bar (responsive-comments), a slash-menu start position that no longer flips while it follows scroll (toolbar), and the compact research-child test now uses a fine primary pointer so it keeps covering the non-phone compact chrome (research-child-surface).Needs a real device: safe-area insets, backdrop blur cost while scrolling, the keyboard-open threshold (120px) on iOS Safari and Android Chrome, and scroll preservation across tab switches on WebKit.
Design-contract review
Reviewed and renewed in
Renew design-contract review hashes for navigation-shell.tsx, viewport.ts and workspace.tsx(dynamic-web.json):apps/web/src/navigation-shell.tsx: the sidebar trigger claim context wraps the shell. The sidebar motion classes and width style are unchanged, only re-indented.apps/web/src/viewport.ts: adds a booleandata-keyboard-openattribute. The--app-*property writes are unchanged.apps/web/src/workspace.tsx: the phone branch chooses between literal frame and motion classes and uses thetab-slidemotion contract.Rebase onto the paper layout (#465)
mx-3 mb-3split frame. A 1440 screenshot matches main.overflow-hidden bg-pageframe, the bottom tab bar and the top bar. The phone top bar is not wrapped in Put Chat on the ground and the document on a raised sheet #465's.workspace-header-slot, becausephone-workspace.csspositions.workspace-root > .phone-top-bar.motion-tab-slideon phones and Put Chat on the ground and the document on a raised sheet #465's backgroundless split chat elsewhere.room-workspace.tsxpasses bothpresenceandphone, and the desktopHeaderkeeps Put Chat on the ground and the document on a raised sheet #465'smembersonly outside split.🤖 Generated with Claude Code