28 KiB
Audit Summary — rolling
summary-version: 8 — AUDIT COMPLETE updated-at: 2026-06-26 batches-complete: 8 of 8 — 124/124 components audited (words/palabras/chronos excluded per user directive) (B1 overlays · B2 menus/overlays · B3 pickers+calendar · B4 fields · B5 collections+data · B6 disclosure+controls · B7 actions+feedback+misc · B8 eidos-only primitives: layout/typography/visual/composite+service/infra)
Counts by severity (FINAL — cumulative B1..B8)
| Severity | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | TOTAL |
|---|---|---|---|---|---|---|---|---|---|
| CRITICAL | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| HIGH | 8 | 2 | 0 | 0 | 6 | 1 | 1 | 0 | 18 |
| MEDIUM | 17 | 23 | 21 | 22 | 14 | 11 | 18 | 3 | 129 |
| LOW | 5 | 17 | 11 | 5 | 2 | 5 | 3 | 12 | 60 |
Final verdict
The framework is in strong shape. 0 CRITICAL, 18 HIGH across 124 components — and the HIGH findings cluster into a
handful of systemic patterns with shared, mechanical fixes, not scattered one-offs. The single highest-leverage fix is
SYS-7 (the A31 O(N²) selection pattern: 9 components, 6 of them HIGH, one ~3-line selectedSet/expandedSet lift
each). After that it is mostly MEDIUM hygiene: SYS-1 scope-drift (~33 components, one definitional decision),
THEME-SYS-1 (overlay z-index ladder, ~10 recipes, one tokenization sweep), and the smaller systemic patterns below.
The non-A31 HIGH findings are isolated and each well-grounded:
- dialog-001 — Content
role/aria-roledescriptiondouble-write (a11y race foralertdialogvariant). - dialog-002 — dead/conflicting
[data-dialog-trigger]CSS envelope after the Button migration. - combobox-001 — virtual+real focus mix (A17 / APG break).
- select-001/002/003 — querySelector injection ·
focus.trapdeclared-not-implemented · keyboard-route divergence. - alert-dialog-001 — the only
: Morfoannotation (degradescreateAttrs). - navigation-menu-006 — an undisposed
$effect.root(bounded leak). - collapsible-NEW-001 —
collapsesequence:'pre'+ handler-state (the Checkbox-244ms-lag mechanism). - breadcrumb-001 —
archetype:'item'false-affordance on a display<li>(the bug Timeline explicitly avoided).
Method honesty (recorded throughout): every HIGH/CRITICAL was lead-verified against the cited source. The adversarial-verify stage repeatedly earned its keep — it refuted a false CRITICAL (eidos-imports-soma on onion-menu), a false HIGH (button archetype, inert because the part isn't runtime-registered), every CSS.escape-on-framework-value HIGH/CRITICAL (grid selectors), and the non-canonical-role flags (content-color slots, not the 9 roles). The lead's own independent greps caught real defects the agents missed (the A31 cluster pre-confirmed) AND made one error of its own (a single-line grep wrongly cleared the field-family A30, which the workflow caught) — no single source was infallible; the cross-check (grep × agent × verify × lead read) is what produced the verdict. The lead also corrected the verify in both directions: elevated under-rated findings (range-calendar Home/End → systemic; collapsible seq-lag MEDIUM→HIGH) and downgraded over-rated ones (tag-group rovingTarget HIGH→LOW; many magic-literals).
Positive references (cite these when building/reviewing): NumberField (renderProps + direct id-wiring + A13),
Popover (Close-via-Button, no role override, hover guards), DropdownMenu (roving-focus consistency, self-managed cleanup,
eidos-in-scope), Button (clean action recipe), Dialog's data-color subset (neutral/risk/threat), Timeline (correctly
omits archetype:'item'), VirtualList (SvelteMap), accordion + tabs (fully clean). The eidos theming layer is clean
(roles/variants/sizes/naming all canonical across 124 components).
Counts by severity (per batch, historical)
B7 result: the action/feedback layer is healthy. The two documented reactivity incidents stayed fixed: Toolbar A35 (
isTabStopusesuntrack, the counter was removed) and Form A36 (form-core.svelte.ts:262 return untrack(...)) — both lead-verified, both REFUTED when an agent re-raised them. Timers route throughuix.timers(toast/clipboard/drag-drop) → no A6 leaks. Button is CLEAN — the reference action component (its one archetype finding was inert: the Spinner part isn't runtime-registered, sodata-archetypenever reaches the DOM — the verify caught the false premise). The one real HIGH: breadcrumb-001 — the Item part (a display<li>) declaresarchetype:'item'AND is runtime-registered (provider:123), sodata-archetype='item'IS emitted and archetypes.css paints it withcursor:pointer+ hover (a false affordance on a non-clickable container). The insight: Timeline explicitly avoided this (its morfo comments "no archetype:'item' — a display<li>omits it"); breadcrumb didn't. A targeted, lead-verified contract bug — not systemic, but a clean example of the archetype-pull anti-pattern one sibling fixed and the other didn't.
B6 result: the disclosure/selection controls are well-built — accordion + tabs are fully CLEAN, the sequence doctrine is correctly applied everywhere it was checked (checkbox/toggle/switch =
post; accordion/tabs/radio-group/ stepper set state at the call-site sopreis fine), slider has no gesture A6 leak. ONE real HIGH: collapsible'scollapseevent issequence:'pre'while setting state in the handler — the verifier traced the runtime and proved it serializes (emit-then-handler), soopen=falselags the full ~240msbriefhold (the same mechanism as the documented Checkbox 244ms-lag). The analyze agent AND the morfo's own comment marked it clean on a false "emit overlaps handler" assumption. Lead note: I initially downgraded it to MEDIUM ("it's the canonical emerge.dismisspre") then RESTORED it to HIGH after re-reading — dialog masks the hold with adata-event/Presence exit animation, but collapsible's exit is keyed on the (delayed)data-state, so the 240ms is a real dead delay. Verifying the adversarial-verify's own catch corrected me — the cross-check cuts both ways.
B5 result: the collections are where the A31 O(N²) selection pattern lives — 6 HIGH, all the SAME root cause: a per-item
$derivedcallingprovider.isSelected(v)/provider.isExpanded(v)that does.includes()on the global array (listbox, grid-list ×2, tree-view, tree-grid, tag-group). All 6 lead-verified by direct read of the cited.includesmethod bodies (matches an independent lead grep run before the workflow). SYS-7 now spans 7 components with ONE shared fix. The verify did its job: it refuted feed's A31 (per-item derived doesn't read global state), downgraded table's (engine-backed by$libs/datagrid), and confirmed virtual-list/grid as A33-clean (SvelteMap). The lead refuted one verifier-ADDED HIGH (tag-group-004 "rovingTarget A31") —rovingTargetElis already LIFTED to a single provider$derivedand the per-item check is an O(1) pointer compare; downgraded to a LOW A18 nit. Even verifier-added findings get verified.
B4 result: the field family is healthy — zero HIGH survived verification. Dominated by SYS-1 scope-drift (nearly all 14), test gaps, a new systemic A30-doctrine deviation, and label-font/magic-literal nits. NumberField is confirmed the clean reference (correct renderProps + direct id-wiring + A13 hidden input). The analyze pass over-reported: the
fieldagent emitted 6 "clean-check" pseudo-findings (impact = "no fix needed"); the verify refuted all 6, and the generator drops clean-checks. Method-honesty note: the lead's own independent grep for A30 violations used a single-line pattern and WRONGLY concluded "field family A30-clean"; the workflow's analyze+verify caught the multi-line$effectthat the grep missed (SYS-A30-EFFECT below). No single source — grep, agent, verify, or lead — is infallible; the cross-check is what produced truth.
B3 result: the picker + calendar family is in good shape — zero HIGH survived verification (the analyze pass raised 2 CRITICAL + several HIGH; all were CSS.escape-on-numeric/ISO-selectors or local-z-index, correctly downgraded to LOW, plus one keyboard-label drift the lead elevated to a systemic MEDIUM). The dominant B3 issues are SYS-1 scope-drift (all 10), inert
open/commit-resetevents (pickers), magic literals, and jsdom-only tests. Method note: the lead's own memory ("pickers don't fire close events") was STALE — an independent grep proved each picker now firestrigger('close'); the inert-events findings are aboutopen/commit-reset, which the verify + grep agree ARE inert. Verifying against current code, not memory, mattered.
Method note (B2): Batch 2 was run as an adversarially-verified workflow (one analyze agent per component → a skeptical verify agent that re-reads the cited code). The verify stage refuted/downgraded a large fraction of the analyze pass's claims (it caught a false-positive CRITICAL on onion-menu — "eidos imports soma" is the normal direction, not a frontier violation — and a false-positive HIGH A30 on context-menu). Both surviving B2 HIGH findings were personally re-verified against the cited source by the lead before shipping. This validates the standing rule "unverified agent finding = not a finding": the raw analyze pass over-reported ~15 HIGHs that collapsed to 2 after verification.
B2 HIGH findings (both lead-verified)
- alert-dialog-001 (HIGH, A) —
export const alertDialogMorfo: Morfo = {…}uses a: Morfoannotation instead ofas const satisfies Morfo; this widens literal types and degradescreateAttrs/compileMorfokey narrowing. The ONLY morfo in the audited set with this defect. (Lead-verified: alert-dialog.ts:4.) - navigation-menu-006 (HIGH, B/A6) —
openedAtEffect = $effect.root(() => { $effect(() => …) })(provider:523) is a DETACHED reactive root whose disposer is stored but never called; each NavigationMenuTriggerProvider leaks one root permanently subscribed toisOpen. A bare$effect(auto-disposed) is the correct tool. (Lead-verified: the only$effect.rootin any provider; no dispose anywhere in the file.)
Counts by severity (Batch 1)
| Severity | Count |
|---|---|
| CRITICAL | 0 |
| HIGH | 8 |
| MEDIUM | 17 |
| LOW | 5 |
| total | 30 |
Counts by component
| Component | C | H | M | L | headline |
|---|---|---|---|---|---|
| select | 0 | 4 | 5 | 2 | injection, focus.trap-unimpl, kbd-route divergence, kbd untested |
| dialog | 0 | 2 | 2 | 2 | role double-write (alertdialog race), dead trigger envelope |
| popover | 0 | 0 | 3 | 0 | cleanest — close double-write, magic-z, test gap |
| combobox | 0 | 2 | 3 | 0 | virtual+real focus mix (A17), kbd untested |
| dropdown-menu | 0 | 0 | 4 | 1 | most disciplined — nav dup, checkbox silent, magic-z |
Counts by dimension (findings tagged with each)
| Dim | What | Count |
|---|---|---|
| A (contract) | scope-drift, undeclared parts, aria-not-in-morfo, translationRef, checkbox silent, virtual-part data | 8 |
| B (behavior) | focus mix, kbd-route divergence, A31, loop off-by, sequence | 7 |
| C (selectors) | querySelector injection | 1 |
| D (frontier) | double-writes, dead trigger envelope, undeclared parts | 5 |
| E (TSC) | 0 (TSC usage was clean where present) | 0 |
| E-bis (theming) | magic z-index, em-font literal, magic opacity, raw rem, subset | 9 |
| F (tests) | jsdom-only + keyboard/focus/dismissal untested | 5 |
| G (redundancy) | directional-nav duplication | 2 |
Top findings by impact
- combobox-001 (HIGH) — Combobox mixes virtual focus (
aria-activedescendant) with realdom.focus()on items; violates A17 and the APG combobox contract (focus must stay on the textbox). - dialog-001 (HIGH) — Dialog Content double-writes
role(syncAttrs'dialog'vs Sveltevariant); forvariant='alertdialog'the accessible role is a non-deterministic race. - select-001 (HIGH) —
scrollSelectedIntoViewinterpolates the user value into aquerySelectorwithoutCSS.escape(the safe helper it imports does escape — this path bypasses it). - select-002 (HIGH) — morfo declares
focus.trap:true+initial:'first-focusable'but the provider implements virtual focus (no FocusScope) — dead/contradictory contract. - select-003 (HIGH) — two keyboard routes (virtual trigger vs real-focus content) duplicate index math and
diverge; the content loop path lands
n-2(off-by) and readsactiveElementthough the component is virtual. - dialog-002 (HIGH) —
[data-dialog-trigger]full-chrome CSS envelope survives the trigger's migration to a composed<Button>; both recipes paint the same element (order-dependent). - SYSTEMIC: MAGIC-Z (5/5) — see systemic #2.
- SYSTEMIC: JSDOM + keyboard/focus untested (5/5) — see systemic #3.
- SYSTEMIC: SCOPE-DRIFT (4/5) — see systemic #1.
- SYSTEMIC: DIRECTIONAL-NAV duplication (3+ components, 4+ copies) — see systemic #4 + SHARED_EXTRACTION.
Systemic issues (recur in ≥3 components → one structural fix each)
SYS-1 · SCOPE-DRIFT — eidos recipe exists but morfo scope omits 'eidos'
- components: select, dialog, popover, combobox (4/5). dropdown-menu CORRECTLY declares
['soma','sema','eidos']. - evidence: each has a full
eidos/components/{c}/recipe + wrapper, yetscope: ['soma','sema']. - why it matters:
scopeis documented (glossary) as "which layers implement the component"; the 2-of-3 rule and tooling key off it. An inconsistent scope makes "which components have eidos" unanswerable from the morfo. - one fix: decide the semantics (does
scopelist authored layers or all consuming layers?) and make it consistent — either add'eidos'everywhere an eidos recipe exists, or document that eidos is implicit andscopelists only soma/sema. Affects ~40+ interactive components beyond Batch 1. - resolution:
scopelists IMPLEMENTING layers; added'eidos'to all 62 drifted morfos (scope-drift now 0). Regression guard added incontracts.test.ts("…declares 'eidos' in morfo scope (SYS-1)") — fails if any maintained component ships an eidos recipe without'eidos'in scope (proven to catch drift; dev-track excluded). - fix-status: fixed (
212624e0)
SYS-2 · MAGIC-Z — overlay content-z/overlay-z are hardcoded integers
- components: select (80), dialog (70 + content calc+1), popover (75 / overlay 60), combobox (80), dropdown-menu (80). 5/5.
- evidence:
lib/recipes/base.tsper-recipe integer z values; no--z-index-*overlay ladder consumed. - why it matters: overlay stacking is inherently cross-component; scattered integers (60/70/75/80) can't be coordinated or reasoned about, and a new overlay just guesses a number.
- one fix: define a canonical overlay z-ladder (
--z-index-{popover,menu,modal,...}) and map every overlay recipe to it in one sweep. (THEMING §35 Bloque C already mandates this for "magic z-index".) - fix-status: open
SYS-3 · TEST-FIDELITY — interaction-heavy overlays tested jsdom-only, keyboard/focus/dismissal unpinned
- components: all 5. select + combobox HIGH (the focus bugs live exactly in the untested paths); dialog, popover, dropdown-menu MEDIUM.
- evidence: every
*-provider.svelte.test.tsis@vitest-environment jsdomand asserts selection/open-close logic; none exercise the directional-nav index math, typeahead, dismissal trigger-exclusion, focus trap/return, or aria-activedescendant sync. jsdom can't validate focus/layout/hydration. - why it matters: the highest-risk behaviors (and several of the HIGH defects above) would ship green.
- one fix: (a) add provider unit tests for the keyboard routes + dismissal exclusion; (b) add a client/ Playwright project test per focus-bearing overlay asserting focus return + activedescendant/roving invariants.
- fix-status: open
SYS-4 · DIRECTIONAL-NAV duplication (+ shared loop off-by)
- components: select (content route), combobox (content), dropdown-menu (content + sub-content). 4+ copies.
- evidence: identical next/prev/Home/End index math; the
currentIndex===-1+ prev + loop branch computesn-2(off-by) in select-003 and dropdown-menu-001. - one fix: a pure
nextIndex(curr, key, len, {loop, dir, orientation})helper, unit-tested once. See SHARED_EXTRACTION DIRECTIONAL-NAV. - fix-status: open
SYS-5 · SYNCATTRS + MANUAL DOUBLE-WRITE
- components: dialog (role/aria-roledescription — HIGH, live race), popover (Close type/aria-label — MEDIUM, benign-valued). 2/5 so far; likely more downstream.
- evidence: a part registered
syncAttrs: true(runtime writes the morfo attrs viadom.apply) AND the props block re-sets a morfo-declared attr → two authorities (active_architecture §7.7). - one fix: a lint that flags
syncAttrs:trueparts whosepropsblock keys intersect morfo-declared attrs (role/aria-*/type). See VALIDATOR_GAPS. Per-component: drop the manual re-set or switch torenderProps()+ single override. - fix-status: open
SYS-6 · UNDECLARED-EIDOS-PARTS — recipe styles data-{c}-{part} absent from the morfo
- components: select (
indicator,item-indicator), dialog (header,footer— acknowledged). 2/5 so far. - evidence: CSS selectors on parts with no morfo entry (decorative/layout). eidos-lint would classify them eidos-only/invalid.
- one fix: a project convention for eidos-only parts — declare them
kind:'internal'in the morfo OR a documented per-recipe allow-list — applied uniformly. - fix-status: open
SYS-7 · A31 O(N²) — per-item isSelected/isExpanded .includes on the global array
- components (9, lead-verified): select-007 (M), combobox-004 (M) [B1] + listbox-001, grid-list-001/002,
tree-view-002, tree-grid-001, tag-group-001 (HIGH) [B5] + toggle-group-001 (M), checkbox-001 (M, via CheckboxGroup)
[B6]. Each has a per-item
$derivedcalling a provider method (isSelected/isExpanded/isItemPressed/isItemChecked) that doesarray.includes(v)— O(K) per item × N items on every mutation = O(N²). Lead-confirmed by reading the exact method bodies: listbox:157, grid-list:151, tree-view:96-97/146, tree-grid:148/189, tag-group:116, toggle-group:72, checkbox:313, select:158, combobox:182. (radio-group is CLEAN — single-value===, not.includes.) - severity split (legitimate): select/combobox are usually small single-select (K≤1) → MEDIUM; the collections are large multi-select / many-expanded (the documented "hangs at 30+" case) → HIGH. The root cause and fix are identical.
- the proof it's a miss, not a design choice: these providers ALREADY lift
rovingTargetElto ONE Set-backed provider$derived(with comments citing "avoids an O(N²) cascade") — but left the siblingisSelectedcalling the O(N) method per-item. The asymmetry is the tell. - one structural fix: a provider-level
readonly selectedSet = $derived(new SvelteSet(opts.value.current))(andexpandedSetfor trees);isSelected/isExpandedconsult.has()(O(1)). ~2 lines per component. See SHARED_EXTRACTION EX-3 (now the highest-value extraction in the audit). - fix-status: open
SYS-A33-CLONE · Reactive-collection clone-and-reassign instead of SvelteMap/Set
- components (B5): listbox, grid-list, tree-grid, tag-group use the
this.x = new Map(this.x)clone-and-reassign workaround for a registry/cache; combobox previously did (now fixed). It IS reactive (field reassignment tracks) but O(N) per mutation + fragile (breaks silently if refactored to.set()direct) — A33 says rewrite to SvelteMap. virtual-list is the positive model (4× SvelteMap). No plain$state(new Map/Set)anywhere in B5 (the dangerous form). - one fix: migrate the four clone-reassign sites to
SvelteMap/SvelteSet(O(1).set, no clone). MEDIUM. - fix-status: open
Batch-2 systemic confirmations + corrections
- SYS-1 (scope-drift) — heavily confirmed. B2 adds menubar, navigation-menu, drawer, command, link-preview, float-panel, menu-dial (7 more) → ~11 components confirmed; tooltip/context-menu/alert-dialog/onion-menu correctly declare or don't need eidos-in-scope. Now the dominant contract-consistency issue. Severity normalized to MEDIUM per-component (it's an informational drift, no user-visible effect) — the fix is the one structural SYS-1 decision.
- SYS-2 (magic-z) — confirmed + made authoritative. B2 adds tooltip
content-z:76, draweroverlay-z:72/inline-z:64, link-previewpreview-z:99. Lead independently verified the canonical scaleSTATIC_Z_INDEX(base 0 · raised 1 · sticky 100 · dropdown 300 · popover 400 · tooltip 500 · modal 700 · toast 900) EXISTS and is consumed by the depth planes + menu-dial (var(--z-index-sticky)). So the overlay recipes hardcode an ad-hoc parallel 60–99/1200 scale whose ordering doesn't even match canon. The token they should use exists. See THEMING_COHERENCE THEME-SYS-1 for the precise per-component mapping. - SYS-3 (test fidelity) — confirmed across all 11. Every B2 component is jsdom/node-unit only; the keyboard/ focus/dismissal hard paths are untested. menu-dial/onion-menu test only the pure nav math (correct as far as it goes) but never the focus/two-moments integration.
- SYS-4 (directional-nav off-by) — PARTIALLY DISPROVEN (important correction). The
currentIndex===-1 + prev + loop → n-2off-by is NOT universal: navigation-menu's loop math is CORRECT (modulo(i+1)%n/(i-1+n)%nWITH acurrentIndex===-1early-return guard, verified at provider:325/336-345). The off-by is specific to Select's and DropdownMenu's content routes, which lack that guard. The shared-helper opportunity (EX-1) stands, but the BUG is narrower than B1 implied — fix select/dropdown, don't assume every menu has it. - SYS-5 (syncAttrs double-write) — one more (drawer-007, MEDIUM). Drawer Content manually sets
role:'dialog'aria-modalwhich the morfo declares; benign value (role constant) so MEDIUM, not the HIGH dialog-001 race.
- SYS-6 (undeclared eidos parts) — mostly REFUTED in B2. The B2 analyze pass flagged data-size/data-depth/
data-floating-gap as "undeclared parts" on menubar/navigation-menu/float-panel; the verify stage refuted these —
they are eidos-owned VISUAL data-attrs (the sanctioned
data-{prop}pattern, like Dialog's data-size), not morfo parts. SYS-6 remains valid only for genuine decorative parts (select indicator, dialog header/footer).
Batch-3 systemic confirmations + new patterns
- SYS-1 (scope-drift) — now ~21 components. All 10 B3 components hit it (incl. the pickers and calendar,
range-calendar nominally includes eidos in scope but its recipe entry is thin). It is the single most pervasive
finding in the whole audit. The fix is one decision (does
scopeenumerate authored layers or all consumers?). - SYS-INERT (NEW) — pickers declare events the provider never fires. date-picker/date-range-picker/time-picker/
time-range-picker/color-picker each declare an
openevent and acommit-resetevent in the morfo that the provider NEVER triggers (it only firesclose+ the field/area events). Lead-verified by grep: each picker provider callstrigger('close')(1×; color-picker 7× incl. commit-set/handle-*) but nevertrigger('open')/trigger('commit-reset'). The events exist only to satisfy the schema validator — a 2-of-3 / contract-honesty gap (MEDIUM). Fix: either fire them (perceptual signal on open / on clear) or drop them from the morfo. - SYS-CAL-HOMEEND (NEW) — calendar + range-calendar Home/End label vs behavior. The morfo declares the Home/End
actions as
first/last-day-of-weekbut both providers implementstart/end-of-month. Either an APG deviation (WAI-ARIA Date Picker Dialog: Home/End move within the WEEK) or a stale label. 2 components (MEDIUM, lead-elevated from the verify's LOW because shared-ness = systemic). - CSS.escape-on-grid-selectors — a consistent LOW class, not a bug. calendar/month-grid/year-grid interpolate
framework-derived NUMERIC (month 1..12, year arithmetic) or ISO (
DateValue.toString()) values intoquerySelectorwithoutCSS.escape. The analyze pass over-rated these HIGH/CRITICAL; verification correctly downgraded ALL to LOW (the values provably cannot contain CSS metacharacters). A defense-in-depth nicety to add uniformly, never a user-hittable defect. (Contrast Select's user-VALUE interpolation, select-001, which is real.) - THEME-SYS-2 (magic opacity) extended — calendar
day-outside-opacity 0.62(off the 0.05-step--opacity-*scale; the same0.62/62%recurs as overlay-opacity in dialog/drawer). See THEMING_COHERENCE.
Batch-4 systemic confirmations + new patterns
- SYS-A30-EFFECT (NEW) — segmented fields register
inputIdvia$effect, not direct assignment. date-field (provider:932-936), time-field (531-535), color-field (521-525) wrap thefield.inputId.current = opts.id.currentregistration in a$effect; the reference NumberField (585-587) does it as a direct constructor guard. Lead-verified in all four. No live loop (the child only WRITES inputId, never reads it;opts.idis stable) — so it is an A30 doctrine deviation (MEDIUM), not a freeze. But it is exactly the shape A30 warns about; a future two-way change reintroduces the freeze. One fix: make all three match NumberField's direct assignment. (Note: the date-field + color-field agents both listed "A30/Behavior" as a no-findings dimension — they MISSED their own$effect; only time-field's agent caught it; the lead added the twins.) - SYS-1 (scope-drift) — nearly universal in the field family (field, date/time/date-range/time-range/color/css/ search/password-field, pin-input, textarea, tags-input, editable). Now the audit's single most pervasive finding across ~33 components. The verify even noted the project's own reference (NumberField) shares the condition — so the one SYS-1 decision must also settle whether NumberField/field "count".
- SYS-3 (test gaps) — the segmented fields under-test their hardest paths: every B4 test is jsdom-only and none
exercise the A24 readonly-without-value
logger.warn(trivial to assert — the harness already stubswarn), the hidden-input render, the commit events, or APG segment keyboard. password-field + textarea have NO provider test. - CLEAN REFERENCE confirmed — NumberField. Correct renderProps (no double-write), direct id-wiring (A30), A13 hidden input, all declared events actually fired. Use it as the field-family template (and the contrast that makes SYS-A30-EFFECT visible).
- A26 / A13 — verified PRESENT, no findings. Lead grep + agents + verify agree: date/time/color-field have
onbeforeinput; the range fields inherit it by composing the single-field Input (A27); number/color/css/tags/pin all render the A13 hidden input. The analyze pass's tentative "missing A26/A13" angles were refuted.
New validator-gap findings from B2 (see VALIDATOR_GAPS)
- VG-8 —
morfo:checkdoes NOT flag a morfo declared with: Morfoinstead ofas const satisfies Morfo(alert-dialog-001 shipped). The single highest-leverage cheap fix: a lint that greps every*Morfoexport for theas const satisfies Morfocloser. - VG-9 — nothing flags an undisposed
$effect.rootin a provider (navigation-menu-006). A lint:$effect.rootoutside a test whose returned disposer isn't wired into a teardown.
Notes
- No CRITICAL across 16 components (the one CRITICAL the B2 agents raised was a false positive, refuted).
- Two B1 HIGH a11y correctness issues (combobox-001 focus mix, dialog-001 role race) remain the most user-impactful;
the B2 HIGHs are contract (alert-dialog
:Morfo) + a bounded leak (navmenu$effect.root). - Positive baselines for later batches: popover (Close-via-Button, no role override, hover guards) and dropdown-menu (roving-focus consistency, uniform renderProps, self-managed cleanup, eidos-in-scope).