fix: keep tab titles stable across pane focus#186
fix: keep tab titles stable across pane focus#186W25X80 wants to merge 3 commits intosemos-labs:mainfrom
Conversation
Reject empty tab rename payloads across IPC and palette flows. Store explicit tab title flags in a legacy-compatible trailer so older layout readers can still consume the base body without losing structural state.
There was a problem hiding this comment.
Pull request overview
This PR fixes tab renaming so a renamed tab’s label remains stable when focus moves between panes, by storing titles on the tab/layout itself and using pane-derived titles only as fallback (including across session serialization/restore).
Changes:
- Move “tab rename” semantics from per-pane custom titles to per-tab stored titles (
SplitLayout.setTitle/getTitle) with fallback hint titles for unnamed tabs. - Extend the layout codec to persist explicit-vs-hint title semantics via an optional trailer while keeping the legacy body readable.
- Update IPC handlers / query output and overlay/UI code to read/write the new tab-title sources, and add targeted tests.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/overlay/command_palette.zig | Prevents rename submission on empty input; adds tests for rename mode behavior. |
| src/ipc/tab_rename.zig | Adds shared payload parsing for active/targeted tab rename IPC messages. |
| src/ipc/handler_windows.zig | Routes tab rename to SplitLayout titles; updates tab/pane listing title resolution. |
| src/ipc/handler_query.zig | Updates list/tab/split query output to prefer explicit tab titles with fallbacks. |
| src/ipc/handler_cmd.zig | Updates targeted tab rename handler to set tab title and persist session layout. |
| src/ipc/handler.zig | Updates active tab rename handler to set tab title and persist session layout. |
| src/config/cli_ipc_help.zig | Documents that tab rename titles must be non-empty. |
| src/app/ui/win_overlays.zig | Updates command palette rename action to set explicit tab title + persist on Windows. |
| src/app/ui/publish.zig | Updates tab title resolution chain to prefer explicit tab titles, then pane-derived fallbacks. |
| src/app/ui/event_loop_windows.zig | Updates Windows tab title resolution; exposes cleanWindowsTitle for reuse. |
| src/app/ui/command_palette_ui.zig | Updates command palette rename action to set explicit tab title + persist. |
| src/app/tab_manager_test.zig | Adds tests covering explicit title persistence across serialize/restore/move/focus/sync. |
| src/app/tab_manager.zig | Persists explicit/hint titles into layout blobs and restores them into SplitLayout. |
| src/app/split_layout.zig | Adds stored title + hint title buffers and accessors on the tab layout object. |
| src/app/pane.zig | Removes per-pane custom title fields/APIs (tab rename is no longer pane-scoped). |
| src/app/layout_codec.zig | Adds title_flags + optional trailer encoding to preserve explicit-vs-hint semantics. |
| src/app/daemon/session_restore_test.zig | Updates restore tests to assert explicit title flags survive detach/reattach. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -42,6 +56,13 @@ pub const LayoutInfo = struct { | |||
| tabs: [max_tabs]TabLayout = undefined, | |||
| }; | |||
|
|
|||
| fn hasTitleTrailer(info: *const LayoutInfo) bool { | |||
| for (0..info.tab_count) |ti| { | |||
| if (info.tabs[ti].title_flags != 0) return true; | |||
| } | |||
There was a problem hiding this comment.
LayoutInfo.tabs is declared as undefined, but serialize() calls hasTitleTrailer() which reads info.tabs[ti].title_flags for every tab. Any caller that builds var info = LayoutInfo{} and only fills node fields (several tests in this file do) will leave title_flags uninitialized, leading to undefined behavior and nondeterministic emission of the ttl2 trailer. Initialize tabs with a default value (e.g., .{.{}} ** max_tabs) or otherwise guarantee title_flags is zeroed for all tabs before serialize() reads it.
src/ipc/handler_windows.zig
Outdated
| if (idx == 0 or idx > ctx.tab_mgr.count) { | ||
| sendError(cmd, "invalid tab index"); | ||
| return; | ||
| } | ||
| ctx.tab_mgr.closeTab(idx - 1); |
There was a problem hiding this comment.
handleTabCloseTargeted interprets the payload index as 1-based (idx == 0 invalid, closeTab(idx - 1)), but the IPC protocol/client and the POSIX handler treat tab_close_targeted as 0-based (client sends n-1, and handler_cmd.handleTabCloseTargeted closes closeTab(ti)). On Windows this makes attyx tab close N close the wrong tab (or reject tab 1 as idx=0). Update this handler to use 0-based indexing to match the protocol and other platforms.
| if (idx == 0 or idx > ctx.tab_mgr.count) { | |
| sendError(cmd, "invalid tab index"); | |
| return; | |
| } | |
| ctx.tab_mgr.closeTab(idx - 1); | |
| // `idx` is 0-based (matches IPC protocol). Validate bounds accordingly. | |
| if (idx >= ctx.tab_mgr.count) { | |
| sendError(cmd, "invalid tab index"); | |
| return; | |
| } | |
| ctx.tab_mgr.closeTab(idx); |
tab renamecurrently behaves like renaming the focused pane, so a renamed tab can change its label when focus moves between panes inside the same tab.This stores explicit titles on the tab itself and keeps pane-derived titles only as fallback for unnamed tabs. As a result, renamed tabs stay stable across pane focus changes, tab moves, and session restore.
Use case: rename a split tab to
editor, then switch focus betweenvim, shell, and logs panes inside that tab. The tab should keep showingeditorinstead of following the currently focused pane title.