8.9 KiB
Audit: date-field
audit-version: 1 audited-at: 2026-06-26 scope: ['soma', 'sema'] (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: G:\dev\svelte\vicen\src\uix\soma\components\date-field\date-field-provider.svelte.ts field-family (A13/A24-26/A30): A13: form-participating hidden input correctly implemented (lines 1011-1040), renders with name/value/required/disabled when name is set. A24: readonly-without-value warning implemented via soma.logger.warn (lines 355-370), guards against misconfiguration when readonlySegments set without value. A26: onbeforeinput preventDefault required for segmented contenteditable — CORRECT implementation at li
Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 0.
Findings
MEDIUM: SYS-1 (scope-drift) — date-field-001
- dimension: scope
- rule: SYS-1 (scope-drift)
- location: morfo line 7 + eidos dir exists
- evidence: Morfo declares scope: ['soma', 'sema'] (src/uix/morfo/components/date-field.ts line 7) but eidos recipe dir src/uix/eidos/components/date-field/ and CSS src/uix/eidos/components/date-field/date-field.css both exist. Scope MUST include 'eidos' when the recipe and CSS layer are present.
- impact: Contract validator may not catch visual-layer rules as part of the component's declared surface area.
- proposed-fix: Update morfo scope from ['soma', 'sema'] to ['soma', 'sema', 'eidos']
- verify: [confirmed] CONFIRMED at MEDIUM. morfo
src/uix/morfo/components/date-field.ts:7declaresscope: ['soma', 'sema'](grep verified), while the eidos layer exists: recipe block'date-field':atsrc/uix/eidos/lib/recipes/base.ts:1172and CSSsrc/uix/eidos/components/date-field/date-field.css(read in full, 228 lines). This matches the documented systemic SYS-1 scope-drift (MEDIUM). NOTE: the project's stated REFERENCE component NumberField has the IDENTICAL condition — number-field.ts:7 also declaresscope: ['soma', 'sema']yetsrc/uix/eidos/components/number-field/exists — so this is a true systemic pattern for the field family, not a date-field-specific defect. Severity stays MEDIUM per SYS-1; confidence high that the condition holds, with the caveat that whether 'eidos' is REQUIRED in scope for these components is a project-wide convention question. - fix-status: fixed (
212624e0)
MEDIUM: Test coverage gap (field-family paths) — date-field-003
- dimension: tests
- rule: Test coverage gap (field-family paths)
- location: date-field-provider.svelte.test.ts (275 lines)
- evidence: Test suite covers: segment helpers (120-165), validation (174-198), segment commit (200-220), time-segment sync (222-240), granularity filtering (242-274). MISSING: (1) hidden-input render when name is set; (2) readonly-without-value warning fired; (3) commit-set/commit-reset event emission; (4) keyboard navigation per APG (ArrowUp/Down/Left/Right/Backspace); (5) Field parent integration (inputId wiring).
- impact: High-risk field-family paths untested: form submission via hidden input, readonly misconfiguration guards, and event-driven workflows.
- proposed-fix: Add test cases for: shouldRender hidden input, soma.logger.warn call when readonlySegments + undefined value, commit event firing on segment completion, keyboard segment navigation (at least one key per segment type), and Field parent inputId registration.
- verify: [confirmed] CONFIRMED at MEDIUM (F dimension — high-risk field-family test gap). The test file
date-field-provider.svelte.test.ts(read in full, 275 lines) covers ONLY: segment helpers (initSegmentStates/isAcceptableSegmentKey/getFirstSegment/handleSegmentNavigation, 120-165), validation min/max/custom (174-198), incremental segment commit (200-220), padded date+time sync (222-240), granularity filtering (242-274). UNTESTED high-risk paths that DO exist in the provider: (1) hidden-input render —DateFieldHiddenInputProvider.shouldRender(provider line 1022) and its A13 ISOvalue/name/requiredprops (1027-1040) are never exercised; (2) readonly-without-value A24 warn — the$effectcallingsoma.logger.warn(provider 354-369) is never triggered, even though the harness already stubslogger.warn: vi.fn()(test line 72), so the assertion would be trivial; (3) commit event emission —runtime.trigger('commit-set'|'commit-reset')(provider line 785) untested; (4) keyboard segment navigation per APG (ArrowUp/Down/Backspace/number-key handlers, provider 1108-1296) untested at provider level; (5) Field parent inputId wiring (DateFieldInputProvider $effect, provider 932-936) untested. Severity MEDIUM (missing tests for high-risk paths); confidence high that these paths are real and uncovered. - fix-status: open
MEDIUM: A30 — inputId registered via $effect instead of direct constructor assignment (SYSTEMIC: date/time/color-field)
- dimension: B
- rule: A30 (child→parent id registration MUST be a direct constructor assignment, not
$effect) - location: date-field-provider.svelte.ts:932-936 (
$effect(() => { const field = this.provider.field; if (!field) return; field.inputId.current = opts.id.current; })) - evidence: lead-confirmed by direct read — the DateFieldInput provider wraps the
field.inputIdregistration in a$effect, whereas the project reference NumberField does it as a direct constructor guard (number-field-provider.svelte.ts:585-587:if (this.provider.field) { this.provider.field.inputId.current = opts.id.current; }). time-field (531-535) and color-field (521-525) use the identical$effect— SYSTEMIC. - impact: no loop today (the Input provider only WRITES
field.inputIdand never reads it back, andopts.idis a stable framework id), so the A30 page-freeze hazard is not live — but it is contrary to A30 doctrine and inconsistent with the NumberField reference; a future reader who makes the registration two-way (or reads inputId in the child) would reintroduce the freeze. - repro: static — compare date/time/color-field Input providers (
$effect) vs number-field (direct). - proposed-fix: replace the
$effectwith a direct constructor assignment guarded byif (this.provider.field), matching NumberField. Apply across date-field, time-field, color-field. - verify: [lead-added] the date-field agent's no-findings claim falsely listed "A30 direct id-wiring" as clean; the verify on time-field-001 + lead reads of date-field:932-936 / color-field:521-525 confirm the
$effectpattern. Elevated to a systemic MEDIUM. - fix-status: open
No-findings dimensions
A (Contract morfo), B (keyboard, A17 focus, A33, A35, A6 cleanup, TWO-MOMENTS — note: A30 id-wiring is NOT clean, see date-field-A30), C (DOM-selector safety), D (Frontier soma→eidos), G (Redundancy)
Theming facts (E-bis)
- magic z-index: none
- magic literals: 'day' | 'month' | 'year' | 'hour' | 'minute' | 'second' | 'dayPeriod' | 'literal' | 'timeZoneName'
- undeclared parts: none
- roles clean: true · variants clean: true
- label-font (one step below input?): Label calc(1em - (var(--font-size-md) - var(--font-size-sm))) produces correct one-step scaling at md but degrades at sm input size (line 109) — see finding date-field-002.
Tests (F)
- exists: true · env: jsdom (vitest)
- covers: segment state initialization; input acceptance (numeric + nav keys only); DOM segment reading (getValueFromSegmentsDOM); segment focus navigation (ActiveDom); validation (custom/min/max); segment commit on fill; time segment padding; granularity filtering (hour → no minute/second)
- untested: hidden-input conditional render when name is set; readonly-without-value warning logger.warn() call; commit-set and commit-reset events firing; keyboard handling per APG (all arrow directions, backspace per segment); Field parent integration (inputId registration); readonly segment visual treatment; cross-segment cascades (month→day clamp, hour→dayPeriod flip, dayPeriod→hour AM/PM); year segment typeahead logic (pressedKeys tracking)
Style observations (non-blocking)
- Recipe tokens correctly reference --space-, --radius-, --font-size-, --color- (lines 1173-1222). No bare hex/rgb/oklch in recipe. No --opacity-* barefoot decimals (opacity-disabled token used, line 1222). Z-index not used in date-field CSS. Theming uses 9 roles (primary/secondary/neutral/affirm/fulfill/risk/threat/loss + implicit default). Label font-weight is var(--font-weight-regular), not emphasized. Segment readonly uses underline dotted var(--color-content-muted) + surface-overlay bg (consistent field family).
- Invalid border color uses var(--color-risk-border) per theming rule (line 1204).
- Focus ring uses outline (not box-shadow post migration, line 147 CSS). Flush offset (0) survives forced-colors.