|
|
|
|
|
# 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-roledescription` double-write (a11y race for `alertdialog` variant).
|
|
|
|
|
|
- **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.trap` declared-not-implemented · keyboard-route divergence.
|
|
|
|
|
|
- **alert-dialog-001** — the only `: Morfo` annotation (degrades `createAttrs`).
|
|
|
|
|
|
- **navigation-menu-006** — an undisposed `$effect.root` (bounded leak).
|
|
|
|
|
|
- **collapsible-NEW-001** — `collapse` `sequence:'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 (`isTabStop` uses `untrack`, 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 through
|
|
|
|
|
|
> `uix.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, so `data-archetype` never reaches the
|
|
|
|
|
|
> DOM — the verify caught the false premise). The one real HIGH: **breadcrumb-001** — the Item part (a display `<li>`)
|
|
|
|
|
|
> declares `archetype:'item'` AND is runtime-registered (provider:123), so `data-archetype='item'` IS emitted and
|
|
|
|
|
|
> archetypes.css paints it with `cursor: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 so `pre` is fine), slider has no gesture A6 leak. ONE real HIGH: **collapsible's
|
|
|
|
|
|
> `collapse` event is `sequence:'pre'` while setting state in the handler** — the verifier traced the runtime and
|
|
|
|
|
|
> proved it serializes (emit-then-handler), so `open=false` lags the full ~240ms `brief` hold (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.dismiss `pre`") then RESTORED it to HIGH after re-reading — dialog masks the hold with a `data-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 `$derived` calling `provider.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 `.includes` method 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") — `rovingTargetEl`
|
|
|
|
|
|
> is already LIFTED to a single provider `$derived` and 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 `field` agent 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 `$effect` that 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-reset` events (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 fires `trigger('close')`; the inert-events findings are about `open`/`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 `: Morfo` annotation instead
|
|
|
|
|
|
of `as const satisfies Morfo`; this widens literal types and degrades `createAttrs`/`compileMorfo` key 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 to `isOpen`. A bare `$effect` (auto-disposed) is the correct tool. (Lead-verified: the
|
|
|
|
|
|
only `$effect.root` in 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
|
|
|
|
|
|
|
|
|
|
|
|
1. **combobox-001 (HIGH)** — Combobox mixes virtual focus (`aria-activedescendant`) with real `dom.focus()` on
|
|
|
|
|
|
items; violates A17 and the APG combobox contract (focus must stay on the textbox).
|
|
|
|
|
|
2. **dialog-001 (HIGH)** — Dialog Content double-writes `role` (syncAttrs `'dialog'` vs Svelte `variant`); for
|
|
|
|
|
|
`variant='alertdialog'` the accessible role is a non-deterministic race.
|
|
|
|
|
|
3. **select-001 (HIGH)** — `scrollSelectedIntoView` interpolates the user value into a `querySelector` without
|
|
|
|
|
|
`CSS.escape` (the safe helper it imports does escape — this path bypasses it).
|
|
|
|
|
|
4. **select-002 (HIGH)** — morfo declares `focus.trap:true` + `initial:'first-focusable'` but the provider
|
|
|
|
|
|
implements virtual focus (no FocusScope) — dead/contradictory contract.
|
|
|
|
|
|
5. **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 reads `activeElement` though the component is virtual.
|
|
|
|
|
|
6. **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).
|
|
|
|
|
|
7. **SYSTEMIC: MAGIC-Z (5/5)** — see systemic #2.
|
|
|
|
|
|
8. **SYSTEMIC: JSDOM + keyboard/focus untested (5/5)** — see systemic #3.
|
|
|
|
|
|
9. **SYSTEMIC: SCOPE-DRIFT (4/5)** — see systemic #1.
|
|
|
|
|
|
10. **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'` <!-- id: SYS-001 -->
|
|
|
|
|
|
- components: select, dialog, popover, combobox (4/5). dropdown-menu CORRECTLY declares `['soma','sema','eidos']`.
|
|
|
|
|
|
- evidence: each has a full `eidos/components/{c}/` recipe + wrapper, yet `scope: ['soma','sema']`.
|
|
|
|
|
|
- why it matters: `scope` is 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 `scope` list authored layers or all consuming layers?) and make it
|
|
|
|
|
|
consistent — either add `'eidos'` everywhere an eidos recipe exists, or document that eidos is implicit and
|
|
|
|
|
|
`scope` lists only soma/sema. Affects ~40+ interactive components beyond Batch 1.
|
|
|
|
|
|
- resolution: `scope` lists IMPLEMENTING layers; added `'eidos'` to all 62 drifted morfos (scope-drift now 0).
|
|
|
|
|
|
Regression guard added in `contracts.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 <!-- id: SYS-002 -->
|
|
|
|
|
|
- components: select (80), dialog (70 + content calc+1), popover (75 / overlay 60), combobox (80),
|
|
|
|
|
|
dropdown-menu (80). **5/5.**
|
|
|
|
|
|
- evidence: `lib/recipes/base.ts` per-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 <!-- id: SYS-003 -->
|
|
|
|
|
|
- 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.ts` is `@vitest-environment jsdom` and 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) <!-- id: SYS-004 -->
|
|
|
|
|
|
- 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 computes
|
|
|
|
|
|
`n-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 <!-- id: SYS-005 -->
|
|
|
|
|
|
- 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 via `dom.apply`) AND the props
|
|
|
|
|
|
block re-sets a morfo-declared attr → two authorities (active_architecture §7.7).
|
|
|
|
|
|
- one fix: a lint that flags `syncAttrs:true` parts whose `props` block keys intersect morfo-declared
|
|
|
|
|
|
attrs (role/aria-*/type). See VALIDATOR_GAPS. Per-component: drop the manual re-set or switch to
|
|
|
|
|
|
`renderProps()` + single override.
|
|
|
|
|
|
- fix-status: open
|
|
|
|
|
|
|
|
|
|
|
|
### SYS-6 · UNDECLARED-EIDOS-PARTS — recipe styles `data-{c}-{part}` absent from the morfo <!-- id: SYS-006 -->
|
|
|
|
|
|
- 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 <!-- id: SYS-007 -->
|
|
|
|
|
|
- **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 `$derived` calling a provider method (`isSelected`/`isExpanded`/`isItemPressed`/`isItemChecked`)
|
|
|
|
|
|
that does `array.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 `rovingTargetEl` to ONE Set-backed
|
|
|
|
|
|
provider `$derived` (with comments citing "avoids an O(N²) cascade") — but left the sibling `isSelected` calling 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))` (and
|
|
|
|
|
|
`expandedSet` for trees); `isSelected`/`isExpanded` consult `.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 <!-- id: SYS-008 -->
|
|
|
|
|
|
- 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`, drawer `overlay-z:72`/
|
|
|
|
|
|
`inline-z:64`, link-preview `preview-z:99`. Lead independently verified the canonical scale `STATIC_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-2` off-by is NOT universal: navigation-menu's loop math is CORRECT (modulo `(i+1)%n`/`(i-1+n)%n` WITH a
|
|
|
|
|
|
`currentIndex===-1` early-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-modal` which 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 `scope` enumerate 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 `open` event and a `commit-reset` event in the morfo that the
|
|
|
|
|
|
provider NEVER triggers (it only fires `close` + the field/area events). Lead-verified by grep: each picker
|
|
|
|
|
|
provider calls `trigger('close')` (1×; color-picker 7× incl. commit-set/handle-*) but never `trigger('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-week` but both providers implement `start/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 into
|
|
|
|
|
|
`querySelector` without `CSS.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 same `0.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 `inputId` via `$effect`, not direct assignment.** date-field
|
|
|
|
|
|
(provider:932-936), time-field (531-535), color-field (521-525) wrap the `field.inputId.current = opts.id.current`
|
|
|
|
|
|
registration 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.id` is 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 stubs `warn`), 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:check` does NOT flag a morfo declared with `: Morfo` instead of `as const satisfies Morfo`
|
|
|
|
|
|
(alert-dialog-001 shipped). The single highest-leverage cheap fix: a lint that greps every `*Morfo` export for
|
|
|
|
|
|
the `as const satisfies Morfo` closer.
|
|
|
|
|
|
- **VG-9** — nothing flags an undisposed `$effect.root` in a provider (navigation-menu-006). A lint: `$effect.root`
|
|
|
|
|
|
outside 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).
|