8000 Feat/saved workspace layouts by jeFF0Falltrades · Pull Request #183 · amirlehmam/wmux · GitHub
[go: up one dir, main page]

Skip to content

Feat/saved workspace layouts - #183

Merged
amirlehmam merged 3 commits into
amirlehmam:masterfrom
jeFF0Falltrades:feat/saved-workspace-layouts
Aug 20, 2026
Merged

amirlehmam merged 3 commits into
amirlehmam:masterfrom
jeFF0Falltrades:feat/saved-workspace-layouts

Conversation

@jeFF0Falltrades
Copy link
Copy Markdown
Contributor

Hey @amirlehmam - Thanks for wmux! I've really come to enjoy cmux and have been looking for a similar experience with WSL and appreciate the work done here!

One feature I really like is the ability to customize my workspace and create "flow state" customizable workspaces for different purposes.

To that end, I've added a feature here that allows users to create their own custom workspaces (custom pane arrangement + startup commands) and save them as presets - one can be set as a default, or a user can use the Command Palette to open a specific preset.

You can check out the changes under the Workspace settings menu.

Cheers, and let me know if you have questions or feedback!

Summary

Adds Saved Layouts: configurable, named pane arrangements (geometry + optional per-pane startup commands) that can be applied to new workspaces on demand or set as the default. Also fixes two pre-existing bugs found while building and testing this.

Background

New workspace creation was inconsistent: Ctrl+N, wmux new-workspace (CLI), and "Open folder as workspace" always opened a single pane, while the sidebar "+" button and first launch used a hardcoded 3-pane layout (buildDefaultSplitTree()). Neither was "correct" — they were two independent, undocumented defaults. Rather than picking one, this PR makes both configurable and keeps their original split-brain behavior only as the fallback when nothing is explicitly set.

What's new

  • Save any live pane arrangement as a named preset — Settings → Workspace → Saved Layouts, or Command Palette → "Save current layout as new preset". Captures geometry plus whatever shell/cwd/startup command each pane already has.
  • Mark one preset as the app default — applies automatically wherever a caller doesn't specify a layout. When nothing is configured, Ctrl+N/CLI/"Open folder" keep their historical single-pane baseline and the sidebar "+"/first-launch keep their historical 3-pane baseline — unchanged from before this PR. A configured default overrides both uniformly.
  • Apply any preset on demand — Command Palette shows a "New Workspace: {name}" entry per saved layout, independent of which one (if any) is marked default.
  • "Set to Current Layout" — re-captures a preset's geometry from whatever workspace is currently open, without discarding hand-edited startup commands for panes the live capture doesn't happen to have one for (position-matched merge).
  • Per-pane startup commands are fully editable in Settings, with add/remove controls, so nothing is silently dropped when a pane was captured with more than one command (e.g. from a Quick Launch profile).
  • Collapsible per-layout "Startup commands" section to keep the list from getting bulky with several saved layouts.

Bug fixes found along the way

  • Command Palette (Ctrl+Shift+P) silently did nothing. A standalone keydown listener in App.tsx compared e.key against the binding without lowercasing — Shift+P delivers e.key === 'P', but the binding is stored as 'p'. useKeyboardShortcuts.ts already had a matchesBinding() helper with the correct case-insensitive comparison (and a comment explaining exactly this); exported and reused it instead of duplicating the broken check.
  • Settings window closed when drag-selecting text that ended outside it. The overlay's outside-click detection used onClick, but a click event fires on the deepest common ancestor of where the mouse went down and up — so selecting text starting inside an input and releasing past the window's edge synthesized a click on the backdrop. Switched to onMouseDown, matching the pattern already used correctly by CommandPalette and ShortcutCheatSheet.

@amirlehmam
amirlehmam merged commit f03538a into amirlehmam:master Aug 20, 2026
amirlehmam added a commit that referenced this pull request Aug 20, 2026
buildDefaultSplitTree moved to split-utils.ts in #183, taking App.tsx's only
uuid() call site with it. The import stayed behind and was the one new lint
warning the two merges introduced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NugDqtU1rmBNQCEv8do4rS
amirlehmam added a commit that referenced this pull request Aug 20, 2026
Minor, not patch: #183 lands a new user-facing feature (saved workspace
layouts). Site asset ?v= query strings stay pinned at 1.5.1 — they bust the
cache for the brand assets, which did not change here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NugDqtU1rmBNQCEv8do4rS
@amirlehmam
Copy link
Copy Markdown
Owner

Merged, and shipping in v1.6.0. Thank you — and glad wmux is filling the WSL-shaped gap for you, that's exactly the itch it started from.

What sold me

The framing, more than the feature. New-workspace creation genuinely was split-brained — Ctrl+N/CLI/"Open folder" gave one pane, the sidebar + and first launch gave the hardcoded 3-pane tree — and I'd have been tempted to just pick a winner and break someone's muscle memory. Making both configurable and keeping the old split-brain only as the fallback when nothing is set means every existing install opens exactly as it did yesterday, and the new behavior is opt-in. That's the right call and I wouldn't have made it as cleanly.

instantiateLayout() is the detail that matters most and it's easy to miss why: wmux leans hard on PTY id === surface id for tab re-attachment (surfaces are all rendered simultaneously and kept alive; switching tabs is pure CSS). A saved layout freezes its pane/surface ids at capture time, so applying one preset twice — or to two workspaces at once — would have handed out duplicate ids and quietly broken re-attachment in a way that would have been miserable to diagnose. Re-minting on every apply is the correct fix, and the type-level comment on SavedLayout pointing at it is the kind of thing that keeps it correct a year from now.

mergeStartupCommands matching old→new panes by position rather than requiring identical tree shape is also the right trade — geometry legitimately changes between saves, and silently eating hand-typed startup commands on every "Set to Current Layout" would have been a slow-burn annoyance.

The two drive-by fixes

Both real, both appreciated, and the Command Palette one is a little embarrassing on my side: matchesBinding() already existed with a comment explaining exactly that failure mode, and App.tsx duplicated the check without it. Reusing the helper instead of adding a third copy is the correct resolution — that listener should never have had its own comparison.

The settings-overlay one is a nice catch too. click firing on the deepest common ancestor of mousedown and mouseup is genuinely non-obvious, and CommandPalette / ShortcutCheatSheet were already doing it right, so this puts the three in agreement.

Verified on merge

  • npx vitest run — 1235 passed / 100 files
  • npm run typecheck — clean
  • npm run lint — back at the master baseline

One small follow-up commit on top: removing buildDefaultSplitTree from App.tsx orphaned its uuid import, which was the one new lint warning. Nothing that needed your attention.

Cheers, and thanks for the care that went into this.

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.

2 participants

0