feat(runs): rename runs in the UI — user-set title as a pure display overlay (#111) #113

Merged
dominik.polakovics merged 1 commit from afk/111 into main 2026-07-10 18:54:26 +02:00

Lets the operator rename any run — live or finished, manual or AFK — from the chat header. The rename is a pure display overlay: a nullable runs.title column that never touches identity (session name, branch, worktree, tmux — all stay keyed off the label per ADR-0008).

What's in here

Backend

  • Migration pair 0008_run_title.sql (sqlite + postgres): nullable runs.title.
  • store.Run.Title *string, UpdateRunTitle (nil clears to NULL), and Run.DisplayName() — the shared title-or-session-name fallback.
  • PATCH /api/v1/runs/{id} with {"title": "..."}": trims, empty-after-trim or JSON null clears, >120 runes → 400, unknown fields → 400, auth+CSRF via the operator middleware tree; returns the full updated run and publishes run.changed` (repo-scoped, same payload shape as the instance service) so other tabs' rails refetch.
  • Both push builders (done-signal in internal/afk/notify.go, needs-input in internal/chat/notify.go) name runs via DisplayName(), so a renamed run's push matches what the operator sees.

Frontend

  • Run.title + updateRun() in the API client; runDisplayTitle() in instanceLabel.ts implements title || label-title || branch.
  • Chat header: click-to-edit inline rename (pencil on hover, enter/check saves, escape/cancel bails, empty save reverts to the generated title — that IS the reset). When a title is set, the raw label · session identity stays visible as secondary text + tooltip for terminal correlation.
  • Rail and History render the title alone via the new fallback chain — no rename affordance there by design.

Docs

  • CONTEXT.md gains the Title vocabulary entry (under Sessions & worktrees, after Instance).
  • ADR-0040 "run title is a display overlay; identity immutable after spawn". The issue asked for "ADR 0037 (next free number)" — 0037–0039 were already taken, so the next free number, 0040, governs.

Verification

  • web/: eslint, prettier check, vitest 581/581, tsc --noEmit + vite build — all green.
  • Go: gofmt clean, go build -tags ui ./cmd/..., full go test -tags ui ./... green (incl. new store round-trip/NULL-clear, PATCH validation matrix, SSE-payload, and title-overlay notification tests), golangci-lint run ./... 0 issues.

Closes #111

🤖 Generated with Claude Code

https://claude.ai/code/session_013FxDEmjMrvPb5N4NeVXHc2

Lets the operator rename any run — live or finished, manual or AFK — from the chat header. The rename is a **pure display overlay**: a nullable `runs.title` column that never touches identity (session name, branch, worktree, tmux — all stay keyed off the label per ADR-0008). ## What's in here **Backend** - Migration pair `0008_run_title.sql` (sqlite + postgres): nullable `runs.title`. - `store.Run.Title *string`, `UpdateRunTitle` (nil clears to NULL), and `Run.DisplayName()` — the shared title-or-session-name fallback. - `PATCH /api/v1/runs/{id}` with `{"title": "..."}": trims, empty-after-trim or JSON null clears, >120 runes → 400, unknown fields → 400, auth+CSRF via the operator middleware tree; returns the full updated run and publishes `run.changed` (repo-scoped, same payload shape as the instance service) so other tabs' rails refetch. - Both push builders (done-signal in `internal/afk/notify.go`, needs-input in `internal/chat/notify.go`) name runs via `DisplayName()`, so a renamed run's push matches what the operator sees. **Frontend** - `Run.title` + `updateRun()` in the API client; `runDisplayTitle()` in `instanceLabel.ts` implements `title || label-title || branch`. - Chat header: click-to-edit inline rename (pencil on hover, enter/check saves, escape/cancel bails, empty save reverts to the generated title — that IS the reset). When a title is set, the raw `label · session` identity stays visible as secondary text + tooltip for terminal correlation. - Rail and History render the title alone via the new fallback chain — no rename affordance there by design. **Docs** - CONTEXT.md gains the **Title** vocabulary entry (under Sessions & worktrees, after Instance). - **ADR-0040** "run title is a display overlay; identity immutable after spawn". The issue asked for "ADR 0037 (next free number)" — 0037–0039 were already taken, so the next free number, 0040, governs. ## Verification - `web/`: eslint, prettier check, vitest **581/581**, `tsc --noEmit` + vite build — all green. - Go: `gofmt` clean, `go build -tags ui ./cmd/...`, full `go test -tags ui ./...` green (incl. new store round-trip/NULL-clear, PATCH validation matrix, SSE-payload, and title-overlay notification tests), `golangci-lint run ./...` 0 issues. Closes #111 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013FxDEmjMrvPb5N4NeVXHc2
feat(runs): rename runs in the UI — user-set title as a pure display overlay (#111)
All checks were successful
ci / native (pull_request) Successful in 5m3s
bde4077233
A nullable runs.title column overlays the label-derived display name;
identity (session name, branch, worktree, tmux) stays immutable after
spawn. PATCH /api/v1/runs/{id} trims, clears on empty/null, caps at 120
runes, and emits run.changed. Inline rename lives in the chat header
(pencil-on-hover, empty save reverts to the generated title) with the
raw label · session identity as secondary text; rail and History render
the new title || label-title || branch chain; both push builders name
runs via the shared Run.DisplayName() fallback. ADR-0040 (the issue
said 0037, but 0037–0039 were taken) and a CONTEXT.md Title entry pin
the vocabulary.

Closes #111

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FxDEmjMrvPb5N4NeVXHc2
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab!113
No description provided.