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.
64 lines
6.6 KiB
64 lines
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 <!-- id: 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 shows `field.inputId` is ONLY written (line 534), never read by this Input provider; `opts.id.current` is 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 guard `if (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 <!-- id: 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: open
|
|
|
|
### LOW: Morfo declares Label part defaultElement='label' but soma renders <div> — time-field-003 <!-- id: time-field-003 -->
|
|
- dimension: A
|
|
- rule: Morfo declares Label part defaultElement='label' but soma renders <div>
|
|
- 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 <label> element is not used despite being declared.
|
|
- proposed-fix: Either: (1) Change morfo defaultElement to 'div' (since segmented input can't use <label for>) or (2) render <label> in soma and provide proper for/aria-labelledby wiring.
|
|
- 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:136 `defaultElement: '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>`). `defaultElement` is 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)
|