keyboard: Tab cycles the surface's text fields, forward-only (keyboard 3 of 4)
Plain Tab now navigates instead of being pure dead weight: the global handler dispatches lh:kbd-cycle-fields and each composer-bearing surface answers with its own chain, gathered from live DOM at event time (the docks mount and unmount — refs would go stale): - chat: reading-thinking → the active dock's field → the composer, visual order, wrap; Tab from anywhere else focuses the first live field (navigate in) - new-session + home: the composer is the whole chain — Tab navigates into it Shift+Tab is untouched (the thinking-level cycle keeps it — owner ruling); the quiz dock's rows↔thinking Tab claim stops propagation at document level, so a claimed Tab never reaches the dispatch — no double act. 1427 tests, tsc, lint, build green.
This commit is contained in:
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
import { Conversation, ConversationContent, ConversationEmptyState } from "@/components/ai-elements/conversation";
|
||||
import { Composer } from "@/components/composer";
|
||||
import { DictationBar, joinDictation, useDictation } from "@/components/dictation";
|
||||
import { useFieldCycle } from "@/components/field-cycle";
|
||||
import { SidebarToggle } from "@/components/sidebar";
|
||||
import { SetupDialog } from "@/components/setup-dialog";
|
||||
import { useStrongKeyless, useZeroKeys, ZeroKeysPill } from "@/components/setup-cta";
|
||||
@@ -112,6 +113,12 @@ export function DraftShell({
|
||||
// Voice dictation (STT wave, 2026-09-17): finals append at the end of the
|
||||
// draft text (via the parent's onChange — the truth lives there); 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<HTMLElement>('[data-testid="composer"] textarea')),
|
||||
[]);
|
||||
useFieldCycle(cycleComposer);
|
||||
const dictation = useDictation({
|
||||
append: (t) => onChange(joinDictation(text, t)),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user