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:
+29
-1
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user