ui: plan canvas ink extent + the keyboard-first round
Plan canvas (owner report: arrows clipped at the viewBox edge in dist — measured 14px/20px cuts in the owner's real plans): blockLayout now unions node boxes with every edge path's control-point hull (a bezier never leaves it; the layout calls edgePathD with the renderer's exact inputs). Dagre's node extent stays the floor, so ordinary plans render byte-identical; the viewBox origin shifts only when ink bulges past zero. Verified on both real chats; pins use the owner's real linear-algebra tree (golden-real-data). Keyboard-first (owner's law: Enter always submits, arrows always cycle; text fields the only exception): - Enter guard split (enter-submits): a focused BUTTON keeps native Enter only when keyboard-walked (:focus-visible) or inside an open overlay (menus/dialogs); pointer-clicked transcript buttons submit, preventDefault suppressing the re-activation underneath. - Focus hygiene (focus-hygiene): pointer clicks on transient controls (agent/user thinking toggles, copy, mermaid expand, plan chip) blur to body — the state clicking empty space produces. - Session search owns the walk: Up/Down wrap-cycle the filtered rows, Enter opens the walked row or the first match (quick-open), then blurs and keeps the query. The Alt-arrow session cycle is RETIRED. - Registry: model-menu walk/pick entries removed (obvious in-field navigation, not settings material); question-dock keys stay displayOnly per owner ruling. Gates: 1489/1489, typecheck, lint. Verified live end-to-end.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* FOCUS HYGIENE — transient transcript controls don't keep focus
|
||||
* (owner, keyboard round 2026-10-10). A pointer click on a thinking
|
||||
* toggle, a copy button, a plan chip used to LEAVE focus on that
|
||||
* button; the learner's next Enter then re-activated the toggle
|
||||
* instead of submitting (see enter-submits.ts for the other half of
|
||||
* the fix). Here we restore, after pointer activation, exactly the
|
||||
* state clicking empty transcript space produces: focus falls back to
|
||||
* the body — Enter submits, arrows cycle the question rows, nothing
|
||||
* re-toggles. Deliberately NOT a focus-into-a-text-field: a focused
|
||||
* field would own the arrows as caret moves, against the arrows-always
|
||||
* law (owner, 2026-10-10).
|
||||
*
|
||||
* Keyboard-activated clicks are untouched: e.detail === 0 marks the
|
||||
* synthesized click of Enter/Space on a keyboard-focused button — the
|
||||
* keyboard user keeps their focus.
|
||||
*/
|
||||
|
||||
/** Attach as (or compose into) a transient control's onClick: blurs the
|
||||
* control after POINTER clicks (keyboard clicks keep focus). Node-safe
|
||||
* duck-typing — no HTMLElement global needed (the test harness runs
|
||||
* bare node with linkedom). */
|
||||
export function blurAfterPointerClick(e: {
|
||||
detail: number;
|
||||
currentTarget: EventTarget | null;
|
||||
}): void {
|
||||
if (e.detail === 0) return;
|
||||
const el = e.currentTarget as { blur?: unknown } | null;
|
||||
if (el && typeof el.blur === "function") (el.blur as () => void).call(el);
|
||||
}
|
||||
Reference in New Issue
Block a user