Skip to content

feat(desktop): floating hover-peek sidebar - #6086

Merged
waleedlatif1 merged 3 commits into
stagingfrom
worktree-sidebar-hover-peek
Jul 30, 2026
Merged

feat(desktop): floating hover-peek sidebar#6086
waleedlatif1 merged 3 commits into
stagingfrom
worktree-sidebar-hover-peek

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

What

On the macOS desktop shell the collapsed sidebar has no width at all (--sidebar-collapsed-width: 0), unlike web's 51px icon rail — so once collapsed the only ways back were ⌘B and the small title-bar toggle. Hovering that toggle now floats the sidebar in as a card over the content, inset from the window edge, retracting when the pointer leaves. Clicking the same control still docks it for good.

Web and macOS native fullscreen (which falls back to the icon rail) are untouched.

How

The card is the existing .sidebar-shell-outer, re-styled — not a second surface. <Sidebar> is 1800 lines with scroll and expansion state, so it must never re-mount; keeping the same element in the same tree position guarantees that. One CSS rule re-points --sidebar-width at a new --sidebar-expanded-width, and the inner shell plus the aside follow with no per-element width overrides.

  • Chrome is all existing tokens: rounded-lg (--radius, same 8px as the content pane it floats beside), border-[var(--border)] (byte-identical to that pane's), shadow-overlay and z-[var(--z-modal)] (what the app's other edge-anchored panels use). The fill is the sidebar's own --surface-1, so docked and floating are the same surface.
  • Enter/exit use the popper idiom emcn's Radix surfaces already use (fade-in-0 zoom-in-95). These are keyframe animations, not transitions, deliberately: an animation runs from mount, so no hidden "from" frame and no requestAnimationFrame to step into — rAF is throttled in an unfocused window and would strand the card mounted-but-invisible.
  • w-auto, not a length: width cannot interpolate to or from auto, so entering and leaving snap instead of animating. With a length the card widened as it appeared and left a 248px ghost shrinking to zero in flow on retract.
  • Retraction is a document-level pointermove hit-test, not mouseleave: every menu the sidebar opens lives in a body portal, so mouseleave would retract the card the instant you reached for a menu item. The card and trigger are matched geometrically (coordinates can't go stale when the tooltip subtree re-renders); poppers are matched via the same predicate emcn's own modal uses.

Also fixed

Search palette migrated from raw z-40/z-50 to --z-modal. It was the one overlay the new card outranked, and the raw values also placed it below --z-toast, inverting the order globals.css documents. Fixing it at the root beat teaching the peek to dodge it.

Settings drive-bys — all pre-existing violations in files this PR already touches:

  • passwords-view: two literal pixel text sizes → scale tokens (text-sm/text-caption), and its cards now route through SettingsResourceRow instead of four re-derived chrome constants (mirroring the established custom-blocks pattern).
  • browser: three {null}-bodied SettingsSections — the only such usage in the codebase — collapse into one section of real label+action rows.

Testing

  • 18 unit tests for the peek hook: hover dwell, geometric hit-test, popper-keeps-open, modal dismissal, the gap between trigger and card, Escape yielding to an open popper, reduced motion, and a regression test for the scrim bug below.
  • 5 tests covering --sidebar-expanded-width across collapse/expand/resize/rehydrate.
  • Verified live in the desktop app via CDP: card geometry [8, 40, 250, 812], 8px radius, shadow on, transform-origin top-left, the 90ms dwell gate, and traversal from trigger into card without retracting.

Not verified: the animation feel. Background windows throttle both timers and CSS animations, and the harness could not reliably bring the Electron window frontmost, so every measurement of the enter/exit animation was invalid. Geometry and chrome were verified with real numbers; the fade/zoom needs a human eye.

Review notes

Three real bugs were found and fixed during review, each with a test:

  1. Moving from the trigger into the card retracted it — SidebarTooltip returns bare children when disabled, so suppressing the tooltip remounted the button and nulled the ref mid-hover. Fixed by anchoring the hover region to a stable wrapper and hit-testing geometrically.
  2. An earlier attempt used [data-native-surface-overlay]:not([aria-modal="true"]) — but emcn stamps that marker on the modal scrim and never sets aria-modal (Radix puts it on a different node), so the exclusion matched nothing and a fixed inset-0 scrim would have pinned the card open permanently.
  3. With a modal already open, hovering still opened the card over it, because the dismissal effect only fired on the transition.

lint:check fails repo-wide on terminal-session.tsx (noFocusedTests on xterm's fit.fit()) — verified pre-existing on clean staging, untouched here.

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 30, 2026 3:23am

Request Review

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches persistent workspace chrome, global CSS variables, and pointer/overlay interaction; scope is desktop-inset + collapsed only, with tests, but regressions could affect sidebar width or modal stacking.

Overview
Desktop hover-peek sidebar — On the macOS inset title-bar shell, when the rail is fully collapsed (0px), hovering the title-bar toggle floats the existing sidebar-shell-outer as a card over the content (fade/zoom enter/exit). New useSidebarPeek drives dwell, document pointermove hit-testing (card, trigger, Radix poppers), modal/search dismissal, and Escape handling; WorkspaceChrome wires peek chrome, a stable hover wrapper on the toggle, and titleBarMode === 'inset' gating. Sidebar gets isPeeking to show expanded UI without resize/double title-bar inset.

Width plumbing — Introduces --sidebar-expanded-width (restore width kept while collapsed) alongside --sidebar-width, with [data-peek] CSS swapping width for the peek card. Pre-paint script in layout.tsx and applySidebarWidths in the sidebar store both publish both variables; store tests cover collapse/expand/sync.

Also in this PR — Search modal overlay/dialog z-index moved to --z-modal so it stacks correctly with the peek card. Browser settings browsing-data rows consolidated into one section; passwords list uses SettingsResourceRow. Extensive unit tests for useSidebarPeek and sidebar width vars.

Reviewed by Cursor Bugbot for commit abf1bde. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a macOS desktop hover-peek for the collapsed sidebar and related chrome fixes.

  • New useSidebarPeek drives a floating card from the title-bar toggle via geometric hit-testing and Radix popper awareness.
  • --sidebar-expanded-width is published from the blocking layout script and sidebar store so peek can restore width while collapsed.
  • Search modal z-index uses --z-modal; settings browser/passwords cleanup is included.

Confidence Score: 5/5

This follow-up pass finds no remaining blocking failures and appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/use-sidebar-peek.ts Hook for open/close/exit phases, pointer sampling, Escape/popper rules, and modal dismissal.
apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx Wires peek only for inset desktop title bar; stable trigger wrapper; card chrome on shell.
apps/sim/stores/sidebar/store.ts Publishes both --sidebar-width and --sidebar-expanded-width on set/toggle/sync/rehydrate.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx isPeeking expands layout and skips title-bar spacer plus resize edge while floating.

Sequence Diagram

sequenceDiagram
  participant User
  participant Toggle as Title-bar toggle
  participant Peek as useSidebarPeek
  participant Shell as sidebar-shell-outer
  participant Sidebar as Sidebar

  User->>Toggle: mouseenter
  Toggle->>Peek: onTriggerEnter
  Peek->>Peek: dwell PEEK_OPEN_DELAY_MS
  Peek->>Shell: phase open (data-peek, absolute card chrome)
  Shell->>Sidebar: isPeeking true (expanded layout)
  User->>Peek: pointermove outside card/trigger/popper
  Peek->>Peek: close after PEEK_CLOSE_DELAY_MS
  Peek->>Shell: exiting animation then unmount flags
Loading

Reviews (3): Last reviewed commit: "fix(desktop): arm the peek before first ..." | Re-trigger Greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Waleed Latif added 3 commits July 29, 2026 20:23
On the macOS desktop shell the collapsed sidebar has no width at all
(`--sidebar-collapsed-width: 0`), so the only ways back were ⌘B and the
title-bar toggle. Hovering that toggle now floats the sidebar in as a card
over the content, inset from the window edge, and it retracts when the
pointer leaves. Clicking the same control still docks it for good.

The card is the existing `.sidebar-shell-outer` re-styled, not a second
surface: `<Sidebar>` is never re-mounted, so its scroll position and
expansion state survive a peek. One CSS rule re-points `--sidebar-width` at
a new `--sidebar-expanded-width`, and the inner shell and aside follow with
no per-element overrides. Chrome is all existing tokens — `--radius`,
`--border`, `shadow-overlay`, `--z-modal` — and the fill is the sidebar's
own `--surface-1`, so docked and floating are the same surface. Enter/exit
use the popper idiom (`fade-in-0 zoom-in-95`) that emcn's Radix surfaces
already use.

Also migrates the search palette from raw `z-40`/`z-50` to `--z-modal`. It
was the one overlay outranked by the new card, and the raw values also put
it below `--z-toast`, inverting the order globals.css documents.

Settings drive-bys, all pre-existing violations in files this touches:
tokenize two literal pixel text sizes in passwords-view, route its cards
through `SettingsResourceRow` instead of four re-derived chrome constants,
and collapse three `{null}`-bodied `SettingsSection`s in browser settings
into one section of real rows.
Review round 1 (Cursor Bugbot) found three ways the card could flicker:

- A modal opening mid-dwell left the open timer pending, so the card
  mounted over the modal before the dismiss path could run.
- A modal opening while the card was already animating out let it keep
  animating on top of the modal.
- Re-hovering the toggle late in the exit window scheduled a fresh dwell
  without clearing the exit timer, so the card unmounted and then
  re-mounted — a visible gap with the pointer never leaving the toggle.

The first two collapse into one fix: merge the two reset effects so an
unavailable peek and a screen-owning modal both drop the card immediately,
unconditionally clearing all three timers. Instant is also the right look,
since the modal's scrim covers the card's position on the same frame.

For the third, a re-hover during the exit now re-opens synchronously rather
than scheduling another dwell — the card is already on screen, so the dwell
that guards against accidental opens from a cold state does not apply.

One regression test each.
Review round 2: the title-bar seed ran in a passive effect, which lands
after paint — long enough for a `mouseenter` on the toggle to be dropped
while the peek still read disabled, with nothing to retry it until the
pointer left and returned. A regression from folding the seed into the
window-state effect during cleanup.

Promoted to a layout effect, which runs before paint and before any mouse
event can be dispatched. The body is cheap synchronous reads plus a
subscription; the bridge's getState stays async and blocks nothing.
@waleedlatif1
waleedlatif1 force-pushed the worktree-sidebar-hover-peek branch from 7c737fd to abf1bde Compare July 30, 2026 03:23
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit abf1bde. Configure here.

@waleedlatif1
waleedlatif1 merged commit edef789 into staging Jul 30, 2026
27 checks passed
@waleedlatif1
waleedlatif1 deleted the worktree-sidebar-hover-peek branch July 30, 2026 03:29
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