feat(web): close a session from its tab, with a two-click guard - #158
Open
defangdevs wants to merge 1 commit into
Open
feat(web): close a session from its tab, with a two-click guard#158defangdevs wants to merge 1 commit into
defangdevs wants to merge 1 commit into
Conversation
Every workspace tab gains a small × posting to the existing /sessions/delete route, so closing a session no longer means a trip to the settings page. Closing kills a live agent and the button sits a few pixels from the session name, so a single click can't do it: the first click arms the button (it becomes a red "Close?" pill and the tab tints red), the second one submits. The armed state gives way to a 4s timeout, Escape, tabbing away, arming another tab, or any click elsewhere, and a double-click is swallowed rather than treated as a confirmation. No confirm() dialog: an inline onsubmit confirm is itself JavaScript, so it guards nobody when scripting is off — the arming replaces it, and the no-JS fallback stays a plain one-click POST. The button is a sibling of the tab link rather than a child: a <form> inside an <a> is invalid markup, and keeping them apart also stops the tab-select click delegation from swallowing the close click. Covered by tests/sessions.nix (server-rendered markup and route) and a new tests/e2e case for all four legs of the guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sxoeoon2SQcbxnaG9tExNY
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.
Motivation
Closing a session meant leaving the workspace for the settings page. This puts a small
×on every tab — but closing kills a live agent, and the button lands a few pixels from the session name, so a plain one-click×would be an accident waiting to happen.What it does
The first click arms the button: it becomes a red
Close?pill and the tab itself tints red, so it is unmistakable which session is at stake. The second click actually closes.The armed state gives way to anything that suggests the user didn't mean it:
×arms and stops;#tab-barwholesale) also drops the armed state — it fails safe.Keyboard works the same way: Enter arms, Enter confirms.
Implementation notes
confirm()dialog. An inlineonsubmitconfirm is itself JavaScript, so it protects nobody when scripting is off — the arming replaces it, and the no-JS fallback stays what it always was for the settings-page delete: a plain one-click POST to the same/sessions/deleteroute.<form>inside an<a>is invalid markup, and keeping them apart also stops the tab-select click delegation from swallowing the close click. It is absolutely positioned over the tab's extra right padding so it still reads as part of the tab.×is always visible (dim until hovered) rather than hover-only — touch has no hover.User-visible and security effects
User-visible: the new
×per tab, and the arm/confirm interaction above. No security effect — it posts to the existing auth-gated, same-origin-checked/sessions/deleteroute with the same payload the settings page sends, and adds no new route, state, or privilege.Checks run
python3 bin/assemble-module.py --check— passes (modules/agent-box.nixregenerated and committed).node --check modules/src/settings.js,ast.parseon the daemon.×through its actual code path to produce the screenshot above.nixVM checks — this flake only exposesx86_64-linuxchecks and the box I worked on isaarch64-linux.tests/sessions.nixgains assertions for the server-rendered close form/button;tests/e2e/root-sessions.spec.tsgains a case covering all four legs of the guard (first click inert, disarm on outside click, double-click swallowed, second click closes) — both need CI or a deployed box.Note for the reviewer
docs/workspace-tabs.pngpredates this change and still shows tabs without the×; worth refreshing from a live box at some point. Separately (pre-existing on master, not touched here):tests/e2e/root-sessions.spec.tsfills#session-editor input[name="name"], but the add form has no name field any more (names are auto-derived), andsettings.js's matchingbody.get("name")is dead — so adding a session no longer focuses its new tab under JS.🤖 Generated with Claude Code
https://claude.ai/code/session_01Sxoeoon2SQcbxnaG9tExNY