Skip to content

fix(desktop): make header chrome zoom-correct and tidy split-pane#941

Draft
tellaho wants to merge 8 commits into
mainfrom
tho/header-zoom-fix
Draft

fix(desktop): make header chrome zoom-correct and tidy split-pane#941
tellaho wants to merge 8 commits into
mainfrom
tho/header-zoom-fix

Conversation

@tellaho

@tellaho tellaho commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Overview

Category: improvement

User Impact: Header chrome in the desktop app now scales correctly at every zoom level (100% / 125% / 150%), and every view that sits beneath it — forum lists, forum threads, single-pane message threads, the ⌘F find bar — renders below the header instead of hiding (or becoming unclickable) underneath it.

Problem: Channel, thread, profile, and agent-session headers were sized with arbitrary px Tailwind values (h-[92px], pt-[48px], h-[76px], …) that don't respect zoom, so at 125% / 150% headers drifted out of alignment with their backdrops. The header also overlays content (negative margin + translucent backdrop), and several bodies never compensated for it: forum content rendered under the header and intercepted clicks, the find bar opened fully hidden behind it, and single-pane threads lost their top clearance.

Solution: Drive header chrome from a small set of shared layout primitives — the real chrome height is measured into a CSS variable (useMeasuredCssVariable, via a callback ref so lazily-mounted headers still trigger measurement on first open) and every body beneath the header clears it with the same channelChrome fragments. Split-pane mode gets a real RightAuxiliaryPane wrapper that owns width / border / resize / testid, with inner panels reduced to content + a slim split header. Heads-up: this branch also softens the shared form controls (button / input / textarea / toggle — icon buttons 36→32px, rounded-mdrounded-lg, opaque input backgrounds), so it is not pixel-identical to main; the restyle is intentional and AgentSessionThreadPanel's simplified buttons depend on it.

Changes

File changes

desktop/src/shared/layout/chromeLayout.ts (new)
Single source of truth for chrome CSS variables (--sprout-top-chrome-height, --sprout-channel-content-top-padding), their defaults, and the Tailwind class fragments (topChromeInset, topChromeBackdrop, channelChrome — padding, height, negative margin, and absolute top offset) every consumer shares.

desktop/src/shared/layout/useMeasuredCssVariable.ts, observeElementBlockSize.ts (new)
Hook that observes an element's block size and writes it as a CSS variable on a target element. Returns a callback ref instead of taking a ref object, so a header that mounts inside a lazy subtree (first-ever channel open) still triggers measurement instead of silently sticking at the fallback value.

desktop/src/shared/layout/MainInsetContext.tsx, TopChromeInsetHeader.tsx, AuxiliaryPanelHeader.tsx (new)
Shared targets and building blocks: the main-inset element that receives the measured variable, the standard header strip below the system chrome, and the slim split-pane panel header plus the content-padding class inner panels use to clear it.

desktop/src/features/channels/ui/RightAuxiliaryPane.tsx (new)
Split-pane wrapper that owns the right panel's width, divider, resize handle (right-auxiliary-pane-resize-handle), and the panel testid — inner panels no longer duplicate any of it.

desktop/src/features/chat/ui/ChatHeader.tsx, AppTopChrome.tsx, AppShell.tsx, TopChromeBackdrop.tsx
Replace the px constellation with the shared rem/measured fragments; the header backdrop overlay now derives from chromeLayout instead of hard-coding its own heights. Drops the top-right actions portal in favor of inline placement.

desktop/src/features/channels/ui/ChannelScreen.tsx, ChannelScreenHeader.tsx
Wire the measurement: the screen passes the hook's callback ref into the header chrome wrapper and applies the measured variable to the main inset, keyed by channel and disabled in single-pane view.

desktop/src/features/channels/ui/ChannelPane.tsx
Composes the split layout through RightAuxiliaryPane (testids live on the wrapper only) and floats the ⌘F find bar just below the measured chrome — it previously rendered at the top of the pane, fully hidden behind the header band.

desktop/src/features/messages/ui/MessageThreadPanel.tsx, AgentSessionThreadPanel.tsx, profile/ui/UserProfilePanel.tsx
Each panel splits into a slim split-mode body vs. a standalone <aside>; split mode defers chrome to the wrapper, standalone keeps its own. Single-pane top clearance is restored (pt-[4.75rem]) so thread content no longer slides under the overlay header on narrow widths.

desktop/src/features/forum/ui/ForumView.tsx, ForumThreadPanel.tsx
Forum list, composer, and thread (including its loading state) now clear the measured chrome. Before, the overlay header sat on top of "Start a new post…" and the "Back to posts" bar — visually clipped and click-intercepted.

desktop/src/features/messages/ui/MessageTimeline.tsx, useComposerHeightPadding.ts
Timeline content clears the measured chrome via the shared fragment; composer height padding follows the same rem-based scheme.

desktop/src/shared/ui/ViewLoadingFallback.tsx
Loading skeletons honor the chrome: the header skeleton flows through TopChromeInsetHeader (it used to render behind the global search strip), and channel/forum body skeletons only reserve the measured header height when no in-flow header skeleton sits above them — so the skeleton matches the loaded layout instead of leaving a dead gap.

desktop/src/shared/ui/button.tsx, input.tsx, textarea.tsx, toggle.tsx, sidebar.tsx
The intentional control softening: icon buttons 36→32px with 18px icons, rounded-mdrounded-lg, inputs/textareas get opaque backgrounds, outline variant softened. App-wide visual change — see Overview.

desktop/src/features/home/ui/HomeView.tsx, InboxDetailPane.tsx, InboxListPane.tsx, FeedSection.tsx, HomeChannelActions.tsx (deleted)
Home/inbox panes consume the shared chrome fragments instead of local px offsets; the standalone channel-actions portal component is no longer needed with inline header actions.

desktop/src/features/channels/ui/ChannelMembersBar.tsx, agent-memory/ui/MemorySection.tsx, huddle/components/HuddleIndicator.tsx, messages/ui/ComposerEmojiPicker.tsx, MessageComposerToolbar.tsx, settings/UpdateIndicator.tsx
Smaller consumers updated to the shared fragments and the new control sizing.

desktop/tests/e2e/messaging.spec.ts
Resize-handle assertions target the renamed right-auxiliary-pane-resize-handle on the wrapper, whose width is what the size assertions measure.

desktop/tests/helpers/zoom-shot.mjs (new)
Screenshot helper for zoom comparisons (--port, --zoom, --open-thread / --open-profile / --open-agent).

Reproduction Steps

  1. Run the desktop app and open a channel. Zoom to 125% and 150% (⌘+/⌘−): the channel header, dividers, and pinned backdrop stay aligned at every level — including the first channel you open after a cold start, which previously stuck at a stale fallback height.
  2. Open a forum: the "Start a new post…" button and post list sit fully below the header and are clickable. Open a post: the "Back to posts" bar is visible (previously both were under the header; the post button was click-dead at default window sizes).
  3. Press ⌘F in a channel: the find bar appears directly below the header (previously it opened completely hidden behind it).
  4. Open a thread in split view: the right panel's resize handle, width persistence, and double-click reset work from the wrapper. Narrow the window into single-pane view: thread content starts below the header instead of underneath it.
  5. cd desktop && pnpm test:e2e:smoke — 178/178 passing, including the previously-broken forum file-attachment spec.

Screenshots/Demos

Zoom-alignment comparisons can be generated with the new helper: node tests/helpers/zoom-shot.mjs --zoom 1.5 --open-thread against a built bundle (--port to compare baseline vs. branch).

@tellaho tellaho requested a review from a team as a code owner June 10, 2026 07:04
@tellaho tellaho marked this pull request as draft June 10, 2026 07:47
@tellaho tellaho force-pushed the tho/header-zoom-fix branch from 7b20477 to 82bb9fa Compare June 10, 2026 19:18
@tellaho tellaho changed the title fix(desktop): header chrome respects browser zoom + split-pane layout cleanup fix(desktop): make header chrome zoom-correct and tidy split-pane Jun 10, 2026
@tellaho tellaho force-pushed the tho/header-zoom-fix branch from 16b1df0 to 40c251b Compare June 10, 2026 21:45
tellaho added a commit that referenced this pull request Jun 11, 2026
- Drop inner data-testids in split layout so RightAuxiliaryPane's
  wrapper testid is the only match (fixes Playwright strict-mode
  violations across messaging/profile/channels/mentions specs)
- Update messaging.spec.ts to the renamed
  right-auxiliary-pane-resize-handle testid
- Restore standalone top padding (pt-[4.75rem]) in MessageThreadPanel's
  scroll region so single-panel thread content clears the overlay header
- Return a callback ref from useMeasuredCssVariable so the measurement
  re-runs when the header mounts inside the lazy ChannelPane (cold-load
  no longer sticks at the 5.75rem fallback)
- Pad ForumView with the measured channel chrome variable; the overlay
  header was intercepting clicks on "Start a new post..." (regression
  introduced by this branch, caught by file-attachment.spec.ts)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tellaho added a commit that referenced this pull request Jun 11, 2026
- Drop inner data-testids in split layout so RightAuxiliaryPane's
  wrapper testid is the only match (fixes Playwright strict-mode
  violations across messaging/profile/channels/mentions specs)
- Update messaging.spec.ts to the renamed
  right-auxiliary-pane-resize-handle testid
- Restore standalone top padding (pt-[4.75rem]) in MessageThreadPanel's
  scroll region so single-panel thread content clears the overlay header
- Return a callback ref from useMeasuredCssVariable so the measurement
  re-runs when the header mounts inside the lazy ChannelPane (cold-load
  no longer sticks at the 5.75rem fallback)
- Pad ForumView with the measured channel chrome variable; the overlay
  header was intercepting clicks on "Start a new post..." (regression
  introduced by this branch, caught by file-attachment.spec.ts)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tellaho tellaho force-pushed the tho/header-zoom-fix branch from a0b5052 to 4fa1983 Compare June 11, 2026 07:39
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 7 commits June 11, 2026 01:09
Convert arbitrary px Tailwind values in header chrome to rem-based units
so layout respects browser zoom (verified at 100%, 125%, 150%).

Also lands the structural cleanup that fell out of the fix:
- Introduce RightAuxiliaryPane wrapper that owns width/border/resize;
  inner panels render content + slim split header (single responsibility).
- Centralize chrome layout helpers in shared/layout (chromeLayout.ts,
  TopChromeInsetHeader, AuxiliaryPanelHeader, MainInsetContext, plus
  measurement utilities for CSS-variable-driven sizing).
- Normalize header action icon sizing.
- Remove split-pane animation residue and dead in-panel resize buttons.
- Drop unreachable pt-[4.75rem] clause and orphaned peer-hover selectors.
- Make UserProfilePanel resize props optional (Pulse path stays alive).

Squashed from 14 iteration commits onto latest main.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Update the shared Button primitive to use rounded-lg controls, tighter icon/text spacing, and a consistent default SVG size.
- Make outline buttons shadowless with softer `border-input/40` borders and muted hover backgrounds.
- Align Input and Textarea with the same rounded-lg, shadowless, background-backed control treatment.
- Align Toggle radius and outline styling with the updated shared control language.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Keep chromeLayout focused on layout geometry by removing component-specific header button and title style exports.
- Add auxiliary panel header content primitives for title and action grouping while keeping content padding tied to channelChrome layout values.
- Convert channel and huddle header actions to generic outline buttons instead of chrome-specific override classes.
- Reuse the same thread, activity, and profile panel header content across split and single-pane shells.
- Keep panel close controls borderless with ghost buttons while preserving outline affordance for navigation actions.
- Let MemoryRefreshButton receive its visual variant from the owning panel header instead of encoding panel layout styling itself.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Move chrome CSS variable names and default values into shared chrome layout objects
- Apply main inset chrome defaults through the shared inline style object in AppShell
- Reuse the shared channel content padding measurement object in ChannelScreen
- Keep Tailwind chrome class fragments static so generated classes remain discoverable

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Drop inner data-testids in split layout so RightAuxiliaryPane's
  wrapper testid is the only match (fixes Playwright strict-mode
  violations across messaging/profile/channels/mentions specs)
- Update messaging.spec.ts to the renamed
  right-auxiliary-pane-resize-handle testid
- Restore standalone top padding (pt-[4.75rem]) in MessageThreadPanel's
  scroll region so single-panel thread content clears the overlay header
- Return a callback ref from useMeasuredCssVariable so the measurement
  re-runs when the header mounts inside the lazy ChannelPane (cold-load
  no longer sticks at the 5.75rem fallback)
- Pad ForumView with the measured channel chrome variable; the overlay
  header was intercepting clicks on "Start a new post..." (regression
  introduced by this branch, caught by file-attachment.spec.ts)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The overlay header (negative-margin + measured CSS var) only had its
padding counterpart in MessageTimeline and ForumView's list. Sweep the
rest of the bodies that render beneath it:

- ForumThreadPanel: pad both the loading and loaded roots; the
  "Back to posts" bar and post head rendered under the header
- ViewLoadingFallback: pad the forum skeleton like the channel one
- ChannelFindBar: float it below the measured chrome (absolute +
  channelChrome.top); it was fully hidden behind the header band
  whenever cmd-F opened it
- chromeLayout: add a channelChrome.top fragment for absolute
  offsets below the measured header

Verified with screenshot probes against a freshly served build on an
isolated port (another dev server was squatting on 4173, serving a
stale worktree build).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Route-level loading fallbacks rendered their header skeleton at the very
top of the pane — behind the global search chrome — while the channel
body skeleton still reserved the full overlay-header height, leaving a
dead gap above the message rows. The header skeleton now flows through
TopChromeInsetHeader like real inset headers, and the channel/forum
bodies only reserve the measured chrome height when no in-flow header
skeleton sits above them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tellaho tellaho force-pushed the tho/header-zoom-fix branch from 4fa1983 to 1901210 Compare June 11, 2026 08:15
Co-Authored-By: Claude Fable 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