Skip to content

th-fe75ca: close a SmoothFlow session out from anywhere, not just a finished card - #604

Merged
brentrager merged 3 commits into
mainfrom
th-fe75ca-session-closeout
Sep 17, 2026
Merged

brentrager merged 3 commits into
mainfrom
th-fe75ca-session-closeout

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Brent: "i still can't like close sessions when i open smooth flow?" — correct, and this fixes it.

flow.close has been frame-complete since th-883ce9, but the only way to reach it was an Inbox card, and only for a session that had already finished. So "close this session" meant "finish it first" — no help for the sessions you actually want to stop. No sidebar menu, no menu-bar item, nothing for a running row.

Reach

Where Applies to
Sidebar row ▸ right-click ▸ Close Out… any session, running included
Session ▸ Close Out… (⌘⌥W) the focused session, running included
Inbox ▸ finished card ▸ Close… unchanged, where it started

A live row closes the same way — the engine kills it before anything else — and the sheet says so before you confirm: "still running — closing kills it first", with the button reading Kill and close.

Detail

The sheet named the pearl and worktree but not what it was about to destroy. It now shows the branch, the uncommitted-file count, and the pearl's title. App-side only — Handoff.Packet already carried branch/dirty and the sheet is loaded before it goes up, so there is no new frame.

No precomputed merged flag, deliberately: the engine reveals merged state at the only moment it matters, by refusing the close with a reason shown verbatim. Force stays post-refusal — never a checkbox you can arm before the reason exists. A refusal on a close started outside the Inbox (sidebar, menu) now gets its own sheet instead of vanishing into the rail.

One safety tightening: hasOwnWorktree now also requires a non-empty project, so a projectless shell row sitting in $HOME is never offered "remove worktree /Users/you".

Tests

SessionClose.decide(session:handoff:) is a pure function over session + handoff producing every toggle, default and line of copy — modelled on PaneClose.decide, tested apart from the AppKit presentation.

  • 95 unit tests green (13 new in SessionCloseTests)
  • 12 XCUITests green, three of them new against mock/server.mjs: the sidebar path on a live session (asserts the kill warning and dirty count are on screen), the Session-menu path on the focused row, and a sidebar-started refusal where Keep it leaves the row exactly where it was.

Docs updated in the same PR: docs/Architecture/SmoothFlow-macOS.md (close-out section + ⌘⌥W in the keyboard table) and docs/Engineering/SmoothFlow-Testing-macOS.md (suite row + the new accessibility identifiers). Changeset included.

Pearl th-fe75ca. Branched off main; #582 rewrites Sources/UI/ and I'll rebase onto whatever keymap it lands, wiring Close Out… through it rather than reverting.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cd506d4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@smooai/smooth Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

brentrager and others added 2 commits September 17, 2026 15:06
…inished card

Close-out existed since th-883ce9 but was reachable ONLY from an Inbox card, and
only for a session that had already finished — so the answer to "close this
session" was "finish it first", which is no answer for the ones you actually
want to stop.

Reach: the sidebar row's context menu (Open / Kill / Close Out…) and Session ▸
Close Out… (⌘⌥W) on the focused row, both for LIVE sessions too — the engine
already kills a running session before closing it, and the sheet now says so
before you confirm ("still running — closing kills it first"), with the button
reading "Kill and close".

Detail: the sheet named the pearl and worktree but not what it was about to
destroy. It now shows the branch, the uncommitted-file count and the pearl's
title. Merged state stays uncached on purpose: the engine reveals it by refusing
the close, and a flag here would be a second source of truth that can disagree at
the instant of the close. Force is still post-refusal only. A refusal on a close
started outside the Inbox gets its own sheet rather than vanishing into the rail.

The decision and every line of its copy are SessionClose.decide(session:handoff:),
a pure function unit-tested apart from the AppKit presentation. hasOwnWorktree
also got stricter: a projectless shell row (worktree = $HOME) is no longer offered
"remove this directory".

Tests: 95 unit (13 new in SessionCloseTests) + 12 XCUITests green, including three
new mock-server tests for the sidebar path on a live session, the Session menu
path, and a sidebar-started refusal leaving the row untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hed-only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brentrager
brentrager force-pushed the th-fe75ca-session-closeout branch from bc12d4e to f4a961b Compare September 17, 2026 19:08
@brentrager
brentrager merged commit 7e3d856 into main Sep 17, 2026
4 checks passed
@brentrager
brentrager deleted the th-fe75ca-session-closeout branch September 17, 2026 19:11
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