Skip to content

feat(session): per-tab shell history and configurable session restore - #6548

Closed
crmolinaz wants to merge 1 commit into
manaflow-ai:mainfrom
crmolinaz:feat/session-shell-history-restore
Closed

crmolinaz wants to merge 1 commit into
manaflow-ai:mainfrom
crmolinaz:feat/session-shell-history-restore

Conversation

@crmolinaz

@crmolinaz crmolinaz commented Jun 21, 2026 •

Copy link
Copy Markdown

Adds a "Sessions" settings area (session.* in cmux.json, both default-on) with two capabilities:

  • Per-tab shell history (session.persistShellHistory): each terminal tab gets its own HISTFILE keyed by the tab's stable surface UUID, injected via the existing per-shell startup hooks and applied in the zsh/bash prompt hook (after the user's rc, with incremental writes) so up-arrow / Ctrl-R recall the tab's commands after a reopen. Also records a per-tab cmux command history (OSC 133, via the existing PTY tee) viewable from the command palette ("Show Command History"). Keyed by surface UUID — not working directory — because a tab's cwd is not stable between first open and restore.

  • Configurable session restore (session.restoreMode: always | ask | never, default ask): SessionRestorePolicy honors the mode and, in "ask", AppDelegate prompts before applying the restore (keeping the snapshot reopenable from File > Restore Previous Launch).

Wired through the settings catalog, Settings UI, JSON schema, docs messages, and Localizable.xcstrings (en + ja). Tests: restore-mode policy, HISTFILE keying + env injection, OSC 133 drain, command-history round-trip, and config defaults.

Summary

  • What changed?
  • Why?

Testing

  • How did you test this change?
  • What did you verify manually?

Demo Video

For UI or behavior changes, include a short demo video (GitHub upload, Loom, or other direct link).

  • Video URL or attachment:

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds a Sessions settings area with per-tab shell history and a configurable session restore mode. Tabs keep their own history across reopens, and launch behavior is now controllable.

  • New Features

    • Per-tab shell history (session.persistShellHistory, default true)
      • zsh/bash tabs get a unique HISTFILE via CMUX_SHELL_HISTFILE, keyed by the tab’s surface UUID; applied after user rc with incremental writes for safe persistence.
      • Records a per-tab cmux command history (OSC 133) under Application Support; open via Command Palette → “Show Command History”.
    • Configurable session restore (session.restoreMode: always | ask | never, default ask)
      • ask shows a launch prompt; never starts fresh; always restores silently. Previous sessions stay available via File → Restore Previous Launch.
    • Settings/UI/schema/localization
      • New Settings > Sessions pane; keys under session.* in ~/.config/cmux/cmux.json; schema and en/ja strings added.
  • Migration

    • No action needed; both features default on.
    • Change behavior in Settings > Sessions or edit session.restoreMode / session.persistShellHistory in ~/.config/cmux/cmux.json.

Written for commit 381b9e8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added session restoration modes (always, ask, never) to control startup behavior
    • Added per-tab shell history persistence for bash and zsh
    • Added command history viewer to display executed commands for the current terminal tab
    • Added Sessions settings section with restore mode and history persistence toggles
    • Enhanced shell integration scripts for independent per-tab command tracking
  • Documentation

    • Updated localization strings and configuration schema for new session settings

@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown

@cmolinatnt is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dab60894-01db-4894-b025-c72e652db67c

📥 Commits

Reviewing files that changed from the base of the PR and between 292444b and 381b9e8.

📒 Files selected for processing (34)
  • Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Parsing/OSC133CommandParser.swift
  • Packages/Shared/CmuxAgentChat/Tests/CmuxAgentChatTests/OSC133CommandParserDrainTests.swift
  • Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/SessionCatalogSection.swift
  • Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/SettingCatalog.swift
  • Packages/macOS/CmuxSettings/Sources/CmuxSettings/Values/SessionRestoreMode.swift
  • Packages/macOS/CmuxSettings/Tests/CmuxSettingsTests/SessionSettingsTests.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSectionID.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/SessionSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Values/SessionRestoreMode+Display.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Spawn/ShellHistoryLocator.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Spawn/TerminalSurface+StartupEnvironment.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Spawn/TerminalSurfaceSpawnPolicy.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeSurfaceCreation.swift
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/FakeSpawnPolicyProvider.swift
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/ShellHistoryLocatorTests.swift
  • Resources/Localizable.xcstrings
  • Resources/shell-integration/cmux-bash-integration.bash
  • Resources/shell-integration/cmux-zsh-integration.zsh
  • Sources/AppDelegate.swift
  • Sources/CommandHistoryWindowController.swift
  • Sources/ContentView.swift
  • Sources/Mobile/MobileTerminalByteTee.swift
  • Sources/SessionPersistence.swift
  • Sources/SettingsNavigation.swift
  • Sources/SettingsSearchAliases.swift
  • Sources/TerminalCommandHistoryRecorder.swift
  • Sources/TerminalSurfaceRuntimeWiring.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/SessionPersistenceTests.swift
  • cmuxTests/TerminalCommandHistoryRecorderTests.swift
  • web/data/cmux.schema.json
  • web/messages/en.json
  • web/messages/ja.json

📝 Walkthrough

Walkthrough

Adds configurable session restore behavior (always, ask, never) and per-tab shell history persistence (CMUX_SHELL_HISTFILE) via new session.* settings keys. Introduces TerminalCommandHistoryRecorder, which consumes raw PTY bytes, parses OSC 133 command blocks, and persists history per terminal surface as JSON. A new CommandHistoryWindowController panel displays the history and is accessible from the command palette.

Changes

Session Restore, Shell History, and Command History

Layer / File(s) Summary
OSC133 drain API and tests
Packages/Shared/CmuxAgentChat/.../OSC133CommandParser.swift, Packages/Shared/CmuxAgentChat/Tests/.../OSC133CommandParserDrainTests.swift
Adds takeCompletedBlocks() to drain finished OSC 133 command blocks while keeping the in-progress block for continued streaming. Tests cover completion draining, open-block preservation, ID uniqueness, and empty-parser behavior.
Session settings data model and restore policy
Packages/macOS/CmuxSettings/.../SessionRestoreMode.swift, Packages/macOS/CmuxSettings/.../SessionCatalogSection.swift, Packages/macOS/CmuxSettings/.../SettingCatalog.swift, Sources/SessionPersistence.swift, Packages/macOS/CmuxSettings/Tests/.../SessionSettingsTests.swift, cmuxTests/SessionPersistenceTests.swift, web/data/cmux.schema.json, web/messages/en.json, web/messages/ja.json
Defines SessionRestoreMode enum and SessionCatalogSection with restoreMode/persistShellHistory keys registered in SettingCatalog. Extends SessionRestorePolicy.shouldAttemptRestore to accept and honor a restoreMode parameter. Tests cover defaults, disk parsing, fallback, round-trip, and restore-policy mode gating. Web schema and docs updated.
Per-tab shell history filesystem and spawn environment
Packages/macOS/CmuxTerminal/.../ShellHistoryLocator.swift, Packages/macOS/CmuxTerminal/.../TerminalSurface+StartupEnvironment.swift, Packages/macOS/CmuxTerminal/.../TerminalSurfaceSpawnPolicy.swift, Packages/macOS/CmuxTerminal/.../TerminalSurface+RuntimeSurfaceCreation.swift, Resources/shell-integration/cmux-zsh-integration.zsh, Resources/shell-integration/cmux-bash-integration.bash, Packages/macOS/CmuxTerminal/Tests/.../ShellHistoryLocatorTests.swift, Packages/macOS/CmuxTerminal/Tests/.../FakeSpawnPolicyProvider.swift
Adds ShellHistoryLocator for deterministic per-surface history file URL computation. applyManagedShellHistoryEnvironment sets CMUX_SHELL_HISTFILE for zsh/bash during spawn when persistShellHistory is enabled; shell integration scripts redirect HISTFILE on first prompt. TerminalSurfaceSpawnPolicy gains persistShellHistory. Tests cover URL stability, uniqueness, path layout, env injection, and directory creation.
Command history recorder and PTY wiring
Sources/TerminalCommandHistoryRecorder.swift, Sources/Mobile/MobileTerminalByteTee.swift, Sources/TerminalSurfaceRuntimeWiring.swift, cmuxTests/TerminalCommandHistoryRecorderTests.swift
Introduces TerminalCommandHistoryEntry and TerminalCommandHistoryRecorder: a serial-queue–confined recorder that consumes PTY bytes from the tee callback, parses OSC 133 blocks via takeCompletedBlocks(), enforces per-surface entry limits, and persists atomically to JSON. Surface teardown drops in-memory state. Tests cover exit-code recording, empty-command filtering, cross-session ID monotonicity, and per-tab isolation.
AppDelegate session restore wiring and history recorder enablement
Sources/AppDelegate.swift, Sources/TerminalSurfaceRuntimeWiring.swift
Refactors session restore snapshot loading to be mode-aware via startupRestoreMode, adds a debug test-seam for the confirmation dialog, and gates the snapshot on confirmRestorePreviousSession() for .ask mode. Toggles TerminalCommandHistoryRecorder enablement from session.persistShellHistory at configure time and on config reload. Wires persistShellHistory into the spawn policy.
Session settings UI and navigation
Packages/macOS/CmuxSettingsUI/.../SessionSection.swift, Packages/macOS/CmuxSettingsUI/.../SessionRestoreMode+Display.swift, Packages/macOS/CmuxSettingsUI/.../SettingsSectionID.swift, Packages/macOS/CmuxSettingsUI/.../SettingsWindowScene.swift, Sources/SettingsNavigation.swift, Sources/SettingsSearchAliases.swift
Adds SessionSection SwiftUI view with a restore-mode picker and persist-history toggle, SessionRestoreMode+Display extension for ordered UI cases and localized labels, inserts the section into the settings window stack, and registers the .session entry across navigation, section ID, and search alias surfaces.
Command history panel and command palette entry
Sources/CommandHistoryWindowController.swift, Sources/AppDelegate.swift, Sources/ContentView.swift
Adds CommandHistoryWindowController (singleton NSPanel with SwiftUI hosting), CommandHistoryView (newest-first list with empty state), and CommandHistoryRow (status icon, selectable command text, copy button). showCommandHistoryForFocusedSurface resolves the focused tab and opens the panel. Registers palette.showCommandHistory in the command palette.
Localization and Xcode project wiring
Resources/Localizable.xcstrings, cmux.xcodeproj/project.pbxproj
Adds English and Japanese localized strings for session restore options, dialog text, shell history UI, and command history panel. Registers CommandHistoryWindowController.swift, TerminalCommandHistoryRecorder.swift, and TerminalCommandHistoryRecorderTests.swift in Xcode build phases.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CommandPalette as ContentView / Command Palette
  participant AppDelegate
  participant Recorder as TerminalCommandHistoryRecorder
  participant Panel as CommandHistoryWindowController

  User->>CommandPalette: invoke palette.showCommandHistory
  CommandPalette->>AppDelegate: showCommandHistoryForFocusedSurface(preferredWindow:)
  AppDelegate->>Recorder: load(surfaceID:)
  Recorder-->>AppDelegate: [TerminalCommandHistoryEntry]
  AppDelegate->>Panel: show(entries:)
  Panel-->>User: NSPanel with CommandHistoryView (newest-first list)
Loading
sequenceDiagram
  participant PTY as PTY byte stream
  participant ByteTee as MobileTerminalByteTee
  participant Recorder as TerminalCommandHistoryRecorder
  participant Parser as OSC133CommandParser
  participant Disk as per-surface commands JSON

  PTY->>ByteTee: raw bytes + surfaceID
  ByteTee->>Recorder: append(surfaceID, bytes) [O(1) when disabled]
  Recorder->>Recorder: enqueue on serial queue
  Recorder->>Parser: feed text
  Recorder->>Parser: takeCompletedBlocks()
  Parser-->>Recorder: [TerminalCommandBlock]
  Recorder->>Recorder: filter empty, trim, assign ID, enforce limit
  Recorder->>Disk: persist atomically (best-effort)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • manaflow-ai/cmux#5914: Both PRs modify session-restore startup wiring in Sources/AppDelegate.swift; the retrieved PR changes prepareStartupSessionSnapshotIfNeeded() to load via SessionPersistenceStore.loadStartupSnapshot(), while this PR refactors the same function to be mode-aware with startupRestoreMode.
  • manaflow-ai/cmux#6216: Both PRs modify OSC133CommandParser—the retrieved PR refactors the parser's type and mutating state handling, while this PR adds takeCompletedBlocks() to drain completed blocks—overlapping at the same parser API and state boundaries.

Suggested reviewers

  • lawrencecchen

🐇 A bunny hops through tabs with glee,
Each shell now has its own history!
OSC 133 marks the command's track,
Restore your session, or hold it back.
always, ask, or never—your call,
The rabbit remembers each terminal's haul! 🕐

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@crmolinaz
crmolinaz force-pushed the feat/session-shell-history-restore branch from 404d370 to cee5184 Compare June 21, 2026 15:44
Adds a "Sessions" settings area (session.* in cmux.json, both default-on) with
two capabilities:

- Per-tab shell history (session.persistShellHistory): each terminal tab gets
  its own HISTFILE keyed by the tab's stable surface UUID, injected via the
  existing per-shell startup hooks and applied in the zsh/bash prompt hook
  (after the user's rc, with incremental writes) so up-arrow / Ctrl-R recall
  the tab's commands after a reopen. Also records a per-tab cmux command
  history (OSC 133, via the existing PTY tee) viewable from the command palette
  ("Show Command History"). Keyed by surface UUID — not working directory —
  because a tab's cwd is not stable between first open and restore.

- Configurable session restore (session.restoreMode: always | ask | never,
  default ask): SessionRestorePolicy honors the mode and, in "ask", AppDelegate
  prompts before applying the restore (keeping the snapshot reopenable from
  File > Restore Previous Launch).

Wired through the settings catalog, Settings UI, JSON schema, docs messages,
and Localizable.xcstrings (en + ja). Tests: restore-mode policy, HISTFILE
keying + env injection, OSC 133 drain, command-history round-trip, and config
defaults.
@crmolinaz
crmolinaz force-pushed the feat/session-shell-history-restore branch from cee5184 to 381b9e8 Compare June 21, 2026 15:49
@crmolinaz crmolinaz closed this Jun 21, 2026
@crmolinaz
crmolinaz deleted the feat/session-shell-history-restore branch June 21, 2026 15:51
@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces two session features — per-tab shell history (keyed by surface UUID, injected via the existing PTY tee and shell-integration prompt hooks) and a configurable session restore mode (always / ask / never) — wired through CmuxSettings, the Settings UI, JSON schema, localization, and tests.

  • Per-tab shell history: TerminalCommandHistoryRecorder captures OSC 133 output on the IO thread, parses on a private serial queue, and persists up to 1 000 entries per tab to <AppSupport>/cmux/shell-history/_commands/<uuid>.commands.json. Shell integration hooks in both zsh and bash redirect HISTFILE to a per-surface file after the user's rc runs.
  • Session restore mode: SessionRestorePolicy.shouldAttemptRestore now accepts a restoreMode; .never skips the snapshot, .ask shows a launch-time alert via confirmRestorePreviousSession(), and .always restores silently. The on-disk snapshot is preserved in all modes so File ▸ Restore Previous Launch remains functional.
  • Settings/localization: New Settings > Sessions pane, session.* JSON keys, en + ja entries in Localizable.xcstrings and web/messages/.

Confidence Score: 3/5

Two user-visible correctness issues need fixing before shipping: the feature's own UI and web schema tell users the wrong story about how their history is scoped, and a new debug-named property lands unconditionally in shipping source against the project's explicit seam policy.

The "grouped by project" / "namespaced by project directory" copy appears in three user-facing surfaces (Settings UI subtitle, JSON schema description, and the web messages file) but the implementation keys history by surface UUID — a first-time user who opens a second tab in the same project will find it starts empty, directly contradicting what they read in Settings. Separately, debugRestoreSessionConfirmationHandler is declared unconditionally in production source as a debug-named member, the same pattern the project's seam rule explicitly targets. The main runtime machinery (hot-path gate, serial-queue discipline, shell-integration hooks, policy logic) is solid; the issues are concentrated in copy and a production property declaration.

Sources/AppDelegate.swift (test seam declaration and synchronous disk read on main actor), Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/SessionSection.swift and web/messages/en.json (incorrect "grouped by project" / "namespaced by project directory" copy).

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds session-restore mode wiring, the confirmRestorePreviousSession() modal, and showCommandHistoryForFocusedSurface(). The debugRestoreSessionConfirmationHandler property violates the no-test-seam-in-production-source rule, and showCommandHistoryForFocusedSurface reads disk synchronously from the main actor on each command-palette invocation.
Sources/TerminalCommandHistoryRecorder.swift New recorder that captures OSC 133 output on the IO thread, parses on a private serial queue, and persists per-surface JSON. Hot-path gate and queue discipline are sound; the static load() path is reused on the main actor from showCommandHistoryForFocusedSurface rather than draining the in-memory queue state.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/SessionSection.swift New Settings > Sessions pane. The "grouped by project" subtitle is factually wrong — history is keyed by surface UUID, not project directory — and will mislead users about the feature's actual behavior.
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Spawn/ShellHistoryLocator.swift New pure utility computing per-surface history and commands file URLs under Application Support; dependency-injected for testing. Looks correct.
Sources/SessionPersistence.swift Extended SessionRestorePolicy.shouldAttemptRestore to accept restoreMode; .never short-circuits before loading the snapshot. Logic is clean and backward-compatible.
Resources/shell-integration/cmux-zsh-integration.zsh Injects HISTFILE override in _cmux_precmd after user rc, sets INC_APPEND_HISTORY, and loads prior history with fc -R. Guard variable prevents double-application. Looks correct.
Resources/shell-integration/cmux-bash-integration.bash Injects HISTFILE override in _cmux_prompt_command, enables histappend, loads prior history with history -r, and appends incrementally via history -a on each prompt. Guard variable prevents double-application. Looks correct.
web/messages/en.json Adds session.restoreMode and session.persistShellHistory schema descriptions. The persistShellHistory description says "namespaced by project directory" which is factually incorrect — history is keyed by surface UUID.
Sources/CommandHistoryWindowController.swift New panel window displaying per-tab command history. Follows the ReleasingWindowController singleton pattern; SwiftUI content rebuilt on each show(). Looks correct.
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Spawn/TerminalSurface+StartupEnvironment.swift Adds applyManagedShellHistoryEnvironment to set CMUX_SHELL_HISTFILE in the spawn environment for zsh/bash. Protected key prevents user override. Looks correct.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant AppDelegate
    participant SessionRestorePolicy
    participant SnapshotStore
    participant Alert

    AppDelegate->>SessionRestorePolicy: shouldAttemptRestore(restoreMode:)
    alt "restoreMode == .never"
        SessionRestorePolicy-->>AppDelegate: false (skip)
    else "restoreMode == .always or .ask"
        SessionRestorePolicy-->>AppDelegate: true
        AppDelegate->>SnapshotStore: loadStartupSnapshot()
        SnapshotStore-->>AppDelegate: snapshot
        alt "restoreMode == .ask"
            AppDelegate->>Alert: confirmRestorePreviousSession()
            alt user picks Restore
                Alert-->>AppDelegate: true - apply snapshot
            else user picks Start Fresh
                Alert-->>AppDelegate: "false - startupSessionSnapshot = nil"
            end
        else "restoreMode == .always"
            AppDelegate->>AppDelegate: apply snapshot silently
        end
    end

    Note over AppDelegate: Per-tab history (parallel flow)
    participant PTYTee as PTY Tee (IO thread)
    participant Recorder as TerminalCommandHistoryRecorder
    participant Disk

    PTYTee->>Recorder: append(surfaceID:bytes:) O(1) if disabled
    Recorder->>Recorder: queue.async - ingest / parse OSC 133
    Recorder->>Disk: persist entries JSON (atomic write)
    AppDelegate->>Recorder: load(surfaceID:) on MainActor - disk read
    Recorder-->>AppDelegate: [TerminalCommandHistoryEntry]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant AppDelegate
    participant SessionRestorePolicy
    participant SnapshotStore
    participant Alert

    AppDelegate->>SessionRestorePolicy: shouldAttemptRestore(restoreMode:)
    alt "restoreMode == .never"
        SessionRestorePolicy-->>AppDelegate: false (skip)
    else "restoreMode == .always or .ask"
        SessionRestorePolicy-->>AppDelegate: true
        AppDelegate->>SnapshotStore: loadStartupSnapshot()
        SnapshotStore-->>AppDelegate: snapshot
        alt "restoreMode == .ask"
            AppDelegate->>Alert: confirmRestorePreviousSession()
            alt user picks Restore
                Alert-->>AppDelegate: true - apply snapshot
            else user picks Start Fresh
                Alert-->>AppDelegate: "false - startupSessionSnapshot = nil"
            end
        else "restoreMode == .always"
            AppDelegate->>AppDelegate: apply snapshot silently
        end
    end

    Note over AppDelegate: Per-tab history (parallel flow)
    participant PTYTee as PTY Tee (IO thread)
    participant Recorder as TerminalCommandHistoryRecorder
    participant Disk

    PTYTee->>Recorder: append(surfaceID:bytes:) O(1) if disabled
    Recorder->>Recorder: queue.async - ingest / parse OSC 133
    Recorder->>Disk: persist entries JSON (atomic write)
    AppDelegate->>Recorder: load(surfaceID:) on MainActor - disk read
    Recorder-->>AppDelegate: [TerminalCommandHistoryEntry]
Loading

Reviews (1): Last reviewed commit: "feat(session): per-tab shell history and..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift
Comment on lines +995 to +998
/// Test seam: when set, ``confirmRestorePreviousSession()`` returns this
/// instead of running the modal restore prompt, so launch-restore tests can
/// drive the Restore/Start-Fresh decision without UI.
var debugRestoreSessionConfirmationHandler: (() -> Bool)?

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.

P1 Debug-named test seam in production source

debugRestoreSessionConfirmationHandler is declared unconditionally in Sources/AppDelegate.swift — not guarded by #if DEBUG at the declaration site — and its name matches the debug… pattern the no-test-debug-seam rule forbids. The analogous debugCloseMainWindowConfirmationHandler pre-exists, but this PR introduces a second one. Because AppDelegate is @testable import-able, the preferred fix is to widen the visibility of the relevant state to internal and inspect it from the test target directly, keeping the property out of shipping source.

Rule Used: Flag Swift files under a production Sources path (... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

configurationReview: .json("session.persistShellHistory"),
String(localized: "settings.session.persistShellHistory", defaultValue: "Per-Tab Shell History"),
subtitle: persistShellHistory.current
? String(localized: "settings.session.persistShellHistory.subtitleOn", defaultValue: "Each tab keeps its own command history (up-arrow / Ctrl-R), grouped by project, and records a cmux command history.")

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.

P1 User-facing copy misrepresents the history key

The subtitle says "grouped by project" but the implementation keys history by surface UUID (not by working directory). The PR description explicitly states: "Keyed by surface UUID — not working directory — because a tab's cwd is not stable between first open and restore." A user who opens a second tab in the same project directory will find it starts with empty history, directly contradicting the "grouped by project" wording. The same incorrect description appears in web/messages/en.json:960 ("namespaced by project directory") and in the SessionCatalogSection doc-comment. All three occurrences should be corrected to describe the actual behavior: each tab retains its own history across session restores via its stable surface ID.

Rule Used: Flag production user-facing text that is not fully... (source)

Comment thread web/messages/en.json
},
"session": {
"restoreMode": "When cmux launches with a previous session available: \"always\" restores it silently, \"ask\" prompts before restoring (the default), and \"never\" starts fresh (the previous session stays reopenable via File > Restore Previous Launch).",
"persistShellHistory": "Give each terminal tab its own shell history (up-arrow / Ctrl-R) namespaced by project directory, and record a cmux command history per tab. When off, tabs use your shell's normal global history (e.g. ~/.zsh_history)."

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.

P1 Web schema description misrepresents the history key

This schema description says "namespaced by project directory" but history is actually keyed by surface UUID. See the matching comment on SessionSection.swift:74 — the same incorrect description needs correction here and in SessionCatalogSection.swift's doc-comment.

Rule Used: Flag production user-facing text that is not fully... (source)

Comment thread Sources/AppDelegate.swift
Comment on lines +6522 to +6531
func showCommandHistoryForFocusedSurface(preferredWindow: NSWindow? = nil) {
guard
let target = resolveFocusedNotificationTarget(preferredWindow: preferredWindow),
let surfaceId = target.surfaceId
else {
NSSound.beep()
return
}
let entries = TerminalCommandHistoryRecorder.load(surfaceID: surfaceId)
CommandHistoryWindowController.shared.show(entries: entries)

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.

P2 Synchronous disk read on @MainActor interactive path

TerminalCommandHistoryRecorder.load(surfaceID:) calls Data(contentsOf:url) followed by JSONDecoder().decode synchronously from this @MainActor function, which is triggered by a command-palette action. The recorder already holds the complete in-memory state for open surfaces in statesBySurfaceID on its private serial queue, but that state is not exposed to the main actor — so every palette invocation goes to disk instead. On a slow filesystem or a file that has grown to the 1 000-entry cap the JSON read blocks the main thread. The fix is either to add an async-safe path that drains the in-memory entries off the queue (e.g. queue.sync { self.statesBySurfaceID[id]?.entries } is safe from MainActor in a short-running call), or to make showCommandHistoryForFocusedSurface an async entry-point that awaits the queue.

Rule Used: Flag production Swift that reads, decodes, or scan... (source)

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