# Audit: scroll-area audit-version: 1 audited-at: 2026-06-26 scope: soma 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/scroll-area/scroll-area-provider.svelte.ts sequence-audit: N/A - scroll-area is not a form control (not checkbox/toggle/switch). Uses pointer events for drag which set scrollTop/scrollLeft directly on the viewport element (not Svelte state), so lag risk does not apply. setPointerCapture is immediate, not deferred. ## Summary Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0. ## Findings ### MEDIUM: Morfo declares parts and their data attributes; provider must emit only declared attribute — scroll-area-001 - dimension: A - Contract - rule: Morfo declares parts and their data attributes; provider must emit only declared attributes or register undeclared ones (2-of-3 rule) - location: src/uix/morfo/components/scroll-area.ts:51-56 (Scrollbar part) vs src/uix/soma/components/scroll-area/scroll-area-provider.svelte.ts:428-436 - evidence: Morfo Scrollbar declares data-state, data-orientation, data-hover, data-dragging. Provider emits those PLUS data-overflow-x and data-overflow-y (lines 432-433) which are NOT declared in the Scrollbar part. Only the Provider part declares overflow-* attributes (lines 21-24). Same issue on Viewport: declared data-at-top/bottom/left/right but provider also emits data-overflow-x/y (lines 195-196). - impact: Contract violation. Runtime validators should catch undeclared attributes. data-overflow-x/y belong on Provider only, or must be registered in each part's morfo data array. - proposed-fix: Add data-overflow-x and data-overflow-y to the Scrollbar and Viewport parts in the morfo (src/uix/morfo/components/scroll-area.ts), or remove them from the provider's rendered props for those parts. - verify: [downgraded] Drift CONFIRMED but severity downgraded HIGH->MEDIUM. The morfo declares data-overflow-x/data-overflow-y ONLY on the Provider part (src/uix/morfo/components/scroll-area.ts:22-23). The provider emits them additionally on Viewport (scroll-area-provider.svelte.ts:195-196 `'data-overflow-x': boolToEmptyStrOrUndef(this.provider.hasOverflowX), 'data-overflow-y': ...`) and on Scrollbar (lines 432-433, same expressions). Note the candidate UNDER-reports: the Thumb part also emits undeclared attrs — Thumb props at lines 566-567 stamp `'data-hover'`/`'data-dragging'` which the Thumb morfo (lines 83-86: only data-state, data-orientation) does NOT declare. So this is the same undeclared-attribute drift across Viewport+Scrollbar+Thumb. However this is a contract-lint concern (SYS-1-adjacent), not a user-facing behavior break: the attrs are informational duplicates of the Provider's own data-overflow-* and the eidos CSS (scroll-area.css) never selects data-overflow-x/y on scrollbar/viewport/thumb. No wrong behavior, no a11y break -> MEDIUM per the severity rubric (token/contract inconsistency), not HIGH. - fix-status: open ## No-findings dimensions B - Behavior (no sequence/lag; pointer drag uses direct dom setPointerCapture; no Gesture layer needed), C - DOM-selector (no interpolation of consumer values; no unsafe querySelector), D - Frontier (no soma->eidos import; data-size/color/variant not used; no syncAttrs divergence), A30/A31/A33/A35/A36 (no id-registration loops; no .includes() in item derivations; no $state(new Map); no SVG loop issues), A6 gesture-layer (not applicable; thumb drag uses native setPointerCapture, not Gesture layer), A10 nested querySelector (not applicable; getItems not used), A12 RTL directional-keys (not applicable; no keyboard directional nav), A13 form participation (scroll-area is not a form control; no hidden input needed), A14 roving-tabindex (not applicable; no radio-group/toggle-group/tabs-style selection), A15 scroll-area-specific gesture (uses native pointer events, not Gesture layer), A6 ResizeObserver disposal (properly disposed in $effect cleanup at line 64-65; resizeCleanups array cleared), A6 Timer disposal (hideTimer is cleared in $effect cleanup at line 263-265; clearHideTimer called on unmount), Event listener disposal (pointerenter/leave cleanup properly returned in $effect at lines 298-303) ## Theming facts (E-bis) - magic z-index: none - magic literals: scroll-area.css:92,97,135 — opacity: 0 / opacity: 1 (should use --opacity-* or --scroll-area-opacity-*) | scroll-area.css:56,102,131 — color-mix percentages 60%, 84% (should use --scroll-area-track-opacity-* tokens) | scroll-area-provider.svelte.ts:141,145 — 'var(--scroll-area-scrollbar-size, 8px)' (8px fallback is reasonable but ideally --scroll-area-scrollbar-size is always defined) | scroll-area-provider.svelte.ts:482,487 — Math.max(ratio * track{Height|Width}, 20) (20px minimum thumb size is hardcoded; should be a token) - undeclared parts: none - roles clean: true · variants clean: true ## Tests (F) - exists: true · env: jsdom (vitest @vitest-environment jsdom) - covers: viewport overflow measurement and state props; scrollbar visibility and timer scheduling; track click scrolling; thumb drag with setPointerCapture and scroll updates; corner visibility logic; resize observer lifecycle - untested: RTL scroll direction (dir='rtl' passed but not exercised in drag tests); interaction with different type modes exhaustively (type='always'/'auto'/'scroll' touched but combined scenarios not covered); gesture cancellation edge-cases (immediate setPointerCapture timing vs moveBuffer deference not tested — not a Gesture layer concern) ## Style observations (non-blocking) - Eidos CSS is well-structured with logical nesting and comprehensive state coverage (data-state, data-orientation, data-hover, data-dragging, data-autosize). Token fallbacks are generous but some hardcoded values (opacity decimals, color-mix percentages, 20px thumb minimum) should be parameterized. - Soma provider correctly manages overflow detection, viewport measurement via ResizeObserver, scrollbar hide timers, and pointer drag. Cleanup lifecycle is sound. - Thumb positioning uses inline transform tokens (--scroll-area-thumb-width/height) which is correct for dynamic sizing.