Header band: one row of controls, 36px, aligned with the sidebar toggle

Owner report: the controls folded onto two rows (search+coverage /
import+gear) — the 2026-09-17 flex-wrap kicking in, plus a real bug: my
previous wave moved max-w-[calc(100%-9rem)] onto the shrink-wrapped
column wrapper, where the percentage reference broke and the wrap fired
far too early. Owner wants the controls to read as header content.

- The width cap moves to the absolute column itself (100% resolves
  against the full-bleed parent, as before): max-w-[calc(100%-5.5rem)].
- One row, NEVER wraps (owner rejected the two-row fold): search is the
  flexible element (w-56 + min-w-0, shrinks under the cap); the other
  controls are shrink-0.
- All controls slimmed into the header band: top-2 (8px) matching the
  header's py-2, 36px tall matching the toggle's h-9 — size-9 buttons,
  h-7 import button, px-1 py-[3px] containers (border included = 36).
- Cost number + pipeline pills stay below the band, unchanged.
This commit is contained in:
amosblomqvist
2026-09-19 22:02:19 +03:00
parent 138d3d8ce6
commit 4e39f04cfa
2 changed files with 31 additions and 20 deletions
+1 -1
View File
@@ -102,7 +102,7 @@ export function GraphCoverageControl({ refreshKey = 0 }: { refreshKey?: number }
title="Extraction coverage — what the knowledge graph has covered" title="Extraction coverage — what the knowledge graph has covered"
aria-expanded={open} aria-expanded={open}
onClick={() => setOpen((o) => !o)} onClick={() => setOpen((o) => !o)}
className={`flex size-11 items-center justify-center rounded-xl border bg-card/85 shadow-lg backdrop-blur transition-colors hover:bg-accent ${ className={`flex size-9 items-center justify-center rounded-xl border bg-card/85 shadow-lg backdrop-blur transition-colors hover:bg-accent ${
open ? "text-foreground" : "text-muted-foreground hover:text-foreground" open ? "text-foreground" : "text-muted-foreground hover:text-foreground"
}`} }`}
> >
+30 -19
View File
@@ -361,7 +361,7 @@ function GraphSettingsControl({
title="Rendering settings" title="Rendering settings"
aria-expanded={open} aria-expanded={open}
onClick={() => setOpen((o) => !o)} onClick={() => setOpen((o) => !o)}
className={`flex size-11 items-center justify-center rounded-xl border bg-card/85 shadow-lg backdrop-blur transition-colors hover:bg-accent ${ className={`flex size-9 items-center justify-center rounded-xl border bg-card/85 shadow-lg backdrop-blur transition-colors hover:bg-accent ${
open ? "text-foreground" : "text-muted-foreground hover:text-foreground" open ? "text-foreground" : "text-muted-foreground hover:text-foreground"
}`} }`}
> >
@@ -1134,24 +1134,35 @@ export function GraphView() {
coverage, import, gear — coverage sits between search and coverage, import, gear — coverage sits between search and
import; the gear keeps the corner. items-start (not center) import; the gear keeps the corner. items-start (not center)
so the search container's no-match note can grow downward so the search container's no-match note can grow downward
without shifting its siblings — every button is 44px tall, without shifting its siblings — every control is 36px tall
top alignment pins the row. Coverage renders even when the (header band, 2026-09-20), top alignment pins the row.
graph is EMPTY — an empty graph with unextracted sessions is Coverage renders even when the graph is EMPTY — an empty
exactly when it matters (extract → the graph gains nodes). graph with unextracted sessions is exactly when it matters
(extract → the graph gains nodes).
lh-no-drag (2026-09-17): home.tsx's floating drag strip lh-no-drag (2026-09-17): home.tsx's floating drag strip
underlays this row — Electron's drag mask ignores z-index, underlays this row — Electron's drag mask ignores z-index,
so without an explicit carve-out the controls were so without an explicit carve-out the controls were
unclickable in the desktop app (owner report). max-w + flex-wrap unclickable in the desktop app (owner report). The old
(narrow-window wave, 2026-09-17): below the sidebar auto-close flex-wrap (narrow-window wave, 2026-09-17) folded the row
threshold the row wraps to a second line instead of overflowing onto two lines below the sidebar threshold — owner rejected
left over the header toggle cluster (92px gutter + 36px button); it (2026-09-20): one row always, the search shrinks (see
9rem keeps one clear column of air alongside it. */} the header-band comment on the column below). */}
<div className="lh-no-drag absolute right-3 top-3 z-10 flex flex-col items-end gap-2"> {/* Header band (owner, 2026-09-20): the controls sit IN the
<div className="flex max-w-[calc(100%-9rem)] flex-wrap items-start gap-2"> header's visual row — top-2 aligns with the sidebar toggle's
band (header py-2, toggle h-9), every control slimmed to 36px.
The width cap lives HERE on the absolute element (100% resolves
against the full-bleed parent) — the row NEVER wraps (owner
rejected the two-row fold); in narrow windows the search is
the flexible element and shrinks instead. */}
<div className="lh-no-drag absolute right-3 top-2 z-10 flex max-w-[calc(100%-5.5rem)] flex-col items-end gap-2">
<div className="flex min-w-0 items-start gap-2">
{!isEmpty && ( {!isEmpty && (
<> <>
{/* Search — its own container (icon + input + no-match note). */} {/* Search — its own container (icon + input + no-match note).
<div className="w-56 rounded-xl border bg-card/85 p-1.5 shadow-lg backdrop-blur"> The header band's flexible element: w-56 normally, shrinks
(min-w-0) when the column's width cap binds in narrow
windows — the row's other controls are shrink-0. */}
<div className="w-56 min-w-0 shrink rounded-xl border bg-card/85 px-1 py-[3px] shadow-lg backdrop-blur">
<div className="relative"> <div className="relative">
<SearchIcon className="absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" /> <SearchIcon className="absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
<input <input
@@ -1160,7 +1171,7 @@ export function GraphView() {
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
placeholder="Search nodes…" placeholder="Search nodes…"
spellCheck={false} spellCheck={false}
className="h-8 w-full rounded-lg border bg-background pl-8 text-xs outline-none placeholder:text-muted-foreground focus:border-ring" className="h-7 w-full rounded-lg border bg-background pl-8 text-xs outline-none placeholder:text-muted-foreground focus:border-ring"
/> />
</div> </div>
{snapshot !== null && visibleIds.size === 0 && query.trim() !== "" && ( {snapshot !== null && visibleIds.size === 0 && query.trim() !== "" && (
@@ -1176,11 +1187,11 @@ export function GraphView() {
with unextracted sessions is exactly when it matters (extract with unextracted sessions is exactly when it matters (extract
→ the graph gains nodes). The panel re-reads when background → the graph gains nodes). The panel re-reads when background
runs settle (refreshTick). */} runs settle (refreshTick). */}
<GraphCoverageControl refreshKey={refreshTick} /> <div className="shrink-0"><GraphCoverageControl refreshKey={refreshTick} /></div>
{!isEmpty && ( {!isEmpty && (
<> <>
{/* Import notes — its own container. */} {/* Import notes — its own container (36px, header band). */}
<div className="rounded-xl border bg-card/85 p-1.5 shadow-lg backdrop-blur"> <div className="shrink-0 rounded-xl border bg-card/85 px-1 py-[3px] shadow-lg backdrop-blur">
<input <input
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
@@ -1196,7 +1207,7 @@ export function GraphView() {
type="button" type="button"
disabled={importing} disabled={importing}
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
className="flex h-8 items-center gap-1.5 rounded-lg px-3 text-xs text-muted-foreground transition-colors hover:bg-accent disabled:opacity-50" className="flex h-7 items-center gap-1.5 rounded-lg px-2.5 text-xs text-muted-foreground transition-colors hover:bg-accent disabled:opacity-50"
> >
<UploadIcon className="size-3" /> <UploadIcon className="size-3" />
{importing ? "Importing…" : "Import notes…"} {importing ? "Importing…" : "Import notes…"}