mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-09-07 04:02:40 +00:00
* feat(explorer): complete ARIA tab pattern for markdown viewer (#1117) The Preview/Source controls exposed role="tablist"/role="tab"/aria-selected but never connected to the content they switch, so assistive technology could not tell which panel the tabs controlled, and the tablist was two separate Tab stops with no arrow-key navigation. Wire the full pattern: - ids from useId(), matching the GraphWorkspace search combobox, so two viewers mounted at once cannot collide on hardcoded ids - aria-controls on both tabs, role="tabpanel" + aria-labelledby on the panel - roving tabindex, so the tablist is a single Tab stop - Arrow Left/Right with wrapping, plus Home/End, all preventDefault'd Two decisions worth recording. Activation is manual (arrows move focus, Enter/Space selects) rather than the automatic activation the APG suggests by default. The APG permits manual activation where switching panels is expensive, and here it is: activating Preview re-runs the whole markdown parse, measured at 385ms for a 1000-row GFM table and 1433ms at 2000 rows while profiling #1118. Automatic activation would freeze the main thread on every arrow keypress. The panel wraps all three render branches, including the empty state, and both tabs point aria-controls at that one id. Only the active view is ever rendered, so per-tab panel ids would leave the inactive tab referencing an element absent from the DOM -- and scoping the panel to the two content branches would dangle the reference for empty nodes. No visible focus style was added: index.css already applies a global :focus-visible ring and tabBtnStyle does not suppress it. Five tests cover the wiring, the empty-state branch, roving tabindex and id uniqueness; each was confirmed to fail against the previous component. Keyboard behaviour is not reachable from the SSR-based suite, so it was verified separately in headless Chromium: Tab enters the tablist on the selected tab, ArrowRight moves focus without activating, Enter and Space activate, ArrowRight wraps, and Home/End work. All 71 tests pass and lint is unchanged. * feat(explorer): complete markdown viewer tab accessibility Complete the Preview/Source ARIA tab pattern with collision-safe IDs, tabpanel wiring, roving tabindex, and manual keyboard activation. Use ref-based focus tracking to prevent keyboard navigation from triggering Markdown re-parsing, and synchronize the roving tabindex after React renders. Add regression coverage for keyboard navigation, wrapping, ARIA labelling, and focus-state invariants. * fix(explorer): move focusedModeRef resource-reset out of render phase The render-phase ref write (focusedModeRef.current = defaultMode inside the activeResourceKey !== resourceKey guard) correctly reset the ref but triggered a react-hooks/refs lint error: refs must not be written during render. Replace with a useLayoutEffect([resourceKey, defaultMode]) that fires synchronously before paint after every resource/defaultMode change. This achieves the same invariant — focusedModeRef is reset before the no-deps tabIndex-correction effect reads it — while satisfying the linter. Lint: npx eslint MarkdownContentViewer.tsx -> 0 errors Tests: npm run test:graph-workspace -> 107/107 pass --------- Co-authored-by: Pravit Ampapathini <pravit.amp@gmail.com> Co-authored-by: Sameer Kadam <sskadam6305@gmail.com> Co-authored-by: Sameer Kadam <sameerkadam@Mac.lan>