Skip to content

feat(app): peek the closed sidebar on hover - #58156

Merged
jlongster merged 1 commit into
betafrom
sidebar-peek
Oct 22, 2026
Merged

jlongster merged 1 commit into
betafrom
sidebar-peek

Conversation

@jlongster

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

With the left sidebar closed, resting on its toggle shows the sidebar as a floating panel over the app. The layout doesn't move. It uses the real sidebar, so every control in it works. It's behind a new Peekable sidebar toggle in the Experiments tab, on by default.

  • Opening: the pointer has to rest on the toggle for 500ms. Leaving sooner cancels it. Clicking the toggle still docks or undocks the sidebar as before.
  • Staying open: it stays open while the pointer is inside the box that spans the toggle and the panel, plus 15px around it. That box covers the path from the toggle down into the panel. Closing is decided from where the pointer actually is: it's checked on pointermove, wheel, scroll, when the pointer leaves the window, and every 150ms. After the pointer leaves the box, the panel waits 100ms before closing, so a short overshoot doesn't close it. An open menu, a held pointer button or drag, and typing in a sidebar field all keep it open. Escape closes it, and so does keyboard focus moving out. Docking ends the peek.
  • Panel: a rounded card 16px from the start edge, starting 12px below the session header. It's as tall as its rows, up to 12px above the bottom of the window, and scrolls inside past that. It uses --v2-elevation-floating and slides in from the start edge on a 0.15s spring with no bounce. With reduced motion it appears instantly.
  • Toggle: while closed it shows the menu (hamburger) icon, cross-fading with layout-left-full. Its tooltip is off while the sidebar is closed so it doesn't cover the peek. If any sidebar session other than the current one would show its unread dot, the icon gets an accent dot. The vertical tab strip already tracks per-row unread state, and it now reports that to the toggle. The aria-label then reads "Toggle sidebar, unread sessions".

Known gaps:

  • Not verified: the menu, drag, typing and keyboard-focus cases, and RTL.
  • The Inbox layout gets no unread dot, because it doesn't use the tab strip.
  • The new aria-label string is English only.

How did you verify your code works?

Playwright against the web app on an OpenCode Drive server, with vertical tabs. Checked: the hover delay, moving into the panel keeps it open, closing on a jump out or past the 15px slack (but not on a short overshoot), Escape, click-to-dock, and that the main area doesn't shift. James also tried it in the web app.

Screenshots / recordings

None attached.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good. I ran the app dev server for both base and this branch with Playwright against a local backend, using Side tabs. On base, hovering the closed toggle only shows its tooltip. With this change there's no peek at 250ms, then at about 850ms the sidebar opens as a floating card and the layout doesn't move. It stays open when the pointer moves into it and closes when the pointer moves well away. The closed toggle shows the hamburger icon. I didn't exercise the unread dot, menus, drag, typing, focus-out, Escape or RTL. typecheck and the src/shell unit tests pass.

Public API: unchanged. This is app-only UI: a local experiment switch, one i18n string and some internal data-slot attributes.

One small issue: turning the experiment off doesn't end an open peek. See the inline comment.

Before

After

createEffect(on(input.open, (open) => open && end(), { defer: true }))

createEffect(() => {
if (!sidebarPeekEnabled()) end()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sidebarPeekEnabled() returns a plain knob field, not a signal. So this effect runs once on creation and never again, and turning the experiment off while a peek is open won't call end(). sidebar-toggle.tsx handles this with motionTuning.subscribe. You could do the same here, e.g. onCleanup(motionTuning.subscribe(() => !sidebarPeekEnabled() && end())), or drop the effect.

@jlongster
jlongster merged commit 110faa1 into beta Oct 22, 2026
8 of 18 checks passed
@jlongster
jlongster deleted the sidebar-peek branch October 22, 2026 17:11
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