Repository navigation
Close safety: ask before closing windows and real workspaces; no silent fallbacks - #469
Conversation
Deep review — #469 "Close safety: ask before closing windows and real workspaces; no silent fallbacks"Thank you for this — it's a genuinely careful safety pass, and the write-up (per-path validation, the explicit "Behavior notes", the clean split between confirmed and pre-confirmed close paths) made it a pleasure to review. This is a re-review after your rebase onto #467: I re-traced every close path and the ref-rejection machinery against the trusted base, and the earlier round's two nits are both addressed (the AlignmentSquarely in scope. Socket/CLI targeting correctness plus operator-safety UX for the operator:agent pair — the same "no silent fallback stomps a peer's target" family #456/#458 established. No tenant-config writes, no display-link/draw-loop, no gratuitous upstream divergence, and the What I verified (holds up)
FindingsNo blockers, no majors. Three small items: 1. (nit) Rejection message says "destructive commands" on now-non-destructive verbs. The ref check is intentionally applied to every 2. (nit / heads-up) Switch-level reject calls don't 3. (nit, addressed-in-spirit) SwiftUI-delegate-swap fragility. The guard covers Process notes (not blocking)
RecommendationMerge after fixes — and "fixes" here is really just nit 1's wording and (optionally) nit 2's clarifying comment; nits 2 and 3 are both safe as-is. The core close-safety machinery is sound, the strict-targeting rejection is complete and consistent across all the destructive verbs plus the folded-in action verbs, the last-window snapshot path is coherent with #467, and the behavioral tests are the right kind. Land it once CI is green and the skill copy is synced. 🌉 Drawbridge deep review (lane 3) — an automated pass; a maintainer has been notified and makes the final call. 🌉 Drawbridge deep review (lane 3) — an automated pass; a maintainer has been notified and makes the final call. |
…nt fallbacks - Main windows ask before closing on every operator path: the red close button, File > Close All and Cmd+Ctrl+W. A guard delegate in front of each main window's own delegate answers windowShouldClose; c11's own closes (a confirmed workspace close, a shell exiting in the last pane, socket and AppleScript closes, app quit) go through closeMainWindowWithoutPrompt. The prompt is a sheet with Cancel as the default. - Closing the last window no longer deletes the session snapshot, so the next launch can still offer those workspaces back. - Closing a workspace asks unless it is a single idle terminal: pinned workspaces, several surfaces, browser or markdown surfaces and anything running all ask. Cmd+W on a last tab and the last-workspace window close go through the same rule. - Destructive confirmations never accept a reflex: pane confirm cards and the legacy close alerts default to Cancel, and Cmd+D (Split Right) no longer accepts any close. Cmd+D still submits text-input cards. - Socket: workspace.action close_others is removed. The destructive verbs (surface/workspace/window close, surface/tab/workspace actions, clear_history) reject an empty ref or one that no longer resolves instead of falling back to the focused target, and the CLI refuses an explicitly empty target flag on those commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tive-only defaults - The window sheet's Close runs after the sheet detaches, so performClose is not refused. - As the last window closes, its full session is written synchronously with conversations suspended, and a later quit with no windows promotes the shutdown to clean instead of leaving it dirty. - The close guard is dropped when a window unregisters, whatever its delegate is by then. - Only destructive confirm cards start on Cancel and refuse Cmd+D; a standard agent question keeps its confirm default. - CloseWindowConfirmDialogUITests: Return cancels, clicking Close closes, and the helpers find the sheet. Dead Cmd+D accept path removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rebased onto #467 (session history). Resolved two conflicts in AppDelegate: both clean-shutdown branches kept (quit with the resume picker open; quit with no windows), and the last-window save is skipped when the window closing is the unanswered resume picker's, so #467's guarantee holds. Drawbridge nit 1 addressed with a must-stay-last comment; nit 2 is intentional (a lone idle workspace closes without asking, and its window cascade follows). Re-validated on a tagged build: Close All prompts, stale refs rejected, close-others gone, Close on the window sheet closes, zero-window quit leaves the sentinel clean. |
0b1c6f4 to
b766386
Compare
Carries #469's destructive-target guards onto the new vocabulary: tab.close and tab.action reject unresolved refs, rejectEmptyTargetFlags checks the --area/--tab alias groups, and the guard's error text names canonical keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…el (#489) * Close confirmations: one tab defaults to Close, more than one to Cancel Since close safety (#469) every destructive close card started on Cancel, so closing a single tab took an arrow key or a click. That is the most common close, and the extra step costs more than it saves. Now Return closes whenever the close takes one tab: - Close tab? (pane card, Ghostty's runtime close, and the legacy alert) - closing an area that holds one tab - Close other tabs when it closes one tab - Close workspace? for an unpinned workspace holding one tab (Cmd+W on a last tab goes through this) - Close window? for a window holding one such workspace Anything that closes more than one tab, and a pinned workspace, still starts on Cancel. Cmd+D still never accepts a destructive card. ConfirmContent gains defaultSelection (nil keeps the role-derived default), and the workspace-close runtime honours it on present. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Close confirmations: keep the change to c11's own cards The NSAlert paths (Close window?, Close other tabs, the legacy close-tab fallback) keep their existing Cancel default; AppKit re-assigns alert key equivalents at present time, so the default belongs on the cards only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Close confirmations: one unit test for the single-tab default Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Atin <benevolent.futures@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* C11-250: guard window close and confirm selection safety * C11-250: exercise real window close notification order * C11-250: assert close guard registry lifecycle * C11-250: exercise close guard teardown ordering * WIP: parked for rollover * C11-250: cover the confirm card's missing-selection fallback with a rendered-card test Adds a hosted-card pixel test (missing selection renders the same as Cancel selected and differs from Confirm selected) and a Return-routing test for an active confirm with a missing selection, plus a DEBUG-only seam to clear the published selection. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * C11-250: classify the rendered confirm card by pixel distance, not byte equality Whole-image byte equality differed in a few antialiased pixels between runs. The test now checks the missing-selection render is closer to the Cancel-selected reference than to the Confirm-selected one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Atin <benevolent.futures@gmail.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Why
Follow-up to #456 and #458. Atin approved audit items 1 to 4 plus removing the socket
close_others. Overlapping findings from surface:180's deeper audit are folded in (last-window session deletion, stale refs on destructive commands, close_others, Return-confirms defaults).What
windowShouldClose; c11's own closes (confirmed workspace close, last shell exiting, socket and AppleScript closes, quit) usecloseMainWindowWithoutPrompt. The prompt is a sheet; Cancel is the default.pane.confirmquestions keep their confirm default). Cmd+D (Split Right) no longer accepts any close; it still submits text-input cards.workspace.action close_othersremoved. Destructive verbs (surface/workspace/window close, surface/tab/workspace actions, clear_history) reject an empty or unresolvable ref instead of falling back to the focused target. The CLI refuses an explicitly empty--surface/--workspace/...on those commands. Skillapi.mdupdated.Validation (tagged build, AppleScript/System Events + tagged CLI)
.clean; normal quit →.clean. File > Close All → sheet (before the guard delegate it closed silently).close-windowcloses with no prompt; afterwards the session file is still on disk with the window's layout.surface:9999/workspace:99on close-surface, workspace-action, tab-action, clear-history →not_found, nothing closed. Empty--surface ''/--workspace ''→ CLI error.workspace-action close-others→ unknown action.Behavior notes (CHANGELOG material)
C11_WORKSPACE_IDpoints at a closed workspace now getsnot_foundfrom destructive commands instead of acting on the selected workspace.workspace.action/tab.action/surface.action, including rename and pin, and treats a non-string ref asempty_ref.Fresh-context review: nothing blocking; its fixes are in the second commit.
🤖 Generated with Claude Code