You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
41 lines
6.3 KiB
41 lines
6.3 KiB
|
4 months ago
|
# 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 <!-- id: 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.
|