Owner polish dispatch (2026-09-23):
- Right-click no longer pins a node or opens the detail card: the
canvas pointerdown/up handlers now gate on the primary button, so
the context menu arrives alone.
- Node context menu: label is just "Delete" and the shell hugs its
content (w-44 -> w-fit); the menu body keeps focus for arrow
cycling but never shows the browser's default focus ring
(outline-none on the MenuShell body - applies to every menu).
- Delete confirm: "Delete node?" title in CMU Serif, red
(text-destructive); the node name sits below in JetBrains Mono
inline-code; the Delete button carries the destructive treatment
(border + tint, fill deepens on hover). Rest of the dialog stays
neutral.
- Sidebar rename row: text and metadata stay at the idle row's x -
the status spacer and the input's border/padding are gone; the
underline is the only editing cue.
- MenuShell: after placement, focus lands in the search when the menu has
one, else the body (tabIndex -1) — the lists' mount-time focus ran during
the hidden measuring pass and silently failed, so no dropdown ever opened
focused. The body owns the arrow machine for searchless menus:
ArrowDown/Up cycle options with wrap (first/last entry from the body,
native-select convention), Home/End, Enter activates, preventDefault so
arrows never scroll the page.
- Appearance: the CSS-var applier only ever setProperty'd — a later write
omitting a var left the stale inline value forever (empty background
never reset, serif toggle-back looked dead). appliedNamesRef tracks what
was set; staleVarNames (pure, exported) returns what to removeProperty.
- CommitInput: controlled — after a commit the field snaps to the stored
record, so the clamp on a -5 prose size is visible (12), Esc reverts.
- Keys: Add-provider dropdown closes on pick; the paste row is inline
(label | input | Save | Cancel); existing-row editors go inline with an
explicit Cancel beside Save/Delete — folding is visible, not Esc-only.
- Graph context: readout is content-sized (tabular-nums) — no fixed-width
dead gap when "off"; the dictation strip fills whatever space remains.
Gates: tsc clean, 866/866 tests (new staleVarNames suite), lint 0, build ok.
Live-verified via Playwright: dropdown focus+arrow cycles+wrap+Enter pick,
background set→empty reset, serif round-trip, visible clamp to 12, provider
pick closes menu, inline editor + Cancel + Esc fold, slider width 270→277px.
- lh:settings-saved CustomEvent (the lh:chat-renamed pattern, owner
ruling): every writer (composer save, SetupDialog save, the settings
write seam — one dispatch point carrying the section) notifies after a
successful PUT; every reader re-reads its own seam: ModelControl's
record read, the Agent section's slots/catalog, and chat.tsx's
configured state (clearing slots mid-session now surfaces the setup
CTA without a reload). Self-notifications re-read idempotently.
- MenuShell now announces lh:menu-open/lh:menu-close (still placement-
only); the settings dialog's content column locks (overflow-hidden,
count-based) while any menu is open — wheel-scrolling with a dropdown
down is dead; the re-place-on-scroll fallback stays for un-lockable
surfaces.
- The settings thinking menus' 'Default' noneRow is retired (owner:
the unset state can't re-enter once chunk 2's auto-populate lands; no
unpin is needed).
- Live-caught regression from the MenuShell migration fixed: the
composer's outside-click handler never learned the portaled menu body
isn't a container descendant — menu-row pointerdowns counted as
outside and ATE THE PICK (composer picks have been dead for pointer
users since 221c99d); rows now carve out via [data-lh-menu], the
settings sections' discipline.
- Item 4 (resolved-level display for legacy records) deferred to chunk 2
per owner ruling — the payload omits the level for legacy no-level
records and DEFAULT_THINKING_ORDER was server-only (now in the core
barrel via bd68486, exactly the seam chunk 2 needs).