6.6 KiB
Audit: time-field
audit-version: 1 audited-at: 2026-06-26 scope: (SCOPE-DRIFT → SYS-1) method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL lead-verified. Clean-check pseudo-findings dropped. B4 ground-truth: segmented date/time/color-field register inputId via $effect (SYS-A30-EFFECT, no loop); number-field is the direct-assignment reference. provider: src/uix/soma/components/time-field/time-field-provider.svelte.ts field-family (A13/A24-26/A30): { "A13_hidden_input": "Hidden input correctly carries name (from Input provider) and ISO value. Renders only when name is set. CORRECT.", "A24_readonly_without_value": "Readonly-without-value warning implemented via $effect (lines 233-247). Guards against spam with lastWarnedKey. CORRECT.", "A26_segmented_contenteditable": "onbeforeinput preventDefault present in sharedSegmentAttrs (line 334
Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 1.
Findings
MEDIUM: A30: child->parent id registration (inputId) must be DIRECT assignment in constructor, NEV — time-field-001
- dimension: B
- rule: A30: child->parent id registration (inputId) must be DIRECT assignment in constructor, NEVER $effect
- location: src/uix/soma/components/time-field/time-field-provider.svelte.ts:531-535
- evidence: $effect(() => { const field = this.provider.field; if (!field) return; field.inputId.current = opts.id.current; });
- impact: $effect child->parent edge can loop when InputProvider reads from Time-field state and writes to parent Field state, causing page freeze on mount if Field's reactivity depends on inputId changes.
- proposed-fix: Move the inputId assignment to the constructor, directly after line 524 (this.provider.setFieldNode). Use: if (this.provider.field) { this.provider.field.inputId.current = opts.id.current; }
- verify: [downgraded] The write IS in a $effect — confirmed at time-field-provider.svelte.ts:531-535:
$effect(() => { const field = this.provider.field; if (!field) return; field.inputId.current = opts.id.current; }). But the HIGH/page-freeze claim is NOT supported. (1) NO loop exists: grep showsfield.inputIdis ONLY written (line 534), never read by this Input provider;opts.id.currentis a stable framework id, so the $effect cannot oscillate. The A30 freeze hazard needs a child->parent write feeding parent reactivity the child re-reads — that closed cycle is absent. (2) Systemic, not a time-field defect: date-field-provider.svelte.ts:932-936 and color-field-provider.svelte.ts:521-525 use the IDENTICAL $effect pattern. (3) Real but minor: the project's REFERENCE number-field-provider.svelte.ts:585-587 uses a plain constructor guardif (this.provider.field) { this.provider.field.inputId.current = opts.id.current; }, so direct constructor assignment IS the cleaner doctrine and the proposed fix is correct. Downgraded to MEDIUM — A30 doctrinal deviation (prefer direct assignment), no freeze. - fix-status: open
MEDIUM: SYS-1: eidos recipe directory exists but morfo 'scope' omits 'eidos' SCOPE-DRIFT — time-field-002
- dimension: A
- rule: SYS-1: eidos recipe directory exists but morfo 'scope' omits 'eidos' SCOPE-DRIFT
- location: src/uix/morfo/components/time-field.ts:7
- evidence: scope: ['soma', 'sema'] — but src/uix/eidos/components/time-field/ directory exists with CSS and components
- impact: Scope declaration is incomplete. The eidos layer provides visual recipes, but morfo declares scope without 'eidos', causing contract validators to miss eidos-layer violations.
- proposed-fix: Update line 7 to: scope: ['soma', 'sema', 'eidos'],
- verify: [confirmed] Confirmed. time-field.ts:7 declares
scope: ['soma', 'sema']— omits 'eidos'. The eidos layer exists in full: Glob of src/uix/eidos/components/time-field/ returns time-field.css, time-field.svelte, time-field-input.svelte, time-field-hidden-input.svelte, time-field-segment.svelte, time-field-label.svelte, types.ts, index.ts, README.md. Classic SYS-1 scope-drift; contract validators won't reach the eidos layer. Fix: add 'eidos' to the scope array. - fix-status: fixed (
212624e0)
LOW: Morfo declares Label part defaultElement='label' but soma renders — time-field-003
- dimension: A
- rule: Morfo declares Label part defaultElement='label' but soma renders
- location: src/uix/soma/components/time-field/components/time-field-label.svelte:35
- evidence: Morfo line 152: defaultElement: 'label' | Soma line 35: <div {...mergedProps}>
- impact: Contract element mismatch. Although functional (the div has onclick focus behavior), morfo specifies 'label' but soma deviates. The semantic
- proposed-fix: Either: (1) Change morfo defaultElement to 'div' (since segmented input can't use
- verify: [downgraded] Element mismatch is real but lower impact than MEDIUM. Confirmed: morfo time-field.ts:152 declares Label
defaultElement: 'label'; time-field-label.svelte:35 renders<div {...mergedProps}>. Systemic — date-field is identical (date-field.ts:136defaultElement: 'label'vs date-field-label.svelte:35<div>), and color-field is the consistent counter-example (color-field.ts:69 'label' + color-field-label.svelte:35<label>).defaultElementis advisory and the part archetype is 'label'; a real<label for>cannot target a composite spinbutton group, so the<div>is an intentional a11y choice (it carries onclick focus). Downgraded to LOW — element naming/contract drift, not a behavioral or a11y break. The candidate's two proposed options are both valid. - fix-status: open
No-findings dimensions
C, D, E, E-bis, F, G
Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
- label-font (one step below input?): Label font-size correctly one typographic step below input. CSS line 101: calc(1em - (var(--font-size-md) - var(--font-size-sm))) resolves to sm when input is md. CORRECT.
Tests (F)
- exists: true · env: jsdom
- covers: validation (custom, min, max constraints); segment-fill commit logic (all segments required before value commits); 12-hour display sync (hour padding, dayPeriod conversion)
- untested: keyboard routes (ArrowUp/Down, Backspace, ArrowLeft/Right segment nav); readonly-without-value warning emit (A24 warning spam guard); Field composition inputId registration (A30); onbeforeinput preventDefault behavior (A26); hidden input form submission with name propagation (A13)