[Design] Workspace renaming - #359
Conversation
Expose the existing server-side renameWorkspace() over the wire and wire
an inline rename into the workspace row's More menu.
- contracts: add workspace.rename RPC ({ id, name } -> Workspace) to
WS_METHODS + WsMethodMap; documented in contracts SPEC.
- server: add "workspace.rename" handler calling renameWorkspace(id, name,
{ lock: true }); renameWorkspace self-emits workspace.updated, so
convergence stays on that push (no extra broadcast, no new domain logic).
- web: add a fire-and-converge renameWorkspace client action (no client
optimism) and a Rename menu item (worktree-only). The name becomes a
bare, chrome-less input in place — same typography token, color, and row
metrics, no border/background/ring/padding. Prefilled + selected on entry;
blur/Enter save, Escape cancels, empty/whitespace or unchanged sends
nothing and restores the server-owned name.
- e2e: inline rename persists via workspace.rename; empty restores; Default
row hides Rename.
The reused renameWorkspace() always derived + git-branch-m'd the branch
from the new display name, so an inline user rename silently renamed (and,
when the slug matched the name, visually dropped) the user's git branch —
"the branch name is lost". The display name and branch are decoupled
(Workspace.name is display-only), so editing the label must not rewrite the
branch.
- workspaces: add renameWorkspace opts.renameBranch (default true, so both
auto-rename passes are unchanged). When false: set the display name only,
keep the branch, and skip sibling diff-base re-pointing (no ref moved).
- server: workspace.rename handler now passes { lock: true, renameBranch:
false } — user rename edits the label, keeps the branch.
- tests: unit test for the display-only path; e2e now asserts the branch is
preserved and surfaces on the row's second line after rename.
| workspaceRemove: "workspace.remove", | ||
| // Deliberate user rename of a worktree workspace — sets the display name only (the git branch is kept: | ||
| // name and branch are decoupled) and locks it (`renamed: true`). Broadcasts `workspace.updated`. | ||
| workspaceRename: "workspace.rename", |
There was a problem hiding this comment.
Blocking:
Problem: This adds a wire method without advancing PROTOCOL_VERSION from the base's already-shipped value 54, violating the versioned-wire contract.
Failure scenario: An independently shipped client containing this action connects to an older v54 host, sees the same advertised version, then every rename fails because that host has no workspace.rename handler.
Suggested fix: Bump PROTOCOL_VERSION for this protocol surface change and update the corresponding protocol-version expectations.
| .catch((err) => toast.error(errorText(err, "Failed to reveal workspace"))); | ||
| }; | ||
|
|
||
| // Event-driven like remove/reveal: fire the RPC and let the host's `workspace.updated` push drive |
There was a problem hiding this comment.
Blocking:
Problem: This diff reintroduces extensive narrative code comments despite the explicit near-zero-comments invariant; this one is already stale because it says rename derives the branch while this RPC deliberately preserves it.
Failure scenario: A maintainer follows the call-site explanation and changes client behavior under the false assumption that a user rename also changes the branch, drifting from the display-only contract recorded in the specs.
Suggested fix: Remove the new narrative comments from implementation and tests, keeping the rationale and behavior contract in the updated owning SPEC.md files and executable assertions.
|
Thanks Julia Shilova (@juliashilovaa) |
Summary
Workspace naming is currently unreliable: even when the agent is explicitly asked to rename a
workspace — including when the desired name is provided in the initial task prompt — it often doesn't
update it correctly.
Clear workspace names are important for keeping parallel tasks organized and easy to scan, especially
as the number of workspaces grows. Renaming is also an expected interaction in modern editors, so it
should be simple and dependable.
This PR makes renaming dependable by giving the user a first-class, direct way to rename a workspace —
an inline rename in the workspace row — backed by a real server capability, instead of relying on
the agent to name it for you.
Demo
Inline rename in the workspace row: open the ⋮ menu → Rename, edit the name in place (no dialog), then click away to save. The git branch is kept and — now differing from the display name — surfaces on the row's second line.
Changes
Wire (
packages/contracts) — new RPCworkspace.rename{ id, name } → Workspace, added toWS_METHODS+WsMethodMap. Echoes the updatedWorkspaceand broadcastsworkspace.updated, thesame convergence contract as
workspace.setDiffBase.Server (
packages/server) — aworkspace.renamehandler that reuses the existingrenameWorkspace()(no rename logic duplicated). It passes a new optionrenameBranch: false:branch are decoupled (
Workspace.nameis display-only), so editing the label must nevergit branch -mthe user's — possibly pushed — branch out from under them.renameBranchdefaults totrue), so a pristineworkspace's branch still tracks its derived name.
renameWorkspaceself-emitsworkspace.updated; the handler adds no extra push. It still throws foran unknown id, a
default/externalkind, or an empty name.Web (
apps/web) — aRenameaction in the workspace row's More menu (worktree-only; hidden fordefault/external, which the server refuses). Selecting it edits the name inline in the row, not in a
dialog: the label becomes a bare, chrome-less
<input>in the same slot — same typography token,color, and row metrics, no border/background/focus-ring/padding. It's seeded with the current name and
selected on entry; blur (click outside) or Enter saves, Escape cancels, and an empty/whitespace or
unchanged value sends nothing. There is no client-side optimism — the row converges on the host's
workspace.updatedpush (updateWorkspace), so domain state stays server-owned.Specs updated alongside the code:
packages/contracts/SPEC.md,packages/server/src/workspaces/SPEC.md,apps/web/src/panels/SPEC.md.Out of scope: the agent's auto-naming heuristics are untouched — this PR adds a reliable manual
path rather than changing when/how the agent auto-renames.
Testing
bun run test(server + contracts + web unit) — 847 pass, 0 fail, incl. a newrenameWorkspace with renameBranch:false renames the display name only, keeping the git branchcase.bun run e2e(full no-agent suite, 6 shards) — all shards passed. Newe2e/workspace-actions.spec.tscases: inline rename persists via
workspace.renameand keeps the branch (it now surfaces on therow's second line); empty value restores the previous name; the Default row hides
Rename.bun run typecheck— clean for@thinkrail/contracts/@thinkrail/server/@thinkrail/web.(
@thinkrail/website'sastro checkrequires Node ≥ 22.12 and the local shell has an older Node —environmental, and the website is untouched by this PR.)
bun run lint— clean.