8.5 KiB
Audit: color-picker
audit-version: 1 audited-at: 2026-06-26 scope: (SCOPE-DRIFT → SYS-1) method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work). provider: src/uix/soma/components/color-picker/color-picker-provider.svelte.ts
Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 2. systemic hits: SYS-1 (scope-drift MEDIUM); SYS-5 (area thumb hardcoded z-index HIGH). composition (A27): Composes (A27): Popover + ColorField + Slider via shared writableActive refs. ChannelSlider composes SliderProvider underneath with channel-aware bridging (gradient + setChannel). ColorField parts (Input/Segment/FormatSelect) re-exported as ChannelInput/ChannelSegment/FormatSelect with data-color-picker identity attrs overlaid. Shared state wiring correct: sharedValue, sharedFormat, sharedOpen passed to both ColorPickerProvider and PopoverProvider/ColorFieldProvider. Content part re-exported from Popover (registers on Popover runtime, not picker runtime).
Findings
MEDIUM: SYS-1 scope-drift — color-picker-001
- dimension: A
- rule: SYS-1 scope-drift
- location: src/uix/morfo/components/color-picker.ts:7
- evidence: colorPickerMorfo declares scope: ['soma', 'sema'] but eidos directory exists at src/uix/eidos/components/color-picker/ containing recipe CSS and multiple eidos component wrappers (.svelte files)
- impact: Morfo scope contract is incomplete. Downstream tooling expecting strict scope adherence will find undeclared layer.
- proposed-fix: Update morfo scope to ['soma', 'sema', 'eidos']
- verify: [confirmed] Confirmed SYS-1 scope-drift. color-picker.ts:7 reads
scope: ['soma', 'sema'],yet a full eidos implementation exists at src/uix/eidos/components/color-picker/ (color-picker.css recipe + 25+ .svelte wrappers per Glob).'eidos'IS a valid Layer (src/uix/types.ts:17export type Layer = 'soma' | 'sema' | 'eidos';), and morfoscope: readonly Layer[](types.ts:799). date-picker.ts:13 has the identicalscope: ['soma', 'sema'], so this is the documented systemic SYS-1 across the picker family. MEDIUM is correct. - fix-status: fixed (
212624e0)
MEDIUM: Inert 'open' event — color-picker-003
- dimension: B
- rule: Inert 'open' event
- location: src/uix/morfo/components/color-picker.ts:14-31
- evidence: Morfo declares name:'open' event with target=partRef('content') and sequence:'pre'. No runtime.trigger('open',...) call found in provider. Comment on close event (line 38-39) documents pattern: provider sets opts.open=false and lets popover unmount, never fires the event explicitly.
- impact: Consumer code declaring open event handlers will not receive them. The event exists only to satisfy schema validators. This is documented for pickers (known pattern like date-picker/time-picker) but represents a contract mismatch.
- proposed-fix: Document in morfo comment that 'open' event is not fired (inert, exists for contract symmetry); or fire it on popover open (requires Popover composition to surface trigger point)
- verify: [confirmed] Confirmed inert event. Morfo declares
name: 'open'(color-picker.ts:14-32) with sequence 'pre' targeting content; grep fortrigger('open'across src/uix/soma/components/color-picker returns NO matches. The provider only ever fires 'close' (via triggerClose, lines 349-375), 'handle-pick'/'handle-drag'/'commit-set'. The morfo's own comment (lines 10-13 'They drive Footer...' and 38-41 'today the provider just sets opts.open = false and lets the popover unmount') documents the known picker-family inert pattern. 'commit-reset' is also inert (never triggered) — candidate only flags 'open', which is in-scope and accurate. MEDIUM matches the audit's 'MEDIUM at most' for inert events. - fix-status: open
LOW: Magic z-index literals not in STATIC_Z_INDEX scale — color-picker-002
- dimension: E-bis
- rule: Magic z-index literals not in STATIC_Z_INDEX scale
- location: src/uix/eidos/components/color-picker/color-picker.css:367,557
- evidence: z-index: 2 on [data-color-picker-area-thumb]:367, z-index: 1 on [data-color-picker-swatch-indicator]:557. Project STATIC_Z_INDEX scale: base=0, raised=1, sticky=100, dropdown=300, popover=400, tooltip=500, modal=700, toast=900. These values (1,2) accidentally collide with low scale but are not intentionally using that scale.
- impact: Fragile stacking context: undocumented magic literals break auditing of depth relationships. If future components add z-index:3 the priority breaks.
- proposed-fix: Use var(--z-index-raised) or var(--z-index-sticky) from canonical scale instead of bare integers
- verify: [downgraded] Literals confirmed present — color-picker.css:367
z-index: 2;(area-thumb) and :557z-index: 1;(swatch-indicator). But the HIGH severity and the SYS-2 mapping are wrong. SYS-2 targets bare 60-99 integers that collide with the depth-PLANE scale (--z-index-dropdown=300/popover=400/modal=700/etc). These values form a self-contained LOCAL sibling stack within the picker subtree: grep shows the file uses only -1 (background layers, :433/:402/:412), 1 (indicator over swatch), 2 (thumb over area-background). The canonical STATIC_Z_INDEX scale is for cross-component depth planes, not intra-component layering; substitutingvar(--z-index-sticky)(=100) as the proposed fix would be semantically wrong for a thumb-over-its-own-background. No real stacking-context fragility (no other component reaches into this subtree's z-context). Downgrade to LOW (cosmetic/token-tidiness at most); not a SYS-2 magic-z violation. - fix-status: open
LOW: Test environment jsdom with interaction-heavy keyboard — color-picker-004
- dimension: F
- rule: Test environment jsdom with interaction-heavy keyboard
- location: src/uix/soma/components/color-picker/color-picker-provider.svelte.test.ts:1
- evidence: @vitest-environment jsdom. Tests cover: ColorPickerAreaThumbProvider.onkeydown (ArrowRight at line 227) and keyboard route logic (HOME/END/PAGE_UP/PAGE_DOWN at lines 905-915 in provider). No Playwright/browser test confirming area keyboard in real DOM.
- impact: Area thumb keyboard handling (Arrow x4 + Home/End + PageUp/PageDown) is interaction-heavy and DOM-geometry-dependent. jsdom may not accurately simulate getBoundingClientRect or pointer geometry used in handlePointerMove / calculateChannelValue.
- proposed-fix: Add browser-level test (Playwright) for area keyboard + pointer drag to verify geometry math in real DOM
- verify: [downgraded] Confirmed jsdom env (test file line 1
// @vitest-environment jsdom) and the provider has interaction-heavy area logic. This is the SYS-3 baseline (jsdom-only / kbd-untested) so a real F-gap exists. BUT the candidate's core rationale is partly wrong: the area-thumb keyboard route (onkeydown, provider lines 882-931) is PURE arithmetic over getChannelRange/getColorChannel — it never reads getBoundingClientRect, so it IS faithfully exercisable in jsdom (test does exercise ArrowRight et al.). Only the POINTER path (handlePointerMove, lines 764-777, consumes DOMRect) is geometry-dependent and genuinely under-covered in jsdom. So the gap is narrower (pointer-drag geometry, not keyboard) than the finding states. Downgrade to LOW per SYS-3. - fix-status: open
No-findings dimensions
C, D
Theming facts (E-bis)
- magic z-index: z-index: 2 | z-index: 1
- magic literals: 16px on area-thumb-size | 11rem on area-height | 1.25rem/1.5rem/1.75rem on swatch sizes
- undeclared parts: none
- roles clean: true · variants clean: true
Tests (F)
- exists: true · env: jsdom
- covers: channel value preservation; area thumb pointer move + keyboard; area channel override; setChannel + commitChange; swatch select + closeOnSelect
- untested: area thumb keyboard routes (Home/End/PageUp/PageDown) in browser; pointer drag geometry in real DOM; popover open/close animations; dismissal outside; eyedropper API integration
Style observations (non-blocking)
- color-picker.css uses CSS containment (contain: layout style) on area to prevent sibling layout shifts during drag
- Transparency checker pattern re-used via CSS vars across multiple elements
- Area overlays (saturation/brightness) use pseudo-elements to avoid conflicts with inline background
- Swatch group uses flex 1 1 0 + aspect-ratio to shrink/grow equally while staying square