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.
60 lines
7.0 KiB
60 lines
7.0 KiB
|
4 months ago
|
# Audit: time-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/time-picker/time-picker-provider.svelte.ts
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 0.
|
||
|
|
systemic hits: SYS-1 (scope-drift); SYS-3 (jsdom-only for interaction-heavy component).
|
||
|
|
|
||
|
|
## Findings
|
||
|
|
### MEDIUM: SYS-1 scope-drift: eidos recipe directory exists but morfo.scope omits 'eidos' — time-picker-001 <!-- id: time-picker-001 -->
|
||
|
|
- dimension: A - Contract (morfo)
|
||
|
|
- rule: SYS-1 scope-drift: eidos recipe directory exists but morfo.scope omits 'eidos'
|
||
|
|
- location: src/uix/morfo/components/time-picker.ts:13
|
||
|
|
- evidence: scope: ['soma', 'sema'] declared, but src/uix/eidos/components/time-picker/ exists with 11212-byte CSS, components, types, index.ts
|
||
|
|
- impact: Scope metadata is incorrect, confusing consumers about which layer(s) implement time-picker. Known systemic issue but should be corrected.
|
||
|
|
- proposed-fix: Change scope to ['soma', 'sema', 'eidos'] to match actual implementation structure
|
||
|
|
- verify: [confirmed] CONFIRMED. morfo line 13: `scope: ['soma', 'sema']` — omits 'eidos'. The eidos dir is real: I read src/uix/eidos/components/time-picker/time-picker.css (308 lines of recipe CSS). This is the catalogued systemic SYS-1 scope-drift, MEDIUM. Severity correct.
|
||
|
|
- fix-status: open
|
||
|
|
|
||
|
|
### MEDIUM: INERT EVENTS: morfo declares 'open' event that provider never fires — time-picker-002 <!-- id: time-picker-002 -->
|
||
|
|
- dimension: A - Contract (morfo), B - Behavior (soma)
|
||
|
|
- rule: INERT EVENTS: morfo declares 'open' event that provider never fires
|
||
|
|
- location: src/uix/morfo/components/time-picker.ts:31-50 (declared) vs src/uix/soma/components/time-picker/time-picker-provider.svelte.ts:240 (only close is triggered)
|
||
|
|
- evidence: Morfo line 33-50 declares: 'open' event with semantic.family='emerge', verb='open', sequence='pre', targeting clock part. Provider line 240 only: `void this.runtime.trigger('close', ...)` — no 'open' trigger exists anywhere in soma/time-picker.
|
||
|
|
- impact: Event declared in contract exists only to pass schema validation. Consumers expecting 'open' event will never receive it, creating a contract/2-of-3 violation. The event must either be fired on the open→true edge or removed from morfo.
|
||
|
|
- proposed-fix: Either (a) add runtime.trigger('open') when open transitions false→true with appropriate semantics, or (b) remove the 'open' event from morfo if unsupported
|
||
|
|
- verify: [confirmed] CONFIRMED as inert event, MEDIUM (correct ceiling per the picker-family INERT-EVENT rubric). morfo lines 33-50 declare the `open` event (family 'emerge', verb 'open', sequence 'pre', commits data-state='open'). A grep of the whole time-picker soma dir for `runtime.trigger` returns exactly ONE hit — line 240 `void this.runtime.trigger('close', {...})`. No `runtime.trigger('open'` exists anywhere; opening is delegated to the composed Popover and the provider only sets `opts.open.current`. So `open` is fired by nothing. NOTE the auditor undercounted: `commit-reset` (morfo lines 71-80) is ALSO inert — `clear()` at provider lines 218-220 sets value to undefined and triggers nothing. Two of the three declared events (`open`, `commit-reset`) are inert; only `close` is wired. The finding's specific `open` claim is accurate; severity MEDIUM stands.
|
||
|
|
- fix-status: open
|
||
|
|
|
||
|
|
### MEDIUM: SYS-3: test environment is jsdom-only; interaction-heavy component untested in browser — time-picker-003 <!-- id: time-picker-003 -->
|
||
|
|
- dimension: F - Tests
|
||
|
|
- rule: SYS-3: test environment is jsdom-only; interaction-heavy component untested in browser
|
||
|
|
- location: src/uix/soma/components/time-picker/time-picker-provider.svelte.test.ts:1
|
||
|
|
- evidence: Test file begins with '// @vitest-environment jsdom'. Component requires interaction testing: slider drag (hour/minute/second), day-period toggle radio change, modal vs inline mode behavior (outside-click dismissal), commit/cancel/clear footer actions, Escape/Tab keyboard handling on the dialog (clock part declares keyboard handlers at morfo line 192-196).
|
||
|
|
- impact: Keyboard interactions (Escape to close, Tab focus trap, roving focus within sliders) and visual state transitions (modal lock, inline auto-close) are untested. The component has complex state coordination (shared refs across TimePickerProvider/PopoverProvider/TimeFieldProvider) that may diverge under user interaction. Risk of focus management bugs and modal/inline mode mismatches.
|
||
|
|
- proposed-fix: Add Playwright test suite (time-picker-provider.svelte.e2e.ts or similar) covering: (1) slider drag value changes, (2) day-period toggle in 12-hour mode, (3) modal mode: Escape does not close, outside-click does not close, Clear/Cancel/Close buttons work, (4) inline mode: Escape closes, outside-click closes, (5) focus trap and roving focus within clock
|
||
|
|
- verify: [downgraded] DOWNGRADED HIGH->MEDIUM. Test file line 1 confirms `// @vitest-environment jsdom` — the env claim is true. But HIGH overstates it: this is the catalogued systemic SYS-3 (jsdom-only / kbd-untested), which the rubric rates as 'missing test high-risk path' = MEDIUM, not HIGH. Crucially, the keyboard handlers the auditor lists (Escape/Tab at morfo lines 192-196, focus trap, roving slider focus) are NOT implemented in THIS provider — it is a thin coordinator (the .svelte.ts has only the provider part + value getters/setters + commit/cancel/clear + triggerClose). Escape/Tab/dismissal/focus-trap live in the composed Popover and PickerShell, which have their own tests. So the untested surface here is genuinely thin (the existing jsdom tests already exercise hour/minute/second/dayPeriod math in both 12h and 24h). Real but MEDIUM.
|
||
|
|
- fix-status: open
|
||
|
|
|
||
|
|
## No-findings dimensions
|
||
|
|
C - DOM-selector, D - Frontier (eidos→soma), G - Redundancy
|
||
|
|
|
||
|
|
## Theming facts (E-bis)
|
||
|
|
- magic z-index: none
|
||
|
|
- magic literals: 14rem | 0.875rem | 2px | 1px | 4px
|
||
|
|
- undeclared parts: none
|
||
|
|
- roles clean: true · variants clean: true
|
||
|
|
|
||
|
|
## Tests (F)
|
||
|
|
- exists: true · env: jsdom
|
||
|
|
- covers: TimePickerProvider.create; hour/minute/second setters; 12-hour/24-hour cycle mapping; day-period toggle; valueOnOpen snapshot capture
|
||
|
|
- untested: slider drag interaction; modal vs inline mode behavior; Escape/Tab keyboard handling; outside-click dismissal; commit/cancel/clear button interactions; focus trap and roving focus
|
||
|
|
|
||
|
|
## Style observations (non-blocking)
|
||
|
|
- CSS imports time-field and slider recipes before time-picker.css, allowing correct layering. Per-channel accent palette (hour=primary, minute=secondary, second=neutral) is well-designed for visual hierarchy. Portal boundary acknowledged in line 260-262 comment explaining why inherited custom properties don't cross (good practice note).
|
||
|
|
- Composition structure is clean: Provider > Input + Trigger > Portal > Content > Clock > Sliders + DayPeriodToggle + Footer. Matches date-picker pattern.
|