From 8aa1e23cba34a9158f41015042808a0f22cc97e1 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Wed, 7 Oct 2026 00:32:05 +0300 Subject: [PATCH] =?UTF-8?q?keyboard:=20home's=20Tab=20targets=20its=20OWN?= =?UTF-8?q?=20composer=20=E2=80=94=20the=20smoke=20catch=20(keyboard=203?= =?UTF-8?q?=20follow-up)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The smoke found it: Tab from body on home preventDefault'd but never focused — home does not render the shared Composer component; its composer is the bespoke topic textarea, and the selector matched nothing. The chain is now topicInputRef itself. Also verified live this round: the one-Esc law on plan/chains/settings (exactly one action, nothing stacked), Enter-submits from the Other input (a real session filed the custom answer immediately), and Shift+Tab staying the thinking-level cycle everywhere. 1427 tests, tsc, lint, build green. --- components/home.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/home.tsx b/components/home.tsx index 4ff9101..a6fb5ce 100644 --- a/components/home.tsx +++ b/components/home.tsx @@ -52,12 +52,6 @@ export function Home({ const [setupOpen, setSetupOpen] = useState(false); // Voice dictation (STT wave, 2026-09-17): finals append at the end of the // topic draft; the strip lives in the stats bar's middle slot. - // Tab navigates INTO the composer (owner, keyboard round 2026-10-06): - // the only text field on this surface — the cycle's whole chain. - const cycleComposer = useCallback((): readonly HTMLElement[] => - Array.from(document.querySelectorAll('[data-testid="composer"] textarea')), - []); - useFieldCycle(cycleComposer); const dictation = useDictation({ // Ref-first: the join reads the mirror so the send resuming after // `await finish()` sees the dictated tail. @@ -72,6 +66,16 @@ export function Home({ * the keyboard from the stop. Falling edge only; this surface's only * dictation target IS this textarea, so no case-split is needed. */ const topicInputRef = useRef(null); + // Tab navigates INTO the topic composer (owner, keyboard round + // 2026-10-06): the only text field on this surface — and home's + // composer is its OWN textarea, not the shared Composer component, + // so the chain is the ref itself (the smoke caught a selector that + // matched nothing here). + const cycleTopicField = useCallback( + (): readonly HTMLElement[] => (topicInputRef.current ? [topicInputRef.current] : []), + [], + ); + useFieldCycle(cycleTopicField); const dictationWasActiveRef = useRef(false); useEffect(() => { if (dictation.active) {