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/stepper.md

9.1 KiB

Audit: stepper

audit-version: 1 audited-at: 2026-06-26 scope: soma, sema (SCOPE-DRIFT → SYS-1) 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/stepper/stepper-provider.svelte.ts sequence-audit: state set at call-site (setStep line 108 before runtime.trigger line 109); morfo uses 'post' sequence for both shift-step and commit-complete events; safe pattern (call-site state tolerates 'post', no lag risk)

Summary

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

Findings

MEDIUM: SYS-1 scope-drift — stepper-001

  • dimension: A, Contract
  • rule: SYS-1 scope-drift
  • location: src/uix/morfo/components/stepper.ts:7
  • evidence: morfo scope: ['soma', 'sema'] declared, but eidos directory exists at src/uix/eidos/components/stepper/ with CSS, types, and component wrappers
  • impact: eidos layer is outside declared scope; validator may not catch recipe inconsistencies in stepper recipe color/spacing tokens
  • proposed-fix: Add 'eidos' to morfo scope array: scope: ['soma', 'sema', 'eidos']
  • verify: [confirmed] CONFIRMED. morfo at src/uix/morfo/components/stepper.ts:7 declares scope: ['soma', 'sema']. A full eidos implementation exists: src/uix/eidos/components/stepper/ (stepper.css 6.8KB, types.ts, index.ts, 11 .svelte wrappers) plus a recipe at src/uix/eidos/lib/recipes/base.ts:3152 (stepper: {). The Morfo.scope doc (types.ts:798-799) defines scope as 'Layers that implement this component.' Reference morfos with eidos dirs DO declare it: toggle-group/tabs/radio-group/checkbox/accordion all have scope: ['soma', 'sema', 'eidos']. Stepper omits 'eidos' despite a complete eidos layer — genuine SYS-1 scope-drift. MEDIUM holds.
  • fix-status: fixed (212624e0)

MEDIUM: Morfo.expression missing for events + scope:['sema'] with existing sema pack — stepper-005

  • dimension: A, Contract
  • rule: Morfo.expression missing for events + scope:['sema'] with existing sema pack
  • location: src/uix/morfo/components/stepper.ts:4-32
  • evidence: The morfo declares events: [...] (shift-step, commit-complete; lines 12-32) and scope: ['soma', 'sema'] (line 7), but declares NO expression field. A sema pack exists at src/uix/sema/components/stepper.ts. The Morfo.expression doc (src/uix/morfo/types.ts:835-837) states: 'If a morfo declares events[] AND scope: ["sema"], expression SHOULD be set explicitly. The morfo-coverage check warns when it's missing.' Since a pack ships, the correct value is expression: 'pack'.
  • impact: The morfo-coverage check (npm run morfo:vocabulary) warns on the missing expression field; the absence of the declaration makes 'has a sema pack' indistinguishable from 'forgot to write one'. Contract incompleteness, not runtime breakage.
  • proposed-fix: Add expression: 'pack' to stepperMorfo (a sema pack exists at src/uix/sema/components/stepper.ts).
  • verify: [verifier-added] added by adversarial verify pass
  • fix-status: open

LOW: Trigger part data-orientation undeclared but emitted — stepper-002

  • dimension: A, Contract
  • rule: Trigger part data-orientation undeclared but emitted
  • location: src/uix/soma/components/stepper/stepper-provider.svelte.ts:350
  • evidence: Soma emits 'data-orientation': this.provider.opts.orientation.current on Trigger, but morfo Trigger part (lines 94-138) does not declare data-orientation in its data array. Item part declares it (line 84-87), Separator declares it (line 174-178), but Trigger does not.
  • impact: Contract asymmetry: Trigger emits an attribute not in its morfo spec. Provider comment at line 343-344 acknowledges this is 'Soma-owned' and intentional, but represents a 2-of-3 rule violation (morfo part missing declaration)
  • proposed-fix: Either (a) add data-orientation to Trigger morfo part data array with values ['horizontal', 'vertical'] and value: v.propRef('orientation'), or (b) document this as sanctioned soma-only attribute in design docs
  • verify: [downgraded] Emission CONFIRMED, severity DOWNGRADED to LOW. Provider stepper-provider.svelte.ts:350 emits 'data-orientation': this.provider.opts.orientation.current on Trigger; Trigger morfo data array (stepper.ts:103-107) declares only data-state/data-current/data-complete/data-incomplete — no data-orientation (sibling Item lines 84-87 and Separator lines 175-178 DO declare it). So the attribute is real and genuinely undeclared. BUT: (a) the Trigger part is registered with NO props: source block and NO syncAttrs (lines 268-272), so the morfo physically cannot source data-orientation for this part — the soma must supply it; (b) it is explicitly documented as intentional at the provider comment lines 343-344 ('the morfo declares these without a value source (documentation only) or not at all (data-orientation)'). No double-write risk (no syncAttrs on Trigger). This is a contract-documentation gap, not user-visible wrong behavior. LOW. Note the SAME class also occurs on the List part (provider line 174 emits data-orientation; List morfo data array is empty, stepper.ts:59) — see newFindings.
  • fix-status: open

LOW: data-disabled undeclared on Trigger but emitted — stepper-004

  • dimension: A, Contract
  • rule: data-disabled undeclared on Trigger but emitted
  • location: src/uix/soma/components/stepper/stepper-provider.svelte.ts:349
  • evidence: Soma emits 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled) on Trigger (line 349), but Trigger morfo part does not declare data-disabled. PrevTrigger and NextTrigger DO declare it (lines 220, 231). This is documented intentionally at line 343-344.
  • impact: Asymmetry: Trigger emits data-disabled (computed isDisabled state) while morfo doesn't declare it. Intentional design per soma comment, but represents undeclared attribute.
  • proposed-fix: Add data-disabled to Trigger morfo data array with severity 'optional' to match PrevTrigger/NextTrigger, or document as exception
  • verify: [downgraded] Emission CONFIRMED, severity confirmed at LOW. Provider line 349 emits 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled) on Trigger; Trigger morfo data array (stepper.ts:103-107) does not declare data-disabled (PrevTrigger line 220 and NextTrigger line 231 DO). Same class as stepper-002: documented-intentional (comment lines 343-344), no registered source on the Trigger part (the value is composite isDisabled = disabled || !canSelectStep, richer than a plain prop-truthy condition), no syncAttrs so no double-write. The proposed fix (declare with severity 'optional', like PrevTrigger/NextTrigger) is reasonable but cosmetic. LOW holds.
  • fix-status: open

LOW: List part emits data-orientation not declared in morfo — stepper-006

  • dimension: A, Contract
  • rule: List part emits data-orientation not declared in morfo
  • location: src/uix/soma/components/stepper/stepper-provider.svelte.ts:174
  • evidence: StepperListProvider.props emits 'data-orientation': this.provider.opts.orientation.current (line 174, with the provider's own comment line 173: 'Soma-only: the morfo's List part declares no data-orientation'). The List morfo part data array is empty: data: [] at src/uix/morfo/components/stepper.ts:59. The List part is registered without syncAttrs (provider lines 158-162), so no double-write — but the attribute is genuinely undeclared in the contract. Same class as stepper-002 (Trigger) but on a different part, missed by the candidate set.
  • impact: Contract-documentation gap: an emitted data-attr the morfo does not promise. eidos CSS that selects [data-stepper-list][data-orientation='vertical'] would have no contract backing. No user-visible breakage.
  • proposed-fix: Declare data-orientation on the List morfo part (values ['horizontal','vertical'], value v.propRef('orientation')) to match Item/Separator, OR document the soma-only attr in design docs as is done for the Trigger.
  • verify: [verifier-added] added by adversarial verify pass
  • fix-status: open

No-findings dimensions

B-sequence, B-roving-tabindex, B-keyboard-RTL, B-gesture, B-form-hidden-input, B-O(N²), C-DOM-selector, D-soma-imports-eidos, D-double-write, E-TSC-composition, E-theming, F-test-coverage, G-redundancy

Theming facts (E-bis)

  • magic z-index: none
  • magic literals: none
  • undeclared parts: none
  • roles clean: true · variants clean: true

Tests (F)

  • exists: true · env: jsdom
  • covers: step-tracking; item-states; linear-rules; keyboard-navigation; trigger-registration; prev-next-triggers; completion-state
  • untested: sequence-event-timing; data-attribute-edge-cases

Style observations (non-blocking)

  • Recipe uses canonical token variables throughout (--stepper-gap-, --stepper-font-size-, etc.)
  • Color roles properly reference --color-* semantic tokens (no raw hex)
  • All spacing and sizing via tokens, no magic literals
  • TSC accent tokens (_accent-solid, _accent-track, _accent-border, _accent-text) properly cascaded per color role in recipes/base.ts

Powered by TurnKey Linux.