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.
svelte-kit-vice/audit/components/splitter.md

7.4 KiB

Audit: splitter

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/splitter/splitter-provider.svelte.ts sequence-audit: N/A - splitter is not a state-binding control (Checkbox/Toggle/Switch/RadioGroup pattern). It is a direct-manipulation drag component with commit-set: 'post' morfo sequence. State changes (panel sizes) are set BEFORE pointerup closes the drag; notifyResizeEnd() routes through runtime.trigger('comm

Summary

Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 1.

Findings

MEDIUM: Frontier: eidos CSS must reference morfo event names exactly; mismatch breaks sema pulse v — splitter-001

  • dimension: Frontier, Data-selector
  • rule: Frontier: eidos CSS must reference morfo event names exactly; mismatch breaks sema pulse visual feedback
  • location: src/uix/eidos/components/splitter/splitter.css:115; src/uix/morfo/components/splitter.ts:51,15
  • evidence: CSS selector [data-event='commit-resize'] but morfo defines event name: 'commit-set'. Morfo comment at line 15 confirms eidos recipe should reference data-event='commit-set' — CSS uses wrong event name.
  • impact: Sema event pulse visual feedback (handle background flash on drag commit) never fires because eidos events layer stamps data-event='commit-set' but CSS selector waits for commit-resize. User gets no visual confirmation of resize completion.
  • repro: Drag a splitter resize trigger; release. Observe no background pulse on the handle.
  • proposed-fix: Change line 115 of splitter.css from [data-splitter-resize-trigger][data-event='commit-resize'] to [data-splitter-resize-trigger][data-event='commit-set']
  • verify: [confirmed] CONFIRMED that the pulse never fires, but the cited rule/fix is partially wrong and severity is overstated. splitter.css:115 selects [data-splitter-resize-trigger][data-event='commit-resize'][data-event-phase='active']; the morfo (splitter.ts:51) names the event commit-set and the provider triggers runtime.trigger('commit-set') (provider lines 205, 97). The sema engine stamps data-event='commit-set', so the commit-resize selector never matches — the comment at css:110-111 and css:5 are stale relics of the old event name. HOWEVER the proposed fix (rename commit-resize→commit-set) is INSUFFICIENT: the morfo (splitter.ts:55) and sema cascade (sema/components/splitter.ts:49 onProvider({ eventName: 'commit-set' })) target v.partRef('provider'), so the stamp lands on the provider element [data-splitter], NOT on [data-splitter-resize-trigger]. The selector targets both the wrong event name AND the wrong element. To actually pulse the handle on commit the selector would need to key off the provider stamp (e.g. [data-splitter][data-event='commit-set'] [data-splitter-resize-trigger]) or the commit event's target would need to move. Severity downgraded HIGH→MEDIUM: this is a purely cosmetic/ornamental sema-pulse that never fired (no functional or a11y impact); the handle still gets its [data-state='dragging']/[data-dragging] active background via css:77-80 during the actual drag, so the user is not left without resize feedback.
  • fix-status: open

LOW: Theming: opacity literals forbidden; must use canonical --opacity-* token or fallback to - — splitter-003

  • dimension: Theming, Tokens
  • rule: Theming: opacity literals forbidden; must use canonical --opacity-* token or fallback to --opacity-disabled.
  • location: src/uix/eidos/components/splitter/splitter.css:35
  • evidence: Line 35: opacity: var(--splitter-disabled-opacity, 0.6);. Recipe base (recipes/base.ts) defines splitter['disabled-opacity'] = 'var(--opacity-disabled)', which is the canonical fallback. CSS uses magic literal 0.6 instead.
  • impact: Opacity value diverges from canonical token scale. If --opacity-disabled is updated system-wide, splitter disabled state won't reflect the change because it has a hardcoded literal fallback.
  • proposed-fix: Change line 35 to opacity: var(--splitter-disabled-opacity, var(--opacity-disabled)); so the fallback chains to the canonical token instead of a magic literal.
  • verify: [downgraded] CONFIRMED as token drift but DOWNGRADED MEDIUM→LOW. splitter.css:35 opacity: var(--splitter-disabled-opacity, 0.6) uses a magic opacity decimal 0.6 as the inline fallback, violating E-bis (opacity decimal → --opacity-*). Verified recipes/base.ts:4231 defines splitter['disabled-opacity'] = 'var(--opacity-disabled)', so under normal operation the recipe value wins and the 0.6 literal NEVER surfaces — it only appears if the recipe layer is entirely absent. Other consumers chain the canonical token (var(--opacity-disabled)) as their fallback, so this is a real inconsistency, but it is the lowest-impact token-drift category: cosmetic, no behavioral divergence in practice. Proposed fix is correct (chain to var(--opacity-disabled)).
  • fix-status: open

No-findings dimensions

Contract: morfo satisfies Morfo and uses as const, Contract: parts registered via runtime.part exist in morfo, Contract: data-{c}/-{part} naming, Behavior: setPointerCapture is immediate (pure handle, no child buttons), Behavior: A12 RTL keyboard via getDirectionalKeys, Behavior: A14 roving not applicable (single handle, not roving group), Behavior: A15 gesture cleanup on unmount (dragSignalFrame, pendingDragPercent cancelled), Behavior: A30 panel registration is direct (sync), not effect-wrapped for props, Behavior: A31 no O(N²) patterns (find/getPanelState are on local panels array, not global), Behavior: A33 no $state(Map/Set), Behavior: A6 requestFrame/cancelFrame properly disposed in effect cleanup, DOM: no querySelector interpolating consumer values, Frontier: soma does not import eidos, Frontier: syncAttrs true with derived props (no double-write), Theming: only standard 9 roles + space tokens referenced, Theming: no raw hex where role alias canonical, Theming: spacing uses --space-* tokens, Theming: no local z-index or canonical --z-index-* used (no z needed for splitter), Test: provider test exists, jsdom env, covers gesture drag, keyboard, collapse

Theming facts (E-bis)

  • magic z-index: none
  • magic literals: 0.6 (splitter-001-adjacent)
  • undeclared parts: none
  • roles clean: true · variants clean: true

Tests (F)

  • exists: true · env: jsdom
  • covers: panel registration and sizing; resize clamping and min/max constraints; collapse/expand with adjacent space redistribution; pointer drag (down/move/up); keyboard arrows (RTL-aware via getDirectionalKeys); keyboard Home/End (minimize/maximize); aria-valuenow/min/max projection; disabled state blocks resize
  • untested: gesture on lostpointercapture recovery; double-click toggle collapse; dragSignalFrame cleanup during unmount with active drag

Style observations (non-blocking)

  • Excellent drag-signal throttling with per-frame payload queue (72ms window)
  • Clean pointer-capture semantics for pure-handle resize (not using Gesture class)
  • Robust collapse/expand logic respects min/max constraints on both panels
  • Well-documented keyboard shortcut implementation (Home/End/ArrowKeys)

Powered by TurnKey Linux.