Skip to content

Conversation

@ThomasK33
Copy link
Member

Adds user-configurable Layout Presets for the left sidebar + right sidebar layout.

  • Presets are stored in ~/.mux/config.json (validated + versioned), exposed via new ORPC endpoints.
  • New Settings → Layouts section to save/update/rename/delete presets and assign them to Slots 1–9.
  • Slots can be triggered via hotkeys (default Ctrl/Cmd+Alt+1..9, optional per-slot override).
  • Presets use portable terminal placeholders and resolve them to existing/new terminal sessions on apply.

Validation:

  • make static-check

📋 Implementation Plan

User-configurable layout presets + hotkeys (investigation + implementation plan)

What exists today (building blocks)

Layout state

  • Left sidebar (projects/workspaces list): persisted boolean sidebarCollapsed (global) set in src/browser/App.tsx.
  • Right sidebar (tools like Costs/Review/Explorer/Terminal/Stats): persisted via a tree layout model in localStorage.
    • Layout schema: RightSidebarLayoutState (src/browser/utils/rightSidebarLayout.ts) → recursive split + tabset nodes with ratios.
    • Persistence keys (src/common/constants/storage.ts):
      • Layout: right-sidebar:layout:${workspaceId} (per-workspace)
      • Collapsed: right-sidebar:collapsed (global)
      • Width: right-sidebar:width (global, stored as a raw string px value via useResizableSidebar)
  • Programmatic manipulation already exists via pure helpers in rightSidebarLayout.ts and via the command palette helper updateRightSidebarLayout() in src/browser/utils/commands/sources.ts (writes to localStorage using updatePersistedState).

Commands + hotkeys

  • Command Palette is already data-driven: commands are registered via registerSource(() => CommandAction[]) (src/browser/contexts/CommandRegistryContext.tsx, wired in src/browser/App.tsx).
  • Keybinds are currently hardcoded in src/browser/utils/ui/keybinds.ts (KEYBINDS constant) and consumed by global listeners in App.tsx, useAIViewKeybinds.ts, and RightSidebar.tsx.
  • A Settings → Keybinds section exists, but it is read-only (lists keybinds; no editing) (src/browser/components/Settings/sections/KeybindsSection.tsx).

Competitive / expectation check

  • JetBrains has named tool-window layouts and lets users bind them in the keymap.
  • VS Code has Profiles (which include some UI state), but they’re “heavy” (settings/extensions/UI state bundled).
  • Neovim users typically use “sessions” + custom mappings.
Sources / links
  • JetBrains: “Save tool window layout” docs
  • VS Code: Profiles docs
  • Neovim: :mksession docs

(See exploration report for links.)


What’s missing (gaps)

  • No concept of named / user-managed layout presets.
  • No way to apply a preset (other than manual resizing/splitting) in one action.
  • No user-configurable hotkeys (for any commands), and no place to bind “apply layout X”.
  • Terminal tabs are session-backed: TerminalTab requires a real terminal:<sessionId>; a layout preset cannot safely store session IDs and reapply them later.

Feasibility + does this make sense?

Yes, and the codebase is already most of the way there. Mux already:

  • serializes layout state (right sidebar) to a stable-ish JSON tree,
  • has a global command system that can generate actions dynamically,
  • has a centralized keybind matcher.

The main complexity is terminal session creation during preset application and keybind conflict management.


Recommended approach (v1): “Layout Presets” stored in ~/.mux/config.json + command palette + hotkeys

Why config.json

  • Matches your preference: presets/hotkeys are file-backed, portable, and don’t disappear on localStorage clears.
  • Works in both desktop and “browser UI served by mux” scenarios because the backend machine owns ~/.mux.

Trade-off: if a user downgrades to an older mux build and that build writes config.json, it will likely drop unknown fields (including layoutPresets). If downgrade/upgrade preservation matters, prefer the dedicated layouts.json alternative below.

Storage + API surface

  1. Persisted schema in ProjectsConfig (src/common/types/project.ts):
    • Add layoutPresets?: LayoutPresetsConfig.
    • LayoutPresetsConfig = { version: 1; presets: LayoutPreset[]; slots: LayoutSlot[] }
    • LayoutSlot = { slot: 1|2|3|4|5|6|7|8|9; presetId?: string; keybindOverride?: Keybind }
  2. Backend read/write (src/node/config.ts):
    • loadConfigOrDefault() parses + validates layoutPresets defensively (invalid → empty).
    • saveConfig() writes layoutPresets back out.
  3. ORPC endpoints (new router group, so we don’t overload the task-settings config APIs):
    • uiLayouts.getAll() -> LayoutPresetsConfig
    • uiLayouts.saveAll({ layoutPresets: LayoutPresetsConfig }) -> void
    • handlers call context.config.editConfig(...).

Preset + slots schema (file-backed)

  • LayoutPreset (hotkeys are attached to slots, not presets):
    • id: string (uuid)
    • name: string
    • leftSidebarCollapsed: boolean
    • rightSidebar: { collapsed: boolean; width: { mode: 'px', value: number } | { mode: 'fraction', value: number }; layout: RightSidebarLayoutPresetState }
  • LayoutSlot[] (1–9):
    • default keybinds: Ctrl/Cmd+Alt+1..9
    • optional override per-slot: keybindOverride?: Keybind
  • RightSidebarLayoutPresetState mirrors RightSidebarLayoutState but is portable:
    • tool tabs: costs|review|explorer|stats
    • terminal placeholders: terminal_new:<stableId> (no session IDs)
    • (v1) drop file:* tabs so presets are project/workspace agnostic.

Save current layout → preset

When user saves a preset for the currently selected workspace:

  1. Read current UI state (left sidebar collapsed, right sidebar collapsed/width, right sidebar layout state).
  2. Convert workspace layout → preset layout:
    • replace each terminal:<sessionId> with a stable terminal_new:*
    • drop file:* tabs
  3. Append/update the preset in layoutPresets.presets, then persist via uiLayouts.saveAll(...).
  4. Optional follow-up: “Assign to slot (1–9)?” → writes layoutPresets.slots[*].presetId.

Apply slot (hotkey) / apply preset (palette)

Applying a slot/preset to the currently selected workspace:
0. Determine the preset:

  • slot hotkey (1–9) → lookup layoutPresets.slots[slot].presetId
  • command palette → can target either a slot or a preset directly
  • if unassigned/missing preset → no-op
  1. Update global UI persisted keys:
    • sidebarCollapsed
    • RIGHT_SIDEBAR_COLLAPSED_KEY
    • RIGHT_SIDEBAR_WIDTH_KEY (compute px if preset uses fraction)
  2. Resolve terminal placeholders by re-using existing sessions first, then creating new sessions if needed:
    • existing = await api.terminal.listSessions({ workspaceId })
    • map first N session IDs to placeholders
    • create additional sessions to reach N (createTerminalSession)
  3. Write resolved RightSidebarLayoutState into right-sidebar:layout:${workspaceId}.

UX surface

  • Settings → Layouts (new section)
    • Slots (1–9)
      • show assigned preset (or “Empty”)
      • Apply
      • Assign/Clear
      • Keybind display: default Ctrl/Cmd+Alt+N or overridden
    • Presets
      • Save from current layout / Update from current layout
      • Rename / Delete
      • Assign to slot(s)
  • Command palette
    • Layout: Apply Slot 1..9 (title includes assigned preset name when set)
    • Layout: Save current as preset…
    • Layout: Apply Preset <preset> (optional escape hatch for >9 presets)

Hotkey handling

  • Default slot keybinds are hardcoded: Ctrl/Cmd+Alt+1..9.
  • Optional per-slot overrides live in layoutPresets.slots[*].keybindOverride and replace the default.
  • Validate overrides:
    • must include at least one modifier (ctrl/alt/shift/meta)
    • must not conflict with hardcoded KEYBINDS.* or other slot bindings
  • App-level keydown handler computes “effective” slot binds (override ?? default) and applies the assigned preset.

Defensive / self-healing rules

  • Version the presets container and validate on read (invalid → ignore).
  • Best-effort apply: if terminal creation fails, still apply non-terminal parts.
  • Never write placeholder terminal IDs into the real workspace layout key.

Net LoC estimate (product code only)

  • ~750–1300 LoC
    • config parsing/persistence + ORPC endpoints (~200–400)
    • preset types + converters (~150–250)
    • settings UI (slots + presets) (~250–450)
    • command palette actions + hotkey plumbing (~150–300)

Alternative approaches

A) Store presets in a dedicated ~/.mux/layouts.json (or .jsonc) file (downgrade-safe)

  • Pros: downgrading mux won’t clobber presets (older versions won’t touch this file).
  • Cons: one more config file to manage/backup.
  • Implementation: add a small backend LayoutsConfig reader/writer (like Config, but scoped to layouts) + ORPC endpoints; UI logic stays the same.
  • Net LoC: ~700–1200

B) “Hard slots only” (fixed hotkeys, no overrides, no preset list)

  • Slots 1–9 store the layout snapshot directly (no reusable named presets).
  • Fixed keybinds: Ctrl/Cmd+Alt+1..9.
  • Pros: minimal UI + smallest surface area.
  • Cons: capped at 9 layouts; no rename/share; harder to reassign.
  • Net LoC: ~250–450
Non-goals for v1
  • Moving chat to a different dock position (chat is the main center panel today; presets will manipulate left sidebar + right sidebar only).
  • Capturing per-project file tabs / editor state.
  • Full user-remappable keybind system for every command.

Generated with mux • Model: openai:gpt-5.2 • Thinking: high • Cost: $3.86

@github-actions github-actions bot added the enhancement New feature or functionality label Jan 17, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b2be1df0a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Addressed: UILayoutsContext.saveAll now updates local state only after a successful uiLayouts.saveAll(...) call, so failed persistence won't cause presets to appear briefly and then disappear on reload.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e7eaf8bec

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Follow-up: slot hotkeys now call preventDefault() only when the slot is actually assigned (so empty slots won't swallow Ctrl/Cmd+Alt+N).

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 067e253903

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ThomasK33
Copy link
Member Author

@codex review

Normalization now drops modifier-less keybindOverride entries in layoutPresets.slots (self-healing for manual/corrupt config). This prevents plain-letter hotkeys from being treated as global slot shortcuts.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8040f7026d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8040f7026d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Change-Id: I10152520fced6d45a8fd2e361c7a8a086689d900
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: Ic4baec7d12a95ebffd8f3d990b54102b78fd0cd3
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I3f2da4aaf32ac1e974bc48b74509c9837fe913dc
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: Idc4c883f7614bed57ced8718c964e086d63a0dc6
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I589572f8da4fe4b47ba7f628c5b940613aed6fa9
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I9ccc39546f476f8ace1232ba3dfa2d42bf7a3f87
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33
Copy link
Member Author

@codex review

Addressed:

  • Slot hotkey handler now returns early for editable/terminal targets and for AltGraph (AltGr) modifier state.
  • Saving a preset with optional slot assignment is now atomic via a single saveAll call (no stale layoutPresets overwrite).

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2e1d92a04

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Change-Id: Iecc135a3a4c5f7ee9e48c69b61f7eaaeb706db34
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33
Copy link
Member Author

@codex review

Addressed:

  • Preset capture for left sidebar collapse now uses the same mobile default as App.tsx when the persisted value isn't set yet.
  • normalizeKeybind now preserves a single-space key so Ctrl+Space overrides survive normalization.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 772d51687f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Change-Id: If0ddbb0182e2cb258cb4e72ae8abb5c7053f5e64
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33
Copy link
Member Author

@codex review

Addressed: write operations now call uiLayouts.getAll() when presets haven't loaded yet (or last load failed) and base mutations on that config. If we can't load the existing config, we throw instead of overwriting ~/.mux/config.json with defaults.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b8f892af5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Change-Id: I56966116ee4c8fd8957f1a8404f4e5de0f8483ea
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33
Copy link
Member Author

@codex review

Addressed: applying a preset now preserves any extra backend terminal sessions by adding tabs for unmapped sessions into the focused tabset, so sessions don’t become unreachable until reload.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Swish!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Change-Id: Ida9e598183242034f2f8f3fef0f2e0069f1ff86e
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I961835bc183c617bb347c9f2e55c525138a6ac26
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I0755a3340a6aa01b9c22a0e3f4aaad1f640a13cc
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33
Copy link
Member Author

UI Tests (Chromatic) is currently pending for this PR and needs baseline approval:

All other GitHub checks are passing on my side.

@ThomasK33 ThomasK33 changed the title 🤖 feat: layout presets + slot hotkeys 🤖 feat: layout slots + slot hotkeys Jan 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant