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:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { usePopoverDismiss } from "@/components/popover-dismiss";
|
||||
import { ListChecksIcon, Loader2Icon } from "lucide-react";
|
||||
import { formatTokens } from "@/components/usage-format";
|
||||
|
||||
@@ -34,6 +35,10 @@ interface CoverageChat {
|
||||
|
||||
export function GraphCoverageControl({ refreshKey = 0 }: { refreshKey?: number }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
// Click anywhere outside (or Escape) closes the panel (2026-09-17) —
|
||||
// the root wraps button + panel, so the toggle itself keeps working.
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
usePopoverDismiss(rootRef, open, () => setOpen(false));
|
||||
const [chats, setChats] = useState<CoverageChat[]>([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [extracting, setExtracting] = useState<string[]>([]); // click-pending ids (optimistic until the poll says running)
|
||||
@@ -83,7 +88,7 @@ export function GraphCoverageControl({ refreshKey = 0 }: { refreshKey?: number }
|
||||
|
||||
const pending = (chat: CoverageChat) => chat.running || extracting.includes(chat.chatId);
|
||||
return (
|
||||
<div className="relative">
|
||||
<div ref={rootRef} className="relative">
|
||||
<button
|
||||
type="button"
|
||||
title="Extraction coverage — what the knowledge graph has covered"
|
||||
|
||||
Reference in New Issue
Block a user