Skip to content

feat(web): allow renaming worker sessions in the sidebar#1748

Merged
harshitsinghbhandari merged 3 commits into
ComposioHQ:mainfrom
harshitsinghbhandari:feat/1647
May 10, 2026
Merged

feat(web): allow renaming worker sessions in the sidebar#1748
harshitsinghbhandari merged 3 commits into
ComposioHQ:mainfrom
harshitsinghbhandari:feat/1647

Conversation

@harshitsinghbhandari
Copy link
Copy Markdown
Collaborator

Summary

  • Adds an inline rename UX to worker session rows in the sidebar — pencil button on hover swaps the label for an input. Enter saves, Escape cancels, empty value clears (revert to default).
  • Persists via new PATCH /api/sessions/:id endpoint that writes the existing displayName metadata field (sanitized, max 80 chars, control-char-stripped).
  • Promotes displayName to the top of getSessionTitle's fallback chain so a user-chosen rename beats PR/issue titles. The session ID (ao-N) stays canonical — only display surfaces are affected.

Closes #1647.

Test plan

  • pnpm --filter @aoagents/ao-web typecheck — clean
  • pnpm lint — 0 errors (49 pre-existing warnings)
  • pnpm --filter @aoagents/ao-web test -- ProjectSidebar — 27 pass (incl. 6 new rename tests)
  • pnpm --filter @aoagents/ao-web test -- api-routes — 73 pass (incl. 8 new PATCH tests; 2 pre-existing failures verified on main via stash round-trip)
  • pnpm --filter @aoagents/ao-web test -- format — 33 pass (incl. updated precedence tests)
  • Manual: hover a sidebar worker row → pencil appears; click → input opens; Enter saves; Escape cancels; empty input reverts to default; bad PATCH rolls back.

🤖 Generated with Claude Code

Closes ComposioHQ#1647

Adds an inline rename UX to each worker session row in the sidebar. A
small pencil button appears on row hover; clicking it swaps the label
for an input pre-filled with the current title. Enter persists via
PATCH /api/sessions/:id, Escape cancels, and an empty value clears the
field — reverting the session to its default title.

The rename writes to the existing displayName metadata field, which is
now the highest-priority signal in getSessionTitle so a user-chosen
label always beats PR/issue titles. The session ID (ao-N) remains
canonical — only display surfaces are affected.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR adds an inline rename UX to worker session rows in the sidebar, with a pencil button on hover that opens an editable input. Renames persist via a new PATCH /api/sessions/:id endpoint that writes to the existing displayName metadata field, and the user-set flag is now checked in getSessionTitle so user-chosen labels always beat auto-derived PR/issue titles.

  • Adds optimistic rename state (pendingRenames) in the sidebar with rollback on failed PATCH; the server correctly strips control chars, collapses whitespace, trims, and caps at 80 chars.
  • Promotes displayName (when displayNameUserSet is true) to the top of getSessionTitle's fallback chain, and the sidebar's effective-title computation now correctly gates on the same flag to avoid stale spawn-time labels shadowing live PR titles.
  • New displayNameUserSet field threads through the core type, metadata serialization, and dashboard serialization layers.

Confidence Score: 4/5

Safe to merge with awareness that the collapsed sidebar won't show optimistic rename updates, and the Enter-blur double-submit path from the prior review remains unresolved.

The core rename feature is well-implemented; the main concerns are the collapsed sidebar not reflecting in-flight optimistic renames, and the unresolved blur-after-Enter double-submit path from the prior review.

packages/web/src/components/ProjectSidebar.tsx — both the collapsed-sidebar optimistic-update gap and the blur/Enter interaction live here.

Important Files Changed

Filename Overview
packages/web/src/components/ProjectSidebar.tsx Adds inline rename UX with optimistic pendingRenames state; expanded sidebar correctly gates on displayNameUserSet, but collapsed sidebar ignores pending renames; blur-after-Enter double-submit guard relies on stale closure (flagged in prior review).
packages/web/src/app/api/sessions/[id]/route.ts New PATCH handler with thorough input validation (control-char strip, whitespace collapse, 80-char cap, identifier validation); correctly uses empty-string convention to remove keys from metadata and clear the displayNameUserSet flag on revert.
packages/web/src/lib/format.ts User-set displayName correctly promoted to top of fallback chain; minor comment numbering error (two "5" labels in the chain).
packages/core/src/metadata.ts Adds displayNameUserSet parsing in readMetadata with consistent on/off/true/false handling that mirrors the prAutoDetect pattern; also included in unflattenFromStringRecord's booleanFields set for correct round-trip storage.
packages/web/src/lib/serialize.ts Correctly serializes displayNameUserSet from session metadata string to boolean in sessionToDashboard; no issues.
packages/core/src/types.ts Adds optional displayNameUserSet?: boolean to SessionMetadata with clear doc-comment explaining the flag's semantics; clean extension with no breaking changes.
packages/web/src/lib/types.ts Adds non-optional displayNameUserSet: boolean to DashboardSession with accurate doc-comment; defaults to false in serialization which is correct for sessions that have never been explicitly renamed.

Sequence Diagram

sequenceDiagram
    participant User
    participant Sidebar as ProjectSidebar
    participant API as PATCH /api/sessions/:id
    participant Meta as metadata.ts (disk)
    participant SM as SessionManager
    participant SSE as SSE refresh

    User->>Sidebar: Click pencil button
    Sidebar->>Sidebar: startRename() → setEditingSessionId + setEditingValue
    User->>Sidebar: Type new name, press Enter (or blur)
    Sidebar->>Sidebar: submitRename() — guard editingSessionId check
    Sidebar->>Sidebar: "setEditingSessionId(null), setPendingRenames({id: newName})"
    Sidebar->>API: "PATCH /api/sessions/:id {displayName}"
    API->>API: validateIdentifier(id)
    API->>API: stripControlChars + trim + slice(0,80)
    API->>SM: sessionManager.get(id)
    API->>Meta: updateMetadata(displayName, displayNameUserSet)
    API->>SM: invalidateCache() [OpenCode only]
    API->>SM: sessionManager.get(id) [read updated]
    API-->>Sidebar: 200 OK / error
    alt PATCH failed
        Sidebar->>Sidebar: setPendingRenames → delete(id) [rollback]
    end
    SSE-->>Sidebar: sessions refresh with new displayName
    Sidebar->>Sidebar: cleanup effect: pendingRenames.delete(id) when displayName matches
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/web/src/lib/format.ts:96-97
The fallback chain comment numbering skips from step 5 to another step 5 — the humanized-branch block should be labeled 6, matching the doc-comment at the top of the function. As-is it conflicts with the "5. User prompt" block above it, making the chain hard to read and audit.

```suggestion
  // 6. Humanized branch — stable semantic fallback.
  // humanizeBranch returns "" when the branch is just the session ID
```

### Issue 2 of 2
packages/web/src/components/ProjectSidebar.tsx:460-467
**Collapsed sidebar ignores `pendingRenames` optimistic updates**

The collapsed sidebar computes `rawTitle` directly from `session.branch ?? getSessionTitle(session)` without consulting `pendingRenames`. If a user renames a session and immediately collapses the sidebar (or the sidebar starts collapsed), the three-letter abbreviation and tooltip still reflect the pre-rename label until the next SSE refresh. The expanded sidebar correctly applies the `pendingRenames` mask; the collapsed view should use the same `effectiveDisplayName` logic so the two views stay in sync during an in-flight rename.

Reviews (3): Last reviewed commit: "fix(web): address review feedback on ses..." | Re-trigger Greptile

PR review flagged that promoting `displayName` to the top of
`getSessionTitle` regressed every existing session: spawn-time
auto-derived `displayName` would shadow live PR/issue titles for
sessions the user never explicitly renamed.

Adds a `displayNameUserSet` boolean flag to SessionMetadata and
DashboardSession. The dashboard fallback chain promotes `displayName`
above PR/issue titles only when this flag is true; auto-derived
spawn-time values stay at their original position (below PR/issue,
above userPrompt).

PATCH /api/sessions/:id sets `displayNameUserSet=true` when the user
types a name, and clears it when they revert. Sidebar gates its
displayName preference on the flag too, so non-renamed rows keep the
existing branch-first behavior.

Also addresses review #3 (rename-while-pending pre-fill) and #4
(double-submit guard on Enter+blur).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@harshitsinghbhandari
Copy link
Copy Markdown
Collaborator Author

Pushed eee8d56 addressing the review:

🔴 Critical #1updateMetadata "fire-and-forget":
Pushing back: updateMetadata is synchronous (function ... : void at packages/core/src/metadata.ts:292, uses withFileLockSync + readFileSync + atomicWriteFileSync). It returns only after the write completes and throws synchronously on error, caught by the surrounding try/catch which returns 500. There's no race, no fire-and-forget — await Promise.resolve(...) would be a no-op. No code change.

⚠️ Warning #2 — precedence regression: ✅ fixed.
Added displayNameUserSet boolean flag to SessionMetadata and DashboardSession. getSessionTitle now only promotes displayName above PR/issue titles when the flag is true; auto-derived spawn-time values stay at their original position (below PR/issue, above userPrompt). PATCH sets the flag on rename and clears it on revert. Sidebar gates its displayName preference on the flag too, so non-renamed rows keep the existing branch-first behavior. New tests:

  • does NOT promote auto-derived displayName above PR title
  • does NOT promote auto-derived displayName above issue title
  • prefers user-set displayName above every other signal (rename wins)
  • serializes and reads back displayNameUserSet flag (core)

⚠️ Warning #3 — stale pending entry on rename-while-pending: ✅ fixed.
startRename now uses the in-flight pending value as the input pre-fill so a re-rename starts from the latest typed value, not the stale prop.

⚠️ Warning #4 — onBlur double-submit: ✅ fixed.
Added if (editingSessionId !== sessionId) return guard at the top of submitRename. New test does not fire a duplicate PATCH if Enter and onBlur both trigger.

💡 CSS reformatting noise: ✅ fixed. Reverted the topbar-zone-pill whitespace changes — those were prettier collateral from pnpm format running across the repo.

Test counts: 1109 core tests pass, 28 sidebar, 34 format, 8 PATCH. The 2 pre-existing api-routes failures (cache-first PR enrichment, killed-session PR refresh) are present on main before this branch — verified via git stash round-trip.

Comment thread packages/web/src/components/ProjectSidebar.tsx
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a user-facing “rename session” capability to the web dashboard by introducing an inline rename control in the ProjectSidebar and a new PATCH /api/sessions/:id API route that persists the chosen label into session metadata (with sanitization and length limits).

Changes:

  • Add inline rename UX in the sidebar with optimistic UI updates.
  • Add PATCH /api/sessions/[id] to persist displayName + provenance flag (displayNameUserSet) into metadata.
  • Update session title selection logic and tests to prioritize user-set display names over PR/issue titles.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/web/src/lib/types.ts Adds displayNameUserSet to the dashboard session model.
packages/web/src/lib/serialize.ts Serializes displayNameUserSet from session metadata into DashboardSession.
packages/web/src/lib/format.ts Updates getSessionTitle fallback chain to prefer user-set display names.
packages/web/src/lib/tests/format.test.ts Adds/updates tests for the new title precedence behavior.
packages/web/src/components/ProjectSidebar.tsx Implements inline rename UI, optimistic updates, and PATCH calls from the sidebar.
packages/web/src/components/tests/ProjectSidebar.test.tsx Adds rename interaction tests (Enter/Escape/blur rollback).
packages/web/src/app/globals.css Adds styling for the rename button and inline input; reformats unrelated CSS block.
packages/web/src/app/api/sessions/[id]/route.ts Introduces PATCH handler to update session metadata for display name renames.
packages/web/src/tests/helpers.ts Updates test session factory to include displayNameUserSet.
packages/web/src/tests/api-routes.test.ts Adds tests and core mocks for the new PATCH route behavior.
packages/core/src/types.ts Extends SessionMetadata with displayNameUserSet.
packages/core/src/metadata.ts Reads/writes/parses displayNameUserSet in metadata serialization.
packages/core/src/tests/metadata.test.ts Adds coverage for displayNameUserSet persistence semantics.
.changeset/rename-worker-sessions.md Publishes the web package change as a minor release with user-facing notes.

Comment thread packages/web/src/components/ProjectSidebar.tsx Outdated
Comment thread packages/web/src/components/ProjectSidebar.tsx Outdated
Comment thread packages/web/src/app/globals.css Outdated
Comment thread packages/web/src/app/globals.css Outdated
Comment thread packages/core/src/metadata.ts Outdated
- ProjectSidebar: gate effective displayName on displayNameUserSet so
  auto-derived spawn-time names no longer shadow live PR/issue titles
  in the sidebar (mirrors the gate already in format.ts:getSessionTitle).
  Adds a regression test.
- ProjectSidebar: drop unreachable `?? currentTitle` from startRename
  initial value — the right side of the nullish-coalescing always returns
  a string, so the fallback is already handled by the `|| currentTitle`
  on the next line.
- ProjectSidebar: reveal rename pencil on `group-focus-within` so keyboard
  users tabbing through the session links discover the affordance, not
  just pointer users.
- globals.css: change rename button + input border-radius from 3px to 0
  to match the repo's --radius-base: 0 design rule for UI controls.
- core/metadata: accept legacy "on"/"off" strings for displayNameUserSet
  in readMetadata for parity with prAutoDetect (defensive — the storage
  write path already converts to boolean via unflattenFromStringRecord).
  Adds coverage for all six accepted forms.
- web/serialize: drop dead `=== "on"` check on displayNameUserSet —
  Session.metadata is Record<string, string> and the value can only ever
  be "true" / "false" after flattenToStringRecord.

Refs ComposioHQ#1647.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@harshitsinghbhandari
Copy link
Copy Markdown
Collaborator Author

Pushed 1131c33 addressing the latest round of review feedback.

Greptile

  • 🔴 Issue 1 — sidebar title ignores displayNameUserSet: ✅ fixed. The sidebar's effectiveDisplayName now gates session.displayName on session.displayNameUserSet, matching the gate in format.ts:getSessionTitle. Auto-derived spawn-time names fall through to getSessionTitle so live PR/issue titles surface. New regression test: does not shadow PR title with auto-derived displayName.
  • ⚠️ Issue 2 — serialize.ts "on" check is dead code: ✅ fixed. Session.metadata is Record<string, string> and only ever sees "true" / "false" after flattenToStringRecord(String(true)). Reduced to === "true".
  • ⚠️ Issue 3 — ?? currentTitle unreachable in startRename: ✅ fixed. Removed; the existing initial || currentTitle on the next line already handles the empty-string fallback.

Copilot

  • ⚠️ Sidebar bypasses getSessionTitle() when displayName non-empty: ✅ same fix as Greptile feat: implement web dashboard with attention-zone UI and API routes #1.
  • ⚠️ Rename button hidden from keyboard users: ✅ fixed. Added group-focus-within:opacity-100 so the pencil reveals on row link focus, not just pointer hover.
  • ⚠️ border-radius: 3px on rename button conflicts with --radius-base: 0: ✅ fixed (set to 0).
  • ⚠️ border-radius: 3px on rename input conflicts with --radius-base: 0: ✅ fixed (set to 0).
  • ⚠️ readMetadata doesn't accept "on"/"off" for displayNameUserSet: ✅ fixed for parity with prAutoDetect (defensive — the write path already coerces via unflattenFromStringRecord). Added a parametrized test covering all six accepted forms.

Verification

  • pnpm build — clean
  • pnpm typecheck — clean
  • pnpm lint — 0 errors (49 pre-existing warnings)
  • pnpm --filter @aoagents/ao-core test — 1110 pass (incl. new on/off coverage)
  • pnpm --filter @aoagents/ao-web test -- ProjectSidebar — 29 pass (incl. new shadow-PR regression test)
  • pnpm --filter @aoagents/ao-web test -- format serialize — clean
  • pnpm --filter @aoagents/ao-web test -- api-routes — 2 pre-existing failures (cache-first PR enrichment, killed-session PR refresh) carried over from main, unrelated to this PR.

Copy link
Copy Markdown
Collaborator

@i-trytoohard i-trytoohard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-Review — All Previous Criticals Fixed

Both criticals from the initial review are resolved:

  1. updateMetadata is synchronous (returns void, uses atomicWriteFileSync) — no missing await. Handler order is correct: write → invalidate cache → re-read.

  2. displayNameUserSet flag cleanly separates user renames from auto-derived names. Two-tier precedence: user-set name wins over everything; auto-derived stays below PR/issue titles. Atomic writes via file lock + atomicWriteFileSync. Boolean parsing covers all 6 forms.

Copilot/Greptile feedback also addressed: group-focus-within for keyboard accessibility, border-radius: 0 matching design spec, on/off parity with prAutoDetect.

CI: 12/12 green.

Approved.

@harshitsinghbhandari harshitsinghbhandari merged commit 71326bc into ComposioHQ:main May 10, 2026
12 checks passed
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.

feat(web): allow renaming agent sessions with a custom display name

3 participants