feat(inspector): per-session right-side layout (#160) - #161
Merged
Conversation
The inspector tab and open detail (file / diff / trace / PR-issue) plus its maximize / list-collapse chrome were global singletons on TermioStore, force- cleared on every session switch. Each terminal tab now keeps its own right-side context in an in-memory `[Session.ID: InspectorState]`: the outgoing session's layout is saved and the incoming one restored, replacing the blanket overlay clear in TerminalPane. Because the maximize host is no longer torn down to nothing on every switch, the fullscreen blank-screen race goes away too. Persistence rides state.json (no SQLite): only the durable subset — the tab and the open file (path / line / read-only) — is written, since a diff / PR / trace is a snapshot of data that gets re-fetched. On restore the file path is validated and silently falls back to no detail if it's gone. Width and open/ closed stay global (they belong to the AppKit split item); it's the content that is session-specific — matching VS Code's sidebar-viewlet ⊥ editor-area model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…sist Three regressions the per-session inspector work introduced (PR #161 review): 1. Sidebar tab clicks cleared `openFileURL` before changing the selection, so the didSet captured an already-emptied layout and the outgoing session's open file was lost on switch-away. Only clear when re-tapping the already-selected row. 2. Durable inspector edits (open a file, switch the tab) don't move `selectedSessionID`, so nothing persisted them — opening a file and quitting without switching lost it. Added a debounced whole-state save funnelled through `refreshDetailPresentation`. 3. Launch restore clobbered a session's seeded layout: the programmatic selection change fired the didSet, which captured the still-default live inspector over the just-seeded state. Guard restore with `isRestoringInspector`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses the two deeper findings from the PR #161 review: 4. `issuesModel` was a single global slot, so restoring a session's saved issue detail could render it against another repo's model (wrong-repo fetch). It's now a `[repoRoot: IssuesPanelModel]` cache; `issuesModel` resolves to the selected session's repo (keyed on `inspectorProjectPath`, the exact string `IssuesView` is built with), falling back to the list when none has loaded. 5. The fullscreen blank-on-tab-switch is the tickless surface sitting on a stale frame after the maximized-detail host is removed. Nudge the selected surface with a short render pump when the host tears down (`repaintSelectedSurface`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
Addressed codex review (5 findings)Regressions fixed (commit
Deeper findings fixed (commit
Confirmed-sound by the review and left as-is: issue+diff restore order, UUID keying, missing-file fallback, old-JSON decode, O(1) switch hot-path. |
…ack window The issue detail's WKWebView was the one branch of `conversationBody` without a fill frame (its error/progress siblings both have `maxWidth/maxHeight: .infinity`). Without it, SwiftUI could size the representable from the web view's intrinsic — near-zero while the HTML is mid-load — collapsing the detail to a sliver, and the empty area paints the window background as black. Most visible across a minimize/restore relayout. Also harden the restore path: `windowDidDeminiaturize` clamps a frame that came back under the content minimum (setFrame ignores contentMinSize), forces a relayout, and nudges the tickless terminal surface to repaint — same surface- won't-repaint-itself family as the maximize-teardown fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #160.
What
The inspector's right-side state — which tab, which open detail (file / diff / trace / PR-issue), and the detail's maximize / list-collapse chrome — was a set of global singletons on
TermioStore, force-cleared on every session switch. It's now per-session: each terminal tab keeps its own right-side context.TermioStore.InspectorState+inspectorStates: [Session.ID: InspectorState](sits alongsideruntimes/surfaces).selectedSessionID's didSet saves the outgoing session's layout and restores the incoming one's, replacing the blanket overlay-clear that lived inTerminalPane.syncRuntimes.Fixes the fullscreen blank-screen bug
Maximizing a detail then switching terminal tabs used to tear the full-window maximize host down to nothing (global
inspectorMaximized+ clear-on-switch racing the surface re-show), leaving a blank window. Restoring the target session's real state instead of clearing removes the race.Persistence — state.json, no SQLite
Only the durable subset is written to
state.json: the tab + the open file (path / line / read-only). A diff / PR / trace is a live snapshot (siblings, PR status, transcript) that gets re-fetched, so those stay in-memory only — matching VS Code's hot exit (restore open files, not transient views). On restore the file path is validated withFileManager; a deleted file / removed worktree silently falls back to no detail, never an error overlay.Scope decision (deliberate)
Content is per-session; width + open/closed stay global (they belong to the AppKit
NSSplitViewItemautosave). This matches VS Code / JetBrains, where the sidebar viewlet and the editor area are independent — opening a file doesn't collapse the sidebar. Width-per-session would mean overriding autosave + re-driving the divider on every switch (jank-prone, low value), so it's intentionally left out.Performance (100-tab target)
Switch hot-path is O(1):
captureInspectorState/applyInspectorStatetouch a fixed set of fields, independent of tab count. Memory is O(N) tiny structs (mostly nil fields). The only O(N) work is building the persisted-layout dict insidepersist(), dwarfed by the pre-existingprojectsencode. No new scaling cliff. (Pre-existing note:persist()writes synchronously on the selection-change path — a candidate for debouncing if 100-tab rapid switching needs it, independent of this change.)Test
swift buildclean🤖 Generated with Claude Code