Skip to content

🤖 feat: add auto-hide sidebar setting#3367

Open
Neppkun wants to merge 1 commit into
coder:mainfrom
Neppkun:autohide_sidebar
Open

🤖 feat: add auto-hide sidebar setting#3367
Neppkun wants to merge 1 commit into
coder:mainfrom
Neppkun:autohide_sidebar

Conversation

@Neppkun
Copy link
Copy Markdown

@Neppkun Neppkun commented May 23, 2026

Summary

Adds an Automatically hide sidebar toggle in Settings → General → Appearance, directly below Theme. When enabled, the left sidebar renders collapsed by default and only expands while the cursor is over it, reusing the existing transition-[width] duration-200 animation. When disabled, behavior reverts to the current user-toggled sidebarCollapsed state. The preference is persisted to ~/.mux/config.json end-to-end — backend reader/writer, oRPC getConfig + dedicated updateAutoHideSidebar route, onConfigChanged fan-out, and a localStorage mirror for flash-free synchronous reads at boot — mirroring the chatTranscriptFullWidth flow exactly.

demo

Background

The left sidebar is a useful workspace navigator but takes up screen real-estate that some users (especially on smaller displays or when focused on a single workspace) would rather reclaim while keeping the sidebar one mouse-flick away. The existing manual collapse hotkey/toggle is per-window state; this adds a persistent "auto" mode so users don't have to remember to expand/collapse it on every context switch.

Patterned after #3350 (full-width chat transcript) so the persistence/sync model is identical and easy to review.

Implementation

  • Config schema + types (appConfigOnDisk.ts, common/types/project.ts): new optional autoHideSidebar?: boolean.
  • Backend (node/config.ts): reader parses the new key via parseOptionalBoolean; writer follows the "only persist when true" convention so disabling the toggle removes the key from disk entirely.
  • oRPC (common/orpc/schemas/api.ts, node/orpc/router.ts): getConfig echoes autoHideSidebar; new updateAutoHideSidebar route uses the shared booleanToggleRoute schema. onConfigChanged fan-out covers cross-window sync generically.
  • Hook (browser/hooks/useAutoHideSidebar.ts): verbatim mirror of useChatTranscriptFullWidth — seeds from localStorage to avoid a flash on boot, refreshes via getConfig, subscribes to onConfigChanged, and uses a fetchVersion nonce so Settings writes can't be clobbered by an in-flight getConfig.
  • Effective collapse boundary (browser/App.tsx): the persisted sidebarCollapsed is intentionally not overwritten while auto-hide is on. Instead, App.tsx computes effectiveSidebarCollapsed = autoHideSidebar ? !sidebarHovered : sidebarCollapsed and passes that to <LeftSidebar>. This way the user's manual collapse preference is preserved for when auto-hide is later turned off.
  • Hover detection (browser/components/LeftSidebar/LeftSidebar.tsx): new optional autoHideEnabled + onHoverChange props attach onMouseEnter/onMouseLeave to the outer sidebar div only when auto-hide is on. The existing transition-[width] duration-200 animates the resulting width change for free — no JS animation needed.
  • Settings UI (features/Settings/Sections/GeneralSection.tsx): new Switch row directly below Theme, with its own load-nonce + serialized update chain, exactly matching the chat-transcript pattern already in this file.

Edge cases

  • Resize handle: lives inside the sidebar, so dragging keeps the cursor inside and the sidebar stays expanded for the duration of the drag.
  • Touch/mobile: onMouseEnter/Leave don't fire on touch, so the toggle is effectively a no-op on mobile — mobile already auto-collapses via the existing isMobile seed and overlay backdrop.
  • First paint: localStorage seed avoids any flash before the backend getConfig resolves.
  • Keyboard/focus accessibility: hover-only expand is a known limitation; left as a follow-up if it grows in scope.

Validation

  • make typecheck
  • make lint
  • make static-check ✅ (exit 0)
  • New + adjacent tests pass:
    • src/common/config/schemas/appConfigOnDisk.test.ts (new flag happy-path + string-rejection)
    • src/node/config.test.ts (round-trip persist on true, drop key on false, ignore invalid)
    • src/node/orpc/router.test.ts (updateAutoHideSidebar + getConfig + on-disk loader)
    • src/browser/hooks/useAutoHideSidebar.test.tsx (8 cases mirroring useChatTranscriptFullWidth)
    • src/browser/features/Settings/Sections/GeneralSection.test.tsx (loads + persists the new toggle)
  • Manual smoke (see GIF above): toggle in Settings collapses the sidebar instantly; hover expands smoothly with the existing 200ms transition; leave collapses again; quitting/relaunching restores the persisted state from ~/.mux/config.json; autoHideSidebar: true appears in the JSON when enabled and is removed when disabled.

Risks

Low.

  • New persisted key is additive and optional; older clients ignore it (loader uses parseOptionalBoolean, writer uses passthrough-friendly partial object).
  • Hover handlers are gated on autoHideEnabled, so when the feature is off the <LeftSidebar> tree is byte-identical to today.
  • The persisted sidebarCollapsed value is deliberately preserved while auto-hide is on, so disabling the toggle returns the user to exactly the manual collapse state they had before.

Generated with mux • Model: anthropic:claude-opus-4-7 • Thinking: high • Cost: $9.51

Adds an 'Automatically hide sidebar' toggle under Settings -> General ->
Appearance. When enabled, the left sidebar renders collapsed by default
and expands on hover (reusing the existing transition-[width] duration-200
animation). When disabled, behavior reverts to the user-toggled
sidebarCollapsed state. The setting is persisted to ~/.mux/config.json
end-to-end, mirroring the chatTranscriptFullWidth flow.
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