session-rename¶
Provenance¶
- Source:
.spec/spexcode/spec-dashboard/dashboard-ui/session-console/session-rename/spec.md - Source SHA-256:
85861a90e32f700c4fc5764e2f20da16515c73fe586be6e5d46e8cc1f96b63dc
session-rename¶
raw source¶
Sessions are labelled automatically — by the spec node they touch, or a few words of their launch
prompt, or their branch. That default is fine until a human needs to fix it: two sessions on the same
node read alike, and a node-agnostic session wears an awkward prompt fragment forever. Right-clicking a
session row should open the session's small action menu: lock on graph makes the board follow it,
rename gives it a human name that sticks, and close offers worktree removal one right-click away —
the destructive twin of the typed /close command ([[session-console]]), distinct from /stop, which only
stops the agent and keeps the worktree.
expanded spec¶
A rename sets a session's name — a user-chosen display override kept distinct from the auto-derived
title, so naming a session never fights or erases the launch-time derivation. The name sits at the
top of the label precedence on every surface (name ▸ node ▸ title ▸ branch ▸ id): once set it wins
over the node a session references, so the human's label is authoritative wherever the session is
named — the top-left window, the [[session-console]] tabs, and the CLI's spex listings — because they
all read that one shared precedence.
The name lives where the rest of a session's record lives: the session's record in the per-user global
store ([[state]]/[[runtime]]), written by the one backend that owns it. So a rename persists — it survives a backend restart and is read
back like any other field, never held only in the browser. A session in any state is renamable
(queued, live, or offline), because the gesture edits the on-disk record, not the live terminal.
The CLI reaches that same write with spex session rename <SEL> "<name>"; inside a launched worker . is the
shared selector for its own session ([[session-selectors]]), so a prompt preset such as [[rename]] can ask the
agent to name itself without learning an id or creating a dashboard-only action.
The gesture is a right-click on a session row in the session board's left-hand session list ([[session-console]]) — the interactive surface where a human manages sessions: a mutation belongs on the board, never on a read-only at-a-glance summary. It opens a cursor-anchored pop-over (its own surface). Picking rename swaps the menu for a centred prompt (the shared modal chrome) that titles itself with the session's headline — the same words its row shows ([[session-activity]]), not the stable rename handle, so the human reads the very label they right-clicked and never renames what looks like a different session — and is prefilled with the current override and ready to type over. Submitting hands the new name to the backend; its successful sessions-domain nudge advances the shared board through graph-stream, so the new label appears on every surface at once rather than only where it was triggered or behind an action-local graph refetch. A failed write may recover through the ordinary board reload path. A blank name is a reset, not an error: it clears the override and the session falls back to its derived label. Renaming an unknown session fails loudly — the endpoint answers 404 — never a silent success.
The menu also carries a reset order item — shown only when the row has been dragged out of birth order — which belongs to the session list's drag-to-reorder gesture ([[session-console]]; it clears that row's manual sort-key), not to this node's rename/close contract; it rides in this pop-over because the same right-click is where a human reaches for it. Archive ([[archive]]) rides the same way, but joins close in the menu's danger group and opens its own confirmation: filing a session out of the active working set is a deliberate lifecycle boundary even though resume remains available. Its confirmation commits through the same archive route; it never becomes a record-only shortcut.
The menu's second item, close, runs the same human-only worktree removal as the typed /close command,
but behind a confirm prompt — a right-click is easy to mis-aim and the removal is destructive, so unlike
the typed command (whose deliberate keystrokes ARE the confirmation) it asks first (the confirm is the shared
modal, its commit button styled as the destructive verb). Like the rename prompt, the confirm titles itself
with the session's headline — the same label its card shows ([[session-activity]]), not the stable rename
handle — so the human reads the very words they right-clicked and never has to map a different name onto the row. Confirming
dismisses the prompt at once and fires the close in the background: worktree + branch removal is
seconds of real work (a git worktree remove plus killing the agent + tmux), and the human must never sit
watching a frozen, disabled dialog wait it out — the same fire-and-forget the New Session launch already uses
([[session-console]]). A successful close invalidates the session graph before its 200 response and pushes
the changed session units to connected boards, so the row leaves every surface when the removal lands even
if the best-effort store/worktree watchers are unavailable; the patrol is recovery, never the normal close
acknowledgement. Cancelling does nothing. The menu carries only the
decisive close, never the soft /stop — stopping-to-resume is a Command Box verb on a live session.
Both lifecycle confirms open with their destructive commit button focused, so a plain Enter confirms the visible archive or close action. Escape, Cancel, and a backdrop click remain cancellation paths; Enter does not weaken the preceding right-click confirmation boundary.
A close refusal is a visible action failure, not a silent background no-op: the backend returns a non-2xx structured error when its ownership guard commits no removal, and the console keeps the selected row while showing that diagnostic once through its shared action-error surface. A refusal never weakens the guard merely to make the row disappear.
An unreadable record exposes one additional, deliberately narrow quarantine item in this same menu, and no healthy row does. Its modal takes the exact adapter/thread/tmux/worktree/branch witness the human recovered from the opaque incident and posts it to the shared record-integrity control. It supplies no guessed lifecycle or cleanup default: the backend independently proves every claimed residue absent (or archives only its exact unowned native thread) before moving the bytes. Success closes the modal and reloads the board so the active corrupt row disappears; refusal keeps the row and routes its precise reason through the existing single action error surface. The public Restore control returns opaque bytes to the active projection without launching a runtime; because a quarantined row no longer belongs to this active list, that recovery is intentionally the CLI/API control rather than a phantom menu item on a missing row.
The right-click confirm consumes both the HTTP status and the JSON {ok,error} body before it asks the board
to reload, so a legacy 200 false response cannot regress into a silent success while the endpoint is being
rolled forward.
Because both the pop-over and its prompt are opened from the board, each must render above it:
a menu or modal that paints behind its own surface is present in the DOM yet invisible and unclickable,
so they live on the top layer — over the board's backdrop, never beneath it. The board also suppresses
the OS context menu everywhere inside it (the terminal-app feel of [[session-console]]) via a native
capture-phase contextmenu listener, and that suppression and this gesture coexist: the same
right-click that kills the browser's menu on a row ALSO opens the rename pop-over (the row's own handler
still fires), so blocking the OS menu never costs the human theirs. Right-clicking the
list's empty space below the rows is simply that block with no pop-over — the OS menu is still suppressed
and the current TUI or Command Box keeps focus, never a stolen-focus gap.
The pop-over is the one home for row-level session actions. Its lock on graph item invokes the console's
existing lock action and routes to #/graph; [[session-console]] owns that lock's no-pending-ops semantics.
The same menu also hosts [[session-multi-select]]'s select item and [[attach-menu]]'s live-only attach item,
so these verbs extend one menu instead of creating parallel gestures or pop-overs. A row with a parent also
gets remove from parent: it dismisses the menu and submits the console's ordinary reparent write with a
null parent, leaving top-level rows free of a disabled or meaningless item. This is the direct counterpart to
the console tree's root drop zone ([[session-nesting]]), not a dashboard-only record edit. It removes only
the child relation's parent watch source; an independent manual observer remains a separate deliberate
relation, never collateral damage from this menu action.
Its surface mounts the shared [[context-menu-chrome]]: compact icon-led text rows, grouped commands, and a separate destructive close row. This node supplies the session actions; it never forks the menu chrome.
This node's slices of the shared files are the rename/confirm-modal styling in styles.css and the
rename route in index.ts; the eval tab's .eval-* styles and its eval-blob endpoint, reworked in
the measure-and-score reframe, are [[spec-eval]]'s churn, not session-rename's drift.