Add Window Projects panel for archiving and restoring terminal windows#620
Draft
YSaxon wants to merge 61 commits into
Draft
Add Window Projects panel for archiving and restoring terminal windows#620YSaxon wants to merge 61 commits into
YSaxon wants to merge 61 commits into
Conversation
Introduces a side-by-side panel (Window > Window Projects…) that lets
users organise iTerm2 windows into arbitrarily-nested projects:
• iTermWindowProjectsModel – data layer (projects, sub-projects, archived
windows). Persisted as JSON in ~/Library/Application Support/iTerm2/
WindowProjects.json.
• iTermProjectsPanelController – floating panel with an NSSplitView:
- Left: NSOutlineView showing the project tree. Each project can hold
sub-projects and archived (closed) windows. Hover preview shows an
ArrangementPreviewView for any archived window.
- Right: NSTableView listing every currently-open window. Hover preview
captures a live screenshot via CGWindowListCreateImage.
Actions: create/rename/delete projects & sub-projects; archive a
selected open window to a selected project (optionally closing it);
restore individual archived windows or all windows in a project.
Right-click context menus on both panes.
• Window > Window Projects… menu item wired to iTermApplicationDelegate.
https://claude.ai/code/session_011nQdWSvNudhnXQN7vu16YW
- Add Window > Window Projects… menu item (awakeFromNib, programmatic) - Add -showWindowProjectsPanel: IBAction to iTermApplicationDelegate - Register both new Swift files in iTerm2SharedARC target https://claude.ai/code/session_011nQdWSvNudhnXQN7vu16YW
YSaxon
marked this pull request as draft
March 17, 2026 21:03
- iTermWindowProject gains a lastUsed: Date field (backward-compatible;
defaults to distantPast on decode so older JSON sorts to the bottom)
- archiveWindow and restoreWindow/restoreAllWindows update lastUsed
- Projects pane header now has a two-segment control ("Name" / "Recent")
that re-sorts root and sub-project lists in the outline view on the fly;
"Recent" is the default
https://claude.ai/code/session_011nQdWSvNudhnXQN7vu16YW
Model (iTermWindowProjectsModel): - Add runtime liveAssociations ([windowNumber → projectID]) - associateWindow/disassociateWindow: mark open windows as belonging to a project without closing them - Auto-archive on close: NSWindow.willCloseNotification observer saves the window arrangement to its associated project when the window closes - closeProject: archive + close all live windows for a project - archivedWindow(id:) and project(id:) lookup helpers Panel UI (iTermProjectsPanelController): Left pane: now shows both live (bold, terminal.fill icon) and archived (grey, terminal icon) windows as leaves under each project. Double-click an open window → bring to front; double-click archived → restore. New "Close All" button + context menu item closes the selected project. Right pane: converted from NSTableView to NSOutlineView grouped by project. Each project that has open windows appears as an expandable group header. Unassociated open windows appear in an "Unassociated" section at the bottom. Bottom bar "Associate with Project" button (replaces old "Archive to Project") records the live association without closing the window. Drag-and-drop (all 6 semantic paths): right window → left project archive + close right window → right group reassign association (keep open) right window → Unassociated/root disassociate (keep open) right group → left pane close-all for that project left archived → right pane restore, remove archive entry left project → right pane restore all windows in project https://claude.ai/code/session_011nQdWSvNudhnXQN7vu16YW
Owner
|
This seems like a useful idea - basically a way of organizing arrangements. I think it would be better to start with a spec; the code as written is pretty far from being mergable. Do you have a vision for where this could go? It feels like a bigger idea is trapped inside waiting to get out. |
…ed list to prevent duplicate restores
…t thumbnails on cold storage hover preview
…screenshot previews
…associate or move them between projects
… cold storage archiving via the Option key
…ing application shutdown, and add complete unit tests
…GraphEncodingTests.swift to allow building and running unit tests successfully
…ems in Window Projects UI
…nsible global fallbacks when nothing is selected
…re in-memory singleton state
…to resolve framework compilation issue
…ree using live process checking and custom styling
…ationWillTerminate save
…metadata, and fix Association button responder targets
…ific development app path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gnostics The Freeze All button routes through closeProject, a duplicate freeze path that still closed the fd (closeFileDescriptorAndDeregisterIfPossible) instead of parking — so the parking fix in archiveWindow never ran for the main UI path, and the fd was closed (killing in-process reattach). Centralize into parkSessionsForReattachment and call it from both archiveWindow and closeProject. Also fix the thaw/pathfinder diagnostics: getForSocketNumber's callback delivers an iTermResult<iTermMultiServerConnection>, not the connection directly, so the unattachedChildren probe always read -1. Unwrap the result. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Across a quit/relaunch, the startup orphan-server adopter swept detached windows' processes into a generic recovered window (since project archives aren't part of macOS window restoration), consuming the child from unattachedChildren so a later project restore fell back to a fresh shell + left a stray tab. Add a claimedChildPIDsProvider hook on iTermOrphanServerAdopter; the app delegate wires it to iTermWindowProjectsModel.claimedMultiserverChildPIDs (all Child PIDs across archived arrangements). The adopter leaves claimed children parked in unattachedChildren so on-demand project restore re-attaches them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…open windows on quit Re-key liveAssociations from window-number to the stable PseudoTerminal terminalGuid and persist it (separate WindowProjectAssociations.json, projects JSON untouched). An open associated window brought back by native window restoration after quit/crash now re-joins its project automatically — the restored window keeps its guid, so project(for:) resolves it with no fragile reconcile step. applicationWillTerminate no longer archives open windows (that created a live restored window + a stale archive duplicate); it just sets isTerminating so the willClose notifications fired during teardown are ignored (preserving associations). windowWillClose still archives a user-initiated close (the closed state), keyed by guid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s helper Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…parsing Add coverage for the recently-built data layer (DesignNotes §8 headless list): - serverDict(in:) / allServerChildPIDs(in:) extraction, incl. nested split-pane Subviews and NSNumber-valued plists - claimedMultiserverChildPIDs() over a nested project tree - totalWindowCount recursion - guid→project association persistence round-trip (save/load), multi-entry, and dangling-association tolerance Remove testSocketAdoptionExplorer: an exploratory probe that asserted a live multiserver daemon connection to a hardcoded socket — unrunnable headlessly (hangs 5s then fails) and copy-paste-buggy. Also fix DesignNotes gnachman#10 (empty 42-byte arrangement bug): add isArchivable() guard and wire it into archiveWindow/closeProject/windowWillClose so an empty capture never produces an unrestorable archive entry (window stays open instead), with a unit test for the guard. Add test-only accessors (testOnlyAssociations, testOnlyReloadAssociationsFromDisk, testOnlyIsTerminating) mirroring the existing testOnlySetRootProjects pattern; setUp/tearDown now back up and restore associations for inter-test isolation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ver occurs Not yet convinced DesignNotes gnachman#10 (empty ~42-byte capture) is a real current bug vs. residue of an older build. Replace the terse guard log with a temporary dev reporter that, if it ever fires, dumps the arrangement bytes/keys, window title/number, live session count, and a 25-frame call stack to /tmp/iterm_wp.log and pops a warning dialog so we can catch it in the act. Clearly marked STRIP BEFORE SHIPPING — remove together with the isArchivable guard wiring once the question is settled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…multi-select GUI session for the Window Projects panel: - Drop-overlay drag model: drag origin + payload decide the action via labeled drop zones. Associated open window(s) / project group dragged right→left show an Archive (75%) | Detach (25%) overlay; saved window(s) / project dragged left→right show a Restore overlay. Unassociated windows still associate by dropping on a project row. New iTermProjectsDropOverlay drives zones off the live NSDraggingInfo. - Naming: open/detached/closed vocabulary. Freeze→Detach, Close&Archive→Archive across buttons/menus; project rows read (N open, M detached, Q closed); saved leaf rows read Detached/Closed. - Multi-select on both panes; actions and context menus apply to the selection. - Left pane: single Restore button with a count label (Restore / Restore N / Restore All… + confirm when nothing selected). Disabled buttons now dim obviously. Drops always target the whole project (no between-rows lines). - Right pane: removed Pathfinder/Manual-Adopt; added Archive/Detach buttons. The Unassociated group is always present and pinned on top and is the only disassociate drop target (no accidental disassociation in empty space). - Previews are pinned to their own side (left→left, right→right) so they never cover the other pane, with an arrow pointing back at the hovered row; saved thumbnails render at 2× for crispness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
So the cursor shows the same redirect-arrow badge as the right pane when dragging a window/archive onto a project. Cosmetic only — the drop behavior (associate / move between projects) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r Server Dict isOrphanedAndRunning only checked the monoserver "Server PID" key, which doesn't exist in multiserver arrangements, so every saved window classified as closed. Now also probe Server Dict→Child PID (via allServerChildPIDs) with kill(pid,0), keeping the monoserver path as a fallback. This is what lets the UI distinguish detached from closed windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Route WindowProjects.json, associations, and thumbnails through iTerm's suite-aware applicationSupportDirectory (was hardcoding .../iTerm2/), so launching with `-suite <name>` isolates Window Projects storage along with the prefs and daemon socket it already isolated. With no -suite the path is byte-identical to before, so prod is unchanged and no migration is needed. This lets manual/E2E test runs (-suite test) avoid clobbering the Window Projects accumulated from real dogfooding, and keeps a test instance's parked/ reattached processes on a separate daemon from real running shells. Adds tools/claude_script_0003_dogfood_suite.sh to launch an isolated test instance (optionally seeding its prefs from prod). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closing (red button / Cmd-W) a window associated with a project now prompts with Save & Detach / Save & Close / Remove from Project / Cancel instead of silently archiving. iTermWarning's "Remember my choice" persists the selection (Cancel is never remembered, so a window can't become unclosable). The dialog hooks PseudoTerminal.windowShouldClose: — the cancelable point — since the model's willClose observer fires after the close is committed. The chosen action runs on the next runloop tick to avoid re-entering close from within windowShouldClose:, and removes the association first so the willClose fallback no-ops (no double archive). That fallback still handles non-user closes (last session exits, programmatic close), where a prompt would be wrong. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gemini added spurious/mismatched nullable annotations while building Window Projects; these stayed dormant as warnings until origin/master's -Werror flip broke the PCH build. Revert the spurious iTermController.h nullable (impl never returns nil), wrap iTermOrphanServerAdopter.h in NS_ASSUME_NONNULL to support our claimedChildPIDsProvider addition, and drop now-redundant optional-handling in iTermWindowProjectsModel.swift now that the real types are correctly non-optional.
These declarations predate Window Projects and aren't ours to redesign. The real trigger isn't -Werror alone: this branch newly imports iTermMultiServerConnection.h and iTermOrphanServerAdopter.h into iTerm2SharedARC-Bridging-Header.h so Swift can call them directly. SwiftGeneratePch enforces a much stricter nullability-completeness audit on headers exposed to Swift than plain ObjC compilation ever did, so latent partial-audit gaps that were always harmless there (argv/ newEnviron char**, a fully-unaudited file) became hard errors the moment these files got bridged. Annotate in place to match actual behavior; revert in favor of an upstream fix if one lands.
ModernTests is a PBXFileSystemSynchronizedRootGroup, so files physically under ModernTests/ are auto-discovered — no manual PBXFileReference/ PBXBuildFile/Sources-phase entry needed. tools/add_file_to_xcodeproj.rb used the old manual-add style when this file was added, creating a duplicate reference resolved against the project root instead of ModernTests/, which failed with "Build input file cannot be found."
Author
|
Funny you should close it now. I'm actually getting much closer to something stable and coherent. I was planning to wait to ping you about my more recent work on it until I had it ready - it needs bug fixes and efficiency refactors, but I think it's actually going in a positive direction |
Owner
|
I do think that a spec would be good to create - this is a pretty big change and it needs to be thought through holisitically. Reopening it for now |
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.
Summary
Introduces a new "Window Projects" panel UI that allows users to organize, archive, and restore iTerm2 terminal windows into named project hierarchies. This feature enables saving window arrangements for later restoration while keeping projects organized in a tree structure.
Key Changes
New Panel UI (
iTermProjectsPanelController.swift):Data Model (
iTermWindowProjectsModel.swift):iTermWindowProject: Hierarchical project nodes supporting nested sub-projectsiTermArchivedWindow: Stores window metadata and binary-encoded arrangement dataProject Management Features:
Window Archiving:
UI Polish:
Integration:
iTermApplicationDelegate.mImplementation Details
CGWindowListCreateImagefor live thumbnailsArrangementPreviewViewcomponenthttps://claude.ai/code/session_011nQdWSvNudhnXQN7vu16YW