feat: session rename + graph chrome polish (popovers dismiss, no-shift no-match, coverage reseat)

Rename (owner ask): clicking the header title opens edit mode with the
whole title selected — typing replaces, arrows move inside. Enter or a
click away commits (PUT /api/chats/:id); Escape and an empty commit
discard. renameChat joins the ChatStore port; like saveSettings it is
NOT conversation activity — updatedAt stays put so sidebar order never
churns on a rename. The sidebar patch is instant: the header dispatches
lh:chat-renamed and SessionsSidebar folds it into its snapshot (the 2.5s
poll is only a backstop). Sidebar right-click rename is DELIBERATELY
deferred: it needs a real context-menu system, which gets its own pass
later (owner decision, 2026-09-17).

Graph chrome:
- The extraction-coverage button moves between search and import; the
  gear keeps the far-right corner. Coverage still renders on an EMPTY
  graph (that is exactly when it matters).
- The no-match search note no longer shifts its siblings: the chrome
  row is items-start (every button is 44px tall, so top alignment pins
  the row while the search container grows downward).
- Popovers dismiss properly: pointerdown anywhere outside the control's
  root (button + panel) closes it, and so does Escape. Two panels can
  never be open at once — the sibling chrome buttons are outside the open
  root, so pointerdown closes the first before the click opens the
  second. New shared hook components/popover-dismiss.ts, wired into
  both the settings gear and the coverage control.

Gates: typecheck, eslint, 498/498 (+2 rename store tests), build.
Live-verified: select-all/focus/Enter/Escape/empty/blur round-trip,
updatedAt untouched, sidebar instant patch; chrome order + frozen
positions under a no-match query; popover close via canvas, search
input, Esc, mutual exclusion, toggle intact.
This commit is contained in:
amosblomqvist
2026-09-17 14:28:20 +03:00
parent 5c380de669
commit 769a3da186
9 changed files with 219 additions and 17 deletions
+29 -1
View File
@@ -528,4 +528,32 @@ describe("mid-turn navigation (turn liveness + write-ahead)", () => {
assert.equal(ok.status, 200);
await drain(ok);
});
});
});
describe("renameChat (the header's click-to-edit title)", () => {
it("persists the title and does NOT bump updatedAt — sidebar order must not churn", () => {
const chat = chatStore.createChat("old title");
createdChats.push(chat.id);
const before = chatStore.getChat(chat.id)!.updatedAt;
const renamed = chatStore.renameChat(chat.id, " new title ");
assert.equal(renamed.title, "new title", "trimmed on the way in");
assert.equal(chatStore.getChat(chat.id)!.title, "new title");
assert.equal(
chatStore.getChat(chat.id)!.updatedAt,
before,
"a rename is not conversation activity",
);
assert.equal(
chatStore.listChats().find((c) => c.id === chat.id)!.title,
"new title",
"the sidebar list view sees it",
);
});
it("falls back to Untitled session on an all-whitespace title (the honest floor)", () => {
const chat = chatStore.createChat("keep me");
createdChats.push(chat.id);
const renamed = chatStore.renameChat(chat.id, " ");
assert.equal(renamed.title, "Untitled session");
});
});