4.2 KiB
Audit: tabs
audit-version: 1 audited-at: 2026-06-26 scope: soma/sema/eidos method: adversarially-verified workflow; HIGH lead-verified. B6 ground-truth: checkbox/toggle/switch commit-toggle = sequence post (lag-fixed); radio-group/tabs/accordion/stepper set state at call-site (pre OK); toggle-group + checkbox-group carry the A31 .includes pattern (SYS-7); slider has no gesture-layer A6 leak. provider: src/uix/soma/components/tabs/tabs-provider.svelte.ts sequence-audit: State is set SYNCHRONOUSLY in selectTab() call-site (line 102: this.opts.value.current = value) BEFORE runtime.trigger() fires (line 114). Morfo sequence='pre' (line 20) matches this pattern — state mutation precedes sema hold, so no 240ms perceptual lag occurs. Calls come from: onclick (line 265),
Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0.
Findings
No findings survived verification (clean component).
No-findings dimensions
A: Contract Morfo - 'as const satisfies Morfo' correct (line 167); all parts registered via runtime.part() with kebabs matching morfo (provider/list/trigger/content/indicator); data-orientation, data-disabled, data-state, data-value naming compliant; no aria/data mismatch, B: Behavior Soma - sequence 'pre' (morfo line 20) is correct: state set at call-site via selectTab() (line 102) before runtime.trigger(), not in handler; roving tabindex A14 verified (line 322: isActive OR isFallbackTabStop yields exactly one tabindex=0 per rules; fallback logic at line 150-154 uses untrack to prevent reactivity thrashing); A12 RTL via getDirectionalKeys (line 282); A15 no gesture layer (not applicable); A6 Presence lifecycle tracked via constructor (line 379) and $effect cleanup (line 372-377); A13 N/A (tabs uses triggers, not hidden form input); A30 id registration DIRECT in constructor (line 244, 369) not $effect; A31 no O(N²) patterns (.includes not present); A33 no SvelteMap/SvelteSet needed (plain Record); A35/A36 no problematic loops, C: DOM-selector CSS.escape used (line 109) for consumer value; soma.dom.focus() used (line 315) not bare window, D: Frontier - no eidos imports in soma detected; syncAttrs:true (line 86) with runtime.part emits only morfo-declared attrs, no double-write divergence, E: TSC/Theming - all tokens use --tabs-* or canonical aliases (--color-, --space-, --font-, --radius-, etc.) from recipe base.ts (lines 3454-3528); z-indices local (0, 1) only; no magic literals, F: Tests - provider test exists (tabs-provider.svelte.test.ts) covers sequence/focus activation (line 203), roving keyboard (line 247), fallback tabstop (line 294), part registration (line 117); env jsdom; high-risk paths covered, G: Redundancy - no re-implemented patterns; content uses Presence correctly (line 379-383) for motion/lazy mount logic
Theming facts (E-bis)
- magic z-index: z-index: 0 (line 236, 274) — local context (triggers layered above at z-index: 1 line 281) | z-index: 1 (line 281) — trigger layering above indicator
- magic literals: 'post' sequence absent (correct: state set call-site, 'pre' tolerates this) | indicator-line-thickness: '2px' (line 3526 recipe) — documented hardcoded thickness for line variant
- undeclared parts: none
- roles clean: true · variants clean: true
Tests (F)
- exists: true · env: jsdom (@vitest-environment jsdom line 1)
- covers: part registration & cross-linking (line 117); automatic vs manual activation modes (line 203); roving keyboard navigation + disabled trigger skip (line 247); fallback tabstop A14 when active value missing (line 294)
- untested: Gesture/scroll-area paths not applicable to tabs; RTL keyboard direction validation (getDirectionalKeys called but not mocked in test); ResizeObserver cleanup on eidos indicator unmount (A6 observer lifecycle in tabs-indicator.svelte covered by ActiveDom tracing, not direct unit test)
Style observations (non-blocking)
- Indicator animation uses cubic-bezier(0.32, 0.72, 0, 1) (line 3525) — intentional non-standard ease for snappy 'slide' feel, documented in comment
- color-mix() used for color blending (lines 3471-3472, 3506, 3508-3509, 3520-3521) — recipe abstraction avoids raw hex; consistent with theming rules
- Reduced motion honored via :has() fallback border (line 150-159) and animation removal (line 403-410)