Compare commits

..

1080 Commits

Author SHA1 Message Date
dev 72e8fb57ea feat(uix): ColorPicker — ColorSwatch primitive, presets/saved colors, readonly rollout
3 months ago
dev 54ecc84e2d docs(eidos): THEMING §38 state-layer + §32 outline-on-surfaces + TOC
3 months ago
dev 353ad59586 fix(uix): --size-* bundle follows the 1:1 font canon (md=16, not 14)
3 months ago
dev ab62cca7b2 fix(uix): focus ring on surfaces — migrate 5 box-shadow rings to outline
3 months ago
dev e7e4870d54 feat(uix): --state-* fold — trigger + menu-row state into the shared layer
3 months ago
dev 627521573c fix(uix): sema timing on uix.timers + overlay open is sequence:'post'
3 months ago
dev 7de6c76bb3 feat(uix): --state-* rollout — 19 neutral-hover components use the state layer
3 months ago
dev c90af708f8 docs(eidos): THEMING §37 — touch-target coarse-gated doctrine
3 months ago
dev a13ca387d4 feat(uix): --state-* state-layer token set + accordion pilot
3 months ago
dev 2750b9ce83 feat(uix): touch-target — radio label-row hits 44px on coarse pointers
3 months ago
dev c0904fc6e1 feat(uix): touch-target — list rows hit 44px on coarse pointers
3 months ago
dev a265d39aef feat(uix): touch-target — button-like controls hit 44px on coarse pointers
3 months ago
dev b2ca998985 feat(uix): ColorPicker presets 4-group + save-color button (demo)
3 months ago
dev 151873d795 feat(uix): squircle is the surface-tier default (hybrid shape doctrine)
3 months ago
dev 6095ff2d23 fix(uix): Card selection ring survives hover
3 months ago
dev 1b8fcd45a5 docs(eidos): inherit/scale coherence audit + fix plan
3 months ago
dev 7bb1ad510b fix(uix): menubar menu gap = --space-1 (4px) from the bar, not the trigger
3 months ago
dev 3fd8234549 docs: mark floating-gap canon continuation RESOLVED
3 months ago
dev 9c48164f92 fix(uix): menu demos follow the floating-gap canon (drop default sideOffset)
3 months ago
dev d494729116 feat(uix): ColorPicker Untitled-UI redesign (tabs + integrated ColorField)
3 months ago
dev 61ea97b5bb feat(uix): menu gap canon + keyboard focus-ring fix + overscroll + archetype :where
3 months ago
dev 5974be2675 feat(uix): ColorField — channel dividers + prefix/suffix glyphs, system Select format switcher
3 months ago
dev 79cd5d7978 feat(uix): CardGroup composes ToggleGroup + Collapsible; ToggleGroup gains selection cardinality
3 months ago
dev 584df80654 feat(uix): v2 demo layout (shared harness) + Button palette-color pilot
3 months ago
dev 2e5823b185 docs(soma): GradientBuilder Phase-2 continuation handoff
3 months ago
dev 8ddd55309a feat(soma): GradientBuilder soma layer — render-ready providers + wrappers (WIP)
3 months ago
dev c474fc6bae feat(eidos): gradient axis (6th builder) + elevation-scaled opacity + GradientBuilder scaffold
3 months ago
dev cae3d2e0ce refactor(uix): pickers delegate the dialog to the composed Popover (+ DateTimePicker time parts)
3 months ago
dev 1114d00b7c test(uix): keyboard/focus tests for select + combobox (select-004, combobox-002)
3 months ago
dev 4c5e33a098 feat(chronos): event-calendar component (month/week/day/agenda)
3 months ago
dev cb1d02a479 Revert Phase 5 (SYS-5 / SYS-A33 / THEME-SYS-2) to the post-z-index state
3 months ago
dev 6c5616be5e fix(eidos): THEME-SYS-2 — dropdown/context-menu disabled items use --opacity-disabled
3 months ago
dev 2f02fdd924 docs(audit): SYS-5 fixed (drawer-007, popover-001) + SYS-A33 refuted
3 months ago
dev 729f3c0b45 fix(uix): SYS-5 — drop double-written attrs owned by syncAttrs (drawer, popover)
3 months ago
dev 8606232125 fix(eidos): THEME-SYS-1 — consolidate overlay z-index into a named scale (+ guard + docs)
3 months ago
dev a2ae2c2007 fix(uix): SYS-A30-EFFECT — register inputId directly, not in a $effect (+ guard)
3 months ago
dev ec100c4bb8 test(uix): add VG-8 + A31 regression guards; fix the toolbar A31 the guard found
3 months ago
dev e378e14a62 test(uix): add SYS-1 scope-drift regression guard
3 months ago
dev 6fd6702226 docs(audit): mark SYS-1 scope-drift fixed (212624e0)
3 months ago
dev 212624e0b2 fix(uix): audit Phase 3 — SYS-1 scope-drift, declare 'eidos' where a recipe exists
3 months ago
dev 75510cbb0b docs(audit): mark the 16 HIGH code findings as fixed
3 months ago
dev bac673158f fix(uix): audit Phase 2 — focus-strategy refactor (combobox-001 + select-003)
3 months ago
dev db33f66cdf fix(uix): audit Phase 2 — 3 more isolated HIGH findings
3 months ago
dev 98954a7c35 fix(uix): audit Phase 2 — 5 isolated HIGH findings
3 months ago
dev 92f988e7e7 feat(uix): calendar-nav unification, TimePicker datetime blocks, A31 perf
3 months ago
dev 25ce77c142 docs(audit): deep component audit — 124/124 audited + executable fix plan
3 months ago
dev 89e69b5d0f feat(charts): SmithChart — RF reflection-coefficient (Γ) disk
3 months ago
dev 31671bb323 feat(eidos): Metrics — KPI/stat component (Untitled/shadcn-grade)
3 months ago
dev c945ac00d5 feat(charts): unified Heatmap (matrix + calendar) + 5 new chart types
4 months ago
dev 4c7e493edc feat(charts): stacking + entrance animations (grow/draw/grow-x/bloom)
4 months ago
dev f950973b03 feat(eidos): calendar month/year via popover+grid; nav controls compose system Button/IconButton
4 months ago
dev 36770da202 fix(eidos): universal trigger-side padding reset for picker fields
4 months ago
dev f20efd4681 fix(eidos): picker triggers flush via field-trigger archetype + footer follows picker size
4 months ago
dev f822d6e4dd fix(eidos): DatePicker/Calendar — flush field triggers + drop listbox `item` chrome from grid cells
4 months ago
dev a4d86edb24 docs(eidos): deepen Lote A hand-off scoping for calendar + code-block
4 months ago
dev 745a486ccd docs(eidos): audit hand-off — close family complete, calendar/code-block remaining
4 months ago
dev a32608dadd feat(eidos): Lote A — float-panel-close composes IconButton
4 months ago
dev 7092450734 feat(eidos): Lote A closes — toast composes IconButton; close family defers to Button defaults
4 months ago
dev 0a43225ec2 feat(eidos): Lote A closes — popover + banner compose IconButton; clean drawer orphans
4 months ago
dev ebc742e73b fix(eidos): component-coherence audit FASE 2 — focus-ring, fields, segments, langs, API nits
4 months ago
dev c19a72b620 feat(charts): zero-dep chart family — $libs/plots engine + eidos components + demos
4 months ago
dev a0da99aa15 feat(eidos): menu-dial speed dial — compositional, arc + 9 zones
4 months ago
dev c1effca1b4 feat(timeline): cross-layer Timeline component (history/feed + progress)
4 months ago
dev ec8428218f docs(eidos): floating-gap canon (THEMING §36) + audit hand-off 2026-06-22
4 months ago
dev d229447a33 feat(floating): cascade the canonical gap to all menus + panels
4 months ago
dev 1590684c2e feat(floating): token-driven canonical gap — foundation + popover pilot
4 months ago
dev 7cf30287de fix(eidos): menus + color-picker trigger to 1:1 font; 2A radius bypass tokens
4 months ago
dev c00bac1345 feat(eidos): action components — fab, onion-menu, button-group, split-button
4 months ago
dev a60abef4bf feat(command): Command.Dialog closes on item select
4 months ago
dev 67d419f839 fix(command): ghost+neutral close, centered + separated from the input
4 months ago
dev c568b4df6c docs(eidos): §13 radius axis → LIVE (A2 complete)
4 months ago
dev eb9f2fddd3 fix(command): concentric items + align dialog input/options + close overlap
4 months ago
dev 4aea4b8121 docs(eidos): §13 radius row — concentric migration status (A2)
4 months ago
dev 8272a02884 refactor(select, combobox): concentric item radius via [data-shape-nest] (A2)
4 months ago
dev 28b0c10202 refactor(menubar): concentric trigger radius via [data-shape-nest] (A2)
4 months ago
dev 3ae9ff339a fix(eidos): --radius-default sm → md (radius archetype default)
4 months ago
dev 845d6579d1 feat(eidos): radius foundation — --radius-default + --radius-factor + scaling-responsive (A2)
4 months ago
dev 5f14793b62 test(eidos): elevation-plane anti-drift guard + mark §13 surface axis LIVE
4 months ago
dev d578f83281 feat(select, combobox): plane-constant popup elevation (B2)
4 months ago
dev be5396cecd feat(link-preview, command): adopt the `overlay` elevation plane (B2)
4 months ago
dev 009f5367a7 feat(menu-family): adopt the `overlay` elevation plane (B2)
4 months ago
dev f80f222596 feat(dropdown-menu): adopt the `overlay` elevation plane (B2)
4 months ago
dev 1431970371 fix(time-range-picker): lay the field on one line
4 months ago
dev 599c142124 feat(eidos): non-modal menus + unified list-surface size layer (control-height rows)
4 months ago
dev d0312ee94f docs(motion): implementation guide + reconcile the doc family
4 months ago
dev fe597f3fa9 fix(motion): audit follow-ups — spring reduced-motion, Motion+loop exit, loop tokens
4 months ago
dev a3d7565564 demo(motion): loops + lifecycle + staggered cascade with an SVG
4 months ago
dev 50d56059d2 fix(card): motion prop (state-domain pilot) + nested-interactive guard
4 months ago
dev 9e4187bc3c feat(motion): loops + state-domain + staggered-exit + debug-stagger
4 months ago
dev b2b1f4ffcf demo(popover): showcase the `spring-pop` JS spring tier via a `motion` control
4 months ago
dev 5a132ead22 feat(eidos): button audit — focus canon, press archetype, intrinsic box, plain pure-emphasis, spinner via Spinner
4 months ago
dev 4878ccfbef feat(motion): wake the dormant `spring` engine tier — `spring-pop` preset
4 months ago
dev 3d41459936 fix(tooltip): the hover entrance animation never played — content mounted as instant-open
4 months ago
dev 7add5cbda0 feat(motion): tooltip entrance is now a bounce (rebote)
4 months ago
dev f2ed308d33 docs(motion): record (b) step-2/3 done + two implementation findings — RFC §D.12
4 months ago
dev 54268717b4 feat(motion): `<Motion>` wrapper — content animate-in/out primitive, (b) step 3
4 months ago
dev 0a6d112cde feat(motion): pilot the universal `motion` prop on a real component (Badge) — (b) step 2
4 months ago
dev 4f15be9f1b feat(motion): `motionAttrs` helper — content-domain animation surface, (b) step 1
4 months ago
dev bfac34dd4f feat(motion): unify the animation selector to one `motion` prop — (a) of §D.12
4 months ago
dev efb20a9c5f docs(motion): RFC §D.12 (universal `motion` prop, 3 domains) + §D.13 (structural constraints)
4 months ago
dev 2a378360e6 feat(motion): cascade fly-out submenus too (DropdownMenu + ContextMenu)
4 months ago
dev 19ecbf44e4 feat(motion): event-driven cascade rollout to the menu family (ContextMenu + Menubar)
4 months ago
dev 9d40ebe9c8 feat(motion): event-driven item cascade — first real consumer (DropdownMenu)
4 months ago
dev 7d31cd3fd7 feat(eidos): dialog/drawer modal-plane + h3 title role + framework-Button trigger/close + density testbed
4 months ago
dev 27012a5c68 feat(motion): structural stagger index + Cascade orchestrator on the verified model
4 months ago
dev 6307d03055 refactor(motion): retire parallel orchestration motor (Plan A) + verified model + Fase 1 prototype
4 months ago
dev b8637976a0 feat(uix): elevation archetype (A1) + coherence audit + popover/dialog/option-list polish
4 months ago
dev 189932d939 feat(eidos): IconButton primitive + Lote 0 iconOnly->IconButton cascade
4 months ago
dev e16c169f37 feat(eidos): color-field leading value swatch + full-width coherence
4 months ago
dev baa1604f5b fix(eidos): resolve segment-min-width orphan; color 3ch / hex 6ch segment width
4 months ago
dev e0bb7ed5f5 feat(eidos): shared field control-trigger + segment state/width layers; uix- id prefix
4 months ago
dev a9111572cd feat(eidos): universal 1:1 control-text scaling + icon font-coupling
4 months ago
dev 8f84b36f6d feat(eidos): field-family font-scaling coherence (input 1:1 + label step-down)
4 months ago
dev 46952dd6fa feat(motion): Fase 0 — prototipo Svelte-nativo valida el re-diseño del cascade
4 months ago
dev dc62a0268b feat(eidos): canonize theming scales + tokenize magic numbers
4 months ago
dev 02016f8ab5 docs(motion): RFC Apéndice C — M9 children-DOM mode + dropdown-menu README + eidos-motion cross-link
4 months ago
dev d797a0cebc feat(motion): M9 F1c+F2 — dropdown-menu item cascade (children-DOM)
4 months ago
dev db1aa14d1d feat(motion): M9 F1+F1b — modo children-DOM (2ª coordinación, esqueleto + demo)
4 months ago
dev 4ff9f0d277 feat(motion): modo (c) — composición sema + motion (opt-out visual en eidos)
4 months ago
dev e9fc493823 feat(motion): M5 naming type-safe + reversa fluida JS + demo de presets + docs de convivencia
4 months ago
dev 48b183672a feat(motion): servicio de coordinación de presencia (M1–M6)
4 months ago
dev 29729e27f5 docs(soma): add the "compose existing components, flag gaps" rule
4 months ago
dev 7389fb6386 refactor(arts): audit fixes — connection barrel + close dedup, http inline diag, color doc
4 months ago
dev 41115b852c docs(sium): correct stale 'pending svelte adapter' claims
4 months ago
dev 37fe04693a feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
4 months ago
dev 25131127bf docs: retire CONTINUE-icon-type-scale.md; fold its live TODOs into pendiente.md
4 months ago
dev 8a9a0e9c2f docs(eidos): fix stale card icon-scale rule in THEMING §5
4 months ago
dev 4061fb2987 docs(corpus): fix two stale references in THEMING.md "## Referencias"
4 months ago
dev 9926add637 docs(corpus): delete the terra-era DATA_ATTRS fossil + its dead generator
4 months ago
dev 001682c254 docs(corpus): fix dead generate:contracts-docs claim; census the pending fossils
4 months ago
dev 9272c54278 docs(corpus): add docs/comparison.md — honest framework-level positioning
4 months ago
dev e737f0f78d docs(corpus): add docs/testing-and-tooling.md — testing, validation, codegen, SSR
4 months ago
dev 333d791e02 docs(corpus): add docs/getting-started.md — clone to first change
4 months ago
dev cc51535356 docs(corpus): add docs/authoring.md — the rules for writing docs here
4 months ago
dev 7193756a06 docs(corpus): add docs/glossary.md — the invented vocabulary, one line each
4 months ago
dev 08d83b5a30 docs(corpus): add docs/README.md — the corpus entry point (E0), wire it from CLAUDE.md
4 months ago
dev 83b355b08d docs(corpus): finish THEMING stub-split — guides (E4) + comparison/FAQ (E3)
4 months ago
dev 6ba268065d docs(corpus): extract TSC from THEMING into eidos/TSC.md (stub-split)
4 months ago
dev ffcd1051fd docs(corpus): THEMING §14 — resolve the motion contradiction, point at eidos-motion.md
4 months ago
dev 5cda75da71 docs(corpus): add docs/decisions.md — the RFC/design decision index
4 months ago
dev 3c9ad7391d docs(corpus): give the two component checklists distinct roles + cross-links
4 months ago
dev 11f4157882 docs(corpus): split soma README (onboarding) from SOMA_ARCHITECTURE (reference)
4 months ago
dev 9ed35b8d63 docs(corpus): extract arts layer hand-offs to docs/process/
4 months ago
dev 0cccaf9635 docs(process): add CONTINUE hand-off for the doc-corpus migration
4 months ago
dev ae49f737b6 docs(active-architecture): §0 hand-off → contracts reference, §10 status → process snapshot
4 months ago
dev 633820efca docs(active-uix): rewrite README as a timeless layer reference
4 months ago
dev 462d86c08e docs(corpus): point the vocabulary docs at CANON.md (authority), wire it into the reading order
4 months ago
dev c9032a7f2a docs(corpus): add the semantic canon (E2) — single source of truth for the vocabulary
4 months ago
dev b7121054e4 docs(active-architecture): §10 status reflects current canon (8 families, 115 morfos)
4 months ago
dev 41c3b0f956 docs(corpus): extract embedded session hand-offs to docs/process/
4 months ago
dev e74a54ba07 docs(corpus): fix canon drift — 8 sema families, two-axis intent policy, real verbs, retire Terra header
4 months ago
dev 67cec940c0 feat(timer): optional key (auto-keyed timers) + close raw-timer/DOM violations
4 months ago
dev 19ce90b040 refactor(sema,eidos): drop 2 orphan @deprecated aliases + translate Spanish comments
4 months ago
dev 5c18a892a0 docs(soma,morfo): fix dead $lib/util aliases + stale morfo count
4 months ago
dev 145e5b020c feat(eidos): icon follows component font (button sm + radio-cards) + fix button icon sizing
4 months ago
dev 6b29e7dc44 feat(eidos): tie icon scale to type scale + button icons on tokens
4 months ago
dev 34f8087fe8 fix(eidos): QrCode size tokens use literal var names (recipe contract)
4 months ago
dev e5303f8217 fix(eidos): button tonal variants render bg/border + variants×colors gallery
4 months ago
dev e7a3d9085b fix(eidos): QrCode logo visibility + demo content presets
4 months ago
dev e0579f7e24 feat(eidos): add QrCode — themeable QR with own zero-dep encoder
4 months ago
dev 4add3ed58f feat(uix): RadioCards orientation as ResponsiveProp + first responsive-prop example
4 months ago
dev 651f31aed0 fix(uix): RadioCards — hover on a selected card keeps the active colour
4 months ago
dev f59396b51a feat(uix): RadioCards — card-style single-select reusing RadioGroup
4 months ago
dev 7633b1360c test(soma): pin year-grid + file-upload in attr-dedup guard
4 months ago
dev 712fc8587f refactor(soma): de-dup attrs in dropdown-menu + context-menu via renderProps (batch 3 tail)
4 months ago
dev 7bf5d047c3 refactor(soma): de-dup attrs in 12 providers via renderProps (batch 3)
4 months ago
dev e1b43cfb53 refactor(soma): de-dup attrs in 11 providers via renderProps (batch 2)
4 months ago
dev 96179ce392 refactor(soma): de-dup attrs in 16 providers via renderProps (batch 1)
4 months ago
dev 927d7f602d fix(soma): audit precision — scan only .assert() blocks
4 months ago
dev 9d11e8e4eb test(soma): attr-dedup audit script + regression guard
4 months ago
dev c44a259916 refactor(soma): de-dup spin-field triggers + scrubbers via renderProps
4 months ago
dev 75e50becf7 refactor(soma): de-dup CssField Input via renderProps (spin-field family)
4 months ago
dev 019413501b feat(morfo): ariaBoolean entry flag — full de-dup of NumberField Input aria
4 months ago
dev 59a851850c feat(soma): morfo-sourced part props — renderProps() + prop-defined (pilot)
4 months ago
dev 3c059ff087 refactor(eidos): share number-field + css-field visual via spin-field
4 months ago
dev bd9d924577 feat(eidos): themeable stepper glyphs + css-field compact steppers
4 months ago
dev ded673ad0f fix(eidos): unify input focus into a parameterised two-ring model
4 months ago
dev 5e98cc3eb3 feat(uix): heading levels H1-H6 + ToggleGroup deselectable
4 months ago
dev 7da7285d94 feat(uix): cropper, image-picker, image-adjustments + reusable zoom-pan layer
4 months ago
dev 1e6a9df874 feat(eidos): drag pickup-lift as a global motion token (--motion-scale-lift)
4 months ago
dev b3ef470646 feat(uix): FloatPanel — draggable/resizable floating panel
4 months ago
dev 9452bc2545 docs(uix): close css-field audit gaps — eidos README Comparativa + Gaps
4 months ago
dev 7ff1738a12 feat(uix): add CssField — validated CSS-value spinbutton + sium CSS helpers
4 months ago
dev e277fd0547 palabras: panel de imagen rico (componentes del framework) + fit/filtros en el motor + cssLength en sium
4 months ago
dev b97a273c98 palabras: "+" de columna en TODAS las columnas (centro/abajo) + estilo
4 months ago
dev 3b1326ec19 palabras: menú de inserción (tipos+iconos), columnas visibles y "+" por columna
4 months ago
dev ec6fbd784b palabras: menú de acciones en el grip + arreglo del parpadeo (4 bugs)
4 months ago
dev 6c8d911d39 palabras: el grip sigue al cursor por el carril/gutter, no solo por los bloques
4 months ago
dev 97c40d3256 palabras: revertir el piloto del wrapper de bloques (base limpia)
4 months ago
dev d68d2c4513 palabras: grip+drag+inspector y fixes de borrado de bloque
4 months ago
dev 913d49d0ea words: harmonize inspector control tier (one height, one body font)
4 months ago
dev 08f2d82d94 fix(words): one sans across all chrome — kill the mixed-typeface inspector
4 months ago
dev ca8f516f8c fix(words): untrack the langs-registration $effect (no reactive loop)
4 months ago
dev ec77ec5a0b refactor(words): prune 55 orphan recipe tokens — eidos suite green (Phase A)
4 months ago
dev d208f1dcd1 fix(words): API contract — drop empty <style>, onMount → $effect (increment 3)
4 months ago
dev 059f36d2fe refactor(words): unify radius scale (audit increment 2)
4 months ago
dev 54d1f658ab refactor(words): wash scale + token contract fix (audit increment 1)
4 months ago
dev 317cb5b297 docs(words): visual audit + homogenization plan
4 months ago
dev 138ea2a162 fix(temas/tema): cut/scoop switch participates as a cut-capsule (not excluded)
4 months ago
dev 97eda62a14 docs(pendiente): full theme anatomy beyond the visual channels (deferred)
4 months ago
dev a8b390a8f2 fix(temas/tema): switch pill no longer deformed by cut/scoop families
4 months ago
dev 27050a5a11 fix(temas/tema): mode toggle now flips the preview dark↔light
4 months ago
dev f47b794b2a demo(temas): /temas/tema — the applyTheme(seed) capstone made tangible
4 months ago
dev be60c449a8 feat(eidos): applyTheme(seed) — the runtime theme capstone
4 months ago
dev c121365bed docs(bundle): Phase 5a done (base.css prune), 5b deferred with rationale
4 months ago
dev 95be3b6cb1 perf(eidos): prune base.css to role-referenced scales (Phase 5) — −18 KB gz
4 months ago
dev 693ed54572 test(eidos): update recipe-CSS contract to the code-split architecture
4 months ago
dev f452c29acd docs(bundle): mark Phases 2-4 complete + record final numbers
4 months ago
dev 48e9794b29 perf(eidos): code-split final 10 recipes — index.css is foundation-only (Phase 3 batch 5 + Phase 4)
4 months ago
dev 725f7abe37 perf(eidos): code-split 16 picker-family recipes (Phase 3, batch 4)
4 months ago
dev 5c79ee4ec5 perf(eidos): code-split 15 form-control recipes (Phase 3, batch 3)
4 months ago
dev 6c8a7260ec perf(eidos): code-split 7 menu recipes + order-independent menu-indicator (Phase 3, batch 2)
4 months ago
dev edd75affb3 perf(eidos): code-split 35 component recipes (Phase 3, batch 1)
4 months ago
dev eb5e4b1833 perf(eidos): code-split dialog CSS (Phase 1 pilot) + set compound pattern
4 months ago
dev b1ab438523 docs(bundle): correct audit + add execution plan + Phase 0 result
4 months ago
dev 308f638833 perf(eidos): de-duplicate the 10 self-importing components from index.css
4 months ago
dev 9677f573f9 fix(forma): concentric inner radius at `full` (top corners, measured)
4 months ago
dev 003a9a9e47 fix(forma): full-bleed cover at `full` so its corners match the card
4 months ago
dev c3985a848e fix(forma): graceful `full` magnitude — no broken pill-in-pill
4 months ago
dev bef15b1cd6 fix(demos): forma — add `full` to the magnitude control (radius scale already had it)
4 months ago
dev c8dccfbc2f docs(eidos): honor the full layer chain — eidos is the visual system, not a shim
4 months ago
dev efd998d1c7 docs(eidos): two-moment model = sema emits, eidos reads (token is the contract)
4 months ago
dev 898af6419a fix(demos): orquesta — real sema engine for sound/haptic + correct channel framing
4 months ago
dev 21057c398f feat(demos): capstone — /temas/orquesta + CHANNELS_SYNTHESIS (one event, N channels)
4 months ago
dev ac2ef0f19a feat(demos): /temas/estructura showcase + THEMING 31 (structural systems)
4 months ago
dev 66ff993211 feat(eidos): structural systems — space rhythm builder (buildSpaceScale/applySpacing) + RFC
4 months ago
dev 27c8214d98 feat(demos): expose the `shape` control in Badge/Button/Card/TagsInput demos
4 months ago
dev 4045e308b1 feat(eidos): shape prop on TagsInput.Item (the chip) + document universal data-shape
4 months ago
dev 6d56188fde feat(eidos): shape `shape` prop on Badge / Card / Button (per-component family)
4 months ago
dev 05a775c072 fix(demos): forma — scoop chips render clean (flat fill) + pendiente.md
4 months ago
dev 2ef4f4427e feat(demos): /temas/forma — richer real-UI showcase (the live workshop)
4 months ago
dev 6d8c552407 feat(eidos): shape Fase 5 — /temas/forma showcase + docs (channel complete)
4 months ago
dev 51571c869b feat(eidos): shape Fase 4 — applyShape runtime builder
4 months ago
dev 7255a52a0a feat(eidos): shape Fase 3 — eventful corner morph (the differentiator)
4 months ago
dev e04c098787 feat(eidos): shape Fase 2 — concentric nested-radius harmony
4 months ago
dev 7e26608ee0 feat(eidos): shape engine — Fase 1 (continuity + families) + SHAPE_ENGINE_RFC
4 months ago
dev 6998c5bde2 feat(eidos): depth Fase 4 — atmosphere (frost) + applyDepth runtime builder
4 months ago
dev 271a2f8ff0 feat(eidos): depth adoption — elevated components consume the channel (shadow + halo)
4 months ago
dev 0e0fbc05d7 feat(eidos): depth Fase 2 (oklab rim halo) + reference-grade /temas/profundidad
4 months ago
dev 9b420f37cf fix(demos): rebuild /temas/profundidad — real + interactive, not abstract tiles
4 months ago
dev 019b93d284 docs(eidos): THEMING §29 — depth channel (planes + eventful + jaula abierta)
4 months ago
dev fa8fa3b8d7 feat(demos): /temas/profundidad showcase — the two moments of depth, live
4 months ago
dev a289ce666f feat(eidos): depth engine — Fase 3 (canal eventful / momento-evento)
4 months ago
dev bb21f026bf docs(eidos): depth RFC §3.bis — map depth onto the two-moment model
4 months ago
dev 290f5d0c12 feat(eidos): depth engine — Fase 1 (plano unificado) + DEPTH_ENGINE_RFC
4 months ago
dev 4fe2ee27df feat(eidos): close typography hygiene — semantic tokens config-driven + font preloads
4 months ago
dev 11fc93b97f feat(demos): /temas/tipografia showcase — live type scale + axes
4 months ago
dev 427d1d2860 feat(eidos): runtime type-scale builder — applyTypeScale (+ buildTypeScale)
4 months ago
dev 6a848b50e3 feat(eidos): wire variable-font axes (weight range + optical sizing)
4 months ago
dev 76dd3e9d66 refactor(uix): consolidate id library into $active-uix/id (createId + useId)
4 months ago
dev 9f007a84f7 refactor(uix): move useId counter from soma to $active-uix/id
4 months ago
dev f8056f438b fix(demos): eliminate each_key_duplicate crash in 22 component demos
4 months ago
dev e97fdc53c2 fix(demos): drop fake soma snippet from 26 eidos-only component demos
4 months ago
dev 8e162221c3 fix(demos): typography demos fill live width + drop fake soma snippet
4 months ago
dev ecc514951f feat(eidos): typography component props (Phase 3b) — wrap/tracking/leading/numeric/measure
4 months ago
dev d85c2fd376 feat(eidos): typography scales — tracking/leading/features/measure + optical tracking (Phase 3a)
4 months ago
dev f53299d42b feat(eidos): config-driven @font-face (Phase 2) — fonts as theme data (next/font model)
4 months ago
dev 667aa49331 feat(eidos): fluid typography engine (Phase 1) — Utopia clamp, rem, fluid headings
4 months ago
dev b273ecfb69 docs(eidos): TYPOGRAPHY_ENGINE_RFC — plan to reach reference-grade typography
4 months ago
dev 0c1f202186 fix(canvas-text): implement fix-stext T1-T5 (font-load invalidation, LRU, bidi opt-in)
4 months ago
dev 458137c027 docs(color): sync arts/color README + COLOR_ENGINE_RFC with implemented API
4 months ago
dev 6b54f47ebe docs(CLAUDE): extend 2026-06-05 hand-off — checkbox fix + color backlog close
4 months ago
dev afa15aea59 chore(eidos): close theming-engine backlog — P3-5 guard + P3-8 API tidy + triage
4 months ago
dev 21b2329a66 feat(eidos): prefers-contrast: more — stronger neutral chrome a11y (P3-2 follow-up)
4 months ago
dev 76e1877281 fix(eidos): consistent surface ladder light/dark — overlay above muted (P3-11)
4 months ago
dev cd8343849d fix(morfo): checkbox commit events sequence pre -> post (kills 244ms check lag)
4 months ago
dev e6fd014d43 fix(eidos): snappier checkbox check — stroke draw 220ms -> --duration-fast (120ms)
4 months ago
dev 4e71d14c63 docs(CLAUDE): session hand-off 2026-06-05 — color system sprint
4 months ago
dev 4eab3306b8 feat(eidos): forced-colors focus a11y (P3-2) + role border ramp 6->7 (P3-3)
4 months ago
dev 0bb03559a9 feat(eidos): wide-gamut-true generator — buildScheme/applyColorScheme emit oklch()
4 months ago
dev 909ab7f944 feat(eidos): wide-gamut OKLCH output, default-on (RFC Phase 2, strategy A)
4 months ago
dev c4d2e34dbc feat(eidos): runtime theme builder API — eidos.applyColorScheme(seed)
4 months ago
dev cc37bdcebe fix(color): base loss->plum + grafito override section uses real axes
4 months ago
dev 1f39a5676b fix(temas/color): group on-solid by Roles/Intents (not white/dark)
4 months ago
dev fc59ceefc0 fix(temas/color): proper color-input fix (action, keep SSR) + split on-solid in 2 groups
4 months ago
dev 1f61ef1b43 fix(temas/color): ssr:false to kill the color-input hydration warning
4 months ago
dev a13352aa9e fix(temas/color): guard seed color input + make white contrast swatches visible
4 months ago
dev 9bf7263366 feat(color): re-author gold + bronze as distinct metals (OKLCH)
4 months ago
dev 81eb572f3d feat(temas/color): group the palette by Radix taxonomy
4 months ago
dev 1270b84a23 fix(temas/color): guard role override color input against empty value
4 months ago
dev ba9656a57d feat(color): temper() — perceptual-temperature match for intents (keeps hue)
4 months ago
dev daac8b2e54 feat(temas/color): subtle intent harmonization (slider, default 12%)
4 months ago
dev 60af383efa feat(temas/color): per-role overrides in the builder (pin any role)
4 months ago
dev 9b29b7d6c1 feat(temas/color): builder drives the whole-page theme live
4 months ago
dev 4a1936b5f6 feat(temas): live theme builder in /temas/color (deriveScheme end-to-end)
4 months ago
dev 60dd14ec74 feat(color): deriveScheme + harmonize — Material 3 formula in OKLCH
4 months ago
dev 71f97cc470 feat(temas): color system demo at /temas/color (motion-demo style)
4 months ago
dev 2d1ccfbeab fix(eidos): base theme tertiary gray->indigo, distinct from secondary/neutral
4 months ago
dev 8e3221f158 feat(color): APCA-driven on-solid contrast pick in render-css (RFC Phase 1)
4 months ago
dev fc6f53a52e feat(color): uix.color isomorphic color-math engine (RFC Phase 0)
4 months ago
dev 29db54f49b feat(scroll-frames): video init-seek + start/end segment scrubbing
4 months ago
dev ba74b6b416 feat(scroll-frames): adapt to framework + demo; add genie-class motion presets
4 months ago
dev 0b7757d027 refactor(libs/logger): inline SILENT_LOGGER test fixture (audit SU2, partial)
4 months ago
dev 0d66e5a4e7 fix: address architectural audit findings + sync ecosystem docs
4 months ago
dev be6bd540aa feat(motion): motion engine as arts/motion service + F6 tokens + F7 typegen
4 months ago
dev 718cb4ac14 words: harden table-cell normalize + session hand-off docs
4 months ago
dev 4a8f633179 words/engine: table factory seeds block cells, not inline nodes
4 months ago
dev 76b0d1c8de words/engine: guard the mark-walk against undefined block slots
4 months ago
dev 2f6bdb4831 words: rail spans the top-level block + delete button + delete→previous active
4 months ago
dev dd7d67d2a1 words: active-block rail — one colour, fixed gutter column, always present
4 months ago
dev 986d61bde6 words: gutter rail tracks the ACTIVE block + grip jitter + preview columns
4 months ago
dev 91bab32bc2 words: active-block marker → the gutter (not the block) + no select in preview
4 months ago
dev be15bbe4f6 words: preview visible cue + gutter active-block tint + no-caret command guard
4 months ago
dev fff17c58dc words: add the editor menu bar (formatting toolbar + Edit/Preview + inspector)
4 months ago
dev 7256f53e0c words: wire HTML5 drag-reorder for blocks via the gutter grip
4 months ago
dev 3a845f0454 i18n(words): localize the block-handle menu + mark rich paste in the doctrine
4 months ago
dev 75556338db feat(words): "Paste below" in the block-handle menu
4 months ago
dev 65883089b3 feat(words): rich HTML paste — clipboard HTML becomes blocks + marks
4 months ago
dev 48695cffad fix(words): replace stray NUL byte in paste-sanitiser regex with \0 escape
4 months ago
dev b943fa7386 fix(words): normalise inspector inputs — one focus frame, framed textarea
4 months ago
dev 905ecfdad2 fix(words): corner radius is a 5-step preset ToggleGroup
4 months ago
dev 329f30cf30 feat(words): text + background colour in the bubble menu
4 months ago
dev ed71ccf46b fix(words): drawer toggle overlap + faithful preview (columns, headings)
4 months ago
dev 65cf4bde35 fix(words): no trailing paragraph when inserting an image into a column/cell
4 months ago
dev c4acfa4460 feat(words): insert blocks INTO table cells (complete P5m's UI path)
4 months ago
dev aac80f7630 docs(words): sign the export-only serialization decision (no roundtrip)
4 months ago
dev 30192bb853 feat(words): production-grade demo topbar (breadcrumb + Edit/Preview + Saved)
4 months ago
dev aa9ffedfd8 feat(words): table cells hold blocks (TableCell.children → WordsBlock[])
4 months ago
dev 6ce4a0e922 fix(words): restore Image in column Add-block menu + make Drawer inspector hide/slide
4 months ago
dev 44fe111ca0 fix(eidos): dropdown-menu z-index + finish ActiveDom canonization (tabs, resize-observer)
4 months ago
dev 64e13d1dd6 fix(words): column Add-block menu (one per kind, localized) + allow empty column
4 months ago
dev 2d60703ab4 feat(adom): canonize dom.raf() frame scheduler + migrate Words overlays
4 months ago
dev 1186d794cd refactor(words): close audit debt D1 (honest cast) + D2 (dedup image picker)
4 months ago
dev 49802e9257 fix(words): close 4 cross-layer audit findings (morfo/soma/eidos)
4 months ago
dev 8090efc833 docs(theming): align THEMING.md + README with current color/scaling engine
4 months ago
dev 89147d0e1b Grafito theme: rename from untitled-ui + 32-scale palette + Color 3-group
4 months ago
dev 152d2ad960 Theming: Radix-parity palette + intent auto-derivation + color-model docs
4 months ago
dev 66ce4f354d Theming: on-solid contrast by luminance (P2-2) + translucent role surfaces (P2-4)
4 months ago
dev bceaef41e0 Theming: add `scaling` zoom axis (Radix parity), separate from density
4 months ago
dev 98d6799ad0 Theming: generate alpha scales via compositing inverse (P1-1)
4 months ago
dev ef9f589d4c docs(theming): clarify synthetic alpha-scale model + alphaScales opt-in (P1-1)
4 months ago
dev c8fa6e87ed docs(theming): fix §19 reference to nonexistent ActiveEidos.setOverrides()
4 months ago
dev 3adc93b2d1 Banner: route palette through recipe forwarders (P1-5)
4 months ago
dev 218ccfcc5e Theming: untitled-ui demo theme, engine fixes, audit + P0/P1 fixes
4 months ago
dev 9324764675 feat(words): inspector UX overhaul — id-based active block, controls, chrome
4 months ago
dev 7be1d54f89 fix(words): inspector follows every click — drop sticky atomic-block selection
4 months ago
dev e81b53e806 refactor(words)!: column block insertion moves to inspector (React Bricks pattern)
4 months ago
dev eb293b4e50 fix(words): column inserter overlay only covers bottom strip on non-empty columns
4 months ago
dev 8cdc4e3b9e fix(words): column inserter `+` reveals on hover for non-empty columns
4 months ago
dev eb946148cd fix(words): column inserter — caret lands in new block, typing replaces stub
4 months ago
dev bcad6647a7 feat(words): column inserter + provider DOM-selection sync after commands
4 months ago
dev da8d03085b feat(words): inspector ColorPicker integration + 4 root-cause picker fixes
4 months ago
dev 73636d07a9 refactor(words)!: F1 — rework engine to base-Block model, drop v2 + sema
4 months ago
dev 1bb16ff636 feat(words): border-light chrome restyle (declutter)
4 months ago
dev 8afe2728bb feat(words): comfortable reading scale for the content surface
4 months ago
dev 9e13d0083e fix(words): refine the left margin band per feedback
4 months ago
dev 540c35e679 feat(words): gutter rework — drop "+", move insert into drag menu, silver band
4 months ago
dev 005239c732 feat(words): inline heading-level control in the Block drawer panel
4 months ago
dev cc4412b8e9 feat(words): complete image visual panel (R5 / POLISH-1b)
4 months ago
dev 4a0661a53b chore(words): sweep D2 orphans from eidos CSS + README
4 months ago
dev 10ed029b91 refactor(words)!: remove V1 engine — V2 is now the sole engine
4 months ago
dev 5450d5f717 fix(words): gutter redesign — rail out, handle at block lower-left, "+" at block bottom-center
4 months ago
dev 74e54384ea feat(words): R4A.1 — foundation V2 ops (types + normalize + setBlockVisual + 5 block ops) + 31 tests
4 months ago
dev 194ca208f9 feat(words): R3 — render-v2 + HTML + MD serializers + 56 tests
4 months ago
dev 92f0432eb0 docs(continue): R2 cerrado — migrator + parity check + 35 tests
4 months ago
dev 6c70699ff7 feat(words): R2 — V1→V2 migrator + sema parity check + 35 tests
4 months ago
dev d18b32dd93 docs(continue): update with R1 V2 architecture status + roadmap
4 months ago
dev 9b13e3cf23 feat(words): R1 — V2 architecture proposal + types-v2 + validator + 50 tests
4 months ago
dev d126e16e65 fix(words): silence canvas truly, drag for list/table, no overlay hovers + day-end docs
4 months ago
dev 125c62cc20 fix(words): silence canvas, silver rail, direct undo/redo, popover widths, Enter empty
4 months ago
dev ce95473261 feat(words): internal scroll for the content area + capture-phase scroll sync
4 months ago
dev c4be6dbe1b fix(words): silence body event spam + slim top toolbar + reposition handle/inserter
4 months ago
dev c250f7d790 fix(words): close 12 type-drift errors from F2/F3/COLOR sprints
4 months ago
dev d2f184da44 refactor(toggle-group): item is structurally a Toggle (zero derivation duplication)
4 months ago
dev 949cc88d75 fix(codex-audit): narrow content non-null in block-handle + inserter
4 months ago
dev d767d88c03 fix(codex-audit): close P0 + tokenize drift introduced by Words sprint
4 months ago
dev 08ae867caf fix(words): grip lands in the rail (rem→px) + drag preview is the block
4 months ago
dev ccd9d3e0ab fix(words): "+" inserter only shows in the rail gap between blocks
4 months ago
dev 6bcbd8a983 refactor(words): rail is now a real column outside the editor box
4 months ago
dev 33b3a580b7 feat(words): visible dotted band in the grip rail + trim to 1rem
4 months ago
dev 0edb7be26c fix(words): trim the gutter rail to 1.5rem (~24px)
4 months ago
dev 5a3029e589 feat(words): real gutter rail for the block-handle + inserter
4 months ago
dev 377168550c feat(words): drawer follows emptiness — Insert when empty, Inspect when filled
4 months ago
dev 2bf6aef035 fix(words): eliminate block-handle flicker in the gutter + bump hit area
4 months ago
dev 4cd54e2111 feat(words): drawer `side` prop (left | right) + overlays auto-mirror
4 months ago
dev 3128b5f13c refactor(words): drawer reinvented as Insert ↔ Inspect
4 months ago
dev 71c24dca9b refactor(words): drop vertical toolbar orientation
4 months ago
dev a62b23538d feat(words): HTML5 drag-drop block reorder + keyboard grab on grip
4 months ago
dev 305fd70129 fix(words): eliminate flicker on block inserter "+" button
4 months ago
dev 9b1a964c69 feat(words): "+ between blocks" hover-zone inserter (POLISH-2b)
4 months ago
dev e6caf308b0 refactor(words): refine drawer Editable — hover pencil + textarea + icon-only controls
4 months ago
dev ffe2c56f42 feat(words): use Editable for ALT/CAPTION in image drawer panel
4 months ago
dev 66225404e1 feat(words): drag handle + block action menu per block (POLISH-2a)
4 months ago
dev 5acee94777 fix(words): color picker uses applyCommand (no sema) + onchange instead of oninput
4 months ago
dev 5c10ab74b3 feat(words): floating image control bar + generic updateBlock/deleteBlock commands (POLISH-1a)
4 months ago
dev c1c3cd9404 feat(words): parametric color marks — foreground + background pickers (COLOR-3)
4 months ago
dev ab06562abe style(words): drawer per-scope accent color + tinted header (COLOR-2)
4 months ago
dev b3524b3446 fix(words): image block selection — click figure activates image drawer mode (COLOR-1)
4 months ago
dev e25ac301b5 feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3)
4 months ago
dev 9a5f3c2359 refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON)
4 months ago
dev 15dbf0f8fd feat(words): drawer contextual panels — format/image/table/code/list (DRAWER-2)
4 months ago
dev 23f90a775c feat(words): companion drawer right-rail (concept A+D) — shell + default panel (DRAWER-1)
4 months ago
dev ef95b1a82a feat(words): slash command /image + paste/drop with onUploadImage (F3.7, F3.8)
4 months ago
dev cf9cb0b127 demo(words): add image block to sample document + fix readPlainText (F3.11)
4 months ago
dev 86b47cef25 fix(words): normalize handles image blocks + 3 image tests (F3.11 partial)
4 months ago
dev 2a51adc00f feat(words): insertImage operation + command + CSS + extension stub (F3.6, F3.9, F3.10)
4 months ago
dev 08bef77f4b feat(words): image extension foundation — types, render, HTML+Markdown serializers (F3.1-F3.5)
4 months ago
dev f25bf1558e demo(words): enrich status bar with char count + selection + dynamic link/commit (F4.5b)
4 months ago
dev 5bd9f9fe4f fix(words): slash menu wider + description wraps to 2 lines instead of ellipsis (F4.5a)
4 months ago
dev eb6a019b5a fix(words): vertical rhythm — heading top margin, blockquote distinctive, code block generous padding (F4.3)
4 months ago
dev c09f7b0d87 fix(words): table accent tone softer, hover cells, stronger header divider (F4.2)
4 months ago
dev 3cc171f747 fix(words): toolbar horizontal-scroll instead of multi-line wrap on mobile (F4.1)
4 months ago
dev 1043f1de4a refactor(words): extract 8 pure table helpers to extensions/table/utils.ts (F2.3h.1)
4 months ago
dev bde6728590 refactor(words): table extension stub — name + nodeTypes + factories + commandNames (F2.3i)
4 months ago
dev 54cd1744c7 docs(CONTINUE): record F2.3a-e completion + remaining sub-plan
4 months ago
dev d839273536 refactor(words): extract table render to extensions/table (F2.3e)
4 months ago
dev 5797e69b88 refactor(words): extract table HTML serializer (F2.3c)
4 months ago
dev d6f0af4846 refactor(words): extract table markdown serializer (F2.3d)
4 months ago
dev a0afa44da4 refactor(words): extract table factories to extensions/table (F2.3b)
4 months ago
dev a82c422b05 docs(CONTINUE): record F2 sprint state and F2.3 sub-plan
4 months ago
dev 088d31ec5d refactor(words): extract table types to extensions/table/types.ts (F2.3a)
4 months ago
dev 1805b0817d feat(words): extension system skeleton (F2.1 + F2.2)
4 months ago
dev 85b8a56ab0 feat(words): add toolbarLayout grouped|inline (responsive)
4 months ago
dev 58c2c0babf fix(words): close audit P0 + quiet focus event (FASE 1)
4 months ago
dev 4a34c4f4df docs(CONTINUE): note variant canon position in hand-off
4 months ago
dev bd73fb98f3 feat(eidos): variants canon — EIDOS_VARIANTS + per-component lint
4 months ago
dev d3f9be4ab0 fix(toggle): add missing per-color track/hover/border forwarders
4 months ago
dev 9c6375c96e feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage
4 months ago
dev 28bf7f8b49 feat(pickers): wire runtime.trigger('close') in 5 picker providers
5 months ago
dev 312c8740ca refactor(date-picker): rename kind 'date' to 'day' for trio consistency
5 months ago
dev 59c3bc96c4 fix(color-picker): emit data-state explicitly on Provider props
5 months ago
dev 9f1466dc57 fix(avatar): data-color severity optional to acknowledge custom-color escape hatch
5 months ago
dev 3542f3a730 fix(button): remove redundant data-state enum (book §1 purity)
5 months ago
dev 681e056102 fix(uix-audit): close 4 pre-existing issues found in system audit
5 months ago
dev f0440d647a docs(dialog-demo): update prose for polymorphic close
5 months ago
dev 34e0064f2c feat(toast): explicit persistence: 'transient' on signal-announce event
5 months ago
dev 9ca8b4e30b docs(claude/libro): document persistence + a11ySemantic + polymorphic close rollout
5 months ago
dev 8dce794ab4 refactor(pickers): collapse close-* into polymorphic close on 5 picker morfos
5 months ago
dev 9f5fbfdb71 refactor(dialog/drawer/popover): collapse close-* into polymorphic close (book §5.3)
5 months ago
dev c34fd1fd31 feat(uix-components): apply persistence + a11ySemantic + live-region messages to 6 morfos
5 months ago
dev 70f6f3bf0d feat(uix): persistence + a11ySemantic + polymorphic core (book §5.3, §6, §9)
5 months ago
dev e6eee766ee Improve words editor tooling
5 months ago
dev 5f7b2354a8 docs(claude): session hand-off + SEMA_FAMILY_POLICY refresh for Lectura C
5 months ago
dev 7afa7057d4 docs(soma): add Sema events section to 23 component READMEs
5 months ago
dev cbf76b66c8 feat(soma): wire runtime.trigger for batch C (gestural + wrappers)
5 months ago
dev 0784cae578 feat(soma): wire runtime.trigger for batch B (8 components)
5 months ago
dev adf89a0811 feat(soma): wire runtime.trigger for batch A (7 components)
5 months ago
dev 66318c4759 fix(tree-view): target branches for emerge-expand/collapse
5 months ago
dev 43b1537291 refactor(picker-shell): close as INTERNAL primitive (audit P1 #5)
5 months ago
dev 826ca2bf46 docs(sema): add D.8 — channels: qué es canal y qué no
5 months ago
dev 2d562f3788 feat(sema): Lectura C steps 5-8 — 11 sema packs + doctrina samples
5 months ago
dev 504780ad8b feat(morfo/sema): add expression field + sema coverage check (P1 #3)
5 months ago
dev b0869878d8 docs(audit): mark P1 #4 (non-canonical event verbs) as RESUELTO
5 months ago
dev 00f0b740e6 refactor(sema/docs): apply author's evaluation — status classification + intentRequirement/intentGuidance split
5 months ago
dev bbfefdb63d refactor(morfo): rename 12 events to {family}-{verb}-{variant} canonical shape (Capa 3 cluster otros)
5 months ago
dev f3641347de feat(sema/morfo): add commit.unselect to canon + apply to 6 morfos
5 months ago
dev 1582572e9b refactor(morfo): commit-clear → commit-reset across 10 components (cluster clear resolved)
5 months ago
dev 670c54177d docs: document deviations from the book canon (LIBRO_VARIACIONES_Y_EXTENSIONES.md)
5 months ago
dev 103c49a7ab refactor(morfo): align 22 events with book family doctrine (Capa 2)
5 months ago
dev a74f26728c fix(sema/verbs): validateEventName longest-prefix-match for multi-word verbs
5 months ago
dev 23afeb6f86 refactor(button): align with book cap. 22 — contact.activate, intent visual-only (Plan B commit 2)
5 months ago
dev af6d6c762a refactor(sema): align verb canon with «Diseñando lo que ocurre» (Plan B commit 1)
5 months ago
dev 928b3c15b6 fix(eidos/command): route shortcut keyboard listener via ActiveDom
5 months ago
dev c85c729dde fix(eidos): use ActiveEidos.require() instead of getActiveUix() in 15 components
5 months ago
dev cd2c0326dc Revert "refactor(soma/eidos): migrate 4 service components from Eidos to Soma"
5 months ago
dev 540abee026 refactor(soma/eidos): migrate 4 service components from Eidos to Soma
5 months ago
dev 546aa8d0ee fix(soma/eidos): close last 5 check errors — PassthroughProps helper + floating/shell cast
5 months ago
dev ee1afdcfbb fix(langs): close P0 #3 — translations 58 errors + 8 warnings → 6 (all words) + 2
5 months ago
dev 5256008700 fix(audit-codex): resolve P0 blockers + close most check errors (42 → 5)
5 months ago
dev 9b809e4ecb chore(eidos): drop \$app/environment imports (decouple from SvelteKit)
5 months ago
dev b54d42e230 feat(format/dates): add formatRelative to engine + route RelativeTime through runtime
5 months ago
dev 7b01397aab feat(format/numbers): per-call locale override (parity with dates engine)
5 months ago
dev 89f52c5e88 feat(eidos): Card, Avatar ext, Image, SText, Badge, service components + dates engine fixes
5 months ago
dev 0804749147 Words: text alignment, heading picker, ToolPopover cleanup, word boundary fixes
5 months ago
dev cc0d38ec52 refactor(floating): extract createFloatingShellRoot + buildFloatingShellWrapperProps helpers (audit Round 3 §3 #1)
5 months ago
dev bf7e116af9 refactor(soma): route FieldProvider through field/internals barrel + register DRP view sub-parts (audit Round 3 §3 #23 + P4 follow-up)
5 months ago
dev 3a23fa239b docs(pendientes): downgrade BaseSegmentProvider from 'diferir' to 'descartar' — false positive
5 months ago
dev 3c87993513 refactor(date-field): split updateSegment into pipeline helpers (audit Round 3 §3 #22)
5 months ago
dev c1301fb550 refactor(soma): extract ListSelectionHelper for combobox/select (audit Round 3 §3 #21)
5 months ago
dev 00cd3a2c61 docs(pendientes): close Kim Round 3 — item 28 done, items 19-24/27/29 deferred
5 months ago
dev d4e668b234 refactor(soma): revert context.set try/catch; mock pickerShellContext explicitly in picker tests
5 months ago
dev cfbe754299 docs(pendientes): add performance bottleneck analysis for docs site
5 months ago
dev 91ecf87cd5 fix(pendientes): tooltip Play buttons + range view-cell tint differentiation
5 months ago
dev e93c0c7337 refactor(date pickers): promote MonthView/YearView to soma parts (audit 5.3 P4)
5 months ago
dev a5243168e5 refactor(time-picker): promote HourScale ruler to soma part (audit 5.3 P2)
5 months ago
dev 74ccfead70 refactor(color-picker): stamp current-color in soma trigger (audit 5.3 P1)
5 months ago
dev a15b785cc1 refactor(table): promote sort affordance from eidos to soma part (audit 5.3 P3)
5 months ago
dev f13103e9a9 fix(uix audit): align morfo sema scope + remove Provider leak on 3 eidos namespaces
5 months ago
dev 12a7e5cfd3 fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
5 months ago
dev bb56e94864 fix(password-field): chrome layout + meter visibility + form field styling
5 months ago
dev 90afe27d81 fix(password-field): visual issues — double border, broken layout, stale lang refs
5 months ago
dev 314f1cd523 fix(eidos): rename `children` prop in 4 wrappers to avoid snippet self-shadow
5 months ago
dev 8d277fe929 fix(password-field): align input chrome with system + add Form/SIUM demo
5 months ago
dev da3e5026bb feat: build PasswordField (full stack)
5 months ago
dev fead097ce1 fix(demos): rewrite Skeleton + Spinner demos to canonical 6-tab depth
5 months ago
dev 616d8535f7 feat(eidos): add Skeleton and Spinner loader primitives
5 months ago
dev f8897d92d6 fix: clear remaining `npm run check` errors (12 → 0)
5 months ago
dev ee54029b1b Update continuation notes for Words
5 months ago
dev 940e94bb64 Build TextArea component + fix locale propagation bug across soma providers
5 months ago
dev 3fc4ced37b feat(carousel): showIndicators + showControls visibility props
5 months ago
dev 92c5e421c9 fix(carousel): :active press feedback was replacing centering transform
5 months ago
dev 8cf2fe813f fix(button): icon wrappers must not capture pointer (carousel triggers dead-clicked)
5 months ago
dev a81b44b40c fix(carousel): first-click no-op — viewportSize was stale until ResizeObserver fired
5 months ago
dev 09bbf05ec0 fix(carousel demo): default loop=true so triggers never go dead at boundaries
5 months ago
dev f197463968 refactor(carousel demo): move Prev/Next triggers outside Viewport
5 months ago
dev b1b1e39dc0 fix(carousel): chevron direction follows orientation
5 months ago
dev a6baae0cf1 fix(carousel/range-calendar/time-range-field): apply canonical i18n + vertical layout patterns
5 months ago
dev 23ad9351ea feat(carousel): wire dir + locale (was hardcoded 'ltr'); expose i18n in demos
5 months ago
dev f744583c12 fix(range-calendar demo): add explicit none/preset chips to minDays + maxDays
5 months ago
dev f291c08865 fix(carousel/range-calendar/time-range-field): visual bugs across all three
5 months ago
dev 44514c3cc1 fix(carousel): vertical overflow + close gaps vs embla/ark-ui/mantine
5 months ago
dev 2cecc0b3b7 feat(eidos): port range-calendar from soma + extract shared partial
5 months ago
dev 68670b6331 feat(eidos): port carousel from soma
5 months ago
dev db21767db1 feat(eidos): port time-range-field from soma
5 months ago
dev 67726cb6f3 fix(soma navigation-menu): suppress click-close right after hover-open
5 months ago
dev a010f8f255 fix(eidos navigation-menu): style sub-items inside Content
5 months ago
dev fd34fa535c fix(eidos navigation-menu): Content floats as popover, not inline
5 months ago
dev 065cc6a369 feat(eidos): port GridList (selectable list w/ interactive row children)
5 months ago
dev 52627e60bb feat(eidos): port NavigationMenu (top-level site navigation w/ mega-menus)
5 months ago
dev 830025b4ad fix(eidos listbox): add max-block-size + overflow-y auto
5 months ago
dev 80b21f63ba feat(eidos): port Listbox (embedded selection list)
5 months ago
dev d58b684c5c docs(uix sidebar): list LinkPreview, Menubar, Command under Overlays
5 months ago
dev 8ab8f9dae1 feat(eidos): port Command (⌘K palette)
5 months ago
dev 9def877dbd feat(eidos): port Menubar (desktop File/Edit/View bar)
5 months ago
dev 0afcabee8b feat(eidos): port LinkPreview (Radix HoverCard equivalent)
5 months ago
dev b414ec2b85 docs(CONTINUE): refresh for next session — #118 + #120 + #127 closed
5 months ago
dev 0b55be5c36 refactor(combobox + search-field): ClearButton/Clear render <Button>
5 months ago
dev 32598d7cf3 fix(alert-dialog): narrow Action.variant to canonical SelectionVariant (3)
5 months ago
dev c98510aff7 demo(alert-dialog): expose variant/size chips for Action and Cancel
5 months ago
dev c6f063dc6a refactor(alert-dialog): Action/Cancel render <Button>; intent flows via snippet
5 months ago
dev a2709476f4 style(menu-indicator): align with Checkbox / RadioGroup visual
5 months ago
dev 4b5bd9f35d refactor(dropdown-menu): remove unused modal; Trigger→Button; canon vocab fixes
5 months ago
dev 0e9628b8f0 refactor(eidos): extract shared menu-item indicator partial
5 months ago
dev dc03773ca5 docs(CONTINUE): refresh for next session — Button + canonical demos done; #118 #120 open
5 months ago
dev a804c39208 feat(eidos): Button + 9 demo rebuilds + picker recipe polish
5 months ago
dev ec7cdd60b4 fix(date-range-field): canonical demo + recipe specificity + DateField overlay
5 months ago
dev 18edecba5b fix(date-range-field demo): key each segments by index, not seg.part
5 months ago
dev 754754d4a4 feat(date-range-field): port from soma to eidos (Tier 2 sprint, 1/3)
5 months ago
dev 2d8599b62f fix(dropdown-menu, context-menu): keep menu open on CheckboxItem / RadioItem activation
5 months ago
dev 207774714a feat(pin-input): port from soma to eidos (Tier 1 sprint, 5/5 — DONE)
5 months ago
dev 21eb8a6264 feat(context-menu): port from soma to eidos (Tier 1 sprint, 4/5)
5 months ago
dev f19b5c7270 docs(uix): add Toggle group · Alert dialog · Dropdown menu to sidebar nav
5 months ago
dev 4e477e637b feat(dropdown-menu): port from soma to eidos (Tier 1 sprint, 3/5)
5 months ago
dev d5630a528f feat(alert-dialog): port from soma to eidos (Tier 1 sprint, 2/5)
5 months ago
dev 61ffbade46 feat(toggle-group): port from soma to eidos (Tier 1 sprint, 1/5)
5 months ago
dev 4324cb489d fix(splitter): register sema scope + cascade so handle-pick/drag actually sound
5 months ago
dev 59505e6da2 feat(splitter): sema + dynamic drag-signal sound infrastructure
5 months ago
dev b504c1c4b7 feat(eidos foundation): vertebrate typography via named-style aliases + R-2.7
5 months ago
dev c703e2e7ff fix(scroll-area): split mount-vs-visibility — the real type='scroll' / 'hover' bug
5 months ago
dev e11d3539ef feat(scroll-area): add size/radius/scrollbars props + fix scroll-type race
5 months ago
dev 560ce8f66c fix(scroll-area, drawer): horizontal scrollbar visibility + drawer drag affordance
5 months ago
dev bd2e40366a feat(layout): Layout Batch 3 — scroll-area, separator, splitter + splitter sema rewire
5 months ago
dev a08a0a9890 fix(aspect-ratio): fill inline axis by default + panel demo uses solid colors
5 months ago
dev d421f1fb5d feat(typography): 8 primitives — Heading, Display, Code, CodeBlock, Kbd, Mark, Highlight, Link
5 months ago
dev 0353170982 feat(typography): Text — POC consuming new --style-* foundation tokens
5 months ago
dev 015bd761bd feat(eidos foundation): emit --style-{name}-* tokens for named typography styles
5 months ago
dev 376f9e33eb feat(layout): Layout Batch 2 — aspect-ratio, auto-grid, banner, float
5 months ago
dev 265a3946b5 feat(layout): plug Batch 1 gaps — Flex.alignContent + Grid.columns/rows/inline/alignContent
5 months ago
dev ec66999b6c docs+style: close last 3 NEEDS-WORK → 77/77 PASS audit
5 months ago
dev fdb0e68028 docs(layout): READMEs for 8 layout primitives (audit PASS)
5 months ago
dev 60a50f1c27 docs: COMPONENT_AUDIT_GUIDE.md — inviolable pre-flight + audit script
5 months ago
dev 4d45cfcc82 docs: COMPONENT_AUDIT_GUIDE.md — inviolable pre-flight for component work
5 months ago
dev 591b08853a feat(layout): canon demos + morfos for the remaining 6 primitives
5 months ago
dev 0dc9c523d1 feat(layout): canon Flex demo + morfo (mirrors Box pattern)
5 months ago
dev 66c6897c61 feat(layout): canon Box demo + relocate grid item placement props to Box
5 months ago
dev 9ab6746c63 chore(uix nav): add Layout group to sidebar for the 8 new primitives
5 months ago
dev 9ec2a57a85 feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
5 months ago
dev 20709caf9c refactor(combobox): SelectedTags as band above the Control (Chakra pattern)
5 months ago
dev 91b0efc461 fix(combobox): inline chips + input flow as siblings via display:contents
5 months ago
dev 87e3d68f86 fix(combobox): pin clear+trigger to first row, not vertical center
5 months ago
dev 64bbe9b158 fix(combobox): pin clear+trigger absolute when chips present (MUI pattern)
5 months ago
dev 30e9517a9a feat(combobox): SelectedTags inline chips for multi mode
5 months ago
dev 26daa9e22e feat(search-field demo): full keyboard nav for results popover
5 months ago
dev 99a1f05cd7 fix(search-field demo): close popover after pick without reopening on refocus
5 months ago
dev 82349f15b4 feat(search-field): floating results popover in demo + hide native clear
5 months ago
dev afb09c2254 feat(picker-shell): extend to Root + Header + Body + Footer layout
5 months ago
dev da10548a68 revert(popover): drop auto-anchor min-width default; keep content-driven sizing
5 months ago
dev 79c94d85d5 wip(popover): unified min-width = anchor + sideOffset 6 across pickers
5 months ago
dev df948aa35a fix(combobox): input mirrors selected label in single mode; multi preserves filter
5 months ago
dev 8aa6086976 docs(search-field demo): add live filterable results panel
5 months ago
dev 9fbf8e48b5 feat(number-field, search-field, combobox): audit-driven improvements
5 months ago
dev 7bc17485dc feat(editable): invalid + required + form + Control + better hover/disabled + defaultValue
5 months ago
dev 75136e6de5 fix(select) + feat(field): match-anchor width, label persistence, OptionalIndicator, auto-label
5 months ago
dev ffdb480403 feat(select): invalid state + ItemDescription + defaultValue + visual fixes
5 months ago
dev 826481f1d5 fix(picker-shell): footer buttons scale with picker size (xs/sm/md/lg)
5 months ago
dev b11ef88bca fix(month-grid, year-grid): stretch to popover content width inside picker
5 months ago
dev 187d7d3751 refactor(date-picker): MonthView / YearView delegate to MonthGrid / YearGrid
5 months ago
dev 8b885e4448 chore(toast): add APG alert pattern URL to morfo
5 months ago
dev fd3a0b00c9 feat(calendar, date-picker, date-range-picker): add xs size + fix duplicate footer
5 months ago
dev f4707bd0c9 feat(month-grid, year-grid): add xs size
5 months ago
dev c65235337d fix(month-grid, year-grid): cell label fallback now renders
5 months ago
dev f7831563a1 feat(month-grid, year-grid): standalone primitives parallel to date-picker views
5 months ago
dev dd22cfc2bc feat(time-range-picker): eidos wrapper + canonical picker-shell parity
5 months ago
dev cae638c993 fix(color-picker): ValueSwatch color overlay now actually shows on top of checker
5 months ago
dev 8515f5e673 fix(color-picker): ValueSwatch now reactively reflects the current color
5 months ago
dev da19728cb6 feat(color-picker): area drag emits handle-pick/handle-drag/commit-set + refresh demo snippets
5 months ago
dev 3e5be857c4 fix(color-picker): single-row preset palette of 10 swatches that share container width
5 months ago
dev aa62f0203c refactor(pickers): date/time/date-range pickers consume shared PickerShell
5 months ago
dev aaf35fdbb0 fix(color-picker/demo): compact value row — swatch + channel input + eyedropper inline
5 months ago
dev 709d8a108e fix(color-picker): fix slider gradients + add live preview + label eyedropper
5 months ago
dev 714ee3735c feat(picker-shell): extract shared Footer/Clear/Cancel/Close + apply to color-picker
5 months ago
dev 5d007ed9f9 refactor(color-picker): channel slider composes generic SliderProvider + N-8 norm
5 months ago
dev 2731ee567b feat(eidos): add ColorPicker wrapper + demo
5 months ago
dev 2f147897d9 feat(morfo/time-picker): declare HourSlider/MinuteSlider/SecondSlider as parts + ruler opt-out prop
5 months ago
dev e7b2e461bc fix(eidos/time-picker): default-compose Range+Thumb and add labelled ruler
5 months ago
dev 391de6e04c feat(eidos): add TimePicker wrapper + demo following P-1..P-5 picker contract
5 months ago
dev ff279ae828 feat(eidos): add TimeField + ColorField wrappers and demos
5 months ago
dev ec878fd2ce docs(eidos): add 'Picker patterns' canonical contract section
5 months ago
dev 72ff90b71f docs: consolidate kind contract + composition norms (sweep)
5 months ago
dev 05a432afc0 date-range-picker: YearRangeView + MonthRangeView (kind grids)
5 months ago
dev 9baccecda9 picker demos: footer composition switches + popover closed by default
5 months ago
dev 4a866cf86a date-range-picker: propagate kind to start/end inputs
5 months ago
dev 6299e6ebc6 pickers: Footer parts = pure composition (drop *Button props)
5 months ago
dev 0956aa1c37 date-picker: kind derives segments at soma layer + year/month views
5 months ago
dev 46ce1d800d date-picker: kind prop (date/month/year) — commit A scaffolding
5 months ago
dev 3956a5d39e picker demos: open by default + footer buttons enabled
5 months ago
dev bf9dc8934d pickers: visible readonly segment styling + date-picker demo control
5 months ago
dev 394aeaa476 pickers: promote Footer/Clear/Cancel/Close to canonical Eidos parts
5 months ago
dev 9bd9db408d date-picker: mode='modal' + footer (clear/cancel/closeButton)
5 months ago
dev 6144837174 audit: add R-2.6 (token presence) + D-7.4 (chip parity) rules
5 months ago
dev dfb64511de eidos: theming fixes + size/variant parity batch + docs
5 months ago
dev 5d31645939 search-field demo: clear button is an X icon, not a word
5 months ago
dev fd9fb633e0 search-field demo: clear button text follows the locale
5 months ago
dev 332ef63177 demos: theme scrollbars + add missing data-uix-radio chrome
5 months ago
dev 8ccd9de033 demos: homogenize input chrome across all demo Controls
5 months ago
dev 539d9fac39 batch: scaffold README sections + audit refinements → 67/67 PASS
5 months ago
dev b11097fcf2 batch: texts.label for 19 interactive components + barrel sync + feed verb fix
5 months ago
dev 98fc40bf62 batch A-3.1: add semantic events to 12 interactive morfos
5 months ago
dev 491e4be75c file-upload demo: ship valid PNG bytes so previews render
5 months ago
dev e372640f0f accordion: tone down open-trigger highlight (no pink text)
5 months ago
dev 6161b0f4cd batch: add texts.label to 12 passive components
5 months ago
dev 2fc8a30b22 drawer: PASS audit + texts.label + Baseline/Gaps
5 months ago
dev f8c9734c00 dialog: PASS audit + texts.label + Baseline/Gaps sections
5 months ago
dev 42755cae23 popover: PASS audit + accept bare canonical verbs in A-3.6
5 months ago
dev 6181dba46d form: PASS audit + refine R-1.2 to require morfo declaration
5 months ago
dev 714df0e5c9 combobox: PASS audit + extend focus-move list for listbox/menu nav
5 months ago
dev 98d233310d icon: README sections + Passive justification + audit refinements
5 months ago
dev cb3dc8a520 calendar: README sections + data-readonly recipe + audit A-3.7 refinement
5 months ago
dev f456e8fd32 docs: add PENDIENTES.md backlog
5 months ago
dev 383e2ff7f7 date-range-picker: mode prop + footer button props + revert group chrome
5 months ago
dev 0d164b54de date-range-picker: unify start + end into a single input chrome
5 months ago
dev 3b464ee361 range-calendar: smart heading + per-calendar title in header
5 months ago
dev fb47f0475b date-field: opt out of value→placeholder sync in range pickers
5 months ago
dev 5f8adbeca6 date-picker + date-range-picker: trigger embedded inside the input
5 months ago
dev 2fcade063b tooltip: expand morfo + recipe states → PASS audit
5 months ago
dev c61ba78d35 date-range-picker: expand morfo + README → PASS audit
5 months ago
dev 9728e76975 eidos/date-range-picker: distinct start/end tones + purge raw hex
5 months ago
dev aaffba2ff5 soma/range-calendar: fix endpoint clear + stop auto-paging on select
5 months ago
dev 2b3f3e093e uix: rename morfo.translations → morfo.texts (idlangref discipline)
5 months ago
dev 39eb0eec86 uix: date-picker + date-range-picker components + component audit infra
5 months ago
dev d6f550554d Add Eidos calendar component
5 months ago
dev fb5dd0919c Advance Eidos demos and form validation docs
5 months ago
dev 09e2618787 Continue Eidos component migration
5 months ago
dev 00742e33b4 Advance Eidos component migration
5 months ago
dev ae5589b0d4 Add Eidos tooltip reference comparison
5 months ago
dev 5c0137e57e Add Eidos popover reference comparison
5 months ago
dev 6707034fbd Add Eidos drawer reference comparison
5 months ago
dev 0b399cc6e8 Add Eidos dialog reference comparison
5 months ago
dev 458996882e Add Eidos breadcrumb wrapper
5 months ago
dev e359cabd4b Document Eidos migration handoff
5 months ago
dev 4dc09c2897 Add Eidos number field wrapper
5 months ago
dev 3c60d33a6c Add Eidos search field wrapper
5 months ago
dev 5a6193d365 Add Eidos rating group wrapper
5 months ago
dev f62b32677c Add Eidos pagination wrapper
5 months ago
dev bdaae718ad Add Eidos slider wrapper
5 months ago
dev 5895f9d8b8 Add Eidos progress and meter wrappers
5 months ago
dev d3efdb5c30 Remove legacy Eidos CSS layout fallback
5 months ago
dev 73915f03a1 Guard Eidos CSS recipe imports
5 months ago
dev cdf47c1f53 Remove Eidos flat API residue
5 months ago
dev 8194ad3a98 Clarify Eidos intent icon wording
5 months ago
dev 753e27487f Guard Eidos recipe value leaks
5 months ago
dev 9c01c12402 Move Eidos wrapper font sizes to recipes
5 months ago
dev c939de90c8 Harden Eidos config and wrappers
5 months ago
dev eba7b4522d Guard persisted Eidos config shapes
5 months ago
dev b2c832b47a Reject unsafe Eidos CSS config values
5 months ago
dev 546ad40251 Validate Eidos color role slots
5 months ago
dev b48a6c48bf Validate Eidos semantic color groups
5 months ago
dev cfee72bfc8 Validate Eidos static primitives
5 months ago
dev 110c5026b9 Validate Eidos motion and icon primitives
5 months ago
dev 45d266b1c6 Validate Eidos typography token keys
5 months ago
dev 56e173f7e2 Validate Eidos CSS-facing keys
5 months ago
dev 8e17e64d13 Harden Eidos runtime config validation
5 months ago
dev bfd3b4020e Validate persisted Eidos configs
5 months ago
dev 3c115ce093 Guard Eidos namespace assignments
5 months ago
dev bddfff9e39 Add UIX permutation axis coverage
5 months ago
dev 277f1f24dc Expand UIX permutation route coverage
5 months ago
dev 6508c7e562 Instrument UIX permutation demos
5 months ago
dev c04b6a241a Reconnect permutation runner routes
5 months ago
dev cf7c83a6d3 Reconnect morfo DOM check
5 months ago
dev f815dd95ac Guard Eidos visual attr wiring
5 months ago
dev 5662f1aa94 Refresh route smoke validation
5 months ago
dev 53b76f6b4d Clarify Eidos visual-only parts
5 months ago
dev 240916e7d6 Refresh Eidos recipe probe
5 months ago
dev 8ffea9f697 Record Eidos route smoke
5 months ago
dev 250ae7f75c Guard Eidos component API shape
5 months ago
dev a8045658aa Guard Eidos recipe CSS contract
5 months ago
dev 306c371aef Address Eidos audit findings
5 months ago
dev f9591c5216 Fill Eidos recipe token gaps
5 months ago
dev 13cc14f971 Normalize Eidos component surface
5 months ago
dev 9bde5b212e Close Soma phase and hand off to Eidos
5 months ago
dev ad4a35800f Align Soma docs with current layering
5 months ago
dev ee84c84679 Record UIX route smoke validation
5 months ago
dev 848259e225 Allow path-based sync Sium combinators
5 months ago
dev ac5fdca4e7 Guard Sium lazy self resolution
5 months ago
dev c4eafd02f2 Allow sync sentinel short-circuits in Sium
5 months ago
dev fbb1e0f645 Tighten Sium validation integrity
5 months ago
dev 7a85ea59c2 Align Soma public wrappers with providers
5 months ago
dev b74e363e01 Update continuation notes for Soma cleanup
5 months ago
dev c9ac269d5d Record Soma scroll lock validation
5 months ago
dev a4c04a00b3 Inject ActiveDom into Soma scroll lock
5 months ago
dev 945ef684c0 Record Soma validation after cleanup
5 months ago
dev f276ef964f Guard Sema runtime boundaries
5 months ago
dev e3689c1080 Remove Soma common langs facade
5 months ago
dev e709316380 Narrow Soma root barrel
5 months ago
dev e7bb709e83 Keep Soma internals off public alias
5 months ago
dev 48e21e5634 Record Soma full test validation
5 months ago
dev 06e6a1141f Record Soma route smoke validation
5 months ago
dev 2fb1dd78c9 Guard Soma component README links
5 months ago
dev 8baa422974 Guard Soma wrapper export contract
5 months ago
dev 959ad3b1f9 Refresh Soma form import docs
5 months ago
dev 96edd41545 Tighten Soma public component surface
5 months ago
dev 1dd9d62eae Refactor Sium domain schema helpers
5 months ago
dev 2255d9f706 Add drag drop provider coverage
5 months ago
dev 2956895e04 Add toast provider coverage
5 months ago
dev ccded8bc4a Add form provider coverage
5 months ago
dev 0464111330 Add table provider coverage
5 months ago
dev d6d23b3c9b Add link preview provider coverage
5 months ago
dev ef157feccc Add grid list provider coverage
5 months ago
dev 9f60aa62a2 Add carousel provider coverage
5 months ago
dev fe61f5be9b Add alert dialog provider coverage
5 months ago
dev 3ceb94619a Add editable provider coverage
5 months ago
dev c3108b6903 Add tags input provider coverage
5 months ago
dev 3e1cd3148d Add feed provider coverage
5 months ago
dev c0221e0365 Add announce provider coverage
5 months ago
dev d2a0998dc8 Add tag group provider coverage
5 months ago
dev c108d02512 Add rating group provider coverage
5 months ago
dev 419ec80890 Add pin input provider coverage
5 months ago
dev 4b5d9078ed Add splitter provider coverage
5 months ago
dev 445c5387f0 Add scroll area provider coverage
5 months ago
dev 7f5e46a66e Add stepper provider coverage
5 months ago
dev 572779e905 Add toolbar provider coverage
5 months ago
dev 9d7c824652 Add breadcrumb provider coverage
5 months ago
dev 500904a2ed Add pagination provider coverage
5 months ago
dev eee0c6b4b1 Add search field provider coverage
5 months ago
dev acab242c61 Add field provider coverage
5 months ago
dev 6a5152a5d7 Add tooltip provider coverage
5 months ago
dev e22b418324 Add clipboard provider coverage
5 months ago
dev 407eb2f455 Add collapsible provider coverage
5 months ago
dev 8c2cb82059 Add progress and meter coverage
5 months ago
dev 41da8a2b26 Add slider provider coverage
5 months ago
dev 9975ba8631 Add tabs provider coverage
5 months ago
dev 15dc542117 Add toggle group provider coverage
5 months ago
dev e3fd5b6a70 Add toggle provider coverage
5 months ago
dev 0dc6141849 Add switch provider coverage
5 months ago
dev 0d338d47f2 Add radio group provider coverage
5 months ago
dev 37cd177c37 Add checkbox provider coverage
5 months ago
dev f3dadd0be7 Add accordion provider coverage
5 months ago
dev 30d77474f0 Add tree view provider coverage
5 months ago
dev 89790957a0 Add listbox provider coverage
5 months ago
dev ad5a9627c7 Add menubar provider coverage
5 months ago
dev 5c434a0cf2 Add context menu provider coverage
5 months ago
dev a6a8aec86d Add dropdown menu provider coverage
5 months ago
dev 558f76626b Add tree grid provider coverage
5 months ago
dev d678d4ee60 Add navigation menu provider coverage
5 months ago
dev c02b13cd47 Add color picker provider coverage
5 months ago
dev 813c4c440c Add color field provider coverage
5 months ago
dev cab1363616 Add file upload provider coverage
5 months ago
dev eeb168043b Add number field provider coverage
5 months ago
dev 5ebe68d640 Add virtual grid provider coverage
5 months ago
dev 859f873eb2 Add virtual list provider coverage
5 months ago
dev 5878e780bc Add date field provider coverage
5 months ago
dev 2b36e2036c Add date range field provider coverage
5 months ago
dev c668cc1543 Add time field provider coverage
5 months ago
dev 5971d6233d Add time range field provider coverage
5 months ago
dev e4ce79cacd Add time range picker provider coverage
5 months ago
dev a44338ff2c Add picker provider coverage
5 months ago
dev 9bbfd0f3c4 Refresh continuation validation totals
5 months ago
dev 05f87dc44a Add Combobox provider coverage
5 months ago
dev 89bb709329 Update continuation notes for Soma audit fixes
5 months ago
dev 9fa00fa258 Move Soma public types out of providers
5 months ago
dev eaeebd6991 Route Soma layer timers through UIX scheduler
5 months ago
dev 3050d273dd Align Select public types and Soma inventory
5 months ago
dev 701f83d648 Document Soma coverage and domain lib split
5 months ago
dev d319ec6b66 Move form core to forms lib
5 months ago
dev 02c1fb43ab Move table core to datagrid lib
5 months ago
dev 4829ce1321 Move command scorer to shared strings lib
5 months ago
dev e1a21269ec Hide internal Soma provider classes from barrels
5 months ago
dev 25870b4e55 Expand Soma component coverage
5 months ago
dev a3bcb66cde Refresh UIX audit follow-up docs
5 months ago
dev c6d24eeea3 Add high-risk Soma provider tests
5 months ago
dev 802ebce655 Tighten sema channel and floating contracts
5 months ago
dev 8b195e38e2 Drive virtualized item attrs from Soma runtime
5 months ago
dev 06d8926356 Drive Feed attrs from Soma runtime
5 months ago
dev 82aa9af76d Drive TagsInput root attrs from Soma runtime
5 months ago
dev 02ee7e49aa Drive TagGroup root attrs from Soma runtime
5 months ago
dev f3e5f113ca Drive RatingGroup attrs from Soma runtime
5 months ago
dev c5595dda71 Drive Stepper state attrs from Soma runtime
5 months ago
dev 72c195d1c6 Drive Splitter attrs from Soma runtime
5 months ago
dev 12b41e2324 Drive Slider attrs from Soma runtime
5 months ago
dev 0eb50840e0 Drive Editable root attrs from Soma runtime
5 months ago
dev e28b486c1c Drive date and time field root attrs from Soma runtime
5 months ago
dev c501ccbdf0 Drive range field attrs from Soma runtime
5 months ago
dev 95905c209b Drive Form root attrs from Soma runtime
5 months ago
dev c72ce793b0 Drive NumberField root attrs from Soma runtime
5 months ago
dev 89243e2f52 Drive SearchField root attrs from Soma runtime
5 months ago
dev c6cf313734 Drive Field attrs from Soma runtime
5 months ago
dev 60403b95a3 Route Soma viewport scroll through ActiveDom
5 months ago
dev 0e57c61c46 Remove global browser guards from Soma runtime paths
5 months ago
dev 1d34200f46 Route Soma observers through ActiveDom
5 months ago
dev 0021826413 Avoid global window access in Soma helpers
5 months ago
dev 9a0ef78b62 Route Soma timers and document lookup through ActiveDom
5 months ago
dev ca9359c32f Add ActiveClipboard service for Soma clipboard
5 months ago
dev d52a1919c6 Tighten Soma table filter and pinning state
5 months ago
dev 108c7ceb95 Harden Soma table pagination and pinning
5 months ago
dev d7e3626580 Stabilize Soma table row indexing
5 months ago
dev 9a8779b9cb Route Soma focus actions through ActiveDom
5 months ago
dev 45c607df90 Route Soma presence scheduling through ActiveDom
5 months ago
dev 986b86aa1d Use ActiveDom frame scheduling in Soma
5 months ago
dev 8ca562ea36 Route Soma focus globals through ActiveDom
5 months ago
dev 48149ccf02 Route Soma DOM actions through ActiveDom
5 months ago
dev 847151387e Remove Soma external facades
5 months ago
dev 487ec9b5c9 Share Soma list typeahead buffers
5 months ago
dev 5e975b6825 Share Soma menu typeahead helper
5 months ago
dev 077953976c Guard Soma timers contract
5 months ago
dev 89911c36c2 Route Soma announce timers through scheduler
5 months ago
dev 88be88f210 Route Soma navigation menu timers
5 months ago
dev 8f4be6d425 Route Soma menu typeahead timers
5 months ago
dev 66a943093b Route Soma typeahead resets through timers
5 months ago
dev 51945706d2 Route Soma drawer scroll guard timer
5 months ago
dev 46009592ed Route Soma scroll area hide timer
5 months ago
dev ad62a440d3 Route Soma clipboard reset through timers
5 months ago
dev 248152263c Route Soma carousel autoplay through timers
5 months ago
dev 9a43cce697 Route Soma hover delays through timers
5 months ago
dev 6578cefdaf Stabilize Soma overlay close targets
5 months ago
dev 5abeb298e9 Guard Soma morfo component coverage
5 months ago
dev 382446199a Document public Soma time picker
5 months ago
dev 65039c1c4c Export public Soma drawer module
5 months ago
dev d6955cf4bc Remove legacy component langs catalog
5 months ago
dev cd75983ed8 Move date time translations to morfo
5 months ago
dev f59dc6a60c Move calendar translations to morfo
5 months ago
dev 9113211d83 Move color translations to morfo
5 months ago
dev a6654b30ff Move form drag translations to morfo
5 months ago
dev fbea8f5a64 Move simple utility translations to morfo
5 months ago
dev 912be7769a Move table pagination translations to morfo
5 months ago
dev 91558f34c2 Move tag translations to morfo
5 months ago
dev 8d685ad2ef Move compact input translations to morfo
5 months ago
dev ed96f7ca4f Move simple navigation translations to morfo
5 months ago
dev 8b573e9c5c Move simple component translations to morfo
5 months ago
dev 78939469e7 Move meter progress translations to morfo
5 months ago
dev b2a0490e7d Move file upload translations to morfo
5 months ago
dev 1ea1cdd9aa Align Soma runtime docs
5 months ago
dev 201a4efb26 Remove stale Soma service types
5 months ago
dev a093963b04 Guard component lang namespaces
5 months ago
dev c050e04ef1 Normalize Soma lang keys
5 months ago
dev 0ca0f01f11 Normalize file upload lang namespace
5 months ago
dev 0074a6cbb7 Guard Soma DOM contract boundaries
5 months ago
dev 8b7e5cbab7 Declare Form AutoFields DOM contract
5 months ago
dev 37de27fcf2 Declare interactive data attrs in morfos
5 months ago
dev caf208ade9 Declare free value data attrs in morfos
5 months ago
dev 3c7984c239 Declare range field input parts
5 months ago
dev fe3defc7ea Remove manual date field segment marker
5 months ago
dev e2313c0f0f Normalize standalone Soma runtimes
5 months ago
dev d5f96aedf9 Normalize Soma root service references
5 months ago
dev f652f54d5b Use root Soma services in overlay providers
5 months ago
dev 0c37c7783f Normalize Soma picker runtimes
5 months ago
dev cde839dc5d Consolidate Soma color picker runtime
5 months ago
dev d3cae96ff2 Consolidate Soma context menu runtime
5 months ago
dev aa5581121d Consolidate Soma dropdown menu runtime
5 months ago
dev 5581f548cd Consolidate Soma range calendar runtime
5 months ago
dev 8618703d31 Consolidate Soma calendar runtime
5 months ago
dev e1df915add Consolidate Soma file upload runtime
5 months ago
dev 3097a209f0 Consolidate Soma command runtime
5 months ago
dev c4c37a3719 Consolidate Soma selection runtimes
5 months ago
dev 131cb4fc1b Consolidate Soma field runtimes
5 months ago
dev 7822e74b3a Consolidate Soma stepper menu runtimes
5 months ago
dev ff91194d71 Consolidate Soma table time runtimes
5 months ago
dev b1b919a8b6 Consolidate Soma tree carousel runtimes
5 months ago
dev 43552dde82 Consolidate Soma tag navigation runtimes
5 months ago
dev c499dd239a Consolidate Soma interaction runtimes
5 months ago
dev c7527fc107 Consolidate Soma overlay runtimes
5 months ago
dev cac7e89232 Consolidate Soma virtual list runtimes
5 months ago
dev 48b0c4ae2f Consolidate Soma range control runtimes
5 months ago
dev 959439f5fa Consolidate Soma input runtimes
5 months ago
dev 6df2f062ad Consolidate Soma form feed runtimes
5 months ago
dev a60d040282 Consolidate Soma provider runtimes
5 months ago
dev 73862094f6 Consolidate Soma group runtimes
5 months ago
dev ef5e9574e2 Checkpoint before Soma components
5 months ago
dev 3a05a0d1de Update UIX continuation state
5 months ago
dev 68dd40ef27 Remove visual prefs dimensions
5 months ago
dev fbacd1133d Remove frontend art
5 months ago
dev a4cbe0c0b9 Tighten intent and morfo contracts
5 months ago
dev e6e438b62a Tighten uix audit contracts
5 months ago
dev 809c17b9bf Close general Eidos architecture
5 months ago
dev a5e6a614a3 Update continuation commit list
5 months ago
dev bd37e5dd9a Clarify legacy frontend density docs
5 months ago
dev 35958a4902 Clarify morfo translation contract
5 months ago
dev 475ac77505 Refresh prefs architecture docs
5 months ago
dev 607dfdbb48 Clarify direct UIX service contracts
5 months ago
dev 71e057652e Align langs diagnostic wording
5 months ago
dev e2ba371beb Add UIX architecture regression guards
5 months ago
dev c13f52ffe9 Update continuation after UIX validation
5 months ago
dev 8ad0471471 Refresh UIX architecture docs
5 months ago
dev 2df58074a1 Use local fonts in UIX docs
5 months ago
dev 4233be62c6 Update UIX continuation notes
5 months ago
dev 659585381f Expose Eidos recipe token queries
5 months ago
dev d822597ffe Generate Eidos recipe tokens from config
5 months ago
dev 6da109875d Prune legacy Eidos CSS runtime
5 months ago
dev 0a014ed9e5 Remove UIX audit docs
5 months ago
dev 26166b9e3c Document UIX and arts ownership
5 months ago
dev b121554918 Fix UIX docs theme and popover dismiss
5 months ago
dev 3dc775f440 Update UIX reauditoria notes
5 months ago
dev 223cdf9e92 Align sema docs with channel ownership
5 months ago
dev 694eb5c567 Clarify prefs projection contract
5 months ago
dev e117141c8f Use plural naming in langs docs
5 months ago
dev 77c37cd940 Clarify prefs ownership docs
5 months ago
dev f76b30499a Align Soma docs with runtime contracts
5 months ago
dev 87ab4f0496 Limit Eidos token barrel to active recipes
5 months ago
dev 6e8ced2e4c Archive Eidos base theme CSS
5 months ago
dev e21bf6c8b8 Stop importing Eidos contracts from tokens
5 months ago
dev 80328a4ad3 Stop importing archived Eidos contracts
5 months ago
dev 8d6d4c5700 Generate Eidos base CSS artifact
5 months ago
dev 501f55b0cc Align Active docs with prefs projection
5 months ago
dev 26b9b83eb5 Move prefs projection out of ActiveUix
5 months ago
dev df1562f852 Document Eidos static CSS boundary
5 months ago
dev 31399d80b2 Add Eidos density scalars
5 months ago
dev d5ab8e9d99 Add Eidos layout primitives
5 months ago
dev ae19318090 Document frontend as legacy opt-in service
5 months ago
dev 6d25364b21 Clarify Morfo value attrs runtime contract
5 months ago
dev f331c988c1 Route UIX managed DOM writes through ActiveDom
5 months ago
dev b90d0a24a9 Reduce UIX runtime allocation churn
5 months ago
dev 6476efffe7 Purge visual slices from Sema
5 months ago
dev 4cc5f26a5a Defer sound channel priming
5 months ago
dev 6dfb07ee44 Clarify UIX theme contracts
5 months ago
dev c12b1c48c8 Refactor active uix architecture
5 months ago
dev e0eb5f718e Advance eidos theming and DOM ownership
5 months ago
dev cb5097b3c3 Add active eidos runtime and morfo lang registry
5 months ago
dev 7214ae7940 uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold
5 months ago
dev e6862265e2 sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color
5 months ago
dev 29ce812f94 collapsible: flat <Collapsible> ergonomic default + compound parts
5 months ago
dev d1850a7de4 demos/collapsible: add soma snippet alongside eidos in Live tab
5 months ago
dev a9ad71118b fix: dark mode + collapsible demo layout shift
5 months ago
dev d10281bc94 collapsible: migrate to eidos wrapper pattern (third pilot)
5 months ago
dev 3d0b234ec8 soma/morfo/active-uix: relocate runtime + rename SomaRuntime + sema opcional
5 months ago
dev 48b646ecc4 demos/switch: full Sema tab visualisations matching toggle
5 months ago
dev 4d26761263 docs + switch: align active docs to canon, migrate switch to eidos wrapper
5 months ago
dev d4dfdaffa9 sema/morfo/eidos: align with GUIA_IMPLEMENTACION_SEMAUIX (canonical guide)
5 months ago
dev 2d35f4b3e9 docs: prune obsolete audits + studies, refresh remaining references
5 months ago
dev f5a2a7fb49 eidos: pilot wrapper pattern + doctrinal API conventions
5 months ago
dev 4546e448bd demos: web/routes/* for the 8 migrated soma components
5 months ago
dev 1b9740f50d soma/{dialog,drawer,popover,toast}: Phase A — runtime-direct migration
5 months ago
dev 74c8b2e189 soma/{switch,avatar}: pilots 3+4 — runtime-direct, drop Provider inheritance
5 months ago
dev 7d125262d8 soma/collapsible: pilot — drop Provider inheritance (multi-part + context)
5 months ago
dev bf49f22b64 soma/toggle: pilot — drop Provider inheritance, use MorfoRuntime directly
5 months ago
dev 52532f5542 soma/provider: dedupe against the morfo compiler (Phase 2b minimal)
5 months ago
dev 7fe9cbffc4 morfo: wire MorfoRuntime to consume CompiledMorfo (Phase 2a)
5 months ago
dev ba487dfc62 soma/form: align with the new ecosystem
5 months ago
dev 8df2e9f748 uix: relocate Layer + PartRef to src/uix/types.ts
5 months ago
dev c69b8be32a cleanup phase E follow-up: drop dead form test + fix doc rot
5 months ago
dev 3e4c0ece44 cleanup phase D: rewrite CLAUDE.md to reflect new architecture
5 months ago
dev 8e800031fe cleanup phase C: reconfig SvelteKit + aliases + web/routes skeleton
5 months ago
dev 0a3914086c cleanup phase B: delete obsolete UI layers + legacy lib
5 months ago
dev 90622e5d58 cleanup phase A: delete src/uix/lib + src/routes
5 months ago
dev 83d2e0d843 eidos: lint a CSS file against its morfo contract
5 months ago
dev 2c32c842bd morfo: introduce compile.ts — Phase 1 of the UIX refactor
5 months ago
dev 217d6969e4 active-uix: uix.runtime(morfo, sources) auto-injects dom + semantic
5 months ago
dev 9009cbfe8c active-uix: standalone mode no longer wraps active-app
5 months ago
dev 83b841a7ca active-uix: introduce UIX layer between active-app and components
5 months ago
dev 5898cc58c7 libs/reactive: export ActiveProps and StateProps types
5 months ago
dev 4bf6dea420 remove backward-compat re-export shims
5 months ago
dev d9b6521020 fix dependency direction: morfo no longer imports from soma
5 months ago
dev 9b07cead6b uix: consolidate DOM + relocate morfo runtime to its layer
5 months ago
dev a6e2c1e443 sema: align vocabulary with libro_semantica (signal, shift, loss)
5 months ago
dev 3faa4473f2 eidos/accordion: fix trigger rotating 90deg when open
5 months ago
dev 930d4c8c16 soma: emit perceptual signal on open in dialog/drawer/popover
5 months ago
dev 86f01f5d8e sema: revive resolver + signatures + real SoundChannel
5 months ago
dev 9338be2662 soma+layouts: migrate App composition to createActiveApp (Phase 4 part 3)
5 months ago
dev a7adfc43b1 sema: add defineEngineSemantic factory for createActiveApp composition
5 months ago
dev 746ce8dbe8 sema: rename SemanticEngine → EngineSemantic + .destroy() → .dispose()
5 months ago
dev c3dbd47f5b morfo/schema: migrate $lib/sium/core → $sium (Phase 3)
5 months ago
dev b532b3a1e4 migrate $lib/util/* → $libs/* in soma + test pages (Phase 2)
5 months ago
dev 62eb00fe8a deprecate src/lib + wire new ecosystem aliases (Phase 0+1)
5 months ago
dev 800e794cea eidos batch: Accordion + Tabs + Checkbox recipes + demos
5 months ago
dev b5453af9c4 popover: extend morfo with events + soft-migrate provider + exit tinting
5 months ago
dev 106b972075 drawer: extend morfo with events + soft-migrate provider + exit tinting
5 months ago
dev d76f4eaf11 test/eidos: 2x2 grid with breathing room for tooltip + popover side demos
5 months ago
dev 5f6373e200 eidos/tooltip: recipe + per-side fade animations + demo
5 months ago
dev d31bbc23f0 eidos/popover: recipe + per-side fade/scale animations + demo
5 months ago
dev abf197a37c eidos/drawer: recipe + slide-in/out per side + demo (modal & non-modal)
5 months ago
dev b096237d3e soma/dialog: dismissWith(action) public API + tinted demo section
5 months ago
dev bb8c8657e1 soma/dialog: soft-migrate close-* events through MorfoRuntime
5 months ago
dev 084322d3db eidos/dialog: add [data-dialog-trigger] envelope (button-style)
5 months ago
dev a50b18d1f1 eidos: drop legacy hand-rolled tokens.css, consume air-translated tokens
5 months ago
dev fa6b01c319 test/eidos: split into per-component demos with shared layout
5 months ago
dev b8dec62b35 eidos phase 2 batch 2: Switch, Collapsible, Toast translated
5 months ago
dev e48f227f89 eidos phase 2: Toggle + Dialog recipes translated from air
5 months ago
dev 0ebc9a9d83 eidos: phase 1 — translate air's foundation (contracts, tokens, themes/base)
5 months ago
dev e4d131b58d sema + morfo: per-event hold override (declarative in morfo)
5 months ago
dev 57590f2d60 scripts/check-eidos-recipes: rAF-grained transition sampling
5 months ago
dev 25dbce936b eidos: dialog entrance/exit transitions (perceptual anchoring)
5 months ago
dev dce320d7ff eidos: focus ring uses box-shadow (project kills outlines globally),
5 months ago
dev d682506df2 eidos: focus ring full opacity + darker hue for accent-bg contrast
5 months ago
dev 375e769428 eidos: V1.5 — Toggle + Switch component recipes
5 months ago
dev 14233af4a7 eidos: drop --_eidos-exit-tint indirection, inline in dialog recipe
5 months ago
dev 286191339e eidos: V1.5 — Dialog component recipe + last-action coverage
5 months ago
dev e69e0cba80 sema: namespace signal attrs under data-event-* (channel never touches state)
5 months ago
dev b66039adb1 sema: VisualChannel preserves structural data-intent on cleanup
5 months ago
dev 4f0108f839 eidos: V1.5 — Toast component recipe
5 months ago
dev 3d9a834886 docs: align cross-layer docs with channel-based Sema
5 months ago
dev b4ceb1bb09 docs: SOMA_ARCHITECTURE — update sema timing reference after channel refactor
6 months ago
dev 5871cbca6e sema: refactor into channel modules — engine becomes registry + dispatch
6 months ago
dev e664b59c96 sema: perceptually-anchored hold + sequential strict emit semantics
6 months ago
dev 44a499d052 eidos: V1 minimum viable — tokens + archetype rules + event reactions
6 months ago
dev 760575c419 docs: src/uix/active_architecture.md — cross-layer vision document
6 months ago
dev 28f93ab089 docs: cross-layer articulation — eidos README + 2-of-3 rule across all layer docs
6 months ago
dev 2e062f216b sema: canonical action verb vocabulary (SEMA_VERBS)
6 months ago
dev aba18117ba morfo: catalog `archetype` across 65 components (~265 part declarations)
6 months ago
dev bf394873e0 morfo: add `archetype` field — cross-layer part categorization
6 months ago
dev e7e32a8355 soma: Avatar migrated to runtime + V2 props bridge
6 months ago
dev 55a5ad2647 soma: runtime.keydown() + Switch keyboard pilot
6 months ago
dev eb0f128c3e soma: V2 props bridge (OptsFromProps + bindProps) — Toggle refactor
6 months ago
dev 6d805a4529 gitignore: ignore .kilo/ (Kilo IDE plugin scratch dir)
6 months ago
dev ddde4601b0 scripts: probe-toast-dismiss — verify the runtime.trigger contract end-to-end
6 months ago
dev 14aa218658 soma: Toast pilot — multi-instance runtime + trigger() integration
6 months ago
dev 2b39a2423d soma + app: runtime.trigger() contract + Sema wired into App/Soma
6 months ago
dev 53d6539abe soma: Collapsible pilot — multi-part runtime migration
6 months ago
dev 62bcb1268f soma/morfo: fix conditional attr cleanup in resolvePartAttrs
6 months ago
dev 8ede535305 soma: MorfoRuntime V1 + Toggle pilot
6 months ago
dev 02fa963cb8 adom + sema: dom.apply + semantic.emit contract skeleton
6 months ago
dev 13caaf215f docs: closed architecture (Morfo + MorfoRuntime + Provider + Effects + Sema + Dom)
6 months ago
dev d87421580d adom + lib/dom: foundation for morfo runtime
6 months ago
dev a0a1485b9f Refactor sema and add shared dom runtime
6 months ago
dev f62ede4a23 sema: add runtime foundation and dialog integration
6 months ago
dev 51dba711e9 sema: spec v0.3.1 + cleanup (drop sema_pre + accordion audit_1)
6 months ago
dev 0d8de2869f soma test: split forms demo into dedicated switch/checkbox/radio-group pages
6 months ago
dev db3ad3497f soma test: complete permutation runner instrumentation (63/66 demos)
6 months ago
dev b68b831320 soma test: instrument 7 more demos for permutation runner (composite + toggleables batch)
6 months ago
dev 1e8500bfac soma test: instrument 5 overlay demos for permutation runner
6 months ago
dev 545d47148c morfo: permutation runner v3 — per-demo axis matrix via `<meta name="perm-axes">`
6 months ago
dev 575748f079 morfo: permutation runner v2 — URL-driven state seeds + RTL variant
6 months ago
dev 61e4ba6d26 morfo: add permutation runner (A37) — third validation layer for state transitions
6 months ago
dev 6efbfeb795 sium: color demo widgets inline + queue CDX-212 (services registry)
6 months ago
dev a722d443b2 sium: migrate 6 remaining demos to App+Soma+extend pattern + specimen-book design
6 months ago
dev 8a4bbb4aa6 sium: v2.0 → v2.2 — Issue idlangref, SiumProvider, progressive forms, plurals
6 months ago
dev 2627dc2094 soma: fix slider thumb alignment + document morfo in README
6 months ago
dev 50821aae44 soma: P3 cleanup — LinkPreview a11y section + TagGroup key API consistency
6 months ago
dev 8d4f53a73f soma/morfo: typed createAttrs + 6 audit batches + form hang fix + A36
6 months ago
dev e1837e8727 docs: add A35 (ref+parent-write effect loop) + update CLAUDE.md
6 months ago
dev 91257e8526 soma/morfo: rename root part → provider + fix toolbar effect loop
6 months ago
dev 0222af7a0b morfo: enrich 65 components with aria + keyboard + focus contracts
6 months ago
dev 8a5b402dd3 morfo: port 65 components + sium.lazy + morfo-check passes 66/66
6 months ago
dev 8c16ffac81 morfo: cross-layer contract + 14 new WAI-ARIA components + Dialog wired
6 months ago
dev 3b704286ba sium: v1.0 — schema core, domain types, i18n, Svelte adapter, AutoFields
6 months ago
dev 72a32e0176 soma: 10 new components + reactivity audit + guide rules A30–A33
6 months ago
dev 16f706439e soma: tier 3 dates + tier 4 time (partial), dias/segments, docs
6 months ago
dev f07addf07b soma: tier 1/2 components, Field/NumberField integration, codex_audit fixes
6 months ago
dev 69a8e8d4b5 soma: dias date library, Calendar + RangeCalendar, Command fixes, doc updates
6 months ago
dev ca768f068a docs: update planned components with all 6 tiers (color + sound added)
6 months ago
dev a8498770f0 docs: add rationale for reactive layer in README §5
6 months ago
dev a1f03b4626 docs: update SOMA_ARCHITECTURE, README, COMPONENT_GUIDE for current state
6 months ago
dev bc9b8246fc soma: resolve all remaining audit issues
6 months ago
dev 015d5e258d soma: gesture layer, drawer component, full audit fixes across 26 components
6 months ago
dev a1d0bee655 refactor: App layer with reactive lang, soma migration, 26 component audits, RTL fixes
6 months ago
dev 8a88ad577a soma: audit fixes — 84 issues across 25 components, Table complete
6 months ago
dev 1fd0b46b2e soma: refactor providers to follow terra patterns, fix translations, add table/number-field
6 months ago
dev dad3d26139 soma: add TagsInput component (23 total)
6 months ago
dev c26e26f7c1 soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes
6 months ago
dev 94c499db96 soma: 12 components, architecture docs, naming fixes, form tier complete
6 months ago
dev 98e3c741a1 Add Tooltip component, export Side/Align/Boundary from $soma/types
6 months ago
dev bb0cdeb554 Add Accordion component (5 parts, keyboard nav, single/multiple, animations)
6 months ago
dev 193bfcdca2 Fix modal popover pointer-events blocked by ScrollLock
6 months ago
dev 9cb5bf8dce Move soma.svelte.ts to core/ directory
6 months ago
dev 1655871619 Add soma external/dates boundary + adapter for glob DatiInstance
6 months ago
dev b0d5407478 Add SOMA_ARCHITECTURE.md — architectural reference for soma framework
6 months ago
dev b6d9f51c47 Add Popover component, Dialog variant/restoreScrollDelay, fix floating z-index
6 months ago
dev 7b65026982 Refactor soma: consolidate layers, add Soma class, Dialog component, animation system
6 months ago
dev 574f9165ad Add soma foundation + air components (Pagination, Editable, PinInput, RatingGroup, Stepper, FileUpload)
6 months ago
dev 265fc9afc6 Add AlertDialog, DropdownMenu, ContextMenu air components; fix terra menu concurrent open
6 months ago
dev 42c3055bf4 Semantic motion+sound engine: activate presets, enrich animations, activate dormant semantics, add drawer modal prop
6 months ago
dev cb3fa2a3c7 Add air components (toggle-group, toolbar, avatar, meter, collapsible), fix CSS audit issues, fix combobox scroll, rebuild documentation
6 months ago
dev b31bc2f055 .
6 months ago
dev 6748851a88 Add remaining air components, semantics, motion, and layout system
6 months ago
dev bb18edc6fe Audit and refactor air/terra system: fix layer 3 violations, rename config→system, add missing motion classes
6 months ago
dev 5eda0c6aef Expand air component system and translator integration
6 months ago
dev b30092dd06 Refactor air architecture: clean dependency boundaries, enrich Behavior/Dom, fix AudioContext
6 months ago
dev 9a9c4d4f14 Add air sound/motion/behavior systems, refactor responsive, update terra config and tokens
6 months ago
dev 19711efa84 Expand air design system with components, tokens, and typography
6 months ago
dev 786658f81f Build out air design system foundation
6 months ago
dev a86ebdcf69 Stabilize terra architecture and runtime contracts
6 months ago
dev 0766cfe49f Add comprehensive CSS documentation to all Terra UI components
6 months ago
dev 978a6f3463 Fix Command demo with Dialog integration
6 months ago

@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "dev",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev", "--", "--port", "5180", "--strictPort"],
"port": 5180
}
]
}

@ -0,0 +1,159 @@
---
name: css_documentation_progress
description: Progreso de documentación de atributos CSS en componentes Terra UI
type: project
---
# Documentación de Atributos CSS - COMPLETADO ✅
## Resumen
Agregar documentación completa de los atributos `data-*` para CSS en todos los componentes de Terra UI. Incluyendo:
- Eliminar el sufijo `-root` de todos los atributos data-_-root → data-_
- Documentar TODOS los atributos para estilos en cada componente
## Cambios Completados ✅
### 1. Sistema de atributos (core)
- [x] **src/uix/terra/utils/attrs.ts** - Modificado `getAttr()` para retornar `data-component` en lugar de `data-component-root` para la parte "root"
- [x] **src/uix/terra/utils/contracts.ts** - Todos los contratos actualizados sin `-root`
- [x] **src/uix/terra/README.md** - Documentación actualizada
### 2. Componentes con documentación CSS completa
**Form Controls:**
- [x] accordion (root, trigger, content, item, header)
- [x] button
- [x] switch (root, thumb)
- [x] checkbox (root, group, group-label)
- [x] radio-group (root, item)
- [x] toggle
- [x] toggle-group (root, item)
- [x] slider (root, thumb, range)
**Disclosure:**
- [x] collapsible (root, trigger, content)
**Overlays/Dialogs:**
- [x] dialog (root, content, trigger, overlay, title, description, close)
- [x] drawer (root, content)
- [x] popover (root, content, trigger)
- [x] tooltip (root)
- [x] alert-dialog (root, content, action, cancel)
**Selection:**
- [x] select (root, trigger, item)
- [x] tabs (root, trigger, content)
- [x] menu (root, content, item)
- [x] menubar (root, menu)
**Date/Time:**
- [x] calendar (root, cell)
- [x] date-picker (root, trigger)
- [x] date-field (root, input, segment)
- [x] date-range-field (root, input, label)
- [x] date-range-picker (root, trigger)
- [x] time-field (root, input, label)
- [x] time-range-field (root, label)
- [x] range-calendar (root)
**Progress/Loading:**
- [x] progress
- [x] meter
- [x] spinner
**Otros:**
- [x] avatar (root, image, fallback)
- [x] aspect-ratio
- [x] label
- [x] separator
- [x] toolbar
- [x] scroll-area
- [x] pagination
- [x] rating-group
- [x] toast (ya tenía documentación previa)
- [x] context-menu (root, content, trigger)
- [x] dropdown-menu (root, content)
- [x] command (root, input, item, list, empty)
- [x] combobox (root, input)
- [x] editable (area, preview, input, edit-trigger, cancel-trigger)
- [x] field (Input, Label, Error, HelperText)
- [x] file-upload (dropzone, item-name)
- [x] form (Label, Error, Control)
- [x] link-preview (root, trigger, content)
- [x] pin-input (root, cell)
- [x] stepper (root)
- [x] splitter (root)
- [x] calendar (day, header, heading, grid, grid-row, grid-head, head-cell, month-select, year-select, next-button, prev-button)
- [x] color-picker (root, trigger, content, area, input, swatches)
## Componentes Pendientes ❌
Para continuar, ejecutar:
```bash
cd "g:\dev\svelte\vicen - copia\src\uix\terra"
grep -L "Selectores CSS" */component/*.svelte 2>/dev/null
```
Posibles componentes pendientes (sin confirmar):
- menu-sub, menu-sub-content, menu-sub-trigger
- menu-checkbox-group, menu-checkbox-item
- menu-radio-group, menu-radio-item
- menu-separator, menu-group, menu-group-heading
- menu-arrow
- drawer (trigger, overlay, title, description, close)
- menubar (trigger, content)
- select (content, content-static, group, group-heading, scroll-up-button, scroll-down-button, viewport)
- tabs-list
- toast (trigger, title, description, close)
- color-picker (alpha-slider, area-thumb, eyedropper, hue-slider, swatch, trigger)
- dialog (trigger)
- form (Field, Description)
- range-calendar (cell, day)
- date-range-picker (calendar, trigger)
- date-picker (calendar)
- scroll-area (viewport, scrollbar, thumb, corner)
- command (loading, separator, viewport, link-item, checkbox-group, checkbox-item, radio-group, radio-item)
- stepper (increment-button, decrement-button)
- rating-group (item)
- file-upload (file-list, item-progress, item-remove)
- dropdown-menu (checkbox-item, radio-item, separator)
- context-menu (separator)
- virtual-list
## Formato de Documentación a Usar
````svelte
/** * ## Selectores CSS * ``` * [data-component] → Descripción * [data-state="open"] → Estado
abierto * [data-state="closed"] → Estado cerrado * [data-disabled] → Presente cuando disabled=true *
... * ``` */
````
## Importante
- NO usar `-root` en los atributos (ej: usar `data-switch`, no `data-switch-root`)
- Documentar TODOS los atributos data-\* que genera cada componente
- Incluir descripción en español para cada selector
---
## ESTADO: COMPLETADO ✅
**Fecha de finalización:** 2026-04-02
**Commit:** 0766cfe - "Add comprehensive CSS documentation to all Terra UI components"
**Total de archivos modificados:** 276 archivos
Todos los componentes de Terra UI ahora tienen documentación completa de sus atributos CSS.

@ -16,7 +16,76 @@
"Bash(grep -v \"^//\")",
"Bash(grep -r \"from.*[''''].*\\\\\\(internal\\\\|shared\\\\\\)/\" g:/dev/svelte/vicen/src/uix/acel --include=*.ts --include=*.svelte)",
"Bash(grep -v '^\\\\s*//')",
"Bash(git -C g:/dev/svelte/vicen status --short -- src/uix/acel/date-field/)"
"Bash(git -C g:/dev/svelte/vicen status --short -- src/uix/acel/date-field/)",
"Bash(npx tsx:*)",
"Bash(for file:*)",
"Bash(sed -i 's/: \\\\\\([^.]*\\\\\\) ? \"\"\"\" : undefined,/: boolToEmptyStrOrUndef\\(\\\\1\\),/g' \"$file\")",
"Bash(node -e \":*)",
"Bash(while read:*)",
"Bash(do if:*)",
"Bash(! grep:*)",
"Bash(sed -i 's/DatePickerRootContext/DatePickerRootState.ctx/g' \"$file\")",
"Bash(sed -i 's/DateRangePickerRootContext/DateRangePickerRootState.ctx/g' \"$file\")",
"Bash(sed -i 's/MenuCheckboxGroupContext/MenuCheckboxGroupState.ctx/g' \"$file\")",
"Bash(sed -i 's/ScrollAreaRootContext/ScrollAreaRootState.ctx/g' \"$file\")",
"Bash(echo \"Fixed: $file\")",
"Read(//g/dev/svelte/vicen - copia/**)",
"Bash(bash /tmp/fix-imports.sh)",
"Bash(sed -i 's/CalendarRootContext/CalendarRootState.ctx/g' src/uix/terra/range-calendar/range-calendar.svelte.ts)",
"Bash(sed -i 's/CalendarRootState\\\\.ctx\\\\.get/CalendarRootState.ctx.get/g' src/uix/terra/range-calendar/range-calendar.svelte.ts)",
"Bash(sed -i \"s/data-switch-root/data-switch/g\" contracts.ts)",
"Bash(sed -i \"s/data-checkbox-root/data-checkbox/g\" contracts.ts)",
"Bash(sed -i \"s/data-toggle-root/data-toggle/g\" contracts.ts)",
"Bash(sed -i \"s/data-toggle-group-root/data-toggle-group/g\" contracts.ts)",
"Bash(sed -i \"s/data-slider-root/data-slider/g\" contracts.ts)",
"Bash(xxd \"G:/dev/svelte/vicen - copia/src/uix/terra/date-picker/components/date-picker-calendar.svelte\")",
"Bash(awk 'NR==40 {print \"\\\\t\\\\t *\"} NR==41 {print \"\\\\t\\\\t * ## Selectores CSS\"} NR==42 {print \"\\\\t\\\\t * ```\"} NR==43 {print \"\\\\t\\\\t * [data-tags-input] \\\\u2192 Elemento contenedor del componente\"} NR==44 {print \"\\\\t\\\\t * [data-disabled] \\\\u2192 Estado cuando el campo est\\\\u00e1 deshabilitado\"} NR==45 {print \"\\\\t\\\\t * ```\"} NR==46 {print \"\\\\t\\\\t *\"} {print}' tags-input-root.svelte)",
"Bash(awk 'NR==25 {print \"\\\\t\\\\t *\"} NR==26 {print \"\\\\t\\\\t * ## Selectores CSS\"} NR==27 {print \"\\\\t\\\\t * ```\"} NR==28 {print \"\\\\t\\\\t * [data-tags-input-tag] \\\\u2192 Elemento contenedor del tag\"} NR==29 {print \"\\\\t\\\\t * [data-readonly] \\\\u2192 Estado cuando el tag es de solo lectura\"} NR==30 {print \"\\\\t\\\\t * [data-disabled] \\\\u2192 Estado cuando el tag est\\\\u00e1 deshabilitado\"} NR==31 {print \"\\\\t\\\\t * ```\"} NR==32 {print \"\\\\t\\\\t *\"} {print}' tags-input-tag.svelte)",
"Bash(awk 'NR==40 {print \"\\\\t\\\\t *\"} NR==41 {print \"\\\\t\\\\t * ## Selectores CSS\"} NR==42 {print \"\\\\t\\\\t * ```\"} NR==43 {print \"\\\\t\\\\t * [data-tags-input] -> Elemento contenedor del componente\"} NR==44 {print \"\\\\t\\\\t * [data-disabled] -> Estado cuando el campo esta deshabilitado\"} NR==45 {print \"\\\\t\\\\t * ```\"} NR==46 {print \"\\\\t\\\\t *\"} {print}' tags-input-root.svelte)",
"Bash(awk 'NR==25 {print \"\\\\t\\\\t *\"} NR==26 {print \"\\\\t\\\\t * ## Selectores CSS\"} NR==27 {print \"\\\\t\\\\t * ```\"} NR==28 {print \"\\\\t\\\\t * [data-tags-input-tag] -> Elemento contenedor del tag\"} NR==29 {print \"\\\\t\\\\t * [data-readonly] -> Estado cuando el tag es de solo lectura\"} NR==30 {print \"\\\\t\\\\t * [data-disabled] -> Estado cuando el tag esta deshabilitado\"} NR==31 {print \"\\\\t\\\\t * ```\"} NR==32 {print \"\\\\t\\\\t *\"} {print}' tags-input-tag.svelte)",
"Bash(sed -i '43s/->/\\\\u2192/; 44s/->/\\\\u2192/' tags-input-root.svelte)",
"Bash(sed -i '28s/->/\\\\u2192/; 29s/->/\\\\u2192/; 30s/->/\\\\u2192/' tags-input-tag.svelte)",
"Bash(sed -i 's/data-terra-dialog-content/data-dialog-content/g' contracts.ts)",
"Bash(sed -i 's/data-terra-dialog-trigger/data-dialog-trigger/g' contracts.ts)",
"Bash(sed -i 's/data-terra-dialog-overlay/data-dialog-overlay/g' contracts.ts)",
"Bash(sed -i 's/data-terra-dialog-title/data-dialog-title/g' contracts.ts)",
"Bash(sed -i 's/data-terra-dialog-description/data-dialog-description/g' contracts.ts)",
"Bash(sed -i 's/data-alert-dialog-content/data-dialog-content/g' contracts.ts)",
"Bash(sed -i 's/data-alert-dialog-trigger/data-dialog-trigger/g' contracts.ts)",
"Bash(sed -i 's/data-alert-dialog-overlay/data-dialog-overlay/g' contracts.ts)",
"Bash(sed -i 's/data-alert-dialog-title/data-dialog-title/g' contracts.ts)",
"Bash(sed -i 's/data-alert-dialog-description/data-dialog-description/g' contracts.ts)",
"Bash(sed -i 's/AlertDialog usa el mismo contrato que Dialog con variant=\"alert-dialog\"/AlertDialog es una variante de Dialog, usa los mismos attrs data-dialog-*/g' contracts.ts)",
"Bash(sed -i '/Los attrs se generan con data-alert-dialog-/d' contracts.ts)",
"Bash(python3)",
"Bash(pnpm run:*)",
"Bash(sed -i 's/<div {...mergedProps}>/<button {...mergedProps}>/g' \"g:/dev/svelte/vicen - copia/src/uix/terra/splitter/components/splitter-resize-trigger.svelte\")",
"Bash(sed -i 's/<\\\\/div>/<\\\\/button>/g' \"g:/dev/svelte/vicen - copia/src/uix/terra/splitter/components/splitter-resize-trigger.svelte\")",
"Bash(sed -n '106p' \"g:/dev/svelte/vicen - copia/src/routes/test/air/spinner/+page.svelte\")",
"Bash(sed -n '123p' \"g:/dev/svelte/vicen - copia/src/routes/test/editable/+page.svelte\")",
"Bash(sed -n '151p' \"g:/dev/svelte/vicen - copia/src/routes/test/editable/+page.svelte\")",
"Bash(sed -n '93p' \"g:/dev/svelte/vicen - copia/src/routes/test/file-upload/+page.svelte\")",
"Bash(sed -n '151p' \"g:/dev/svelte/vicen - copia/src/routes/test/file-upload/+page.svelte\")",
"Bash(perl -i -pe 's/\\\\.\"\\\\$$\"/\"/g' air/spinner/+page.svelte editable/+page.svelte file-upload/+page.svelte link-preview/+page.svelte stepper/+page.svelte)",
"Bash(mkdir -p external/dates)",
"Bash(cp -r adapters/dates/* external/dates/)",
"Bash(find src/uix/terra -type f '\\(' -name '*.ts' -o -name '*.svelte' -o -name '*.md' '\\)' -exec sed -i 's|from ['\\\\'']\\\\$terra/adapters/dates['\\\\'']|from '\\\\''$terra/external/dates'\\\\''|g' '{}' +)",
"Bash(find src/uix/terra -type f '\\(' -name '*.ts' -o -name '*.svelte' '\\)' -exec perl -pi -e 's|from [\"\\\\x27]\\\\$terra/adapters/dates[\"\\\\x27]|from \"$terra/external/dates\"|g' '{}' +)",
"Bash(find src/uix/terra -type f '\\(' -name '*.ts' -o -name '*.svelte' '\\)' -exec sed -i 's|from \"/external/dates\"|from \"$terra/external/dates\"|g' '{}' ';')",
"Bash(xargs -n1 basename)",
"Bash(find \"g:/dev/svelte/vicen - copia/src/uix/air/icons/lib\" -name \"*.svelte\" -exec basename {} \\\\;)",
"Bash(find \"g:/dev/svelte/vicen - copia/src/uix/air/icons/lib\" -name \"*.svelte\" -exec basename {} .svelte \\\\;)",
"Read(//tmp/**)",
"Read(//g/dev/svelte/**)",
"Bash(sed -i -e 's/\\\\bairReveal\\\\b/reveal/g' -e 's/\\\\bairDismiss\\\\b/dismiss/g' -e 's/export function airContext\\\\b/export function contextIn/g' -e 's/export function airContextOut\\\\b/export function contextOut/g' -e 's/\\\\bairExpand\\\\b/expand/g' -e 's/\\\\bairExpandOut\\\\b/expandOut/g' g:/dev/svelte/vicen/src/uix/air/internal/motion/transitions.ts)",
"Bash(sed -i -e 's/\\\\bairFeedback\\\\b/feedback/g' -e 's/\\\\bairAttention\\\\b/attention/g' -e 's/\\\\bairEmphasis\\\\b/emphasis/g' -e 's/\\\\bairPersistence\\\\b/persistence/g' -e 's/\\\\bairSelection\\\\b/selection/g' -e 's/\\\\bairCompletion\\\\b/completion/g' g:/dev/svelte/vicen/src/uix/air/internal/behavior/behaviors.svelte.ts)",
"Read(//c/Users/dev/.claude/projects/g--dev-svelte-vicen/memory/**)",
"Bash(npm run *)",
"Bash(echo \"dev pid: $!\")",
"Bash(pkill -f \"vite dev\")"
],
"additionalDirectories": [
"g:\\dev\\svelte\\vicen\\src\\uix\\soma\\components\\search-field"
]
}
}

@ -51,7 +51,364 @@
"Bash(__NEW_LINE_9a92beff9c90538f__ TERRA=\"G:/dev/svelte/vicen/src/uix/terra\" __NEW_LINE_9a92beff9c90538f__ find \"$TERRA\" \\\\\\( -name \"*.ts\" -o -name \"*.svelte.ts\" -o -name \"*.svelte\" \\\\\\))",
"Bash(git -C G:/dev/svelte/vicen status --short)",
"Bash(git:*)",
"Bash(ls:*)"
"Bash(ls:*)",
"Bash(npm install:*)",
"Bash(xargs wc:*)",
"Bash(wc:*)",
"Bash(sed:*)",
"Bash(find:*)",
"Bash(cat)",
"Bash(chmod +x /tmp/audit.sh)",
"Bash(bash /tmp/audit.sh)",
"Bash(chmod +x /tmp/final_check.sh)",
"Bash(bash /tmp/final_check.sh)",
"Bash(awk '{print $NF}')",
"Bash(cp -r G:/dev/svelte/vicen/src/uix/terra/config G:/dev/svelte/vicen/src/uix/terra/system)",
"Bash(cp -r G:/dev/svelte/vicen/src/uix/air/config G:/dev/svelte/vicen/src/uix/air/system)",
"WebFetch(domain:www.radix-ui.com)",
"WebSearch",
"WebFetch(domain:ui.shadcn.com)",
"WebFetch(domain:github.com)",
"WebFetch(domain:bits-ui.com)",
"WebFetch(domain:www.shadcn.io)",
"WebFetch(domain:chakra-ui.com)",
"WebFetch(domain:ark-ui.com)",
"Bash(xargs -I{} basename {})",
"Bash(xargs -I {} sh -c 'echo \"=== {} ===\" && grep \"air\\\\.interaction\\\\.play\" {}')",
"WebFetch(domain:www.shadcn-svelte.com)",
"WebFetch(domain:next.shadcn-svelte.com)",
"WebFetch(domain:www.bits-ui.com)",
"WebFetch(domain:shadcn-svelte.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"Bash(curl -sL \"https://api.github.com/search/code?q=pagination-link+repo:huntabyte/shadcn-svelte+extension:svelte\")",
"Bash(timeout 10 npx vite dev --port 5173)",
"Bash(curl -s http://localhost:5175/test/air/color-picker)",
"Bash(curl -s http://localhost:5173/test/air/color-picker)",
"Bash(curl -s http://localhost:5174/test/air/color-picker)",
"Bash(npx vite:*)",
"Bash(bash /tmp/layer_stats.sh)",
"Bash(cat:*)",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/dialog\")",
"Bash(curl -s \"https://ark-ui.com/docs/components/dialog\")",
"Bash(curl -s \"https://bits-ui.com/docs/components/dialog\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/popover\")",
"Bash(curl -s \"https://ark-ui.com/docs/components/popover\")",
"Bash(curl -s \"https://bits-ui.com/docs/components/popover\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/accordion\")",
"Bash(curl -s \"https://ark-ui.com/docs/components/accordion\")",
"Bash(curl -s \"https://bits-ui.com/docs/components/accordion\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/tooltip\")",
"Bash(curl -s \"https://ark-ui.com/docs/components/tooltip\")",
"Bash(curl -s \"https://bits-ui.com/docs/components/tooltip\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/dropdown-menu\")",
"Bash(curl -s \"https://ark-ui.com/docs/components/menu\")",
"Bash(curl -s \"https://bits-ui.com/docs/components/dropdown-menu\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/overview/introduction\")",
"Bash(curl -s \"https://ark-ui.com/docs/overview/introduction\")",
"Bash(curl -s \"https://bits-ui.com/docs\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/switch\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/checkbox\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/radio-group\")",
"Bash(curl -s \"https://www.radix-ui.com/primitives/docs/components/select\")",
"Bash(curl -s \"https://ark-ui.com/docs/components/select\")",
"Bash(curl -s \"https://next.bits-ui.com/docs/components/select\")",
"WebFetch(domain:next.bits-ui.com)",
"Bash(curl -s \"https://ark-ui.com/docs/components/combobox\")",
"Bash(curl -s \"https://next.bits-ui.com/docs/components/combobox\")",
"Bash(curl -s http://localhost:5173/test/soma/tabs)",
"WebFetch(domain:sonner.emilkowal.ski)",
"WebFetch(domain:www.radix-vue.com)",
"WebFetch(domain:react-resizable-panels.vercel.app)",
"WebFetch(domain:mantine.dev)",
"WebFetch(domain:react-aria.adobe.com)",
"WebFetch(domain:reka-ui.com)",
"WebFetch(domain:www.w3.org)",
"WebFetch(domain:zagjs.com)",
"WebFetch(domain:react-spectrum.adobe.com)",
"WebFetch(domain:headless-tree.lukasbach.com)",
"WebFetch(domain:mui.com)",
"WebFetch(domain:primereact.org)",
"WebFetch(domain:a11ymatters.com)",
"WebFetch(domain:base-ui.com)",
"WebFetch(domain:tanstack.com)",
"WebFetch(domain:icflorescu.github.io)",
"WebFetch(domain:www.ag-grid.com)",
"Bash(sort -t'\"' -k2)",
"Bash(uniq -f1)",
"Bash(netstat -tlnp)",
"Bash(xargs -I {} basename {})",
"Bash(curl:*)",
"Bash(gh api:*)",
"Bash(python -c \"import sys,json; [print\\(f['name']\\) for f in json.loads\\(sys.stdin.read\\(\\)\\)]\")",
"Bash(npx vitest:*)",
"WebFetch(domain:vaul.emilkowal.ski)",
"Bash(npm ls *)",
"Bash(npx -y degit chakra-ui/ark/packages/react/src/components/dialog ark-dialog)",
"Bash(ls ark-dialog/)",
"Bash(npx -y degit emilkowalski/vaul vaul-source --force)",
"Bash(npx -y degit chakra-ui/ark/packages/react/src/components/drawer ark-drawer)",
"Bash(ls ark-drawer/)",
"Bash(npx -y degit radix-ui/primitives/packages/react/dialog/src radix-dialog-source --force)",
"Bash(npx -y degit chakra-ui/zag/packages/machines/drawer/src zag-drawer)",
"Bash(ls zag-drawer/)",
"Bash(npx -y degit chakra-ui/zag/packages/machines/drawer zag-drawer-pkg)",
"Bash(ls zag-drawer-pkg/)",
"Bash(ls zag-drawer-pkg/src/)",
"WebFetch(domain:deepwiki.com)",
"WebFetch(domain:www.vaul-svelte.com)",
"WebFetch(domain:emilkowal.ski)",
"WebFetch(domain:www.npmjs.com)",
"Bash(awk NR==507||NR==635||NR==723||NR==866 { print NR\": \"$0 } *)",
"Bash(awk '$4 > 0 {print}')",
"Bash(awk '/FAIL/{flag=1; print; next} /PASS/{flag=0} flag')",
"Bash(awk 'BEGIN{cur=\"\"} /^FAIL/{cur=$2; print \"--- \"$2\" ---\"; next} /^PASS/{cur=\"\"; next} cur && /\\\\[/{print \" \"$0}')",
"Bash(BASE_URL=http://localhost:5173 node --import tsx/esm scripts/probe-toggle-pressed-intents.ts)",
"Bash(BASE_URL=http://localhost:5173 node --import tsx/esm scripts/probe-toggle-fresh.ts)",
"Bash(awk '{print length, $0}')",
"Bash(gzip -c src/uix/eidos/generated/base.css)",
"Bash(awk -F'-' '{print $3}')",
"Bash(sort -k2 -rn)",
"Bash(awk '/^\tcheckbox: \\\\{/,/^\t\\\\},$/' src/uix/eidos/lib/recipes/base.ts)",
"Bash(grep -c data-color= src/uix/eidos/components/__TRACKED_VAR__/__TRACKED_VAR__.css)",
"Bash(grep -c data-variant.*data-color src/uix/eidos/components/__TRACKED_VAR__/__TRACKED_VAR__.css)",
"Bash(awk '/^\tbutton: \\\\{/,/^\t\\\\},$/' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk '/^\tswitch: \\\\{/,/^\t\\\\},$/' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk '/^\tswitch: \\\\{/{flag=1} flag{print NR\": \"$0} /^\t\\\\},$/{if\\(flag\\){flag=0}}' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk '/^\twords: \\\\{/,/^\t\\\\},$/' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk '/^\tstepper: \\\\{/,/^\t\\\\},$/' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk \"/^\t'tags-input': \\\\{/,/^\t\\\\},$/\" src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk 'BEGIN{p=0} /^\t.tags-input.: \\\\{/{p=1} p{print NR\": \"$0} /^\t\\\\},$/{if\\(p\\){p=0; exit}}' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk 'BEGIN{p=0} /^\twords: \\\\{/{p=1} p{print NR\": \"$0} /^\t\\\\},$/{if\\(p\\){p=0; exit}}' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk \"BEGIN{p=0} /^\t'file-upload': \\\\{/{p=1} p{print NR\\\\\": \\\\\"\\\\$0} /^\t\\\\},\\\\$/{if\\(p\\){p=0; exit}}\" src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk \"BEGIN{p=0} /^\t'tag-group': \\\\{/{p=1} p{print NR\\\\\": \\\\\"\\\\$0} /^\t\\\\},\\\\$/{if\\(p\\){p=0; exit}}\" src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk \"BEGIN{p=0} /^\tbadge: \\\\{/{p=1} p{print NR\\\\\": \\\\\"\\\\$0} /^\t\\\\},\\\\$/{if\\(p\\){p=0; exit}}\" src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk \"BEGIN{p=0} /^\tcard: \\\\{/{p=1} p{print NR\\\\\": \\\\\"\\\\$0} /^\t\\\\},\\\\$/{if\\(p\\){p=0; exit}}\" src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk \"BEGIN{p=0} /^\teditable: \\\\{/{p=1} p{print NR\\\\\": \\\\\"\\\\$0} /^\t\\\\},\\\\$/{if\\(p\\){p=0; exit}}\" src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk 'NR>=2800 && NR<=2870 && /^\\\\[|^:root|^}/')",
"Bash(git show *)",
"Bash(awk 'NR>=2800 && NR<=2870 && /^\\\\[|^:root|^}/' /tmp/old-base.css)",
"Bash(awk 'NR>=2800 && NR<=2870 {print NR\": \"$0}' /tmp/old-base.css)",
"Bash(awk 'NR>=2700 && NR<=2870 && \\(/^\\\\[/ || /^:root/ || /^}/\\)' /tmp/old-base.css)",
"mcp__Claude_Preview__preview_start",
"Bash(awk '/^\ttoggle: \\\\{/,/^\t\\\\},/' G:/dev/svelte/vicen/src/uix/eidos/lib/recipes/base.ts)",
"mcp__Claude_in_Chrome__tabs_context_mcp",
"mcp__Claude_in_Chrome__browser_batch",
"mcp__Claude_in_Chrome__navigate",
"mcp__Claude_in_Chrome__javascript_tool",
"mcp__Claude_in_Chrome__read_console_messages",
"mcp__Claude_in_Chrome__computer",
"Bash(kill %1)",
"Bash(wait)",
"Bash(Select-Object -Last 60)",
"Bash(Select-Object -Last 70)",
"mcp__Claude_in_Chrome__list_connected_browsers",
"mcp__Claude_in_Chrome__select_browser",
"mcp__Claude_in_Chrome__find",
"mcp__Claude_in_Chrome__read_page",
"Bash(stat -c '%Y %n' \"G:/dev/svelte/vicen/src/uix/soma/components/dropdown-menu/dropdown-menu-provider.svelte.ts\" 2>/dev/null && date +%s)",
"WebFetch(domain:discuss.prosemirror.net)",
"mcp__Claude_in_Chrome__tabs_create_mcp",
"WebFetch(domain:www.reactbricks.com)",
"Bash(awk 'NR>=370 && NR<=400 {printf \"%d| %s\\\\n\", NR, $0}' src/uix/soma/components/words/words-provider.svelte.ts)",
"Bash(awk 'NR>=20 && NR<=36 {printf \"%d| %s\\\\n\",NR,$0}' src/uix/eidos/components/words/words-inspector.svelte)",
"Bash(awk 'NR<=22 {printf \"%d| %s\\\\n\",NR,$0}' src/uix/soma/components/words/types.ts)",
"Bash(awk '/function blockTitle/{f=1} f{printf \"%d| %s\\\\n\",NR,$0} f&&/^\\\\t\\\\}/{exit}' src/uix/eidos/components/words/words-inspector.svelte)",
"Bash(awk '/function walkPath/{f=1} f{printf \"%d| %s\\\\n\",NR,$0} f&&/^\\\\t\\\\}/{exit}' src/uix/eidos/components/words/words-inspector.svelte)",
"Bash(npm run *)",
"Bash(echo \"started dev, pid $!\")",
"Bash(awk 'NR==284{printf \"L284: [%s]\\\\n\",$0}' src/uix/eidos/components/words/words-inspector.svelte)",
"Bash(awk 'NR>=283 && NR<=286 {printf \"%d|%s\\\\n\",NR,$0}' src/uix/eidos/components/words/words-inspector.svelte)",
"Bash(awk 'NR>=35 && NR<=39' src/uix/soma/components/words/words-provider.svelte.ts)",
"Bash(awk 'NR>=216 && NR<=232 {printf \"%d| %s\\\\n\",NR,$0}' src/uix/soma/components/words/words-provider.svelte.ts)",
"Bash(npx svelte-kit *)",
"Bash(xargs -I {} basename {} .ts)",
"Bash(awk 'NR>=118 && NR<=135' src/uix/soma/components/words/engine/operations/text.ts)",
"PowerShell($f = 'src/uix/soma/components/words/engine/operations/text.ts'; Get-Content $f | Select-Object -Skip 108 -First 22 | ForEach-Object -Begin {$n=109} -Process { \"{0}: {1}\" -f $n, $_; $n++ })",
"Bash(awk '/insertBlockInColumn/{f=1} f{print NR\": \"$0} f&&/^}/{c++; if\\(c>=1\\)exit}' src/uix/soma/components/words/engine/operations/insert-block-types.ts)",
"Bash(awk 'NR>=795 && NR<=820 {printf \"%d| %s\\\\n\",NR,$0}' src/uix/eidos/components/words/words-block-panel.svelte)",
"Bash(awk 'NR>=996 && NR<=1064 {print NR\": \"$0}' src/uix/soma/components/words/engine/blocks/built-ins.ts)",
"Bash(pkill -f \"vite\")",
"Bash(pkill -f \"node.*svelte\")",
"Bash(netstat -ano)",
"Bash(tee /tmp/chk.txt)",
"Bash(echo \"EXIT=$?\")",
"PowerShell($f='src/uix/soma/components/words/engine/operations/commands.ts'; $lines = Get-Content $f; for\\($i=0; $i -lt $lines.Count; $i++\\){ if\\($lines[$i] -match 'insertBlockInColumn'\\){ \"{0}: {1}\" -f \\($i+1\\), $lines[$i].Trim\\(\\) } })",
"Bash(awk '/insertBlockInColumn/{print NR\": \"$0}' src/uix/soma/components/words/engine/operations/commands.ts)",
"Bash(awk '{print $5}')",
"PowerShell(Set-Location G:\\\\dev\\\\svelte\\\\vicen; $conn = Get-NetTCPConnection -LocalPort 5173 -State Listen -ErrorAction SilentlyContinue; if \\($conn\\) { $procId = $conn.OwningProcess | Select-Object -First 1; Write-Output \"Killing PID $procId on 5173\"; Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue; Start-Sleep -Seconds 2; Write-Output \"killed\" } else { Write-Output \"no listener on 5173\" })",
"Bash(echo \"dev restarting, pid $!\")",
"Bash(awk '/function validateColumnFields/{f=1} f{print NR\": \"$0} f&&/^}/{exit}' src/uix/soma/components/words/engine/blocks/built-ins.ts)",
"Bash(awk '/export function getNodeAtPath/{f=1} f{print NR\": \"$0} f&&/^}/{exit}' src/uix/soma/components/words/engine/operations/helpers.ts)",
"Bash(awk '/export function updateNodeAtPath/{f=1} f{print NR\": \"$0} f&&/^}/{exit}' src/uix/soma/components/words/engine/operations/helpers.ts)",
"Bash(awk 'NR>430 && /block\\\\./ {printf \"%d| %s\\\\n\",NR,$0}' src/uix/eidos/components/words/words-inspector.svelte)",
"Bash(echo \"restarting pid $!\")",
"Bash(npx prettier *)",
"Bash(echo \"restart pid $!\")",
"Bash(echo \"=== how is a soft break represented? \\\\\\\\n in text? <br>? ===\")",
"Bash(echo \"=== how does renderer emit text with \\\\\\\\n? does it split on newline? ===\")",
"Bash(cd /g/dev/svelte/vicen; npm run dev > /tmp/words-dev5.log 2>&1 & *)",
"Bash(awk '/export function insertInlinesAtRange/{f=1} f{print NR\": \"$0} f&&/^}/{c++; if\\(c>=1\\)exit}' src/uix/soma/components/words/engine/operations/text.ts)",
"WebFetch(domain:www.untitledui.com)",
"WebFetch(domain:untitled-ui-webflow-library.webflow.io)",
"WebFetch(domain:www.colorsandfonts.com)",
"Bash(echo \"exit:$?\")",
"PowerShell(npm run generate:eidos-css 2>&1)",
"PowerShell(Get-ChildItem -Recurse G:\\\\dev\\\\svelte\\\\vicen\\\\web\\\\routes\\\\temas -File | ForEach-Object { \"{0,6} {1}\" -f \\(Get-Content $_.FullName | Measure-Object -Line\\).Lines, $_.FullName.Replace\\('G:\\\\dev\\\\svelte\\\\vicen\\\\web\\\\routes\\\\temas\\\\',''\\) })",
"Bash(Get-ChildItem -Path \"G:\\\\dev\\\\svelte\\\\vicen\\\\src\\\\uix\\\\eidos\\\\components\" -Directory)",
"Bash(Select-Object -ExpandProperty Name)",
"Bash(echo \"CHECK_DONE exit=$?\")",
"Bash(echo \"SOMA_TESTS_DONE exit=$?\")",
"Bash(echo \"vocab exit=$?\")",
"Bash(echo \"morfo exit=$?\")",
"Bash(npx tsx *)",
"Bash(echo \"eidoslint exit=$?\")",
"Bash(Get-ChildItem -Path \"G:\\\\dev\\\\svelte\\\\vicen\\\\src\\\\uix\\\\soma\\\\components\\\\words\" -Recurse -File)",
"Bash(Select-Object FullName)",
"Bash(Sort-Object FullName)",
"Bash(grep -E \"\\\\[data-words\" G:/dev/svelte/vicen/src/uix/eidos/components/words/words.css | sed 's/.*\\\\\\(\\\\[data-words[^]]*\\\\]\\\\\\).*/\\\\1/' | sort | uniq > /tmp/eidos_attrs.txt && wc -l /tmp/eidos_attrs.txt)",
"Bash(echo \"exit=$? \\(0=found→agent WRONG, 1=missing→agent RIGHT\\)\")",
"Bash(awk '/const columnsSpec|type: .columns./{print NR\": \"$0}' src/uix/soma/components/words/engine/blocks/built-ins.ts)",
"Bash(awk 'NR>=400 && NR<=440' src/uix/eidos/components/words/words.css)",
"Bash(awk '/export function toggleMarkSet/,/^}/' src/uix/soma/components/words/engine/operations/marks.ts)",
"Bash(awk '/export function actionFromPaste/,/^}/' src/uix/soma/components/words/engine/operations/input.ts)",
"Bash(perl -ne 'print \"$.: \", $_ if /\\\\x00/' src/uix/soma/components/words/engine/operations/input.ts)",
"Bash(perl -i -pe 's/\\\\x00/\\\\\\\\0/g' src/uix/soma/components/words/engine/operations/input.ts)",
"Bash(echo \"check started in background \\(pid $!\\)\")",
"Bash(break)",
"Bash(echo \"check started \\(pid $!\\)\")",
"Bash(rg -n 'bind:api|api\\\\s*=\\\\s*$bindable|getContext|setContext|WordsProvider.ctx|export.*getWords|getWordsApi|context<' src/uix/soma/components/words/components/words.svelte src/uix/soma/components/words/exports.ts)",
"Bash(echo \"words tests started \\(pid $!\\)\")",
"Bash(rg -n \"\\\\-\\\\-words-block-gap\" src/uix/eidos/components/words/*.ts src/uix/eidos/components/words/recipe* src/uix/eidos/**/recipes/*.ts)",
"Bash(rg -rn \"block-gap\" src/uix/eidos/components/words/)",
"Bash(rg -rn \"block-gap|content-py|content-px\" src/uix/eidos --glob '*.ts')",
"Bash(rg -rn \"block-gap\" src/uix/eidos --glob '*.ts')",
"Bash(rg -rn \"data-words-node\" src/uix/soma/components/words/engine/)",
"Bash(rg -n 'import \\\\{ Button|from '\\\\''\\\\$uix/eidos/components/button'\\\\''' src/uix/eidos/components/words/words-inspector.svelte)",
"Bash(rg --files src/uix/soma/components/words/engine/extensions)",
"Bash(xargs -I{} sh -c \"rg -n 'function slashInsertionCommand' {} && sed -n '/function slashInsertionCommand/,/^}/p' {}\")",
"Bash(rg -n \"insertIntoActiveColumn\" src/uix/eidos/components/words/*.svelte)",
"Bash(Get-ChildItem -Path \"G:\\\\dev\\\\svelte\\\\vicen\\\\src\\\\uix\\\\\" -Directory | Select-Object -ExpandProperty Name)",
"WebFetch(domain:www.chakra-ui.com)",
"Bash(echo \"---EXIT: $?---\")",
"PowerShell($ProgressPreference='SilentlyContinue'; \\(Get-Command gh -ErrorAction SilentlyContinue\\).Source; gh --version 2>&1 | Select-Object -First 1)",
"WebFetch(domain:api.github.com)",
"WebFetch(domain:cdn.jsdelivr.net)",
"Bash(tee /tmp/eidos-check.log)",
"Bash(grep -iE \"ERROR \" /tmp/eidos-check.log | grep -iv \"WARNING\" | head; echo \"=== any of my new files mentioned at all? ===\"; grep -icE \"lib.motion|presets.css|runtime\\\\.ts\" /tmp/eidos-check.log)",
"Bash(Select-Object Name)",
"Bash(for dir *)",
"WebFetch(domain:m3.material.io)",
"WebFetch(domain:carbondesignsystem.com)",
"WebFetch(domain:motion.dev)",
"WebFetch(domain:spectrum.adobe.com)",
"WebFetch(domain:fluent2.microsoft.design)",
"WebFetch(domain:www.hackingwithswift.com)",
"WebFetch(domain:www.useyourloaf.com)",
"WebFetch(domain:swiftwithmajid.com)",
"WebFetch(domain:design.google)",
"WebFetch(domain:swiftui-lab.com)",
"WebFetch(domain:nerdyak.tech)",
"Bash(tee /tmp/f1-check.log)",
"Bash(tee /tmp/f1-check2.log)",
"Bash(tee /tmp/f2-check.log)",
"Read(//g/g/dev/svelte/vicen/src/uix/**)",
"Read(//g/g/dev/svelte/vicen/src/uix/sema/**)",
"Bash(python -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\('\\\\n'.join\\(f'{k} = {v}' for k,v in d['scripts'].items\\(\\)\\)\\)\")",
"Bash(echo \"dev started pid $!\")",
"Bash(echo \"vocabulary exit: $?\")",
"Bash(echo \"EXIT: $?\")",
"Bash(kill 319)",
"Bash(which pandoc *)",
"Bash(which python *)",
"Bash(which python3 *)",
"Bash(jobs)",
"Bash([ -f \"build/temas/$d/index.html\" ])",
"Bash(awk '/^\\\\twords: \\\\{/,/^\\\\t\\\\},?$/' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk -F: '{sum += $NF} END {print \"Total it\\(\\) blocks:\", sum}')",
"Bash(awk -F: '{s+=$NF} END {print \"Total it\\(\\) test cases: \" s}')",
"Bash(awk -F: '{s+=$NF} END {print \"Total describe\\(\\) blocks: \" s}')",
"Read(//tmp/**)",
"Bash(sleep 3; echo \"=== b0gpqhsd4 ===\"; cat \"C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/a680a4dc-e38b-4376-a8bc-ef77e61a50c1/tasks/b0gpqhsd4.output\" 2>/dev/null; echo \"=== /tmp ===\"; cat /tmp/pcheck2.txt 2>/dev/null | head)",
"Bash(cat \"C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/a680a4dc-e38b-4376-a8bc-ef77e61a50c1/tasks/bmftg72nm.output\" 2>/dev/null; echo \"---tmp---\"; cat /tmp/pcheck4.txt 2>/dev/null | head)",
"Read(//dev/**)",
"Bash(:)",
"Bash(Get-ChildItem -Path \"G:\\\\dev\\\\svelte\\\\vicen\\\\src\\\\uix\\\\soma\\\\components\\\\words\" -Recurse)",
"PowerShell(\"---\")",
"mcp__Claude_in_Chrome__switch_browser",
"Bash(sort -u -t: -k2)",
"PowerShell(try { $r = Invoke-WebRequest -Uri \"http://localhost:5173/\" -UseBasicParsing -TimeoutSec 5; \"DEV SERVER UP: $\\($r.StatusCode\\)\" } catch { \"DEV SERVER DOWN: $\\($_.Exception.Message\\)\" })",
"WebFetch(domain:docs.slatejs.org)",
"WebFetch(domain:www.blocknotejs.org)",
"mcp__Claude_in_Chrome__resize_window",
"Bash(rm -f \"G:/dev/svelte/vicen/src/uix/soma/components/words/engine/operations/_adv_nested_cols.test.ts\" \"/tmp/nested-cols.test.ts\" && echo \"cleaned\")",
"Bash(awk 'NR>=247 && NR<=254' src/uix/eidos/components/palabras/palabras-handle.svelte)",
"Bash(python -)",
"Bash(awk '/data-palabras-panel-card \\\\{/,/^\\\\}/' src/uix/eidos/components/palabras/palabras.css)",
"Bash(awk '/data-palabras-panel-foot \\\\{/,/^\\\\}/' src/uix/eidos/components/palabras/palabras.css)",
"Bash(awk 'NR>=300 && NR<=345' src/uix/eidos/components/palabras/palabras.css)",
"Bash(awk '/\\\\[data-palabras-panel-card\\\\] \\\\{/{f=1} f{print NR\": \"$0} /^\\\\}/{if\\(f\\){f=0}}' src/uix/eidos/components/palabras/palabras.css)",
"mcp__Claude_in_Chrome__get_page_text",
"Bash(Select-Object -Last 30)",
"Bash(awk 'NR>=1960 && NR<=2009 && /^\\\\t[a-z][a-z-]*: \\\\{/ {name=$0} END{print name}' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk 'NR>=1980 && NR<=2009 && /^\\\\t[a-zA-Z][a-zA-Z-]*: \\\\{/ {print NR\": \"$0}' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk -F: '$1>=1700 && $1<=2100 {print}')",
"Bash(awk -F: '$1>=1850 && $1<=2009')",
"Bash(awk 'NR>=300 && NR<=340 && /: \\\\{/ {k=$0} /iconSize: .xxl./ {print NR\": owned by → \"k}' src/uix/eidos/lib/primitives/static.ts)",
"Bash(awk 'NR>=315 && NR<=335 && /: \\\\{/ {k=$0} /iconSize: .\\(xl|xxl\\)./ {print NR\": \"k\" → \"$0}' src/uix/eidos/lib/primitives/static.ts)",
"Bash(rg 'from '\\\\''\\\\$lib'\\\\''|from \"\\\\$lib\"|from '\\\\''\\\\$lib/' src/ -n --glob !**/node_modules/**)",
"Bash(awk -F: '$1 < 427')",
"Bash(awk -F: '$1 < 848')",
"Bash(rg -n --no-messages \"data-radio-cards-icon|<Icon|radio-cards.*Icon\" web/routes)",
"Bash(rg -ln --no-messages \"RadioCards\" web/routes)",
"Bash(rg -n --no-messages \"Icon|svg|<RadioCards.Icon\" web/routes/uix/components/radio-cards/+page.svelte)",
"Bash(rg -ln --no-messages \"color-surface-subtle|leading-body|font-family-body|segment-focus-shadow\" src/uix/eidos --glob \"*.test.ts\")",
"Bash(echo \"exit: $?\")",
"Bash(rg -ln --no-messages \"consumed.*defined|undefined.*token|orphan\" src/uix/eidos --glob \"*.test.ts\")",
"Bash(echo \"exit-grep=$?\")",
"Bash(awk 'NR>=1100 && NR<=1160 && /^\\\\t[a-z-]+:|recipe|component:|name:/' src/uix/eidos/lib/recipes/base.ts)",
"Bash(awk -F/ '{print $1\"/\"$2}')",
"Bash(awk -F/ '{ if \\($1==\"src\"\\) print \"src/\"$2\"/\"$3; else print $1 }')",
"Bash(grep -nE 'validateMorfo|export \\(async \\)?function loadMorfos|testTimeout|import' src/uix/eidos/../../../scripts/soma-attr-audit.ts 2>/dev/null | head; echo '--- direct run audit loadMorfos timing ---'; timeout 60 node --import tsx/esm -e \"import\\('./scripts/soma-attr-audit.ts'\\).then\\(async m=>{const t=Date.now\\(\\);const ms=await m.loadMorfos\\(\\);console.log\\('loaded',ms.length,'in',Date.now\\(\\)-t,'ms'\\)}\\).catch\\(e=>console.log\\('ERR',e.message\\)\\)\" 2>&1 | tail -5)",
"PowerShell(npm run check 2>&1)",
"PowerShell(1..3 | ForEach-Object { Write-Output \"=== Run $_ ===\"; npx vitest run src/uix/soma/soma-attr-audit.test.ts 2>&1 | Select-String -Pattern \"passed|failed|timed out|Duration\" })",
"Bash(cd /g/dev/svelte/vicen)",
"Bash(ls src/libs/timer/)",
"Bash(ls src/arts/timer/*.md)",
"Bash(find src/arts/timer src/libs/timer -name '*.test.ts')",
"Bash(npx rg *)",
"PowerShell(npx prettier --write \"src/uix/eidos/MOTION_SERVICE_RFC.md\")",
"PowerShell(npx prettier --check \"src/uix/eidos/MOTION_SERVICE_RFC.md\")",
"Bash(node_modules/.bin/prettier --write src/arts/sium)",
"Bash(node_modules/.bin/prettier --check src/arts/sium)",
"Bash(rg -ln 'from '\\\\''\\\\$uix/eidos'\\\\''' web/routes)",
"Bash(rg -oN \"var\\\\\\(--\\(space|radius|color|font-size|line-height|shadow|duration\\)-[a-z0-9-]+\\\\\\)\" src/uix/eidos/components/date-picker/*.css src/uix/eidos/components/button/*.css)",
"Bash(rg -oN \"var\\\\\\(--\\(space|radius|font-size|line-height|shadow|color-neutral|color-surface|color-text|focus|duration\\)-[a-z0-9-]+\\\\\\)\" src/uix/eidos/components/**/*.css)",
"Bash(ls web/routes/uix/components/demos/)",
"Bash(cat \"C:/Users/dev/AppData/Local/Temp/claude/G--dev-svelte-vicen/80e4dfad-c7bf-4627-b0ea-9337414354c7/tasks/b8u5eude9.output\")",
"Bash(echo \"exit morfo:check: $?\")",
"Bash(rg -i \"chronos|components\\\\\\\\\\\\\\\\demos\")",
"Bash(cd \"G:/dev/svelte/vicen\" && npm run morfo:check > /tmp/mc.txt 2>&1; ec=$?; echo \"morfo:check EXIT=$ec\"; echo \"=== linea qr-code ===\" && grep -iE \"qr-code|invariant|fail|FAIL|✗\" /tmp/mc.txt | head)",
"Bash(rg -rln \"RecipeTokens|defineRecipe|recipe\" src/uix/eidos/components/calendar/)",
"Bash(rg -i \"chronos|recipes\\\\\\\\\\\\\\\\base|sema\\\\\\\\\\\\\\\\components\\\\\\\\\\\\\\\\index\")",
"Bash(node_modules/.bin/prettier --write src/arts/connection/index.ts src/arts/http/consts.ts src/arts/http/diagnostics.ts src/arts/color/index.ts)",
"Bash(node_modules/.bin/prettier --check src/arts/connection src/arts/http src/arts/color)",
"Bash(node_modules/.bin/prettier --check src/arts/connection/index.ts src/arts/http/consts.ts src/arts/http/diagnostics.ts src/arts/color/index.ts)",
"Bash(node_modules/.bin/prettier --write docs/process/arts-audit-2026-06.md docs/process/README.md)",
"Bash(node_modules/.bin/prettier --check src/arts/connection/index.ts src/arts/http/consts.ts src/arts/http/diagnostics.ts src/arts/color/index.ts docs/process/arts-audit-2026-06.md docs/process/README.md)",
"Bash(rg -ln 'from '\\\\''\\\\$uix/eidos/components/button'\\\\''' src/uix/eidos/components/)",
"Bash(rg -n \"Button|child\" src/uix/eidos/components/date-picker/*.svelte)",
"Bash(rg -n \"Soma|getActiveUix|ActiveEidos\" src/uix/eidos/components/icon/*.svelte)",
"Bash(node_modules/.bin/prettier --write src/arts/connection/types.ts src/arts/connection/engine-connections.ts)",
"Bash(node_modules/.bin/prettier --check src/arts/connection/types.ts src/arts/connection/engine-connections.ts)",
"Bash(rg -rn \"toCalendarDateTime|export .*\\\\bTime\\\\b|toCalendarDate\" src/libs/days/)",
"Bash(sed -n '1,50p' src/uix/eidos/components/drag-drop/index.ts)",
"Bash(sed -n '1,40p' src/uix/soma/components/drag-drop/exports.ts)",
"Bash(rg -i \"chronos-view|chronos\\\\\\\\\\\\\\\\chronos|components.chronos|components.demos|drag-drop\")",
"Bash(echo \"=== ad-hoc opacity \\(non 0/1\\) with their selector context ===\")",
"Bash(awk ' *)"
],
"additionalDirectories": [
"\\tmp"
]
}
}

11
.gitignore vendored

@ -21,3 +21,14 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# Runtime artifacts (dev server logs, agent worker scratch, preview worktrees)
/tmp
/worker.js
/workers
.claude/worktrees/
.kilo/
# Agent scratch (Codex debug logs + screenshots dropped at repo root)
/.codex-*
/debug.log

@ -3,6 +3,7 @@
<words>
<w>acel</w>
<w>crossorigin</w>
<w>langs</w>
<w>numr</w>
<w>referrerpolicy</w>
</words>

@ -0,0 +1,203 @@
# Plan: Tema "pretty" — Basado en Chakra UI
## Objetivo
Crear un nuevo tema llamado **"pretty"** para el sistema Air, inspirado en los design tokens de Chakra UI v3. Incluye variantes **light** y **dark**.
---
## Mapeo Chakra UI → Air Tokens
### Colores primarios
Chakra usa **purple** como color de acento principal (`#faf5ff` → `#1a032e`). Se mapea a `--air-primitive-primary-*`.
### Paleta neutral
Chakra usa **gray** (`#fafafa` → `#111111`). Se mapea a `--air-primitive-neutral-*`.
### Paletas semánticas
| Chakra | Air |
| -------------------------------- | --------------------------- |
| `red` (`#fef2f2` → `#1f0808`) | `--air-primitive-danger-*` |
| `green` (`#f0fdf4` → `#03190c`) | `--air-primitive-success-*` |
| `orange` (`#fff7ed` → `#220a04`) | `--air-primitive-warning-*` |
| `blue` (`#eff6ff` → `#0c142e`) | `--air-primitive-info-*` |
### Tipografía
- **Sans**: Chakra usa Inter → se cambia `--air-font-sans` a Inter con fallbacks del sistema
- **Serif**: Se mantiene Lora (ya en el sistema Air)
- **Mono**: Se mantiene Azeret Mono
- Escala de 12 pasos: Se ajustan sizes/line-heights para coincidir con escala Chakra
### Border radius
Chakra es más redondeado: `sm: 4px`, `md: 6px`, `lg: 8px`, `xl: 12px`, `2xl: 16px`, `3xl: 24px`
→ Se ajustan `--air-radius-*` para reflejar la personalidad "pretty" (más suave/redondeado)
### Sombras
Se adaptan las sombras semánticas de Chakra (light/dark con `gray.900/10` y `black/64`)
### Spacing
Chakra tiene una escala más granular (con `.5`). Se mantiene la escala Air existente ya que los componentes la consumen.
### Motion
Se usan los easings y duraciones de Chakra: `moderate: 200ms`, `ease-in-smooth: cubic-bezier(0.32, 0.72, 0, 1)`
---
## Archivos a crear
### 1. `src/uix/air/themes/pretty/_static.css`
Overrides estáticos del tema pretty:
- `--air-font-sans` → Inter con fallbacks
- `--air-radius-*` → valores más redondeados (estilo Chakra)
- `--air-motion-distance-*` → distancias de Chakra
- `--air-duration-*` → duraciones de Chakra
- `--air-ease-*` → easings de Chakra
### 2. `src/uix/air/themes/pretty/light.css`
Tokens de color para variante light:
- Primitivos de color: neutral (gray Chakra), primary (purple), info (blue), success (green), warning (orange), danger (red)
- Tokens semánticos: surfaces, content, borders, paletas por categoría
- Sombras light
### 3. `src/uix/air/themes/pretty/dark.css`
Tokens de color para variante dark:
- Primitivos de color: versiones dark de cada escala
- Tokens semánticos: surfaces dark, content dark, borders dark
- Sombras dark
### 4. `src/uix/air/themes/pretty/index.css`
Barrel CSS: importa `_static.css`, `light.css`, `dark.css`
### 5. `src/uix/air/themes/pretty/index.ts`
Exporta `PRETTY_LIGHT_THEME` y `PRETTY_DARK_THEME` como objetos `AirTheme`
## Archivos a modificar
### 6. `src/uix/air/themes/index.css`
Añadir `@import './pretty/index.css';` después de earth
### 7. `src/uix/air/themes/index.ts`
- Añadir `'pretty-light' | 'pretty-dark'` al union type `AirThemeId`
- Añadir exports de `PRETTY_LIGHT_THEME` y `PRETTY_DARK_THEME`
---
## Valores concretos (light)
### Primitivos neutral (gray Chakra)
```
1: #fafafa 2: #f5f5f5 3: #e5e5e5 4: #d4d4d4
5: #a3a3a3 6: #737373 7: #525252 8: #404040
9: #262626 10: #171717 11: #111111 12: #09090b
```
### Primitivos primary (purple Chakra)
```
1: #faf5ff 2: #f3e8ff 3: #e9d5ff 4: #d8b4fe
5: #c084fc 6: #a855f7 7: #9333ea 8: #7e22ce
9: #6b21a8 10: #581c87 11: #3b0764 12: #1a032e
```
### Primitivos info (blue Chakra)
```
1: #eff6ff 2: #dbeafe 3: #bfdbfe 4: #93c5fd
5: #60a5fa 6: #3b82f6 7: #2563eb 8: #1d4ed8
9: #1e40af 10: #1e3a8a 11: #172554 12: #0c142e
```
### Primitivos success (green Chakra)
```
1: #f0fdf4 2: #dcfce7 3: #bbf7d0 4: #86efac
5: #4ade80 6: #22c55e 7: #16a34a 8: #15803d
9: #166534 10: #14532d 11: #052e16 12: #03190c
```
### Primitivos warning (orange Chakra)
```
1: #fff7ed 2: #ffedd5 3: #fed7aa 4: #fdba74
5: #fb923c 6: #f97316 7: #ea580c 8: #c2410c
9: #9a3412 10: #7c2d12 11: #431407 12: #220a04
```
### Primitivos danger (red Chakra)
```
1: #fef2f2 2: #fee2e2 3: #fecaca 4: #fca5a5
5: #f87171 6: #ef4444 7: #dc2626 8: #b91c1c
9: #991b1b 10: #7f1d1d 11: #450a0a 12: #1f0808
```
### Primitivos dark (versiones invertidas)
Los primitivos dark se generan invirtiendo las escalas de Chakra (claro→oscuro) manteniendo la misma lógica que base-dark.
### Radius (pretty = más redondeado)
```
sm: 6px md: 8px lg: 12px xl: 16px xxl: 24px
```
### Sombras light
```
subtle: 0 1px 2px rgb(9 9 11 / 0.1), 0 0 0 1px rgb(9 9 11 / 0.2)
raised: 0 2px 4px rgb(9 9 11 / 0.1), 0 0 0 1px rgb(9 9 11 / 0.3)
overlay: 0 4px 8px rgb(9 9 11 / 0.1), 0 0 0 1px rgb(9 9 11 / 0.3)
```
### Sombras dark
```
subtle: 0 1px 1px rgb(0 0 0 / 0.64), 0 0 0 1px inset rgb(163 163 163 / 0.2)
raised: 0 2px 4px rgb(0 0 0 / 0.64), 0 0 0 1px inset rgb(163 163 163 / 0.3)
overlay: 0 4px 8px rgb(0 0 0 / 0.64), 0 0 0 1px inset rgb(163 163 163 / 0.3)
```
### Motion (overrides Chakra)
```
duration-fast: 150ms
duration-normal: 200ms
duration-moderate: 300ms
duration-slow: 400ms
ease-default: cubic-bezier(0.4, 0, 0.2, 1)
ease-out: cubic-bezier(0, 0, 0.58, 1)
ease-in: cubic-bezier(0.42, 0, 1, 1)
ease-spring: cubic-bezier(0.32, 0.72, 0, 1)
```
---
## Orden de implementación
1. Crear `src/uix/air/themes/pretty/_static.css` con overrides de radius, motion, tipografía
2. Crear `src/uix/air/themes/pretty/light.css` con primitivos + semánticos + sombras light
3. Crear `src/uix/air/themes/pretty/dark.css` con primitivos + semánticos + sombras dark
4. Crear `src/uix/air/themes/pretty/index.css` (barrel CSS)
5. Crear `src/uix/air/themes/pretty/index.ts` (exports TS)
6. Modificar `src/uix/air/themes/index.css` — añadir import pretty
7. Modificar `src/uix/air/themes/index.ts` — añadir tipos y exports
8. Verificar con `npm run check` y `npm run build`

@ -7,3 +7,6 @@ bun.lockb
# Miscellaneous
/static/
# Generated artifacts
src/uix/eidos/generated/

@ -12,5 +12,5 @@
}
}
],
"tailwindStylesheet": "./src/routes/layout.css"
"tailwindStylesheet": "./web/routes/layout.css"
}

@ -2,7 +2,18 @@
This file provides guidance to agents when working with code in this repository.
## ⚠ INVIOLABLE — component work pre-flight
Before creating, porting, or modifying ANY UIX component, you MUST read:
1. **[`web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md`](web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md)** — pre-flight audit + reference library matrix + architectural rules + anti-patterns
2. **[`web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md`](web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md)** — locked demo template (6 tabs, stage, observer, snippets)
3. **[`src/uix/eidos/components/README.md`](src/uix/eidos/components/README.md)** — eidos contract
Skipping these produced the broken Layout Batch 1 (commit `9ec2a57a`) that was reverted + redone. Don't repeat the mistake.
## Build/Test Commands
```bash
npm run dev # Start dev server
npm run build # Production build (static site)
@ -18,7 +29,9 @@ npm run format # Auto-format with Prettier
## Critical Architecture
### Dual Alias Configuration Required
Path aliases must be synced in BOTH [`svelte.config.js`](svelte.config.js:11) AND [`vite.config.ts`](vite.config.ts:14) for TypeScript, Svelte compiler, and Vitest to resolve consistently:
- `@/` → `src/`
- `@/ling` → `src/lib/ling` (i18n)
- `@/logr` → `src/lib/logr` (logging)
@ -27,16 +40,21 @@ Path aliases must be synced in BOTH [`svelte.config.js`](svelte.config.js:11) AN
- `$uix` → `src/uix` (UI components)
### Svelte 5 Runes Mode Enforced
[`svelte.config.js`](svelte.config.js:31) forces `runes: true` for all project files via `dynamicCompileOptions`. All components must use Svelte 5 runes (`$state`, `$derived`, `$effect`, etc.).
### Vitest Two-Project Structure
[`vite.config.ts`](vite.config.ts:35) defines separate test projects:
- **client**: Browser tests via Playwright for `*.svelte.{test,spec}.{js,ts}` files
- **server**: Node environment for `*.{test,spec}.{js,ts}` files (excludes svelte tests)
### Internal Library Pattern
Each library (`ling`, `logr`, `glob`, `actx`) uses factory functions (`createLing`, `createLogr`, etc.) that return instances with internal state. The `logr` library depends on `ling` for localized messages.
## Code Style
- Tabs for indentation, single quotes, no trailing commas, 100 char print width
- Spanish comments in code are acceptable

@ -0,0 +1,916 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
> **Branch note (`active-uix`):** the legacy layers `src/lib/`, `src/uix/terra/`,
> `src/uix/air/` and the old `src/routes/` test tree were removed during the
> UIX refactor. The new ecosystem lives in `src/arts/`, `src/libs/`, `src/svrs/`,
> and `src/uix/{active-uix,soma,sema,eidos,morfo}`. Demos are being re-authored
> under `web/routes/` from scratch.
## Build / Test Commands
```bash
npm run dev # Start dev server (routes resolve from web/routes)
npm run build # Production build (static via adapter-static)
npm run test # Run all tests once
npm run test:unit # Run tests in watch mode
npx vitest run src/uix/morfo/compile.test.ts # Run a single test file
npx vitest run -t "describe name" # Run tests matching a pattern
npm run check # Type check with svelte-check
npm run lint # Check formatting (Prettier)
npm run format # Auto-format
```
## Code Style
- Tabs for indentation, single quotes, no trailing commas, 100 char print width (Prettier).
- Comments in **English**. (Some legacy Spanish comments remain — don't add new ones.)
- Svelte 5 runes mode is enforced globally (`svelte.config.js` `dynamicCompileOptions`).
- DOM event handlers: lowercase (`onclick`, `onfocus`). User callbacks: camelCase (`onComplete`, `onInteractOutside`).
- State: `$state()` without `#`. Derived: `readonly x = $derived.by(...)`.
## Path Aliases
Aliases are kept in sync between `svelte.config.js` and `vite.config.ts`. Source
of truth is `vite.config.ts` (single `aliases` const reused by both top-level
resolve and the server-test project).
| Alias | Target |
| --- | --- |
| `@` | `src/` |
| `$active-app`, `$adom`, `$auth`, `$bus`, `$cache`, `$clipboard`, `$color`, `$connection`, `$format`, `$http`, `$langs`, `$logger`, `$motion`, `$orca`, `$perm`, `$prefs`, `$session`, `$sium`, `$storage`, `$timer` | `src/arts/{name}` |
| `$libs`, `$locale`, `$reactive` | `src/libs`, `src/libs/locale`, `src/libs/reactive` |
| `$svrs` | `src/svrs/` |
| `$uix`, `$active-uix`, `$soma` | `src/uix`, `src/uix/active-uix`, `src/uix/soma` |
## Vitest Two-Project Structure
`vite.config.ts` defines two test projects:
- **client**: Browser tests via Playwright for `*.svelte.{test,spec}.{js,ts}`.
- **server**: Node environment for `*.{test,spec}.{js,ts}` (excludes svelte tests).
## Architecture
The UIX is layered around a declarative contract (morfo) that the other layers
consume. Each consumer reads the morfo via `compileMorfo(morfo)`, never by
walking the raw declaration.
```
arts/ → runtime artifacts: active-app, adom, motion, perm, prefs, ...
libs/ → pure helpers (zero-dep): reactive, days, dom, locale, ...
svrs/ → server-authoritative engines
uix/morfo → declarative contract: parts, data-attrs, ARIA, keyboard,
events with semantic family/intent. Compiler at compile.ts
emits CompiledMorfo (cached by WeakMap).
uix/sema → semantic engine + channels (visual / sound / vibra).
The visual channel writes data-event* during a hold window.
State attrs (data-state, data-disabled, ...) are NEVER
touched by sema — they belong to the morfo runtime.
uix/soma → headless component layer. Each provider consumes morfo +
sema via `runtime.trigger(eventName)` which sequences:
prewrite → semantic.emit → handler → effect-driven state attrs.
uix/eidos → CSS layer that selects against the data-attrs the morfo
contract promises. lint.ts validates a CSS file against
compileMorfo(morfo).contracts.cssSelectors.
uix/active-uix → composition root. Two boot modes:
- createActiveUix(options) — standalone (owns services)
- attachActiveUix(activeApp) — attach to external app
```
**Motion** lives in `arts/motion` (`EngineMotion`, alias `$motion`), exposed as
`uix.motion` and consumed by BOTH soma (`Presence.motion` → `motion.run`) and
eidos (CSS generation + preset registration). It's an art — not part of eidos —
so soma can drive JS motion (spring) without a soma→eidos dependency. The DOM
arrives injected via a structural `MotionDom` port (`adom` satisfies it).
Two-moment model + F1–F7: `src/arts/motion/README.md` +
`src/uix/eidos/eidos-motion.md`.
### morfo (`src/uix/morfo/`)
Single source of truth for a component. A `Morfo` declares:
- `parts[]` — kebab + archetype + `data` + `aria` + optional `keyboard`
- `events[]` — name + `target` partRef + `semantic` + optional `prewrite` + `commits`
- `focus` — initial / trap / return / restore (for overlay components)
`compileMorfo(morfo)` returns a `CompiledMorfo` with pre-resolved `AttrPlan[]`,
`KeyboardPlan[]`, `ActionPlan[]`, and `contracts.cssSelectors` (the closed set
of selectors eidos is allowed to use). Cached by morfo identity.
### eidos linter
`scripts/eidos-lint.ts <component>` and `scripts/eidos-lint-all.ts` classify every
`[data-*]` selector in eidos CSS as morfo-backed / eidos-only / invalid. Use
this to detect drift between the morfo contract and the eidos rules.
## Key Conventions
- **Never modify the morfo declaration files** without updating the eidos
selectors and provider sources that depend on them.
- **Read before acting.** When told to read a file, read it. Don't paraphrase.
- **Verify before reporting done.** Run `npm run check` and the relevant
vitest scope. UI claims need a browser check; if you can't run a browser, say so.
- **Compose existing components; flag gaps.** When building a component (or its
demo, or any UI), compose the framework's existing soma/eidos components
(`Button`, `Field`, `Popover`, `Icon`, …) — never re-implement a primitive
inline. If a needed component is missing, **flag the gap** so it gets built as
a reusable component, never reinvented ad-hoc. Detail: `soma/COMPONENT_GUIDE.md` §4.
- **No backward-compat shims** when relocating code. Update consumers and delete
the old path; don't leave a re-export.
- **No re-export façades** between layers (e.g. soma must not re-export dias —
consume `$libs/days` directly).
- **Provider naming**: child providers reference the parent as `provider`, never
`root`. The exported component is `<Xxx.Provider>`. `createAttrs` still uses
the part kebab `'provider'` internally.
- **Data-attr naming**: `data-{component}` (provider) and `data-{component}-{kebab}`
(sub-parts). Never `data-soma-*`. The compiler emits these via
`compiled.parts.attrs`; runtime queries match exactly.
- **CSS token naming (eidos recipes)**: bare-prefixed by component, never
by layer. Public tokens: `--{component}-…` (e.g. `--dialog-content-bg`,
`--toggle-radius-md`). Internal recipe tokens: `--_{component}-…`.
Never `--eidos-…`, `--air-…`, `--terra-…`, `--soma-…`. Migrating
`air-` baselines means stripping the `air-` prefix, not replacing it.
- **DOM helpers**: `$adom` is the single public surface for DOM-related
functionality at component / soma level. It re-exports all of
`$libs/dom` wholesale, plus the reactive `createActiveDom` runtime
and stateful primitives (`BodyScrollLock`, `DOMContext`, `RovingFocusGroup`).
Components and soma NEVER import from `$libs/dom` directly —
consume `$adom`. Only `arts/adom/*` (the implementation), `arts/frontend`
(peer service fallback), and tests of `$libs/dom` may reach into it.
This includes responsive types (`ResponsiveProp<T>`, `Breakpoint`,
`Breakpoints`) — they travel with the runtime, so they live in `$adom`.
- **Iframe / popup correctness**: when you have a `uix.dom`, prefer
`uix.dom.getDocument(node?)` and `uix.dom.getWindow(node?)` over the
bare functions of the same name. The instance methods respect the
active-dom's `targetWindow`; the bare functions fall back to the
global `document` / `window`, which is wrong in iframe / popup /
happy-dom-test contexts.
## Critical Rules
- **NEVER delete files without explicit instruction** ("delete", "remove",
"borra", "elimina"). If ambiguous, ASK first.
- **NEVER create fake translators / mocked services** in tests for components.
Use real instances composed via `createActiveUix` or `attachActiveUix`.
- **NEVER skip hooks (`--no-verify`) or bypass signing** unless explicitly asked.
If a hook fails, fix the root cause.
- **`$reactive` survives, `$lib` does not.** The `$reactive` alias points to the
current `src/libs/reactive`; the singular `$lib` alias was removed in the
cleanup phase and any code that resurfaces it is a regression.
## Reference Documents
**Start here**: [`docs/README.md`](docs/README.md) — the documentation entry point.
It maps the whole corpus (the strata, the reading order, and where every doc
lives) with task-oriented shortcuts ("I want to…"). New sessions and agents
should read it first to orient.
Architecture overview and motivations: [`src/uix/active_architecture.md`](src/uix/active_architecture.md).
Per-layer references:
- [`src/uix/morfo/README.md`](src/uix/morfo/README.md)
- [`src/uix/sema/README.md`](src/uix/sema/README.md)
- [`src/uix/soma/SOMA_ARCHITECTURE.md`](src/uix/soma/SOMA_ARCHITECTURE.md) +
[`src/uix/soma/COMPONENT_GUIDE.md`](src/uix/soma/COMPONENT_GUIDE.md)
- [`src/uix/eidos/README.md`](src/uix/eidos/README.md) +
[`src/uix/eidos/components/README.md`](src/uix/eidos/components/README.md)
The canonical guide for sema vocabulary, morfo event shape, color
tokens, persistence and a11ySemantic lives in
[`src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md`](src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md).
That document is authoritative — when in doubt about families, intents,
verbs, color subsets per component, or holds, that's the source.
## Sema cascade selectors must use the typed builder
Cascade rules in `src/uix/sema/components/*.ts` MUST construct their
`selector` via `semaSelector(morfo, partKebab, matchers?)` from
`$uix/morfo`. **Never hand-write selector strings** that target morfo-
emitted attrs (`[data-{component}]`, `[data-{component}-{part}]`,
`[data-event="..."]`). The helper:
- Compile-error if `partKebab` doesn't exist on `morfo.parts`.
- Compile-error if `matchers.eventName` doesn't match `morfo.events`.
- Type-checks `eventFamily`/`eventIntent` against the canonical sema
unions.
- Output is the same CSS string the cascade rule needs — zero runtime
cost beyond concatenation.
Hand-written selectors in cascade rules are an architecture violation:
when morfo renames a part or event, the strings go stale silently. The
helper wires the cascade to the morfo's contract so renames break at
the type level.
```ts
// good
import { semaSelector } from '$uix/morfo'
import { dialogMorfo } from '$uix/morfo/components/dialog'
{
selector: semaSelector(dialogMorfo, 'content', { eventFamily: 'commit' }),
haptic: { kind: 'tap' }
}
// bad — string drifts silently if morfo renames
{
selector: '[data-dialog-content][data-event-family="commit"]',
haptic: { kind: 'tap' }
}
```
## Sema: intent policy per family
Per book canon there are **8 families**, encoded in `SEMA_FAMILY_POLICY` at
`src/uix/sema/types.ts`. Each family declares two independent axes:
```ts
{
contact: { intentRequirement: 'optional', intentGuidance: 'discouraged' },
commit: { intentRequirement: 'required', intentGuidance: 'expected' },
signal: { intentRequirement: 'required', intentGuidance: 'expected' },
handle: { intentRequirement: 'optional', intentGuidance: 'contextual' },
emerge: { intentRequirement: 'optional', intentGuidance: 'contextual' },
shift: { intentRequirement: 'optional', intentGuidance: 'contextual' },
sustain: { intentRequirement: 'optional', intentGuidance: 'contextual' },
delegate: { intentRequirement: 'optional', intentGuidance: 'contextual' }
}
```
- **`intentRequirement`** (`'required' | 'optional' | 'forbidden'`) — compile-time gate. `required` → `intent` REQUIRED in `MorfoEventSemantic` (discriminated union). `forbidden` is reserved for future use (no family uses it today).
- **`intentGuidance`** (`'expected' | 'contextual' | 'discouraged'`) — doctrinal hint, not type-enforced. Drives lint warnings and editor tooltips.
Type derivation: `SemaEvent` and `MorfoEventSemantic` discriminate over `intentRequirement`. Editing the const reshapes the discriminated union.
Runtime enforcement: `validateSemaEvent` throws when an `intentRequirement: 'required'` family declares an event without intent. Optional families pass silently.
**`delegate`** is the 8th family per book cap. 29 — reparto de iniciativa entre usuario y sistema. Structural; carries no intent of its own. Active channels empty (delegate composes with sustain / signal / commit for perceptual layering, doesn't own a base signature).
The earlier flat `intentPolicy: 'allowed' | 'expected' | 'optional'` mixed type-requirement with doctrinal guidance. The split was applied in commit `00f0b740`.
## Sema: open channel registry + flat CSS-style cascade
The perceptual layer is **NOT closed**. The book defines **8 expression
channels** (time · motion · presence · depth · shape · color · sound · haptic);
the framework SPLITS them by owner — and this is the single canonical narrative:
- **Sema executes 2 runtime channels** — `sound` + `haptic`, the ONLY two
declared in `SemaChannelSignatures` — plus projects the `visual` meta-channel
(it stamps `data-event-*` during the hold). `motion`/`color`/`presence` were
explicitly removed from sema (`sema-map.ts`); the engine knows no DOM/CSS.
- **Eidos materializes the visual channels** (motion / presence / depth / shape /
color) by reacting in CSS to those `data-event-*` + the morfo's
`data-state`/`data-intent`. Eidos is the sole visual owner.
Apps add more *sema* channels (a11y, voice, …) via TypeScript declaration
merging of `SemaChannelSignatures`.
**The semantic tokens are `data-event-*`**: the engine stamps `data-event`,
`data-event-family`, `data-event-intent`, `data-event-phase`, `data-event-id`
on `signal.target` BEFORE the cascade resolves. Cascade rules use CSS
selectors that target those tokens directly — same surface that eidos CSS
already reads. Engine owns stamp/unstamp; VisualChannel only owns the hold.
Cascade rules are **flat** (no `overrides: { eventLabel: ... }` middle
layer): a single selector + per-channel deltas, like a CSS rule.
```ts
{
selector: '[data-dialog-content][data-event-intent="threat"]',
sound: { sampleUrl: '/sounds/dialog-fail.wav' },
haptic: { kind: 'error', pattern: [50, 80, 50, 80, 50] }
}
```
Resolution cascade (each layer overrides the previous):
```
1. SEMA_MAP.families[F].base — per-channel signatures
2. SEMA_MAP.intents[I].deltas — valenced families only;
numbers ADD (modificadores)
3. signal.overrides + signal.channels — per-event morfo overrides;
numbers REPLACE
4. engineOpts.overrides.runtime — path-based globals; REPLACE
5a. engineOpts.components[].cascade — per-component packs
(sema/components/{name}.ts)
5b. engineOpts.overrides.cascade — app-level rules; appended
AFTER packs so they win
on equal specificity
```
**Per-component packs live in `src/uix/sema/components/{name}.ts`** —
symmetric to morfo / soma / eidos. Each exports a `Sema` with
`cascade` rules + optional `preloadSamples` (WAVs decoded at boot).
**Override semantics — numbers**:
- intent.deltas (capa 2): numbers ADD (modificadores compositivos).
- overrides (capas 4, 5, 6): numbers REPLACE (`color: red`, not "add red").
- Use `{ op: 'add' }` / `{ op: 'multiply' }` / `{ op: 'replace' }` for
explicit semantics either way.
**`intent.deltas` owns the perceptual signature of the intent** — the
sound primitives `pitch`, `gain`, `contour` (and the haptic `intensity`
when shaped per-intent) come from capa 2. **Cascade rules (capas 6a/6b)
MUST NOT override these primitives** — doing so flattens the perceptual
difference between `risk` / `threat` / `loss` (or `affirm` / `fulfill`).
Cascade rules add character (`haptic.kind`, `haptic.pattern`,
`sound.sampleUrl`, `motion.easing`, `presence.backdrop`) — not the
intent's evaluative profile. If a per-component cascade rule needs to
*shift* a numeric primitive (e.g. close-* in emerge goes descending),
use `{ op: 'add', value: -150 }` so it composes on top of the intent
rather than erasing it.
`HapticChannel` (V1, real — Vibration API) is opt-in via
`new EngineSemantic({ haptic: true })`. Sound and haptic both default
off because they have audible / tactile side effects.
Canonical sources: `src/uix/sema/sema-map.ts` (SEMA_MAP value +
SemaChannelSignatures registry + Sema), `src/uix/sema/resolver.ts`
(cascade implementation + CSS specificity), `src/uix/sema/stamp.ts`
(stamp/unstamp), `src/uix/sema/components/*.ts` (per-component packs),
`src/uix/sema/README.md`.
## Eidos drift defense — types over lint
Selector drift between morfo's emitted attrs and the layers that style /
react to them (eidos CSS, sema cascade) is defended **at compile time
via typed builders**, not at lint time.
- **Sema cascade rules** (`sema/components/*.ts`) consume
`semaSelector(morfo, partKebab, matchers?)` from `$uix/morfo`. Renames
in morfo break the cascade at type-check time. See "Sema cascade
selectors must use the typed builder" above.
- **Eidos plain `.css` recipes** are still raw CSS today — there is no
CSS-side typed builder. For those, `scripts/eidos-lint.ts` remains as
an **opt-in safety net** that classifies each `[data-*]` selector as
`morfo-backed` / `eidos-only` / `invalid`. It is not the architectural
contract; the contract is the morfo declaration.
Rule: when a layer can consume the morfo via TypeScript (anything in
`.ts` / `.svelte`), it MUST use the typed builder. Lint is for the
remaining surface (plain CSS recipes) until those gain a builder of
their own. Hand-written morfo-targeting selector strings in TypeScript
files are an architecture violation, not a lint warning.
## Session hand-off — 2026-06-05 (sistema de color — cierres + theme builder runtime + wide-gamut + a11y)
Sprint dando "un giro de vueltas" al sistema de color de Eidos hasta reference-grade. 6 commits. El motor `uix.color` (`$color`, art puro isomórfico: OKLCH↔sRGB, APCA, alpha compositing-inverse, `deriveScheme`/`generateScale`/`temper`) ya existía; este sprint lo CONSUME end-to-end + cierra los ítems de calidad de `THEMING_AUDIT`.
**Cierres rápidos** (`cc37bdce`):
- `themes/base.ts`: `loss` era `purple` ≡ primary → mapeado a `plum` (escala canónica). Última colisión de roles del tema base cerrada (tras tertiary→indigo).
- `temas/grafito`: la sección "override por componente" pasaba nombres de escala (teal/amber/…) a `Button.color`, que solo acepta el override jerárquico (`primary|secondary|neutral`) — type error + inerte. Reescrita a los 2 ejes reales: `color` (jerarquía) + `intent` (paleta evaluativa). Cierra el último error de `svelte-check` → **0 errores**.
**API runtime — theme builder** (`c4d2e34d`):
- Nuevo `src/uix/eidos/lib/build-scheme.ts` (PURO): `buildScheme(seed, opts)` compone `deriveScheme` + `generateScale` + APCA on-solid + alpha en el mapa de override `--primitive-{role}-*` (+ `--color-{role}-contrast`). `seed → { variables, roles }`. Sin DOM.
- `ActiveEidos.applyColorScheme(seed, opts)` / `clearColorScheme()`: resuelve escalas-donantes + background del tema activo, escribe el bloque `uix-eidos-scheme` DESPUÉS del de tema (gana cascada), RE-DERIVA al cambiar de modo (sigue light/dark). Devuelve `BuildSchemeResult`. `opts`: `variant` (tonal|vibrant|monochrome) + `temper` (cohesión de intents, mantiene hue) + `overrides` per-rol + `selector`. Exportado de `$uix/eidos`. Demo `/temas/color` dogfooda `buildScheme`.
**Wide-gamut OKLCH** (`909ab7f9` output + `0bb03559` generador):
- Output OKLCH-nativo default-on (RFC §7 estrategia A): `render-css > appendColorScaleDeclarations` emite por cada paso de paleta el hex (fallback) + un hermano `oklch()` que gana donde se soporta. SIN flag.
- Generador wide-gamut-TRUE: `buildScheme`/`applyColorScheme` retienen el OKLCH raw de `generateScale` (sin clamp) → `result.wideGamut` + `result.roles[].stepsOklch`. Nuevo `schemeDeclarations(result, { fallback })` apila hex+oklch (default) u oklch-only (inline).
- **HONESTIDAD**: la paleta Radix shipped es hex sRGB → sus `oklch()` son sRGB-equivalentes (idéntico hoy). El wide-gamut REAL vive en el generador: seed con croma > sRGB sale más saturado en P3. Demo `/temas/color`: slider **vivacidad P3** + badge «fuera de sRGB → P3» (`isInSrgbGamut`). Verificado: ×1.70 → croma primary-9 0.18→0.31. La paleta autorada NO se migró a semillas (regresaría los valores exactos de Radix sin añadir wide-gamut visible).
**P3-2 forced-colors + P3-3 ramp de bordes** (`4eab3306`):
- **forced-colors (Windows HCM)**: el navegador auto-mapea bordes/texto/fondos (`forced-color-adjust: auto`) pero ELIMINA `box-shadow` → el focus ring (`--focus-ring`, box-shadow) desaparecía. Fix: foundation emite siempre `renderForcedColorsBlock` → `@media (forced-colors: active) { :focus-visible { outline: 2px solid Highlight } }`. Componentes con outline propio (Button) lo conservan por especificidad. Pendiente: `prefers-contrast: more`.
- **ramp de bordes**: slot de rol `border` 6 (separador sutil) → **7** (UI element border de Radix). `DEFAULT_COLOR_ROLE_SLOT_STEPS`. element/hover/active=3/4/5 se quedan. Verificado: `--color-{role}-border` → primitive-7 (checkbox OK).
**Verificación**: `check` 0 errores. Suite eidos 155/158 (3 fallos pre-existentes del track `words`, confirmados con `git stash` baseline). `build-scheme` 8 + scheme 4 + forced-colors 1. `generated/base.css` regenerado y en sync.
**Decisiones con efecto duradero**:
- **Capas**: `uix.color` = matemática · `eidos/lib/build-scheme` = composición (pura) · `ActiveEidos` = aplicación DOM. No mezclar.
- **Wide-gamut default-on, sin flag** (estrategia A); fallback hex → sRGB idéntico, no-disruptivo.
- **Paleta autorada = exacta-Radix-sRGB** (sin regresión). Wide-gamut VISIBLE de la paleta = Fase 3 futura (autorar/generar en OKLCH).
- **forced-colors**: el box-shadow muere en HCM → el foco debe ser `outline`. Migrar los componentes que aún usan `var(--focus-ring)` (box-shadow) a `outline` propio es trabajo futuro per-componente (hoy dependen del fallback global).
- **slot `border` = step 7** (no 6).
**Pendiente (solo higiene del engine, NO calidad de color)** — _todo resuelto-o-decidido en la continuación, abajo_: `THEMING_AUDIT` P3-4/5/6/7/8/9/11 + mitades P2.
Docs: `COLOR_ENGINE_RFC.md` §6.2/§7, `THEMING.md` §26/§27/§28 + TOC, `README.md` tabla, `THEMING_AUDIT_2026-06-01.md` P3-1/2/3 resueltos.
**Continuación (mismo día) — fix de UX + cierre del backlog**:
- **Checkbox lag — NO era color, era timing del sema** (`e6fd014d` + `cd834384`). El usuario reportó el check "muy lento". Medido frame-a-frame: el `data-state` tardaba **244ms** en cambiar tras el click. Causa: el provider del checkbox fija el estado en el HANDLER del `runtime.trigger`, y el morfo declaraba `sequence: 'pre'` → el runtime hace `await runEmit()` (que **espera el hold del canal visual ~240ms**, `engine.emit()` → `await visualChannel.handle`) ANTES del handler. Fix: `commit-toggle-check`/`commit-toggle-uncheck` → `sequence: 'post'` (handler primero, pulso después). 244ms → 46ms. Secundario: trazo del checkmark `stroke-duration` 220ms hardcoded → `var(--duration-fast)`. **Verificado que radio-group (47ms) y tabs (31ms) NO laggean** aunque son 'pre' — fijan estado en el call-site, no en el handler; no se tocaron. Toggle/Switch ya eran 'post'.
- **DOCTRINA NUEVA**: un control cuyo estado se fija en el HANDLER del trigger DEBE usar `sequence: 'post'`; con `'pre'` el hold perceptual del emit bloquea el cambio funcional. Los que fijan estado en el call-site toleran 'pre' sin lag.
- **P3-11 surface ladder** (`76e18772`). Light `overlay` era `neutral-3` == `muted` (popovers indistinguibles de paneles muted en light); dark ya tenía overlay=4. Light overlay → `neutral-4` → ladder consistente en ambos modos: `default(1) < raised(2) < muted(3) < overlay(4)`. Verificado (popover light: overlay L93% ≠ muted L95.5%).
- **prefers-contrast: more** (`21b2329a`). Completa el a11y de color junto a forced-colors. `renderPrefersContrastBlock` → `@media (prefers-contrast: more) { :root:root { … } }` refuerza bordes (neutral 7/8/9) + texto de-enfatizado (12/11). `:root:root` (0,2,0) gana al `:root` del tema; aditivo, gated, estrictamente más fuerte. THEMING §28.
- **Cierre del backlog del engine** (`afa15aea`). `THEMING_AUDIT` P3 todo fixed-or-decided:
- ✅ **P3-5** guard `appendScaledMetricDeclarations` (solo escala números finitos ≠0; keywords/`var()`/`calc()` verbatim — evita `calc(auto * …)`).
- ✅ **P3-6** confirmado ya resuelto (dispose vía `dom`, sin `document` directo).
- ✅ **P3-8** index ya NO re-exporta los render-fns crudos (API pública = clase `ActiveEidos`; `./lib/render-css` para uso interno). Consumidor de test redirigido al módulo.
- ⏸️ **P3-4** deferido (densidad gana por orden de fuente determinista, estable; restructure `:where(:root)` = coste alto por nit teórico).
- ⏸️ **P3-7** deferido (reactividad callback-driven vía `apply()` POR DISEÑO; runes = refactor riesgoso sin bug que lo justifique).
- ⏸️ **P3-9** deferido (forwarders huérfanos `_accent` — cirugía de recipe con riesgo de cascada, valor bajo).
- Único pendiente real: 2 mitades **P2** (poda de contrato + validación de identificador-color pelado) — edge-case, deferidas por bajo valor.
**Estado final del color/theming**: apariencia + a11y (forced-colors + prefers-contrast) + wide-gamut + theme builder runtime + backlog del engine — todo resuelto-o-decidido, cero ítems colgando. `check` 0 errores. Suite eidos 156/159 (3 fallos de words, pre-existentes, confirmados con baseline). Docs extra: `THEMING.md` §28 (prefers-contrast), `THEMING_AUDIT_2026-06-01.md` scorecard P3 completo.
## Session hand-off — 2026-05-27 (persistence + a11ySemantic + polymorphic)
Sprint cerrando los 3 ítems diferidos del hand-off 2026-05-08: persistence + holds-by-intent (libro §6), a11ySemantic per event (libro §9), polymorphic events (libro §5.3). Detalle completo en `src/docs/LIBRO_VARIACIONES_Y_EXTENSIONES.md` D.9 / D.10 / D.11.
**Persistence (libro §6.1)** — separa `hold` (duración perceptible mínima) de `persistence` (lifecycle real):
- Nuevo tipo `SignalPersistence = 'transient' | 'untilAction' | 'untilFix' | 'stateBound'` en `src/uix/sema/types.ts`, propagado a `SemanticSignal`, `MorfoEventSemantic` y `TriggerOptions`.
- Tabla canónica `SEMA_HOLDS_BY_INTENT` en `src/uix/sema/holds.ts` (referencia, NO auto-aplicada — default conservador `'transient'`).
- `EngineSemantic.emit()` ahora devuelve `Promise<string>` (el id). Para `persistence !== 'transient'` mantiene la proyección viva pasado el hold; expone `engine.clear(id)`, `engine.clearTarget(target)`, `engine.hasActive(id)`.
- `SomaRuntime.trigger()` devuelve `TriggerResult { id?, persistence? }`. Expone `runtime.clearSignal(id)`, `runtime.clearTarget(target)`, `runtime.partRef(part)` (helper que devuelve el HTMLElement registrado de un part — útil para que providers limpien sin trackear ids).
- 6 morfos declaran `persistence` explícita: `announce.signal-alert` (untilAction), `dialog/drawer.close-after-fail` (transient explícito), `file-upload.signal-warn-reject` + `form.signal-warn-invalid` (untilFix), `password-field.signal-notify-caps-state` (stateBound).
- 3 providers cabledados con `clearTarget(provider)`: form (limpia al validar / reset), file-upload (limpia antes de cada nueva ronda de accept/reject + en remove / clear), password-field (limpia indicator al apagarse caps lock).
**a11ySemantic (libro §9.1)**:
- Nuevo `MorfoA11ySemantic` interface en `src/uix/morfo/types.ts`: `requiresPersistentTrace?`, `requiresLiveRegion?`, `requiresFocusMove?`, `keyboardEquivalent?`, `reducedMotionFallback?: 'state' | 'text' | 'focus' | 'none'`. Añadido como `a11ySemantic?` opcional en `MorfoEvent`. Pasa por compile a `ActionPlan.a11ySemantic`.
- Helper de reduced-motion en `src/arts/adom/reduced-motion.svelte.ts` (paralelo a `viewport.svelte.ts`): `ReducedMotionTracker` reactivo del media query con SSR-safe fallback. Expuesto en `ActiveDom.prefersReducedMotion.matches`.
- `ActiveUix.announce(message, priority?, timeout?)`: live region compartida lazy-creada vía `dom.writeNode`. No depende de soma. Polite/assertive son regiones separadas, cleanup en `dispose()`.
- `Soma.runtime()` auto-wires `sources.announce = (m, p) => uix.announce(m, p)`. Tests usan fake `announce`.
- `SomaRuntime.trigger()` honra a11ySemantic después del emit: live region (con `opts.message`), focus move, reduced-motion fallback (incluye forzar `channels: []` cuando fallback = 'state').
- 6 morfos anotados (los mismos consumidores).
**Polymorphic events (libro §5.3)** — ADITIVO sobre el shape concreto:
- Morfo declara `family` + `intent` + `verb` como default. Si añade `allowedFamilies: readonly SemaFamily[]`, los providers pueden override la family en `runtime.trigger(name, { semantic: { family, intent?, verb? } })`. El default family del morfo es IMPLÍCITAMENTE allowed.
- `isPolymorphicSemantic(semantic)` helper exportado de `$uix/morfo`.
- `SomaRuntimePolymorphicError` cuando override no está en allowedFamilies.
- Diseño aditivo (no variante separada con `defaultSemantic`) elegido para mantener backwards-compat con 29 morfos existentes y demos que acceden `event.semantic.family` directamente.
**Tests**: 671/671 pasan en `src/uix/sema + morfo + soma + src/arts/adom/test`. 4 fallas pre-existentes en `src/uix/contracts.test.ts` (words component translation keys) — confirmadas en baseline antes de mis cambios via `git stash`.
**`npm run check`**: 1 error pre-existente en `src/uix/soma/components/command/command-provider.svelte.ts:384` (`Cannot find name 'attrs'`) que ya estaba en main.
**Pendientes deliberadamente fuera del sprint** (resueltos en la segunda mitad — ver hand-off 2026-05-27 #2 abajo):
- ~~Extender persistence a tags-input + textarea~~ — DONE
- ~~Caller messages para announce~~ — DONE (form / file-upload / password-field / dialog / drawer)
- ~~Demo polymorphic real~~ — DONE (Dialog refactorizado a polymorphic close)
## Session hand-off — 2026-05-27 #2 (messages + persistence ext + Dialog polymorphic)
Segunda mitad del sprint cerrando el backlog del hand-off anterior. 3 grupos de cambios.
**M — Caller messages para live region**:
Cada provider que dispara un evento con `a11ySemantic.requiresLiveRegion` ahora pasa `opts.message` con texto localizado. La live region (ya cabledada via `Soma.runtime() → sources.announce`) finalmente anuncia algo:
- `form-provider`: `signal-warn-invalid` → "1 form error..." / "{N} form errors..." (count = `Object.keys(form.issues).length`). Usa `FORM_LANGS.ERROR_SUMMARY_*`.
- `file-upload-provider`: `signal-warn-reject` → "1 file was rejected" / "{N} files were rejected". Nuevas entradas `FILE_UPLOAD_LANGS.REJECT_SUMMARY_SINGLE/MULTI` + langs catalog.
- `password-field-provider`: `signal-notify-caps-state` → "Caps Lock is on" (usa `PASSWORD_FIELD_LANGS.CAPS_WARNING` que ya existía).
- `dialog-provider.dismissWith(action, { message })`: signatura aceptando `message` opcional, forwarded al trigger.
- `drawer-provider.dismissWith(action, { message })`: igual que dialog.
**P — Persistence extendido a tags-input + textarea**:
Misma doctrina §6.2 — signal+risk = untilFix:
- `tags-input.signal-warn-reject` → persistence: `untilFix` + a11ySemantic + provider clear via `clearTarget(input)`. Nueva entrada `tags-input.reject-warning` en langs catalog. Provider gana método privado `emitWarnReject(target)` que centraliza clear + emit + message. Successful add también clearea (la adición VÁLIDA es el "fix" de un reject previo).
- `textarea.signal-warn-count-overflow` → persistence: `untilFix` + a11ySemantic + provider clear en transición OUT-of-overflow. Nueva entrada `textarea.overflow-warning` en langs catalog. Provider emite SOLO en transición INTO overflow (no en cada keystroke al cap) — el `untilFix` proyecta persistente, no necesita re-anuncio.
**POLY — Dialog refactor a polymorphic close**:
Caso real de uso del feature de eventos polymorphic (§5.3). 5 eventos `close-*` colapsados en 1 evento `close`:
- **Morfo** (`dialog.ts`): un evento `close` con `family: 'emerge', verb: 'close', allowedFamilies: ['emerge', 'commit', 'signal']`. SIN `prewrite` (el provider escribe `data-last-action` imperativamente).
- **Validador relajado**: el invariante "cada `values[]` debe ser prewritten por algún event" cayó al sentido único "cada prewrite con value debe estar en `values[]`". Razón: con polymorphism los values pueden ser escritos imperativamente. El comentario en `schema.ts` explica.
- **Provider** (`dialog-provider.svelte.ts`): `DISMISS_CAUSES` mapa de acciones a `{ lastAction, semantic }`. `dismissWith(action, opts)` traduce a `dom.apply` + `runtime.trigger('close', { semantic, ... })`. `triggerClose` es privado ahora.
- **Cascade sema** (`sema/components/dialog.ts`): selectores `eventName: 'close-dismiss-outside'` → `eventName: 'close', state: { attr: 'data-last-action', value: 'dismissed-outside' }`. `eventNamePrefix: 'close-'` → `eventName: 'close', eventFamily: 'emerge'`.
- **Eidos CSS** (`dialog.css`): NO cambió — ya leía `data-last-action` para tintar, no nombres de evento.
- **API pública preservada**: consumidores externos no notan diferencia (`dismissWith` mantiene firma + comportamiento observable).
- **Drawer / Popover**: NO refactorizados. Mismo patrón aplicable; defer porque cada uno requiere reescribir su cascade + decisión separada de timing.
**Tests**:
- `npx vitest run src/uix/sema src/uix/morfo src/uix/soma src/arts/adom/test`: **679/679 pass**.
- `npm run check`: 1 error pre-existente (`command-provider.svelte.ts:384` — ya en baseline).
- 4 fallas pre-existentes en `src/uix/contracts.test.ts` (words translations) — no relacionadas.
**Pendientes a futuro** (cubierto en hand-off 2026-05-27 #3 abajo):
- ~~Refactor analógico de Drawer + Popover a polymorphic close~~ — DONE
- Otros `signal.warn-*` no incluidos: hay variantes con `verb: 'warn'` que podrían también beneficiarse de persistence untilFix (revisar caso por caso).
## Session hand-off — 2026-05-27 #3 (Drawer + Popover polymorphic close)
Tercera tanda del sprint — completa el rollout del patrón polymorphic close (book §5.3) a los dos componentes hermanos de Dialog. Mismo refactor aplicado de forma sistemática.
**Drawer**:
- Morfo: 5 close-* → 1 polymorphic `close` con `family: 'emerge', allowedFamilies: ['emerge', 'commit', 'signal']`, sin prewrite. Drag events (drag-start / drag-progress / drag-end / resize) intactos.
- Provider: `DISMISS_CAUSES` map + `dismissWith(action, opts?)` traduce a `dom.apply(data-last-action)` + `runtime.trigger('close', { semantic })`. `triggerClose` privado. 4 callsites internos migrados a `dismissWith` (Content escape, Content keydown escape, Overlay click-outside, Close button).
- Sema cascade (`sema/components/drawer.ts`): selector `eventNamePrefix: 'close-'` → `eventName: 'close', eventFamily: 'emerge'`.
- Test (`drawer-provider.svelte.test.ts`): `triggerClose('close-dismiss', ...)` → `dismissWith('dismiss')`; expect name='close'.
**Popover**:
- Morfo: igual refactor. 5 close-* → 1 polymorphic `close`.
- Provider: igual patrón con DISMISS_CAUSES + dismissWith. Internal callsites (5 lugares: scheduleHoverClose, trigger toggle click, trigger toggle keydown, escape, outside) migrados a `dismissWith`. Close button: usa el prop `action` traducido a dismissWith.
- Sema cascade (`sema/components/popover.ts`): mismo patrón — `eventName: 'close'` + `state: { attr: 'data-last-action', value: 'dismissed-outside' }` para la regla de dismiss-outside passive.
- Test (`popover-provider.svelte.test.ts`): no había refs a close-* directos. Ningún cambio necesario.
**Test fixtures migrados antes del refactor**:
- `compile.test.ts`: prewrite test usaba `drawerMorfo` → ahora usa `colorPickerMorfo`.
- `runtime.svelte.test.ts`: prewrite test usaba `drawerMorfo` → ahora usa `colorPickerMorfo`.
**Resultado**:
- 3 componentes overlay (Dialog / Drawer / Popover) usan el patrón polymorphic close de forma consistente.
- 5 picker components (color-picker / date-picker / date-range-picker / time-picker / time-range-picker) mantienen el shape per-event. Son la canonical reference para `prewrite` declarativo + sirven como fixtures de tests.
- API pública intacta — `dismissWith` mantiene signatura en los tres overlays.
- Eidos CSS no tocó (ya leía `data-last-action`).
**Tests**: 680/680 pass en `src/uix/sema + morfo + soma + src/arts/adom/test`. `npm run check`: 1 error pre-existente.
**Pendientes a futuro** (picker family cubierto en hand-off 2026-05-27 #4):
- ~~Picker family refactor~~ — DONE
- Otros `signal-warn-*` no cubiertos (textarea / tags-input ya hechos; revisar caso por caso si emergen más).
## Session hand-off — 2026-05-27 #4 (Picker family polymorphic close)
Cuarta tanda — completa el rollout polymorphic close al picker family (5 componentes). Más decoupling de tests.
**Refactor de los 5 pickers**:
- `color-picker`: 4 close-* → 1 polymorphic `close`
- `date-picker`: 4 close-* → 1 polymorphic `close`
- `date-range-picker`: 4 close-* (incl. `close-range-commit`) → 1 polymorphic `close`
- `time-picker`: 4 close-* → 1 polymorphic `close`
- `time-range-picker`: 4 close-* → 1 polymorphic `close`
Cada morfo: `family: 'emerge', verb: 'close', target: v.partRef('calendar'|'clock'|'content'), persistence: 'transient', allowedFamilies: ['emerge', 'commit', 'signal']`.
**Hallazgo importante**: los picker providers NO disparan los close events. Solo togglean `opts.open = false`. Los eventos estaban declarados pero **inertes** — su único consumidor era el schema validator y los compiler tests. El refactor es alineación doctrinal, no de comportamiento.
**Sema cascade**: solo `color-picker` tiene un sema pack y no referenciaba close-* (solo handle-*). Cero updates necesarios en cascades.
**Test fixtures decoupling**:
- Nuevo `src/uix/morfo/test-fixtures.ts` con `prewriteFixtureMorfo` sintético.
- `compile.test.ts`: migrado de `colorPickerMorfo` → `prewriteFixtureMorfo`.
- `runtime.svelte.test.ts`: igual migración.
- Beneficio: los tests validan el contrato del compiler/runtime sin depender del catálogo de componentes. Futuros refactors del catálogo no rompen estos tests.
**Estado final del rollout polymorphic** (3 sprints combinados):
| Componente | Polymorphic morfo | Provider cabledado |
|---|---|---|
| Dialog | ✓ | ✓ (`dismissWith` → `runtime.trigger`) |
| Drawer | ✓ | ✓ |
| Popover | ✓ | ✓ |
| color-picker | ✓ | ✗ (toggle opts.open) |
| date-picker | ✓ | ✗ |
| date-range-picker | ✓ | ✗ |
| time-picker | ✓ | ✗ |
| time-range-picker | ✓ | ✗ |
Único morfo con shape pre-polymorphic restante: el fixture sintético `prewriteFixtureMorfo` — vivo sólo para tests.
**Tests**: 680/680 pass. `npm run check`: 1 error pre-existente.
**Pendiente a futuro**:
- Cabledar `runtime.trigger('close', { semantic })` en los providers picker cuando justifique disparar el evento (telemetría, sound on commit picker, etc.).
## Session hand-off — 2026-05-27 #5 (TSC v2.2 + cobertura universal de theming)
Cierra la migración universal del Token Scope Contract. El usuario rechazó el framing "excepciones arquitectónicas" para los 3 componentes que vivían fuera de TSC v2.1 (`select`, `avatar`, `toggle-group`) — la respuesta fue extender TSC con dos features nuevas (`parts` + `composition`) y migrar los 3.
**Extensiones TSC v2.2** (`src/uix/eidos/lib/config-types.ts` + `lib/render-css.ts` + `lib/config.ts`):
- **Multi-part scope** — `RecipeTokenMultiDeclaration.parts?: readonly string[]`. Cuando un token tiene `parts: ['x', 'y']` el generador emite selectores comma-separados (`[data-{c}-x][...], [data-{c}-y][...]`) en lugar del default `[data-{c}][...]`. Cubre el caso del select donde `data-color` vive en Trigger + Content (Content portaliza fuera del árbol del Trigger). Único consumer hoy.
- **Cross-recipe composition** — nuevo sibling field `composition?: { foreignComponent: { targetSelector, tokens } }`. Permite que un recipe declare overrides de tokens de OTRO recipe scoped a su propia cascade, con un selector de descendant para alcanzar la part foránea. Genera `{host-scope} {targetSelector} { --{foreign}-{tokenName}: ... }`. Validator rechaza root-scoped composition (un override no-scoped pertenece al foreign recipe). Único consumer hoy: `toggle-group` modifica `--toggle-palette-*` en sus items.
Validador (`config.ts`) extendido con `validateRecipeComposition`. Generator (`render-css.ts`) gana `stripCompositionKey` + `emitComposition`. Contract builder (`contract.ts`) skip-list de la reserved key `composition` para que no aparezca como `--{c}-composition` knob en el contrato público. Test helpers (`tokenKeys`/`tokenEntries` en `recipe-css-contract.test.ts`) filtran `composition` en todos los iteradores.
**Migraciones de los 3 componentes restantes**:
- `select`: 4 → 3 private `_accent-*` con `parts: ['trigger', 'content']`. (`_accent-solid` no se migró porque la CSS nunca lo consumía — era orphan; se removió de la recipe.)
- `avatar`: 6 tokens (`_bg`/`_fg`/`_border` + `_badge-bg`/`_badge-fg`/`_badge-border`) con declarations[] generadas por un helper inline `matrix()` que recorre los 8 colores × 3 variants. `_badge-*` usa `parts: ['badge']` para retargetear a `[data-avatar-badge]`. Reemplaza ~150 declarations CSS por las mismas declarations, generadas desde TS.
- `toggle-group`: bloque `composition: { toggle: { targetSelector: '[data-toggle-group-item]', tokens: { 'palette-*': {...} } } }` con 8 palette tokens × 4 colors. Reemplaza los 4 bloques CSS per-color.
**Bug post-migración del toggle-group (toggle-group color cascade)** — encontrado y arreglado:
La composition correctamente override `--toggle-palette-*` en `[data-toggle-group-item]`, pero la cascade se rompía aguas abajo. Después de la migración TSC v2 original de toggle, los tokens DERIVADOS (`--toggle-solid-on-bg`, `--toggle-outline-fg`, etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es SIBLING (no descendant) de `[data-toggle]`, así que `var(--toggle-solid-on-bg)` resolvía a undefined en el item.
Fix (en `toggle-group.css`): **inlined las derivation expressions** que leen palette directamente en `[data-toggle-group-item]` y sus variant cascades. Las expresiones reflejan las de `recipes/base.ts > toggle.{solid,outline,ghost}-*`. Duplicación documentada en el header. Alternativa estructural (hacer el item carrier de `[data-toggle]` + propagar `data-color` morfo/soma) no se aplicó — change too big.
**Documentación**:
- `THEMING.md` §18 reescrito como "Cobertura universal de TSC" (sin excepciones). §7 extendido con subsecciones "Multi-part scope" y "Cross-recipe composition" con ejemplos completos. TOC actualizado.
- `eidos/README.md` tabla de referencia ampliada con entradas para TSC v2.2 + §18.
**Tests**: 786/786 pass en `src/uix/{eidos,morfo,soma,sema}`. `npm run check`: los mismos 6 errores pre-existentes en `lib/_demo`/`soma/components/internal`/`web/routes/active` (no relacionados).
**Pendiente a futuro**:
- Si emergen patrones similares al toggle-group (otros wrappers compositivos como button-group, nav-menu), la composition TSC v2.2 los cubre — no requiere más extensiones.
- Los `inlined derivation expressions` en `toggle-group.css` son la única duplicación entre recipes/base.ts y CSS. Si Toggle's derivations cambian, hay que actualizar ambos.
## Session hand-off — 2026-05-27 #6 (variants canon — `EIDOS_VARIANTS` + lint)
Pregunta arquitectónica del usuario: "si activeUIX quiere ser referencia como framework, ¿qué es lo lógicamente coherente respecto a la extensibilidad de variants?". Respuesta firme: **variants son canon del eidos, NO del theme** — paralelo a las 8 sema families del libro.
**Cambios**:
- `src/uix/eidos/lib/types.ts`: nueva constante `EIDOS_VARIANTS` (5 archetypes: `control`/`selection`/`chip`/`marker`/`tabs`) como single source of truth. Los 5 union types se derivan via `[number]` indexed access — valor y tipo no pueden desincronizarse. Nueva constante `EIDOS_VARIANT_VALUES` (Set flat de todos los valores canónicos + utilidades cross-component como `'plain'` y `'subtle'`).
- `src/uix/eidos/recipe-css-contract.test.ts`: nuevo test "variant CSS selectors per component match the declared type union". Por cada componente: extrae el union type de `components/{c}/types.ts` via regex (literal-union + archetype-alias patterns soportados, Extract<>/conditional types caen a advisory mode); compara con los `[data-{c}][data-variant='X']` selectores en `{c}.css`; reporta typos y unauthorized extensions bidireccionalmente. 101/101 tests pasan.
- `src/uix/eidos/THEMING.md` §19: nueva sección "Variants son canon del eidos, NO del theme" con argumentación (portabilidad, type safety, archetypes perceptuales), tabla de las 3 capas de la cebolla (sema → variants → palette), referencia a `EIDOS_VARIANTS`, comparación con Radix Themes/Mantine/Chakra v3/Ark UI/shadcn. TOC actualizado.
- `src/uix/eidos/README.md`: tabla de referencia ampliada con §19.
**Doctrina sostenida**: Theme = retintar lo perceptualmente fijo. Cambia QUÉ color es `affirm`, no QUÉ significa `outline`. Si una app necesita un look brandeado, hace override de tokens en `EidosConfig.recipes` o crea un wrapper composicional — NO inventa un nuevo variant.
**Variants component-specific permitidos** (Banner `inline`/`overlay`/`persistent`, Spinner `bars`/`dots`/`ring`, Button `'plain'`): viven en cada `components/{c}/types.ts` y el lint los valida contra la CSS del componente.
**Tests**: 101/101 pass en `src/uix/eidos`. `npm run check`: los mismos 6 errores pre-existentes (lib/_demo, soma/components/internal, web/routes/active).
## Session hand-off — 2026-05-28 (toggle-group structural identity)
Cierra la deuda dejada explícitamente abierta en el hand-off 2026-05-27 #5 ("Los `inlined derivation expressions` en `toggle-group.css` son la única duplicación entre recipes/base.ts y CSS. Si Toggle's derivations cambian, hay que actualizar ambos."). Ahora son cero.
**Pregunta arquitectónica del usuario** tras verificar el fix EXT-FIX (color cascade): "¿qué es lo recomendado en referencia al ecosistema y su diseño y arquitectura?". Respuesta firme: **resolver la duplicación estructuralmente, no con un test de drift**. El item de toggle-group ES un toggle (mismo press, mismo variant/color/size, misma máquina on/off) — la doctrina "morfo declara DNA" pide declararlo.
**Cambios**:
- `src/uix/morfo/components/toggle-group.ts`: el part `Item` declara `{ attr: 'data-toggle', value: v.literal(''), severity: 'required' }`. Cada item proyecta `data-toggle=""` como atributo de presencia. Cero overhead, captura la identidad estructural.
- `src/uix/eidos/components/toggle-group/context.ts` (nuevo): contexto Svelte tipado (`ToggleGroupEidosCtx` con getters reactivos para `variant` y `size`) — propaga las dos perillas eidos-only del root a cada item.
- `src/uix/eidos/components/toggle-group/toggle-group.svelte`: setea el contexto en el root. Getters mantienen reactividad cuando el prop cambia.
- `src/uix/eidos/components/toggle-group/toggle-group-item.svelte`: lee el contexto y escribe `data-variant={ctx?.variant}` + `data-size={ctx?.size}` en el button. Combinado con `data-toggle` del morfo, el elemento es DOM-equivalente a un `<Toggle>` standalone.
- `src/uix/eidos/components/toggle-group/toggle-group.css`: borradas ~150 líneas (todas las derivaciones base + variant cascade + size cascade + focus-visible / disabled / icon-only duplicados). El CSS conserva SOLO grouping concerns (flex layout, orientation, attached con first/last/border-radius, block, focus z-index, group-level disabled). El header comment se rescribe documentando la nueva división de responsabilidades.
- `src/uix/eidos/components/toggle-group/README.md`: sección "Recipe" + "Decisiones" actualizadas — la entrada "Structural identity" documenta el patrón.
**Por qué TSC v2.2 composition se queda en la mezcla**: la composition sigue siendo necesaria para overridear `--toggle-palette-*` en el item bajo `[data-toggle-group][data-color='X'] [data-toggle-group-item]`. El color NO se propaga via contexto porque la composition ya hace el trabajo a nivel CSS, sin overhead reactivo. Variant/size sí se propagan porque tienen muchos derivados (height, padding, gap, font, radius × 5 sizes; bg, fg, border, hover, on, on-hover × 3 variants) que solo Toggle's recipe ya emite — no había ningún beneficio en mantener cascadas paralelas.
**Verificación**: probe DOM en `/uix/components/toggle-group` capturando computed values en las 12 combinaciones (4 colores × 3 variants) × 2 estados (on/off) — match bit-a-bit con baseline pre-refactor. Ejemplos:
- `affirm/solid` on: `rgb(18,165,148)` = `#12a594` (palette-solid affirm)
- `risk/outline` on: `color(srgb 0.2 0.118 0.043)` ≈ `#331e0b` (color-mix outline on-bg)
- `threat/ghost` on: `rgb(25,17,17)` = `#191111` (palette-track threat)
**Tests**: 163/163 pass en `src/uix/morfo` + `src/uix/eidos`. 4/4 pass en `src/uix/soma/components/toggle-group`. `npm run check`: 16 errores pre-existentes (los mismos del sprint Words), CERO añadidos por esta migración.
**Coste real vs estimado**: el hand-off #5 estimó el cambio como "too big" — incorrecto. Total = 1 entrada en morfo + 1 archivo de contexto (~30 líneas) + 2 ediciones puntuales en wrappers (~5 líneas cada) + 1 rewrite de CSS reduciendo ~150 líneas a ~85. La parte engañosa era pensar que requería tocar Toggle's CSS — no lo hace.
**Doctrina reforzada**: cuando un wrapper componente reusa visualmente otro, la respuesta canónica NO es duplicar el cascade ni extender TSC con un tercer feature. Es declarar la identidad estructural en el morfo del wrapper. Patrón aplicable si emerge button-group, link-group, etc.
## Session hand-off — 2026-05-28 #2 (Words editor — 6 sprints UX)
Segunda mitad de la sesión (2026-05-28) dedicada a fixes del editor Words por feedback iterativo del usuario. Seis sprints EV-* + cleanup del check.
**Cleanup pre-sprint — `npm run check` 16 → 0**:
- 4 errores `orientation` drift en `soma/components/words-toolbar*` (deuda mía del sprint anterior — removí orientation del type pero no del soma component). Quitado el prop + create() call.
- 12 errores en el sprint Words activo (post-2026-05 F2/F3/COLOR): `onUploadImage` threading en soma `Words.Provider`, `'insert-image'` añadido a 2 Records `WordsToolbarButtonCommandName`, `leafItem` snippet hoisted FUERA de `<SomaWords.Provider>` (snippets dentro de un component element son props en Svelte 5; el snippet era helper local), 5 arrays inline en `words-drawer.svelte` extraídos a constantes typed `as const satisfies readonly { id: WordsCommandName | WordsMark; ... }[]`.
**EV-A — spam de eventos del canvas**:
- `isInsideWordsTool` extendido a los 5 overlays añadidos post-DRAWER: `data-words-drawer`, `data-words-block-handle`, `data-words-block-handle-menu`, `data-words-block-inserter`, `data-words-image-float-bar`. Antes, cualquier click sobre estos overlays se interpretaba como blur EXTERNO → fire `commit-save-content` + re-focus → fire `contact-focus` = 2 sonidos por interacción.
- `contact-focus` target movido de `content` a `provider` en morfo + sema cascade (doctrina: focus es evento de componente, no de body).
**EV-B — toolbar slim**:
- Presets demo (`minimal`/`formatting`/`full` + custom) reducidos a acciones GLOBALES: history (undo/redo) + insert + link + tools + find-replace. Text/block/list/align/table OUT porque el drawer ya los cubre por scope.
**EV-C — drag handle UX**:
- Borrado `e.dataTransfer.setDragImage(hoverBlockEl, 12, 12)`. El browser usa su snapshot por defecto (= el grip button) como ghost — el ghost viaja con el cursor mientras el bar en la gutter queda fijo como ancla visual. Nuevo `data-dragging` + CSS fade del ancla a 0.35 opacity.
**EV-D — inserter al borde inferior**:
- `seam.y` para seams entre bloques cambia de `(a.bottom + b.top) / 2` (midpoint) a `a.bottom` (borde inferior del bloque anterior). Half-open interval `[top, bottom)` para que el píxel exacto del bottom pertenezca al seam, no al bloque.
**EV-E — scroll interno del content**:
- Nuevo token `content-max-block-size-sm/md/lg` (50/60/70vh) en el recipe. `[data-words-content]` gana `max-block-size: var(--_words-content-max-block-size)` + `overflow-y: auto`. El min-block-size baseline queda como starting height para editores vacíos.
- block-handle / block-inserter / image-float-bar: scroll listeners migrados de `window.addEventListener('scroll')` a `document.addEventListener('scroll', { capture: true })`. Razón: scroll events NO burbujean — la versión anterior solo captaba scroll del root document; con la rail/handle/inserter ahora dentro de un content scrollable, había que capturar también scroll DENTRO del content para que las overlays se re-midieran.
**EV-F — paquete de 6 fixes en uno**:
1. Cascade sema de `contact-focus` REMOVIDA → canvas mudo en focus (EV-A fixaba solo el target; la cascade seguía sonando).
2. Rail bg → flat silver (`#d4d4d4`) + borde derecho `#9a9a9a`, sin dot pattern. Tokens `words.rail-bg / rail-border` en el recipe. **Fixed-tone, NOT theme-aware** — la intención es emular un margen físico de cuaderno, debe verse igual en dark/light theme.
3. Toolbar: `undo` + `redo` DIRECTOS (no popover); `insert-menu` eliminado. Resultado: 5 items.
4. Family-panel base 12.5 → 16rem · tools 17rem · link 20rem. Sin scroll horizontal.
5. Link popover sin scroll vertical (consecuencia del #4).
6. Engine `insertParagraph` con guarda explícita: paragraph vacío + Enter = no-op; heading vacío + Enter = demote a paragraph (canonical Notion UX).
**EV-G — último pass (parcial)**:
- `runtime.trigger('contact-focus')` comentado en soma. Causa real del sonido residual: la family `contact` en `SEMA_MAP` tiene BASE signature de sound (pitch 800, gain 0.25) que suena aunque no haya cascade per-component. Solución: no disparar el evento. Telemetry vacía para focus, aceptado.
- Drag handle + inserter ahora cubren `data-words-node="list"` y `data-words-node="table"`, no solo `"block"`. Los selectores en `findBlockElement`, `blockUnderCursorY`, `listBlockBoundaries` aceptan los tres tipos para top-level blocks. Listas y tablas tienen drag handle + inserter seam.
- `:hover` rules eliminadas en `[data-words-block-inserter-button]` y `[data-words-block-handle]`. Las overlays son AMBIENT — estado visual cambia solo en data attrs (`data-open`, `data-grabbed`, `data-dragging`). No se iluminan en hover.
**Decisiones arquitectónicas con efecto duradero**:
- **Canvas perceptualmente silencioso** (Words): `contact-focus` no se emite. Si en el futuro un consumidor necesita el evento, descomentar el `runtime.trigger` en `words-provider.svelte.ts > onfocus`.
- **Tokens FIXED-TONE para concepto físico** (Words rail): `--words-rail-bg/border` son hex literal en el recipe, no `var(--color-*)`. Justificación: el concepto visual es "margen de papel" — debe verse igual en todos los themes.
- **Drag-handle predicate cubre 3 tipos de node**: `block`/`list`/`table`. No solo `block`. Cualquier nuevo top-level node type tiene que entrar en la lista.
- **Overlays ambient, no interactive buttons**: gutter overlays (block-handle, block-inserter button) no tienen `:hover`. Estado visual via data attrs solamente.
- **Engine guard sobre bloque vacío**: `insertParagraph` ya no duplica párrafos vacíos. Convierte heading→paragraph en vacío.
**Pendientes documentados en CONTINUE.md**:
- P1 — Drag handle aparece FUERA del rail sobre code blocks (no diagnosticado).
- P1 — Heading inline level change (h1↔h2↔h3) — la UI no permite cambiar nivel inline; el drawer Block panel lo tendría que ofrecer cuando `currentBlock === 'heading'`.
- POLISH-1b / POLISH-3 / WORDS-F2.3-F2.6 / WORDS-DRAGDROP-SOMA — heredados.
**Tests al cierre**: 467/467 pass en morfo + sema + eidos + soma/components/words. 85/85 engine pass. `npm run check`: **0 errors**.
## Session hand-off — 2026-05-09 (selector discipline + lint reframing)
- **Typed selector builder applied** in
`src/uix/sema/components/dialog.ts`. Every cascade rule now constructs
its selector via `semaSelector(dialogMorfo, 'content', matchers?)`
instead of hand-writing strings. The helper lives in
`src/uix/morfo/selectors.ts` and is exported from `$uix/morfo`.
- **Eidos-lint reframed** from "architectural drift mechanism" to
"opt-in safety net for plain CSS recipes". The architectural mechanism
is compile-time typing. The lint script + scripts continue to work
unchanged but are no longer the doctrinal source of safety.
- **Intent.deltas signature ownership documented** — cascade rules MUST
NOT override `pitch` / `gain` / `contour` on `sound`, because doing so
collapses the per-intent perceptual difference. Captured both inline
in the dialog cascade (NOTE comment on the intent block) and in the
"Override semantics — numbers" rule above.
- **Documentation updated**:
- `src/uix/morfo/README.md` — new "Typed selector builder —
`semaSelector`" section.
- `src/uix/eidos/README.md` — replaced "Linter" section with
"Defensa contra drift de selectores" framing the two-tier defense
(compile-time builder for TS/Svelte, opt-in lint for plain CSS).
- `src/uix/sema/README.md` — already documented the builder + the
cascade discipline + the SEMA_FAMILY_POLICY shape (from the prior
refactor).
**Verification at hand-off**: `npm run check` reports 0 errors, 2
pre-existing Svelte warnings unrelated to this work. `npm run test`
reports 1883/1883 tests green across 154 files (~22s).
## Session hand-off — 2026-05-08 (post-canon alignment)
The codebase has been aligned to the canonical guide. Most recent
architectural move:
- **Runtime relocated + renamed** — what was `MorfoRuntime` in
`src/uix/morfo/runtime.svelte.ts` is now `SomaRuntime` in
`src/uix/soma/runtime.svelte.ts`. Reason: morfo must remain pure
declarative TypeScript (DNA); the runtime imports Svelte runes,
`$adom`, `$uix/sema`, and `$libs/reactive` — those are soma's
territory. Per the doctrine "morfo declares, soma executes" the
runtime now lives where it conceptually belongs. Active-uix's
`uix.runtime(morfo, sources)` factory stays unchanged; it imports
`createSomaRuntime` from `$soma`.
- **Sema is now genuinely ornamental** — `ActiveUix.semantic` returns
`EngineSemantic | undefined` (no longer throws). `SomaRuntime.trigger()`
skips the `emit` step and the target-resolution check when no engine
is present. Components stay functional in SSR / headless tests /
audio-disabled environments.
The earlier swept phases (still in effect):
- **Sema verbs** (`src/uix/sema/verbs.ts`): restructured to
family-keyed `Record`. `select`/`toggle`/`acknowledge` moved to
`commit`; `edit` removed from `handle` (now expressed as
`shift.enter-mode`). New verbs added per the canon (tap, focus,
release, complete, restore, remind, route, step, syncing, etc.).
`validateEventName` recognises both `{verb}-{variant}` and
`{family}-{verb}` shapes.
- **Morfo event shape** (`src/uix/morfo/types.ts`): `target` moved
inside `semantic`; added optional `verb` (advisory, validated against
`SEMA_VERBS[family]`) and `sequence: 'pre' | 'coincident' | 'post'`.
All 22 events across toggle/toast/popover/drawer/dialog refactored
to the new shape.
- **Color tokens** (`src/uix/eidos/themes/base/{light,dark}.css`):
doctrinal 8 tokens — `primary, secondary, neutral, affirm, fulfill,
risk, threat, loss`. Renamed `success → fulfill`, `warning → risk`,
`danger → threat` (same hex). New primitives for `secondary`
(slate-blue), `affirm` (teal-mint, low activation), `loss` (deep
violet-grave, posterior). `info` palette deleted entirely. Legacy
aliases in `_static.css` deleted (clean cut, no transition).
29 consumers (recipe CSS + tokens) migrated to doctrinal names.
Earlier in the session:
- **Toggle is the eidos pilot** (`src/uix/eidos/components/toggle/`):
recipe + Svelte wrapper + types + index + README. The pattern is
documented in `src/uix/eidos/components/README.md`.
- **Eidos has a shared lib** at `src/uix/eidos/lib/types.ts`. First
type is `Size` (`'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full'`).
Components narrow with `Extract<Size, 'sm' | 'md' | 'lg'>` per the
per-component-subset doctrine.
- **No `Eidos` prefix on types**. The path
`$uix/eidos/components/{x}` already conveys layer. Public types are
`ToggleProps`, `ToggleVariant`, `ToggleSize`.
- **API doctrine**: soma stays compound (`Toggle.Provider`); eidos
exports both `default` and `Provider` for single-part components so
`<Toggle>` works for the ergonomic case while `<Toggle.Provider>`
stays available.
- **SoundChannel eager-init landed** in `src/uix/sema/chans/sound.ts`
to fix the autoplay race: the AudioContext is created + resumed
synchronously on the first user gesture (capture-phase listener on
`document`).
- **Demo page (`web/routes/toggle/+page.svelte`)** has the live
preview rendered ALWAYS above the tablist (so Sema-tab Play buttons
can fire on the real toggle) and the motion preview amplifies scale
×8 visually only — doctrinal values stay in the `<dl>`.
**Mandatory checklist before any eidos component migration**:
1. Recover the air baseline from git:
```
git ls-tree -r 0a391408^ --name-only | Select-String "air.*{name}"
git show 0a391408^:src/uix/air/components/{name}/types.ts
git show 0a391408^:src/uix/air/components/{name}/{name}.svelte
```
2. Inventory air's props, slots, effects, perceptual emits.
3. Build a comparison table: feature × (air had it | canon allows it |
eidos exposes it). Flag each gap as `regression`, `enhanced`,
`dropped-by-canon`, or `nuevo`.
4. Present the table BEFORE coding. Get explicit per-gap decision.
5. Only then write the wrapper.
Switch was migrated WITHOUT this discipline and shipped a regression
(no `ResponsiveProp<Size>` for breakpoint-aware sizing). Don't repeat.
Next concrete steps:
1. Migrate `collapsible → dialog → drawer → popover → toast → avatar`
to the eidos wrapper pattern. **For each: walk the air-baseline
checklist above first.** Toggle / switch are templates for the
subdirectory shape, but the per-component prop surface must come
from air's recovery, not from copying toggle's props.
2. **Persistence + holds-by-intent** (guide §6) — defer until the
first concrete `signal.warn` / `signal.alert` consumer appears.
Today every signal is `transient` with a numeric hold. Need to add
`persistence: 'transient' | 'untilAction' | 'untilFix' | 'stateBound'`
to `SemanticSignal` + the holds-by-intent table per the canon.
3. **a11ySemantic per event** (guide §9) — defer until there's a
reduced-motion runtime helper + live-region helper to consume the
contract.
4. **Polymorphic events** (guide §5.3, `allowedFamilies` +
`defaultSemantic`) — defer; no current component needs polymorphism.
5. The docs site's topbar prefs (sound mute toggle, theme switcher)
are wired up but the sound mute does NOT yet drive
`SoundChannel.masterGain`. Wiring belongs on layout.svelte's `$effect`.
Baseline `npm run check` shows 39 pre-existing errors in test files
unrelated to this work. Tests are 217/217 green across uix.
## Session hand-off — 2026-05-26 (sema canon + soma emission + READMEs)
Sprint completing the alignment of the project with the book *Diseñando lo que ocurre*. Nine commits closed P1 #3, P1 #4 and P1 #5 of the audit-codex:
```
504780ad expression field + sema coverage check
2d562f37 11 sema packs + doctrina samples (D.5/D.6/D.7)
826ca2bf D.8 channels scope doctrine
43b15372 picker-shell INTERNAL (P1 #5 closed)
66318c47 tree-view target → branch for emerge events
adf89a08 batch A — soma emission for 7 packs-shipped components
0784cae5 batch B — 8 components with sema scope, no pack
cbf76b66 batch C — drag-drop + virtual-list/grid
7afa7057 23 component READMEs with Sema events sections
```
**Architectural deltas from this sprint:**
- **`Morfo.expression`** new field (`'pack' | 'family-default' | 'delegated' | 'none'`) declares how each morfo with declared events materialises its perceptual signature. Schema-validated; lint enforces coverage. See `src/uix/morfo/types.ts` + `LIBRO_VARIACIONES_Y_EXTENSIONES.md` D.4.
- **`SemaFamily` is now 8** (added `delegate` per book cap. 29). `SEMA_FAMILY_POLICY` carries two axes (`intentRequirement` + `intentGuidance`) — see updated section above.
- **`SEMA_VERBS`** extended with `signal.inform`, `commit.unselect`, contextual verbs (`apply`, `partial`, `block`, `move`, `upload`, `acknowledge`, `confirm`).
- **38 sema packs** under `src/uix/sema/components/*.ts` compose 9 canonical tuning profiles (`form.commit.soft/subtle`, `form.toggle.silent`, `tooltip.silent`, `emerge.soft`, `emerge.exit/.soft`, `emerge.medium`, `emerge.dismiss.passive`, `tabs.select.soft`). Apps override at `defineEngineSemantic({ overrides: { cascade: [...] } })`.
- **Soma emission cabled** in 18 components (toggle-group, menubar, navigation-menu, dropdown-menu, context-menu, tree-view, tree-grid, listbox, grid-list, table, feed, command, carousel, announce, clipboard, drag-drop, virtual-list, virtual-grid). Pattern: emit from the central state-mutator with `fallbackTarget` resolving the specific element so the cascade matches the right instance.
- **`handle-scroll*` intentionally NOT emitted** in virtual-list / virtual-grid — family.handle activates haptic only; emitting on every pixel of scroll would buzz the device nonstop. Apps that want scroll-feedback wire their own throttled emit. Documented in both READMEs.
- **picker-shell relocated** from `src/uix/morfo/components/` to `src/uix/morfo/internal/` — declares its INTERNAL status by file location. Apps never write `<PickerShell>`; the five composite pickers (date / date-range / time / time-range / color) re-export the shell parts under their own namespace. README added at `src/uix/eidos/components/picker-shell/README.md`.
- **`LIBRO_VARIACIONES_Y_EXTENSIONES.md`** is the authoritative registry for project decisions vs the book canon. Sections D.4 (`expression` field), D.5 (toggles soft-tuned packs), D.6 (menus/trees packs), D.7 (samples doctrine — `SOUND_LIBRARY` = resources, `SOUND_TUNINGS` = canon, packs compose tunings never samples; `sampleOverlay` rejected permanently), D.8 (channels scope — `sound` + `haptic` are the only canonical runtime channels; ARIA structural lives in morfo, ARIA dynamic in soma, visual in eidos — never canonized as channels).
- **23 soma component READMEs** now include a `## Sema events` table with event / family / verb / target / intent / when + pack reference + doctrinal notes for the verb-rename corrections applied across the sprint.
**Verification at hand-off**: `npm run morfo:vocabulary` EXIT 0 (clean). `npx vitest run src/uix/sema src/uix/morfo` 195/195. `npx vitest run src/uix/soma` 419/419. Pre-existing words/* type errors unchanged (separate dev track).
**What's left from the audit-codex**: P2 (34 demos NEEDS-WORK) — explicitly out of scope (full refactor of demos + web routes pending separately).
---
# Behavioral Guidelines
These reduce common LLM coding mistakes. They bias toward caution over speed —
for trivial tasks, use judgment.
## 1. Think before coding
Don't assume. Don't hide confusion. Surface tradeoffs.
- State assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them — don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity first
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
## 3. Surgical changes
Touch only what you must. Clean up only your own mess.
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it — don't delete it.
When your changes create orphans, remove the imports / variables / functions
your changes made unused. Don't remove pre-existing dead code unless asked.
## 4. Goal-driven execution
Define success criteria. Loop until verified.
- "Add validation" → "Write tests for invalid inputs, then make them pass."
- "Fix the bug" → "Write a test that reproduces it, then make it pass."
- "Refactor X" → "Ensure tests pass before and after."
Strong success criteria let you loop independently. Weak criteria require
constant clarification.

@ -0,0 +1,97 @@
# Continuation — Floating-gap canon + menu focus-ring (2026-06-28)
Where we left off. Spun out of the eidos inherit/radius audit; converged on
canonizing the trigger→panel gap of menus and killing the keyboard focus-ring
that framed the whole menu panel.
## ✅ RESOLVED 2026-06-29
The canon-read fix WORKS — verified live: **menubar** (no `sideOffset`) and
**dropdown** show the **4px** canonical gap on real interaction (the `$derived`
returns 4 → Floating UI offset 4). The keyboard focus-ring fix was already live.
Menu demos (dropdown / context / select) cleaned to follow the canon — commit
`9c48164f`. The canon-read + token are in commit `61ea97b5`.
**Minor known artifact (demo-only, NOT a real-world bug):** the dropdown demo
opens on mount (`open: on`), so Floating UI positions the panel against the
trigger's TRANSIENT initial-layout rect and doesn't re-measure on the settle
(autoUpdate watches scroll/resize, not arbitrary layout shifts) → it shows ~36px
until you close+reopen. Real menus open on a user click (settled layout) → 4px.
To make the demo pixel-perfect, give Floating UI a layout-shift re-measure on open.
The original (now historical) handoff follows.
## ✅ DONE + VERIFIED LIVE (CSS — HMR is reliable)
1. **Archetype `:where()` systemic fix** (`src/uix/eidos/archetypes.css`) — every
DEFAULT archetype rule wrapped in `:where()` (specificity 0,0,0) so any component
recipe (0,1,0) wins regardless of load order. Kept at full specificity ON PURPOSE:
the `:has([data-archetype='field-trigger'])` padding override + the canonical item
highlight.
2. **Overscroll containment** — `overscroll-behavior: contain` added to the shared
ScrollArea viewport (`scroll-area.css`) + 7 overlay scroll regions (context-menu,
dialog, drawer, dropdown-menu, float-panel, popover, tooltip). Fixes scroll chaining
to the `<body>`. Verified on select + dropdown.
3. **Focus-ring fix** (`archetypes.css`) — the universal `[data-archetype]:focus-visible`
box-shadow ring now EXCLUDES `item`, `option`, AND `content`:
- Menu/listbox ROWS keyboard-focus via roving tabindex → drew the thick ring on
keyboard but only a subtle bg on hover (inconsistent). Now rows use the canonical
highlight for BOTH `:hover` AND `:focus-visible`.
- Menu/popover/dialog PANELS (`data-archetype='content'`) take focus for key capture
→ drew the ring around the WHOLE float (the thick purple frame the user hated). Now
excluded; the panel's own elevation (border + shadow) is the boundary.
- **Verified live**: loaded rule tail = `…:not([item]):not([option]):not([content])`;
highlight selector includes `:focus-visible`.
## ⏳ DONE but NEEDS A FRESH-LOAD CHECK (JS — the tab cached the old module)
4. **`--floating-gap-menu` token**: `0px → var(--space-1)` (4px) in `render-css.ts` +
regenerated `generated/base.css`. Menus get a small gap now, not flush.
5. **nav-menu wired to the canon** (`navigation-menu.css`) — both orientations now
`calc(100% + var(--floating-gap, 0px))` (was hardcoded `--space-1`). nav-menu is
CSS-positioned (not portaled) so it consumes the canon through CSS. **Verified 4px
early, before the cache trouble — reliable.**
6. **Canon-read FIX** (`src/uix/soma/layers/floating/floating.svelte.ts`) — THE fix:
- `FloatingContent.canonicalGapPx` was a `$state` filled by a one-shot rAF
(`requestFrame`) inside a `watch(contentRef)`. For PORTALED menus that rAF callback
**never fired** (instrumented: watch passes the guard, frame scheduled, never run,
not cancelled — `requestFrame`/`resolveWindow` at `arts/adom/active-dom.svelte.ts:481`
suspected). So `canonicalGapPx` stayed `null` → menus fell back to `sideOffset`
(flush). The token change alone never reached them.
- **Fix**: `canonicalGapPx` is now a `$derived` reading `--floating-gap` off
`contentRef.current` directly (reactive, no rAF). The rAF keeps only the zIndex read.
`--floating-gap` is a style value (no layout) so reading on mount is safe.
- **Type-clean** (svelte-check passed) but **NOT visually verified** — see CACHE below.
## ⚠️ CACHE GOTCHA — READ FIRST (it ate most of the session)
- Editing a deeply-imported `.svelte.ts` (e.g. `floating.svelte.ts`) does NOT reliably
HMR, AND the connected tab serves that module from a STRONG cache. `navigate` and a
`?nocache=` query do NOT revalidate the module (it's fetched by path).
- **Tell-tale**: a `gap: 12` from a hardcode I'd reverted long ago persisted through
edits + a full dev-server restart → the tab was running a frozen old module.
- **Detect stale**: temporarily set `mainAxis: 12` in the offset middleware → if the gap
isn't 12, the code is fresh.
- **Only reliable bust**: browser HARD reload (Ctrl+Shift+R). Not `navigate`.
- CSS (`archetypes.css`, recipes) DOES hot-update fine — only the `.svelte.ts` module is
the problem.
## ▶️ FIRST ACTIONS TOMORROW
1. **Hard-reload** (Ctrl+Shift+R) the menubar/dropdown page.
2. Confirm the canon-read fix: **menubar** (passes no `sideOffset`) should now show a
~4px gap (no longer pegado). If still flush → re-instrument; you're guaranteed fresh.
3. **Clean the menu demos** so they follow the canon (now that the read works). These
pass explicit `sideOffset` that BYPASSES the canon:
- `web/routes/uix/components/dropdown-menu/+page.svelte` → `sideOffset = $state(4)`
- `web/routes/uix/components/context-menu/+page.svelte` → `sideOffset = $state(0)`
- `web/routes/uix/components/select/+page.svelte` → hardcoded `<Select.Content sideOffset={6}>`
Default each to `undefined` so the token retunes all. Slider pattern to avoid the
`number|undefined` bind error: `value={sideOffset ?? 0} oninput={(e) => (sideOffset = e.currentTarget.valueAsNumber)}` + label `{sideOffset === undefined ? 'auto' : sideOffset + 'px'}`.
4. If the canon-read fix DIDN'T take on fresh load, isolate why `requestFrame`'s rAF
never fires for portaled menu content — but the `$derived` should sidestep it entirely.
## Dev server
Restarted fresh on 5199: `npm run dev -- --port 5199 --strictPort` (the bare `dev`
script uses the default port, so pass the flags).

@ -0,0 +1,65 @@
# CONTINUE — hand-off (2026-06-26 → next session)
Branch `active-uix`. This session's work is **committed + pushed**. Pick up the
**DateTimePicker refactor** next.
## Next task — shared "time-in-popover" for DatePicker + DateRangePicker
Full plan: `C:\Users\dev\.claude\plans\woolly-knitting-fairy.md` (approved).
Decisions locked with the user: composable `<DatePicker.Time />` part (not
auto-rendered) · DateRangePicker shows **start + end** (two time pickers).
Goal: lift the ad-hoc time editor out of the DatePicker **demo** into a
**shared, reusable mechanism**, then give DateRangePicker the same. DatePicker
becomes effectively a DateTimePicker (granularity reaching `hour|minute|second`
surfaces the time editor); the **hour cycle derives from `locale`** when
`hourCycle` is unset (en-US → 12h, es-ES → 24h), for input **and** time editor.
Both soma providers already plumb `granularity` + `hourCycle`; the missing piece
is the popover time editor (today hand-composed in the date-picker demo).
Execution order (see plan §1–6):
1. `picker-shell/picker-time.ts` (new) — pure helpers: `localeHourCycle`,
`granularityHasTime`, `timeOf`, `withTimeOn`, `withoutTime` (reuse `$libs/days`).
2. `picker-shell/picker-time-row.svelte` (new) — value-agnostic time row (knows
only `TimeValue`); self-hides when granularity has no time; composes the
`<TimePicker>` namespace; owns the relocated time-row CSS.
3. Both `*-picker.svelte` roots derive `hourCycle ?? localeHourCycle(locale)` →
pass to the soma Provider (input honours it); publish a bridge context
(value get/set + granularity/locale/resolved hourCycle).
4. `.Time` parts: `date-picker-time.svelte` (one row) + `date-range-picker-time.svelte`
(two rows, start/end); register in each `index.ts` + `types.ts`.
5. langs: `picker-shell.ts` keys `time` / `start` / `end`.
6. Demos: date-picker uses `<DatePicker.Time />` (drop the local bridge/CSS);
date-range-picker gains a granularity control + `<DateRangePicker.Time />`.
Verify: `npx svelte-check … --threshold error` (0 new; the only pre-existing
time-picker error is `time-picker-content.svelte` size/Popover) ·
`npx vitest run src/uix/soma/components/{date-picker,date-range-picker,time-picker}` ·
browser DOM checks (preview 5180; `:hover`/screenshot tooling is flaky).
## Done this session (in the commit)
- **Calendar-nav unification** — shared `CalendarNav` context so
Calendar/RangeCalendar/DateRangePicker reuse the refined month/year selects +
prev/next buttons (deleted the range pickers' bespoke selects). New
`calendar-nav-button`, `calendar-select.css`, `visual-context`, soma `calendar-nav`.
- **Popover** — fixed trigger re-click bounce.
- **TimePicker → datetime building blocks**: `DayPeriodToggle` now composes the
system `<ToggleGroup>` (was native radios; `type=single` `deselectable=false`
solid/primary, right-aligned); sliders self-hide per granularity; `ruler` →
`showScales`; slider labels follow the canonical label scale per size;
active-toggle hover fixed (no wash; `archetypes.css` hover rule now excludes
`data-state=on` so a pressed toggle keeps its accent instead of going
content-primary/black in light).
- **DatePicker demo** — locale-derived hour cycle + composed TimePicker time row
(this is what the next-task plan generalises to the component).
- **Perf (audit A31)** — lifted selection/checked sets across 9 providers.
- **langs** — picker-shell footer strings.
## Do NOT commit (active / separate tracks)
`soma/components/words/**`, `eidos/components/palabras/**` (firm rule), the
untracked `chronos/` component, `web/routes/demos/**`, and the loose untracked
docs (`README.md`, `SYSTEM-AUDIT-*`, `Disenando_*`, `COMPARATIVA_SIUM_*`).
`metrics.ts` langs rode along this commit only because `langs/index.ts` imports it.

@ -0,0 +1,259 @@
# UIX Component Audit — Worklist (resumable index)
worklist-version: 1
generated-at: 2026-06-26
total-names: 127 (82 soma-backed + 45 eidos-only)
status: **AUDIT COMPLETE — 124/124 audited** (words, palabras, chronos excluded per user directive) across 8 batches.
result: 0 CRITICAL · 18 HIGH · 129 MEDIUM · 60 LOW. See SUMMARY.md for the verdict + systemic findings.
mode: per-component reports in audit/components/{kebab}.md; roll-ups in audit/{SUMMARY,SHARED_EXTRACTION,THEMING_COHERENCE,VALIDATOR_GAPS}.md
> This is the resumable index. Each row's `status` flips `pending → audited` as its
> `audit/components/{kebab}.md` is written. Re-runs (`MODE=reeval`) read this to know
> what exists and what was covered.
## Legend
| Col | Meaning |
| --- | --- |
| **scope** | `morfo.scope` declared layers (the authored contract) |
| **morfo** | `Y` = `morfo/components/{n}.ts`; `INT` = `morfo/internal/{n}.ts`; `-` = none |
| **prov** | `Y` = `soma/components/{n}/{n}-provider.svelte.ts`; `dir*` = soma dir exists but **no standard provider file**; `-` = no soma dir |
| **eidos** | `Y` = `eidos/components/{n}/` dir; `(co)` = rendered inside a sibling's eidos dir; `-` = none |
| **demo** | `Y` = `web/routes/uix/components/{n}/`; `-` = none |
| **test** | `Y` = `{n}-provider.svelte.test.ts`; `-` = none |
| **#0** | cross-reference flag worth confirming during that component's audit |
---
## Cross-reference anomalies (candidate finding-#0, to confirm in per-component audit)
These are recorded now so the audit doesn't "discover" them as surprises; each must be
confirmed (or dismissed) inside the owning component's report.
| Component | Anomaly | Status to confirm |
| --- | --- | --- |
| `menu-dial` | scope `['soma','sema','eidos']` but soma dir holds only `index.ts` — no `menu-dial-provider.svelte.ts` | **RESOLVED (Batch 2):** compositional API; the headless runtime + nav helpers live in `soma/components/menu-dial/` (non-standard filenames), the eidos root composes them. Benign by design (matches the compositional-not-data-driven doctrine). |
| `onion-menu` | same as menu-dial | **RESOLVED (Batch 2):** runtime in `soma/components/onion-menu/onion-menu.svelte.ts` + `onion-menu-nav.ts`; eidos `<OnionMenu>` root composes it via `createOnionMenuRuntime`. The agent's CRITICAL "eidos imports soma = frontier violation" was a FALSE POSITIVE (eidos→soma is the normal direction) — refuted in verification. |
| `metrics` | scope `['sema','eidos']` yet has a soma dir with only `index.ts` | **RESOLVED (Batch 7):** the soma logic lives in `soma/components/metrics/metrics.svelte.ts` (non-standard filename, not `-provider.svelte.ts`); it's a sema+eidos display component with a small soma helper. Legitimately structured, not dead. |
| `picker-shell` | `INTERNAL` morfo, soma dir = `index.ts` only, no demo | **RESOLVED (Batch 3):** internal-by-design. The shell logic lives in `soma/components/picker-shell/picker-shell-handle.svelte.ts` + `exports.ts` (re-exported by the 5 pickers); Clear/Cancel/Close compose `<Button>`, Footer/Header/Body are layout zones. Not an orphan. |
| `avatar-group` | morfo `Y`, no own eidos dir — rendered by `eidos/components/avatar/avatar-group.svelte` | Confirmed co-located (not orphan); audit theming under `avatar` |
| `cascade`, `motion` | eidos morfos with **no demo** | Confirm they are infra contracts (not user-facing components) — expected |
| `words` | full stack present | **EXCLUDED from audit** — active separate dev track (`words`/`palabras`), per `docs/README.md` note + project rule + user directive 2026-06-26. |
| `palabras` | eidos for `words` | **EXCLUDED from audit** — user directive 2026-06-26. |
| `chronos` | full stack present | **EXCLUDED from audit** — user directive 2026-06-26 (active track). |
| `button` | scope `['soma','sema','eidos']`, **no provider test** | Test-coverage gap (dimension F) |
| `chronos`,`textarea`,`password-field`,`month-grid`,`year-grid` | interactive, **no provider test** | Test-coverage gaps (dimension F) |
Components lacking a `*-provider.svelte.test.ts` (dimension-F gaps to record per row):
`button, chronos, menu-dial, metrics, month-grid, onion-menu, password-field, picker-shell, textarea, year-grid` — plus every eidos-only primitive (expected: no provider).
---
## Group 1 — Overlays · portal + floating + dismissal + focus (hardest / most systemic)
| Component | scope | morfo | prov | eidos | demo | test | #0 | status |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| select | soma,sema | Y | Y | Y | Y | Y | | **audited** (4H/5M/2L) |
| combobox | soma,sema | Y | Y | Y | Y | Y | | **audited** (2H/3M) |
| dialog | soma,sema | Y | Y | Y | Y | Y | | **audited** (2H/2M/2L) |
| popover | soma,sema | Y | Y | Y | Y | Y | | **audited** (3M) |
| dropdown-menu | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (4M/1L) |
| context-menu | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (3M/1L) |
| menubar | soma,sema | Y | Y | Y | Y | Y | | **audited** (3M/4L) |
| navigation-menu | soma,sema | Y | Y | Y | Y | Y | | **audited** (1H/3M) |
| tooltip | soma,eidos,sema | Y | Y | Y | Y | Y | | **audited** (1M/1L) |
| drawer | soma,sema | Y | Y | Y | Y | Y | | **audited** (5M) |
| alert-dialog | soma,eidos | Y | Y | Y | Y | Y | | **audited** (1H) |
| command | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M/5L) |
| link-preview | soma | Y | Y | Y | Y | Y | | **audited** (1M) |
| float-panel | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M) |
| onion-menu | soma,sema,eidos | Y | dir* | Y | Y | - | #0 RESOLVED | **audited** (1M/3L) |
| menu-dial | soma,sema,eidos | Y | dir* | Y | Y | - | #0 RESOLVED | **audited** (3M/3L) |
## Group 2 — Pickers + calendar family (portal + composite + grid nav)
| Component | scope | morfo | prov | eidos | demo | test | #0 | status |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| date-picker | soma,sema | Y | Y | Y | Y | Y | | **audited** (4M) |
| date-range-picker | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M) |
| time-picker | soma,sema | Y | Y | Y | Y | Y | | **audited** (3M) |
| time-range-picker | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M/2L) |
| color-picker | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M/2L) |
| calendar | soma,sema | Y | Y | Y | Y | Y | | **audited** (4M/2L · MID-REFACTOR) |
| range-calendar | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (2M/2L · MID-REFACTOR) |
| month-grid | soma,sema | Y | Y | Y | Y | - | F | **audited** (1M/1L) |
| year-grid | soma,sema | Y | Y | Y | Y | - | F | **audited** (1M/1L) |
| picker-shell | INT | INT | dir* | Y | - | - | #0 RESOLVED | **audited** (1M/1L) |
## Group 3 — Fields (input + composition + masking)
| Component | scope | morfo | prov | eidos | demo | test | #0 | status |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| field | soma | Y | Y | Y | Y | Y | | **audited** (2M) |
| date-field | soma,sema | Y | Y | Y | Y | Y | | **audited** (3M · A30-effect) |
| date-range-field | soma,eidos | Y | Y | Y | Y | Y | | **audited** (2M/3L) |
| time-field | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M/1L · A30-effect) |
| time-range-field | soma | Y | Y | Y | Y | Y | | **audited** (1M) |
| color-field | soma,sema | Y | Y | Y | Y | Y | | **audited** (3M · A30-effect) |
| css-field | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| number-field | soma,sema | Y | Y | Y | Y | Y | | **audited** (1L · clean REFERENCE) |
| search-field | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M) |
| password-field | soma,sema | Y | Y | Y | Y | - | F | **audited** (1M) |
| pin-input | soma,eidos | Y | Y | Y | Y | Y | | **audited** (1M) |
| textarea | soma,sema | Y | Y | Y | Y | - | F | **audited** (2M) |
| tags-input | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| editable | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
## Group 4 — Collections + data (keyboard nav + virtualization + selection)
| Component | scope | morfo | prov | eidos | demo | test | #0 | status |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| listbox | soma,sema | Y | Y | Y | Y | Y | | **audited** (1H/1M · A31) |
| grid-list | soma,sema | Y | Y | Y | Y | Y | | **audited** (2H/1M · A31) |
| tree-view | soma,sema | Y | Y | Y | Y | Y | | **audited** (1H/3M · A31) |
| tree-grid | soma,sema | Y | Y | Y | Y | Y | | **audited** (1H · A31) |
| table | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M · engine-backed, A31 refuted) |
| virtual-list | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M · A33 SvelteMap clean) |
| virtual-grid | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| carousel | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M/1L) |
| feed | soma,sema | Y | Y | Y | Y | Y | | **audited** (clean · A31 refuted) |
| pagination | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| tag-group | soma,sema | Y | Y | Y | Y | Y | | **audited** (1H/2M/1L · A31) |
| rating-group | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
## Group 5 — Disclosure + selection controls (stateful)
| Component | scope | morfo | prov | eidos | demo | test | #0 | status |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| accordion | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (CLEAN) |
| collapsible | soma,sema | Y | Y | Y | Y | Y | | **audited** (1H/1M · seq-lag) |
| tabs | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (CLEAN) |
| stepper | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M/3L) |
| toggle | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M · seq post OK) |
| toggle-group | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (1M · A31) |
| switch | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M · seq post OK) |
| checkbox | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (1M/1L · A31 group, seq post OK) |
| radio-group | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (1M · A31 clean) |
| slider | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M · no A6 leak) |
| splitter | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (1M/1L) |
| scroll-area | soma | Y | Y | Y | Y | Y | | **audited** (1M) |
## Group 6 — Actions + feedback + misc behavior
| Component | scope | morfo | prov | eidos | demo | test | #0 | status |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| button | soma,sema,eidos | Y | Y | Y | Y | - | F | **audited** (CLEAN · ref impl) |
| toast | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| announce | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M) |
| clipboard | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M/2L) |
| drag-drop | soma,sema | Y | Y | Y | Y | Y | | **audited** (3M) |
| form | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M · A36 fix held) |
| file-upload | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| toolbar | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M · A35 fix held) |
| breadcrumb | soma | Y | Y | Y | Y | Y | | **audited** (1H · archetype-item) |
| meter | soma | Y | Y | Y | Y | Y | | **audited** (1M) |
| progress | soma | Y | Y | Y | Y | Y | | **audited** (1M) |
| timeline | soma,sema,eidos | Y | Y | Y | Y | Y | | **audited** (1M · archetype clean) |
| image-adjustments | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| image-picker | soma,sema | Y | Y | Y | Y | Y | | **audited** (1M) |
| cropper | soma,sema | Y | Y | Y | Y | Y | | **audited** (2M/1L) |
| chronos | soma,sema | Y | Y | Y | Y | - | EXCL | excluded |
| metrics | sema,eidos | Y | dir* | Y | Y | - | #0 RESOLVED | **audited** (1M) |
| words | soma,sema | Y | Y | Y | Y | Y | EXCL | excluded |
## Group 7 — Eidos-only · layout primitives (theming-heavy; A/B/C/D light)
| Component | scope | morfo | prov | eidos | demo | test | status |
| --- | --- | --- | --- | --- | --- | --- | --- |
| box | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| flex | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| grid | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| stack | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| group | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| wrap | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| container | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| section | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| aspect-ratio | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| auto-grid | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| float | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
## Group 8 — Eidos-only · typography + text
| Component | scope | morfo | prov | eidos | demo | test | status |
| --- | --- | --- | --- | --- | --- | --- | --- |
| text | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| heading | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| display | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| code | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| code-block | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| kbd | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| mark | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| highlight | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| link | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| s-text | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| s-text-virtual-list | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
## Group 9 — Eidos-only · visual elements + composite-eidos + service
| Component | scope | morfo | prov | eidos | demo | test | status |
| --- | --- | --- | --- | --- | --- | --- | --- |
| badge | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| card | sema,eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| avatar | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| avatar-group | eidos | Y | - | (co) | Y | - | **audited** (B8 theming) |
| image | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| separator | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| skeleton | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| spinner | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| qr-code | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| icon | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| banner | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| chart | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| button-group | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| split-button | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| fab | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| radio-cards | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| format-date | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| format-number | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| relative-time | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| trans | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
| cascade | eidos | Y | - | Y | - | - | **audited** (B8 theming) |
| motion | eidos | Y | - | Y | - | - | **audited** (B8 theming) |
| scroll-frames | eidos | Y | - | Y | Y | - | **audited** (B8 theming) |
---
## Proposed audit order (batches)
Rationale (from §1.4): the hardest, most-coupled components are audited first because
they expose **systemic** issues (dismissal-trigger-exclusion, focus return, floating-gap
canon, virtual-vs-roving keyboard divergence, scope-vs-eidos-dir drift, TSC eager
substitution) that then recur in the simpler ones — finding them early makes every later
audit faster and lets the roll-ups (`SUMMARY`, `SHARED_EXTRACTION`, `THEMING_COHERENCE`,
`VALIDATOR_GAPS`) accrete real systemic findings.
- **Batch 1 (first, on approval) — the 5 canonical overlays:**
`select`, `dialog`, `popover`, `combobox`, `dropdown-menu`.
These are exactly the protocol's named hard set; portal + floating + dismissal + focus
+ (select/combobox) typeahead + virtual-vs-roving focus. Systemic patterns surface here.
- **Batch 2 — remaining overlays + menus:** `context-menu`, `menubar`,
`navigation-menu`, `tooltip`, `drawer`, `alert-dialog`, `command`, `link-preview`,
`float-panel`, `onion-menu`, `menu-dial` (+ confirm the two #0 provider anomalies).
- **Batch 3 — pickers + calendar family:** the 5 pickers, `calendar`, `range-calendar`,
`month-grid`, `year-grid`, `picker-shell`.
- **Batch 4 — fields:** Group 3 (14).
- **Batch 5 — collections + data:** Group 4 (12).
- **Batch 6 — disclosure + selection controls:** Group 5 (12).
- **Batch 7 — actions + feedback + misc:** Group 6 (17; `words` excluded).
- **Batch 8 — eidos-only primitives:** Groups 7–9 (44). Dimensions A/B/C/D are light
(no provider behavior); focus is **E / E-bis (theming coherence)** + the `THEMING_COHERENCE`
roll-up, where these contribute the most distinct hardcoded-value / token-naming data.
After each batch: write the per-component `audit/components/{kebab}.md` files, regenerate
the roll-ups (`00_WORKLIST` statuses, `SUMMARY`, `SHARED_EXTRACTION`, `THEMING_COHERENCE`,
`VALIDATOR_GAPS`), then stop and report before the next batch.
> Path note: the protocol fixes the output root at `audit/` (repo root) so `MODE=reeval`
> can find `audit/components/{kebab}.md`. The repo's own convention parks audits under
> `docs/process/`; I followed the protocol's literal path. Say the word if you want it
> relocated under `docs/process/audit/` instead.

@ -0,0 +1,183 @@
# UIX Audit — Fix Execution Plan (ready to run)
plan-version: 1
prepared: 2026-06-26
source: this audit (124/124 components, 0 CRITICAL · 18 HIGH · 129 MEDIUM · 60 LOW)
branch: active-uix
status: NOT STARTED — read-only audit done; this is the authorized fix track.
## How to use this plan
Execute **phase by phase, top to bottom** (ordered by leverage). After EACH phase:
1. `npm run check` → 0 new errors.
2. Run the phase's vitest scope (named per phase).
3. For HIGH UI/behavior fixes, a browser check (the audit was read-only; some HIGHs — e.g. collapsible
sequence-lag — need a frame-by-frame measurement to confirm the fix, like the original Checkbox fix).
4. Update the finding's `fix-status:` in `audit/components/{kebab}.md` (`open` → `fixed`), then re-run
`MODE=reeval` later to confirm.
**Hard exclusions (every phase):** never touch `soma/components/words/**`, `eidos/components/palabras/**`,
or `chronos` — active dev tracks, excluded from the audit. When committing, stage explicit paths.
**Severity legend:** 🔴 HIGH · 🟠 MEDIUM · ⚪ LOW. Each item links its finding id.
---
## Phase 1 — SYS-7: the A31 O(N²) selection cluster ⭐ HIGHEST LEVERAGE
**6 HIGH + 3 MEDIUM, one mechanical pattern, ~2 lines per component.** This is the single best ROI in the audit.
Pattern: a per-item `$derived` calls `provider.isSelected(v)`/`isExpanded(v)`/`isItemPressed(v)`/`isItemChecked(v)`
which does `array.includes(v)` (O(N)). With N items re-deriving on every mutation → O(N²) ("hangs at 30+").
**Fix (apply to each provider):**
- Add a provider-level `readonly selectedSet = $derived(new SvelteSet(this.opts.value.current))` (import
`SvelteSet` from `svelte/reactivity`). Trees also add `expandedSet`.
- Rewrite the provider method to `return this.selectedSet.has(value)` (O(1)). The per-item `$derived` is unchanged.
- The providers already prove the shape — `rovingTargetEl` is lifted exactly this way (with a comment citing
"avoids an O(N²) cascade"). Mirror it.
| 🔴/🟠 | Component | provider method | id |
| --- | --- | --- | --- |
| 🔴 | listbox | `isSelected` @ listbox-provider:157-158 (item @ :413) | listbox-001 |
| 🔴 | grid-list | `isSelected` :493 + per-checkbox `isChecked` :151 | grid-list-001/002 |
| 🔴 | tree-view | `isSelected`/`isExpanded` :96-98/:145-147 (items :351/:353/:569) | tree-view-002 |
| 🔴 | tree-grid | `isSelected`/`isExpanded` :148/:189 (rows :578-579) | tree-grid-001 |
| 🔴 | tag-group | `isSelected` :115-117 (item :321 + link :410) | tag-group-001 |
| 🟠 | toggle-group | `isItemPressed` :72 (item :146) | toggle-group-001 |
| 🟠 | checkbox | `CheckboxGroupProvider.isItemChecked` :313-315 | checkbox-001 |
| 🟠 | select | `isSelected` :158 (single-select, low-risk but harmless) | select-007 |
| 🟠 | combobox | `isSelected` :182 (single-select, low-risk) | combobox-004 |
**Optional extraction (EX-3):** a 3-line shared `liftedSet(() => string[])` helper, then all 9 consume it.
Decide inline-vs-helper at execution (≥2 consumers justify the helper).
**Verify:** `npx vitest run src/uix/soma/components/{listbox,grid-list,tree-view,tree-grid,tag-group,toggle-group,checkbox,select,combobox}` + a 50-item browser smoke on listbox/tree.
---
## Phase 2 — the isolated HIGH findings (12)
Each is well-grounded and self-contained. Order within the phase is by risk.
1. 🔴 **dialog-001** — Content double-writes `role`/`aria-roledescription` (a11y race for `alertdialog`).
`dialog-provider:391-397` registers Content `syncAttrs:true`, then `:460-463` sets `role:variant` +
`aria-roledescription:undefined`. **Fix:** make `role` morfo-expressible (bind a `propRef('variant')` /
stateRef so the morfo owns the variant role) OR drop `syncAttrs` for Content and supply role/aria via
`renderProps()` + a single override. Don't mix syncAttrs + manual same-attr writes. Verify alertdialog role
is stable across ticks.
2. 🔴 **dialog-002** — dead/conflicting `[data-dialog-trigger]` CSS envelope after the trigger migrated to a
composed `<Button>`. **Fix:** delete the `[data-dialog-trigger]` envelope (dialog.css:22-54) + its
`--dialog-trigger-*` tokens (base.ts:727); Button owns the chrome (the Close recipe is the model).
3. 🔴 **combobox-001** — virtual+real focus mix (A17). Morfo declares `aria-activedescendant`; provider calls
`dom.focus(items[0])` (:392/408/684). **Fix:** commit to virtual focus — keep DOM focus on the input, drive
selection via `highlightedId`/`data-highlighted` + `scrollIntoView`, never `dom.focus(item)`. (Higher effort;
needs a client/Playwright test asserting `activeElement` stays on the input.)
4. 🔴 **select-001** — `scrollSelectedIntoView` (:244-246) interpolates the user value without `CSS.escape`.
**Fix:** reuse the safe `resolveListItemEl` helper (it already `CSS.escape`s), or wrap in `CSS.escape`.
5. 🔴 **select-002** — morfo `focus.trap:true`/`initial:'first-focusable'` but provider implements virtual focus
(no FocusScope). **Fix:** change the morfo `focus` to reflect virtual focus (trap:false, keep return:'trigger');
cross-check combobox.
6. 🔴 **select-003** — two keyboard routes duplicate index math; the content route uses real-focus (`activeElement`)
in a virtual-focus component + the loop off-by (`n-2`). **Fix:** extract the pure `nextIndex` helper (EX-1),
delete/repair the content route. (Pairs with combobox-001.)
7. 🔴 **alert-dialog-001** — `export const alertDialogMorfo: Morfo = {` uses `: Morfo`. **Fix (1 line):**
`export const alertDialogMorfo = { … } as const satisfies Morfo;` (alert-dialog.ts:4 + the closer).
8. 🔴 **navigation-menu-006** — undisposed `$effect.root` (:523-527) leaks per trigger. **Fix:** replace with a
bare `$effect` in the constructor (auto-disposed), matching the root provider's existing pattern.
9. 🔴 **collapsible-NEW-001** — `collapse` is `sequence:'pre'` + sets state in the handler → ~240ms lag (the
Checkbox-244ms mechanism). **Fix:** change `collapse` to `sequence:'post'` (collapsible.ts:48) OR set state at
the call-site in `toggle()` before `runtime.trigger`. **Confirm with a frame-by-frame browser measurement
before+after** (the original Checkbox lag was measured). If a visible-during-exit cue is wanted, drive it from
CSS keyed on `data-state='closed'`.
10. 🔴 **breadcrumb-001** — Item `<li>` declares `archetype:'item'` (pulls cursor:pointer/hover). **Fix:** remove
`archetype:'item'` from the Item part (breadcrumb.ts:50) — Timeline shows the correct pattern (a display `<li>`
omits it). Verify the breadcrumb li no longer shows pointer/hover.
**Verify:** per-component vitest + a browser pass on dialog (alertdialog role), combobox/select (focus), collapsible
(collapse latency), breadcrumb (cursor).
---
## Phase 3 — SYS-1 scope-drift (one decision, then mechanical) 🟠 ~33 components
**First decide the semantics** (this is a project call, not a per-component fix): does morfo `scope` enumerate
*authored* layers or *all consuming* layers? Two options:
- (a) Add `'eidos'` to every morfo whose component has an `eidos/components/{c}/` dir + recipe (~33 one-line edits).
- (b) Document that eidos is implicit and `scope` lists only soma/sema (then SYS-1 is closed by doc, 0 code edits).
Recommend (a) for explicitness (dropdown-menu/accordion/tabs/checkbox/radio-group/splitter already do it). The
list of drifted morfos is in `THEMING_COHERENCE.md` / each component report's `scopeDrift: true`.
**Verify:** `npm run morfo:check` + `npm run check`.
---
## Phase 4 — THEME-SYS-1: overlay z-index ladder 🟠 ~10 recipes, one sweep
A canonical `STATIC_Z_INDEX` scale EXISTS (`lib/primitives/static.ts:291`: base/raised/sticky/dropdown/popover/
tooltip/modal/toast) and is consumed by the depth planes + menu-dial — but ~10 overlay recipes hardcode an ad-hoc
60–99/1200 scale. **Fix (precise mapping, base.ts):**
- dropdown-menu/context-menu `content-z` → `var(--z-index-dropdown)`
- select/combobox/popover/link-preview content → `var(--z-index-popover)`
- tooltip → `var(--z-index-tooltip)`
- dialog/drawer overlay → `var(--z-index-modal)` (content = +1 or a new `--z-index-modal-content`)
- toast `toaster-z` → `var(--z-index-toast)`
Verify the rendered stacking order is preserved (60→1200 today, must stay ordered after mapping to 0..900).
**Verify:** browser — overlay stacking (popover under dialog under toast).
---
## Phase 5 — the smaller systemic patterns 🟠
- **SYS-A30-EFFECT** (date/time/color-field): replace the `$effect`-wrapped `inputId` registration with a direct
constructor guard, matching NumberField (number-field-provider:585-587). 3 components.
- **SYS-A33-CLONE** (listbox, grid-list, tree-grid, tag-group): migrate the `new Map(this.x)` clone-and-reassign
registries to `SvelteMap`/`SvelteSet` (pairs with Phase 1 — same providers). 4 sites.
- **SYS-5 double-write**: drawer-007 (Content manual `role`/`aria-modal` while morfo declares them), popover-001
(Close re-declares `type`/`aria-label`). Drop the manual re-sets; let `syncAttrs`/`renderProps` own them.
- **SYS-2 magic-z (non-overlay)** + **THEME-SYS-2 magic-opacity**: dropdown-menu/context-menu `item-disabled-opacity
0.55` → `var(--opacity-disabled)`; calendar `day-outside-opacity 0.62` → snap to `--opacity-*` (or one named
`--opacity-scrim` for the recurring 0.62/62%). select `item-description-font-size '0.85em'` → tokenize.
- **SYS-INERT** (5 pickers): either fire `open`/`commit-reset` events (perceptual signal) or drop them from the
morfo. Decision per picker.
---
## Phase 6 — MEDIUM/LOW hygiene + test gaps 🟠⚪
- **SYS-3 test gaps** (pervasive): add the cheap, high-value tests first — the A24 readonly-without-value
`logger.warn` assertions in the segmented fields (the harness already stubs `warn`), and provider keyboard-route
tests for the overlays. Add client/Playwright tests for the focus-bearing overlays (select/combobox/dialog).
- **Calendar Home/End** (calendar-006 / range-calendar-002): decide APG-week vs month, then align the morfo action
label and the provider impl in BOTH calendar + range-calendar.
- The remaining LOW magic-literals (em-padding on code/kbd/mark, scroll-frames, radio-cards easing, etc.) — batch
them when convenient; none are blocking.
- ⚠️ **calendar / range-calendar are MID-REFACTOR** (uncommitted view-switch work) — coordinate with that track
before touching their eidos; their findings reflect in-flight state.
---
## Parallel track — VALIDATOR EXTENSIONS (prevent the class) — see VALIDATOR_GAPS.md
Highest leverage of all: fixing the validator stops the whole class recurring. Do the cheap ones first.
| VG | What it catches | Effort |
| --- | --- | --- |
| **VG-8** ⭐ | `: Morfo` instead of `as const satisfies Morfo` (a grep) — would've caught alert-dialog-001 | trivial |
| **VG-3** | `syncAttrs:true` + props re-setting a morfo attr (dialog-001/popover-001 class) | med-high |
| **VG-4** | `aria-activedescendant` morfo + `dom.focus(item)` (combobox-001 class) | medium |
| **VG-12** | `sequence:'pre'` event whose handler sets bound state (collapsible-NEW-001 class) | medium |
| **VG-10** | morfo event never reached by `runtime.trigger` (SYS-INERT) | medium |
| VG-1/2/6/9/11 | em/% font-size · magic z/opacity/tracking · translationRef-key · `$effect.root`/`$effect`-id | low-med |
---
## Suggested day-1 order (if time-boxed)
1. **Phase 1 (SYS-7)** — biggest correctness win, mechanical. (½ day)
2. **alert-dialog-001 + VG-8** — 1-line fix + the grep that prevents it. (15 min)
3. **breadcrumb-001, navigation-menu-006, dialog-002, select-001** — small, self-contained HIGHs. (½ day)
4. **dialog-001, collapsible-NEW-001** — need a browser measurement; do with the dev server up.
5. **combobox-001 + select-002/003** — the focus-strategy refactor (highest effort HIGH); schedule its own block.
6. Phases 3–6 + remaining VGs as follow-up.

@ -0,0 +1,63 @@
# Shared extraction opportunities — rolling
updated-at: 2026-06-26
batches-complete: 1
Ranked by (components affected × LOC removed × divergence risk). "Should extract" vs "looks similar but
legitimately diverges" is called out per entry.
## EX-1 · Directional-nav index helper <!-- id: EX-001 -->
- verdict: **SHOULD EXTRACT** (pure math is identical; only the focus-application differs)
- components: select (content route), combobox (content route), dropdown-menu (content + sub-content). 4+ copies.
- pattern: `next/prev/Home/End` index math —
`loop ? (i±1+len)%len : Math.min/Math.max(i±1, …)` + the `currentIndex===-1` seed.
- shared bug it would kill: the `currentIndex===-1` + prev + loop branch lands `n-2` instead of last
(select-003, dropdown-menu-001).
- proposed: `nextIndex(curr: number, key: string, len: number, opts: {loop; dir; orientation}): number`
(pure, in `soma/keyboard` or a `list-nav.ts` next to `list-selection.ts`). Each caller keeps its own
focus/scroll application (real focus for menu/select-content, virtual highlight for select-virtual).
- est. LOC removed: ~20-35 per site (×4) − ~40 helper = net ~60-100 removed; + 1 unit-tested boundary suite.
- divergence note: select's *trigger* route is virtual-focus and its *content* route is real-focus — the INDEX
math is the same; extract only the index computation, not the focus side-effect.
- **B2 correction:** the `n-2` loop off-by is NOT universal. navigation-menu (and the verified menu loop math) use
modulo WITH a `currentIndex===-1` early-return guard, so they're correct. The off-by lives specifically in
Select's and DropdownMenu's content routes (no guard). The extraction still pays off (one canonical, tested
helper replacing ~6 copies, several already correct), but the *bug fix* is scoped to select + dropdown-menu.
## EX-2 · Dismissal trigger/anchor exclusion <!-- id: EX-002 -->
- verdict: **SHOULD EXTRACT** (every overlay re-implements the same bounce guard)
- components: select, combobox, popover, dropdown-menu (4/5) each hand-roll "ignore an interact-outside that
lands on the trigger (and input)" via `contains()` / `isValidEvent`, with near-identical comments about the
pointerdown-close-then-click-reopen bounce.
- proposed: a `Dismissal` option `excludeRefs: () => (HTMLElement|null)[]` (or `excludeWithin`) that the layer
applies before invoking the close action — removes the per-provider guard + the `isValidEvent` duplication.
- est. LOC removed: ~8-15 per site (×4) net positive; centralizes a subtle, repeatedly-rediscovered bug.
- divergence note: combobox excludes trigger+input, popover/select exclude trigger only — the option takes a
list, so it covers both.
## EX-3 · `selectedSet`/`expandedSet` lifted derivation (A31) — HIGHEST-VALUE EXTRACTION <!-- id: EX-003 -->
- verdict: **SHOULD EXTRACT — confirmed across 7 components, fixes 6 HIGH + 2 MEDIUM with one helper.**
- components (lead-verified, B1+B5): select-007, combobox-004 (MEDIUM) + listbox-001, grid-list-001/002,
tree-view-002, tree-grid-001, tag-group-001 (HIGH). Each: per-item `$derived` → `provider.isSelected(v)` /
`isExpanded(v)` → `opts.value.current.includes(v)` (O(N²)).
- proposed: a tiny shared helper `liftedSet(() => string[]): { has(v): boolean }` returning a provider-level
`$derived(new SvelteSet(arr))`; `isSelected`/`isExpanded` become `.has(value)` (O(1)). The providers already
prove the pattern works — `rovingTargetEl` is lifted exactly this way (with comments). This just extends it to
the selection/expansion path.
- est. LOC: ~2 lines per component (×7) + a 3-line helper; the value is **correctness** (removes the documented
"hangs at 30+" hazard from every large multi-select list/tree/grid), not LOC.
- divergence note: trees need `expandedSet` too (same shape, different array). Single-select select/combobox are
the low-risk variant but the same fix applies and is harmless.
## EX-4 · Reactive registry/cache: SvelteMap over clone-and-reassign (A33) <!-- id: EX-004 -->
- verdict: **SHOULD MIGRATE** (4 sites, fragile pattern)
- components: listbox, grid-list, tree-grid, tag-group each do `this.x = new Map(this.x)` clone-and-reassign for a
registry/cache. virtual-list is the positive model (SvelteMap). combobox already migrated.
- proposed: replace each with `SvelteMap`/`SvelteSet` — `.set`/`.delete` become O(1) + reactive without the clone,
and the "why clone?" foot-gun disappears. Pairs with EX-3 (same providers, same modernization sweep).
## Positive model (already extracted — the bar to match)
- **`list-selection.ts`** (`computeListSelection` + `resolveListItemEl`) is shared correctly between Select and
Combobox: pure state machine, `CSS.escape` in the resolver, no `this.handleClose()` baked in. EX-1/EX-2/EX-3
should follow this shape. (Note: Select's `scrollSelectedIntoView` bypasses `resolveListItemEl` and
reintroduces the unescaped query — select-001; fixing it = "use the helper you already have".)

@ -0,0 +1,346 @@
# 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).

@ -0,0 +1,111 @@
# Theming coherence — consolidated (E-bis roll-up)
updated-at: 2026-06-26
batches-complete: 1
canon refs: THEMING §4 (9 roles), §5 (sizes + 1:1 font), §6 (naming/slots), §16 (anti-patterns A–H),
§19 (EIDOS_VARIANTS), §35 (scales — z/opacity/border)
## (a) Hardcoded-where-canonical values (the consolidation)
| Value (literal) | Canonical token it should be | Components hardcoding it | Severity |
| --- | --- | --- | --- |
| `content-z` / `overlay-z` integers (`80`/`75`/`70`/`60`) | `--z-index-{overlay-ladder}` (THEMING §35-C) | select(80), dialog(70), popover(75/60), combobox(80), dropdown-menu(80) — **5/5** | **SYSTEMIC** |
| `item-disabled-opacity: '0.55'` | `var(--opacity-disabled)` | dropdown-menu | MEDIUM |
| `item-description-font-size: '0.85em'` | `var(--font-size-*)` (escapes the px/rem guard) | select | MEDIUM |
| `group-heading-letter-spacing: '0.04em'` | `var(--tracking-caps)` | select | LOW |
| `item-description-mt: '0.125rem'` | `var(--space-0-5)` | select | LOW |
| `item-height: '2rem'` | `--list-item-height-*` / `--control-height-*` (list-surface layer) | dropdown-menu | LOW |
> `dialog.overlay-opacity: '62%'` is left as recipe-legitimate (scrim opacity is a recipe knob, not on the
> `--opacity-*` step scale) — noted, not flagged.
## (b) Near-duplicate tokens (same resolved value, different name)
- `content-z: '80'` appears identically in **select**, **combobox**, **dropdown-menu** — three names, one
value, no shared source. Folds into SYS-2 (MAGIC-Z).
- No other near-duplicate token pairs surfaced in Batch 1 (the recipes alias `--color-*`/`--space-*`/`--radius-*`
consistently).
## (c) Non-canonical role / variant / size / slot names found
- **Roles:** NONE. All 5 use only the 9 canonical roles (`primary/secondary/tertiary/neutral` +
`affirm/fulfill/risk/threat/loss`). No `success`/`warning`/`danger`/`info`. ✓ (THEMING §16-C clean.)
- **Variants:** NONE invented. `SelectVariant = ControlVariant`, dialog uses position/size data-attrs (visual,
eidos-owned, not "variants"). ✓
- **Sizes:** NONE invented (`xs..xl` subsets). One raw-rem size (`dropdown-menu.item-height: 2rem`) that should
bridge the list-surface scale — drift, not an invented name.
- **Slots:** NONE invented; recipes use `track/border/text/bg/hover/...` from the fixed vocabulary. ✓
## (d) Per-component canon-conformance
| Component | roles | sizes | variants | naming | no-overlap |
| --- | --- | --- | --- | --- | --- |
| select | ✓ | ✓ | ✓ | ✓ | ✗ (em-font literal escapes guard; magic-z; subset type=all-9 vs 8 accents) |
| dialog | ✓ | ✓ | ✓ | ✓ | ✗ (dead `[data-dialog-trigger]` envelope overlaps composed Button; magic-z) |
| popover | ✓ | ✓ | ✓ | ✓ | ⚠ (magic-z only) |
| combobox | ✓ | ✓ | ✓ | ✓ | ⚠ (magic-z only) |
| dropdown-menu | ✓ | ⚠ (item-height 2rem) | ✓ | ✓ | ⚠ (magic-z + magic opacity) |
Legend: ✓ clean · ⚠ minor drift · ✗ real overlap/violation.
## (e) Systemic theming finding (≥3 components → one structural fix)
### THEME-SYS-1 · Overlay z-index is an ad-hoc parallel scale; a canonical `--z-index-*` ladder already exists <!-- id: THEME-SYS-001 -->
- recurs in (B1+B2): select 80 · dialog overlay 70 · popover content 75 / overlay 60 · combobox 80 ·
dropdown-menu 80 · **tooltip 76** · **drawer overlay 72 / inline 64** · **link-preview preview-z 99** ·
navigation-menu content-z (token, value hardcoded) · toast toaster-z 1200. **~10 overlay recipes.**
- **lead-verified ground truth:** the canonical scale `STATIC_Z_INDEX` (`lib/primitives/static.ts:291`) EXISTS:
`base 0 · raised 1 · sticky 100 · dropdown 300 · popover 400 · tooltip 500 · modal 700 · toast 900`, exposed as
`--z-index-{key}` and consumed by the depth planes (`overlay → var(--z-index-popover)`, `modal → var(--z-index-
modal)`) AND by **menu-dial** (`var(--z-index-sticky, 1100)`). So the overlay recipes hardcode an ad-hoc 60–99/
1200 scale that (a) duplicates a scale that already exists and (b) doesn't even match its ordering — e.g. ad-hoc
tooltip 76 < dropdown 80, but canonical tooltip 500 > dropdown 300.
- precise fix mapping (one sweep): dropdown-menu/context-menu → `--z-index-dropdown`; select/combobox/popover/
link-preview content → `--z-index-popover`; tooltip → `--z-index-tooltip`; dialog/drawer overlay → `--z-index-
modal` (content = +1 or a `--z-index-modal-content`); toast → `--z-index-toast`. menu-dial already does it right.
- fix-status: open
### THEME-SYS-2 · Recipe opacity decimals where a numeric `--opacity-*` scale exists <!-- id: THEME-SYS-002 -->
- recurs in: dropdown-menu `item-disabled-opacity 0.55`, context-menu `item-disabled-opacity 0.55`, onion-menu
`muted-opacity 0.85`/`hover-ring-opacity 0.65`, **calendar `day-outside-opacity 0.62`** (B3). The `--opacity-*`
scale is a 0.05 step (static.ts:255-275: `--opacity-60=0.6`, `--opacity-65=0.65`) — `0.62` maps to NO step, and
the same off-scale `0.62`/`62%` recurs as overlay-opacity in dialog (base.ts:729) + drawer (:895): an unnamed
brand value. Dialog uses `var(--opacity-disabled)` for disabled — the positive model.
- one fix: map disabled opacities to `var(--opacity-disabled)`, snap decimals to the nearest `--opacity-*` step,
and give the recurring `0.62` scrim value ONE named token (`--opacity-scrim`?) instead of three literals.
- fix-status: open
### B3 theming note — calendar `8ch` select-min-width is LEGIT (not drift)
- The verify correctly ruled `'select-min-width': '8ch'` (calendar recipe) a non-violation: `ch` is the right unit
for a text-box min-width (tracks glyph width of the month/year label), there is no canonical `ch`-scale to
reference, and it's already a named component token. Token-naming nit at most (LOW). Recorded so the next sweep
doesn't re-flag it.
## Batch-8 closing — eidos primitives (44) are theming-clean
The final batch swept all eidos-only primitives (layout · typography · visual · composite+service · infra) for E-bis.
Result: **the eidos theming layer is in excellent shape.**
- **(c) NON-CANONICAL ROLES — CLEAN across all 124 components (authoritative).** Lead grep + the B8 category sweep +
verify agree: NO recipe / type union / `data-color` enum / token name anywhere uses `success`/`warning`/`danger`/
`info`/`error`/`brand`. The only occurrences are COMMENTS that correctly *document the mapping* (badge/types.ts:22:
"success → affirm, warning → risk, error → threat"). The B8 agents' "TextColor/HeadingColor must use ColorRole"
flags were all REFUTED: typography color uses the **content-color** slots (`content-primary/secondary/muted/disabled/
on-solid`), which are valid content roles, NOT the 9 hierarchy/intent roles. THEMING §16-C is fully satisfied.
- **VARIANTS / SIZES / NAMING — CLEAN.** No invented `data-variant`, no invented size, no `--eidos-/--soma-/--air-`
prefix, no redundant `color-` segment found in any primitive.
- **RAW HEX — only the physically-fixed ones.** qr-code's `#fff`/`#000` (scanability) is the sole raw-hex, correctly
ruled legitimate (refuted).
- **The residual literals are minor (3 MEDIUM, 12 LOW), no systemic class:**
- `code`/`kbd`/`mark` inline-pill padding in `em` (0.1–0.4em) — there is NO canonical em-padding scale, so this is
borderline-acceptable LOW (a candidate for a future `--inline-pad-*` token if it ever recurs more).
- `scroll-frames` layout + opacity literals (2 MEDIUM) — should reference `--space-*` / `--opacity-*`.
- `radio-cards` one-off easing literal (MEDIUM) — should reference `--ease-*`.
- `section` `calc(--space-16 * 1.5)` (LOW) — composes the canonical token for an off-scale xl=96px (no `--space-24`).
- `container` `80rem` defensive fallback, `group` `1px` border-collapse — physically-fixed/defensive (LOW, accept).
- **No new systemic theming finding from B8.** The two systemic theming issues remain THEME-SYS-1 (overlay z-ladder,
~10 overlay recipes — the only structural theming fix worth a sweep) and THEME-SYS-2 (magic opacity decimals).
## Notes for the full sweep
- The biggest theming wins are E-bis structural (tokenize z-ladder), not per-recipe. Batch 8 (eidos-only
primitives) will add the most distinct hardcoded-value data — re-run this roll-up after each batch and
promote any value hardcoded in ≥3 components to a systemic entry here.
- The px/rem font-size coherence guard has an `em`/`%` hole (select-008) — tracked in VALIDATOR_GAPS.

@ -0,0 +1,142 @@
# Validator gaps — rolling
updated-at: 2026-06-26
batches-complete: 1
For each behavioral/contract/theming finding that a validator *should* have caught but didn't, the proposed
validator extension. Fixing the validator prevents the whole class — highest leverage.
## VG-1 · Coherence guard misses `em`/`%` font-size literals <!-- id: VG-001 -->
- finding: select-008 (`item-description-font-size: '0.85em'`).
- which validator: `recipe-css-contract.test.ts` (THEMING §5 guard: "no recipe `font-size-*` token may be a
literal px/rem — must reference `--font-size-*`").
- gap: the guard checks **px/rem** literals only; a bare `em` (or `%`) font-size slips through.
- proposed: extend the guard to reject ANY non-`var()` value on a `*font-size*` recipe token (px/rem/em/%/number).
- effort: low (regex/value-shape check already exists).
## VG-2 · No check for magic z-index / magic opacity / magic letter-spacing in recipes <!-- id: VG-002 -->
- findings: SYS-2 (content-z/overlay-z 5/5), dropdown-menu-003 (`0.55` opacity), select-010 (`0.04em` tracking).
- which validator: none (THEMING §35-C is doctrine, not enforced).
- gap: bare numeric `z-index` values, bare opacity decimals where `--opacity-*` exists, and bare `em`
letter-spacing where `--tracking-*` exists are not flagged anywhere.
- proposed: a recipe-token lint that flags: (a) a `*-z`/`z-index` token whose value is a bare integer;
(b) an `*opacity*` token whose value is a bare decimal (suggest `--opacity-*`); (c) a `*letter-spacing*`/
`*tracking*` token whose value is a bare `em` (suggest `--tracking-*`). Allow an explicit `// coherence-ok:`
escape for genuinely physical values (scrim opacity).
- effort: medium.
## VG-3 · No check for `syncAttrs:true` + manual re-set of a morfo-declared attr <!-- id: VG-003 -->
- findings: dialog-001 (role/aria-roledescription — live a11y race), popover-001 (Close type/aria-label).
- which validator: none (morfo:check validates the morfo, not the provider's props block).
- gap: a part registered `syncAttrs: true` whose `props` `$derived` re-declares a key the morfo already
resolves is a double-write (active_architecture §7.7) — undetected.
- proposed: a provider lint (AST or grep-assisted) — for each `runtime.part(part, { syncAttrs: true })`, flag
any literal key in that part's `props` object that matches a morfo-declared attr name for that part
(role / aria-* / type / the data-* WITH a value source). Hard part: associating the props block with the
part; a heuristic grep (`syncAttrs: true` in a class + `role:`/`aria-…:`/`type:` in its `props`) catches most.
- effort: medium-high; high value (a11y correctness).
## VG-4 · No check that `aria-activedescendant` (virtual focus) excludes real `dom.focus()` on items <!-- id: VG-004 -->
- finding: combobox-001 (A17 violation — virtual + real focus mixed).
- which validator: none.
- gap: A17 is doctrine; nothing flags a provider that both declares `aria-activedescendant` in its morfo AND
calls `dom.focus(item)` / `el.focus()` on its option/item elements.
- proposed: a lint — if a component's morfo declares `aria-activedescendant` on any part, its provider file
must not contain `dom.focus(` / `.focus()` on item-class elements (heuristic: `focus(` near `getItems`/
`items[`). Pair with a `perm:check` permutation that asserts `document.activeElement` stays on the input.
- effort: medium (lint heuristic) + the perm-runner assertion (higher fidelity).
## VG-5 · No check that morfo `focus.trap` is actually implemented <!-- id: VG-005 -->
- finding: select-002 (morfo `focus.trap:true` + `initial:'first-focusable'`, provider has no FocusScope).
- which validator: none.
- gap: the morfo `focus` block is declarative but nothing verifies the provider instantiates `FocusScope`
with a matching `trap`. Conversely a virtual-focus component (aria-activedescendant) declaring `trap:true`
is contradictory.
- proposed: (a) lint — if morfo `focus.trap:true`, the provider must reference `FocusScope.use`; (b) flag the
contradiction `aria-activedescendant` + `focus.trap:true` in the same morfo.
- effort: low-medium.
## VG-6 · `translationRef` key not validated against the `texts` map <!-- id: VG-006 -->
- finding: combobox-003 (passes the full idlangref `'#?components.combobox.toggle|Toggle'` as the key instead
of the `texts` key `'toggle'`).
- which validator: morfo:check / translations:check (catches missing runtime keys, but an absolute idlangref
resolves, so it stays silent).
- gap: `v.translationRef(key)` where `key` is not a declared `texts` entry (and especially when it's a literal
`#?…` idlangref) bypasses the indirection and leaves `texts` entries dead — not flagged.
- proposed: morfo lint — `v.translationRef(key, …)` `key` MUST be a key of the morfo's `texts` map; reject a
`key` that starts with `#?` (that's an absolute ref, belongs in `v.commonRef`/an idlangref constant, not
`translationRef`).
- effort: low.
## VG-7 · eidos-lint not enforced for undeclared parts <!-- id: VG-007 -->
- findings: select-005 (`data-select-indicator`/`item-indicator`), dialog-006 (`data-dialog-header`/`footer`).
- which validator: `scripts/eidos-lint.ts` — DOES classify `[data-{c}-{part}]` selectors as
morfo-backed / eidos-only / invalid, but it's opt-in (THEMING/TSC framing: "secondary safety net").
- gap: undeclared `data-{c}-{part}` selectors aren't blocked; there's no agreed disposition for legitimate
eidos-only decorative/layout parts.
- proposed: (a) run eidos-lint in CI with an allow-list file for sanctioned eidos-only parts;
(b) adopt SYS-6's convention (declare decorative parts `kind:'internal'` in the morfo) so the lint can be
strict.
- effort: low (wiring) + the per-component convention decision.
## VG-8 · `morfo:check` does not flag `: Morfo` instead of `as const satisfies Morfo` <!-- id: VG-008 -->
- finding: alert-dialog-001 (HIGH) — `export const alertDialogMorfo: Morfo = {…}` (the only morfo in 16 audited
using the annotation form).
- which validator: `morfo:check` / `npm run check` (TS compiles fine — `: Morfo` is valid TS, just lossy).
- gap: the `: Morfo` annotation widens literal types (kebabs, `v.literal(...)` values, part names) so
`compileMorfo`/`createAttrs` lose exact-key narrowing — silent, no error.
- proposed: a trivial lint/grep over `src/uix/morfo/components/*.ts` — every `export const *Morfo` must be closed
with `as const satisfies Morfo` and must NOT carry a `: Morfo` annotation. (The single cheapest high-value gap.)
- effort: trivial.
## VG-9 · No check for an undisposed `$effect.root` in a provider <!-- id: VG-009 -->
- finding: navigation-menu-006 (HIGH) — `openedAtEffect = $effect.root(...)` whose returned disposer is stored
but never called → a detached reactive root leaks per trigger instance.
- which validator: none.
- gap: `$effect.root` deliberately escapes the component effect scope and MUST be manually disposed; nothing flags
the case where the returned cleanup isn't wired into a teardown. (Legit uses are tests, which DO call the cleanup.)
- proposed: a lint — `$effect.root(` in a non-test `*-provider.svelte.ts` whose return value is assigned to a field
that is never invoked in a teardown (`$effect(() => () => field())` / dispose()) → flag. Heuristic grep:
`$effect.root` in a provider + no matching invocation of the assigned identifier.
- effort: medium.
## VG-10 · No check that a declared morfo event is ever fired (inert events) <!-- id: VG-010 -->
- finding: SYS-INERT (B3) — date/date-range/time/time-range/color-picker each declare `open` + `commit-reset`
events the provider never calls `runtime.trigger(...)` for (only `close` + field/area events are fired).
- which validator: none (`smoke`/`morfo:check` validate the morfo shape, not whether the provider exercises it).
- gap: an event declared in `morfo.events[]` but never reached by any `runtime.trigger('<name>')` in the provider
is dead contract — it exists only to pass the schema. Lead-confirmed by grep (trigger-call counts per picker).
- proposed: a lint that, per component, intersects `morfo.events[].name` with the set of string literals passed to
`runtime.trigger(...)` / `.trigger(...)` in `{kebab}-provider.svelte.ts`; warn on any declared event with zero
trigger sites. (Polymorphic `close` fired via `dismissWith` is reached — match the resolved name, or allow an
opt-out comment.)
- effort: medium. Catches a recurring picker-family contract gap.
## VG-11 · No check for `$effect`-wrapped parent-id registration (A30 doctrine) <!-- id: VG-011 -->
- finding: SYS-A30-EFFECT (B4) — date-field/time-field/color-field register `field.inputId.current = opts.id.current`
inside a `$effect` instead of a direct constructor assignment (number-field is the correct reference).
- which validator: none (`morfo:check`/`perm:check` don't inspect the registration shape; no loop fires today).
- gap: A30 mandates direct assignment for id registration; a `$effect` that writes a parent's `*Id.current` is the
latent-freeze shape even when currently write-only. Nothing flags it.
- proposed: a lint — a `$effect(...)` body whose only statement writes `<parent>.<x>Id.current = opts.id.current`
(a parent-id registration) → warn "use a direct constructor assignment (A30)". Composes with VG-9 ($effect.root).
- effort: medium.
## VG-12 · No check for `sequence:'pre'` event whose runtime handler sets the bound state (the lag class) <!-- id: VG-012 -->
- finding: collapsible-NEW-001 (HIGH) — `collapse` is `sequence:'pre'` and the runtime `events.collapse` handler does
`this.opts.open.current = false`; the runtime serializes emit-then-handler, so the state lags the ~240ms hold (the
documented Checkbox 244ms-lag class, which was fixed by flipping to `post`).
- which validator: none. `morfo:check` knows the `sequence` but not where the provider sets the state.
- gap: the doctrine "a control that sets functional state INSIDE the runtime.trigger handler must use `sequence:'post'`"
is enforced by humans only. A `pre` event whose `events: { <name>: () => { ...opts.X.current = ... } }` handler
mutates a bound state is a latent lag — undetected.
- proposed: a lint over each provider's `runtime(...)` `events` map: if `events.<name>` body assigns to an
`opts.*.current` / bound state AND the morfo declares that event `sequence:'pre'` → warn "set state at the call-site
or use `sequence:'post'` (lag)". (Skip `emerge.dismiss`/`close` events that drive a Presence/`data-event` exit — those
legitimately defer; the heuristic: flag only when no exit-animation path consumes the hold.) Pairs with `perm:check`
which could measure the click→state-change latency.
- effort: medium; high value (catches a perceptible-UX regression class the morfo author can rationalize wrongly).
## Notes
- VG-3, VG-4 (B1) and VG-8 (B2) are the highest-value mechanical wins (each catches a real shipped defect class).
VG-8 is the single cheapest — a grep would have caught alert-dialog-001. Re-evaluate as later batches recur.

@ -0,0 +1,36 @@
# Audit: accordion
audit-version: 1
audited-at: 2026-06-26
scope:
method: adversarially-verified workflow; HIGH lead-verified. B6 ground-truth: checkbox/toggle/switch commit-toggle = sequence post (lag-fixed); radio-group/tabs/accordion/stepper set state at call-site (pre OK); toggle-group + checkbox-group carry the A31 .includes pattern (SYS-7); slider has no gesture-layer A6 leak.
provider: G:\dev\svelte\vicen\src\uix\soma\components\accordion\accordion-provider.svelte.ts
sequence-audit: State set at call-site (line 104 in setValue) before events fire (lines 107-108). Morfo declares both 'open' and 'close' events as sequence='post'. State change precedes trigger emission; perceptual signal acknowledges resolved state, not delaying reveal. Compliant with intentional design documented
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0.
## Findings
_No findings survived verification (clean component)._
## No-findings dimensions
A: Morfo Contract, B: Behavior & Sequence, B: Roving Tabindex (A14), B: RTL Directional Keys (A12), B: Gesture & Observer Disposal (A15, A6), B: Hidden Input Form Participation (A13), B: ID Registration (A30), B: O(N²) Detection (A31), B: State Reactivity (A33), B: Keyboard Navigation, C: DOM Selector Safety, D: Soma-Eidos Frontier, E: TSC Token Composition, E-bis: Theming & Magic Literals, F: Test Coverage, G: Redundancy
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: toggle state coordination; single/multiple mode; keyboard navigation (roving triggers); aria-disabled when single non-collapsible; header/content props wired to item state; item registration/unregistration
- untested:
## Style observations (non-blocking)
- Morfo uses 'as const satisfies Morfo' (strict typecheck enabled)
- CSS variables follow --accordion-{slot} naming with role aliases for variants (outline|surface|ghost)
- ResizeObserver$ wraps observer disposal in $effect lifecycle
- Nested accordion support via closest() filter excludes siblings
- Parts registration matches morfo declarations (5/5: provider, item, header, trigger, content)
- Item id-registration direct (not $effect-wrapped)
- Keyboard routing uses getDirectionalKeys(dir, orientation) for RTL/LTR/orientation awareness

@ -0,0 +1,42 @@
# Audit: alert-dialog
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'eidos'] (composes/delegates close to Dialog; Provider is virtual)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: G:\dev\svelte\vicen\src\uix\soma\components\alert-dialog\alert-dialog-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 1 · MEDIUM 0 · LOW 0.
systemic hits: none.
## Findings
### HIGH: A Contract(morfo): morfo MUST be 'as const satisfies Morfo' — alert-dialog-001 <!-- id: alert-dialog-001 -->
- dimension: A
- rule: A Contract(morfo): morfo MUST be 'as const satisfies Morfo'
- location: G:\dev\svelte\vicen\src\uix\morfo\components\alert-dialog.ts:4
- evidence: export const alertDialogMorfo: Morfo = { ... }
- impact: Type narrowing degraded in createAttrs; morfo loses const assertion benefits and exhaustiveness checks
- repro: Build and check TypeScript diagnostics; createAttrs will not narrow string literal keys correctly
- proposed-fix: Change to: export const alertDialogMorfo = { ... } as const satisfies Morfo;
- verify: [confirmed] Read src/uix/morfo/components/alert-dialog.ts line 4 verbatim: `export const alertDialogMorfo: Morfo = {`. This is a bare type annotation, not the canonical `as const satisfies Morfo` form. The file ends at line 76 with `};` (no `as const satisfies Morfo` closer). Confirmed against the four baseline morfos which all use the canonical pattern: dialog.ts:15 `export const dialogMorfo = {` closing at :303 `} as const satisfies Morfo;`, popover.ts:4 closing at :251, drawer.ts:4 closing at :313, toggle.ts:22 closing at :143. The `: Morfo` annotation widens literal types (e.g. `kebab: 'alert-dialog'`, part kebabs `'provider'|'action'|'cancel'`, the `v.literal('button')` aria values) to their base types, degrading the exact-key narrowing compileMorfo/createAttrs depend on — exactly the rule-A violation described. Severity HIGH is appropriate: it is a contract-shape rule the morfo validators should catch.
- fix-status: fixed (98954a7c)
## No-findings dimensions
B, 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
## Tests (F)
- exists: true · env: jsdom
- covers: action/cancel data attribute projection; aria-label resolution and reactivity; onclick close delegation to Dialog; callback execution on button click
- untested: keyboard interaction (delegated to Dialog); focus trap/return (delegated to Dialog); Escape key behavior override (delegated to Dialog)
## Style observations (non-blocking)
- AlertDialog properly delegates all state/focus/keyboard to Dialog with variant='alertdialog' forced
- Escape key behavior override (close→ignore regression fix) is well-documented in README and implemented via wrapper
- Eidos Action/Cancel properly forward dialog intent to Button via snippet binding
- Action emits intent-driven color (risk→red), Cancel stays neutral—clear visual contrast matching WAI-ARIA guidance

@ -0,0 +1,47 @@
# Audit: announce
audit-version: 1
audited-at: 2026-06-26
scope:
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: src/uix/soma/components/announce/announce-provider.svelte.ts
cleanup-audit (A6/A35/A36): Line 75-77: Single $effect with cleanup return (clearTimers()). Disposes: politeTimer and assertiveTimer via cancel() if present (lines 149-161). Timers are scheduled via soma.uix.timers.schedule() (A6 compliant). Global createAnnouncer (global.svelte.ts): timers stored in RegionPair.timer and cance
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 0.
## Findings
### MEDIUM: eidos CSS selectors must match morfo-declared or runtime-auto-generated attributes. A decl — announce-001 <!-- id: announce-001 -->
- dimension: E-bis: Theming / Selector drift
- rule: eidos CSS selectors must match morfo-declared or runtime-auto-generated attributes. A declared prop like `visuallyHidden` must have a corresponding data attribute or inline style to drive styling.
- location: src/uix/eidos/components/announce/announce.css:23
- evidence: Line 23 uses selector `[data-announce-region]:not([data-visually-hidden])` to style visible regions. However, `data-visually-hidden` attribute is never written by soma. The provider (announce-provider.svelte.ts:246) writes inline `style` when hidden, but never writes a `data-*` attribute to signal visibility state to eidos.
- impact: The CSS selector `:not([data-visually-hidden])` always matches because the attribute is never present. This means card styling (display, padding, border, etc.) is incorrectly applied to ALL regions, including those where `visuallyHidden=true` (which should be sr-only with no card chrome).
- repro: Mount an Announce.Region with `visuallyHidden={false}`. The region will receive card styling (padding, border, etc.). Mount another with `visuallyHidden={true}` (default). It will ALSO receive card styling, breaking the sr-only case.
- proposed-fix: Option A: Modify AnnounceRegionProvider.props (line 242-248) to write `data-visually-hidden: hidden ? '' : undefined` so the CSS `:not([data-visually-hidden])` selector works correctly. Option B: Change the CSS selector to check for inline style presence (not reliable) or add a separate `data-visible` attribute when visuallyHidden is false. Recommend Option A with a morfo data declaration for the attribute.
- verify: [downgraded] MECHANISM CONFIRMED, IMPACT REFUTED. Confirmed: `data-visually-hidden` is NEVER written anywhere. Morfo (announce.ts:88-99) declares only `data-role`+`data-live` on the `region` part; the provider (announce-provider.svelte.ts:243-247) drives `visuallyHidden` ONLY via inline `style: hidden ? VISUALLY_HIDDEN_STYLE : undefined`; grep across soma+eidos finds no writer of the attr (announce.css:23 is the sole reference). So `[data-announce-region]:not([data-visually-hidden])` (announce.css:23) ALWAYS matches — the negation is dead/non-functional CSS; the 'visible regions only' guard the author intended never excludes the sr-only case. THAT part is a real selector-drift defect (E-bis), MEDIUM stands. BUT the candidate's IMPACT ('incorrectly applied to ALL regions ... breaking the sr-only case') is REFUTED: when visuallyHidden=true, soma sets the inline sr-only style (`position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)`) which has higher specificity than the stylesheet rule and overrides `padding:0`/`border:0` inline (lines 12-22). The leftover `display:block`/`background`/`border-radius`/`font-size`/`line-height` from the CSS block are visually inert on a 1x1px clipped box — the region stays hidden. So the sr-only case is NOT broken; the defect is dead CSS / a non-functional visible-vs-hidden distinction, not an a11y/visual regression. Keeping MEDIUM but correcting the rationale/impact.
- fix-status: open
### MEDIUM: eidos CSS selectors must key off attributes the morfo declares or soma actually writes; a — announce-101 <!-- id: announce-101 -->
- dimension: E-bis: Theming / selector drift, SYS-1-adjacent
- rule: eidos CSS selectors must key off attributes the morfo declares or soma actually writes; a `:not([attr])` guard against an attribute that is never emitted is dead CSS that silently never differentiates state.
- location: src/uix/eidos/components/announce/announce.css:23
- evidence: `[data-announce-region]:not([data-visually-hidden]) { display:block; padding:...; border:...; background:...; }` — but `data-visually-hidden` is never written: announce.ts:88-99 declares only `data-role` and `data-live` on the region part, and announce-provider.svelte.ts:243-247 drives `visuallyHidden` exclusively via inline `style` (`hidden ? VISUALLY_HIDDEN_STYLE : undefined`), never a data attribute. Grep across soma+eidos confirms no writer.
- impact: The intended 'visible regions only' branch is non-functional: the `:not()` negation always passes, so the visible-card rule applies to every region. It is masked in the default (visuallyHidden=true) case because the higher-specificity inline sr-only style clips the box, so there is no user-visible regression today — but the CSS no longer expresses the visible-vs-hidden intent and will silently mis-style if the inline style ever changes. Dead-code distinction.
- repro: Mount `<Announce.Region visuallyHidden={false}>` and `<Announce.Region visuallyHidden={true}>`; inspect computed styles — the visible-card rule (display:block/background/font-size) resolves on BOTH because `:not([data-visually-hidden])` matches both; only the inline sr-only clip differentiates them, not the CSS branch as intended.
- proposed-fix: Either (a) project the state as a real attribute the CSS can key on — e.g. declare `data-visually-hidden` in the region part's morfo `data` and have soma write it from `opts.visuallyHidden`, then keep `:not([data-visually-hidden])`; or (b) gate the visible-card chrome on an opt-in attribute the consumer sets (e.g. `data-variant='visible'`) rather than a negation of a never-present attr. Option (a) keeps the existing selector working.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
## No-findings dimensions
A: Contract (morfo), B: Behavior (A35/A36/A6/A15/live-regions/A30/A33/A31), C: DOM-selector, D: Frontier, E: TSC, F: Tests, G: Redundancy
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom via vitest
- covers: timer scheduling and cancellation via soma.uix.timers; A/B toggle for repeated announcements; role/aria-live derivation; declarative region attrs sync; standalone region without provider; provider context context injection
- untested: visible region CSS application (visuallyHidden=false styling); async timer edge cases under network throttle; cross-document announcer (createAnnouncer in iframe/shadow DOM)

@ -0,0 +1,19 @@
# Audit: aspect-ratio
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract OK, no recipe entry, CSS-driven via --aspect-ratio variable, child fill via 100% inline/block-size.
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: auto-grid
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract OK, composes through Grid, no recipe entry (marker selector only, template computed in JS).
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: avatar-group
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), layout-only (no events), stacking modes (side-by-side/overlap/overlap-reverse), local z-index OK within component.
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 1 @ avatar.css:362 -> z-index (LOCAL scope, stacking order for overlap-reverse) | 20 @ avatar.css:366 -> --avatar-group-reverse-z-max (defined in recipe, canonical) | -0.35 @ avatar.css:313-329 -> overlap calc (local layout multiplier, acceptable) | -0.25 @ avatar.css:263,268,273,278 -> translate for badge (local 45deg geometry)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: avatar
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), 3 variants (solid/soft/outline) × 8 colors via TSC v2.2 matrix, radius + ring support, no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 1px @ avatar.css:96,113 -> z-index (LOCAL scope, image layering within component) | 0 @ avatar.css:113 -> z-index (LOCAL scope, fallback layering) | 2 @ avatar.css:230 -> z-index (LOCAL scope, badge absolutely positioned) | 0.26 @ avatar.css:227 -> multiplier for badge dot sizing (local calc, acceptable) | 0.3 @ avatar.css:226 -> multiplier for badge sizing (local calc, acceptable) | 0.22 @ avatar.css:243 -> multiplier for font-size (local calc, acceptable) | white @ avatar.css:138,287 -> fallback contrast (physical constant) | 25% @ avatar.css:263-278 -> translate offset for badge position (local geometry, acceptable) | 30% @ avatar.css:292 -> color-mix blend (physical constant)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: badge
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), 8-role palette cascade via TSC v2.1, 4 variants (soft/solid/outline/ghost), no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 0.5em @ badge.css:325 -> --badge-dot-size (em allowed for internal scaling) | 1em @ badge.css:326 -> --badge-icon-size (em allowed for internal scaling) | 1.25em @ badge.css:327 -> --badge-remove-size (em allowed for internal scaling) | 0.125em @ badge.css:328 -> --badge-remove-margin-start (em allowed for internal scaling) | 15% @ badge.css:178 -> color-mix blend (physical constant, acceptable)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: banner
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), full-bleed layout primitive (no events), 8-intent roles (primary/secondary/neutral/affirm/fulfill/risk/threat/loss), 4 variants (soft/solid/outline/ghost), 3 sizes, no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 1.4 @ banner.css:29 -> line-height (named --font-line-height-sm fallback, acceptable)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: box
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract OK, data-box-area presence-flag gates grid-area shorthand, 'revert-layer' policy for all cascade defaults.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,42 @@
# Audit: breadcrumb
audit-version: 1
audited-at: 2026-06-26
scope:
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: G:/dev/svelte/vicen/src/uix/soma/components/breadcrumb/breadcrumb-provider.svelte.ts
cleanup-audit (A6/A35/A36): EFFECT (line 173-180): $effect writing item.isCurrent with cleanup that resets to false. No listener/observer. No resources held. Disposer verified present: return () => { item.isCurrent = false; } ✓. A35/A36 loop check: SAFE — isCurrentRaw is $derived (acyclic); writing item.isCurrent does not caus
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 1 · MEDIUM 0 · LOW 0.
## Findings
### HIGH: Archetype 'item' is reserved for selectable/interactive row parts; display items should om — breadcrumb-001 <!-- id: breadcrumb-001 -->
- dimension: A: Contract(morfo)
- rule: Archetype 'item' is reserved for selectable/interactive row parts; display items should omit archetype
- location: G:/dev/svelte/vicen/src/uix/morfo/components/breadcrumb.ts:50
- evidence: Item part declares `archetype: 'item'` but Item is a display `<li>` container (not selectable). Timeline.ts explicitly documents: "No `archetype: 'item'` — that archetype is for selectable menu/listbox/option rows... A timeline entry is a display `<li>`, structurally unique, so it omits the archetype." Breadcrumb.Item is semantically identical to Timeline.Item.
- impact: Incorrect archetype declaration violates the 2-of-3 rule (archetype field inconsistent with actual DOM semantics). May confuse CSS-side consumers expecting interactive row styling (cursor:pointer, select, hover). Runtime validator should flag this.
- repro: none (static violation)
- proposed-fix: Remove `archetype: 'item'` from the Item part definition (line 50). Item is a display container, not a selectable option.
- verify: [confirmed] CONFIRMED. breadcrumb.ts:48-56 declares the Item part with `archetype: 'item'` (line 50) on a display container `<li>` (`defaultElement: 'li'`, no role, not selectable). The runtime stamps `data-archetype='item'` on the element whenever the morfo sets it (morfo/types.ts:720 'the runtime emits `data-archetype="..."` on the part's DOM element via `partProps`'; README:177 same). That `[data-archetype='item']` selector triggers archetypes.css:139-153 — `cursor: pointer; user-select: none; min-block-size: var(--list-item-height, auto); padding-block: var(--list-item-py, var(--space-1-5))` — PLUS the hover/highlight band at archetypes.css:161-171 (`background-color: var(--color-surface-raised)` painted on the WHOLE `<li>` on hover). breadcrumb.css:36-41 styles `[data-breadcrumb-item]` only with `display:inline-flex; align-items:center; min-inline-size:0` — it does NOT reset cursor/padding-block/hover, so the styling is unmitigated. This is the exact documented bug: Timeline.ts:88-91 ('No `archetype: 'item'` — that archetype is for selectable menu/listbox/option rows (it ships cursor:pointer, user-select:none, internal padding + a hover highlight). A timeline entry is a display `<li>`, structurally unique, so it omits the archetype.'), and Calendar.ts:247-249 / 270-273 + range-calendar.ts:285 repeat the same doctrine for display `<tr>`/`<td>`. Breadcrumb.Item is semantically identical to Timeline.Item. Real visible regression: a breadcrumb `<li>` gets `cursor:pointer`, `padding-block: var(--space-1-5)`, and a surface-raised hover band over the whole crumb row. HIGH is correct per the rubric's 'archetype-pulls-wrong-styling on a real part'. Fix: remove `archetype: 'item'` from breadcrumb.ts:50 (the Item is a display container; the interactive Link inside owns its own affordance).
- fix-status: fixed (98954a7c)
## No-findings dimensions
B: Behavior(soma): A35/A36 loops absent (effect mirrors current to item without cycle; no async state writes), B: A6 resource cleanup (no timers/listeners/observers), B: A31 O(N²) derivations absent, B: A30 child->parent id registration (uses $effect correctly), B: A33 state(Map/Set) cloning (no Maps/Sets used), C: DOM-selector (no querySelector with user values), D: Frontier (no soma->eidos imports), E-bis: Theming (recipe uses canonical --space-*, --font-size-*, --duration-* tokens; focus-ring uses canonical CSS var), F: Tests environment jsdom (provider test exists, covers label projection, current mirroring, ellipsis semantics), G: Passive component justified in README (zero state, zero keyboard nav, zero events by design)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom (@vitest-environment jsdom)
- covers: Provider label projection and resolution (BREADCRUMB_LANGS); Link.current state mirroring to Item.isCurrent; aria-current conditional emission (true/'page'/'step'); renderSpan conditional tag switching; Ellipsis decorative->interactive semantics via interactive prop; aria-haspopup conditional on interactive; role=presentation conditional on !interactive
- untested: E2E browser interaction (Playwright): full click/focus flow, ARIA tree, CSS targeting; Eidos variant/size responsive resolution (ActiveEidos.resolve); Ellipsis with DropdownMenu composition (documented example in README)
## Style observations (non-blocking)
- Link focus-visible uses canonical --focus-ring-width and --focus-ring-color vars (breadcrumb.css:61-64, 107-109) ✓
- Ellipsis interactive and link hover transitions use canonical --breadcrumb-transition-duration and --breadcrumb-transition-ease (line 52-54, 94-98) ✓
- Recipe declares all semantic spacing (gap-xs/sm/md/lg → var(--space-*)) and sizing (font-size-xs/sm/md/lg, ellipsis-size → var(--control-height-xs)) ✓
- no hardcoded hex colors; all use role aliases (--breadcrumb-color, --breadcrumb-link-color, etc.) ✓

@ -0,0 +1,26 @@
# Audit: button-group
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (composite-and-service), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: Contract: 'as const satisfies Morfo' ✓ | Scope: eidos ✓ | Roles: none/N-A ✓ | Variants: data-orientation, data-disabled only; propagated to children ✓ | Sizing/Tokens: all via recipe ✓ | Local z allowed ✓
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
### LOW: MAGIC_LITERAL where canonical scale exists — button-group-001 <!-- id: button-group-001 -->
- dimension: MAGIC_LITERAL
- location: src/uix/eidos/lib/recipes/base.ts:4384
- evidence: 'divider-strength': '18%'
- impact: Bare percentage where --opacity-* scale could be canonical; though this is a specialized mix-opacity control, the 18% literal bypasses the established opacity scale
- proposed-fix: Consider documenting whether this 18% is a one-off interaction opacity or should reference an opacity token if theming consistency is desired
- verify: [downgraded] Confirmed literal at base.ts:4384 'divider-strength': '18%'. But this is a color-mix percentage (currentColor mix for the segmented seam, css:93), not an --opacity-* element-opacity case. The recipe comment (4381) explicitly flags it as 'the lone magic number'. color-mix proportion has no canonical token; LOW cosmetic, not opacity-scale drift.
- fix-status: open
## Theming facts (E-bis)
- magic literals: 18% @ src/uix/eidos/lib/recipes/base.ts:4384 -> should document opacity rationale or reference token | 1 @ src/uix/eidos/components/button-group/button-group.css:98,102 -> local z acceptable | 2 @ src/uix/eidos/components/button-group/button-group.css:106 -> local z acceptable
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,30 @@
# Audit: button
audit-version: 1
audited-at: 2026-06-26
scope: soma,sema,eidos
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: /g/dev/svelte/vicen/src/uix/soma/components/button/button-provider.svelte.ts
cleanup-audit (A6/A35/A36): No timers, listeners, observers, or cleanup needed. ButtonProvider uses no setTimeout, setInterval, ResizeObserver, IntersectionObserver, MutationObserver, or other disposable resources. All state is reactive via $derived.by() with no effect roots. No $effect blocks with side effects. A35/A36 loop c
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0.
## Findings
_No findings survived verification (clean component)._
## No-findings dimensions
B, C, D, F, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: false · env: N/A
- covers:
- untested: ButtonProvider initialization and onclick flow; Intent → color cascade resolution (intent wins over color, neutral fallback); Field provider OR-merge of disabled state; Loading state gating of contact-activate event; onPress callback invocation; data-color, data-loading, data-disabled attribute synchronization
## Style observations (non-blocking)
- Focus-ring implementation (button.css 99-107) correctly uses outline with --focus-ring-* tokens and does NOT hardcode any two-ring composite — single primary-tinted ring is intentional per comment. :active (button.css 90-97) correctly uses scale(var(--press-scale)) with canonical press tokens, not a hardcoded translateY. Variant slice pattern (158-212) uniformly applies via palette variables for soft/surface/outline/ghost/plain — no per-variant hardcoding. Icon sizing correctly feeds --icon-size from --_button-icon-size which cascades per size (lines 30-35, 111-154).

@ -0,0 +1,111 @@
# Audit: calendar
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work).
provider: src/uix/soma/components/calendar/calendar-provider.svelte.ts
> **MID-REFACTOR CAVEAT:** this component has uncommitted view-switch changes on this branch (M/D/?? files). Findings reflect the current in-flight state; treat structural inconsistencies as in-progress, not shipped defects.
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 2.
systemic hits: SYS-1 scope-drift (eidos recipe exists, morfo scope omits 'eidos').
## Findings
### MEDIUM: SYS-1: eidos recipe directory exists but morfo scope omits 'eidos' — calendar-002 <!-- id: calendar-002 -->
- dimension: A
- rule: SYS-1: eidos recipe directory exists but morfo scope omits 'eidos'
- location: src/uix/morfo/components/calendar.ts:7
- evidence: scope: ['soma', 'sema'],
- impact: The eidos directory at src/uix/eidos/components/calendar/ exists (with calendar.css, components, types.ts), but morfo declares scope as ['soma', 'sema'] without 'eidos'. Per known systemic SYS-1, this is acceptable if the eidos layer is optional or only consumed via composition.
- proposed-fix: Verify with team: is eidos-layer consumption optional? If mandatory, update scope to ['soma', 'sema', 'eidos'].
- verify: [confirmed] Confirmed at calendar.ts:7 `scope: ['soma', 'sema'],` omits 'eidos', yet a full eidos layer exists: recipe block `calendar:` at base.ts:1626 (60+ tokens) AND src/uix/eidos/components/calendar/ contains calendar.css + ~25 .svelte wrappers + recipe consumption. This is exactly the known systemic SYS-1 scope-drift (MEDIUM). The candidate's framing as 'acceptable if optional' is the SYS-1 baseline narrative; the drift itself is real and matches the documented systemic pattern.
- fix-status: fixed (212624e0)
### MEDIUM: Magic literal in recipe token: '0.62' opacity not from canonical scale — calendar-004 <!-- id: calendar-004 -->
- dimension: E-bis
- rule: Magic literal in recipe token: '0.62' opacity not from canonical scale
- location: src/uix/eidos/lib/recipes/base.ts:1688
- evidence: 'day-outside-opacity': '0.62',
- impact: Opacity value 0.62 is hardcoded; no reference to a canonical --opacity-* token from the static scale. Day-outside-month opacity is not unified with other opacity tokens.
- proposed-fix: Map to a canonical --opacity-* token (e.g. --opacity-60 if the scale includes it), or introduce a new token for calendar-specific opacity levels.
- verify: [confirmed] Confirmed `'day-outside-opacity': '0.62',` at base.ts:1688. Verified STATIC_OPACITY (static.ts:255-275) is a 0.05-step scale: --opacity-60=0.6, --opacity-65=0.65 — 0.62 maps to NO canonical step. Per E-bis 'bare opacity decimal -> a --opacity-{0..100} scale exists' this is a magic literal (MEDIUM). Note: the same 0.62 design value appears as '62%' literals at base.ts:729 and 895 (other components' overlay-opacity), so it is an established off-scale brand value with no token — but the rule still flags the bare decimal. Confirmed at MEDIUM.
- fix-status: open
### MEDIUM: Test coverage gap: keyboard navigation routes not exercised in jsdom environment — calendar-005 <!-- id: calendar-005 -->
- dimension: F
- rule: Test coverage gap: keyboard navigation routes not exercised in jsdom environment
- location: src/uix/soma/components/calendar/calendar-provider.svelte.test.ts
- evidence: Test file has 217 lines, 7 tests. No tests for handleDayKeydown, ArrowRight/ArrowLeft/ArrowUp/ArrowDown, Home/End, PageUp/PageDown, focus management, or tabindex routing. All tests run in jsdom environment.
- impact: Critical interaction paths (APG GRID keyboard navigation: arrows, Home/End, PageUp/PageDown + shift, focus roving) are NOT tested. Keyboard behavior regressions would not be caught.
- proposed-fix: Add browser-level (Playwright/client) test suite covering:
- handleDayKeydown for each KEYS.*
- focus tabindex management (focused day has tabindex=0, others -1)
- week navigation (ArrowUp/Down by 7)
- month/year navigation via PageUp/PageDown +shift
- placeholder update when target outside visible months
- two-moments ordering (value.current write before trigger)
- verify: [confirmed] Confirmed: calendar-provider.svelte.test.ts is `// @vitest-environment jsdom` (line 1), 7 tests, all exercising pure helpers + bounds-validation logging. NONE drive handleDayKeydown (the APG grid keyset Arrow x4 / Home / End / PageUp+PageDown / shiftKey at provider lines 470-521), roving tabindex (line 1124), focus management, or two-moments ordering (value write at 436/443 before runtime.trigger at 445). This is the documented SYS-3 (jsdom-only, kbd-untested). The provider HAS rich, branchy keyboard + focus code that is entirely unexercised — genuine high-risk coverage gap. Confirmed at MEDIUM (matches SYS-3 baseline severity).
- fix-status: open
### LOW: DOM querySelector selector interpolation must use CSS.escape for untrusted values — calendar-001 <!-- id: calendar-001 -->
- dimension: C
- rule: DOM querySelector selector interpolation must use CSS.escape for untrusted values
- location: src/uix/soma/components/calendar/calendar-provider.svelte.ts:514-516
- evidence: const el = root.querySelector<HTMLElement>(
`[data-calendar-day][data-value="${target!.toString()}"]`
);
- impact: If target.toString() ever produces values with CSS selector metacharacters (e.g. quotes, brackets), the selector could break or be unsafe. DateValue.toString() should be ISO-safe, but the pattern violates the defense-in-depth rule requiring CSS.escape().
- proposed-fix: const el = root.querySelector<HTMLElement>(
`[data-calendar-day][data-value="${CSS.escape(target!.toString())}"]`
);
- verify: [downgraded] Confirmed the selector exists at calendar-provider.svelte.ts:514-516: `root.querySelector<HTMLElement>(\`[data-calendar-day][data-value="${target!.toString()}"]\`)`. `target` is a DateValue produced by date arithmetic (shiftDate/subtract/add), NOT a consumer-supplied string — DateValue.toString() emits an ISO date (e.g. '2026-05-15') with zero CSS metacharacters, ever. The rule about CSS.escape targets UNTRUSTED/consumer-derived values; this is a framework-derived ISO token that cannot contain a quote/bracket. Same pattern at line 173 (`[${attrs.day}][data-focused]`) uses no interpolation. Real defect risk = none; defense-in-depth nicety only. Not HIGH. Downgraded to LOW.
- fix-status: open
### LOW: Magic literal in recipe token: '8ch' not referenced as a canonical size — calendar-003 <!-- id: calendar-003 -->
- dimension: E-bis
- rule: Magic literal in recipe token: '8ch' not referenced as a canonical size
- location: src/uix/eidos/lib/recipes/base.ts:1663
- evidence: 'select-min-width': '8ch',
- impact: Character unit (ch) is hardcoded; no corresponding token in a canonical size scale. This token controls the month/year select minimum width and is not derived from --space-* or --control-height-* primitives.
- proposed-fix: Either introduce a canonical token (e.g. --select-width-base: 8ch) and reference it, or document this as a component-specific sizing exception.
- verify: [downgraded] Confirmed `'select-min-width': '8ch',` at base.ts:1663. Per E-bis, font-size/icon-size/spacing/radius MUST reference canonical scales, but a min-width is sizing dimension with no canonical `ch`-based scale — `--space-*` is the wrong axis (a min-width in character units intentionally tracks glyph width of the month/year select, not the spacing rhythm). `ch` is a legitimate CSS unit for text-box min sizing and there is no `--{c}` token it should reference because no canonical width-in-ch scale exists. This is a named recipe token (component-scoped `--_calendar-select-min-width`), already the canonical home for a one-off dimension. Not a px/rem font-size literal. Token-naming nit at most; downgraded to LOW.
- fix-status: open
### MEDIUM: Home/End morfo action label ('first/last-day-of-week') vs provider impl (start/end-of-month) — SYSTEMIC with range-calendar <!-- id: calendar-006 -->
- dimension: A, B
- rule: morfo↔code drift + APG Date Picker Dialog (Home/End move within the WEEK, not the month)
- location: calendar.ts:76-77 (`{key:'Home', action:'first-day-of-week'}`, `{key:'End', action:'last-day-of-week'}`) vs calendar-provider.svelte.ts:481-485 (`Home → startOfMonth(date)`, `End → endOfMonth(date)`)
- evidence: the morfo action labels say *day-of-week* but the provider navigates to *start/end of month*. Surfaced by the range-calendar agent (range-calendar-002); the verify confirmed calendar does the IDENTICAL thing → systemic, not range-calendar-specific. (The calendar agent's own style-obs even claims "Home/End all implemented" — it saw the handlers but didn't check the label-vs-behavior semantics.)
- impact: either (a) an APG deviation — WAI-ARIA Date Picker Dialog defines Home/End as first/last day of the current WEEK and PageUp/PageDown as month — so Home jumping to the first of the *month* is non-standard; or (b) the morfo label is stale. Two-component contract/keyboard drift.
- repro: focus mid-month in a Calendar grid, press Home → focus jumps to day 1 of the month (APG expects first day of the current week).
- proposed-fix: decide intended behavior. APG-week → change provider to first/last-day-of-week within the visible row; month variant → rename the morfo action to `first/last-day-of-month` so label and behavior agree. Apply to BOTH calendar + range-calendar.
- verify: [lead-added] elevated from the verify's LOW on range-calendar-002 (it downgraded because "not range-calendar-specific" — that shared-ness is exactly why it's systemic MEDIUM). Both providers confirmed via the verify cross-reference.
- fix-status: open
## No-findings dimensions
B (loop/roving math is correct — modulo with guards), D, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 8ch | 0.62
- undeclared parts: none
- roles clean: true · variants clean: true
- conformance: Data attributes and CSS color roles align with the canonical 9-role system (primary, secondary, neutral, affirm, fulfill, risk, threat, loss). No undeclared roles or unapproved role names detected.
## Tests (F)
- exists: true · env: jsdom only
- covers: placeholder resolution from selection; month grid generation; time preservation in date selection; multiple selection toggle and maxDays enforcement; date flags (disabled, unavailable, holiday) independence; selection/view bounds validation; invalid bounds logging
- untested: handleDayKeydown routing for all 10 keyboard actions (ArrowRight/Left/Up/Down, Home, End, PageUp, PageDown, Enter, Space); focus management and tabindex roving; placeholder update and monthchange when target outside visible months; announcement generation for navigation; two-moments: value.current write before trigger ordering; view switching (day ↔ month ↔ year); readonly behavior (select blocked, focus+nav allowed); disabled behavior (all interaction blocked)
## Style observations (non-blocking)
- Keyboard event handling is well-structured with proper directional key resolution for ltr/rtl.
- APG GRID keyboard actions (arrows, Home/End, PageUp/PageDown + shift) are all implemented in handleDayKeydown.
- Focus management uses queueMicrotask to ensure DOM updates before focus shift.
- Morfo `as const satisfies Morfo` is correctly applied at line 357.
- Parts registered via runtime.part() (provider, header, heading, prev-button, next-button, month-select, year-select, grid, grid-head, grid-body, grid-row, head-cell, cell, day) align with morfo declarations.
- No soma imports in provider (CRITICAL rule passes).
- Date utilities correctly use $libs/days, not @internationalized/date.
- No evidence of $effect.root without disposal, setTimeout/setInterval, or memory leaks.
- CSS role attributes (application, grid, button, row, gridcell, header, label) are declared and rendered correctly.
- aria-readonly and aria-disabled follow propRef + ariaBoolean pattern consistent with framework conventions.

@ -0,0 +1,19 @@
# Audit: card
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), 8-role palette, 4 variants (soft/solid/outline/ghost), interactive + selected states, no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 8px @ card.css:521 -> --card-emerge-distance (named token in recipe) | -2px @ card.css:522 -> --card-hover-lift (named token in recipe) | white @ card.css:152 -> --card-color-custom-contrast (fallback for custom color, acceptable)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,60 @@
# Audit: carousel
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH lead-verified by direct read of the cited code. B5 ground-truth: the A31 O(N²) isSelected/isExpanded (.includes from a per-item $derived) is confirmed across listbox/grid-list/tree-view/tree-grid/tag-group (SYS-7); rovingTargetEl is correctly LIFTED everywhere (not A31); virtual-* use SvelteMap (A33-clean).
provider: G:\dev\svelte\vicen\src\uix\soma\components\carousel\carousel-provider.svelte.ts
reactivity (A31/A33/A35): "CLEAN: Per-item isActive derivations read global state directly (opts.value.current), not via method calls—O(1) per item, not O(N²). No $state(Map/Set) usage (A33 clean). ItemGroup effect at line 529–540 is component-level drag tracking, not per-item—no A35 loop risk. Ref registrations via onRefChange (lines 487, 522), not $effect—A30 clean. ResizeObserver and autoplay timer both disposed via eff
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 1.
## Findings
### MEDIUM: SYS-1 (Scope-drift) — carousel-001 <!-- id: carousel-001 -->
- dimension: A
- rule: SYS-1 (Scope-drift)
- location: src/uix/morfo/components/carousel.ts:7
- evidence: Morfo declares scope: ['soma', 'sema'] but eidos directory exists at src/uix/eidos/components/carousel/ with full recipe and visual components
- impact: Eidos concerns (visual/size/variant/color/block attrs) are implemented but not declared in morfo scope, creating scope ambiguity
- proposed-fix: Add 'eidos' to morfo scope: scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] Confirmed at src/uix/morfo/components/carousel.ts:7 — `scope: ['soma', 'sema'],` with NO 'eidos', while a full eidos implementation exists: src/uix/eidos/components/carousel/carousel.css is 10912 bytes plus types.ts, context, and 8 visual .svelte wrappers (carousel-item.svelte, carousel-viewport.svelte, etc.). This is exactly the SYS-1 scope-drift pattern: eidos materializes visual/size/variant attrs the morfo scope doesn't declare. Severity MEDIUM is correct (matches baseline). Note this is broadly systemic on this branch — calendar.ts:7, color-picker.ts:7, combobox.ts:7, command.ts:7, dialog.ts:18 and others all declare ['soma','sema'] yet ship eidos dirs — so the fix should likely be a sweep, but the carousel finding stands on its own evidence.
- fix-status: fixed (212624e0)
### MEDIUM: SYS-3 (jsdom-only test of interaction-heavy path) — carousel-102 <!-- id: carousel-102 -->
- dimension: F
- rule: SYS-3 (jsdom-only test of interaction-heavy path)
- location: src/uix/soma/components/carousel/carousel-provider.svelte.test.ts:1
- evidence: `// @vitest-environment jsdom`. The suite covers trigger/keyboard nav, autoplay timer + pause states, viewport-geometry props and timer disposal (line 370), but the DRAG path is entirely untested: finishDrag() (provider lines 389-415) computes fraction/velocity/fastSwipe + RTL-mirrored direction, and the ItemGroup drag $effect (lines 529-540) writes dragOffset from gesture.offsetX/Y. None of fraction-threshold, fastSwipe (velocity>0.5), or the `isRtlHorizontal` mirror (line 411-412) is exercised.
- impact: Swipe-to-advance (the carousel's primary touch interaction) and its RTL direction-flip have zero regression coverage; a sign or threshold regression in finishDrag would ship silently.
- proposed-fix: Add a unit test that drives the gesture via the provider's finishDrag()/dragOffset surface with synthetic offset+velocity across LTR/RTL and vertical axes; assert the resulting next()/prev() and dragOffset reset.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
### LOW: A10 (getItems must filter nested via .closest === root) — carousel-101 <!-- id: carousel-101 -->
- dimension: B
- rule: A10 (getItems must filter nested via .closest === root)
- location: src/uix/soma/components/carousel/carousel-provider.svelte.ts:169-173
- evidence: getItems() returns `Array.from(group.querySelectorAll<HTMLElement>(`[${attrs.item}]`))` with no `.closest('[data-carousel]') === group` filter. A carousel nested inside another carousel's slide would have its inner `[data-carousel-item]` elements counted by the OUTER provider's querySelectorAll, inflating `slideCount` (line 175) and corrupting the outer carousel's clamp/loop math (commit() uses slideCount at line 266).
- impact: Only manifests when carousels are nested (rare). When it happens, the outer carousel mis-counts slides → wrong lastSnap clamp, wrong canGoNext, broken End-key target. No crash, no leak.
- repro: Render a Carousel whose slide contains another Carousel; observe outer provider.slideCount includes inner slides.
- proposed-fix: Filter to direct items: `Array.from(group.querySelectorAll('[data-carousel-item]')).filter(el => el.closest('[data-carousel-item-group]') === group)` — mirrors the A10 pattern used elsewhere.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
## No-findings dimensions
B-A31, B-A33, B-A35, B-A30, B-A6, B-A12, B-A14, B-A18, B-A10, C, D-soma-imports-eidos, E-tokens, E-bis-theming, F-test-structure, G-redundancy
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: Navigation by trigger (next/prev), keyboard (Arrow keys, Home, End), and indicator click; Loop wrapping math (lastSnap calculation, modulo wrapping for loop mode, clamping for non-loop); Disabled state guard (keyboard/click/programmatic blocked); Autoplay lifecycle via uix.timers.interval with proper disposal on effect cleanup; Pause/resume on hover/focus state transitions; Viewport/ItemGroup/Item/Indicator props projection and data-state/data-index attrs; User interaction stops autoplay when stopOnInteraction=true; Indicator multi-click and aria-selected toggle
- untested: Virtualization edge cases (very large slide count with multiple slidesPerView); RTL keyboard navigation and transform direction mirroring with dragging; Drag gesture offset tracking and finishDrag snap-point logic; ResizeObserver responsiveness to viewport resize/orientation flip; Vertical orientation layout and indicator group flex direction
## Style observations (non-blocking)
- CSS recipe uses canonical 9-role color system with -solid/-element variants (primary, secondary, affirm, risk, threat, fulfill, loss, neutral). All spacing via --space-* and radius via --radius-*. Opacity uses --opacity-disabled. Z-index: 1 is local-relative within carousel subtree (triggers), acceptable per audit rules. Indicator size sizes with var(--_carousel-indicator-size) cascaded from --space-* + explicit px multiples in data-size breakpoints.
- Vertical orientation layout correctly flips root flex-direction to row with viewport taking flex: 1 1 auto and indicator-group as narrow flex: 0 0 auto column on right, matching common pattern in Mantine/ark-ui.

@ -0,0 +1,19 @@
# Audit: cascade
audit-version: 1
audited-at: 2026-06-26
scope: ['eidos'] (eidos-only primitive)
method: theming-coherence category sweep (infra), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: eidos foundation primitive; 'as const satisfies' ✓; passive; no recipe/CSS; structural.
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,26 @@
# Audit: chart
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: Contract solid (as const), display-only (no events), SVG frame + marks + legend + title parts, canonical token recipe, no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
### LOW: Magic literal in SVG stroke-dasharray — chart-001 <!-- id: chart-001 -->
- dimension: literal
- location: chart.css:93
- evidence: `stroke-dasharray: 3 3` @ line 93. This is a visual pattern (dashed line) for the crosshair overlay, not a component dimension or theme token.
- impact: Physical constant for visual pattern. Does not break theming. Local to chart component only.
- proposed-fix: No fix needed. Per baseline: '3.5px @keyframes amplitude' kind of literal is NOT drift; `3 3` dasharray pattern falls into same category.
- verify: [confirmed] chart.css:93 — `stroke-dasharray: 3 3` on [data-chart-crosshair] is an SVG dash-pattern visual constant for the dashed crosshair overlay, scoped to the chart component. Same exception class as the @keyframes amplitude literals (not a dimension/spacing/color where a canonical scale applies). Correctly LOW; not drift.
- fix-status: open
## Theming facts (E-bis)
- magic literals: 3 3 @ chart.css:93 -> stroke-dasharray (pattern literal, physical constant, LOW)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,45 @@
# Audit: checkbox
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema', 'eidos']
method: adversarially-verified workflow; HIGH lead-verified. B6 ground-truth: checkbox/toggle/switch commit-toggle = sequence post (lag-fixed); radio-group/tabs/accordion/stepper set state at call-site (pre OK); toggle-group + checkbox-group carry the A31 .includes pattern (SYS-7); slider has no gesture-layer A6 leak.
provider: src/uix/soma/components/checkbox/checkbox-provider.svelte.ts
sequence-audit: State set in handler: applyPending() at line 134 called by runtime event handler (lines 68-69). Morfo sequence='post' (checkbox.ts:28, 43). VERIFIED: state change deferred until AFTER sema dispatch via runtime.trigger() → event handler → applyPending(). Lag risk MITIGATED. Prior 244ms lag from 'pre'
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 1.
## Findings
### MEDIUM: A31: per-item $derived calling provider method reading global state (.includes) = O(N²) — checkbox-001 <!-- id: checkbox-001 -->
- dimension: A31-O(N²)
- rule: A31: per-item $derived calling provider method reading global state (.includes) = O(N²)
- location: src/uix/soma/components/checkbox/checkbox-provider.svelte.ts:313-315, 320
- evidence: CheckboxGroupProvider.isItemChecked(value: string): boolean { return this.opts.value.current.includes(value); } and updateItemChecked also calls .includes(value). Each checkbox item in a group calls isItemChecked() to sync its state (line 104), resulting in N calls to .includes() on the array, yielding O(N²) when rendering N checkboxes in a group.
- impact: For large checkbox groups (50+ items), observable performance degradation during render as each item calls isItemChecked() which iterates the entire value array. Not immediately critical for typical forms but documented risk.
- proposed-fix: Convert group value from string[] to a Set (or use Map if needed for fast lookup). Migrate isItemChecked to Set.has() (O(1)). Update updateItemChecked to use Set.add/delete. Ensure Set is wrapped as $state(new SvelteSet()) for reactivity.
- verify: [unverified]
- fix-status: open
### LOW: Comments in English must match the actual contract; stale doc referencing wrong sequence i — checkbox-002 <!-- id: checkbox-002 -->
- dimension: A-doc-drift
- rule: Comments in English must match the actual contract; stale doc referencing wrong sequence is drift
- location: src/uix/soma/components/checkbox/checkbox-provider.svelte.ts:42-47
- evidence: The `_pendingChecked` JSDoc states: "The two-step dance lets sema fire BEFORE the structural commit (canon `sequence: 'pre'`)." But checkbox.ts:28 and :43 both declare `sequence: 'post'` (with an explicit morfo comment: "'pre' made the functional check wait for the perceptual hold (~240ms) → laggy"). The provider comment is stale: it cites 'pre' as the canon while the morfo deliberately uses 'post'.
- impact: Misleading documentation. A future maintainer reading the provider could re-introduce the 244ms-lag 'pre' regression believing it is canon. No runtime effect — the morfo is correct.
- proposed-fix: Update the comment to reference `sequence: 'post'` and explain the handler-runs-after-sema ordering, consistent with checkbox.ts:25-28.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
## No-findings dimensions
Contract(morfo): checkboxMorfo is 'as const satisfies Morfo' (line 172). All 6 parts registered via runtime.part() exist in morfo: 'provider', 'indicator', 'hidden-input', 'group', 'group-label' (lines 72, 193, 238, 304, 353). 2-of-3 rule satisfied., data-{c}/-{part} naming: All data attributes use kebab-cased part names (data-state, data-disabled, data-readonly, data-invalid on provider; data-state on indicator; data-orientation on group). No data-soma-* violations., aria/data emitted: morfo declares aria-checked, aria-required, aria-readonly, aria-invalid, aria-hidden on provider/indicator/hidden-input. Provider also declares type='button'. All values reference morfo specs correctly., Scope includes eidos: Morfo declares scope=['soma','sema','eidos'] (line 7). No eidos imports in soma provider (verified: no 'eidos' in grep output), which is correct—eidos layer is optional consumer. SYS-1 scope-drift REFUTED: eidos scope is declared but not imported into soma, which is the correct pattern., Events: Both commit-toggle-check and commit-toggle-uncheck have sequence='post' (lines 28, 43). No inert events (all emit to sema)., Gesture/A6: No gesture, setTimeout, setInterval, ResizeObserver, or MutationObserver in checkbox provider. No cleanup risk., Hidden input A13: CheckboxHiddenInputProvider renders <input type='checkbox'> with name/value for form participation (lines 249-255). aria-hidden='true' keeps it off AT tree so button role='checkbox' is sole control. tabindex=-1 excludes from focus. Correct A13 pattern., A30 direct registration verified: CheckboxGroupLabelProvider.setLabelId() called in constructor (line 362), not in $effect. Prevents reactive loop., Theme tokens: All CSS custom properties reference --checkbox-* or --font-*/--space-*/--color-*/--opacity-* canonical tokens from recipes/base.ts. No hex literals, no magic numbers in CSS (px/rem only in recipe: lines 639-653). Token naming --{c}-{slot} followed (--checkbox-size-md-box-size, --checkbox-bg-off, etc.). Private TSC tokens use _checkbox-palette-* pattern (lines 8-10, 162)., Color role subset: CheckboxColor = AffirmativeColorRole (types.ts:42), declared as primary|secondary|neutral|affirm (no risk/threat/loss). Per spec: invalid state handled via data-invalid attribute, not color role., Variants: CheckboxVariant = SelectionVariant (types.ts:30: solid|outline|ghost). CSS implements all three (lines 83-126). No undeclared variants., Sync attrs: runtimePart registered with syncAttrs=true (lines 77, 200, 242, 309, 356). Props returned via $derived.by for each part (lines 167-174, 208-212, 246-257, 331-335, 365-367). No double-write risk., A14 roving: Not applicable—checkbox is not a roving component (single input, not a group with tabindex navigation)., A12 RTL: Not applicable—checkbox has no directional keys (no slider, tabs, radio-group)., A15 gesture: Not applicable—checkbox does not use Gesture layer (no slider/splitter/scroll-area patterns)., Sequence lag FIXED: Prior 244ms lag incident from 'pre' sequence is RESOLVED by current 'post' design. State change in applyPending() handler defers until after sema dispatch.
## Theming facts (E-bis)
- magic z-index: none
- magic literals: stroke-dasharray: 100 | stroke-dashoffset: 100 | 8% | 18% | 82% | 72% | 78%
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: sequence/state commit (toggle, indeterminate clear); readonly/disabled guards; hidden input props + form participation; group value sync; label id context registration
- untested: A31 O(N²) performance with large groups (50+ items); visual state transitions in real DOM; keyboard spacebar event in integration

@ -0,0 +1,62 @@
# Audit: clipboard
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: src/uix/soma/components/clipboard/clipboard-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 2.
## Findings
### MEDIUM: SYS-1 scope-drift — morfo scope omits 'eidos' although an eidos recipe/CSS dir exists for — CLIP-1 <!-- id: CLIP-1 -->
- dimension: A
- rule: SYS-1 scope-drift — morfo scope omits 'eidos' although an eidos recipe/CSS dir exists for the component
- location: src/uix/morfo/components/clipboard.ts:7
- evidence: `scope: ['soma', 'sema'],` — yet src/uix/eidos/components/clipboard/ exists with clipboard.css, clipboard.svelte, clipboard-trigger.svelte, clipboard-indicator.svelte and types.ts. The eidos layer materializes the `data-clipboard` / `data-clipboard-indicator` recipe selectors.
- impact: The morfo's declared scope no longer reflects reality: an eidos consumer exists but the contract says only soma+sema. Scope is the declared surface; drift means tooling/audits that read `scope` will under-count the eidos layer.
- proposed-fix: Add 'eidos' to the scope array: `scope: ['soma', 'sema', 'eidos']` (cf. button.ts:47 which declares it). Or — if the project decides hand-authored thin consumer wrappers do NOT count as an eidos recipe — codify that rule, since toggle/switch/collapsible omit it too. This is the confirmed-systemic SYS-1; clipboard matches the majority that omit it.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: fixed (212624e0)
### LOW: Recipe spacing MUST reference --space-* tokens; raw rem literals are drift — CLIP-2 <!-- id: CLIP-2 -->
- dimension: E-bis
- rule: Recipe spacing MUST reference --space-* tokens; raw rem literals are drift
- location: src/uix/eidos/components/clipboard/clipboard.css:34
- evidence: `padding: 0.125rem 0.5rem;` — raw rem literals, while the SAME rule uses `gap: var(--space-1);` one line above (line 33). 0.5rem == --space-2 in the project scale.
- impact: The indicator chip's inset bypasses the spacing scale, so density/scaling theme changes (`:root` --space-* overrides) won't reach it. Minor visual-only drift.
- proposed-fix: Replace with token-derived values, e.g. `padding: var(--space-0-5, 0.125rem) var(--space-2);` (confirm the exact step names against primitives/static.ts).
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
### LOW: Recipe font-weight should reference the bare token; hardcoded numeric fallback diverges fr — CLIP-3 <!-- id: CLIP-3 -->
- dimension: E-bis
- rule: Recipe font-weight should reference the bare token; hardcoded numeric fallback diverges from convention
- location: src/uix/eidos/components/clipboard/clipboard.css:40
- evidence: `font-weight: var(--font-weight-medium, 500);` — the `500` literal fallback. Every recipe occurrence in lib/recipes/base.ts (lines 48, 153, 231, 314, 449) uses bare `var(--font-weight-medium)` with no fallback.
- impact: Cosmetic inconsistency; the `500` fallback masks a missing token rather than failing visibly, and diverges from the recipe convention.
- proposed-fix: Drop the fallback: `font-weight: var(--font-weight-medium);` to match base.ts usage.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
## No-findings dimensions
A: Contract - Morfo 'as const satisfies' correct, A: Parts registered via runtime.part(), A: Data attributes (data-copied) declared in morfo, A: ARIA attributes correct (aria-label, aria-live, aria-hidden), A30: No per-item loops or sequence lags detected, A31: No O(N²) derived patterns detected, A33: No Map/Set state issues detected, A35: No per-item effect loops detected, A36: No async-mediated microtask freeze patterns detected, A6: Timer disposal via resetTimer with clearResetTimer() cleanup and $effect return, A15: No gesture/drag-drop patterns needed for clipboard, A34: Live regions - Trigger has aria-live polite for label announcement, B: Provider pattern correct - soma imports only soma core, no eidos imports, D: Frontier - No soma->eidos imports, E-bis: No recipe token or theming issues (uses canonical color roles), F: Tests exist - jsdom environment, happy path, error handling, timer mocks
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: copy success and copied state reset via timer; onCopy callback invocation; onError callback on clipboard API rejection; timer scheduling with uix.timers.schedule; aria-label resolution and swap on copied state; indicator visibility conditional on copied state; mergedProps application
- untested:
## Style observations (non-blocking)
- CSS uses canonical color-role variables (--color-affirm-solid, etc.) with cascading support via data-color attribute
- Animation (clipboard-pop) respects prefers-reduced-motion
- Typography uses canonical --font-size-xs and --font-weight-medium
- Spacing uses --space-* tokens (0.5rem mapped to --space-1)
- Focus ring follows canonical two-ring model via Button consumer
- No hardcoded hex colors or magic literals in eidos CSS

@ -0,0 +1,19 @@
# Audit: code-block
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: morfo OK; no recipe; ControlVariant canonical; no color/role issues
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,26 @@
# Audit: code
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: morfo OK; no recipe; roles fragmented (content-role subset)
as-const: true · roles clean: false · variants clean: true · has-recipe: false
## Findings
### LOW: em-literal padding where no canonical scale exists — code-002 <!-- id: code-002 -->
- dimension: E-bis: Theming (SIZES)
- location: src/uix/eidos/components/code/code.css:30-31
- evidence: padding-inline: 0.32em; padding-block: 0.1em (lines 30-31, also variants at 36-37)
- impact: Magic em literals; no canonical padding-em scale for pill chrome
- proposed-fix: Define --code-padding-inline and --code-padding-block tokens or inherit from mark
- verify: [downgraded] code.css:30-31,36-37 'padding-inline: 0.32em; padding-block: 0.1em' are em-relative paddings for an inline-flow pill, intentionally outside the absolute --space-* scale so the pill scales with surrounding prose font-size (the purpose of inline <code>). No canonical em-based padding scale exists. Legitimate physically-relative literal for inline chrome, not drift against an existing scale. Downgrad
- fix-status: open
## Theming facts (E-bis)
- magic literals: 0.32em @ src/uix/eidos/components/code/code.css:30 -> canonical code-padding-inline token | 0.1em @ src/uix/eidos/components/code/code.css:31 -> canonical code-padding-block token
- magic z-index: none
- non-canonical names: none
- roles clean: false · variants clean: true

@ -0,0 +1,53 @@
# Audit: collapsible
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B6 ground-truth: checkbox/toggle/switch commit-toggle = sequence post (lag-fixed); radio-group/tabs/accordion/stepper set state at call-site (pre OK); toggle-group + checkbox-group carry the A31 .includes pattern (SYS-7); slider has no gesture-layer A6 leak.
provider: G:\dev\svelte\vicen\src\uix\soma\components\collapsible\collapsible-provider.svelte.ts
sequence-audit: expand: post-sequence ✓ (state set in handler lines 68-70, morfo sequence:post at line 39). collapse: pre-sequence ✓ (state set in handler lines 71-73, morfo sequence:pre at line 48). Both events fire via runtime.trigger() in toggle() which defers handler execution. No lag risk: handler state-settin
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 1 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 scope-drift: eidos directory exists but morfo scope omits 'eidos' — collapsible-001 <!-- id: collapsible-001 -->
- dimension: Contract(morfo)
- rule: SYS-1 scope-drift: eidos directory exists but morfo scope omits 'eidos'
- location: src/uix/morfo/components/collapsible.ts:22
- evidence: morfo declares scope: ['soma', 'sema'] but src/uix/eidos/components/collapsible/ directory exists with full implementation (collapsible.svelte, collapsible.css, index.ts). Accordion (sibling pattern) correctly declares scope: ['soma', 'sema', 'eidos']
- impact: Scope declaration diverges from actual architecture; contract validators may not catch eidos-layer changes. Documentation of morfo scope is stale.
- proposed-fix: Update collapsibleMorfo scope to ['soma', 'sema', 'eidos'] to match accordion pattern and actual directory structure
- verify: [confirmed] CONFIRMED. morfo declares `scope: ['soma', 'sema']` at src/uix/morfo/components/collapsible.ts:22, but src/uix/eidos/components/collapsible/ exists with a full implementation (collapsible.svelte, collapsible.css, collapsible-trigger.svelte, collapsible-content.svelte, types.ts, index.ts, README.md — verified via glob). The sibling disclosure pattern accordion correctly declares `scope: ['soma', 'sema', 'eidos']` at src/uix/morfo/components/accordion.ts:7. This is SYS-1 scope-drift, MEDIUM. Severity unchanged.
- fix-status: fixed (212624e0)
### HIGH: SEQUENCE-LAG — `collapse` is `sequence:'pre'` but sets state in the handler (the Checkbox-244ms-lag mechanism) — collapsible-NEW-001 <!-- id: collapsible-NEW-001 -->
- dimension: Behavior(soma)
- rule: SEQUENCE / TWO-MOMENTS — a control whose functional state is set INSIDE the runtime.trigger HANDLER must use morfo `sequence: 'post'`; with 'pre' the perceptual hold (~240ms) blocks the state change (documented Checkbox 244ms-lag incident).
- location: src/uix/morfo/components/collapsible.ts:48 (collapse sequence:'pre') + src/uix/soma/components/collapsible/collapsible-provider.svelte.ts:71-73 (handler sets state)
- evidence: The `collapse` event sets the functional state INSIDE the trigger handler: provider lines 71-73 `collapse: () => { this.opts.open.current = false; }`. The morfo declares `sequence: 'pre'` at collapsible.ts:48. For 'pre', the runtime serializes the perceptual emit BEFORE the handler: runtime.svelte.ts:749-752 `else { await runEmit(); if (handler) await handler(); }`. `runEmit` awaits `sources.eventEngine.emit` (runtime.svelte.ts:696 `await sources.eventEngine.emit(...)`), whose engine awaits the visual channel hold (engine.ts:250 `await visualChannel.handle(enriched, effective)`), which does `await sleep(holdMs)` (visual.ts:79-81). collapsible declares no per-event `hold` and the sema pack (sema/components/collapsible.ts) declares none, so `emerge` falls back to label `brief` (visual.ts:43) = 240ms (durations.ts:27). Result: the content does NOT receive `hidden` until ~240ms after the click — the exact magnitude of the documented Checkbox 244ms-lag. (The `expand` event is correctly `post` at collapsible.ts:39, so opening has no lag — only closing lags.) This refutes the prompt's sequenceAudit line which marked `collapse: pre-sequence ✓`.
- impact: User clicks the trigger to collapse an open section; the panel stays fully visible for ~240ms before hiding — a perceptible dead delay on every close, identical in mechanism and magnitude to the Checkbox 244ms-lag that the project already documented and fixed by flipping to `sequence:'post'`. The morfo's inline justification (collapsible.ts:10-13, 'collapse remains pre-sequence so the exit signal can play while the content is still visible') is based on a false concurrency assumption: the runtime does NOT overlap emit and handler for 'pre' — it serializes emit-then-handler, so the content is held visible for the FULL hold, then hidden, rather than the exit sound playing during a CSS exit transition.
- repro: Open a Collapsible (expand — instant), then click the trigger to collapse. Measure the delay between click and `hidden`/`data-state='closed'` taking effect on the content. Expect ~240ms (the emerge `brief` hold) rather than near-zero. The expand direction does not exhibit the delay.
- proposed-fix: Apply the documented Checkbox/Toggle/Switch doctrine: change `collapse` to `sequence: 'post'` (collapsible.ts:48) so the handler flips `open=false` first and the exit signal then acknowledges the resolved state, matching how accordion's symmetric close event is handled (accordion close is post per accordion.ts pattern). If a visible-during-exit cue is genuinely wanted, drive it from eidos CSS keyed on `data-state='closed'` + a CSS exit transition rather than blocking the functional state behind the sema hold. Alternatively set state at the call-site in `toggle()` (collapsible-provider.svelte.ts:85-89) before `runtime.trigger`, which would make 'pre' tolerable (the RadioGroup/Tabs pattern).
- verify: [verifier-added → LEAD-CONFIRMED HIGH] The analyze agent (and the morfo's own inline comment) marked this CLEAN, assuming `'pre'` overlaps emit+handler so the exit plays during the hold. The verifier traced the actual runtime and proved it SERIALIZES (`runtime.svelte.ts:749-752: await runEmit(); await handler()`), so `open=false` is delayed the full ~240ms `brief` emerge hold. I initially LEANED toward MEDIUM (reasoning "collapse is emerge.dismiss 'pre', the canonical animate-before-hide pattern like dialog/popover"), but that masking only holds for components that animate on `data-event`/Presence DURING the hold (dialog does); collapsible's exit is keyed on `data-state='closed'`, which doesn't flip until AFTER the hold — so the 240ms is a real dead delay, not a filled exit animation. Restored to HIGH. Caveat: like the checkbox fix, the user-perceptible magnitude should be confirmed by a frame-by-frame browser measurement before/after; the MECHANISM (and the refuted morfo justification) are confirmed in code.
- fix-status: fixed (db33f66c)
## No-findings dimensions
Behavior(soma) [except the sequence-lag above], DOM-selector, Frontier, TSC, Theming, Tests
## Theming facts (E-bis)
- magic z-index: none
- magic literals: collapsible.css:4,19 translateY(-4px) — LOCAL animation amplitude in @keyframes, acceptable
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: vitest jsdom
- covers: ID linking via partRef; expand/collapse events + state toggle; disabled guard + disabled attr projection
- untested: onOpenChange callback firing
## Style observations (non-blocking)
- CSS uses canonical token vars (--space-*, --color-*, --font-*, --duration-*, --ease-*, --opacity-*, --radius-*)
- Recipe collapsible: in base.ts lines 712-725 with 12 tokens, all reference --*; no raw hex/px/rem/em
- Naming: --collapsible-{slot}.{property} consistent (trigger-color, trigger-padding, content-padding, etc)
- Animations in @keyframes use local px values for amplitude (-4px translateY), not tied to design tokens (acceptable for local animation logic)
- No magic z-index or opacity decimals

@ -0,0 +1,55 @@
# Audit: color-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/color-field/color-field-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 0.
## Findings
### MEDIUM: SYS-1: morfo scope declares eidos layer when eidos recipe dir exists — color-field-001 <!-- id: color-field-001 -->
- dimension: Frontier
- rule: SYS-1: morfo scope declares eidos layer when eidos recipe dir exists
- location: src/uix/morfo/components/color-field.ts:7
- evidence: scope: ['soma', 'sema'], but src/uix/eidos/components/color-field/ directory exists with 9 files (color-field.svelte, color-field.css, types.ts, etc.)
- impact: Contract drift: callers may assume eidos layer is not provided; scope-drift masks available visual customization surface.
- proposed-fix: Add 'eidos' to scope declaration: scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] Confirmed SYS-1 scope-drift. morfo/components/color-field.ts:7 `scope: ['soma', 'sema'],` omits 'eidos'; eidos dir src/uix/eidos/components/color-field/ exists with real color-field.css (10039 bytes) + recipe. MEDIUM correct. Systemic across field family (css/date/number/password/search/time-field all do the same).
- fix-status: fixed (212624e0)
### MEDIUM: F: test coverage misses high-risk readonly-segments + value=undefined path — color-field-002 <!-- id: color-field-002 -->
- dimension: Tests
- rule: F: test coverage misses high-risk readonly-segments + value=undefined path
- location: src/uix/soma/components/color-field/color-field-provider.svelte.test.ts:94-258
- evidence: Test suite covers: segment fills (lines 100-123), keyboard hex input (125-155), format switching (157-188), wiring (190-257). Missing: readonly-segment-without-value warning assertion (defined in soma at lines 215-233) and edge cases like backspace, Home/End navigation, readonly interaction.
- impact: A24 warning logic is unexercised; regressions in readonlySegments guard logic could slip through. readonly-segment test is a HIGH-RISK gap per audit rules.
- proposed-fix: Add test case: 'warns when readonlySegments set without value' — assert soma.logger.warn is called with expected message. Add test for backspace on first segment (moveToPrev).
- verify: [confirmed] Confirmed. Provider warning at color-field-provider.svelte.ts:215-233 (spam-guarded via lastWarnedKey, calls soma.logger.warn for readonlySegments set + value undefined) is unexercised: test only mocks `warn: vi.fn()` (line 46) and seeds empty `readonlySegments` (line 76). No assertion, no non-empty+undefined case. A24 high-risk path missing test = MEDIUM.
- fix-status: open
### MEDIUM: A30 — inputId registered via `$effect` instead of direct constructor assignment (SYSTEMIC: date/time/color-field) <!-- id: color-field-A30 -->
- dimension: B
- rule: A30 (child→parent id registration MUST be a direct constructor assignment, not `$effect`)
- location: color-field-provider.svelte.ts:521-525 (`$effect(() => { const field = this.provider.field; if (!field) return; field.inputId.current = opts.id.current; })`)
- evidence: lead-confirmed by direct read — identical `$effect` shape to date-field (932-936) and time-field (531-535); the reference NumberField uses a direct guard (number-field:585-587).
- impact: no live loop (write-only + stable id), but contrary to A30 doctrine + the NumberField reference. See SYS-A30-EFFECT in SUMMARY.
- proposed-fix: replace with a direct constructor assignment matching NumberField; apply across date/time/color-field.
- verify: [lead-added] the color-field agent listed "Behavior" as a no-findings dimension, which masked the `$effect` id-wiring; lead read of color-field:521-525 confirms it. Systemic MEDIUM.
- fix-status: open
## No-findings dimensions
Contract, Behavior (except A30 id-wiring — see color-field-A30), DOM-selector, TSC
## 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?): CLEAN: calc(1em - (var(--font-size-md) - var(--font-size-sm))) at src/uix/eidos/components/color-field/color-field.css:106 correctly implements label one typographic step below input size (md → sm = 2px)
## Tests (F)
- exists: true · env: jsdom
- covers: segment-commit: RGB fill sequencing (lines 100-123); keyboard: hex segment digit input with aria spinbutton (125-155); format-select: switching formats + allowedFormats narrowing + locked state (157-188); hidden-input: form field name/value wiring + required attribute (190-257); label-focus: label click routes focus to first segment (252-253)
- untested: readonly-segment-without-value warning (A24 guard); backspace navigation edge case (moveToPrevSegment on null segment); Home/End key boundary behaviors (first-item / last-item); readonly segments interaction with keyboard input; Arrow key wrapping (cycle vs no-cycle per channel config)

@ -0,0 +1,72 @@
# Audit: color-picker
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work).
provider: src/uix/soma/components/color-picker/color-picker-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 2.
systemic hits: SYS-1 (scope-drift MEDIUM); SYS-5 (area thumb hardcoded z-index HIGH).
composition (A27): Composes (A27): Popover + ColorField + Slider via shared writableActive refs. ChannelSlider composes SliderProvider underneath with channel-aware bridging (gradient + setChannel). ColorField parts (Input/Segment/FormatSelect) re-exported as ChannelInput/ChannelSegment/FormatSelect with data-color-picker identity attrs overlaid. Shared state wiring correct: sharedValue, sharedFormat, sharedOpen passed to both ColorPickerProvider and PopoverProvider/ColorFieldProvider. Content part re-exported from Popover (registers on Popover runtime, not picker runtime).
## Findings
### MEDIUM: SYS-1 scope-drift — color-picker-001 <!-- id: color-picker-001 -->
- dimension: A
- rule: SYS-1 scope-drift
- location: src/uix/morfo/components/color-picker.ts:7
- evidence: colorPickerMorfo declares scope: ['soma', 'sema'] but eidos directory exists at src/uix/eidos/components/color-picker/ containing recipe CSS and multiple eidos component wrappers (.svelte files)
- impact: Morfo scope contract is incomplete. Downstream tooling expecting strict scope adherence will find undeclared layer.
- proposed-fix: Update morfo scope to ['soma', 'sema', 'eidos']
- verify: [confirmed] Confirmed SYS-1 scope-drift. color-picker.ts:7 reads `scope: ['soma', 'sema'],` yet a full eidos implementation exists at src/uix/eidos/components/color-picker/ (color-picker.css recipe + 25+ .svelte wrappers per Glob). `'eidos'` IS a valid Layer (src/uix/types.ts:17 `export type Layer = 'soma' | 'sema' | 'eidos';`), and morfo `scope: readonly Layer[]` (types.ts:799). date-picker.ts:13 has the identical `scope: ['soma', 'sema']`, so this is the documented systemic SYS-1 across the picker family. MEDIUM is correct.
- fix-status: fixed (212624e0)
### MEDIUM: Inert 'open' event — color-picker-003 <!-- id: color-picker-003 -->
- dimension: B
- rule: Inert 'open' event
- location: src/uix/morfo/components/color-picker.ts:14-31
- evidence: Morfo declares name:'open' event with target=partRef('content') and sequence:'pre'. No runtime.trigger('open',...) call found in provider. Comment on close event (line 38-39) documents pattern: provider sets opts.open=false and lets popover unmount, never fires the event explicitly.
- impact: Consumer code declaring open event handlers will not receive them. The event exists only to satisfy schema validators. This is documented for pickers (known pattern like date-picker/time-picker) but represents a contract mismatch.
- proposed-fix: Document in morfo comment that 'open' event is not fired (inert, exists for contract symmetry); or fire it on popover open (requires Popover composition to surface trigger point)
- verify: [confirmed] Confirmed inert event. Morfo declares `name: 'open'` (color-picker.ts:14-32) with sequence 'pre' targeting content; grep for `trigger('open'` across src/uix/soma/components/color-picker returns NO matches. The provider only ever fires 'close' (via triggerClose, lines 349-375), 'handle-pick'/'handle-drag'/'commit-set'. The morfo's own comment (lines 10-13 'They drive Footer...' and 38-41 'today the provider just sets opts.open = false and lets the popover unmount') documents the known picker-family inert pattern. 'commit-reset' is also inert (never triggered) — candidate only flags 'open', which is in-scope and accurate. MEDIUM matches the audit's 'MEDIUM at most' for inert events.
- fix-status: open
### LOW: Magic z-index literals not in STATIC_Z_INDEX scale — color-picker-002 <!-- id: color-picker-002 -->
- dimension: E-bis
- rule: Magic z-index literals not in STATIC_Z_INDEX scale
- location: src/uix/eidos/components/color-picker/color-picker.css:367,557
- evidence: z-index: 2 on [data-color-picker-area-thumb]:367, z-index: 1 on [data-color-picker-swatch-indicator]:557. Project STATIC_Z_INDEX scale: base=0, raised=1, sticky=100, dropdown=300, popover=400, tooltip=500, modal=700, toast=900. These values (1,2) accidentally collide with low scale but are not intentionally using that scale.
- impact: Fragile stacking context: undocumented magic literals break auditing of depth relationships. If future components add z-index:3 the priority breaks.
- proposed-fix: Use var(--z-index-raised) or var(--z-index-sticky) from canonical scale instead of bare integers
- verify: [downgraded] Literals confirmed present — color-picker.css:367 `z-index: 2;` (area-thumb) and :557 `z-index: 1;` (swatch-indicator). But the HIGH severity and the SYS-2 mapping are wrong. SYS-2 targets bare 60-99 integers that collide with the depth-PLANE scale (--z-index-dropdown=300/popover=400/modal=700/etc). These values form a self-contained LOCAL sibling stack within the picker subtree: grep shows the file uses only -1 (background layers, :433/:402/:412), 1 (indicator over swatch), 2 (thumb over area-background). The canonical STATIC_Z_INDEX scale is for cross-component depth planes, not intra-component layering; substituting `var(--z-index-sticky)` (=100) as the proposed fix would be semantically wrong for a thumb-over-its-own-background. No real stacking-context fragility (no other component reaches into this subtree's z-context). Downgrade to LOW (cosmetic/token-tidiness at most); not a SYS-2 magic-z violation.
- fix-status: open
### LOW: Test environment jsdom with interaction-heavy keyboard — color-picker-004 <!-- id: color-picker-004 -->
- dimension: F
- rule: Test environment jsdom with interaction-heavy keyboard
- location: src/uix/soma/components/color-picker/color-picker-provider.svelte.test.ts:1
- evidence: @vitest-environment jsdom. Tests cover: ColorPickerAreaThumbProvider.onkeydown (ArrowRight at line 227) and keyboard route logic (HOME/END/PAGE_UP/PAGE_DOWN at lines 905-915 in provider). No Playwright/browser test confirming area keyboard in real DOM.
- impact: Area thumb keyboard handling (Arrow x4 + Home/End + PageUp/PageDown) is interaction-heavy and DOM-geometry-dependent. jsdom may not accurately simulate getBoundingClientRect or pointer geometry used in handlePointerMove / calculateChannelValue.
- proposed-fix: Add browser-level test (Playwright) for area keyboard + pointer drag to verify geometry math in real DOM
- verify: [downgraded] Confirmed jsdom env (test file line 1 `// @vitest-environment jsdom`) and the provider has interaction-heavy area logic. This is the SYS-3 baseline (jsdom-only / kbd-untested) so a real F-gap exists. BUT the candidate's core rationale is partly wrong: the area-thumb keyboard route (onkeydown, provider lines 882-931) is PURE arithmetic over getChannelRange/getColorChannel — it never reads getBoundingClientRect, so it IS faithfully exercisable in jsdom (test does exercise ArrowRight et al.). Only the POINTER path (handlePointerMove, lines 764-777, consumes DOMRect) is geometry-dependent and genuinely under-covered in jsdom. So the gap is narrower (pointer-drag geometry, not keyboard) than the finding states. Downgrade to LOW per SYS-3.
- fix-status: open
## No-findings dimensions
C, D
## Theming facts (E-bis)
- magic z-index: z-index: 2 | z-index: 1
- magic literals: 16px on area-thumb-size | 11rem on area-height | 1.25rem/1.5rem/1.75rem on swatch sizes
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: channel value preservation; area thumb pointer move + keyboard; area channel override; setChannel + commitChange; swatch select + closeOnSelect
- untested: area thumb keyboard routes (Home/End/PageUp/PageDown) in browser; pointer drag geometry in real DOM; popover open/close animations; dismissal outside; eyedropper API integration
## Style observations (non-blocking)
- color-picker.css uses CSS containment (contain: layout style) on area to prevent sibling layout shifts during drag
- Transparency checker pattern re-used via CSS vars across multiple elements
- Area overlays (saturation/brightness) use pseudo-elements to avoid conflicts with inline background
- Swatch group uses flex 1 1 0 + aspect-ratio to shrink/grow equally while staying square

@ -0,0 +1,115 @@
# Audit: combobox
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (eidos recipe present though 'eidos' not in morfo scope — systemic SCOPE-DRIFT)
files:
- provider: src/uix/soma/components/combobox/combobox-provider.svelte.ts (present)
- morfo: src/uix/morfo/components/combobox.ts (present)
- recipe: src/uix/eidos/components/combobox/combobox.css (558L) + lib/recipes/base.ts §combobox (present)
- demo: web/routes/uix/components/combobox (present, not inspected this pass)
- test: src/uix/soma/components/combobox/combobox-provider.svelte.test.ts (present, jsdom)
- readme: present (not inspected this pass)
## Summary
Combobox is well-built in several respects: the A33 `labelRegistry` clone-reassign incident is fixed (now
`SvelteMap`), dimension C is clean (`CSS.escape` on the highlighted-id query + the shared `resolveListItemEl`
helper escapes `data-value`), dismissal excludes both trigger and input (A22), and the inputValue auto-sync /
mode-reset effects are loop-safe (`untrack` + guards). The headline defect is a **focus-strategy violation
(A17)**: the morfo declares `aria-activedescendant` (virtual focus) but the provider moves **real DOM focus**
onto items (`dom.focus(items[0])`), mixing the two strategies and breaking the APG combobox contract (focus
must stay on the textbox). Plus a `translationRef` misuse, the systemic A31 + magic-z-index, and a jsdom-only
test gap right where the focus bug lives.
Counts: CRITICAL 0 · HIGH 2 · MEDIUM 3 · LOW 0.
## Findings
### HIGH: Mixes virtual focus (`aria-activedescendant`) with real DOM focus (`.focus()` on items) — A17 violation + breaks APG combobox <!-- id: combobox-001 -->
- dimension: B, A
- rule: A17 ("Never mix [virtual + DOM focus] in the same component. If the trigger has aria-activedescendant,
items must NOT call .focus()") + APG Combobox pattern (focus stays on the textbox) + morfo↔code drift
- location: morfo combobox.ts:106 (Input declares `aria-activedescendant`) vs provider:392, 408, 661, 684
(`this.provider.soma.dom.focus(...)` on items / input) + the items carry `tabindex: -1` (:803) so they take focus.
- evidence: the Input keydown opens then `dom.focus(items[0])` AND sets `highlightedId` (:387-394); the Content
keydown computes `currentIndex` from `highlightedId` (virtual) yet also `dom.focus(target)` (:684, real). So
the component simultaneously (a) advertises `aria-activedescendant` on the input and (b) moves real focus to
the option. The morfo's `aria-activedescendant` declaration says "virtual"; the code does "real".
- impact: with focus moved onto an option, the input's `aria-activedescendant` is meaningless (it only applies
while focus is on the input). Screen readers get a contradictory model (an option both has DOM focus and is
the input's active descendant). APG's combobox keeps focus on the textbox; moving it to options is the
listbox-with-DOM-focus pattern — picking one is required.
- repro: open the listbox, ArrowDown, inspect `document.activeElement` (an option) while the input still has
`aria-activedescendant` set to that option.
- proposed-fix: commit to virtual focus (the morfo's declared model): keep DOM focus on the input, drive
selection purely via `highlightedId`/`data-highlighted` + `scrollIntoView`, never `dom.focus(item)`. (Or, if
real focus is intended, drop `aria-activedescendant` from the morfo and the highlightedId machinery — but
that diverges from APG combobox.)
- fix-status: fixed (bac67315)
### HIGH: Tests pin selection logic only — keyboard/focus (where the A17 bug lives), dismissal-exclusion, custom-value, backspace untested; jsdom-only <!-- id: combobox-002 -->
- dimension: F
- rule: dimension-F honesty check
- location: combobox-provider.svelte.test.ts (3 tests: single-select, multi toggle + inputValue, getItems disabled-filter)
- evidence: no test exercises Input/Content `onkeydown` (the focus moves in combobox-001), the dismissal
trigger/input exclusion (`isValidEvent` + `onInteractOutside`, :594-626), `commitCustomValue`,
backspace-remove-last, or the inputValue auto-sync effect. `@vitest-environment jsdom` (:1) — focus isn't
faithful, so even a keyboard test here couldn't validate the A17 behavior.
- impact: the focus-strategy bug and every keyboard path ship unverified.
- proposed-fix: add provider tests for the keyboard routes + dismissal exclusion + custom value; add a
client/Playwright test that asserts `activeElement` stays on the input (the A17 contract).
- fix-status: open
### MEDIUM: `aria-label` parts pass the full idlangref as the `translationRef` key instead of the `texts` key <!-- id: combobox-003 -->
- dimension: A
- rule: A3 / A34 (translation namespace) + consistency with the canonical `v.translationRef(key, fallback)` form
- location: morfo combobox.ts:142, 168, 291, 323 — e.g. `v.translationRef('#?components.combobox.toggle|Toggle')`
while `texts.toggle = '#?components.combobox.toggle|Toggle'` (:11) is declared.
- evidence: every other component references a `texts` entry by its KEY (Dialog:
`v.translationRef('content.roledescription', 'dialog window')`). Combobox passes the resolved absolute
idlangref string as the key, so the `texts` entries (`toggle`/`clear`/`selectedTags`/`selectedTagRemove`) are
declared but never referenced by key — the indirection is bypassed.
- impact: at best redundant (the string happens to be a valid idlangref so it may resolve); at worst a
missing-key lookup. Either way it's inconsistent with the framework's pattern and leaves the `texts` keys
dead. Smoke's translation-key check should be confirmed against these (see VALIDATOR_GAPS).
- proposed-fix: use `v.translationRef('toggle', 'Toggle')` etc., referencing the `texts` keys.
- fix-status: open
### MEDIUM: Per-item `isSelected` reads the whole `value` array through the provider (A31 O(N²)) <!-- id: combobox-004 -->
- dimension: B
- rule: A31
- location: combobox-provider.svelte.ts:782 (`isSelected = $derived.by(() => this.provider.isSelected(value))`)
→ :182-184 (`.includes`)
- evidence: identical to select-007 — each Item's derivation calls a provider method reading the shared `value`
array (`.includes`, O(N)); on value change all N items re-derive → O(N²).
- impact: degrades on large multi-select lists (the A31 "5 fine / 30 hangs" class).
- proposed-fix: lift `selectedSet = new SvelteSet(value)` on the provider; item derivation does `.has(value)`.
(Shared with Select — see SHARED_EXTRACTION / systemic A31-LIST-SELECTION.)
- fix-status: open
### MEDIUM: `content-z: '80'` magic z-index literal <!-- id: combobox-005 -->
- dimension: E-bis
- rule: THEMING §35 Bloque C (z-index magic → `--z-index-*` tokens)
- location: lib/recipes/base.ts:4254 (`'content-z': '80'`); combobox.css:261.
- evidence: bare `80` (same value as `select.content-z`). Fourth overlay with a hardcoded content-z
(select 80, dialog 70, popover 75/60, combobox 80) → SYSTEMIC z-ladder finding.
- impact: see select-009 / popover-002.
- proposed-fix: tokenize to the canonical overlay z-ladder.
- fix-status: open
## No-findings dimensions
- **C (selectors):** the Enter handler uses `#${CSS.escape(highlightedId)}` (:417-419) and `resolveItemEl`
delegates to `resolveListItemEl` which escapes `data-value` (list-selection.ts:145). No injection. (This is
the safe pattern Select's `scrollSelectedIntoView` fails to use — see select-001.)
- **A33:** `labelRegistry` is `SvelteMap` (:97) — the documented A33 clone-reassign incident is resolved.
- **B (loop-safety):** inputValue auto-sync (:151-160) and mode-switch reset (:166-175) effects guard writes
and use `untrack`; no feedback loop. Dismissal excludes trigger + input (A22, :594-626).
- **D (frontier, severe half):** provider imports only soma layers + morfo — zero eidos imports.
- **E-bis (colors):** combobox.css has no raw hex / raw px-rem font literals in the scanned surface (only the
z-index literal above). (Full per-selector morfo-part diff not exhaustively run this pass — the CSS is 558L.)
## Style observations (opinion, non-blocking)
- The A17 violation is the single most important fix: the morfo already commits to virtual focus, so aligning
the provider to it (rather than the reverse) keeps the APG-correct model and removes the real-focus paths.
- The `list-selection.ts` extraction (shared with Select) is a good example of dimension-G done right — the
selection state machine is one pure, testable module. The remaining duplication (the directional-nav index
math) is the part still copied per-component (see select-003 / SHARED_EXTRACTION).

@ -0,0 +1,91 @@
# Audit: command
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/soma/components/command/command-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 5.
systemic hits: SYS-1 (scope-drift); SYS-3 (jsdom-only, keyboard untested).
## Findings
### MEDIUM: SYS-1 scope-drift — command-001 <!-- id: command-001 -->
- dimension: E-bis
- rule: SYS-1 scope-drift
- location: src/uix/morfo/components/command.ts:7
- evidence: scope: ['soma', 'sema'], — but eidos/components/command/ directory exists with CSS and components. Morfo declares scope omitting 'eidos'
- impact: Component has eidos visuals and recipes but scope doesn't declare it; scope drift breaks the four-layer contract and may cause issues with validation or tooling that expect declared scopes
- proposed-fix: Add 'eidos' to the scope array: scope: ['soma', 'sema', 'eidos']
- verify: [downgraded] morfo line 7 reads `scope: ['soma', 'sema'],` and an eidos/components/command/ dir with CSS exists. But this is NOT command-specific drift: I counted 63 stateful morfos (combobox, calendar, date-picker, color-picker, dialog, select, popover, listbox, etc.) that ALL omit 'eidos' from scope while shipping an eidos recipe dir. The `scope` field semantics in src/uix/morfo/types.ts:770-839 govern SEMA/event coherence ('If a morfo declares events[] AND scope:['sema']...'), not eidos-recipe presence — the visual layer is not gated by the scope array. No contract test asserts scope must include 'eidos' when CSS exists. Treating this as a HIGH per-component contract violation contradicts the framework's own baseline (63/64 components do it). At most a systemic LOW doc/convention observation; the stated HIGH severity and 'breaks the four-layer contract' impact are not supportable.
- fix-status: fixed (212624e0)
### LOW: Magic literal — em font sizes — command-003 <!-- id: command-003 -->
- dimension: E-bis
- rule: Magic literal — em font sizes
- location: src/uix/eidos/components/command/command.css:260-261,272
- evidence: inline-size: 1.125em (line 260-261) and font-size: 0.875em (line 272) are bare em values without canonical token references
- impact: Icon and shortcut sizing use hard-coded em multipliers instead of --icon-size-* or --font-size-* tokens; inconsistent with theming system
- proposed-fix: Replace 1.125em with a canonical icon size scale reference (e.g., --icon-size-md or create --command-item-icon-size). Replace 0.875em with a font-size scale reference or scale modifier
- verify: [downgraded] Confirmed the literals: command.css:260-261 `inline-size: 1.125em; block-size: 1.125em;` (icon box) and 272 `font-size: 0.875em;` (shortcut). However em-based slot sizing is an established, idiomatic codebase pattern — grep across eidos shows field-control-trigger.css:30/45 `1.75em`/`1em`, menu-dial.css:281-282 `1em`, password-field.css:293/297-298 `1em`, table.css:339-340 `1em`, fab.css:103 `1em`. The `em` unit deliberately scales with the local `--_command-item-font-size` cascade (which IS token-driven), so these are RELATIVE sizing, not fixed px/rem drift the E-bis rule targets. At most a LOW consistency nit; MEDIUM overstates it given the framework-wide convention.
- fix-status: open
### LOW: Magic literal — pixel heights — command-004 <!-- id: command-004 -->
- dimension: E-bis
- rule: Magic literal — pixel heights
- location: src/uix/eidos/components/command/command.css:153,219,296
- evidence: block-size: 2px (loading bar), gap: 1px (group items), block-size: 1px (separator) — bare pixel literals
- impact: Three structural heights use magic pixels instead of token references; brittle to design changes
- proposed-fix: Create or reference --border-width for 1px lines (already canonical for most components). For 2px loading bar, create --command-loading-height or use a custom CSS variable scoped to command
- verify: [downgraded] Confirmed: command.css:153 `block-size: 2px;` (loading bar), 219 `gap: 1px;` (group items), 296 `block-size: 1px;` (separator). The 1px values are hairline-divider widths idiomatic across the codebase (separator/border lines); 2px is an off-scale loading-bar height. These are genuine bare-pixel literals but they are structural hairlines, not tokens that carry theming intent — LOW severity at most, not MEDIUM. No user-visible or behavioral impact.
- fix-status: open
### LOW: Morfo contract — archetype declaration — command-006 <!-- id: command-006 -->
- dimension: A
- rule: Morfo contract — archetype declaration
- location: src/uix/morfo/components/command.ts:98-124
- evidence: Item part declares archetype: 'item' but does not declare Icon or Shortcut as sub-parts in the morfo
- impact: Eidos defines Item.Icon and Item.Shortcut sub-parts (in eidos/components/command/index.ts) with their own data-* selectors, but they are not declared in the morfo. They are eidos-only sub-parts and properly documented in types.ts, so this is intentional (not an error)
- proposed-fix: N/A — this is a documented eidos extension pattern
- verify: [confirmed] Confirmed non-finding by the candidate's own admission. morfo declares Item (lines 97-124) with archetype 'item' and data-value/data-selected/data-disabled, but Icon/Shortcut are eidos-only sub-parts (index.ts:49-62 attaches ItemIcon/ItemShortcut; command.css:255/265 selectors documented as 'eidos-only' sub-part). This is the documented eidos-extension pattern, explicitly proposedFix 'N/A'. Correctly emitted as LOW with no action.
- fix-status: open
### LOW: Test environment — jsdom only — command-007 <!-- id: command-007 -->
- dimension: F
- rule: Test environment — jsdom only
- location: src/uix/soma/components/command/command-provider.svelte.test.ts:1
- evidence: // @vitest-environment jsdom — test suite uses jsdom, which cannot test keyboard input, focus, or DOM pointer events accurately
- impact: Keyboard navigation (ArrowDown/Up/Home/End/Enter/vim bindings) is complex behavioral code but only tested in jsdom (which doesn't fire real keyboard/mouse events). Focus logic untested.
- proposed-fix: Add a client/Playwright test suite for keyboard navigation (arrow keys, grid columns, loop wrapping, Home/End), pointer selection, and focus sync. See SYS-3 pattern.
- verify: [confirmed] Confirmed and arguably under-severed. test file line 1 `// @vitest-environment jsdom`; whole file is 122 lines with only 2 `it` blocks (line 77 filters+auto-select, line 96 navigates via provider.next/prev/selectCurrent API). No `dispatchEvent`/`KeyboardEvent`/`focus(` anywhere — the real onkeydown route in CommandInputProvider.onkeydown (lines 452-508: vim ctrl+n/p/j/k, Home/End, grid `columns`, RTL `getDirectionalKeys`) is NEVER exercised through a key event, only the underlying API methods are. This is a clean SYS-3 hit (interaction-heavy + jsdom-only + keyboard route untested). LOW is defensible but MEDIUM would be equally justified per the SYS-3 rubric.
- fix-status: open
### LOW: Viewport structure assumption — command-008 <!-- id: command-008 -->
- dimension: C
- rule: Viewport structure assumption
- location: src/uix/soma/components/command/command-provider.svelte.ts:598
- evidence: const child = el.firstElementChild as HTMLElement | null; — assumes the viewport ref's first child is the measurable element
- impact: If a consumer passes a custom child to Viewport (via the child snippet), firstElementChild may fail or measure the wrong element
- proposed-fix: Add a fallback measurement: measure the viewport itself if firstElementChild is not present, or document this as a contract requirement (viewport must wrap content in a single element)
- verify: [confirmed] Confirmed at LOW. provider line 598 `const child = el.firstElementChild as HTMLElement | null;` then observes it for resize. command-viewport.svelte renders `{@render children?.()}` with NO wrapper element, so firstElementChild measures whatever the consumer passes — fragile if the consumer renders text or multiple top-level nodes. Real robustness nit, but Viewport is `optional: true` in the morfo (line 93) and rarely used (List composes ScrollArea instead), and there is a `if (!child) return;` guard so it fails safe (no crash, just no height var). Correctly LOW; not behavioral/a11y.
- fix-status: open
## No-findings dimensions
B, D, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: command-003 (em sizes) | command-004 (px heights)
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: filter/visibility; navigation (basic); disabled items; group registration
- untested: keyboard input (Arrow/Home/End/vim); pointer events; focus state; grid columns; loop wrapping edge cases; RTL keyboard inversion; two-moments ordering
## Style observations (non-blocking)
- Command CSS is well-structured with good comments explaining concentric radius + shape-nest pattern
- Dialog integration (lines 71-99) cleanly separates panel chrome from palette chrome
- Input styling mirrors Select trigger (consistent archetype treatment)
- Item sub-parts (Icon/Shortcut) leverage inherited flex layout smartly

@ -0,0 +1,26 @@
# Audit: container
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: Contract OK, composes through Box, max-width set via recipe, padding via standard Box props.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
### LOW: MAGIC LITERAL 80rem in recipe fallback — container-001 <!-- id: container-001 -->
- dimension: magic-literal
- location: src/uix/eidos/lib/recipes/base.ts:4061
- evidence: 'width-xl': 'var(--layout-container-width-xl, 80rem)'
- impact: 80rem is a hard-coded container breakpoint, not sourced from canonical token in recipe.
- proposed-fix: Low impact—foundation token --layout-container-width-xl exists; fallback is defensive but non-canonical.
- verify: [downgraded] base.ts:4061 `'width-xl': 'var(--layout-container-width-xl, 80rem)'`. The 80rem is a defensive fallback for the LAYOUT foundation token --layout-container-width-xl (confirmed present at generated/base.css:3342), not an eidos --space-*/--radius- scale token. Recipe comment documents the xl cap. Defensive fallback, not canonical-scale drift.
- fix-status: open
## Theming facts (E-bis)
- magic literals: 80rem @ src/uix/eidos/lib/recipes/base.ts:4061 -> should reference --container-width-xl
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,69 @@
# Audit: context-menu
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema', 'eidos']
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/soma/components/context-menu/context-menu-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 1.
systemic hits: SYS-2 (magic-z and magic-literals); SYS-3 (jsdom-only keyboard testing).
## Findings
### MEDIUM: SYS-2 — context-menu-002 <!-- id: context-menu-002 -->
- dimension: E-bis
- rule: SYS-2
- location: src/uix/eidos/lib/recipes/base.ts:4303-4322
- evidence: The context-menu recipe block (lines 4303-4322) does NOT declare 'content-z' token, unlike dropdown-menu which declares 'content-z': '80' at the equivalent location. Context-menu content floats with z-index: auto, risking paint-through by positioned page elements.
- impact: Missing z-index token is a known systemic SYS-2 issue. Without explicit z-index, the portaled content panel may render behind elements with lower z-index that have explicit positioning (e.g. a toggle-group-item at z-index 1).
- proposed-fix: Add 'content-z': '80' to the context-menu recipe block at base.ts:4304 (right after the opening brace), matching the dropdown-menu z-index for consistency since both are floating overlay menus.
- verify: [confirmed] CONFIRMED real SYS-2 divergence. base.ts context-menu block (4303-4322) has NO 'content-z' token; the sibling dropdown-menu block declares `'content-z': '80'` (4279) with a comment: 'Without this the portaled panel inherits z-index: auto and any positioned page element with a positive z-index (e.g. a selected [data-toggle-group-item], z-index 1) paints THROUGH it.' context-menu.css panel rule (22-34) has NO `z-index` declaration, whereas dropdown-menu.css (34) sets `z-index: var(--dropdown-menu-content-z)` and notes 'The soma floating layer reads this computed z-index and mirrors it onto the positioner wrapper' (31-32). context-menu is an identically-portaled cursor-anchored overlay, so the same paint-through risk applies and is unmitigated. MEDIUM is correct.
- fix-status: open
### MEDIUM: SYS-3 — context-menu-005 <!-- id: context-menu-005 -->
- dimension: F
- rule: SYS-3
- location: src/uix/soma/components/context-menu/context-menu-provider.svelte.test.ts:1
- evidence: Test file header: '@vitest-environment jsdom'. All keyboard tests (lines 191-281, 283-336) run in jsdom, which does not simulate real DOM focus or keyboard event routing like a browser environment.
- impact: Keyboard routing (onkeydown handlers, arrow nav, Home/End, Escape) and focus-sync behavior (lines 334-371 in provider, lines 1021-1063 in SubContent) are tested in jsdom but should also be verified in a client environment (Playwright). Focus management edge cases (focus return on close, focus trap behavior) are HIGH-RISK for regressions.
- proposed-fix: Add a client/Playwright test file covering: (1) focus enters at first item on open, (2) arrow keys navigate correctly with loop=true/false, (3) focus returns to trigger on Escape/outside-click close, (4) submenu focus scope is isolated (arrow-left exits submenu, arrow-right enters), (5) Home/End jump correctly when items are disabled.
- verify: [confirmed] CONFIRMED SYS-3. Test header line 1 is `// @vitest-environment jsdom`. The test bodies (it() at 130/170/191/229/283/338) cover: open-from-contextmenu, getItems scoping, item activation via click + Space(216), checkbox/radio selection + Enter(275), submenu open via hover/click/ArrowRight(331), group/separator props. CRITICALLY UNTESTED: the Content onkeydown arrow-navigation index math (provider 334-371) — the loop modulo-vs-clamp logic at 348-355, Home/End at 356-361 — plus focus-return-to-trigger on close and Escape-close. The highest-risk keyboard route (directional index math) is exercised on zero paths, and runs jsdom-only with no client/Playwright companion. Matches the 'complex behavior tested only on its easy path (selection/open-close) while keyboard/focus untested' SYS-3 profile. MEDIUM correct.
- fix-status: open
### MEDIUM: 2-of-3 — context-menu-006 <!-- id: context-menu-006 -->
- dimension: A
- rule: 2-of-3
- location: src/uix/morfo/components/context-menu.ts:73-77 (radio-group part)
- evidence: RadioGroup part (kebab: 'radio-group', archetype: 'group') declares role='group' and data/aria as empty arrays (lines 174-176). No data attributes are bound. Only soma registers it at line 675; sema does not select it; eidos does not consume it.
- impact: RadioGroup part violates 2-of-3: used by soma (runtime.part registration) but not by sema or eidos. It is a structural grouping container with no perceptual or visual binding. This is acceptable if it is purely structural, but it should be marked optional or have a docstring clarifying its role.
- proposed-fix: RadioGroup is intentionally structural-only (holding radio items that self-manage checked state via the RadioItem logic). This is a design choice, not a bug. Document in morfo: /* Structural container; individual RadioItems manage state and selection. Eidos/sema do not target this part directly. */
- verify: [confirmed] Confirmed as the candidate itself frames it (confidence:low, proposedFix concedes 'This is a design choice, not a bug'). Morfo RadioGroup (168-177): role='group', empty data:[]/aria:[]. Provider registers it (675) for context publication (ContextMenuRadioGroupProvider.ctx) so RadioItems can read the group value. Grep of context-menu.css finds NO `data-context-menu-radio-group` selector (eidos uses `display: contents` only implicitly — actually no rule targets it at all), and the sema pack does not select it. So it is soma-only on the 2-of-3 axis, but legitimately: it is a pure structural/context container with no perceptual or visual surface, role='group' carries the a11y semantics declaratively, and RadioItems self-manage checked state. Acceptable structural exception, not a defect. LOW doc-nit at most.
- fix-status: open
### LOW: SYS-2 — context-menu-004 <!-- id: context-menu-004 -->
- dimension: E-bis
- rule: SYS-2
- location: src/uix/eidos/components/context-menu/context-menu.css:171
- evidence: Trigger outline on data-state='open' hardcodes '1px dashed' border: 'outline: 1px dashed var(--color-border-default);'. Outline width and style are magic literals without token backing.
- impact: The outline appearance cannot scale with theme or design system constraints. No way to override or scale the 1px width globally.
- proposed-fix: Create recipe tokens 'trigger-focus-outline-width': '1px' and 'trigger-focus-outline-style': 'dashed', then reference them: 'outline: var(--context-menu-trigger-focus-outline-width, 1px) var(--context-menu-trigger-focus-outline-style, dashed) ...'.
- verify: [downgraded] Partially valid kernel, over-stated. context-menu.css:171 `outline: 1px dashed var(--color-border-default)`. The bare `1px` IS a minor drift: every sibling component uses `var(--border-width)`/`var(--border-width-medium)` for border/outline widths (announce.css:29, combobox.css:96, dialog.css:28, listbox.css:41, etc.); this line is the only bare `1px` outline-width across the eidos components grep. So `1px`->`var(--border-width)` is a legitimate tokenization. BUT the candidate's proposed custom tokens for `dashed` over-reach — outline-style is a discrete keyword with no scale to reference. `--color-border-default` is a valid role token. Impact is minimal: this is a purely decorative courtesy outline on the otherwise-inert trigger when open (per the CSS comment 156-164). LOW magic-literal nit, not MEDIUM.
- fix-status: open
## No-findings dimensions
B, C, D, E, G
## Theming facts (E-bis)
- magic z-index: context-menu-002: recipe missing 'content-z' token (SYS-2) | context-menu-004: trigger outline 1px hardcoded
- magic literals: context-menu-003: sub-trigger arrow size 0.625rem, rotate 45deg (SYS-2) | context-menu-004: trigger outline 1px dashed (SYS-2) | src/uix/eidos/components/context-menu/context-menu.css:114: opacity 0.55 (but covered by token var(--context-menu-item-disabled-opacity)) | src/uix/eidos/components/context-menu/context-menu.css:130: opacity var(--opacity-muted) - correct
- undeclared parts: context-menu-001: 'sub' part declared in morfo but never registered via runtime.part() in provider
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom only (vitest-environment jsdom at line 1)
- covers: contextmenu trigger event and anchor point capture (lines 130-168); item scoping to current content, excluding nested submenus (lines 170-189); item activation by click and keyboard (lines 191-227); checkbox and radio state toggling (lines 229-281); submenu open via pointer hover with 100ms delay (lines 283-336); group heading and separator accessibility attributes (lines 338-365)
- untested: Arrow key navigation in content (loops, clamping when loop=false); Home/End key jump to first/last item; Escape key close menu and focus return to trigger; Tab key escape from menu (trap=false expected); Typeahead character matching; Submenu arrow-key navigation (left/right to close/open submenu); Focus sync when items are disabled (skip to next focusable); SafePolygon hover exit behavior; Dismissal on outside-click and interact-outside events; Focus trap behavior (when trapFocus=true)
## Style observations (non-blocking)
- Context-menu recipe tokens closely mirror dropdown-menu except for missing 'content-z' and arrow size tokens, which suggests the component was copied but not fully reconciled for floating layer z-index and icon sizing patterns.
- The trigger outline affordance (dashed outline on open) is a nice UX cue but lacks a way to disable or customize the width/style globally.

@ -0,0 +1,62 @@
# Audit: cropper
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: src/uix/soma/components/cropper/cropper-provider.svelte.ts
cleanup-audit (A6/A35/A36): Timers: soma.uix.timers.schedule(null, 120, ...) at line 302 — disposed by runtime ✓. Listeners: All pointer events (onpointerdown/move/up in Viewport/Selection/Handle) use setPointerCapture + releasePointerCapture in same handler; no separate cleanup needed. Image load handler in eidos cropper.svel
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 1.
## Findings
### MEDIUM: SYS-1: Scope-drift — eidos recipe directory exists but morfo scope omits 'eidos' — cropper-001 <!-- id: cropper-001 -->
- dimension: A
- rule: SYS-1: Scope-drift — eidos recipe directory exists but morfo scope omits 'eidos'
- location: src/uix/morfo/components/cropper.ts:20
- evidence: Morfo declares scope: ['soma', 'sema'] but src/uix/eidos/components/cropper/ exists with CSS and SVG component
- impact: Scope mismatch signals incomplete contract declaration; framework tooling may not validate eidos layer against morfo
- proposed-fix: Update morfo scope to ['soma', 'sema', 'eidos']
- verify: [confirmed] Confirmed SYS-1 scope-drift. morfo cropper.ts:20 `scope: ['soma', 'sema']` omits 'eidos', yet the eidos layer exists: dir src/uix/eidos/components/cropper/ (cropper.css, cropper.svelte, types.ts) AND a recipe `cropper:` at src/uix/eidos/lib/recipes/base.ts:1152. Matches the established systemic baseline (sibling image-picker.ts:20 and image-adjustments.ts:18 carry the same omission). MEDIUM stands.
- fix-status: fixed (212624e0)
### MEDIUM: SYS-3: Missing test coverage for high-risk paths (gesture, async, timers) — cropper-003 <!-- id: cropper-003 -->
- dimension: F
- rule: SYS-3: Missing test coverage for high-risk paths (gesture, async, timers)
- location: src/uix/soma/components/cropper/cropper-provider.svelte.test.ts
- evidence: Tests cover geometry (moveRect, resizeRect) and ZoomPan, but no coverage for: gesture handlers (onpointerdown/move/up in Viewport/Selection/Handle), cropImage() async function, zoom throttle timer, or fixed-size $effect. Test env is jsdom.
- impact: Critical business logic (drag/resize/zoom interactions, canvas extraction, timer cleanup) is untested and may regress without detection
- proposed-fix: Add tests for: (1) pointer gesture sequences (down→move→up), (2) zoom throttle behavior, (3) cropImage() Blob production with shape masking, (4) fixed-size centering effect
- verify: [confirmed] Confirmed SYS-3 / dim F. Test file (cropper-provider.svelte.test.ts, `@vitest-environment jsdom` line 1) covers pure geometry thoroughly (moveRect, resizeRect incl. aspect/min/max/edge corners, lines 58-137), ZoomPan (139-181), and provider setCrop/disabled/snippetProps (189-223). UNTESTED high-risk paths: (1) pointer gesture sequences (down→move→up in Viewport/Selection/Handle providers), (2) `cropImage()` async canvas→Blob extraction incl. round-shape masking (provider lines 237-290), (3) zoom-throttle timer + emitZoom gate (294-305), (4) fixed-size centering $effect (183-205). MEDIUM stands — geometry is well-covered so this is a partial, not total, gap.
- fix-status: open
### LOW: A6: Event listener / handler not disposed in $effect return — cropper-002 <!-- id: cropper-002 -->
- dimension: B, A6
- rule: A6: Event listener / handler not disposed in $effect return
- location: src/uix/eidos/components/cropper/cropper.svelte:44-58
- evidence: $effect creates new Image element and assigns img.onload handler (line 52) but never cleans up. On src change, new Image created; old handler may fire if response arrives after effect re-run.
- impact: Old onload handlers can fire after component re-runs the effect, causing stale handler invocations and potential memory pressure from accumulated handlers
- proposed-fix: Return a cleanup function from the $effect that clears the handler, or store img in state and reuse it with cleanup: `return () => { img.onload = null; img.src = ''; }`
- verify: [downgraded] Downgraded from MEDIUM/A6 to LOW. The $effect at cropper.svelte:44-58 creates a local `const img = new ImageCtor()` (line 51), sets one-shot `img.onload` (line 52), `img.src = s` (line 57), with no cleanup return. This is NOT an A6 resource leak: the Image is never inserted into the DOM, the handler is one-shot, and the element is GC-eligible once the local goes out of scope — there is no persistent listener/observer/timer to dispose. The genuine (minor) defect is a stale-async-write race: if `src` changes and an OLD image's load resolves AFTER the new one, the old onload writes `aspectRatio` (line 54) to a stale ratio. It is NOT an A35/A36 loop — the effect reads `src`, not `aspectRatio`, so no tracked read-back, no freeze. Impact: transient wrong aspect-ratio on rapid src swaps. The provider's `loadImage` (cropper-provider.svelte.ts:96-106) avoids this by awaiting a Promise with no reactive write. LOW.
- fix-status: open
## No-findings dimensions
C, D, E, E-bis, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 9999px in box-shadow (line 52 cropper.css) — documented mask pattern
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: moveRect geometry; resizeRect with aspect/minSize/maxSize; ZoomPan scale clamping and pan bounds; ZoomPan fraction mapping; setCrop updates state and fires callback; disabled blocks setCrop; snippetProps exposes shape/dragging
- untested: Gesture handlers (onpointerdown/move/up); cropImage() Blob canvas extraction; Zoom throttle timer behavior; Fixed-size $effect re-centering; Shape masking (round crop); Image load aspect-ratio tracking; commit-crop event trigger on canvas complete
## Style observations (non-blocking)
- Zoom buttons correctly use --focus-ring-width + --focus-ring-color tokens (canonical two-ring model)
- Recipe tokens all reference theme vars or semantic space/radius/opacity
- Box-shadow 9999px mask is documented pattern (not magic literal violation)
- Cursor values (move, grab, grabbing, resize variants) are semantic browser cursors
- :active pseudo-class on img is native browser feedback for pressed state (acceptable)

@ -0,0 +1,41 @@
# Audit: css-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: G:\dev\svelte\vicen\src\uix\soma\components\css-field\css-field-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SCOPE-DRIFT: eidos recipe dir exists but morfo 'scope' omits 'eidos' — css-field-001 <!-- id: css-field-001 -->
- dimension: Frontier, SYS-1
- rule: SCOPE-DRIFT: eidos recipe dir exists but morfo 'scope' omits 'eidos'
- location: G:\dev\svelte\vicen\src\uix\morfo\components\css-field.ts:7
- evidence: scope: ['soma', 'sema'], — eidos/components/css-field/ exists with css-field.svelte, css-field-input.svelte, etc., but scope array does not include 'eidos'
- impact: Contract validator cannot check that eidos wrappers only import from morfo PARTS, risking cross-layer coupling over time as the component evolves
- proposed-fix: Add 'eidos' to morfo scope: scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] Confirmed as a SYS-1 scope-drift instance. `css-field.ts:7` = `scope: ['soma', 'sema']` while an eidos dir exists with a wrapper + css (`eidos/components/css-field/css-field.svelte` imports `./css-field.css`, plus `types.ts`/`index.ts`). MEDIUM is correct per the SYS-1 catalog. Caveat for the orchestrator: this is SYSTEMIC across the whole field family, not a css-field anomaly — the project's REFERENCE baseline NumberField is identical (`number-field.ts:7` = `scope: ['soma', 'sema']` with its own eidos dir), as are time-field/color-field/date-field (all `scope: ['soma', 'sema']`). Note also css-field has no dedicated recipe: its eidos css (`css-field.css`) declares 'CssField has no visual of its own: it IS a spin-field' and styling comes from the shared `spin-field` recipe (`base.ts:1016`) via structural identity. The scope-validator gap still stands regardless.
- fix-status: fixed (212624e0)
## No-findings dimensions
Contract, DOM-selector, TSC, Tests, Redundancy, Behavior (keyboard), Behavior (A30), Behavior (A6), Behavior (TWO-MOMENTS)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 400 | 100 | 5
- undeclared parts: none
- roles clean: true · variants clean: true
- label-font (one step below input?): COMPLIANT: Field label font-size is derived as calc(var(--_field-control-font-size) - (var(--font-size-md) - var(--font-size-sm))) per field.css, which is one typographic step below the input. CssField uses shared spin-field visual, which inherits --_field-control-font-size from the recipe (e.g. field-control-font-size-md: var(--font-size-md) = 16px), making the label 14px (one step down). ✓
## Tests (F)
- exists: true · env: jsdom
- covers: commit on blur; increment/decrement with per-unit steps; bare number default unit; disallowed unit rejection (untilFix); keyword acceptance; clamping to min/max; sium schema delegation; scrubber scrubbing with unit preservation; spinbutton aria props
- untested: readonly-without-value warning; onbeforeinput paste/IME rejection; clearTarget(input) actually clears the warn signal; RTL scrubber mirroring; A30 inputId registration with parent Field
## Style observations (non-blocking)
- Default step-by-unit logic (rem/em: 0.1, else 1) is sensible and matches the UI pattern for CSS editing
- Scrubber sensitivity (5px per step default) is well-tuned for precise CSS values
- Bare-number regex parsing (line 39) handles leading dot correctly
- Comments throughout provider are clear and precise (Book §6.2 reference, untilFix signal flow)

@ -0,0 +1,62 @@
# 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 <!-- id: 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:7` declares `scope: ['soma', 'sema']` (grep verified), while the eidos layer exists: recipe block `'date-field':` at `src/uix/eidos/lib/recipes/base.ts:1172` and CSS `src/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 declares `scope: ['soma', 'sema']` yet `src/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 <!-- id: 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 ISO `value`/`name`/`required` props (1027-1040) are never exercised; (2) readonly-without-value A24 warn — the `$effect` calling `soma.logger.warn` (provider 354-369) is never triggered, even though the harness already stubs `logger.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) <!-- id: date-field-A30 -->
- 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.inputId` registration 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.inputId` and never reads it back, and `opts.id` is 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 `$effect` with a direct constructor assignment guarded by `if (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 `$effect` pattern. 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.

@ -0,0 +1,69 @@
# Audit: date-picker
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work).
provider: src/uix/soma/components/date-picker/date-picker-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 4 · LOW 0.
systemic hits: SYS-1 (scope-drift: morfo scope ['soma', 'sema'] but eidos dir exists).
composition (A27): COMPOSES: DatePicker correctly composes via shared writableActive refs. date-picker.svelte creates sharedOpen, sharedValue, sharedPlaceholder, and passes them to PopoverProvider.create() and DateFieldProvider.create() (lines 167-194). Trigger wraps PopoverTriggerProvider. Calendar wraps CalendarProvider with filtered isDateDisabled to enforce readonlySegments coherence. No re-implementation detected; all composed parts (Popover, DateField, Calendar) are referenced via shared refs or forwarded exports.",
<parameter name="styleObservations">["data-kind is stamped on the Provider element (line 197) to drive visual view switching; this is morfo-declared and visually clean. CSS uses cascading :has() selectors (line 138-145) for responsive popover width overrides when hosting calendar/grid, avoiding hardcoded breakpoints.", "Disabled state uses opacity var(--date-field-disabled-opacity) (line 103), consistent with field theming. Role/aria attrs are delegated to composed Calendar part (dialog role, aria-label) and not duplicated in Provider.", "No bare literals for spacing, sizing, z-index, or opacity; all theme tokens properly referenced."]
## Findings
### MEDIUM: SYS-1 scope-drift: an eidos recipe dir exists but morfo 'scope' omits 'eidos' — date-picker-001 <!-- id: date-picker-001 -->
- dimension: A: Contract (morfo)
- rule: SYS-1 scope-drift: an eidos recipe dir exists but morfo 'scope' omits 'eidos'
- location: src/uix/morfo/components/date-picker.ts:13
- evidence: scope: ['soma', 'sema'], but src/uix/eidos/components/date-picker/ directory exists with date-picker.css, *.svelte files, and types.ts
- impact: Morfo contract mismatches the actual layer composition. Eidos recipes exist but scope doesn't declare them, risking validator confusion and runtime assumptions about which layers are active.
- proposed-fix: Change morfo scope to ['soma', 'sema', 'eidos'] to match the actual directory structure and eidos recipe presence.
- verify: [confirmed] Confirmed as SYS-1 scope-drift. date-picker.ts:13 reads `scope: ['soma', 'sema'],` yet `src/uix/eidos/components/date-picker/` exists with date-picker.css (6115 bytes), date-picker.svelte, types.ts, and several *.svelte wrappers. This is systemic across the whole picker family: color-picker, date-range-picker, time-picker, time-range-picker, calendar all read `scope: ['soma', 'sema']` while their eidos dirs exist; only range-calendar declares eidos. MEDIUM is the correct severity per the SYS-1 baseline — it is a contract/metadata mismatch, not a runtime defect (createAttrs/morfo are unaffected since the morfo is `as const satisfies Morfo` at line 230).
- fix-status: fixed (212624e0)
### MEDIUM: INERT EVENTS: morfo declares 'open' event but provider never fires it via runtime.trigger( — date-picker-002 <!-- id: date-picker-002 -->
- dimension: A: Contract (morfo), B: Behavior (soma)
- rule: INERT EVENTS: morfo declares 'open' event but provider never fires it via runtime.trigger()
- location: src/uix/morfo/components/date-picker.ts:22-40 (event declaration) vs src/uix/soma/components/date-picker/date-picker-provider.svelte.ts (no 'open' trigger)
- evidence: Morfo declares: {name: 'open', semantic: {family: 'emerge', verb: 'open', target: v.partRef('calendar'), sequence: 'pre', ...}}. Provider only fires 'close' via triggerClose() at lines 222, 232, 268; no runtime.trigger('open', ...) call exists.
- impact: The 'open' event exists only to satisfy schema validation. Consumers cannot listen to it because the provider never emits it. This is inconsistent with the morfo contract which signals the event should fire.
- proposed-fix: Either (a) fire runtime.trigger('open', ...) when the popover opens (handleDateSelect path or in a watch on opts.open), or (b) remove the 'open' event declaration from morfo if it is not needed per the design.
- verify: [confirmed] Confirmed but DOWNGRADE-CAPPED to MEDIUM (it was already MEDIUM). The `open` event (morfo lines 23-41) is genuinely inert: grep for `runtime.trigger` in the provider returns ONLY line 268 `runtime.trigger('close', ...)` (fired from triggerClose at 210/222/232). There is no `runtime.trigger('open', ...)` anywhere. NOTE the candidate's evidence is partly stale — it claimed line numbers 222/232/268 fire 'close', which is correct, and it correctly identified `open` as never fired. The morfo's own comment (lines 42-49) explicitly anticipates this: 'Today's date-picker provider just toggles opts.open; if it ever explicitly calls runtime.trigger(close...)'. Matches the documented picker-family inert-events pattern. MEDIUM is the ceiling per baseline.
- fix-status: open
### MEDIUM: Test coverage is jsdom-only and does not exercise high-risk paths: keyboard navigation, fo — date-picker-003 <!-- id: date-picker-003 -->
- dimension: F: Tests
- rule: Test coverage is jsdom-only and does not exercise high-risk paths: keyboard navigation, focus management, modal vs inline mode dismissal, date validation, cancel/commit/clear actions.
- location: src/uix/soma/components/date-picker/date-picker-provider.svelte.test.ts:1 (@vitest-environment jsdom) and lines 111-174
- evidence: Tests are marked jsdom-only (@vitest-environment jsdom). The test suite only covers: (1) part registration, (2) state flags, (3) closeOnDateSelect flag. Missing: keyboard Escape/Tab/Enter/Space, grid navigation (arrow keys), focus trap, modal mode behavior, cancel() reverting value, commit() triggering close event, clear() resetting to undefined, two-moments sequence (data-last-action before data-state).
- impact: Risk of undiscovered bugs in interactions-heavy paths (dismissal, modal locking, date-specific validation edge cases) that jsdom doesn't simulate. Keyboard routes in calendar grids, focus roving, and dismissal semantics are untested.
- proposed-fix: Add browser-level (Playwright/client) tests covering: (a) Escape closes popover + triggers 'close' event, (b) Tab focus-traps in modal mode, (c) modal mode prevents outside-click dismissal, (d) cancel() reverts value to snapshot, (e) commit() and clear() fire correct close cause, (f) data-last-action writes before data-state flips.
- verify: [confirmed] Confirmed as SYS-3 jsdom-only / kbd-untested. Test file line 1 is `// @vitest-environment jsdom`. The suite (lines 111-174) has exactly 3 `it()` blocks: provider part registration, root state flags, and the closeOnDateSelect toggle via handleDateSelect. No coverage of: Escape/Tab/Enter/Space keyboard, calendar grid arrow navigation, focus trap (morfo declares focus.trap:true, focus.return:'trigger'), cancel() value revert, commit()/clear() close-cause, or the two-moments data-last-action-before-data-state ordering in triggerClose (266-268). These are the high-risk interaction paths. MEDIUM is correct (not HIGH) — it is a missing-coverage observation on interaction-heavy paths, consistent with the SYS-3 baseline severity.
- fix-status: open
### MEDIUM: INERT EVENTS: morfo declares 'commit-reset' event but provider never fires it via runtime. — date-picker-N01 <!-- id: date-picker-N01 -->
- dimension: A: Contract (morfo), B: Behavior (soma)
- rule: INERT EVENTS: morfo declares 'commit-reset' event but provider never fires it via runtime.trigger()
- location: src/uix/morfo/components/date-picker.ts:66-76 (commit-reset declaration) vs src/uix/soma/components/date-picker/date-picker-provider.svelte.ts:240-242 (clear())
- evidence: Morfo declares a third event `{ name: 'commit-reset', semantic: { family: 'commit', verb: 'reset', target: v.partRef('calendar'), sequence: 'post', intent: 'neutral' } }` whose intent (per its comment line 67) is 'User cleared the date (explicit clear button or backspace).' The provider's `clear()` method does exactly that — `this.opts.value.current = undefined;` — but does NOT call `runtime.trigger('commit-reset', ...)`. Grep of the provider for runtime.trigger returns only the 'close' call at line 268. So commit-reset is a second inert event the candidate auditor missed (it only flagged 'open').
- impact: A 'commit' family event with a real perceptual intent (clearing the value) never reaches the sema engine — clearing the date is perceptually silent even though the morfo says it should emit. Consumers wiring telemetry/sound to commit-reset receive nothing. Same INERT-EVENT category as the 'open' finding; MEDIUM ceiling per the picker-family baseline.
- repro: Open date-picker, select a date, press the Clear footer action: opts.value resets to undefined but no commit-reset signal is emitted to sema.
- proposed-fix: In clear() fire `runtime.trigger('commit-reset', { fallbackTarget: this.runtime.partRef('calendar') ?? undefined })` after setting value undefined (sequence:'post' is already declared, so the state write precedes the emit), OR remove the commit-reset event from the morfo if a silent clear is intended.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
## No-findings dimensions
B: Behavior (soma) - keyboard/grid routes, C: DOM-selector (CSS.escape), D: Frontier (eidos<->soma cross-layer imports), E: TSC (eidos css), E-bis: Theming (roles/tokens), G: Redundancy (composition correctly avoids re-implementation)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 'data-kind' | 'date-picker' | 'field-trigger'
- undeclared parts: none
- roles clean: true · variants clean: true
- conformance: CLEAN: data-* attrs follow [data-{component}] and [data-{component}-{part}] naming; eidos recipe imports only composed component CSS (date-field, calendar, month-grid, year-grid), not isolated primitives; role attributes and aria-* attrs match morfo declarations in Provider/Trigger/Calendar parts; no double-write issues detected (syncAttrs not used); token references are canonical (--color-{role}-*, --space-*, --date-field-*)
## Tests (F)
- exists: true · env: jsdom
- covers: provider part registration; state flags (data-state, data-disabled/readonly/required/invalid); closeOnDateSelect flag toggle
- untested: keyboard routes (Escape, Tab, Enter, Space, arrow keys for grid nav); focus trap and focus restoration; modal mode (prevents outside-click, requires explicit commit/cancel); dismissal cause tracking (data-last-action write order); cancel() value revert snapshot; commit() and clear() event emission; date validation edge cases (minValue, maxValue, custom validate); two-moments: data-last-action write before data-state flip; locale/dir resolution; readonly segment filtering in calendar; readonly segment coherence with navigation

@ -0,0 +1,81 @@
# Audit: date-range-field
audit-version: 1
audited-at: 2026-06-26
scope:
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-range-field\date-range-field-provider.svelte.ts
field-family (A13/A24-26/A30): A13: NOT APPLICABLE - no hidden-input parts. A24: MISSING warning for readonly+undefined. A25: IMPLEMENTED split segments, test coverage gap. A26: INHERITED from DateField composition. A30: CLEAN - labelNode direct callback, inputId uses untrack()
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 3.
## Findings
### MEDIUM: FIELD FONT RULE: field LABEL is ONE typographic step BELOW the input; input font is 1:1 wi — date-range-field-002 <!-- id: date-range-field-002 -->
- dimension: E-bis
- rule: FIELD FONT RULE: field LABEL is ONE typographic step BELOW the input; input font is 1:1 with type scale
- location: G:\dev\svelte\vicen\src\uix\eidos\components\date-range-field\date-range-field.css lines 54-55
- evidence: [data-date-range-field-label] { font-size: var(--date-field-label-font-size, var(--font-size-sm)); } hardcodes label to sm for all input sizes. Compare to DateField which uses calc(1em - (var(--font-size-md) - var(--font-size-sm))). Correct only at md size.
- impact: Label font size violates typographic hierarchy at sizes xs/sm/lg/xl
- proposed-fix: Use: font-size: calc(1em - (var(--font-size-md) - var(--font-size-sm))); and ensure it inherits 1em from the input's dynamic --_date-field-font-size
- verify: [confirmed] Confirmed MEDIUM. date-range-field.css:51-55 sets `[data-date-range-field-label] { font-size: var(--date-field-label-font-size, var(--font-size-sm)); }` — fixed at `sm` for every input size, and notably no `font: inherit`. The reference DateField (date-field.css:104-111) does it correctly: `font: inherit;` then `font-size: calc(1em - (var(--font-size-md) - var(--font-size-sm)));` with the comment 'Label one typographic step below the input ... Scales.' So DateRangeField's label is correct only at md and violates the FIELD FONT RULE (label one step below input, scaling) at xs/sm/lg/xl. Note: systemic — time-range-field.css:52-56 has the identical hardcoded-sm bug.
- fix-status: open
### MEDIUM: Tests must cover high-risk paths including split readonly segments per endpoint (A25) — date-range-field-005 <!-- id: date-range-field-005 -->
- dimension: F
- rule: Tests must cover high-risk paths including split readonly segments per endpoint (A25)
- location: G:\dev\svelte\vicen\src\uix\soma\components\date-range-field\date-range-field-provider.svelte.test.ts lines 73-74
- evidence: Test creates startReadonlySegments and endReadonlySegments but does not verify they work independently. No test case sets different readonly segments for start vs end.
- impact: Missing test coverage for A25 (split start/endReadonlySegments) — divergent readonly settings may not work correctly without verification
- proposed-fix: Add test case where startReadonlySegments=['month'] and endReadonlySegments=['day'] to verify each endpoint respects its own readonly configuration
- verify: [confirmed] Confirmed but lower confidence. The provider test (date-range-field-provider.svelte.test.ts:73-74) seeds startReadonlySegments/endReadonlySegments as empty `state<EditableSegmentPart[]>([])` and never exercises divergent start-vs-end values; no test asserts A25 split behavior. The split-readonly wiring actually lives in components/date-range-field-input.svelte:73-77 (start→startReadonlySegments, end→endReadonlySegments), which this provider-level test cannot reach anyway — so the coverage gap is real but the untested code is trivial, type-checked forwarding. Genuine F-gap on a path the rubric calls high-risk (A25 says a single symmetric readonlySegments is wrong), so MEDIUM stands; the missing assertion would belong to a component/DOM test, not this provider test.
- fix-status: open
### LOW: Parts registered via runtime.part('x') must exist in morfo & vice-versa. 2-of-3 rule. — date-range-field-001 <!-- id: date-range-field-001 -->
- dimension: A
- rule: Parts registered via runtime.part('x') must exist in morfo & vice-versa. 2-of-3 rule.
- location: G:\dev\svelte\vicen\src\uix\soma\components\date-range-field\date-range-field-provider.svelte.ts lines 1-30
- evidence: Morfo declares 3 parts: Provider (line 20), Label (line 46), Input (line 59). Provider calls runtime.part('provider', ...) at line 110. Label calls runtime.part('label', ...) at line 192. Input part is NOT registered via runtime.part. DateField by contrast registers 'input' with runtime.part.
- impact: Input part attrs not enforced by runtime; contract violation breaks 2-of-3 semantic guarantee
- proposed-fix: DateRangeFieldInputProvider should call runtime.part('input', {...}) in its constructor to register the Input part, matching DateField's pattern
- verify: [downgraded] Downgraded HIGH→LOW. The claim that the morfo 'input' part is unregistered and 'breaks the 2-of-3 guarantee' is not supported. The part IS materialized: components/date-range-field-input.svelte:107-109 overlays `[dateRangeFieldAttrs.input]: ''` (from createAttrs(dateRangeFieldMorfo)) plus `'data-endpoint': type`, and role=group/aria come from the composed DateField input provider (date-field-provider.svelte.ts:913 runtime.part('input',...)). The 2-of-3 rule is satisfied — soma emits `data-date-range-field-input`+`data-endpoint`, eidos recipe targets them (date-range-field.css:60,65-71). Pattern is deliberate, documented (CSS header lines 12-16, README lines 72-74) and identical to TimeRangeField. No behavioral/a11y break; the part renders correctly. Real residue is only LOW: the DateRangeField-specific marker attrs are emitted via createAttrs rather than routed through runtime.part('input')/assert, so the DateRangeField runtime doesn't itself enforce that part's contract on the element — a consistency nit, not a contract violation. The cited line numbers in the evidence ('Provider line 110','Label line 192') are real, but the conclusion (Input part missing) is wrong because the part is emitted by the .svelte component, which the candidate did not read.
- fix-status: open
### LOW: Recipe font-size/icon-size tokens MUST reference --font-size-*/--icon-size-* (a literal px — date-range-field-003 <!-- id: date-range-field-003 -->
- dimension: E-bis
- rule: Recipe font-size/icon-size tokens MUST reference --font-size-*/--icon-size-* (a literal px/rem/em/% is drift)
- location: G:\dev\svelte\vicen\src\uix\eidos\components\date-range-field\date-range-field.css line 83
- evidence: font-size: 0.9em; /* literal: separator scales with the field font-size */
- impact: Magic literal bypasses coherence guard; separator scaling not tokenized
- proposed-fix: Extract to token --date-range-field-separator-font-scale: 0.9; in recipes/base.ts
- verify: [downgraded] Kept LOW but flagged as defensible/style. date-range-field.css:83 `font-size: 0.9em; /* literal: separator scales with the field font-size */` is a relative multiplier on a decorative ::before em-dash, not an absolute px/rem drift, and is explicitly commented as scaling with the inherited field font. There is no canonical token expressing '0.9× inherited size' (the --font-size-* scale is absolute steps, not multipliers), so tokenizing it as proposed would not improve coherence. Identical 0.9em appears in time-range-field.css:84. Borderline taste — not a meaningful magic-literal defect.
- fix-status: open
### LOW: Bare opacity decimal -> --opacity-* (0.05 step) — date-range-field-004 <!-- id: date-range-field-004 -->
- dimension: E-bis
- rule: Bare opacity decimal -> --opacity-* (0.05 step)
- location: G:\dev\svelte\vicen\src\uix\eidos\components\date-range-field\date-range-field.css line 89
- evidence: opacity: var(--date-field-disabled-opacity, 0.55); fallback 0.55 is hardcoded, not a --opacity-* token
- impact: Magic opacity literal in CSS; inconsistent with token-based opacity scale
- proposed-fix: Use: opacity: var(--date-range-field-disabled-opacity, var(--opacity-disabled));
- verify: [confirmed] Confirmed LOW (kept). date-range-field.css:89 `opacity: var(--date-field-disabled-opacity, 0.55);`. The fallback `0.55` is DEAD: the date-field recipe defines `'disabled-opacity': 'var(--opacity-disabled)'` (base.ts:1222) → `--date-field-disabled-opacity` (= --opacity-disabled = 0.4, generated/base.css:296) is always defined on `[data-date-field]`, and the range root overlays `data-date-field=''` (date-range-field.svelte:44), so the token resolves and the literal never renders. So this is not a live magic-opacity number affecting rendering — it's a misleading dead fallback whose value (0.55) also disagrees with the token it shadows (0.4). LOW token-hygiene nit, not a rendering defect. Same dead `0.55` in time-range-field.css:90.
- fix-status: open
## No-findings dimensions
C, D, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 0.9em (separator font-size) | 0.55 (opacity fallback)
- undeclared parts: none
- roles clean: true · variants clean: true
- label-font (one step below input?): VIOLATED: hardcoded to --font-size-sm instead of dynamic calc(); correct at md, wrong at xs/sm/lg/xl
## Tests (F)
- exists: true · env: jsdom
- covers: validation ordering; min/max bounds; custom validator; label click focus
- untested: split readonly independence; readonly-without-value warning; shared placeholder constraint; endpoint validation delegation
## Style observations (non-blocking)
- Separator comment notes that literal em is intentional for scaling but should be tokenized
- Label font-family references date-field-label-font-family (correct layering)
- Grid layout uses space-* tokens consistently

@ -0,0 +1,47 @@
# Audit: date-range-picker
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work).
provider: src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 0.
systemic hits: SYS-1: scope-drift (eidos recipe missing); SYS-5: no syncAttrs double-write detected (eidos properly wraps soma without forcing attr overrides).
composition (A27): Composes via A27: PopoverProvider and DateRangeFieldProvider bridged via SHARED writableActive refs (sharedOpen, sharedValue, sharedPlaceholder) in Provider component. Only root Provider, Trigger, and Calendar sub-parts are unique; Popover, Field, and RangeCalendar parts re-exported from composed primitives.
## Findings
### MEDIUM: SYS-1 SCOPE-DRIFT: morfo declares scope=['soma','sema'] but an eidos recipe directory exis — date-range-picker-001 <!-- id: date-range-picker-001 -->
- dimension: A: Contract
- rule: SYS-1 SCOPE-DRIFT: morfo declares scope=['soma','sema'] but an eidos recipe directory exists (25 files in src/uix/eidos/components/date-range-picker/) yet NO recipe entry exists in src/uix/eidos/lib/recipes/base.ts
- location: src/uix/morfo/components/date-range-picker.ts:13 + src/uix/eidos/lib/recipes/base.ts (missing entry)
- evidence: Morfo line 13: `scope: ['soma', 'sema'],` but src/uix/eidos/components/date-range-picker/ directory has 25 component files. Grep for 'date-range-picker' in base.ts returns 0 matches.
- impact: Eidos CSS file (date-range-picker.css) exists and is imported in eidos component (line 8) but no canonical recipe tokens/config define size/color/variant scales for this component; recipes are the contract bridge for eidos.
- proposed-fix: Add 'eidos' to morfo scope declaration: `scope: ['soma', 'sema', 'eidos']` and create the date-range-picker recipe block in src/uix/eidos/lib/recipes/base.ts with token definitions for size (xs/sm/md/lg), color (primary/secondary/neutral/affirm/fulfill/risk/threat/loss), and variant (surface/outline/ghost).
- verify: [confirmed] CONFIRMED as SYS-1 scope-drift (MEDIUM). morfo line 13 `scope: ['soma', 'sema']` omits 'eidos', yet src/uix/eidos/components/date-range-picker/ has 25 files incl. date-range-picker.css imported by the eidos component. Grep for 'date-range-picker' in lib/recipes/base.ts = 0 matches (confirmed). HOWEVER the candidate's proposedFix is partly WRONG and I downgrade its remedy claim: the eidos CSS does NOT need its own recipe block. I read date-range-picker.css (690 lines) — it owns NO `--date-range-picker-*` recipe tokens; it COMPOSES tokens from sibling recipes (`--date-field-height-md`, `--calendar-padding-md`, `--calendar-accent-solid`, lines 2-28) plus global scales (`--color-primary-*`, `--space-*` L391/419, `--radius-md` L589, `--font-size-md/sm` L595/621). This is the A27 picker-composition pattern: the picker composes Field+Calendar+Popover and legitimately has no recipe entry. Verified systemic across the family: date-picker.ts also has `scope: ['soma','sema']` and 0 recipe matches; time-range-picker same. So the ONLY real defect is the missing 'eidos' token in the scope array — not a missing recipe block. Severity MEDIUM (SYS-1) is correct; the fix is to add 'eidos' to scope, NOT to author recipe tokens.
- fix-status: fixed (212624e0)
### MEDIUM: INERT EVENTS: morfo declares 'commit-reset' event but provider NEVER fires it via runtime. — date-range-picker-002 <!-- id: date-range-picker-002 -->
- dimension: A: Contract
- rule: INERT EVENTS: morfo declares 'commit-reset' event but provider NEVER fires it via runtime.trigger()
- location: src/uix/morfo/components/date-range-picker.ts:65-72 (declaration) vs src/uix/soma/components/date-range-picker/date-range-picker-provider.svelte.ts:247-249 (clear() implementation)
- evidence: Morfo line 65-72 declares: `{ name: 'commit-reset', semantic: { family: 'commit', verb: 'reset', ... } }`. Provider clear() method (line 247-249) reads: `clear(): void { this.opts.value.current = { start: undefined, end: undefined }; }` with NO `runtime.trigger('commit-reset', ...)` call. Grep for 'commit-reset' in provider finds 0 trigger sites.
- impact: The event exists only to satisfy the schema validator. Consumers expecting `onCommitReset` callback or downstream event handlers observing 'commit-reset' will never fire, violating the 2-of-3 rule (morfo declares it; soma does not fire it).
- proposed-fix: Add `void this.runtime.trigger('commit-reset', { fallbackTarget: ... });` in the clear() method (line 248) BEFORE resetting the value, to match the pattern used in triggerClose() (line 270). Alternatively, remove the event declaration from morfo if it is not semantically needed.
- verify: [confirmed] CONFIRMED as the known picker-family inert-event pattern (MEDIUM cap, matches the prompt's explicit guidance). morfo lines 65-72 declare `{ name: 'commit-reset', semantic: { family: 'commit', verb: 'reset', target: v.partRef('calendar'), sequence: 'post', intent: 'neutral' } }`. Provider clear() at lines 247-249 reads exactly: `clear(): void { this.opts.value.current = { start: undefined, end: undefined }; }` — sets value, no trigger. Grep across the WHOLE component (provider + components/ + internals.ts) confirms the ONLY `runtime.trigger` call is `trigger('close', ...)` at line 270; zero `commit-reset` trigger sites anywhere. So the event is genuinely inert — it exists only to satisfy the schema validator, violating the 2-of-3 rule (morfo declares it; soma never fires it; sema/eidos read data-state not this event). Confidence high. MEDIUM is the correct severity per the INERT-EVENTS rule (MEDIUM at most). The candidate's proposedFix (add trigger in clear()) is a reasonable remedy, though note `data-last-action` is NOT among clear()'s causes since clear keeps the popover open — a commit-reset trigger would need its own non-close wiring.
- fix-status: open
## No-findings dimensions
B: Behavior (keyboard handlers on grid cells are soma-owned; no grid keyset declared in morfo per design), C: DOM-selector (no untrusted querySelector usage found), D: Frontier (soma provider has zero eidos imports; eidos wrapper normally imports soma - correct), E: TSC (no bare z-index, hex, or magic literals in CSS; all tokens reference --color-*, --space-*, --radius-*, --font-size-*, --icon-size-* scales), E-bis: Theming (token naming clean; roles from canonical 9-role set only; size/color/variant from EIDOS_VARIANTS)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
- conformance: Morfo annotation is 'as const satisfies Morfo' (correct); all data-* attributes follow data-{component}-{part} or data-component-{nested} naming; aria-* attrs present for Trigger (aria-expanded, aria-haspopup, aria-controls, aria-label) and Calendar (aria-label); no double-write via syncAttrs observed in provider.
## Tests (F)
- exists: true · env: jsdom
- covers: provider part registration; state flag rendering (open/closed/disabled/readonly/required/invalid); closeOnRangeSelect behavior; range validation (single-day, minDays, maxDays, min/max value)
- untested: commit/cancel/clear actions via footer (not provider-level); dismissed event on Escape/outside click (PopoverProvider owns this); commit-reset event firing; modal vs inline mode end-to-end; range re-anchor (end month rightmost) layout verification; keyboard navigation on month/year grids

@ -0,0 +1,137 @@
# Audit: dialog
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (eidos recipe present though 'eidos' not in morfo scope — systemic SCOPE-DRIFT)
files:
- provider: src/uix/soma/components/dialog/dialog-provider.svelte.ts (present)
- morfo: src/uix/morfo/components/dialog.ts (present)
- recipe: src/uix/eidos/components/dialog/dialog.css + lib/recipes/base.ts §dialog (present)
- demo: web/routes/uix/components/dialog (present, not inspected this pass)
- test: src/uix/soma/components/dialog/dialog-provider.svelte.test.ts (present, jsdom)
- readme: present (not inspected this pass)
## Summary
Dialog is the reference for polymorphic close (book §5.3) and gets most of the hard stuff right: nesting via
`watch`+`untrack` (loop-safe), FocusScope/ScrollLock derived from `modal` (A16), correct two-moments ordering
(emit-before-state on `close` with sequence 'pre'), a thoughtfully tight `data-color` subset (neutral/risk/
threat), and a clean eidos→soma frontier. Two real defects stand out: the Content provider **double-writes
`role`/`aria-roledescription`** against the syncAttrs path (an a11y race for the `alertdialog` variant), and the
**`[data-dialog-trigger]` CSS envelope is stale dead/conflicting chrome** left behind after the trigger migrated
to a composed `<Button>`. Plus the systemic magic z-index and jsdom-only test gap.
Counts: CRITICAL 0 · HIGH 2 · MEDIUM 2 · LOW 2.
## Findings
### HIGH: Content double-writes `role` and `aria-roledescription` (syncAttrs + manual props) — racy role for `alertdialog` <!-- id: dialog-001 -->
- dimension: D, A
- rule: active_architecture §7.7 ("lo que `dom.apply` escribe, Svelte no lo renderiza — una sola autoridad por atributo") + rule 7.8
- location: dialog-provider.svelte.ts:391-397 (Content registered `syncAttrs: true`) vs :460-463 (props override)
- evidence:
```ts
this.runtimePart = this.provider.runtime.part('content', { …, syncAttrs: true });
…
readonly props = $derived.by(() => ({
...this.runtimePart.props,
role: this.provider.opts.variant.current, // 'dialog' | 'alertdialog'
'aria-roledescription': undefined, // nukes the morfo's value
…
}));
```
The morfo declares Content `role: 'dialog'` (morfo:150) and `aria-roledescription` (:211-215); with
`syncAttrs: true` the runtime writes both via `dom.apply`. The provider then ALSO sets `role` (to `variant`)
and `aria-roledescription: undefined` in the Svelte-rendered props. Two authorities per attribute.
- impact: for `variant='alertdialog'`, `dom.apply` writes `role="dialog"` while Svelte writes
`role="alertdialog"` — order-dependent ping-pong; the accessible role of an alert dialog is non-deterministic
(a WAI-ARIA correctness break). `aria-roledescription` is written-then-blanked. The morfo (the cross-layer
source of truth) says `role='dialog'` but the component renders a variant-dependent role it can't express.
- repro: render `<Dialog variant="alertdialog">`, inspect Content `role` across effect ticks.
- proposed-fix: make `role` morfo-expressible (bind a `propRef('variant')`/stateRef so the morfo owns the
variant-dependent role), OR drop `syncAttrs` for Content and supply role/aria via `renderProps()` + a single
override. Don't mix syncAttrs with manual same-attr writes.
- fix-status: fixed (db33f66c)
### HIGH: `[data-dialog-trigger]` CSS envelope is dead/conflicting after the trigger migrated to a composed `<Button>` <!-- id: dialog-002 -->
- dimension: E-bis, D
- rule: THEMING §16 "no solapar" (overlapping/mis-owned styles) + COMPONENT_GUIDE §4 (compose, don't reinvent) + project memory `project_dialog_button_2026-06-20`
- location: dialog.css:22-54 (`[data-dialog-trigger]` full button envelope) vs dialog-trigger.svelte:17-26
(renders `<Button {...props}>` via soma `child`); recipe `dialog.trigger-font-size` (base.ts:727).
- evidence: the eidos `<Dialog.Trigger>` composes the framework `<Button>` through soma's `child` snippet, so
the rendered element carries BOTH `data-button` AND `data-dialog-trigger`. The dialog recipe still defines a
complete competing chrome on `[data-dialog-trigger]` (height/padding/border/background/hover/disabled +
`--dialog-trigger-font-size`). Both single-attribute selectors have equal specificity → source-order decides;
the two recipes fight over the same element. The CSS header comment (:15-21, "the trigger needs its own
envelope here") is stale — it predates the Button migration.
- impact: silent visual incoherence (two sources for one button's chrome), order-dependent; the
`--dialog-trigger-*` tokens are orphan knobs. Exactly the overlap E-bis.5 forbids.
- repro: inspect a `<Dialog.Trigger>` — it has `data-button` + `data-dialog-trigger`; toggle CSS order to see
the chrome change.
- proposed-fix: delete the `[data-dialog-trigger]` envelope + its tokens (Button owns the chrome); keep only a
dialog-specific concern if any (there is none — the Close recipe is the correct model, dialog.css:319-325).
- fix-status: fixed (98954a7c)
### MEDIUM: `overlay-z: '70'` magic z-index literal (content = `calc(overlay-z + 1)`) <!-- id: dialog-003 -->
- dimension: E-bis
- rule: THEMING §35 Bloque C (z-index magic → `--z-index-*` tokens) + feedback "no intentional magic numbers"
- location: lib/recipes/base.ts:731 (`'overlay-z': '70'`); dialog.css:67 + :91 (`calc(var(--dialog-overlay-z) + 1)`).
- evidence: a bare `70` where a `var(--z-index-modal)` token belongs. Same class as `select.content-z: '80'` →
candidate systemic finding (overlay stacking ladder should be one tokenized source, not per-recipe integers).
- impact: the modal/popover/overlay z-ladder can't be coordinated centrally; integers drift apart.
- proposed-fix: map dialog overlay/content to `--z-index-modal`/`--z-index-modal-content` (or the canonical
ladder) and audit all overlay recipes together.
- fix-status: open
### MEDIUM: Tests pin open/close/disabled/modal-derivation; dismissal paths, polymorphic causes, nesting, focus untested; jsdom-only <!-- id: dialog-004 -->
- dimension: F
- rule: dimension-F honesty check
- location: dialog-provider.svelte.test.ts (3 tests)
- evidence: only `dismissWith('dismiss')` of the five causes is exercised (`save`/`fail`/`cancel`/
`dismiss-outside` and their distinct `data-last-action` + semantic.family/intent are not). No test for the
Escape/interact-outside dismissal wiring, `nestedOpenCount` increment/decrement, or focus trap+return (jsdom
can't exercise FocusScope faithfully — `@vitest-environment jsdom` :1).
- impact: the polymorphic-close matrix (the component's headline feature) and the focus/nesting behavior ship
largely unverified; dialog-001's role race wouldn't be caught.
- proposed-fix: parametrize a test over all five `DISMISS_CAUSES` asserting `data-last-action` + emitted
family/intent; add nesting-count tests; add a client/Playwright test for focus trap + return.
- fix-status: open
### LOW: Provider part declares `data-state`/`data-color`/`data-disabled` but is `kind:'virtual'` / `defaultElement:'none'` <!-- id: dialog-005 -->
- dimension: A
- rule: dimension-A (part data must reach an element) + 2-of-3
- location: morfo dialog.ts:96-126 (Provider `kind:'virtual'`, `defaultElement:'none'`, yet declares 3 data attrs);
provider registers it `syncAttrs: true` with optional `ref` (provider renders no DOM).
- evidence: the DialogProvider renders only children (no host element); a virtual part with no element can't
carry `data-state`/`data-color`. The same data also lives on Content (where it's actually consumed by the CSS).
- impact: the Provider-part data declarations are likely unreachable (dead contract) unless the eidos root
wrapper renders an element and applies them — needs confirmation. If dead, they mislead readers/tools.
- proposed-fix: confirm whether any element carries `data-dialog` (eidos root). If not, drop the data from the
virtual Provider part (Content already carries state/color), or give the provider a real host.
- fix-status: open
### LOW: Eidos-only `[data-dialog-header]`/`[data-dialog-footer]` shells not morfo-backed <!-- id: dialog-006 -->
- dimension: A, D
- rule: active_architecture §7.6 (eidos consumes declared data-*) — acknowledged exception
- location: dialog.css:260-290 (commented "Layout shells (eidos-only, no morfo backing)").
- evidence: pure layout containers with no ARIA/behavior, styled by the recipe but absent from the morfo. The
CSS comment is explicit about it.
- impact: minimal (no behavior/ARIA), but it's the same undeclared-part pattern as select-005 — worth a
consistent disposition (eidos-only parts either get `kind:'internal'` morfo entries or a documented allow-list).
- proposed-fix: adopt a project-wide convention for eidos-only decorative/layout parts (see SHARED_EXTRACTION /
systemic UNDECLARED-EIDOS-PARTS).
- fix-status: open
## No-findings dimensions
- **B (behavior):** two-moments ordering correct (`open`/`close` sequence 'pre' → emit precedes state via
`await runtime.trigger`); nesting count uses `watch` + `untrack` (loop-safe, A30-spirit); FocusScope +
ScrollLock derive from `modal` (A16); disabled guards at call-site (rule 10). Clean.
- **C (selectors):** no `querySelector` with interpolated user values in the provider. Clean.
- **D (frontier, severe half):** provider imports only soma layers + morfo — zero eidos imports. (role
double-write filed under D as dialog-001, but it's a runtime/contract issue, not a cross-layer import.)
- **E (TSC):** `data-color` declared per-part in the morfo (provider+content), CSS consumes it on content; no
eager-substitution `:root` derived bug. Colors fully tokenized. Clean.
## Style observations (opinion, non-blocking)
- The `data-color` subset (neutral/risk/threat only, with the rationale "a dialog is not a hierarchy and
irreversible loss should not be a dialog") is an exemplary application of THEMING §4 subset-por-componente —
worth citing as the positive reference for other components.
- The polymorphic-close `DISMISS_CAUSES` map is clean and readable; the only gap is test coverage of its breadth.

@ -0,0 +1,26 @@
# Audit: display
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: morfo OK; no recipe; roles fragmented (content-role subset)
as-const: true · roles clean: false · variants clean: true · has-recipe: false
## Findings
### LOW: em-literal for underline offset; hero sizing is special case — display-002 <!-- id: display-002 -->
- dimension: E-bis: Theming (SIZES)
- location: src/uix/eidos/components/display/display.css:33
- evidence: text-underline-offset: 0.12em (different from text 0.15em)
- impact: Magic em literal; intentional hero differentiation but undocumented as canonical
- proposed-fix: Define --display-underline-offset or document hero-specific em scale
- verify: [downgraded] display.css:33 'text-underline-offset: 0.12em' is an em-relative underline offset for hero copy. The 0.12 vs 0.15 difference is a deliberate optical choice at hero sizes (smaller offset reads better on large type), documented in types.ts:20-27 re: hero tracking. No canonical --*-underline-offset token exists; em is the accepted unit (cf. link recipe base.ts:878). Cosmetic em literal, not drift aga
- fix-status: open
## Theming facts (E-bis)
- magic literals: 0.12em @ src/uix/eidos/components/display/display.css:33 -> canonical display-underline-offset token (or hero scale)
- magic z-index: none
- non-canonical names: none
- roles clean: false · variants clean: true

@ -0,0 +1,65 @@
# Audit: drag-drop
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: src/uix/soma/components/drag-drop/drag-drop-provider.svelte.ts
cleanup-audit (A6/A35/A36): TIMERS/LISTENERS:
1. setupPointerDrag() — lines 261-294:
- this.soma.dom.listen(win, 'pointermove', onPointerMove) → cleanupPointerMove [DISPOSED]
- this.soma.dom.listen(win, 'pointerup', onPointerUp) → cleanupPointerUp [DISPOSED]
- this.soma.dom.listen(win, 'pointercancel', onPoint
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 0.
## Findings
### MEDIUM: SYS-1 SCOPE-DRIFT — drag-drop-001 <!-- id: drag-drop-001 -->
- dimension: D
- rule: SYS-1 SCOPE-DRIFT
- location: src/uix/morfo/components/drag-drop.ts:7, src/uix/eidos/components/drag-drop/
- evidence: Morfo declares scope: ['soma', 'sema'] (line 7) but eidos component directory exists at src/uix/eidos/components/drag-drop/ with drag-drop.svelte, drag-drop.css, and sub-components (draggable, droppable, preview). The recipe dir exists but morfo scope omits 'eidos'.
- impact: Framework scope validation may flag this as inconsistent; eidos layer is present but not declared in morfo scope.
- proposed-fix: Update morfo scope declaration to scope: ['soma', 'sema', 'eidos'] to match the actual component hierarchy.
- verify: [confirmed] CONFIRMED. morfo declares scope: ['soma', 'sema'] (drag-drop.ts:7) yet a full eidos implementation exists: recipe entry 'drag-drop': { 'preview-z': '99' } (recipes/base.ts:4259-4261), drag-drop.css, and 3 svelte wrappers (draggable/droppable/preview). The Morfo.scope doctrine (morfo/types.ts:798) reads 'Layers that implement this component' and 'eidos' is a valid Layer (schema.ts:49: union(literal('soma'), literal('sema'), literal('eidos'))). Omitting 'eidos' is genuine scope-drift = SYS-1 baseline MEDIUM. Note: many siblings (calendar/carousel/color-field/combobox/command) share the same drift, consistent with SYS-1 being systemic.
- fix-status: fixed (212624e0)
### MEDIUM: SYS-2 MAGIC Z-INDEX — drag-drop-002 <!-- id: drag-drop-002 -->
- dimension: B
- rule: SYS-2 MAGIC Z-INDEX
- location: src/uix/soma/components/drag-drop/drag-drop-provider.svelte.ts:656, 664
- evidence: DragPreviewProvider.style $derived.by inlines 'z-index': '9999' (lines 656, 664) as a hardcoded literal in the style object. The eidos CSS correctly uses var(--drag-drop-preview-z) (drag-drop.css:97) which resolves to 99 per generated base.css:3400, but the inline style hardcodes 9999 instead.
- impact: z-index value is 9999 (extremely high, above all canonical layers) instead of 99 (--drag-drop-preview-z). This violates the canonical --z-index-* scale and may unexpectedly layer above modals (--z-index-modal: 700) and toast (--z-index-toast: 900).
- proposed-fix: Replace hardcoded '9999' with 'var(--drag-drop-preview-z)' to consume the canonical token, e.g., 'z-index': 'var(--drag-drop-preview-z)'.
- verify: [downgraded] DIVERGENCE CONFIRMED, severity DOWNGRADED HIGH->MEDIUM. DragPreviewProvider.style $derived.by hardcodes 'z-index': '9999' on both branches (drag-drop-provider.svelte.ts:656 and :664). The CSS rule [data-drag-drop-preview] uses z-index: var(--drag-drop-preview-z) (drag-drop.css:97) which resolves to 99 (generated/base.css:3400). Because the provider sets z-index inline via props.style, inline precedence wins, so effective z is 9999 and the recipe token is inert. 9999 is off the canonical scale entirely (--z-index-base 0 .. --z-index-toast 900; generated/base.css:304-311) and exceeds modal (700) and toast (900). This is a magic-literal that overrides its own dedicated token (SYS-2 magic-z), NOT a syncAttrs double-write (no data-attr involved). Per rubric magic-literal == MEDIUM and SYS-2 is baselined MEDIUM, so HIGH is unwarranted.
- fix-status: open
### MEDIUM: Test coverage for high-risk paths — drag-drop-003 <!-- id: drag-drop-003 -->
- dimension: F
- rule: Test coverage for high-risk paths
- location: src/uix/soma/components/drag-drop/drag-drop-provider.svelte.test.ts:126-306
- evidence: Test file contains 2 test cases: (1) keyboard drag start/navigation/commit, (2) drag prevention, accept filters, cancel, pointer preview. Missing explicit coverage for: pointer drag initiation and movement (setupPointerDrag), pending pointer-down state promotion (DraggableProvider.onpointerdown moveBuffer threshold), pointer listener cleanup on premature cancellation, and live-region disposal on component unmount.
- impact: Pointer drag logic and pending state lifecycle are not directly tested; test env is jsdom-only. A/B testing (live-region dual-region toggle) is not verified.
- proposed-fix: Add test case for pointer drag from pointerdown to pointermove threshold to active drag promotion; verify cleanup on pointercancel; add test for announcer timer cancellation on provider cleanup.
- verify: [confirmed] CONFIRMED. Test file has exactly 2 it() cases (drag-drop-provider.svelte.test.ts:132 keyboard drag; :213 prevention/accept/cancel/preview). Both drive the flow by calling provider.startDrag(...) directly (e.g. lines 243-250, 256-264 with keyboard:false) rather than dispatching real pointerdown -> pointermove -> moveBuffer promotion (DraggableProvider.onpointerdown at :417-457, threshold Math.hypot < moveBuffer at :432). The setupPointerDrag window-listener flow (:261-294) and teardown/listener-cleanup (:245-257) are not exercised via real events. The preview-style assertion (:267-275 objectContaining) does NOT assert z-index, so the 9999 hardcode is also untested. jsdom-only env = SYS-3. MEDIUM stands.
- fix-status: open
## No-findings dimensions
A, C
## Theming facts (E-bis)
- magic z-index: src/uix/soma/components/drag-drop/drag-drop-provider.svelte.ts:656,664 — hardcoded '9999' instead of var(--drag-drop-preview-z)
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: keyboard drag start/activate via Space/Enter; keyboard navigation (ArrowDown/ArrowRight, ArrowUp/ArrowLeft, Tab/Shift+Tab); keyboard drop (Enter/Space on overTarget); keyboard cancel (Escape); onDragStart preventDefault block; accept filter evaluation; pointer drag cancel on invalid drop; pointer preview style positioning (fixed layout, offset from pointer)
- untested: pointer drag initiation (onpointerdown → pending state); pointer drag promotion (moveBuffer threshold exceeded); pointer listener cleanup on premature cancel; keyboard listener cleanup/disposal; announcer timer cleanup on provider dispose; live-region dual-region A/B toggle announce pattern
## Style observations (non-blocking)
- Draggable cursor transitions from grab → grabbing on :active, correct interaction feedback
- Droppable uses inset box-shadow rings for accept/dragover states, consistent with design tokens
- Preview uses --opacity-ghost (opacity-disabled), --space-* spacing, --radius-md, and role aliases correctly
- Color cascade via data-color attribute in eidos component correctly routes to --_drag-drop-accent-soft and --_drag-drop-accent-solid variables

@ -0,0 +1,89 @@
# Audit: drawer
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/soma/components/drawer/drawer-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 5 · LOW 0.
systemic hits: SYS-1: scope-drift (morfo scope missing 'eidos' despite recipe directory existing); SYS-2: magic z-index (bare integers in recipe: overlay-z='72', inline-z='64'), magic opacity/dimensions (overlay-opacity='62%', handle-length='36px', handle-thickness='4px', content-ring-dragging-offset='2px'), magic opacity in color-mix (32%); SYS-3: jsdom-only test with untested high-risk paths (keyboard, drag, focus, gesture cleanup); SYS-5: double-write (provider explicitly sets role and aria-modal which morfo also declares).
## Findings
### MEDIUM: SYS-1 — drawer-001 <!-- id: drawer-001 -->
- dimension: A
- rule: SYS-1
- location: src/uix/morfo/components/drawer.ts:7
- evidence: scope: ['soma', 'sema'] but eidos directory exists at src/uix/eidos/components/drawer/
- impact: Scope does not declare eidos as a layer despite recipe existing. Signals incomplete contract annotation.
- repro: Read src/uix/morfo/components/drawer.ts line 7 and ls src/uix/eidos/components/drawer/
- proposed-fix: Update morfo scope to include 'eidos': scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] morfo line 7: `scope: ['soma', 'sema'],` — 'eidos' absent. Eidos recipe block exists (base.ts:892 `drawer: {` with ~50 tokens) plus a components/drawer/ dir. Systemic SYS-1: sibling dialog.ts:18 has the identical `scope: ['soma', 'sema']` with its own eidos recipe (base.ts:726). HIGH is correct per SYS-1 contract-drift; the morfo scope under-declares a real layer.
- fix-status: open
### MEDIUM: SYS-2 — drawer-002 <!-- id: drawer-002 -->
- dimension: E-bis
- rule: SYS-2
- location: src/uix/eidos/lib/recipes/base.ts:896,897
- evidence: 'overlay-z': '72', 'inline-z': '64' — bare integers without --z-index-* token references
- impact: Magic z-index values not parameterized via canonical token system. Inconsistent with project theming rules.
- repro: grep "'overlay-z'\|'inline-z'" src/uix/eidos/lib/recipes/base.ts
- proposed-fix: Create --z-index-overlay and --z-index-inline tokens in the canonical scale and reference via var() in recipe.
- verify: [confirmed] base.ts:896 `'overlay-z': '72',` and 897 `'inline-z': '64',` are bare integers. A canonical `--z-index-*` scale exists and is consumed elsewhere (base.ts:4357 `z: 'var(--z-index-sticky)'`; comment at 4250-4252 references 'the global --z-index-* scale'). These two values bypass it. Genuine SYS-2 magic-z. MEDIUM appropriate.
- fix-status: open
### MEDIUM: SYS-2 — drawer-003 <!-- id: drawer-003 -->
- dimension: E-bis
- rule: SYS-2
- location: src/uix/eidos/lib/recipes/base.ts:895
- evidence: 'overlay-opacity': '62%' — bare opacity literal without --opacity-* token
- impact: Magic opacity value not referenced via canonical opacity token scale.
- repro: grep "'overlay-opacity'" src/uix/eidos/lib/recipes/base.ts
- proposed-fix: Create --opacity-overlay token or use existing project opacity scale, reference via var().
- verify: [confirmed] base.ts:895 `'overlay-opacity': '62%',` — bare percentage. An `--opacity-*` scale exists and is consumed widely (e.g. base.ts:890 `'disabled-opacity': 'var(--opacity-disabled)'`). The 62% literal does not reference it. Confirmed SYS-2 magic-opacity; note it is replicated verbatim in dialog (base.ts:729) — systemic, not drawer-specific. MEDIUM appropriate.
- fix-status: open
### MEDIUM: SYS-5 — drawer-007 <!-- id: drawer-007 -->
- dimension: A
- rule: SYS-5
- location: src/uix/soma/components/drawer/drawer-provider.svelte.ts:1044-1045
- evidence: Provider props explicitly set role: 'dialog' and 'aria-modal': this.provider.isOverlay ? true : undefined, while morfo also declares role:'dialog' and aria-modal with prop-truthy condition
- impact: Double-write of role and aria-modal. If morfo condition diverges from provider logic, the two values could conflict. Violates 'una sola autoridad por atributo'.
- repro: Read src/uix/soma/components/drawer/drawer-provider.svelte.ts lines 1044-1045 and compare with morfo aria declarations at lines 184, 214-217
- proposed-fix: Remove explicit role and aria-modal from provider props — let the runtime apply them from morfo. If dynamic aria-modal is needed, ensure logic matches the morfo's prop-truthy condition exactly.
- verify: [downgraded] Real double-write confirmed but severity overstated. Content part is `syncAttrs: true` (provider line 517), so the runtime's syncPartAttrs effect (runtime.svelte.ts:456-465) writes morfo's static `role: 'dialog'` (morfo:184) + dynamic `aria-modal` gated by `prop-truthy modal` (morfo:214-217). Provider props ALSO set them (provider:1044 `role: 'dialog' as const`, 1045 `'aria-modal': this.provider.isOverlay ? true : undefined`). So both attrs are written by two authorities — a true SYS-5 'una sola autoridad' violation. BUT the rubric reserves HIGH for SYS-5 'when the two values can diverge'. They cannot: `role` is the same literal both sides, and `aria-modal` = `isOverlay` (provider:190 `variant==='overlay'`) is identical to morfo's `modal` source (provider:254 `() => opts.variant.current === 'overlay'`). Non-divergent double-write → MEDIUM, not HIGH. Same pattern repeats for Title (provider:1208 `role:'heading'` + 1209 `aria-level` vs morfo:279/282) and Trigger (provider:443-446 type/aria-haspopup/aria-expanded vs morfo:154/162-164) — systemic across this provider.
- fix-status: open
### MEDIUM: SYS-3 — drawer-008 <!-- id: drawer-008 -->
- dimension: F
- rule: SYS-3
- location: src/uix/soma/components/drawer/drawer-provider.svelte.test.ts:1
- evidence: @vitest-environment jsdom — tests only cover direction resolution, dismissal, and variant modes. No tests for keyboard handling (Tab/Escape), focus return to trigger, drag gestures, gesture cleanup, or focus trap behavior.
- impact: Interaction-heavy component (keyboard nav, drag, focus management) tested only on low-risk paths with jsdom. Lacks client/Playwright tests for gesture interaction and focus side-effects.
- repro: Read drawer-provider.svelte.test.ts; note tests use jsdom and @vitest-environment jsdom decorator
- proposed-fix: Add a client-environment (Playwright) test suite covering: Tab/Shift+Tab keyboard navigation, Escape dismissal, drag-to-dismiss interaction, focus return on close, and gesture cleanup on unmount.
- verify: [confirmed] Test file line 1 `// @vitest-environment jsdom`. Only 4 tests (describe at :96, it at :102 direction-resolution, :115 dismissal+snap-clear, :155 persistent-open, :174 inline-escape). No coverage of: Tab/Shift+Tab (morfo keyboard focus-next/focus-prev at morfo:234-235), focus trap/return-to-trigger (morfo focus.trap/return at :109-114; provider handleClose:359 manual focus return), drag gesture (provider gesture/axial at :682/:883), or two-moments emit ordering (close is sequence:'pre', morfo:52). Interaction-heavy + jsdom-only + no client test = SYS-3. MEDIUM appropriate.
- fix-status: open
## No-findings dimensions
B, C, D, G
## Theming facts (E-bis)
- magic z-index: content-z: calc(var(--drawer-overlay-z) + 1) — relies on recipe's bare '72' integer
- magic literals: overlay-z '72' should be --z-index-overlay var | inline-z '64' should be --z-index-inline var | overlay-opacity '62%' should be --opacity-overlay var | handle-length '36px' should reference --size-* or --space-* token | handle-thickness '4px' should reference --space-* token | content-ring-dragging-offset '2px' should reference --space-* token | content-ring-dragging-color opacity 32% should be extracted to --opacity-dragging-ring token
- undeclared parts: none
- roles clean: true · variants clean: true
- conformance: roles/sizes: 'sm'|'md'|'lg'|'full' per component subset (correct). variants: 'overlay'|'inline'|'persistent' (component-specific, documented). naming: --drawer-{slot} public, --_drawer-* private (observed). no undeclared parts in recipe (header/footer are intentional eidos-only layout parts).
## Tests (F)
- exists: true · env: jsdom (@vitest-environment jsdom)
- covers: logical direction resolution (start/end → physical sides per dir); polymorphic close with data-last-action imperative prewrite; persistent variant ignores open state; inline variant skips focus trap and scroll lock
- untested: keyboard navigation (Tab, Shift+Tab, Escape) from morfo's keyboard action routes; focus trap and focus return to trigger in overlay/modal mode; drag-to-dismiss gesture and progress signals; resize-on-drag with snap points; snap-point sequential cycling; gesture cleanup on unmount (cancellation of requestAnimationFrame, event listeners); nesting behavior (data-nested, nestedOpenCount management); drag-start/end/progress semantic emissions; scroll-lock guard (scrollLockTimeout behavior); handle-only gesture restriction; pointer event composition (gesture + axial mutual exclusivity); overlay opacity modulation based on activeSnapPoint
## Style observations (non-blocking)
- Morfo declaration is well-structured with clear event semantics (polymorphic close, drag handle family, resize handle family). Events properly declare sequence (pre/coincident/post).
- Provider architecture cleanly separates concerns: DrawerProvider (root state), DrawerTriggerProvider, DrawerContentProvider (gesture+focus), DrawerOverlayProvider, DrawerHandleProvider, DrawerTitleProvider, DrawerDescriptionProvider, DrawerCloseProvider. Each uses direct constructor-level child→parent registration (A30-compliant).
- Gesture layer properly throttles drag-progress to rAF via flushDragProgress and rAF request/cancel lifecycle with proper cleanup in $effect.
- Focus return correctly implemented: non-modal returns focus to triggerNode (lines 359, 366); modal relies on FocusScope layer.
- Dismiss layer correctly gated: interactOutside always 'ignore' (drawer is not a popover), Escape gated by modal flag (non-modal manually handles via onkeydown in content).
- CSS uses intentional eidos-only layout parts (data-drawer-header, data-drawer-footer) not declared in morfo — correct separation of layout concerns from behavioral contract.

@ -0,0 +1,120 @@
# Audit: dropdown-menu
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema', 'eidos'] (eidos IS declared — no scope-drift, unlike select/dialog/popover/combobox)
files:
- provider: src/uix/soma/components/dropdown-menu/dropdown-menu-provider.svelte.ts (present)
- morfo: src/uix/morfo/components/dropdown-menu.ts (present)
- recipe: src/uix/eidos/components/dropdown-menu/dropdown-menu.css + lib/recipes/base.ts §dropdown-menu (present)
- demo: web/routes/uix/components/dropdown-menu (present, not inspected this pass)
- test: src/uix/soma/components/dropdown-menu/dropdown-menu-provider.svelte.test.ts (present, jsdom, 6 tests)
- readme: present (not inspected this pass)
## Summary
The most disciplined provider of Batch 1. Consistent roving DOM focus (no virtual/real mix — the morfo declares
NO `aria-activedescendant`, the provider uses real `dom.focus`, A17-correct), uniform `renderProps()` usage
(no double-writes), a clean eidos→soma frontier, correct `data-disabled`/`accessibleWhenDisabled` handling, and
fully self-managed cleanup — including `SafePolygon`, which registers its document listeners inside an internal
`$effect` via `dom.listen` (verified: no A6 leak). It is the only Batch-1 overlay that correctly declares
`eidos` in its morfo scope. No HIGH findings. The remaining issues are the systemic directional-nav duplication,
magic theming literals, a checkbox/radio toggle that emits no semantic event, and a navigation-untested gap.
Counts: CRITICAL 0 · HIGH 0 · MEDIUM 4 · LOW 1.
## Findings
### MEDIUM: Directional-nav index math duplicated (Content + SubContent, and across Select/Combobox) — incl. the loop off-by <!-- id: dropdown-menu-001 -->
- dimension: G, B
- rule: dimension-G redundancy + the loop-boundary edge case
- location: dropdown-menu-provider.svelte.ts:384-422 (Content.onkeydown) and :1165-1209 (SubContent.onkeydown)
- evidence: both handlers re-implement the identical next/prev/Home/End + typeahead index math
(`(i+1)%len` / `Math.min` / `Math.max` / `(i-1+len)%len`). It's also the same math as Select content route
(select-003) and Combobox content route. The `currentIndex === -1` + prev + loop path computes
`(-1-1+n)%n = n-2` (lands second-to-last, not last) — the same off-by as select-003; reachable when keydown
fires with focus on the container rather than an item.
- impact: four copies of one algorithm (one bug fixed in one place misses the others); the loop off-by is a
latent edge-case bug.
- proposed-fix: extract a pure `nextIndex(curr, key, len, {loop, dir, orientation})` helper, unit-test the
boundaries once, and consume it in all menu/select/combobox keyboard routes. (See SHARED_EXTRACTION
DIRECTIONAL-NAV.)
- fix-status: open
### MEDIUM: Checkbox/Radio item activation mutates state but emits no semantic event (unlike plain Item) <!-- id: dropdown-menu-002 -->
- dimension: A, B
- rule: M-3.7 (every state-mutating keyboard action needs a semantic event) + the morfo only declares
`commit-select` on `item`
- location: provider `MenuItemProvider.onclick/onkeydown` fire `runtime.trigger('commit-select', …)` (:510, :521),
but `MenuCheckboxItemProvider.activate` (:735-745) and `MenuRadioItemProvider.activate` (:860-865) mutate
`checked`/group value and call only `onSelect()` + `handleClose()` — no `runtime.trigger`.
- evidence: toggling a checkbox-item or selecting a radio-item changes state with no perceptual emit; the morfo
declares no event for `checkbox-item`/`radio-item`.
- impact: with `closeOnSelect: false` (the common case for checkbox menus) there is zero sema feedback on
toggle, while a plain Item select gets `commit.select + affirm`. Inconsistent perceptual contract; a
checkbox toggle is canonically `commit.toggle`.
- repro: open a menu with a `closeOnSelect={false}` CheckboxItem, toggle it — no `data-event` is stamped.
- proposed-fix: declare `commit-toggle` (checkbox) / `commit-select` (radio) events on those parts in the
morfo and fire them from `activate()`, or document the silence as deliberate in the README.
- fix-status: open
### MEDIUM: Magic theming literals — `content-z: '80'`, `item-disabled-opacity: '0.55'`, `item-height: '2rem'` <!-- id: dropdown-menu-003 -->
- dimension: E-bis
- rule: THEMING §35 (z-index/opacity magic → tokens) + §5 (size from `--list-*`/`--control-height-*`/`--space-*`) + §4
- location: lib/recipes/base.ts:4279 (`'content-z': '80'`), :4292 (`'item-disabled-opacity': '0.55'`),
:4286 (`'item-height': '2rem'`)
- evidence: `content-z: '80'` (5th overlay with a hardcoded content-z — systemic z-ladder). `0.55` should be
`var(--opacity-disabled)` (the canonical disabled opacity Dialog uses). `2rem` item-height is a raw rem
where the list-surface size layer (`--list-*`, per project memory 2026-06-21) should drive it.
- impact: opacity/size/z values that won't track theme/density/scale changes; the disabled opacity diverges
from the canonical `--opacity-disabled` used elsewhere.
- proposed-fix: `content-z` → canonical overlay z token; `item-disabled-opacity` → `var(--opacity-disabled)`;
confirm `item-height` should bridge `--list-item-height-*`.
- fix-status: open
### MEDIUM: Arrow-key NAVIGATION index math is untested (activation/groups/submenu ARE tested); jsdom-only <!-- id: dropdown-menu-004 -->
- dimension: F
- rule: dimension-F honesty check
- location: dropdown-menu-provider.svelte.test.ts (6 tests)
- evidence: the suite is the strongest of Batch 1 — it covers trigger toggle, item scoping + disabled policy,
click/keyboard activation + close, checkbox/radio groups, submenu open (hover/click/ArrowRight), and
group/separator a11y. But no test calls `Content.onkeydown`/`SubContent.onkeydown` with ArrowDown/ArrowUp/
Home/End to exercise the navigation index math (dropdown-menu-001) or the loop boundary. `@vitest-environment
jsdom` (:1) — `dom.focus` moves aren't faithful.
- impact: the duplicated nav math + loop off-by ship unverified.
- proposed-fix: add Content/SubContent navigation tests asserting the focused index after each arrow key incl.
the loop wrap; add a client/Playwright test for real focus movement.
- fix-status: open
### LOW: `open`/`close`/`commit-select` set state imperatively alongside a non-awaited `void trigger` (sequence not runtime-sequenced) <!-- id: dropdown-menu-005 -->
- dimension: B
- rule: two-moments doctrine + the Dialog pattern (events wired into the runtime so `trigger` sequences emit→handler)
- location: runtime created with `{}` (no events map, :93); `handleOpen`/`handleClose` do
`void this.runtime.trigger(...)` then set `open` synchronously (:116-140).
- evidence: unlike Dialog (which wires `events: { open, close }` so `runtime.trigger` awaits emit before the
handler), dropdown-menu sets `open` imperatively next to a fire-and-forget emit, so the morfo's
`sequence: 'pre'` is not enforced by the runtime — it relies on Presence keeping content mounted during exit.
- impact: works in practice (Presence + the 'pre' content still exists), and the non-await is likely a
deliberate snappiness choice for a high-frequency surface; but it diverges from the canonical sequencing and
makes `sequence: 'pre'` advisory here.
- proposed-fix: either wire the open/close handlers into the runtime `events` map (Dialog parity) or document
that menus intentionally don't await the emit. Decide once and apply to all menu-family components.
- fix-status: open
## No-findings dimensions
- **A17 (focus strategy):** consistent roving DOM focus — morfo declares no `aria-activedescendant`, provider
uses real `dom.focus`. No mixing (contrast Combobox). Clean.
- **C (selectors):** `getItems` composes attribute selectors with `.closest()` scoping (A10), no user-value
interpolation. Clean.
- **D (frontier, severe half):** provider imports only soma layers + morfo — zero eidos imports.
- **A6 (cleanup):** typeahead `destroy` (:111), SubTrigger open timer (:1007), and `SafePolygon` (self-manages
via an internal `$effect` + `dom.listen` disposers, verified safe-polygon.ts:209-215) all clean up. No leak.
- **A (renderProps discipline):** every part spreads `renderProps()` and adds only soma-owned extras
(data-state/tabindex/handlers) — no syncAttrs+manual double-write (contrast Dialog/Popover).
- **A30:** SubTrigger ref mirror via `onRefChange`, not `$effect` (:988).
- **SCOPE:** morfo scope correctly includes `eidos` — the only Batch-1 overlay without the scope-drift.
## Style observations (opinion, non-blocking)
- Use dropdown-menu as the positive reference for: roving-focus consistency, uniform `renderProps()` (no
double-write), self-managed layer cleanup, and correct `eidos`-in-scope declaration. Its main debts are
shared, systemic ones (nav duplication, magic literals), not local mistakes.
- The checkbox/radio silent-toggle (dropdown-menu-002) is the one genuinely local contract gap worth an
explicit decision.

@ -0,0 +1,35 @@
# Audit: editable
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/editable/editable-provider.svelte.ts
field-family (A13/A24-26/A30): Not a field-family component; does not participate in Field composition. Optional hidden-input for form submission is correctly declared and configured with name, value, required, disabled propagation.
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 SCOPE-DRIFT — editable-001 <!-- id: editable-001 -->
- dimension: Morfo
- rule: SYS-1 SCOPE-DRIFT
- location: src/uix/morfo/components/editable.ts:7
- evidence: Morfo declares scope: ['soma', 'sema'] but eidos recipe directory exists at src/uix/eidos/components/editable/ with recipe tokens in src/uix/eidos/lib/recipes/base.ts:2724-2815
- impact: The morfo scope should include 'eidos' to signal that the component has visual styling rules. Missing 'eidos' in scope creates confusion about the component's boundaries.
- proposed-fix: Change morfo line 7 from scope: ['soma', 'sema'] to scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] Confirmed. editable.ts:7 declares `scope: ['soma', 'sema']` — verified by reading the file. The eidos layer materially implements this component: 13 files under src/uix/eidos/components/editable/ (incl. editable.css) plus a recipe block at src/uix/eidos/lib/recipes/base.ts:2724 (`editable: {`). The Layer field is documented in types.ts:799 as 'Layers that implement this component', so omitting 'eidos' while shipping a full eidos implementation is genuine contract drift — matches the SYS-1 scope-drift baseline (MEDIUM). Severity MEDIUM is correct: this is a contract/documentation inconsistency, not a behavior bug (the eidos CSS still loads and works regardless of the morfo scope array). Confirmed systemic, not editable-specific: combobox, color-picker, date-field, calendar all have eidos CSS files yet their morfo scope is `['soma', 'sema']` too (e.g. calendar.ts:7, combobox.ts:7, color-picker.ts:7). Proposed fix (add 'eidos' to the array) is correct.
- fix-status: fixed (212624e0)
## No-findings dimensions
Contract (2-of-3 parts rule), Contract (morfo as const satisfies), Contract (data-/aria- naming), Contract (declared events fired), Behavior (A30 id registration), Behavior (A6 frame disposal), Behavior (A31 O(N^2)), Behavior (keyboard APG match), Behavior (two-moments commit ordering), DOM-selector (CSS.escape), Frontier (soma->eidos import), Theming (9-role limit), Theming (magic z-index), Theming (magic opacity), Theming (magic literals px/rem/em/%)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: startEdit with focus and state projection; preview activation (click/dblclick/focus modes); keyboard commit/cancel (Enter/Escape keys); blur control guards (focus retention on trigger buttons); readonly and disabled state enforcement; trigger label exposure and disabled states; value commit and revert semantics
- untested: hidden-input form submission with name/required/disabled propagation; maxLength enforcement on input; autoResize field-sizing behavior; blur submit vs cancel mode differentiation; selectOnFocus text selection on edit start

@ -0,0 +1,19 @@
# Audit: fab
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (composite-and-service), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract: 'as const satisfies Morfo' ✓ | Scope: eidos ✓ | Roles: none (composes Button) ✓ | Variants: data-fab-size, data-placement, data-extended ✓ | All sizing via --fab-* recipe tokens ✓ | Em and env fallbacks OK ✓
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 1em @ src/uix/eidos/components/fab/fab.css:103 -> icon canonical scaling (acceptable) | 0px (env fallback) @ src/uix/eidos/components/fab/fab.css:121-134 -> safe-area contract (acceptable)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,32 @@
# Audit: feed
audit-version: 1
audited-at: 2026-06-26
scope:
method: adversarially-verified workflow (analyze → refute); HIGH lead-verified by direct read of the cited code. B5 ground-truth: the A31 O(N²) isSelected/isExpanded (.includes from a per-item $derived) is confirmed across listbox/grid-list/tree-view/tree-grid/tag-group (SYS-7); rovingTargetEl is correctly LIFTED everywhere (not A31); virtual-* use SvelteMap (A33-clean).
provider: src/uix/soma/components/feed/feed-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0.
## Findings
_No findings survived verification (clean component)._
## No-findings dimensions
A, C, D, E, E-bis, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: feed.css:147 outline-offset: 2px (should use --focus-ring-offset token or define local token) | feed.css:200-201 inline-size: 0.75rem; block-size: 0.75rem; (should use design token like --space-3 or define --_feed-spinner-size) | feed.css:206 animation: feed-spin 0.7s linear infinite (0.7s should use --duration-* token) | feed.css:217 animation-duration: 4s (4s should use --duration-* token, e.g., --duration-slowest)
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom (@vitest-environment jsdom)
- covers: keyboard navigation (PageUp/PageDown/Ctrl+Home/Ctrl+End); article position calculation (posinset/setsize); nested thread level inheritance; sentinel IntersectionObserver integration; fallback to feed.onLoadMore when sentinel doesn't have onIntersect
- untested: Performance under high item count (30+ articles — A31 hazard not tested); Dynamic article addition/removal with auto-reposition; RTL direction handling (CSS has RTL rules but no test coverage); Multiple feeds on same page (context isolation)
## Style observations (non-blocking)
- feed.css:42 data-block attribute selector: uses `data-block` instead of `data-block=''` on root for boolean flags — matches component pattern (feed.svelte:42 sets data-block={block ? '' : undefined})
- feed.css:147 outline-offset: 2px differs from --focus-ring-offset (1px). Most components use --focus-ring-offset token; feed variant may be intentional for visual clarity but creates drift
- feed.css:153 fallback font-weight: 600 in var() — fallback should not be used if token is guaranteed to exist; minor cosmetic issue
- feed.css colors (lines 71-98) cascade via --_feed-accent / --_feed-accent-soft using 9 roles (primary, secondary, affirm, risk, threat, fulfill, loss, neutral). Follows project theming conventions correctly

@ -0,0 +1,49 @@
# Audit: field
audit-version: 1
audited-at: 2026-06-26
scope:
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\field\field-provider.svelte.ts
field-family (A13/A24-26/A30): {
"A13": "Not applicable: field is the generic wrapper, not a form-participating field (number-field, tags-input, pin-input, color-field, css-field are form-fields). No hidden <input> rendered by field itself.",
"A24": "Not applicable: field does not hold a value; child inputs (FieldInputProvider) handle value state. A24 'readonly-without-value warning' applies to child-level components.",
"
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 0.
## Findings
### MEDIUM: SYS-1 scope-drift: morfo 'scope' declares ['soma'] but eidos directory exists at src/uix/e — field-002 <!-- id: field-002 -->
- dimension: A
- rule: SYS-1 scope-drift: morfo 'scope' declares ['soma'] but eidos directory exists at src/uix/eidos/components/field
- location: src/uix/morfo/components/field.ts:7 vs src/uix/eidos/components/field/
- evidence: fieldMorfo.scope = ['soma'], yet a full eidos/ recipe directory exists with field.css, field.svelte, and 10+ component files.
- impact: MEDIUM (expected pattern per audit notes: 'No eidos scope-drift expected? (it has an eidos dir)'). Eidos wrapper components import soma correctly (all 11 .svelte files in eidos/field show 'import * as Field from $soma/components/field'), so the directory exists but is subordinate to soma — this is the normal composition pattern (eidos wraps soma visually, doesn't add a new layer to scope). No contract or behavior violation.
- proposed-fix: This is systemic (SYS-1). Document as expected: field is a soma component with an eidos visual wrapper layer; scope correctly declares soma as the source of truth.
- verify: [downgraded] Confirmed shape: fieldMorfo.scope = ['soma'] (field.ts:7) and a full eidos/components/field/ recipe dir exists. But SYS-1 scope-drift is the pattern where a soma component has an eidos visual layer not reflected in 'scope'; here every eidos wrapper composes soma (grep for 'import.*eidos' in field-provider returned NO matches — frontier clean). This is the normal eidos-wraps-soma composition, not a contract or behavior violation. Candidate's own impact text says 'expected pattern' / 'no contract or behavior violation'. Downgrade MEDIUM->LOW; record as systemic-expected, not a Field-specific defect.
- fix-status: fixed (212624e0)
### MEDIUM: Tests: {kebab}-provider.svelte.test.ts must exist; coverage high-risk paths: segment commi — field-007 <!-- id: field-007 -->
- dimension: F
- rule: Tests: {kebab}-provider.svelte.test.ts must exist; coverage high-risk paths: segment commit/keyboard, hidden-input, Field inheritance, readonly-without-value warning, validation.
- location: src/uix/soma/components/field/field-provider.svelte.test.ts (exists)
- evidence: Test file exists and covers: (1) wires field parts, ARIA props, runtime attrs (lines 85-183); (2) updates input value unless disabled/readonly (185-219); (3) integrates with Form state and touched registry (221-256). Paths covered: ARIA wiring (aria-labelledby, aria-invalid, aria-describedby, aria-required, role=alert, aria-live), disabled/readonly guards, Form integration.
- impact: Field-provider tests cover core A30 wiring and Form integration. NO COVERAGE for A24 'readonly-without-value warning' (field is generic, doesn't hold value itself — A24 applies to value-holding fields like number-field). Test count is lean (3 it() blocks) but focused on the unique field responsibilities.
- proposed-fix: Optional: add a test verifying that child inputs read disabled/readonly/required/invalid from the parent provider context. Current tests verify runtime wiring but not snippet props propagation. Consider: 'reads disabled/readonly/required/invalid from parent' to guard inheritance.
- verify: [uncertain] Test exists (field-provider.svelte.test.ts, 256 lines, jsdom env, 3 it() blocks). Coverage is genuinely good for the field's unique responsibilities: ARIA wiring incl aria-labelledby/aria-invalid/aria-describedby join (141-179), disabled/readonly input guards (185-219), Form integration + touched registry + unregister-on-cleanup (221-255). The real, narrow gap the candidate names is legitimate: no explicit assertion that descendant inputs READ disabled/readonly/required/invalid from the parent context (snippetProps inheritance is computed but only the FieldInputProvider's own props are asserted, e.g. 'required: true' at 146 comes from the input part). Holds at MEDIUM as a low-risk test-coverage gap, not a confirmed bug. F: provider test PRESENT, env jsdom (SYS-3 noted).
- fix-status: open
## No-findings dimensions
A (contract: morfo scope/naming/parts), C (DOM selector), G (redundancy)
## 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?): Clean: label font-size is calc(control-font - 2px) per base.ts recipe. Label renders at ONE step below input per CSS line 154. Scales correctly with size prop (xs/sm/md/lg/xl).
## Tests (F)
- exists: true · env: jsdom
- covers: ARIA wiring (aria-labelledby, aria-invalid, aria-describedby, aria-required, role, aria-live); input value update with disabled/readonly guards; Form Provider integration (registerField, unregisterField, getFieldState, touched registry)
- untested: Child inheritance of disabled/readonly/required/invalid from parent context (snippet props verified, but context propagation to child .svelte components not exercised); Cleanup of id registration on unmount (tested via effect cleanup but not via explicit unmount/remount cycle)

@ -0,0 +1,44 @@
# Audit: file-upload
audit-version: 1
audited-at: 2026-06-26
scope: SCOPE_DRIFT (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: src/uix/soma/components/file-upload/file-upload-provider.svelte.ts
cleanup-audit (A6/A35/A36): A6 LISTENERS & CLEANUP VERIFIED:
$effect (line 99-110): soma.dom.listen(doc, 'dragover') + listen(doc, 'drop') → cleanupDragOver() + cleanupDrop() returned in cleanup function ✓
$effect (line 637-647, ItemPreviewProvider): URL.createObjectURL() → URL.revokeObjectURL(url) returned in cleanup function
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1: morfo scope must include all layer dirs that exist — file-upload-001 <!-- id: file-upload-001 -->
- dimension: Contract
- rule: SYS-1: morfo scope must include all layer dirs that exist
- location: src/uix/morfo/components/file-upload.ts:7
- evidence: morfo declares scope: ['soma', 'sema'] but src/uix/eidos/components/file-upload/ directory exists with 13 .svelte files (file-upload.svelte, file-upload-dropzone.svelte, file-upload-item.svelte, etc.). Eidos layer is implemented but not declared in morfo scope.
- impact: Scope mismatch signals incomplete morfo contract. Consumers expect morfo scope to list all layers present. This is a systemic architectural issue affecting component layering.
- repro: grep -n 'scope:' src/uix/morfo/components/file-upload.ts; ls -d src/uix/eidos/components/file-upload
- proposed-fix: Update line 7 from scope: ['soma', 'sema'] to scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] CONFIRMED. morfo src/uix/morfo/components/file-upload.ts:7 declares `scope: ['soma', 'sema']`. The eidos layer is fully implemented: src/uix/eidos/components/file-upload/ holds 13 .svelte wrappers + file-upload.css (foundation reading `--file-upload-*` recipe tokens, e.g. `--_file-upload-gap: var(--file-upload-gap-md)`) + types.ts + README.md, AND a recipe exists at src/uix/eidos/lib/recipes/base.ts:2369 ('file-upload': {...} with per-color solid/track tokens). Eidos is present but omitted from morfo scope → SYS-1 scope-drift. MEDIUM is correct per baseline. Note: the candidate's repro `grep -n 'scope:'` is fine, but a `grep 'file-upload:'` on base.ts misses the recipe because the key is tab-indented — the recipe DOES exist.
- fix-status: fixed (212624e0)
## No-findings dimensions
Behavior (A35/A36 loops: no per-item $effect reading opts.ref.current AND writing provider state; no microtask-mediated async without untrack), Behavior (A6 cleanup: all listeners via soma.dom.listen() return disposers; URL.createObjectURL/revokeObjectURL pair in $effect return), Behavior (A33: no $state(new Map/Set); A31: no O(N²) includes checks), Behavior (A30: hiddenInputId set directly in constructor line 444, not in $effect; hiddenInputRef via onRefChange callback line 440-442), Behavior (A15 GESTURE: no drag-drop/cropper gesture logic; dropzone uses native drag events with proper preventDefault), Behavior (LIVE REGIONS: signal-warn-reject dispatches with message; untilFix persistence documented line 191-194), Contract (2-of-3 rule: morfo + soma + eidos all present; all 13 morfo parts registered via runtime.part()), Contract (data naming: all data-* attributes are kebab-cased; no data-soma-* violations), Contract (morfo validation: 'as const satisfies Morfo' present; all aria and data attrs declared), Frontier (no soma->eidos imports; eidos correctly imports from soma), Frontier (no syncAttrs double-write; parts use renderProps() correctly), Theming (focus rings use --focus-ring-width and --focus-ring-color CSS vars; no hardcoded hex colors or magic px values; no z-index magic), DOM selectors (no querySelector with interpolated consumer values; no direct document/window; uses soma.dom.getDocument()), Passive roles (ItemProgress role='progressbar' declares no component-level events; correct), Archetype validation (Item archetype:'item' is correct; CSS does not assign cursor:pointer or interactive styling to display items), Tests (provider.svelte.test.ts covers rejection logic, maxFiles caps, dropzone interactions, item metadata, removal, progress, clear state; jsdom environment)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: vitest/jsdom
- covers: accept/reject logic (FILE_INVALID_TYPE, FILE_TOO_LARGE, FILE_TOO_SMALL, FILE_EXISTS, FILE_CUSTOM_ERROR); validation hook callback; maxFiles cap enforcement; disabled state propagation; required/invalid/empty data attrs; dropzone dragenter/dragover/dragleave/drop handlers; hidden input onchange binding; label for= linking; item name/size/progress display; item removal; clear trigger disabled when empty; progress clamping 0-100
- untested: signal-warn-reject dispatch with live-region message (signal event routing not tested); preventDocumentDrop listener cleanup on unmount; ItemPreview URL.createObjectURL/revokeObjectURL lifecycle across file changes; paste event on dropzone; keyboard Enter/Space on dropzone (role='button'); multiple=false single-file replacement behavior
## Style observations (non-blocking)
- Signal-warn-reject event (line 210) correctly uses 'post' sequence and passes message for live-region announce per a11ySemantic.requiresPersistentTrace + reducedMotionFallback='text'
- A30 pattern correctly applied: hiddenInputId set direct in constructor (line 444), hiddenInputRef via onRefChange callback (line 440-442) — no $effect overhead
- untilFix persistence clearly documented (line 191-194): clearTarget(provider) on each addFiles call resets signal before evaluating new rejections
- Item archetype correctly declared as 'item' (not 'option'/'interactive'); CSS treats item as display container (no cursor:pointer, interactive children are explicit buttons)

@ -0,0 +1,19 @@
# Audit: flex
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract OK, composes through Box via data-flex, gap defaults via Box fallback, data-direction exposed for selector rules.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,53 @@
# Audit: float-panel
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/soma/components/float-panel/float-panel-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 2 · LOW 0.
systemic hits: SYS-1; SYS-2; SYS-3; SYS-5; SYS-6.
## Findings
### MEDIUM: SYS-1 — float-panel-001 <!-- id: float-panel-001 -->
- dimension: E-bis
- rule: SYS-1
- location: src/uix/morfo/components/float-panel.ts:7
- evidence: scope: ['soma', 'sema'], but eidos recipe directory exists at src/uix/eidos/components/float-panel/
- impact: Morfo contract omits eidos in scope declaration despite full eidos implementation present; creates contract-documentation drift.
- repro: List the morfo scope and observe eidos directory exists but not declared.
- proposed-fix: Change morfo scope to ['soma', 'sema', 'eidos'] to match the presence of src/uix/eidos/components/float-panel/
- verify: [confirmed] morfo line 7 literally reads `scope: ['soma', 'sema'],` while the eidos recipe dir exists (Glob returned src/uix/eidos/components/float-panel/ with float-panel.css, types.ts, 14 .svelte parts, recipe block at base.ts:1076). Genuine SYS-1 scope-drift: a complete eidos implementation is present but 'eidos' is omitted from the morfo scope tuple. HIGH per SYS-1 (contract/validator-catchable). Confirmed.
- fix-status: open
### MEDIUM: SYS-3 — float-panel-005 <!-- id: float-panel-005 -->
- dimension: F
- rule: SYS-3
- location: src/uix/soma/components/float-panel/float-panel-provider.svelte.test.ts:1 (jsdom environment only)
- evidence: Test file runs in jsdom (@vitest-environment jsdom); keyboard interaction tests at lines 216-247 only cover basic ArrowRight/ArrowDown moves, not Home/End/PageUp/PageDown/resize keyboard, and no client/Playwright test exists.
- impact: Keyboard grab-mode (move + resize) has complex state transitions (kbToggleMove, kbCancelMove, kbMove with Home/End logic, and kbResize equivalents) exercised only on easy paths (arrow keys) in jsdom. Full keyboard interactivity (especially Home/End reaching bounds and PageUp/PageDown step logic) untested; resize keyboard path completely absent from test coverage.
- repro: Run tests; no keyboard resize tests exist. grep test file for 'kbResize' (absent). Manually verify Home/End position clamping with a real browser.
- proposed-fix: Add integration tests covering (a) keyboard move Home/End/PageUp/PageDown reaching bounds, (b) resize keyboard equivalents, (c) stage transitions cancelling grab-mode, in a client environment (Playwright or @vitest-environment happy-dom/puppeteer).
- verify: [downgraded] The factual premise (jsdom) is WRONG, so the SYS-3 rule citation is misapplied — but a real coverage gap remains. The file is `float-panel-provider.svelte.test.ts`, which matches the CLIENT project glob `src/**/*.svelte.{test,spec}.{js,ts}` (vite.config.ts:72) and runs in headless chromium (browser:{instances:[{browser:'chromium'}]}, line 67-70) — NOT jsdom. The `// @vitest-environment jsdom` line at test:1 is inert for browser-mode tests; the test itself confirms (line 219-220 'this .svelte.test runs in a real browser whose width varies', line 117 reads live window.innerWidth). So it is NOT a SYS-3 jsdom-only case. The valid kernel: keyboard RESIZE (kbToggleResize/kbResize) is entirely untested (grep test for 'kbResize'/'kbToggleResize' = absent) and keyboard MOVE Home/End/PageUp/PageDown + bound-hit announce paths are untested (only ArrowRight + Shift+ArrowDown + cancel + maximized-guard exercised, lines 231-243). Real but not SYS-3; downgrade to MEDIUM coverage-gap with corrected rule (behavioral test gap, not jsdom-only).
- fix-status: open
## No-findings dimensions
A, C, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: opacity: 1; at line 260, 265 (should map to --opacity-* token) | scale: 1; at line 344, 349 (should map to --scale-reset or similar) | line-height: 1; at line 189 (should be canonical or documented)
- undeclared parts: data-animation-style set by eidos but not declared in morfo
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: position seeding and clamping; size min/max clamping; anchor positioning (side=bottom, align=start); dismissal via dismissWith; stage toggling and drag block while maximized; keyboard grab-mode move (basic arrows only)
- untested: keyboard move Home/End/PageUp/PageDown; keyboard resize toggle and all keyboard resize keys; stage transitions cancelling keyboard grab-mode; pointer drag gesture lifecycle (startDrag → pointermove → endDrag); pointer resize gesture lifecycle; multi-panel stacking (bring-to-front during gestures); all dismiss causes (save, cancel, fail, dismiss-outside) semantic payloads; focus return to trigger on close; ResizeGrip focus and keyboard resize affordance
## Style observations (non-blocking)
- CSS composition is clean: drag position uses distinct translate property (not affected by state animations); scale-fade animation preset only touches scale+opacity, leaving translate free for drag.
- Gesture sequencing is well-designed: dragging/resizing state lifted on pointerdown (before move), so shadow transitions before motion begins; live position/size applied imperatively per pointermove, committed to state on release.
- Keyboard grab-mode architecture is clear: pre-grab snapshot enables Escape revert; axis tracking (kbAxis='x'/'y') disambiguates Home/End targets; debounced announcements prevent spam.
- Focus management mirrors Popover: non-modal panel doesn't trap; Dismissal layer's isValidEvent returns false so interior clicks don't trigger dismiss; focus return to trigger is deferred to FocusScope layer (not verified in this audit).

@ -0,0 +1,19 @@
# Audit: float
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract OK, gap defaults to --space-3 via recipe, data-side presence flag for end placement.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: var(--space-3) @ src/uix/eidos/lib/recipes/base.ts:3994 -> canonical token
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,43 @@
# Audit: form
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: src/uix/soma/components/form/form-provider.svelte.ts
cleanup-audit (A6/A35/A36): No timers/listeners/observers/ResizeObserver/IntersectionObserver/MutationObserver/setPointerCapture found in form component. No $effect blocks that require disposal. No $effect.root calls with missing disposers. A35/A36 loop check: No per-item $effect reading opts.ref + writing provider state detec
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 scope-drift: an eidos recipe dir + recipe entry exist for the component, but the mor — FORM-SYS1-SCOPE-DRIFT <!-- id: FORM-SYS1-SCOPE-DRIFT -->
- dimension: A
- rule: SYS-1 scope-drift: an eidos recipe dir + recipe entry exist for the component, but the morfo `scope` omits 'eidos'. The morfo scope must declare every layer that materializes against its contract.
- location: src/uix/morfo/components/form.ts:7 (scope: ['soma', 'sema']) vs eidos dir src/uix/eidos/components/form/ + recipe src/uix/eidos/lib/recipes/base.ts:836 (form:) + form.css:68/77/81 selecting [data-form][data-pending], [data-form][data-invalid], [data-form-error-summary]
- evidence: form.ts:7 `scope: ['soma', 'sema']` — 'eidos' absent. Yet src/uix/eidos/components/form/form.css line 68 `[data-form][data-pending] {`, line 77 `[data-form][data-invalid] {`, line 81 `[data-form-invalid] [data-form-error-summary]` all select morfo-emitted data-attrs, and base.ts:836 declares a `form:` recipe. The eidos layer fully consumes the contract while the morfo scope denies eidos is a consumer.
- impact: The morfo's declared scope under-reports its real consumers. Any scope-driven tooling (coverage checks, layer-presence assertions) will treat form as having no eidos materialization, masking drift if the eidos selectors fall out of sync with the morfo's emitted attrs.
- proposed-fix: Add 'eidos' to formMorfo.scope -> scope: ['soma', 'sema', 'eidos'].
- verify: [verifier-added] added by adversarial verify pass
- fix-status: fixed (212624e0)
## No-findings dimensions
A Contract(morfo): MUST be 'as const satisfies Morfo', Parts registered via runtime.part() exist in morfo & vice-versa, 2-of-3 rule, data-{c}/-{part} naming, aria/data emitted in morfo, A35: per-item $effect reading opts.ref and writing provider state, A36: async write + read-back without untrack, A33: $state(new Map/Set) not reactive, A31: per-item $derived reading global state via provider method, A6: setTimeout/setInterval/listener/ResizeObserver/IntersectionObserver/MutationObserver disposal, A15: GESTURE drag-drop/cropper cleanup, LIVE REGIONS: announce via uix.announce, A30: child->parent id registration DIRECT, DOM-selector: querySelector safe framework values, SOMA imports eidos, data-size/color/variant eidos VISUAL attrs, syncAttrs double-write, TSC tokens, Theming: 9 roles, canonical vars, focus-ring canonical usage, archetype item/option on non-interactive parts, Tests: provider test exists, jsdom env, Redundancy: re-implemented patterns
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 10%, 70%, 48% in color-mix (form.css:198,206,256) - design system blending factors, intentional
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: handleSubmit with invalid validation; first invalid field focus; submit/reset/error-summary state projection; error count and field error list; submit count tracking; disabled state per form state
- untested: Multiple submit attempts with different validation states; Custom onValidSubmit/onInvalidSubmit callbacks; Schema override after form creation; Form created with prebuilt form instance; validationBehaviour parameter (progressive/onSubmit/onBlur/onChange); Async validation in onValidSubmit; noValidate attribute emission; Reset button onclick handler behavior
## Style observations (non-blocking)
- form.css uses canonical CSS variables throughout (--form-*, --color-* roles, --focus-ring-*)
- form.css respects disabled state with canonical --form-disabled-opacity variable
- form auto-fields parts styled as containers/groups, not interactive (no cursor/hover/select pull)
- error-summary uses --color-risk-* role variables appropriately for risk signaling
- button action states properly separated (hover, focus-visible, disabled)
- Layout uses flex/grid with responsive gap sizing via CSS custom properties

@ -0,0 +1,19 @@
# Audit: format-date
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (composite-and-service), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract: 'as const satisfies Morfo' ✓ | Scope: eidos ✓ | Service component (no CSS, no behavior) ✓ | No variants/roles ✓
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: format-number
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (composite-and-service), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract: 'as const satisfies Morfo' ✓ | Scope: eidos ✓ | Service component (no CSS, no behavior) ✓ | No variants/roles ✓
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,55 @@
# Audit: grid-list
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH lead-verified by direct read of the cited code. B5 ground-truth: the A31 O(N²) isSelected/isExpanded (.includes from a per-item $derived) is confirmed across listbox/grid-list/tree-view/tree-grid/tag-group (SYS-7); rovingTargetEl is correctly LIFTED everywhere (not A31); virtual-* use SvelteMap (A33-clean).
provider: G:/dev/svelte/vicen/src/uix/soma/components/grid-list/grid-list-provider.svelte.ts
reactivity (A31/A33/A35): GRID-LIST-A31-HAZARD: Per-row `isSelected = $derived.by(() => this.provider.isSelected(this.opts.value.current))` at line 493 and per-checkbox `isChecked = $derived.by()` at line 612 call `isSelected(value)` method (line 150-152) which does `this.opts.value.current.includes(value)` — array linear scan O(N) per row/checkbox. For N rows, total O(N²) on any selection mutation. Lifted fix: move to pro
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 2 · MEDIUM 1 · LOW 0.
## Findings
### HIGH: A31: Per-item derived calling provider method that reads global state causes O(N²) re-runs — grid-list-001 <!-- id: grid-list-001 -->
- dimension: B - Behavior (A31 reactivity)
- rule: A31: Per-item derived calling provider method that reads global state causes O(N²) re-runs on any mutation
- location: G:/dev/svelte/vicen/src/uix/soma/components/grid-list/grid-list-provider.svelte.ts:493
- evidence: GridListRowProvider line 493: `readonly isSelected = $derived.by(() => this.provider.isSelected(this.opts.value.current))` calls line 150-152: `isSelected(value: string): boolean { return this.opts.value.current.includes(value); }` - performs linear array scan per row, every row re-runs on any selection change
- impact: For N rows, each row's derived calls O(N) .includes() method → O(N²) total. Works fine under ~15 items, becomes noticeably slow at 30+ items.
- proposed-fix: Lift a `Set<string>` on GridListProvider: `readonly selectedSet = $derived(() => new Set(this.opts.value.current))`. In isSelected, use `selectedSet.has(value)` for O(1). Update per-row derived to use the pre-computed set.
- verify: [confirmed] Confirmed A31 O(N²). Line 493: `readonly isSelected = $derived.by(() => this.provider.isSelected(this.opts.value.current))` per GridListRowProvider calls provider.isSelected (lines 150-152: `return this.opts.value.current.includes(value)`), a linear array scan reading GLOBAL selection state. Every row's derived depends on `this.opts.value.current`; selection replaces the array (line 172 `this.opts.value.current = next`), invalidating ALL row deriveds, each re-running O(N) .includes() → O(N²). The provider DID lift a Set for the roving target (line 144 `const selected = new Set(this.opts.value.current)` inside the single `rovingTargetEl` derivation) but did NOT lift one for the per-row isSelected path — fix is real and applicable. Matches SYS-7 / documented Listbox-rovingTarget incident.
- fix-status: fixed (92f988e7)
### HIGH: A31: Per-checkbox derived calling provider method that reads global state — grid-list-002 <!-- id: grid-list-002 -->
- dimension: B - Behavior (A31 reactivity)
- rule: A31: Per-checkbox derived calling provider method that reads global state
- location: G:/dev/svelte/vicen/src/uix/soma/components/grid-list/grid-list-provider.svelte.ts:612-614
- evidence: GridListSelectionCheckboxProvider line 612-614: `readonly isChecked = $derived.by(() => { if (!this.rowValue) return false; return this.provider.isSelected(this.rowValue); })` - calls the O(N) isSelected method. Also line 636 in props derived repeats the same call.
- impact: Per-checkbox derived re-runs and calls isSelected (array .includes), multiplied by number of checkboxes in the list.
- proposed-fix: Same fix as grid-list-001: consume the lifted selectedSet instead of calling isSelected.
- verify: [confirmed] Confirmed A31 on the checkbox part. Lines 612-615: `readonly isChecked = $derived.by(() => { if (!this.rowValue) return false; return this.provider.isSelected(this.rowValue) })` calls the same O(N) linear-scan isSelected (line 151). Additionally line 636, inside the `props` $derived (632-651): `const checked = this.rowValue ? this.provider.isSelected(this.rowValue) : false` — a SECOND read of the linear scan per checkbox. Both deriveds depend on `value.current` via isSelected and re-run for every checkbox on any selection mutation. Same lifted-Set fix as 001. HIGH per SYS-7.
- fix-status: fixed (92f988e7)
### MEDIUM: SYS-1: Scope-drift when eidos recipe directory exists but 'eidos' is omitted from morfo.sc — grid-list-003 <!-- id: grid-list-003 -->
- dimension: A - Contract (morfo scope)
- rule: SYS-1: Scope-drift when eidos recipe directory exists but 'eidos' is omitted from morfo.scope
- location: G:/dev/svelte/vicen/src/uix/morfo/components/grid-list.ts:7
- evidence: Morfo declares `scope: ['soma', 'sema']` (line 7) but full eidos directory exists at G:/dev/svelte/vicen/src/uix/eidos/components/grid-list/ with grid-list.svelte (lines 1-58), grid-list.css (lines 1-236), types.ts, and child components (grid-list-row.svelte, grid-list-cell.svelte, grid-list-selection-checkbox.svelte). Eidos scope is present but not declared.
- impact: Inconsistency in declared vs actual scope. Eidos components are real and functional but not flagged in morfo. May confuse consumers about component structure.
- proposed-fix: Either (1) add 'eidos' to morfo.scope: `scope: ['soma', 'sema', 'eidos']`, OR (2) remove the eidos directory if GridList is soma-only. Recommend option 1 since eidos wrapper clearly exists and re-exports soma.
- verify: [confirmed] Confirmed SYS-1 scope-drift. grid-list.ts:7 declares `scope: ['soma', 'sema']` but a full eidos directory exists: grid-list.svelte, grid-list.css, types.ts, index.ts, grid-list-row.svelte, grid-list-cell.svelte, grid-list-selection-checkbox.svelte (verified via glob). The eidos wrapper is real and functional yet 'eidos' is omitted from the morfo scope. This is the documented systemic SYS-1 pattern (siblings command/combobox/date-picker show the same omission). MEDIUM per baseline. No recipe entry in recipes/base.ts (grep `grid-list:`/`gridList` returned no match), so the eidos surface is CSS-only — adding 'eidos' to scope is the correct alignment.
- fix-status: fixed (212624e0)
## No-findings dimensions
C - DOM-selector (CSS.escape used correctly on line 184 for consumer value), D - Frontier (no soma→eidos imports; eidos→soma normal), E - TSC/Theming (--control-height-*, --font-size-*, --space-*, --color-* all canonical; no magic hex or z-index), F - Tests (test environment jsdom is acceptable for this DOM-interactive pattern; keyboard nav, selection, typeahead tested), G - Redundancy (no detected duplication in selection/keyboard navigation logic), E-bis - No A33 ($state(new Map/Set)), A30 (id registration is direct field, not $effect), A6 (listeners cleaned in $effect.root), A14 (roving tabindex correctly implements exactly one tabindex=0), A34 DOM-TOPOLOGY (require() pattern not used)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 0.875rem (checkbox size at grid-list.css:196 - specific rem for UI element size, acceptable) | 60vh in min() at grid-list.css:27 - viewport unit for max-height, not a magic z-index
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom (@vitest-environment jsdom at grid-list-provider.svelte.test.ts:1)
- covers: row selection and deselection via click; shift+click range selection; ctrl/meta+click toggle; keyboard navigation (ArrowDown, ArrowUp, Home, End, PageUp, PageDown); row focusing and roving tabindex; typeahead character matching; select-all (Ctrl+A) and clear (Escape); checkbox row association and toggle; cell-level horizontal arrow navigation (ArrowRight/Left); merging Field provider flags (disabled, readonly, required, invalid)
- untested: O(N²) performance regression with 100+ items (no perf test); RTL keyboard navigation specifics (tests hardcoded ltr); Typeahead buffer timer cleanup edge-cases

@ -0,0 +1,19 @@
# Audit: grid
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract OK, composes through Box via data-grid, item placement props (gridColumn/Row/Area) declared on Box child.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,26 @@
# Audit: group
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (layout), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: Contract OK, data-grow and data-attached presence flags, attached implies gap=0.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
### LOW: MAGIC LITERAL 1px physically-fixed for border collapse — group-001 <!-- id: group-001 -->
- dimension: magic-literal
- location: src/uix/eidos/lib/recipes/base.ts:3997
- evidence: 'attached-overlap': '1px'
- impact: Hardcoded 1px overlap is physically-fixed (border collapse depth), not a drift.
- proposed-fix: ACCEPT—1px is a physically-fixed value for border collapse, not a scale literal.
- verify: [confirmed] base.ts:3997 `'attached-overlap': '1px'` — physically-fixed border-collapse overlap (sibling borders), not a scale literal. Analyzer itself recommends ACCEPT. Genuine non-defect / LOW.
- fix-status: open
## Theming facts (E-bis)
- magic literals: 1px @ src/uix/eidos/lib/recipes/base.ts:3997 -> physically-fixed (border collapse)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: heading
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: morfo OK; no recipe; roles fragmented (content-role subset)
as-const: true · roles clean: false · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 0.15em @ src/uix/eidos/components/heading/heading.css:88 -> canonical underline-offset token
- magic z-index: none
- non-canonical names: none
- roles clean: false · variants clean: true

@ -0,0 +1,19 @@
# Audit: highlight
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: morfo OK; no recipe; ColorRole correct; composition shell (no own chrome)
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,19 @@
# Audit: icon
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), minimal leaf element (no parts), decorative/semantic toggle via aria-hidden, recipe provides size + stroke-width, no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,49 @@
# Audit: image-adjustments
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: src/uix/soma/components/image-adjustments/image-adjustments-provider.svelte.ts
cleanup-audit (A6/A35/A36): No $effect in ImageAdjustmentsProvider or sub-providers that manage timers, listeners, or observers. No setTimeout, setInterval, addEventListener, ResizeObserver, IntersectionObserver, MutationObserver, or pointer capture in the ImageAdjustments layer (all delegated to composed <Slider>). The test f
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 — Scope-Drift: eidos directory exists but morfo scope does not include 'eidos' — image-adjustments-003 <!-- id: image-adjustments-003 -->
- dimension: D
- rule: SYS-1 — Scope-Drift: eidos directory exists but morfo scope does not include 'eidos'
- location: src/uix/morfo/components/image-adjustments.ts:18 vs src/uix/eidos/components/image-adjustments/
- evidence: The morfo declares scope: ['soma', 'sema'] (line 18), but a full eidos implementation exists at src/uix/eidos/components/image-adjustments/ containing image-adjustments.svelte, image-adjustments.css, types.ts, README.md, and index.ts. Per the baseline, this is a confirmed systemic issue (SYS-1) affecting several B7 components.
- impact: Scope mismatch: the morfo's declared scope does not reflect reality. Tooling relying on scope declarations may fail to validate or organize the eidos layer. No functional impact on the component itself, but the contract is misleading.
- repro: Inspect src/uix/morfo/components/image-adjustments.ts line 18 (scope: ['soma', 'sema']) and verify that src/uix/eidos/components/image-adjustments/ exists with a full component implementation.
- proposed-fix: Update the morfo's scope to: scope: ['soma', 'sema', 'eidos']. This aligns the contract with the actual implementation layers present.
- verify: [confirmed] CONFIRMED. image-adjustments.ts:18 declares `scope: ['soma', 'sema']`, omitting 'eidos'. A full eidos implementation exists at src/uix/eidos/components/image-adjustments/ (image-adjustments.css verified read, README.md verified read, recipe block at base.ts:1112-1132 'image-adjustments': {...}). This is the confirmed-systemic SYS-1 scope-drift pattern. MEDIUM. Fix: scope: ['soma', 'sema', 'eidos'].
- fix-status: fixed (212624e0)
## No-findings dimensions
B, C, F, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: computeImageFilter: neutral (none), tone functions (brightness/contrast/saturation/hue), 0-based (blur/grayscale/sepia), temperature approximation (warm/cool), multi-adjustment composition; ImageAdjustmentsProvider: live filter derivation, isModified tracking, setAdjustment mutation + callbacks, reset() via runtime trigger, custom adjustments subset, disabled state blocks mutations, catalog + defaults exposed; ImageAdjustmentsItemProvider: derived value/label/formattedValue per adjustment, setValue callback; Snippet props exposure and live callback routing
- untested: A15 gesture: pointer interaction is delegated to the composed <Slider>, not handled directly by ImageAdjustments. Pointer cleanup verified in Slider tests, not here.; A6 timers/listeners: no direct setTimeout/setInterval/addEventListener/ResizeObserver/etc. in ImageAdjustments provider or components. Interaction heavy lifting delegated to Slider.; A35 effect loop: no per-item $effect reading opts.ref.current and writing provider state. No reactive loop risk.; A36 microtask loop: no async (.then/microtask/setTimeout) writing reactive vars without untrack. Callbacks (onValueChange/onFilterChange) are consumer-driven, not effect-mediated.; Live region: the component does not emit a live region for adjustment changes (per README: 'ItemValue is a plain <span>, not a live region. The Slider thumb already announces aria-valuenow'). Announcement delegated to composed Slider.; A30 id registration: no child->parent id registration loop. Each part registered directly via runtime.part() in the constructor.
## Style observations (non-blocking)
- CSS correctly uses data attribute selectors matching the morfo kebabs: [data-image-adjustments], [data-image-adjustments-item], [data-image-adjustments-item-label], [data-image-adjustments-item-value], [data-image-adjustments-reset].
- Reset button uses :focus-visible for keyboard navigation cue (line 70-74), not hardcoded :focus. Correct.
- Reset button uses var(--focus-ring-width) and var(--focus-ring-color), consuming the canonical two-ring focus model from the theme. Correct.
- Disabled state uses var(--opacity-disabled) (line 19, 77). Correct token usage.
- Reset hover state applies var(--color-content-primary), correct semantic color. Line 67.
- Hue row track override correctly uses var(--image-adjustments-hue-track), exiling the literal rainbow to the recipe layer (line 86). Correct raw-color contract.
- No hardcoded :active, no magic z-index, no raw hex. All CSS tokens correctly namespaced to component.
- Row typography uses var(--font-size-sm/xs), var(--font-weight-medium), var(--font-mono) — all reference the base tokens, not px/rem literals. Correct.
- Spacing uses var(--space-*) and var(--image-adjustments-*) recipe tokens. No hardcoded gap/margin px values. Correct.
- Reset focus-visible outline uses canonical --focus-ring-width + --focus-ring-color, not hardcoded values. Correct.
- Tabular numerals (font-variant-numeric: tabular-nums) on ItemValue to prevent reflow during drag — good UX consideration (line 48).

@ -0,0 +1,35 @@
# Audit: image-picker
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: /g/dev/svelte/vicen/src/uix/soma/components/image-picker/image-picker-provider.svelte.ts
cleanup-audit (A6/A35/A36): One $effect at line 82: reads opts.file.current, creates URL.createObjectURL, writes to this.url, cleanup function properly returns and calls URL.revokeObjectURL(u). Cleanup is attached and will fire on effect cleanup. No timers, listeners, ResizeObserver, IntersectionObserver, MutationObserver, or
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 scope-drift: a component with a real eidos implementation (recipe dir + CSS selectin — image-picker-NEW-001 <!-- id: image-picker-NEW-001 -->
- dimension: A_Contract
- rule: SYS-1 scope-drift: a component with a real eidos implementation (recipe dir + CSS selecting morfo data-attrs) MUST list 'eidos' in morfo.scope ('Layers that implement this component' — types.ts:798-799).
- location: src/uix/morfo/components/image-picker.ts:20
- evidence: Morfo declares `scope: ['soma', 'sema']` (line 20), omitting 'eidos'. But a full eidos layer implements the component: src/uix/eidos/components/image-picker/{image-picker.svelte,image-picker.css,types.ts,index.ts} all exist, and image-picker.css selects exclusively against morfo-emitted data-attrs — `[data-image-picker]` (:12), `[data-image-picker][data-disabled]` (:18), `[data-image-picker-preview][data-rotation='90']` (:49), `[data-image-picker-toolbar]` (:59), `[data-image-picker-rotate]`/`[data-image-picker-remove]` (:67-68). These are the morfo's `contracts.cssSelectors` surface, so eidos is a genuine implementing layer per the scope doctrine.
- impact: Contract drift: the morfo under-declares its implementing layers. Tooling/coverage that keys off `scope` (lint, layer audits, sema coverage) treats the component as having no eidos layer, masking the eidos↔morfo contract. Cosmetic-to-tooling, no runtime user impact — matches the confirmed SYS-1 baseline severity (MEDIUM).
- proposed-fix: Add 'eidos' to the scope array: `scope: ['soma', 'sema', 'eidos']` in src/uix/morfo/components/image-picker.ts:20.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: fixed (212624e0)
## No-findings dimensions
B_Behavior_A6, B_Behavior_A35, B_Behavior_A36, A_Contract_Parts, A_Contract_DataAria_SyncAttrs, D_Frontier_SomaEidos, D_Frontier_DoubleWrite, E_Theming_MagicZ, E_Theming_MagicColors, E_Theming_MagicOpacity, E_bis_RecipeFocus, E_bis_RecipeButton, F_Tests, G_Redundancy
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: state_empty_ready; setFile_fires_onSelect_onChange_triggers_commit_select; rotate_cycles_90_wraps_360; remove_clears_file_resets_transforms; filter_composition; disabled_blocks_actions
- untested:

@ -0,0 +1,19 @@
# Audit: image
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), status-driven layering (idle/loading/loaded/error), fit + position + radius controls, no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 0 @ image.css:125 -> z-index (LOCAL, fallback layering) | 1 @ image.css:223 -> z-index (LOCAL, error layering) | 70% @ image.css:189 -> icon size ratio (local sizing, acceptable)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,26 @@
# Audit: kbd
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: morfo OK; no recipe; ControlVariant canonical; padding literals need canonicalization
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
### LOW: em-literal padding and tracking; canonical scale should exist for kbd chrome — kbd-001 <!-- id: kbd-001 -->
- dimension: E-bis: Theming (SIZES)
- location: src/uix/eidos/components/kbd/kbd.css:24-25, 30
- evidence: padding-inline: 0.4em; padding-block: 0.18em; letter-spacing: var(--tracking-wide)
- impact: Mixed: tracking uses canonical, but padding em-literals lack canonical tokens
- proposed-fix: Define --kbd-padding-inline and --kbd-padding-block canonical tokens
- verify: [downgraded] kbd.css:24-25 'padding-inline: 0.4em; padding-block: 0.18em' — same rationale as code-002: em-relative inline-pill padding, no canonical em padding scale, scales with prose. The finding itself notes letter-spacing already uses canonical var(--tracking-wide) (line 30) and radius uses --radius-sm (line 23). Only the em paddings remain; legitimate inline-flow relative units. Downgraded MEDIUM→LOW.
- fix-status: open
## Theming facts (E-bis)
- magic literals: 0.4em @ src/uix/eidos/components/kbd/kbd.css:24 -> canonical kbd-padding-inline token | 0.18em @ src/uix/eidos/components/kbd/kbd.css:25 -> canonical kbd-padding-block token
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,44 @@
# Audit: link-preview
audit-version: 1
audited-at: 2026-06-26
scope: ['soma'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/soma/components/link-preview/link-preview-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
systemic hits: SYS-1 (scope-drift: eidos exists but not declared in morfo scope); SYS-2 (magic literals in CSS: 1px, 0.18em, 4px, 0.985 scale).
## Findings
### MEDIUM: SYS-1 — link-preview-001 <!-- id: link-preview-001 -->
- dimension: A
- rule: SYS-1
- location: src/uix/morfo/components/link-preview.ts:7
- evidence: scope: ['soma'] declared in morfo, but eidos recipe dir exists at src/uix/eidos/components/link-preview/ with 7 files (index.ts, link-preview.svelte, link-preview-content.svelte, link-preview-trigger.svelte, link-preview-arrow.svelte, link-preview.css, types.ts)
- impact: Morfo scope omits 'eidos' while eidos recipe and components exist; creates maintenance confusion and violates the documented scope contract.
- repro: grep -r "src/uix/eidos/components/link-preview" to confirm files exist; check morfo scope at src/uix/morfo/components/link-preview.ts:7
- proposed-fix: Either (a) add 'eidos' to morfo scope: `scope: ['soma', 'eidos']`, or (b) if eidos is intentional internal structure, document why scope is intentionally ['soma'] only and mark eidos as internal pattern.
- verify: [confirmed] Confirmed SYS-1 scope-drift. morfo line 7: `scope: ['soma'],` while a full eidos recipe dir exists: ls src/uix/eidos/components/link-preview/ shows index.ts, link-preview.svelte, link-preview-content.svelte, link-preview-trigger.svelte, link-preview-arrow.svelte, link-preview.css, types.ts. morfo/types.ts:799 documents `scope` as 'Layers that implement this component. Eidos-only primitives may declare ["eidos"].' Peer overlays correctly list eidos: dropdown-menu.ts:11 `scope: ['soma', 'sema', 'eidos']`, tooltip.ts:14 `scope: ['soma', 'eidos', 'sema']`. link-preview omits 'eidos' despite shipping a recipe. MEDIUM is right per batch-1 SYS-1 baseline.
- fix-status: open
## No-findings dimensions
B, C, D, F, G
## Theming facts (E-bis)
- magic z-index: z-index is tokenized via --popover-content-z (canonical preset)
- magic literals: text-decoration-thickness: 1px (should use --border-width or clarify as exempt) | text-underline-offset: 0.18em (should use --tracking-* or clarify as exempt) | opacity: 0 in keyframes (acceptable as terminal state) | scale(0.985) and 4px offsets in keyframes (should be tokenized or documented)
- undeclared parts: none
- roles clean: true · variants clean: true
- conformance: link-preview uses only primary role (canonical); no invented roles or sizes; size variants xs|sm|md|lg|xl conform to E-bis subset.
## Tests (F)
- exists: true · env: jsdom
- covers: open/close timers with configurable delays (openDelay/closeDelay); touch pointer ignored (only mouse/pen); disabled flag blocks open; SafePolygon pointer bridge (content hover prevents close); dismissal via Escape key and outside-click; floating content/arrow props exposure; data-state transitions
- untested: keyboard Escape key (mentioned in README but not explicitly tested in jsdom); focus return to trigger on close (A17 not exercised); content visibility presence transitions (Presence API self-cleanup verified at code level); client/Playwright integration tests (jsdom-only; no browser interaction test)
## Style observations (non-blocking)
- LinkPreview correctly uses trigger as navigational <a> (not button), which is documented and intentional per APG guidance for hover-card discovery.
- aria-hidden=true on content is explicit design choice to hide preview from AT; justified by README study §2.3 (duplicate content, hover-only activation, focus stays on trigger).
- SafePolygon bridge with touch guard (pointer type check) is well-implemented to preserve preview on pointer transitions.
- Color tokens use primary role only; fallback to --color-content-primary is defensive.
- Animation preset (4 directional in/out keyframes) matches floating-ui positioning strategy; naming data-side correlates CSS state to floating placement.

@ -0,0 +1,19 @@
# Audit: link
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: morfo OK; no recipe; ColorRole correct (9 roles); variants canonical (default/subtle/plain)
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: 0.85em @ src/uix/eidos/components/link/link.css:112 -> canonical icon-size token (acceptable; glyph scale)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,45 @@
# Audit: listbox
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH lead-verified by direct read of the cited code. B5 ground-truth: the A31 O(N²) isSelected/isExpanded (.includes from a per-item $derived) is confirmed across listbox/grid-list/tree-view/tree-grid/tag-group (SYS-7); rovingTargetEl is correctly LIFTED everywhere (not A31); virtual-* use SvelteMap (A33-clean).
provider: /g/dev/svelte/vicen/src/uix/soma/components/listbox
reactivity (A31/A33/A35): FOUND: A31 O(N²) latent bug at line 413. Per-item isSelected $derived calls provider.isSelected() which does array.includes() — triggers on every provider.value change. Multi-select mode: N items × O(N) array search = O(N²). Roving-target was correctly lifted (line 148-153) but isSelected was not. Single-select unaffected (selection ≤ 1). Tests max 3 items, so bug not caught. HIGH severity.
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 1 · MEDIUM 1 · LOW 0.
## Findings
### HIGH: A31: Per-item $derived reading global provider state via .includes() O(N) method — listbox-001 <!-- id: listbox-001 -->
- dimension: B (Reactivity - A31)
- rule: A31: Per-item $derived reading global provider state via .includes() O(N) method
- location: src/uix/soma/components/listbox/listbox-provider.svelte.ts:413, :157-158
- evidence: Line 413: `readonly isSelected = $derived.by(() => this.provider.isSelected(this.opts.value.current));` — per-item derived calling Line 157-158: `isSelected(value: string): boolean { return this.opts.value.current.includes(value); }`. When provider.opts.value changes, ALL N items' derived re-run, each performing O(N) array.includes() search. With multi-select, N items × O(N) selection-array = O(N²).
- impact: Multi-select listboxes with 30+ items + selections will experience quadratic re-derivation cost. Single-select mode is unaffected (selection array ≤ 1 item). Performance degradation not caught by existing tests which max 3 items.
- proposed-fix: Lift isSelected as a provider-level $derived returning a Set<string> keyed by value, similar to rovingTargetEl pattern (line 148-153). Items would then do O(1) .has() checks instead of O(N) .includes() calls. Alternatively, compute a derived Set at provider level and expose it for item consumption.
- verify: [confirmed] CONFIRMED HIGH. Re-read listbox-provider.svelte.ts. Line 413 (per-item): `readonly isSelected = $derived.by(() => this.provider.isSelected(this.opts.value.current));`. Line 157-158 (provider method): `isSelected(value: string): boolean { return this.opts.value.current.includes(value); }` — reads GLOBAL `opts.value` and does O(value.length) Array.includes(). This is the textbook A31 pattern: every `value` mutation invalidates all N items' `isSelected` derivations, each re-running an O(K) array search (K = selection size). Multi-select: N items x O(N) = O(N²). Single-select: K<=1 so effectively O(N), unaffected. The asymmetry is the proof this is a genuine miss, not a design choice: `rovingTargetEl` (lines 148-153) was deliberately LIFTED to ONE provider-level $derived building `new Set(this.opts.value.current)` with an explicit comment (141-146) documenting 'avoids an O(N²) cascade' — yet the sibling `isSelected` reactive path was left calling the O(N) provider method per-item. Matches the documented Listbox-rovingTarget incident exactly (same provider). Tests max out at 3 items (alpha/beta/gamma, lines 167-212) with no large-N/perf test, so the hazard is unguarded. Fix per candidate is correct; cleanest form: provider-level `readonly selectedSet = $derived(new Set(this.opts.value.current))` consulted via `.has()` from `isSelected()`, mirroring the existing line-151 Set.
- fix-status: fixed (92f988e7)
### MEDIUM: SYS-1: Scope drift — eidos recipe directory exists but morfo.scope does not declare 'eidos — listbox-002 <!-- id: listbox-002 -->
- dimension: A (Contract)
- rule: SYS-1: Scope drift — eidos recipe directory exists but morfo.scope does not declare 'eidos'
- location: src/uix/morfo/components/listbox.ts:7 declares scope: ['soma', 'sema']; eidos components exist at src/uix/eidos/components/listbox/
- evidence: Morfo line 7: `scope: ['soma', 'sema']` omits 'eidos'. Directory structure confirms eidos layer exists with full implementation: listbox.svelte, listbox-item.svelte, listbox-group.svelte, listbox-item-indicator.svelte, listbox-group-label.svelte, listbox.css, types.ts, index.ts.
- impact: Morfo contract validator may not check eidos layer compliance; documentation/tooling may omit eidos layer from automated coverage. Scope mismatch creates maintenance liability if eidos changes are made without realizing morfo is unaware.
- proposed-fix: Add 'eidos' to morfo.scope array: `scope: ['soma', 'sema', 'eidos']`
- verify: [confirmed] CONFIRMED MEDIUM (canonical SYS-1). Morfo line 7: `scope: ['soma', 'sema']` (file ends correctly with `} as const satisfies Morfo;` — contract annotation is fine, not a separate finding). The eidos layer materially exists at src/uix/eidos/components/listbox/: listbox.css (6574 bytes), listbox.svelte, listbox-item.svelte, listbox-group.svelte, listbox-group-label.svelte, listbox-item-indicator.svelte, types.ts, index.ts — a full implementation. Per SYS-1, eidos-recipe-dir-exists + morfo-scope-omits-'eidos' = MEDIUM scope-drift, and that is exactly the case here. Context: this is systemic — only 63/128 morfo components declare 'eidos' in scope despite 130 eidos dirs; major interactive siblings (toggle/dialog/select/combobox/popover) all omit it too. Minor correction to candidate evidence: there is NO `listbox:` recipe key in lib/recipes/base.ts (grep returns nothing) — the CSS is foundation-only, not recipe-driven. But SYS-1 triggers on the eidos DIR existence, which is confirmed, so the finding stands at MEDIUM. Fix: add 'eidos' to the scope array.
- fix-status: fixed (212624e0)
## No-findings dimensions
C (DOM-selector - CSS.escape use safe on framework numeric IDs), D (Frontier - no soma→eidos imports, no double-write divergence), E (TSC theming - all CSS vars reference canonical --color-* / --space-* / --radius-* / --opacity-* / --font-size-*), G (Redundancy - keyboard nav index math consistent, no duplication with divergence risk)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 0.875rem (listbox.css:185 item-indicator size - should reference --icon-size-sm or similar scale)
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom (@vitest-environment jsdom)
- covers: Root accessibility props + focus state; Item selection + toggle + roving tabindex; Keyboard navigation (Arrow Down/Up, Home, End, Ctrl+A typeahead); Group + GroupLabel context wiring; ItemIndicator presence toggling
- untested: Large-scale multi-select (30+ items) performance under rapid selection changes — A31 O(N²) risk not validated; Disabled item filtering in roving target (getItems() correctly scoped to enabled items per line 137-138); Horizontal orientation + RTL keyboard direction (declared in morfo, not tested)

@ -0,0 +1,26 @@
# Audit: mark
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (typography), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
conformance: morfo OK; no recipe; ColorRole correct (9 roles); padding em-literals need canonicalization
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
### LOW: em-literal padding; no canonical scale defined — mark-001 <!-- id: mark-001 -->
- dimension: E-bis: Theming (SIZES)
- location: src/uix/eidos/components/mark/mark.css:14-15
- evidence: padding-inline: 0.16em; padding-block: 0.04em
- impact: Magic em literals for mark decoration; same issue as code.css padding
- proposed-fix: Define --mark-padding-inline and --mark-padding-block tokens
- verify: [downgraded] mark.css:14-15 'padding-inline: 0.16em; padding-block: 0.04em' — same rationale: em-relative inline-highlight padding scaling with prose font-size, no canonical em padding scale, radius uses canonical --radius-sm. Legitimate inline-flow literal, not drift. Downgraded MEDIUM→LOW.
- fix-status: open
## Theming facts (E-bis)
- magic literals: 0.16em @ src/uix/eidos/components/mark/mark.css:14 -> canonical mark-padding-inline token | 0.04em @ src/uix/eidos/components/mark/mark.css:15 -> canonical mark-padding-block token
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,90 @@
# Audit: menu-dial
audit-version: 1
audited-at: 2026-06-26
scope: ['soma','sema','eidos'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: G:/dev/svelte/vicen/src/uix/soma/components/menu-dial
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 3.
systemic hits: SYS-1 (scope-drift: sema declared but not implemented); SYS-3 (keyboard/focus untested at component level, only nav logic tested); SYS-2 (magic z-index fallbacks, bare duration literals).
## Findings
### MEDIUM: THEMING — menu-dial-002 <!-- id: menu-dial-002 -->
- dimension: A
- rule: THEMING
- location: src/uix/morfo/components/menu-dial.ts:19
- evidence: Morfo declares scope: ['soma','sema','eidos'] but no recipe file found in src/uix/eidos/lib/recipes/base.ts or recipe subdirectory for menu-dial
- impact: If the morfo declares eidos in scope, the component requires either a recipe in base.ts or a recipe directory (src/uix/eidos/lib/recipes/menu-dial/), but neither exists. CSS is present but not declared in the recipe system.
- proposed-fix: Verify intent: if eidos styling is complete via CSS alone, document this exception. If tokens should be in base.ts, add a menu-dial recipe entry.
- verify: [confirmed] Confirmed via grep: `grep menu-dial base.ts` returns NOTHING — there is no `'menu-dial':` recipe key in src/uix/eidos/lib/recipes/base.ts. The morfo declares `scope: ['soma','sema','eidos']` (line 19). Crucially the TWIN component it repeatedly mirrors, `onion-menu`, DOES have a recipe block (base.ts:4368) whose header comment reads 'this owns the magic numbers that USED to be literals in onion-menu.css.' Menu-dial's CSS still carries those exact literals un-tokenized (28ms stagger, 2px glyph thickness — base.ts:4374 names onion's as `glyph-bar-thickness: '2px'`). So this is a genuine THEMING/recipe gap, not just a documentation note. Downgraded HIGH→MEDIUM: it's a token-coverage inconsistency vs the sibling, not a user-visible break. Confirmed at MEDIUM.
- fix-status: open
### MEDIUM: SYS-3 (keyboard route test coverage) — menu-dial-005 <!-- id: menu-dial-005 -->
- dimension: F
- rule: SYS-3 (keyboard route test coverage)
- location: src/uix/soma/components/menu-dial/menu-dial-nav.test.ts
- evidence: Only menu-dial-nav.test.ts exists (tests pure navigation logic). No provider test (no menu-dial-provider.svelte.test.ts or equivalent in eidos). The nav tests verify wrapping, disabled-skipping, Home/End but do NOT test keyboard routing through the soma/eidos integration: Enter/Space to open/close, Arrow routing through list while open, focus sync, dismiss on Escape.
- impact: Keyboard behavior (the primary interaction mode per APG) is untested at the component level. The nav helpers are tested in isolation but their integration with focus, dismissal, and the open/close state is not exercised.
- proposed-fix: Add a @vitest-environment=jsdom or Playwright client test file (e.g., menu-dial-provider.svelte.test.ts) covering: trigger keydown (Enter/Arrow to open), list keydown (Arrow/Home/End navigation, Escape to close), focus sync after open, Tab-out dismiss. Mark high-risk paths.
- verify: [confirmed] Confirmed: `ls src/uix/soma/components/menu-dial/` shows only `menu-dial-nav.test.ts` (verified, reads it) which tests the PURE `dialNav*` index math (count 4 wrap, disabled-skip [1,2], Home/End, degenerate empty/all-disabled) in isolation — no DOM, no @vitest-environment. There is NO provider/integration test anywhere (grep'd both soma and eidos menu-dial dirs). The integration surface is entirely untested: trigger keydown→open (runtime.keydown('trigger')), list Arrow/Home/End routing through focus (`focusActionAt` + actionEls() querySelectorAll), Escape→close, focusout/outside-pointerdown dismiss ($effect lines 254-291), focus-return-to-trigger (line 176), the pendingFocus $effect (270-279). This is a textbook SYS-3: interaction-heavy component, the easy pure-math path tested, every keyboard/focus/dismissal integration path untested. Confirmed MEDIUM.
- fix-status: open
### MEDIUM: THEMING (recipe ownership of magic numbers) / 2-of-3 — menu-dial-101 <!-- id: menu-dial-101 -->
- dimension: A, E-bis
- rule: THEMING (recipe ownership of magic numbers) / 2-of-3
- location: src/uix/eidos/components/menu-dial/menu-dial.css:25,291-292 vs src/uix/eidos/lib/recipes/base.ts:4368-4377
- evidence: menu-dial.css line 25 `--_menu-dial-stagger-step: 28ms;` and line 291-292 `block-size: 2px; margin-block-start: -1px;` (the glyph bar) are un-tokenized literals. The TWIN component onion-menu — which menu-dial's own comments say it 'mirrors' — moved exactly these into a base.ts recipe: base.ts:4374 `'glyph-bar-thickness': '2px'`, and onion's header (4364-4366) explicitly says the recipe 'owns the magic numbers that USED to be literals in onion-menu.css.' menu-dial has NO base.ts recipe block at all (grep 'menu-dial' base.ts → empty).
- impact: menu-dial regressed on the precise pattern the framework already established for its sibling: the speed-dial's tunable timing (28ms stagger) and glyph thickness (2px) are frozen in CSS with no recipe knob, so a theme cannot retune them and they can silently drift from onion-menu's tokenized equivalents. This is the concrete, code-grounded core of candidate findings 002+006.
- repro: grep -n "menu-dial" src/uix/eidos/lib/recipes/base.ts # → no matches; compare base.ts:4368 onion-menu block
- proposed-fix: Add a `'menu-dial':` block to base.ts recipes mirroring onion-menu: e.g. `'stagger-step': '28ms'`, `'glyph-bar-thickness': '2px'` (referencing the same source onion uses), and consume `var(--menu-dial-glyph-bar-thickness)` / `var(--menu-dial-stagger-step)` in the CSS.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
### LOW: A22 (aria-controls, aria-labelledby) — menu-dial-003 <!-- id: menu-dial-003 -->
- dimension: A
- rule: A22 (aria-controls, aria-labelledby)
- location: src/uix/eidos/components/menu-dial/menu-dial.svelte:314-339
- evidence: Morfo declares aria-controls on trigger (line 84-88 of morfo: 'aria-controls: value: v.partRef("list")', recommended severity) and aria-labelledby on list (line 112-116: 'aria-labelledby: value: v.partRef("trigger")'), but neither is set in the Svelte implementation. Trigger has no aria-controls attribute; list has no aria-labelledby attribute.
- impact: Accessibility regression: screen readers cannot programmatically associate the trigger button with the menu it controls, violating ARIA authoring practices. The list is labeled aria-label=ariaLabel (same as trigger), creating ambiguity.
- proposed-fix: Generate unique IDs for trigger/list on mount. Add aria-controls=listId to Fab trigger and aria-labelledby=triggerId to list div. Both attributes marked as 'recommended', so failing to set them should be flagged but component is still functional.
- verify: [downgraded] Confirmed factually: morfo lines 83-88 declare trigger `aria-controls: v.partRef('list')` and lines 110-116 declare list `aria-labelledby: v.partRef('trigger')`. grep for `aria-controls|aria-labelledby|id=|useId` in the menu-dial eidos dir returns ZERO hits — neither attr is emitted, and no IDs are generated. menu-dial.svelte sets only `aria-haspopup='menu'` + `aria-expanded` on the Fab (lines 321-322) and `aria-label={ariaLabel}` on both trigger and list (lines 320, 335). HOWEVER both morfo declarations carry `severity: 'recommended'` (lines 87, 115) — NOT 'required'. The component is functional and named: trigger has aria-label + aria-haspopup + aria-expanded; list has aria-label + role='menu'. The sibling onion-menu also omits aria-controls (grep confirmed), so this is a family-wide pattern, not a unique regression. A missing 'recommended' attr on an otherwise-named control is a LOW/nice-to-have a11y nit, not a HIGH a11y break. Downgraded HIGH→LOW.
- fix-status: open
### LOW: TWO-MOMENTS (sequence pre vs post) — menu-dial-004 <!-- id: menu-dial-004 -->
- dimension: B
- rule: TWO-MOMENTS (sequence pre vs post)
- location: src/uix/eidos/components/menu-dial/menu-dial.svelte:169-177
- evidence: closeMenu() sets open=false (line 171) BEFORE calling runtime.trigger('close', ...) (line 175). Morfo declares close event has sequence='pre', meaning it should fire BEFORE state changes. The reactive data-state binding (line 334: data-state={open ? 'open' : 'closed'}) will update synchronously when open changes, moving the list to data-state='closed' before the event is emitted.
- impact: For 'pre' sequence events, the content (list with data-state) should still be marked as open when the event fires (for announcements, sounds), but the binding changes the state first. Same issue in openMenu (line 164).
- proposed-fix: Reorder: trigger the event BEFORE updating open state, or untrack the state update so the event fires first. e.g., `runtime.trigger('close', ...); open = false;` or use $effect to emit after measuring.
- verify: [downgraded] Factually correct: closeMenu (eidos line 169-177) sets `open = false` (line 171) BEFORE `runtime.trigger('close', ...)` (line 175); same for openMenu (line 164 before 166). The template binds `data-state={open ? 'open' : 'closed'}` reactively (line 334). BUT the impact is overstated for two reasons I verified: (1) runtime.svelte.ts:741-753 shows `sequence` ONLY orders `runEmit` vs an optional `handler` — menu-dial passes NO handler to trigger(), so 'pre' vs 'post' is functionally moot here; the morfo `commits` is also inert because eidos does not register the list via `runtime.part(...)`, it drives data-state itself via the Svelte binding. (2) The emit target is `listEl` (fallbackTarget, line 175), the list ELEMENT which persists in the DOM regardless of `data-state` — so the sema `data-event-*` stamp + hold are NOT lost; only the visual `data-state='closed'` flips one tick early. The perceptual signal still fires on a live element. This is a mild ordering smell, not a dropped-signal bug. Downgraded MEDIUM→LOW.
- fix-status: open
### LOW: THEMING (bare numbers in transition/animation) — menu-dial-006 <!-- id: menu-dial-006 -->
- dimension: E-bis
- rule: THEMING (bare numbers in transition/animation)
- location: src/uix/eidos/components/menu-dial/menu-dial.css:25,136,312
- evidence: Line 25: --_menu-dial-stagger-step: 28ms (bare millisecond value); line 136 & 312: fallback 150ms (bare ms in var() fallback); line 283 transition uses var(--duration-normal, 300ms) — fallback hardcoded.
- impact: Bare duration literals (28ms, 150ms, 300ms) are not canonical tokens. Should reference --duration-* scale (e.g., --duration-fast, --duration-normal). Fallbacks in var() are fragile.
- proposed-fix: Define --_menu-dial-stagger-step as a component-scoped alias of a canonical duration token (or expose it in recipes). Replace magic durations: 28ms should be a named token if used for stagger timing; 150ms/300ms should use canonical tokens (--duration-fast, --duration-normal, --duration-slow).
- verify: [downgraded] Split verdict on the cited literals. The `var(--duration-fast, 150ms)` (line 136) and `var(--duration-normal, 300ms)` (line 283) are NOT drift — they reference the canonical tokens (verified `--duration-fast`/`--ease-default`/`--ease-out` are defined in generated/base.css); the ms fallback is dead/redundant noise, and the IDENTICAL pattern appears in sibling onion-menu.css (lines 43, 182) — so flagging it as a defect here while the family uses it is inconsistent. The ONE genuine un-tokenized literal is `--_menu-dial-stagger-step: 28ms` (line 25): unlike onion-menu's `glyph-bar-thickness` it has no recipe token backing it, and it's a real animation-timing magic number that the recipe (absent, see 002) should own. So the finding has a kernel of truth (28ms) wrapped in two false flags (150ms/300ms fallbacks are token-backed). Downgraded MEDIUM→LOW and folded into 002's recipe gap.
- fix-status: open
## No-findings dimensions
C, D, G
## Theming facts (E-bis)
- magic z-index: src/uix/eidos/components/menu-dial/menu-dial.css:67: var(--z-index-sticky, 1100) | src/uix/eidos/components/menu-dial/menu-dial.css:310: calc(var(--z-index-sticky, 1100) - 1)
- magic literals: src/uix/eidos/components/menu-dial/menu-dial.css:25: 28ms (stagger-step) | src/uix/eidos/components/menu-dial/menu-dial.css:136: 150ms (visibility fallback) | src/uix/eidos/components/menu-dial/menu-dial.css:312: 150ms (animation fallback) | src/uix/eidos/components/menu-dial/menu-dial.css:283: 300ms (duration-normal fallback)
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: node (pure nav logic, no vitest-environment annotation)
- covers: dialNavNext wrapping; dialNavPrev wrapping; dialNavFirst/Last; disabled skipping
- untested: keyboard route integration (Enter/Arrow/Escape on trigger/list); focus sync and focus return; open/close perceptual emit timing; dismiss on Tab-out; commit-select lifecycle; aria-label / aria-controls resolution
## Style observations (non-blocking)
- The CSS is well-structured with clear placement zones (9 fixed positions) and arc math (cos/sin layout). The stagger via CSS custom property --_menu-dial-i is elegant. Hover close delay (260ms) is reasonable for crossing the gap. Glyph rotation from + to × is smooth. No redundancy vs other components observed; this design is specific to radial fan layout.
- The arc geometry calculation in CSS (radius := max(r-clear, r-spread)) is mathematically sound and self-contained.

@ -0,0 +1,109 @@
# Audit: menubar
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/soma/components/menubar/menubar-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 4.
systemic hits: SYS-1 (scope-drift: morfo omits 'eidos' but eidos recipe exists); SYS-2 (magic z-index: 1 without token); SYS-3 (jsdom-only tests on interaction-heavy component); SYS-4 (duplicated keyboard index math in two methods).
## Findings
### MEDIUM: SYS-1 scope-drift — menubar-001 <!-- id: menubar-001 -->
- dimension: A (Contract)
- rule: SYS-1 scope-drift
- location: src/uix/morfo/components/menubar.ts:7
- evidence: scope: ['soma', 'sema'], but a full eidos recipe directory exists at src/uix/eidos/components/menubar/ with 8 files (menubar.svelte, menubar.css, menubar-trigger.svelte, menubar-content.svelte, menubar-context.ts, menubar-menu.svelte, types.ts, index.ts)
- impact: Morfo declares scope omits 'eidos' while an eidos recipe dir exists, violating the scope-drift rule SYS-1. The eidos layer exists and provides the full visual contract (menubar.css, size context, data-stagger, data-size on provider).
- repro: Check src/uix/morfo/components/menubar.ts line 7 against presence of src/uix/eidos/components/menubar/ directory.
- proposed-fix: Add 'eidos' to the scope array: scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] morfo line 7 reads `scope: ['soma', 'sema'],` yet a full eidos recipe dir exists at src/uix/eidos/components/menubar/ (menubar.css, menubar.svelte, menubar-content.svelte, menubar-context.ts, types.ts, index.ts). The morfo's OWN comment contradicts the scope: line 8-10 cites the sema pack, and the eidos wrapper stamps data-size + reuses the dropdown recipe. Sibling components with an eidos dir DO list 'eidos' (accordion.ts:7, dropdown-menu.ts:11 both `['soma','sema','eidos']`). This is exactly SYS-1 scope-drift. HIGH is justified: the compiled scope is the contract the validators key on, and it's wrong.
- fix-status: fixed (212624e0)
### MEDIUM: SYS-4 keyboard route duplication + index math duplicated — menubar-004 <!-- id: menubar-004 -->
- dimension: B (Keyboard), G (Redundancy)
- rule: SYS-4 keyboard route duplication + index math duplicated
- location: src/uix/soma/components/menubar/menubar-provider.svelte.ts:131-146 and 283-296
- evidence: navigateAdjacent() method (lines 135-143): if (idx === -1) { next = delta === 1 ? 0 : values.length - 1; } else if (loop) { next = (idx + delta + values.length) % values.length; } else { next = Math.max(0, Math.min(...)); } — IDENTICAL logic in onContentKeydown() (lines 285-291) for nextIdx calculation.
- impact: The index arithmetic for navigating between triggers is duplicated across two code paths (navigateAdjacent called from trigger onkeydown and directly in onContentKeydown). This duplicated logic risks divergence if one path is later updated. Both methods calculate the next index identically, but SYS-4 flags this as a known pattern to extract.
- repro: Compare lines 135-143 (navigateAdjacent index calc) with lines 285-291 (onContentKeydown index calc) — they are textually identical.
- proposed-fix: Extract the index calculation into a private helper method like getNextIndex(currentValue, delta, values, loop) and reuse it in both navigateAdjacent and onContentKeydown.
- verify: [confirmed] Confirmed as duplication, MEDIUM is right. Lines 137-143 (navigateAdjacent): `if (idx === -1) { next = delta === 1 ? 0 : values.length - 1; } else if (this.opts.loop.current) { next = (idx + delta + values.length) % values.length; } else { next = Math.max(0, Math.min(values.length - 1, idx + delta)); }` is textually identical to lines 285-291 in onContentKeydown (same three-branch math on nextIdx). This is the SYS-4 nav-dup pattern. IMPORTANT: the off-by-n-2 half of SYS-4 is NOT present here — the idx===-1 branch lands on `values.length - 1` (the LAST item) for delta -1, which is correct, not n-2. So it's pure extract-worthy duplication with divergence risk, not a behavioral bug. MEDIUM confirmed.
- fix-status: open
### MEDIUM: Test environment: jsdom only, no client/Playwright coverage — menubar-007 <!-- id: menubar-007 -->
- dimension: F (Tests)
- rule: Test environment: jsdom only, no client/Playwright coverage
- location: src/uix/soma/components/menubar/menubar-provider.svelte.test.ts:1
- evidence: // @vitest-environment jsdom — tests run in jsdom (headless) only. 268 lines of test code covering MenubarProvider, but no client (Playwright) variant to verify DOM interaction, focus management, floating positioning, or hover behavior in a real browser.
- impact: SYS-3: jsdom-only tests + interaction-heavy component + keyboard/focus logic. While keyboard routes are tested (ArrowRight, ArrowLeft, Home, End, Enter, Space, ArrowDown), they are mocked event objects in a simplified environment. Focus sync, floating anchor positioning, and real keyboard interaction are untested.
- repro: Test file environment is jsdom; test coverage focuses on state/logic, not DOM/focus/positioning. No client-side tests found.
- proposed-fix: Create a parallel menubar-provider.test.ts with @vitest-environment=node or add menubar-provider.e2e.ts with Playwright to verify: (1) real focus shifts, (2) floating content positioning, (3) hover-follow switching, (4) Escape focus return, (5) RTL directional keys.
- verify: [confirmed] Confirmed. menubar-provider.svelte.test.ts:1 is `// @vitest-environment jsdom`. Events are plain mocked objects (keyEvent/pointerEvent factories lines 84-98 return `{ key, target, preventDefault: vi.fn() }`). Keyboard ROUTES are exercised (ArrowRight/Left, Home/End, ArrowDown open, content-nav switch), which is better than many SYS-3 cases, but real focus movement is only asserted via a `vi.spyOn(dom,'focus')` spy — actual DOM focus, roving-tabindex realization, floating positioning, and Escape focus-RETURN-to-trigger (the queueMicrotask path at provider:258-261) are NOT verified in a browser. Interaction-heavy floating component + jsdom-only + no client/Playwright sibling = SYS-3. MEDIUM appropriate.
- fix-status: open
### LOW: Undeclared ARIA attribute (aria-controls) — menubar-002 <!-- id: menubar-002 -->
- dimension: A (Contract)
- rule: Undeclared ARIA attribute (aria-controls)
- location: src/uix/soma/components/menubar/menubar-provider.svelte.ts:431
- evidence: 'aria-controls': this.isOpen ? this.menu.menu.contentId.current || undefined : undefined, — emitted by soma but not declared in morfo's trigger part aria array
- impact: The trigger part declares aria: [{ attr: 'type', ... }, { attr: 'aria-haspopup', ... }, { attr: 'aria-expanded', ... }] but aria-controls is emitted dynamically by soma without being in the contract. This breaks the two-of-3 rule (morfo ↔ soma/sema/eidos consistency).
- repro: grep 'aria-controls' soma provider; verify it is not in morfo trigger aria array at lines 55-59.
- proposed-fix: Add aria-controls to morfo trigger part: { attr: 'aria-controls', value: v.expr('open ? menuId : undefined') } or similar pattern to declare the dynamic aria-controls as a morfo-driven attribute.
- verify: [downgraded] Confirmed factually: morfo trigger aria array (lines 55-59) declares only `type`, `aria-haspopup`, `aria-expanded` — no `aria-controls`. Provider emits it at line 431: `'aria-controls': this.isOpen ? this.menu.menu.contentId.current || undefined : undefined`. The sibling dropdown-menu morfo DOES declare it (dropdown-menu.ts:90-95 with `condition: { when: 'part-present', part: 'content' }`), so the canonical fix is exactly the proposed one. BUT severity HIGH overstates it: the attribute is functionally correct and present at runtime, ARIA is not broken, and it is conditionally emitted only while open. This is a contract-completeness gap (the morfo should declare what soma emits), not a user-visible a11y break or a divergence risk — LOW/contract-noise tier, not HIGH.
- fix-status: open
### LOW: Magic literal: outline-offset: 1px — menubar-005 <!-- id: menubar-005 -->
- dimension: E-bis (Theming)
- rule: Magic literal: outline-offset: 1px
- location: src/uix/eidos/components/menubar/menubar.css:104
- evidence: [data-menubar-trigger]:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: 1px; ... }
- impact: A bare 1px literal is used for outline-offset instead of a canonical --outline-offset-* or --focus-offset-* token. This violates E-bis rule: spacing/offset values should use canonical token scale --space-* or a purpose-specific --focus-offset-*.
- repro: grep 'outline-offset' menubar.css shows a bare pixel value without a var() reference.
- proposed-fix: Replace outline-offset: 1px with a canonical token like outline-offset: var(--outline-offset-focus) (if defined in the design system) or define one.
- verify: [downgraded] menubar.css:104 `outline-offset: 1px;` is a bare literal — factually correct. But this is a pervasive baseline idiom, not a menubar drift: badge.css:183 uses the identical `outline-offset: 1px;`, and dozens of components use bare `outline-offset: 0/2px`. There is no `--outline-offset-*`/`--focus-offset-*` token in the system for this (the proposed fix invents one). A 1px focus-ring gap is a fixed perceptual constant, not a position on the --space-* rhythm. Real but LOW cosmetic token-nit, not MEDIUM.
- fix-status: open
### LOW: Magic literal: z-index: 1 — menubar-006 <!-- id: menubar-006 -->
- dimension: E-bis (Theming)
- rule: Magic literal: z-index: 1
- location: src/uix/eidos/components/menubar/menubar.css:106
- evidence: [data-menubar-trigger]:focus-visible { ... z-index: 1; } — a bare integer z-index instead of a named token
- impact: SYS-2: bare z-index integer without a canonical --z-index-* token. The value 1 is arbitrary and not tied to the design system's z-index scale (e.g., --z-index-overlay, --z-index-dropdown).
- repro: Line 106 of menubar.css has z-index: 1 without var().
- proposed-fix: Define or reuse a canonical z-index token: z-index: var(--z-index-focus) or similar, ensuring it fits the system's layering strategy.
- verify: [downgraded] menubar.css:106 `z-index: 1;` inside `[data-menubar-trigger]:focus-visible` (with `position: relative`) is a bare integer — factually correct. But this is the standard focus/in-flow stacking idiom (raise the focused sibling within a local stacking context so its outline isn't clipped), used by the POSITIVE REFERENCE tabs.css:281 (`position: relative; z-index: 1;` for triggers over the indicator) and ~19 other components (avatar, button-group, carousel, dialog, combobox, toggle-group, virtual-list...). SYS-2's cited examples are LAYERING-SCALE values (content-z/overlay-z) — e.g. dropdown-menu.css:34 `z-index: var(--dropdown-menu-content-z)` — NOT focus +1. A local +1 is not a position on the global layering scale and has no canonical --z-index-* token. Real literal but LOW, and flagging it MEDIUM/SYS-2 while the reference component does the identical thing is inconsistent.
- fix-status: open
### LOW: 2-of-3 rule: data-menubar-value consumed only by soma trigger, not by sema or eidos — menubar-008 <!-- id: menubar-008 -->
- dimension: A (Contract)
- rule: 2-of-3 rule: data-menubar-value consumed only by soma trigger, not by sema or eidos
- location: src/uix/morfo/components/menubar.ts:53 and src/uix/soma/components/menubar/menubar-provider.svelte.ts:435
- evidence: morfo declares { attr: 'data-menubar-value' } in trigger part data array (no values list, consumed by soma). Eidos does not consume it (not in menubar-trigger.svelte), and no sema reference found.
- impact: The 2-of-3 rule suggests each morfo field should be consumed by >=2 of soma/sema/eidos. data-menubar-value is only used by soma (MenubarTriggerProvider.props emits it). This is a low-priority observation because it may be intentional for internal soma wiring (store the menu value for internal reference), but it's worth confirming the rule intent.
- repro: data-menubar-value is declared in morfo but appears only in soma provider output, not in eidos CSS selectors or sema logic.
- proposed-fix: Verify if data-menubar-value is only a soma-internal marker. If it's consumer-facing, ensure eidos or another layer consumes it for styling or state tracking.
- verify: [confirmed] Confirmed factually and the analyzer correctly rated it LOW. morfo declares `{ attr: 'data-menubar-value' }` in trigger data (menubar.ts:53); provider emits it at menubar-provider.svelte.ts:435 `'data-menubar-value': this.menu.opts.value.current`. grep for data-menubar-value across src/uix/eidos and src/uix/sema returned EMPTY — neither eidos CSS nor the sema pack consumes it (the sema pack keys on `data-menubar`/`data-menubar-trigger` via semaSelector, not the value). So it is consumed by exactly 1 of 3 (soma only). It is a legitimate soma-internal sibling-location marker (cross-menu nav reads the registry, but the attr documents the value on the DOM), so the 2-of-3 carve-out for internal wiring applies. LOW contract-noise, as rated.
- fix-status: open
## No-findings dimensions
C (DOM-selector), D (Frontier), E (TSC)
## Theming facts (E-bis)
- magic z-index: z-index: 1 at menubar.css:106
- magic literals: outline-offset: 1px at menubar.css:104
- undeclared parts: data-size on provider (eidos emits, morfo not declares) | data-stagger on content (eidos emits, morfo not declares) | data-floating-gap on content (eidos emits, morfo not declares)
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: coordinates sibling menus through root value; navigates enabled triggers in DOM order + skips disabled; opens trigger from keyboard without moving focus; switches open menu from content horizontal navigation
- untested: real DOM focus sync; floating anchor positioning; hover-follow behavior in real browser; Escape focus-return in real browser; RTL directional key behavior (ArrowRight/Left swap); submenu escape and cross-menu navigation in real content; typeahead (if implemented); client-side interaction
## Style observations (non-blocking)
- The eidos recipe is well-structured and reuses dropdown-menu parts effectively for per-menu content.
- data-shape-nest concentric-radius pattern (menubar-trigger.svelte:18) is a thoughtful design that avoids hand-rolled calc.
- Hover-follow and cross-menu arrow navigation logic is coherent and well-documented.
- The size cascade via context (MenubarSizeContext) is clean and follows Svelte patterns.
- MenubarMenuProvider acts as a clean adapter layer between menubar state and nested DropdownMenu, which is architecturally sound.

@ -0,0 +1,35 @@
# Audit: meter
audit-version: 1
audited-at: 2026-06-26
scope:
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: G:/dev/svelte/vicen/src/uix/soma/components/meter/meter-provider.svelte.ts
cleanup-audit (A6/A35/A36): MeterProvider: NO timers, listeners, observers, or resources requiring disposal. All state is via $derived (safe, reactive). MeterIndicatorProvider: same - no cleanup needed. A35/A36 loops: NOT POSSIBLE - no $effect usage anywhere, only safe $derived and readableActive helpers. Result: clean lifecyc
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 scope-drift: eidos recipe/dir exists but morfo `scope` omits 'eidos' — meter-SYS1-scope <!-- id: meter-SYS1-scope -->
- dimension: A
- rule: SYS-1 scope-drift: eidos recipe/dir exists but morfo `scope` omits 'eidos'
- location: src/uix/morfo/components/meter.ts:7
- evidence: Morfo declares `scope: ['soma']` (meter.ts:7), yet a full eidos layer materializes the component: a recipe entry `meter: { ... }` at src/uix/eidos/lib/recipes/base.ts:981-1010 (height/radius/ring/track/indicator tokens), and an eidos component dir src/uix/eidos/components/meter/ containing meter.css, meter.svelte (visual wrapper, size+shape), meter-indicator.svelte, types.ts. The scope array does not list 'eidos' despite this eidos materialization.
- impact: Contract metadata drift: the morfo scope no longer reflects which layers consume the component. Tooling/lint that keys off `scope` to know an eidos recipe should exist will under-report. Consistency only — runtime behavior unaffected.
- proposed-fix: Add 'eidos' to the meter morfo `scope` array: `scope: ['soma', 'eidos']`, matching the other B7 components that declare the eidos scope.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: fixed (212624e0)
## No-findings dimensions
B, C, D, E, F, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: meter value attrs and indicator zone projection; default resolution and clamping; labelledby and valueText overrides
- untested: circular shape variant; responsive size changes; edge cases for optimum threshold calculation

@ -0,0 +1,36 @@
# Audit: metrics
audit-version: 1
audited-at: 2026-06-26
scope:
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: G:/dev/svelte/vicen/src/uix/soma/components/metrics/metrics.svelte.ts (runtime-only, no traditional provider)
cleanup-audit (A6/A35/A36): No timers, setInterval, ResizeObserver, IntersectionObserver, MutationObserver, addEventListener, or pointer listeners declared in eidos or soma directories. A35 loop check: metrics-delta's $effect writes to context.setIntent (line 46-48), which updates root's currentIntent state, but no component r
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: F (Tests): a component with a non-trivial soma runtime path (live-region dispatch + value- — metrics-no-runtime-test <!-- id: metrics-no-runtime-test -->
- dimension: F
- rule: F (Tests): a component with a non-trivial soma runtime path (live-region dispatch + value-change effect + intent-publish effect) should have a provider/runtime test mapping the RISK paths. None exists.
- location: src/uix/soma/components/metrics/ (no *.test.ts) — verified via find/grep: zero files reference metricsMorfo/createMetricsRuntime/notifyUpdate in any *.test.ts/*.spec.ts
- evidence: `createMetricsRuntime` (metrics.svelte.ts:17) is exercised through three reactive code paths with no coverage: (1) the live-signal dispatch — metrics.svelte:57-63 `notifyUpdate` → `runtime.trigger('signal-notify-update', { fallbackTarget: el, message: text })`, which routes through `uix.announce` via the morfo's `a11ySemantic.requiresLiveRegion` (metrics.ts:64); (2) the value-change detector — metrics-value.svelte:21-32 (`prev`/`firstRun` memo, the `live`-off no-op path, and the first-run-suppressed path); (3) the intent-publish effect — metrics-delta.svelte:46-48. These are exactly the live-region/effect paths the audit flags as high-risk, yet there is no test asserting the trigger fires only on a real change, stays inert when `live` is off, suppresses the first run, and announces `message`.
- impact: Regressions in the live-update signal (e.g. re-introducing a first-run announce, or firing when value is unchanged, or breaking the `live`-off no-op gate) would ship silently. The change-detection memo logic (metrics-value.svelte:18-32) is subtle and untested.
- repro: find/grep over src for metric*test / metricsMorfo|createMetricsRuntime|notifyUpdate in *.test.ts returns nothing.
- proposed-fix: Add src/uix/soma/components/metrics/metrics.svelte.test.ts (or a runtime test) using a real ActiveUix via createActiveUix/attachActiveUix (never a fake announce per project rule): assert `notifyUpdate` triggers `signal-notify-update` only on a value change when `live` is on, is a no-op when `live` is off, suppresses the first run, and forwards `message` to the live region.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
## No-findings dimensions
A: Contract (morfo scope satisfies, parts declared and present, 2-of-3 rule, part data-* naming correct, aria declarations present), B: Behavior (A35: no per-item effect reading opts.ref.current + writing provider state; A36: no async-microtask-mediated freeze; A33: no $state(Map/Set) non-reactive; A31: no O(N²) derived with global provider reads; A6: no timers/observers/listeners declared; A30: no child->parent id registration in $effect; A15: no gesture; LIVE REGIONS: signal-notify-update correctly dispatched via runtime.trigger with message + announce; A34: announce provider reachable), C: DOM-selector (no interpolated consumer values; global document/window not used), D: Frontier (no soma imports from eidos; no eidos imports from soma except in root to call createMetricsRuntime; data-size/color/variant are visual attrs; Progress/Gauge use framework Meter composition), E: TSC tokens (all spacing via --space-* tokens; all icon-size via --icon-size-*; all color via CSS custom properties; no hardcoded focus-ring or :active), E-bis Theming (Delta composes Badge which uses canonical color roles; featured icon colors use 9 canonical roles; no hardcoded hex; recipe tokens via --metrics-* private scale), F: Tests (no test file present - gap noted but not violation per baseline), G: Redundancy (standard context pattern for live-signal bridge; no gesture re-implementation)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: false · env: N/A
- covers:
- untested: signal-notify-update dispatch when live=true and value changes; intent derivation (trend vs goodTrend); size cascading to sub-parts (Badge, Meter, icon size); aria-label generation on Delta; part composition (Badge, Meter, Sparkline) integration; reduced-motion behavior (if applicable); announce() called with correct priority (polite/assertive)

@ -0,0 +1,61 @@
# Audit: month-grid
audit-version: 1
audited-at: 2026-06-26
scope: soma,sema (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work).
provider: src/uix/soma/components/month-grid/month-grid-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 1.
systemic hits: SYS-1 (scope-drift: eidos exists but not in morfo scope).
composition (A27): N/A (not a picker)
## Findings
### MEDIUM: SCOPE-DRIFT (SYS-1): An eidos recipe dir exists but morfo 'scope' omits 'eidos' — month-grid-002 <!-- id: month-grid-002 -->
- dimension: A
- rule: SCOPE-DRIFT (SYS-1): An eidos recipe dir exists but morfo 'scope' omits 'eidos'
- location: src/uix/morfo/components/month-grid.ts:7 (morfo scope declaration) vs src/uix/eidos/components/month-grid/ (eidos directory exists)
- evidence: Morfo declares `scope: ['soma', 'sema']` but eidos components and CSS exist at src/uix/eidos/components/month-grid/
- impact: Systemic inconsistency. Consumers may not expect eidos layer. Type safety and documentation alignment degraded.
- repro: Observe the eidos directory structure.
- proposed-fix: Update morfo scope to `scope: ['soma', 'sema', 'eidos']`
- verify: [confirmed] Confirmed SYS-1 scope-drift. month-grid.ts:7 declares `scope: ['soma', 'sema']` while a full eidos layer exists: src/uix/eidos/components/month-grid/ contains month-grid.css (recipe-consuming `[data-month-grid]` block at lines 3-7, e.g. `--_month-grid-cell-size: var(--calendar-day-size-md)`), plus 8 .svelte wrappers, types.ts, index.ts and README.md. The morfo omits 'eidos' from scope. This is the known systemic MEDIUM baseline — matches the SYS-1 pattern seen across batch-1/2.
- fix-status: fixed (212624e0)
### LOW: DOM-selector: querySelector/querySelectorAll must not interpolate consumer/state-derived v — month-grid-001 <!-- id: month-grid-001 -->
- dimension: C
- rule: DOM-selector: querySelector/querySelectorAll must not interpolate consumer/state-derived values without CSS.escape()
- location: src/uix/soma/components/month-grid/month-grid-provider.svelte.ts:247-248
- evidence: const el = root.querySelector<HTMLElement>(`[data-month-grid-cell][data-value="${this.placeholderYear}-${nextMonth!}"]`);
- impact: If placeholderYear or nextMonth contained CSS special characters (e.g., backslash, quote, bracket), the selector would break or match unintended elements. While current numeric values are safe, future state mutations or indirect consumer control could introduce injection risk.
- repro: Not exploitable with current numeric-only values, but violates the defensive selector rule.
- proposed-fix: Use CSS.escape(): `const el = root.querySelector<HTMLElement>(`[data-month-grid-cell][data-value="${CSS.escape(this.placeholderYear + '-' + nextMonth!)}"]`);`
- verify: [downgraded] Selector at month-grid-provider.svelte.ts:247-248 reads: `root.querySelector<HTMLElement>(`[data-month-grid-cell][data-value="${this.placeholderYear}-${nextMonth!}"]`)`. The interpolated parts are strictly numeric: `placeholderYear = this.opts.placeholder.current.year` (line 113, `MonthPlaceholder.year: number`) and `nextMonth` is a month clamped to 1..12 by the overflow logic at lines 224-233. Neither can carry CSS metacharacters, so this is NOT user-hittable and NOT CRITICAL — the candidate's own repro field admits 'Not exploitable with current numeric-only values'. It IS a real deviation from the project's own consistent discipline: radio-group:93, tabs:109, listbox:196, grid-list:184, command:394, tree-view:110, tree-grid:162/214 all wrap data-value interpolation in CSS.escape(). But the closest sibling year-grid-provider.svelte.ts:219 does the identical unescaped numeric interpolation (`[data-year-grid-cell][data-value="${nextYear!}"]`), confirming this is an intentional grid-family pattern for numeric coordinates, not an oversight unique to month-grid. Downgrade CRITICAL -> LOW: cosmetic consistency nit, zero behavioral/security impact.
- fix-status: open
## No-findings dimensions
B, 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
- conformance: All CSS tokens use var(--*) references; no hardcoded px/rem/em/%. All 8 of 9 canonical color roles used (primary, secondary, neutral, affirm, fulfill, risk, threat, loss). Sizes xs/sm/md/lg via responsive prop. Variants use ControlVariant. No bare z-index, opacity, or letter-spacing literals. data-size/variant/color are eidos-owned visual attrs, not violations.
## Tests (F)
- exists: false · env: N/A
- covers:
- untested: keyboard navigation (all 10 keys: Arrow×4, Home/End, PageUp/PageDown, Enter/Space); year wrap-around on month overflow; roving focus with tabindex toggle; placeholder year changes; min/max value bounds enforcement; readonly mode; disabled state
## Style observations (non-blocking)
- Excellent keyboard handling: all 10 morfo-declared keys implemented with correct overflow wrapping (months 0→12 of prev year; months 13→1 of next year)
- Roving focus implemented correctly: single tabindex=0 on focused cell, -1 on others
- Event emit sequence correct: state mutation pre-emits (commit-set on value.current change, shift-navigate-step on placeholder change)
- Heading id registration via direct assignment in constructor (A30 compliant, not $effect)
- No composition issues (N/A for month-grid; it's not a picker)
- CSS selector at line 248 uses numeric-safe values but lacks defensive CSS.escape() per rule C
- All 7 declared morfo parts registered via runtime.part(); no orphans
- 2-of-3 rule satisfied: all data-* attrs consumed by both soma and eidos
- No data-soma-*, data-eidos-*, or data-air-* naming violations
- No setTimeout/setInterval/listeners/observers → no A6 leak risk

@ -0,0 +1,19 @@
# Audit: motion
audit-version: 1
audited-at: 2026-06-26
scope: ['eidos'] (eidos-only primitive)
method: theming-coherence category sweep (infra), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: eidos foundation primitive; 'as const satisfies' ✓; passive; no recipe/CSS; uses preset rules.
as-const: true · roles clean: true · variants clean: true · has-recipe: false
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: none
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

@ -0,0 +1,74 @@
# Audit: navigation-menu
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 1 · MEDIUM 3 · LOW 0.
systemic hits: SYS-1 (scope-drift: morfo omits 'eidos' despite eidos recipe); SYS-3 (jsdom-only test, keyboard routes untested); SYS-6 (undeclared eidos parts: data-size, data-depth, data-floating-gap).
## Findings
### HIGH: A6 — navigation-menu-006 <!-- id: navigation-menu-006 -->
- dimension: B
- rule: A6
- location: src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts:523-527
- evidence: `openedAtEffect = $effect.root(() => { $effect(() => { if (this.isOpen) this.lastOpenedAt = Date.now(); }); });` — the disposer RETURNED by `$effect.root` is stored in the `openedAtEffect` field and NEVER invoked. NavigationMenuTriggerProvider has no teardown effect/`$effect(() => () => openedAtEffect())` and no dispose() (grep 'dispose|destroy' in this file: only this one match).
- impact: A6 leak: `$effect.root` deliberately creates a DETACHED, non-auto-disposing reactive root. Each NavigationMenuTriggerProvider instance leaks one root permanently subscribed to `this.isOpen` (which reads provider.opts.value via item.isOpen). Triggers mount/unmount with dynamic nav / route changes; every destroyed trigger leaves a live tracking root — accumulating reactive subscriptions for the page lifetime. Contrast: every other `$effect.root` in the codebase is in test files where the returned cleanup IS called.
- repro: Render a NavigationMenu, dynamically add/remove Trigger items (or navigate between routes that mount different nav menus) — each removed trigger leaves an undisposed $effect.root subscribed to isOpen; reactive roots accumulate without bound.
- proposed-fix: Drop `$effect.root` entirely — the provider constructor already runs in a tracking context (the SAME constructor pattern uses bare `$effect` at lines 99 and 109 of the root provider, which auto-dispose). Replace with a bare `$effect(() => { if (this.isOpen) this.lastOpenedAt = Date.now(); })` in the constructor (or class field run during construction), which auto-cleans on unmount. If `$effect.root` must be retained, wire its disposer into an `$effect(() => () => this.openedAtEffect())` teardown.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: fixed (98954a7c)
### MEDIUM: behavioral — navigation-menu-001 <!-- id: navigation-menu-001 -->
- dimension: C
- rule: behavioral
- location: src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.ts:567
- evidence: const content = this.provider.opts.ref.current?.querySelector<HTMLElement>(`#${this.item.contentId.current}`);
- impact: contentId is user-derived (from optional id prop in types.ts line 139) and not CSS-escaped. Special characters like ':' or '[' can break the selector or inject CSS selector logic.
- repro: Pass id='nav-content:test' or id='nav-content[special]' to NavigationMenuContent; ArrowDown keyboard navigation will fail to find the content.
- proposed-fix: Use CSS.escape(this.item.contentId.current) before interpolating into the selector: `#${CSS.escape(this.item.contentId.current)}`
- verify: [confirmed] Line 567: `querySelector(`#${this.item.contentId.current}`)` — no CSS.escape. contentId derives from Content's `id?: string` consumer prop (types.ts:139 'DOM id. Auto-generated when omitted'). The project's OWN convention escapes id selectors: combobox-provider.svelte.ts:418 does `#${CSS.escape(this.provider.highlightedId)}`, and 7 other providers (tabs/listbox/radio-group/grid-list/command/tree-grid/tree-view) wrap data-value interpolations in CSS.escape. navigation-menu is the lone unescaped `#`-id selector. Real dimension-C fragility; HIGH per 'untrusted/consumer-derived selector' rule. ArrowDown-into-content nav silently fails for an id containing ':' '[' etc.
- fix-status: open
### MEDIUM: SYS-3 — navigation-menu-004 <!-- id: navigation-menu-004 -->
- dimension: F
- rule: SYS-3
- location: src/uix/soma/components/navigation-menu/navigation-menu-provider.svelte.test.ts:1
- evidence: @vitest-environment jsdom; tests cover ArrowDown, ArrowRight, but do not test Home, End, Escape, ArrowLeft, or loop=true wrapping behavior. No client/Playwright test exists.
- impact: Critical keyboard navigation paths (Home/End/loop wrap) are untested in jsdom. Interaction-heavy component with high-risk keyboard behavior only validated in headless environment; edge cases (wrapping, boundary conditions) undetected.
- proposed-fix: Add tests for all keyboard routes (Home → first item, End → last item, Escape when open, ArrowLeft, loop=true wrapping at boundaries). Consider a client test for real DOM focus + browser event dispatch.
- verify: [downgraded] CONFIRMED as a gap but DOWNGRADED HIGH→MEDIUM. Test is jsdom-only (line 1 `@vitest-environment jsdom`), no client/Playwright variant exists (only navigation-menu-provider.svelte.test.ts). It exercises ArrowRight (handleListKeydown→close+focus, line 268) and ArrowDown (trigger→content focus, line 211) + open/close/skip-delay timers, but NOT Home/End/Escape/loop-wrap. This is SYS-3 (interaction-heavy + jsdom-only + kbd partially untested). Severity is MEDIUM per the rubric ('missing test on a high-risk path' = MEDIUM), not HIGH: the untested loop math at provider.svelte:336-345 is actually CORRECT — modulo `(i+1)%n` / `(i-1+n)%n` with a `currentIndex===-1` early-return guard (line 325), so the SYS-4 n-2 off-by branch does NOT exist here. No latent bug behind the missing tests, only coverage debt.
- fix-status: open
### MEDIUM: SYS-1 — navigation-menu-005 <!-- id: navigation-menu-005 -->
- dimension: A
- rule: SYS-1
- location: src/uix/morfo/components/navigation-menu.ts:7
- evidence: scope: ['soma', 'sema'] but src/uix/eidos/components/navigation-menu/ directory exists with CSS and component files (navigation-menu.css, navigation-menu.svelte, etc.)
- impact: Scope drift: morfo declares only soma + sema participation, but eidos has full recipes and visual styling. If a consumer or validator only reads morfo, they won't know eidos is active.
- proposed-fix: Add 'eidos' to morfo scope: scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] CONFIRMED SYS-1 at MEDIUM. navigation-menu.ts:7 `scope: ['soma', 'sema']` omits 'eidos', yet a full eidos recipe dir exists (navigation-menu.css 310 lines + navigation-menu.svelte + types.ts NavigationMenuSize). Systemic: calendar/color-picker/combobox/command/date-picker morfos likewise scope `['soma','sema']` despite having eidos dirs. MEDIUM is the right severity — informational drift between morfo declaration and the active eidos layer, no user-visible/behavioral consequence.
- fix-status: open
## No-findings dimensions
B, G
## Theming facts (E-bis)
- magic z-index: 1 at line 285 (indicator z-index should be --z-index-* or declared per spec)
- magic literals: 0.5rem chevron size (line 135-136) | -1px transform (line 140) | 2px transform (line 146) | -4px scale transform (line 236) | 0.985 scale (line 236) | 1px outline-offset (lines 121, 209) | 12px slide distance (lines 261-270) | 2px block-size indicator (line 288)
- undeclared parts: data-size | data-depth | data-floating-gap
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: timer scheduling; open/close state sync; trigger-content linkage; keyboard focus navigation (ArrowDown, ArrowRight); accessibility props emission
- untested: Home key navigation; End key navigation; Escape key close; ArrowLeft navigation; loop=true wrapping behavior at boundaries; RTL (dir=rtl) keyboard mapping; content focus delegation on Enter; hover-triggered skip-delay window; click-race suppression (lastOpenedAt timing)
## Style observations (non-blocking)
- Responsive size tokens (sm/md/lg) implemented cleanly via CSS custom properties per data-size
- Animation easing (ease-out, ease-default) properly referenced from design tokens
- Chevron indicator rotates smoothly with transition; uses currentColor for theming flexibility
- Content panel z-index hardcoded but references --navigation-menu-content-z (indicates planning for future token)
- Trigger + Link share identical chrome (text color, hover, active states) — intentional design

@ -0,0 +1,37 @@
# Audit: number-field
audit-version: 1
audited-at: 2026-06-26
scope:
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\number-field\number-field-provider.svelte.ts
field-family (A13/A24-26/A30): A13: COMPLIANT - hidden input rendered when name prop exists (line 76-83 in components/number-field.svelte), carries value + name + disabled/required for form submission. A24: NOT APPLICABLE (no readonly-segments with undefined value scenario). A25: NOT APPLICABLE (not a range field). A26: NOT REQUIRED - number-field uses contenteditable-free architecture (native <input type=text> with oninput fil
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 1.
## Findings
### LOW: A6 — every timer/listener/observer disposed; provider with no disposer that still owns a t — NF-1 <!-- id: NF-1 -->
- dimension: B
- rule: A6 — every timer/listener/observer disposed; provider with no disposer that still owns a timer source
- location: src/uix/soma/components/number-field/number-field-provider.svelte.ts:653-666 (IncrementTrigger) and 719-731 (DecrementTrigger), repeater created in constructor; SpinPressRepeater.stop at :98-107
- evidence: The trigger providers create `this.repeater = createSpinPressRepeater(...)` in their constructors and start keyed `uix.timers` (`schedule`/`interval`) on pointerdown. `repeater.stop()` is wired to onpointerup/leave/cancel/lostpointercapture (props at :694-697 / :760-763), but the provider class has no dispose() that calls `repeater.stop()`. If the component unmounts WHILE a press is mid-flight (delay or interval timer pending), nothing cancels the keyed timer; the pending interval would keep invoking `() => this.provider.increment()` until superseded. The interval task does re-check `if (this.isDisabled())` (:85-88) which mitigates but does not unconditionally stop on unmount.
- impact: Tiny-window latent timer survival: requires pointer held on a spin trigger AND simultaneous unmount. Realistically rare; the keyed scheduler is the shared uix.timers (disposed at UIX teardown) and the isDisabled re-check usually halts it. Not a user-visible leak in normal flows.
- repro: Press-and-hold an increment trigger, then unmount the NumberField before releasing the pointer; observe whether the keyed interval task is cancelled.
- proposed-fix: If a per-component disposer becomes available, call `repeater.stop()` from it; otherwise document that the keyed timers rely on the shared scheduler's lifecycle. No change required if the framework guarantees keyed-timer cleanup on dom dispose.
- verify: [verifier-added] added by adversarial verify pass
- fix-status: open
## No-findings dimensions
A Contract(morfo), A30 id-wiring, A13 hidden-input, B Behavior(soma), B Keyboard routes vs APG, B A6 cleanup, C DOM-selector safety, D Frontier(soma/eidos isolation), E Theming tokens, E-bis Label font rule, F Tests coverage
## 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?): CORRECT - field recipe defines label at 1-step-below control font via calc(control-font - 1-step); spin-field references --font-size-* tokens correctly (e.g., font-size-md: var(--font-size-md) per THEMING §5)
## Tests (F)
- exists: true · env: jsdom
- covers: parseValue with locale separators + rounding + clamp on blur; keyboard arrow/page/home/end/enter routing vs APG spinbutton; increment/decrement trigger clicks and repeater hold-delay; scrubber movement buffer and pointer capture; RTL horizontal scrub inversion (E-W mirror); vertical scrub RTL-independence; field inheritance via FieldProvider context; locale resolution (prop → soma.langs.getLocale → en); direction resolution (prop → soma.prefs.getDir → ltr)
- untested:

@ -0,0 +1,82 @@
# Audit: onion-menu
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema', 'eidos']
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified against cited code by the lead.
provider: src/uix/eidos/components/onion-menu/onion-menu.svelte (compositional; soma runtime is src/uix/soma/components/onion-menu/onion-menu.svelte.ts, nav helpers isolated in src/uix/soma/components/onion-menu/onion-menu-nav.ts)
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 3.
systemic hits: SYS-3: interaction-heavy component with keyboard navigation + focus management tested only via unit nav functions; no client test for the eidos root behavior..
## Findings
### MEDIUM: F Tests (Interaction coverage) — onion-menu-004 <!-- id: onion-menu-004 -->
- dimension: F
- rule: F Tests (Interaction coverage)
- location: src/uix/soma/components/onion-menu/
- evidence: Only onion-menu-nav.test.ts exists; no onion-menu-provider.svelte.test.ts or eidos component test.
- impact: Keyboard behavior is tested only on the radial nav pure functions (wrap, disabled skip). The critical interaction paths are untested: open/close trigger (A17 focus return, two-moments), drill navigation (drill-up via Backspace), focus management in SVG (real focus in sectors), outside-click dismissal (non-modal). This is SYS-3: interaction-heavy + jsdom-only + lacks a client test.
- repro: grep -r 'test' src/uix/soma/components/onion-menu/ and src/uix/eidos/components/onion-menu/ — only nav test exists.
- proposed-fix: Create src/uix/soma/components/onion-menu/onion-menu-provider.svelte.test.ts and src/uix/eidos/components/onion-menu/onion-menu.test.ts with at least: (1) trigger open/close + focus return; (2) ArrowRight/Left wrapping in a ring with disabled; (3) drill path transitions (Enter on branch); (4) Backspace drill-up; (5) outside-click close; (6) focus remains/restored through open/close cycle. Use client/Playwright if SVG focus testing requires interaction.
- verify: [downgraded] Confirmed: only onion-menu-nav.test.ts (pure index math), geometry.test.ts and color.test.ts (pure engines) exist; no onion-menu-provider.svelte.test.ts nor an eidos .svelte test. The integration behavior lives entirely in onion-menu.svelte (openMenu/closeMenu focus-return at lines 268-281, drillUp Backspace 315-324, real DOM focus into SVG sectors via sectorEl().focus() 262/363, outside-click pointerdown dismissal 372-381, focusout Tab-out 335-340) and is UNTESTED. This is a real SYS-3 gap. Downgraded HIGH->MEDIUM per rubric: the highest-risk index math (wrap/disabled-skip/degenerate) IS covered by onion-menu-nav.test.ts; the untested surface is focus/dismissal/two-moments integration = 'missing test on a high-risk path' (MEDIUM).
- fix-status: open
### LOW: E-bis Theming (magic literals) — onion-menu-002 <!-- id: onion-menu-002 -->
- dimension: E-bis
- rule: E-bis Theming (magic literals)
- location: src/uix/eidos/components/onion-menu/onion-menu.css:172-173
- evidence: outline: 2px solid var(--focus-ring-color, currentColor);
outline-offset: 2px;
- impact: Hard-coded px values for focus ring width and offset are not derived from canonical tokens. These should reference --z-index-* or --spacing-* tokens so focus ring sizing is consistent across the design system.
- repro: Read onion-menu.css lines 172-173 and check base.ts recipe tokens for onion-menu key — focus ring tokens are missing.
- proposed-fix: Define or reference canonical tokens for focus ring: outline should use var(--focus-ring-width, 2px) and outline-offset should use var(--focus-ring-offset, 2px). Wire these from the recipe base.ts if they don't exist.
- verify: [downgraded] Confirmed the literals exist - onion-menu.css:171-173: 'outline: 2px solid var(--focus-ring-color, currentColor); outline-offset: 2px;'. But this is the framework's CANONICAL focus-ring treatment, not drift: the color-engine hand-off (2026-06-01) establishes 'outline: 2px solid' as the canonical focus + forced-colors fallback ('outline: 2px solid Highlight'), and Button uses the same. The cited rule/fix is wrong - it claims --z-index-*/--spacing-* should size a focus ring, which is nonsensical. At most a cosmetic consistency nit, not a MEDIUM token-drift defect.
- fix-status: open
### LOW: E-bis Theming (bare numbers in recipe) — onion-menu-003 <!-- id: onion-menu-003 -->
- dimension: E-bis
- rule: E-bis Theming (bare numbers in recipe)
- location: src/uix/eidos/lib/recipes/base.ts:4372-4376
- evidence: 'sector-ring-width': '2.5',
'sector-hover-ring-width': '2',
'muted-opacity': '0.85',
'hover-ring-opacity': '0.65'
- impact: Token values are bare numbers without units or as decimal opacity values without a wrapping token reference. Recipe tokens should consistently reference named scales or declare full CSS values.
- repro: Check base.ts lines 4372-4376 and verify the tokens are used correctly in onion-menu.css (e.g., stroke-width: var(--onion-menu-sector-ring-width) should resolve to a valid CSS value).
- proposed-fix: Either fully qualify with units ('2.5px' for ring-width) or wrap opacity in a token lookup. Consider defining canonical --opacity-muted, --opacity-hover-ring as theme tokens that the recipe can then reference. Or emit the values as they are consumed (px for widths, unitless for opacity).
- verify: [downgraded] Mixed claim. The ring-width tokens base.ts:4372-4373 'sector-ring-width':'2.5' / 'sector-hover-ring-width':'2' are consumed as SVG stroke-width: var(--onion-menu-sector-ring-width) (onion-menu.css:53,86,102) - unitless user-space lengths are VALID for SVG stroke-width, NOT a defect; the candidate's 'add px' fix is wrong. The opacity decimals base.ts:4375-4376 'muted-opacity':'0.85' / 'hover-ring-opacity':'0.65' are the only substantive part: a canonical numeric scale --opacity-{0..100} (Tailwind step-5) exists and is widely consumed, so these could reference var(--opacity-85)/var(--opacity-65). Real but minor; the candidate over-broadens and misprescribes.
- fix-status: open
### LOW: C DOM-selector (querySelector safety) — onion-menu-005 <!-- id: onion-menu-005 -->
- dimension: C
- rule: C DOM-selector (querySelector safety)
- location: src/uix/eidos/components/onion-menu/onion-menu.svelte:112-113
- evidence: const ring = surfaceEl?.querySelectorAll<SVGPathElement>(
`[data-onion-menu-item][data-depth="${level + 1}"]`
);
- impact: The interpolation is numeric-only (level + 1 produces a number), so CSS.escape is not needed here. However, the selector construction is fragile: it relies on DOM order matching the index array exactly. If the SVG structure changes (conditional rendering, key changes), the nth-child relationship breaks silently.
- repro: Read onion-menu.svelte line 111-115 and trace how sectors are inserted/removed — order assumption is implicit in the array access at line 115 (ring?.[index]).
- proposed-fix: Consider using a Map<key, element> stored during render instead of querying by index. Alternatively, add a data-sector-id that is both numeric and query-stable (e.g., data-sector-id="${level}-${index}") and query by that instead of relying on order.
- verify: [confirmed] Confirmed at LOW (the candidate's own severity). onion-menu.svelte:112-113: surfaceEl?.querySelectorAll<SVGPathElement>(`[data-onion-menu-item][data-depth="${level + 1}"]`). The interpolated value is numeric (level+1, internal counter), never consumer-derived, so CSS.escape is correctly NOT required - the candidate concedes this. Only the DOM-order reliance (ring?.[index]) is a design note, and lines 107-116 explicitly justify it (avoids bind:this into a computed key + a ref-map timing race). Borderline a styleObservation; no actual injection/fragility defect.
- fix-status: open
## No-findings dimensions
A, B, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: 2px focus ring width (line 172 onion-menu.css) → --onion-menu-focus-ring-width | 2px outline-offset (line 173 onion-menu.css) → --onion-menu-focus-ring-offset | '2.5' sector-ring-width in recipe (line 4372 base.ts) → should be '2.5px' or unitless opacity | '0.85' muted-opacity (line 4375 base.ts) → should wrap in --opacity-* token
- undeclared parts: none
- roles clean: true · variants clean: true
- conformance: Morfo is 'as const satisfies Morfo' ✓; all parts match declared data/aria; naming uses --onion-menu-{slot} for public tokens ✓; no invented theme roles or sizes ✓
## Tests (F)
- exists: true · env: vitest (unit tests only — onion-menu-nav.test.ts has no @vitest-environment directive, defaults to node/jsdom)
- covers: onionNavNext/Prev wrapping; onionNavFirst with disabled leading; disabled option skipping; degenerate cases (empty ring, all-disabled)
- untested: Open/close trigger via Enter/Space/ArrowDown (keyboard); Focus return to trigger on close (A17); Drill into a branch (data-expanded toggle, drill-path mutation); Drill-up via Backspace (drill-path rollback); ArrowLeft/ArrowRight focus movement in the active ring; SVG sector focus() call and roving focus state sync; Outside-click dismissal while open (non-modal); Programmatic open state change (pendingFocus queueing); All perceptual signals (open/close/commit-select) routing correctly; Focus return when Menu is closed without selecting a leaf
## Style observations (non-blocking)
- The component uses theme-driven colour derivation (color-mix) consistently for fills/text, avoiding hardcoded hex/rgb/oklch ✓
- CSS classes use .onion-menu-* prefix throughout; data-* attributes follow [data-onion-menu-*] and [data-onion-menu-{part}] correctly ✓
- The focus-visible ring styling is semantic (uses currentColor) but literals (2px) should be tokens ✓
- Sector animation uses a simple opacity keyframe with @media prefers-reduced-motion guard ✓

@ -0,0 +1,40 @@
# Audit: pagination
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH lead-verified by direct read of the cited code. B5 ground-truth: the A31 O(N²) isSelected/isExpanded (.includes from a per-item $derived) is confirmed across listbox/grid-list/tree-view/tree-grid/tag-group (SYS-7); rovingTargetEl is correctly LIFTED everywhere (not A31); virtual-* use SvelteMap (A33-clean).
provider: src/uix/soma/components/pagination/pagination-provider.svelte.ts
reactivity (A31/A33/A35): CLEAN. Per-item isSelected (line 457-459) uses simple comparison operator, no global state read. Per-item isDisabled (line 461) reads provider.opts.disabled.current (direct ref access, O(1)). Per-item ariaLabel (line 463-468) calls soma.langs.t() but with local item value only. No A31 quadratic patterns detected. No A33 $state(Map/Set). No A35 per-item $effect loops. Clamping effect (lines 116-122
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 SCOPE-DRIFT: eidos recipe dir exists but morfo scope omits 'eidos' — pagination-001 <!-- id: pagination-001 -->
- dimension: Contract (morfo)
- rule: SYS-1 SCOPE-DRIFT: eidos recipe dir exists but morfo scope omits 'eidos'
- location: src/uix/morfo/components/pagination.ts:7
- evidence: Morfo declares scope: ['soma', 'sema'] but eidos directory exists at src/uix/eidos/components/pagination/ with README confirming pagination is 'el wrapper visual de Eidos sobre el primitivo Soma'.
- impact: Scope mismatch signals incomplete layer declaration. Eidos layer is real and should be listed in morfo scope.
- proposed-fix: Update morfo scope from ['soma', 'sema'] to ['soma', 'sema', 'eidos'] to match the existing eidos layer.
- verify: [confirmed] Confirmed SYS-1 scope-drift. src/uix/morfo/components/pagination.ts:7 declares `scope: ['soma', 'sema'],` but a complete eidos layer exists: src/uix/eidos/components/pagination/ contains pagination.css (4255 bytes), 7 Svelte wrappers, index.ts, types.ts, and a README, plus a recipe entry at src/uix/eidos/lib/recipes/base.ts:1715 (`pagination: {`). Peer components with an eidos layer include it in scope (accordion.ts:7 `scope: ['soma', 'sema', 'eidos']`, button.ts:47, checkbox.ts:7). The omission is a real layer-declaration drift. MEDIUM is correct.
- fix-status: fixed (212624e0)
## No-findings dimensions
Contract parts registration (2-of-3 rule verified: all 7 parts registered), Contract data/aria naming (data-{c}-{part} pattern used, no data-soma-* violations), Contract Morfo as const satisfies, Behavior A31 per-item O(N²) derived reading global state, Behavior A33 $state(new Map/Set) reactivity, Behavior A35/A36 per-item $effect ref-write loops, Behavior A30 id-registration via $effect, Behavior A6 cleanup (no Resource leaks detected), Behavior A18/A10/A14/A12 keyboard navigation (N/A: pagination uses snippet-based rendering, not nav), Behavior A34 require() topology (all triggers/items require parent Provider, DOM descendants), DOM-selector (no querySelector usage), Frontier eidos→soma import (correct), Frontier syncAttrs double-write (Provider and Ellipsis only, no conflicting props), Theming roles/tokens (recipe uses --space-*, --font-size-*, --radius-*, --opacity-*, --color-* role references), Tests environment (jsdom with @vitest-environment directive), Tests coverage (page ranges, clamping, navigation, disabled state all tested)
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom (via @vitest-environment directive)
- covers: Page range computation with siblingCount=1, boundaryCount=1; Page clamping when value exceeds totalPages; Array slicing via .slice(); Navigation methods: goToNextPage, goToPrevPage, goToFirstPage, goToLastPage; Provider/trigger/item/ellipsis attrs through runtime; aria-current and aria-label projection; disabled state propagation (provider + item + trigger); onclick handlers for all triggers and items
- untested: Edge case: siblingCount/boundaryCount extremes (e.g., siblingCount > totalPages); Dynamic count/pageSize/siblingCount/boundaryCount changes post-init; onPageChange callback firing on page mutation
## Style observations (non-blocking)
- FirstTrigger and LastTrigger disable when already at boundaries (page 1 / last page), consistent with Ark UI pattern; intentional UX choice not a bug
- Page range algorithm uses Set deduplication before sort, efficient O(n log n) approach
- Comments accurately describe behavior (e.g., line 222 mentions hasPrevPage check is intentional)

@ -0,0 +1,42 @@
# Audit: password-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/password-field/password-field-provider.svelte.ts
field-family (A13/A24-26/A30): A30 compliant: inputId registered directly in constructor (line 142) without $effect. Not A13 field (no hidden input required). Not A26 field (not segmented contenteditable). Not A24/A25 field (not date/range).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SCOPE-DRIFT: Eidos recipe directory exists but morfo 'scope' omits 'eidos' — password-field-001 <!-- id: password-field-001 -->
- dimension: B, SYS-1
- rule: SCOPE-DRIFT: Eidos recipe directory exists but morfo 'scope' omits 'eidos'
- location: src/uix/morfo/components/password-field.ts:31
- evidence: scope: ['soma', 'sema'], — but eidos recipe exists at src/uix/eidos/lib/recipes/base.ts:2097 and components at src/uix/eidos/components/password-field/
- impact: Component maintainers may miss that eidos layer exists; recipe synchronization risk if scope drift persists across the codebase
- proposed-fix: Change line 31 to scope: ['soma', 'sema', 'eidos']
- verify: [confirmed] CONFIRMED at the cited severity. morfo src/uix/morfo/components/password-field.ts:31 declares `scope: ['soma', 'sema'],` — 'eidos' is omitted. Yet eidos demonstrably implements the component: (a) the recipe entry `'password-field': {` exists at src/uix/eidos/lib/recipes/base.ts:2097 (the candidate's cited line is exact), with control-height/space/font tokens following; (b) the eidos component directory src/uix/eidos/components/password-field/ contains 8 files (password-field.svelte, -input, -visibility-trigger, -strength-meter, -caps-lock-indicator, types.ts, index.ts, password-field.css). The `scope` field is documented in src/uix/morfo/types.ts:799 as 'Layers that implement this component', so the declared array genuinely diverges from the real implementation. This is the established SYS-1 scope-drift pattern (MEDIUM in baseline) — informational/metadata drift, not a behavioral or a11y bug: the component renders and works; the `scope` array does not gate eidos at runtime. MEDIUM is the correct severity (not HIGH — no latent behavioral failure; not LOW — it is a real contract/metadata inconsistency the coverage tooling tracks). Note: this is systemic, not unique to password-field — calendar (calendar.ts:7), color-field (color-field.ts:7), color-picker, combobox, command and other components likewise carry `['soma', 'sema']` while shipping eidos directories, consistent with SYS-1 being a confirmed systemic finding.
- fix-status: fixed (212624e0)
## No-findings dimensions
A Contract (Morfo), A Behavior (Soma), C DOM-selector, D Frontier, E TSC, E-bis Theming, F Tests
## Theming facts (E-bis)
- magic z-index: none
- magic literals: z-index: 1 (line 74 password-field.css) — LOCAL subtree stacking only, acceptable per rules
- undeclared parts: none
- roles clean: true · variants clean: true
- label-font (one step below input?): N/A — password-field is composable (no built-in label). Consumers use Field.Label wrapper whose font is controlled by field-level rules.
## Tests (F)
- exists: false · env: jsdom
- covers:
- untested: visibility toggle keydown/click; caps-lock signal on/off lifecycle (stateBound persistence); strength meter clamping and warnings flow; field integration (inputId wiring, disabled/readonly/required inheritance); validation paths (invalid state + error id describedby wiring)
## Style observations (non-blocking)
- Line 74 password-field.css: z-index: 1 is LOCAL subtree stacking (children above pseudo-element chrome), compliant per rules
- Theming: 9 roles used correctly (primary/secondary/neutral/affirm/fulfill/risk/threat/loss); caps-indicator uses risk-track/risk-text role alias
- Recipe tokens (font-size, spacing, radius) reference canonical CSS variables; no magic pixels/em/% drift
- Label font rule N/A: password-field has no internal label; consumer wraps with Field.Label (font controlled at field level)

@ -0,0 +1,52 @@
# Audit: picker-shell
audit-version: 1
audited-at: 2026-06-26
scope: INTERNAL morfo (morfo/internal/picker-shell.ts) (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work).
provider: src/uix/soma/components/picker-shell/components/picker-shell.svelte
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 1.
systemic hits: SYS-1 scope-drift (known pattern per audit baseline).
composition (A27): Re-export pattern (A27 verified): Clear/Cancel/Close compose Button (proper wrapper). Footer/Header/Body are pure layout zones. Picker-shell parts are imported and re-exported under DatePicker/ColorPicker/etc namespaces, not re-implemented. Shared-ref wiring: pickerShellContext set by host picker provider (DatePickerProvider.pickerShellHandle), read by Footer/Clear/Cancel/Close via getPickerShellHandle().
## Findings
### MEDIUM: SYS-1 SCOPE-DRIFT — picker-shell-001 <!-- id: picker-shell-001 -->
- dimension: A
- rule: SYS-1 SCOPE-DRIFT
- location: src/uix/morfo/internal/picker-shell.ts:32 (morfo scope declaration)
- evidence: Morfo declares `scope: ['soma']` at line 32, but eidos directory exists at `src/uix/eidos/components/picker-shell/` with 7 visual components: picker-shell.svelte, picker-shell-header.svelte, picker-shell-body.svelte, picker-shell-footer.svelte, picker-shell-clear.svelte, picker-shell-cancel.svelte, picker-shell-close.svelte. Comment at line 23 explicitly states 'Scope is `soma` only' but implementation contradicts.
- impact: Morfo contract mismatch: scope declaration does not reflect actual layer composition. Audit tools scanning morfo/components/ correctly skip this (internal placement), but if ever moved to public, scope would need updating.
- repro: Read src/uix/morfo/internal/picker-shell.ts line 32 and compare to ls src/uix/eidos/components/picker-shell/ — 7 files present.
- proposed-fix: Update morfo scope from `scope: ['soma']` to `scope: ['soma', 'eidos']` to match the architecture. Alternatively, if intentionally minimizing the contract surface, document that eidos-layer parts are NOT part of the public morfo (correct per current design) and clarify in the comment.
- verify: [downgraded] Facts confirmed but severity overstated. Read src/uix/morfo/internal/picker-shell.ts:32 `scope: ['soma'],` and the eidos dir DOES exist (ls showed picker-shell.svelte/-header/-body/-footer/-clear/-cancel/-close + picker-shell.css + recipe usage). HOWEVER this is NOT undocumented SYS-1 drift: picker-shell.ts:23-25 explicitly states `Scope is \`soma\` only — no semantic events; the host picker emits commit-*/shift-* on its own provider. No \`expression\` field because there is no morfo-emitted event to express.` The README (lines 15-19) and audit-codex P1 #5 closure (line 51) document picker-shell as an INTERNAL primitive whose morfo is deliberately a single Provider stub. The eidos divs (`data-picker-shell`, `data-picker-footer`) are standalone layout containers, NOT morfo-declared parts — so there is no contract<->visual part mismatch, which is what SYS-1/2-of-3 actually polices. The candidate's own proposedFix concedes 'correct per current design'. Real, intentional, documented => LOW doc/observation, not MEDIUM scope-drift. SYS-1 baseline is for components with an UNINTENDED scope omission, not a documented internal contract-surface minimization.
- fix-status: fixed (212624e0)
### LOW: Unused emitted data attribute — picker-shell-002 <!-- id: picker-shell-002 -->
- dimension: E-bis
- rule: Unused emitted data attribute
- location: src/uix/eidos/components/picker-shell/picker-shell.svelte:19 and picker-shell-footer.svelte:15
- evidence: Both picker-shell.svelte and picker-shell-footer.svelte emit `data-mode={mode}` attribute (`<div data-picker-shell data-mode={mode} ...>` and `<div data-picker-footer data-mode={mode} ...>`) but this attribute is never consumed — not referenced in picker-shell.css, not grepped in any CSS, not used by sema or other components.
- impact: Dead attribute emitted to DOM; adds noise without utility. Low impact cosmetic issue.
- repro: grep -n 'data-mode' src/uix/eidos/components/picker-shell/*.svelte src/uix/eidos/components/picker-shell/*.css — only .svelte files match, no .css usage found.
- proposed-fix: Either remove `data-mode={mode}` from both components if unused, OR add CSS rules consuming it (e.g., `[data-picker-footer][data-mode='modal']`) if modal-specific styling is intended.
- verify: [confirmed] Confirmed. picker-shell.svelte:19 `<div data-picker-shell data-mode={mode} {...rest}>` and picker-shell-footer.svelte:15 `<div data-picker-footer data-mode={mode} {...rest}>` both emit data-mode, where `mode = $derived(shell?.getMode() ?? 'inline')`. grep for 'mode' in picker-shell.css => 'No matches found'; grep 'data-mode' across all of src/uix => only the two emit sites; no sema file references getMode/data-mode. The comment at picker-shell.svelte:10-12 ('reads `mode` off `pickerShellContext` ... so the CSS can react to inline vs modal') states an intent the CSS never fulfills — the attribute is inert/dead today. getMode() is a real interface method (picker-shell-handle.svelte.ts:21), so it does not crash; purely a dead attribute / unfulfilled-comment-intent. LOW cosmetic is correct.
- fix-status: open
## No-findings dimensions
B, C, D, E, F, G
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: Header | Body | Footer | Clear | Cancel | Close
- roles clean: true · variants clean: true
- conformance: Internal morfo with minimal contract (1 part: Provider). Eidos-layer parts (Header/Body/Footer/Clear/Cancel/Close) are NOT declared in morfo but ARE properly exported and composed. This is by design per the INTERNAL status. All tokens follow canonical naming (--space-*, --color-*, --font-*, --border-width). No magic hex/rgb/hsl/z-index. No double-write or syncAttrs issues. A27 composition verified: parts are re-exported by 5 picker families, not re-implemented.
## Tests (F)
- exists: false · env: N/A
- covers:
- untested: Context registration flow; pickerShellHandle contract across inline/modal modes; Clear/Cancel/Close button click handlers; Visual context size propagation; Popover content scoping by picker-size
## Style observations (non-blocking)
- CSS is clean, readable, well-commented. Uses custom properties for spacing/sizing uniformly. Clear separation of layout concerns (gap, alignment, borders) vs component-owned styling (Button recipe owns button chrome). Selector specificity is appropriate for scoped composition (`[data-popover-content][data-picker-size='X']` cascade).

@ -0,0 +1,36 @@
# Audit: pin-input
audit-version: 1
audited-at: 2026-06-26
scope:
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/pin-input/pin-input-provider.svelte.ts
field-family (A13/A24-26/A30): A13-PASS: Hidden input rendered at line 92 of src/uix/soma/components/pin-input/components/pin-input.svelte with name attribute propagated from opts.name (line 366 of provider). A30-PASS: inputId registered via direct assignment in PinInputProvider constructor (line 124 of pin-input-provider.svelte.ts), NOT $effect, matching the Field.inputId convention. Test confirms at line 136 of pin-input-prov
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: THEMING §5: Recipe font-weight tokens MUST reference --font-weight-* variables, not litera — pin-input-001 <!-- id: pin-input-001 -->
- dimension: E-bis: Theming
- rule: THEMING §5: Recipe font-weight tokens MUST reference --font-weight-* variables, not literal numeric values
- location: src/uix/eidos/lib/recipes/base.ts:4190
- evidence: 'cell-font-weight': '500' should be 'var(--font-weight-medium)'
- impact: Decouples the pin-input cell font weight from the canonical type scale, breaking theming coherence if --font-weight-medium is ever changed
- proposed-fix: Change line 4190 from "'cell-font-weight': '500'," to "'cell-font-weight': 'var(--font-weight-medium)',"
- verify: [confirmed] Confirmed at src/uix/eidos/lib/recipes/base.ts:4190: the line is exactly "'cell-font-weight': '500'," — a numeric literal. The canonical token exists (generated/base.css:341: "--font-weight-medium: 500;"), so 500 maps 1:1 to --font-weight-medium and should consume the value-preserving token. This is genuine drift, not a physically-fixed value: a grep across the entire base.ts recipe file shows EVERY other font-weight declaration uses var(--font-weight-*) (e.g. lines 48, 153, 314, 1453-1457, 1669, 1686, 3670-3674, 4196 contexts), and a regex for numeric-literal font-weights ('font-weight':'<digit>) matches ONLY line 4190 — it is the sole offender in the whole file. THEMING §5 / 'no magic literals — tokenize don't intentionalize' (feedback_no_intentional_magic_numbers): a literal that equals a scale step must reference the token. MEDIUM (token inconsistency / magic literal) and high confidence are both appropriate.
- fix-status: open
## No-findings dimensions
A: Contract (morfo), B: Behavior (soma), C: DOM-selector, D: Frontier (soma/eidos boundary), E: TSC, F: Tests, G: Redundancy
## Theming facts (E-bis)
- magic z-index: none
- magic literals: pin-input recipe line 4190: literal '500' for cell-font-weight
- undeclared parts: none
- roles clean: true · variants clean: true
- label-font (one step below input?): N/A (no visible label component in morfo; soma uses aria-label on hidden input)
## Tests (F)
- exists: true · env: jsdom
- covers: onComplete callback firing once when value reaches length; pattern filtering on oninput; paste transformation and distribution; selection tracking and cell state derivation; focus/blur lifecycle; Field flag OR-merging (disabled, readonly, required, invalid)
- untested: IME/drop edge cases (note: keydown.preventDefault alone is insufficient per A26 — but this is not a segmented input so A26 does not apply)

@ -0,0 +1,95 @@
# Audit: popover
audit-version: 1
audited-at: 2026-06-26
scope: ['soma', 'sema'] (eidos recipe present though 'eidos' not in morfo scope — systemic SCOPE-DRIFT)
files:
- provider: src/uix/soma/components/popover/popover-provider.svelte.ts (present)
- morfo: src/uix/morfo/components/popover.ts (present)
- recipe: src/uix/eidos/components/popover/popover.css + lib/recipes/base.ts §popover (present)
- demo: web/routes/uix/components/popover (present, not inspected this pass)
- test: src/uix/soma/components/popover/popover-provider.svelte.test.ts (present, jsdom)
- readme: present (not inspected this pass)
## Summary
The cleanest of the three overlays audited so far. Popover gets the hard parts right: correct two-moments
(`present`/`close` sequence 'pre', emit-before-state), modal-derived FocusScope/ScrollLock (A16), the
hover-bounce guards (re-fire-while-open no-op; modal+hover loop guard) that match the recorded popover fixes,
dismissal trigger-exclusion via `contains()` (no `querySelector` injection), timer cleanup (A6), a clean
eidos→soma frontier, NO `role` double-write (it lets the morfo own role), and a Close that composes `<Button>`
with no bespoke recipe. Only real defect: the Close button re-declares `type`/`aria-label` that `syncAttrs`
already writes — the same double-write anti-pattern the component's own Trigger comment warns against. Plus the
systemic magic z-index and a jsdom-only test gap on the dismissal/focus paths.
Counts: CRITICAL 0 · HIGH 0 · MEDIUM 3 · LOW 0.
## Findings
### MEDIUM: Close button re-declares `type` + `aria-label` already written by `syncAttrs` (double-write) <!-- id: popover-001 -->
- dimension: D, A
- rule: active_architecture §7.7 (one authority per attribute) + the component's own discipline (trigger comment :423-426)
- location: popover-provider.svelte.ts:739-758 (Close registered `syncAttrs: true`, then props sets `type` + `aria-label`)
- evidence:
```ts
this.runtimePart = this.provider.runtime.part('close', { …, syncAttrs: true });
…
readonly props = $derived.by(() => ({
...this.runtimePart.props,
type: 'button' as const,
'aria-label': this.provider.soma.langs.ts(POPOVER_LANGS.CLOSE),
onclick: this.onclick
}));
```
The morfo Close declares `type` (literal 'button') and `aria-label` (`v.commonRef('buttons.close')`,
morfo:209-216); with `syncAttrs: true` the runtime writes both. The props block writes them again. The
Trigger comment a few hundred lines up explicitly says "Spreading them manually here would duplicate (and
potentially desync) the runtime's authority" — the Close violates exactly that.
- impact: benign today (both resolve to the same value), but it's duplicate authority + desync risk if
`POPOVER_LANGS.CLOSE` and the morfo `commonRef` ever diverge, and an internal inconsistency in the
component's own discipline. (Contrast Dialog where the analogous double-write IS a live bug — dialog-001.)
- proposed-fix: drop `type`/`aria-label` from the Close props; let `syncAttrs` own them (the morfo already
declares both). Keep only `onclick`.
- fix-status: open
### MEDIUM: `content-z: '75'` / `overlay-z: '60'` magic z-index literals <!-- id: popover-002 -->
- dimension: E-bis
- rule: THEMING §35 Bloque C (z-index magic → `--z-index-*` tokens)
- location: lib/recipes/base.ts:1753-1754 (`'content-z': '75'`, `'overlay-z': '60'`); popover.css:53 + :63.
- evidence: bare integers where `var(--z-index-*)` tokens belong. Third instance after `select.content-z: '80'`
and `dialog.overlay-z: '70'` → confirms the SYSTEMIC z-ladder finding (see THEMING_COHERENCE / SUMMARY).
- impact: the overlay stacking ladder lives as scattered per-recipe integers (60/70/75/80…) that can't be
coordinated or reasoned about centrally.
- proposed-fix: introduce a canonical `--z-index-{overlay,popover,modal}` ladder and map all overlay recipes
to it in one sweep.
- fix-status: open
### MEDIUM: Tests cover toggle/hover/labelling; dismissal trigger-exclusion, focus, and polymorphic close causes untested; jsdom-only <!-- id: popover-003 -->
- dimension: F
- rule: dimension-F honesty check
- location: popover-provider.svelte.test.ts (3 tests: toggle, hover open/close via timers + emit, title/description labelling)
- evidence: no test exercises the `onInteractOutside` trigger-exclusion bounce guard (:559-568, a documented
past bug), the Escape path, FocusScope trap+return (jsdom can't), or the `save`/`fail`/`cancel` close causes
(only `dismiss` is hit, via hover-close). `@vitest-environment jsdom` (:1).
- impact: the bounce-guard regression and focus behavior would ship unverified.
- proposed-fix: add a provider test that fires an interact-outside landing on the trigger (asserts no double
toggle), parametrize the close causes, and add a client/Playwright test for focus trap+return.
- fix-status: open
## No-findings dimensions
- **B (behavior):** two-moments ordering correct; hover bounce guards present and correct (re-fire-while-open
no-op :284, modal+hover loop guard :405/:419); FocusScope/ScrollLock derive from `modal` (A16); dismissal
trigger-exclusion via `contains()`; `hoverTimer` cleaned in `$effect` return (A6). Clean.
- **C (selectors):** uses `contains(triggerNode, target)` — no `querySelector` with interpolated user values. Clean.
- **D (frontier, severe half):** provider imports only soma layers + morfo — zero eidos imports.
- **A (contract):** the virtual Provider part declares empty `data` (no unreachable-data issue, unlike Dialog);
Trigger correctly lets the morfo own aria/type/data-state via `syncAttrs` (no override, unlike Dialog's role).
All CSS-targeted parts (`title`/`description`/`arrow`/`overlay`) are morfo-declared (no undeclared-part drift,
unlike Select/Dialog).
- **E (TSC):** no `data-color`, no derived-token-at-root risk; clean.
## Style observations (opinion, non-blocking)
- Popover is the positive reference among the overlays for: (a) Close-via-`<Button>` with no bespoke recipe
(the model Dialog's stale trigger envelope should follow), (b) letting the morfo own Trigger ARIA without a
manual override (the model Dialog's role override violates), and (c) the documented hover-bounce guards.
Worth citing in the SUMMARY as the "do it like Popover" baseline.
- The one inconsistency (Close double-write, popover-001) is small but notable precisely because the same
file's Trigger comment articulates the correct rule — fixing it makes the file self-consistent.

@ -0,0 +1,35 @@
# Audit: progress
audit-version: 1
audited-at: 2026-06-26
scope: (SCOPE-DRIFT → SYS-1)
method: adversarially-verified workflow; HIGH lead-verified. B7 ground-truth: Toolbar A35 + Form A36 incidents STAYED FIXED (untrack present); toast/clipboard/drag-drop use uix.timers (no A6 leak); button is clean (its archetype finding was inert — Spinner not runtime-registered); data-size/data-shape are eidos visual attrs (not contract violations).
provider: G:\dev\svelte\vicen\src\uix\soma\components\progress\progress-provider.svelte.ts
cleanup-audit (A6/A35/A36): A6 cleanup audit: Zero timers, observers, listeners. No setTimeout, setInterval, ResizeObserver, IntersectionObserver, MutationObserver, addEventListener in soma/components/progress. Test uses $effect.root() with proper disposer cleanup (line 21-24 captured, line 126 cleanup called, line 127 dom.dis
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 0.
## Findings
### MEDIUM: SYS-1 scope-drift: morfo scope[] must include 'eidos' when an eidos recipe + component dir — PROGRESS-SCOPE-1 <!-- id: PROGRESS-SCOPE-1 -->
- dimension: A
- rule: SYS-1 scope-drift: morfo scope[] must include 'eidos' when an eidos recipe + component dir exist for the component.
- location: src/uix/morfo/components/progress.ts:7
- evidence: `scope: ['soma'],` — yet a full eidos recipe exists at src/uix/eidos/lib/recipes/base.ts:1799 (`progress: { 'height-xs': ... }`) plus a complete eidos component dir (src/uix/eidos/components/progress/{progress.svelte,progress.css,types.ts}). Compare button.ts:`scope: ['soma','sema','eidos']`, spinner.ts/skeleton.ts:`scope: ['eidos']` — all eidos-backed components declare 'eidos'.
- impact: The morfo contract under-declares the component's real consumer layers; any tooling that walks `scope` to know which layers materialize a component (lint/coverage/codegen) will skip eidos for Progress even though eidos owns its size/shape visual surface. Same drift class as meter.ts (also `['soma']`), so it is a shared B7-family systemic issue, not a one-off.
- proposed-fix: Add 'eidos' to the scope tuple: `scope: ['soma', 'eidos']`. (No 'sema' — Progress declares no events, expression is none/passive, which is correct.)
- verify: [verifier-added] added by adversarial verify pass
- fix-status: fixed (212624e0)
## No-findings dimensions
A_contract, B_behavior, C_dom_selector, D_frontier, E_theming, F_tests, G_redundancy
## Theming facts (E-bis)
- magic z-index: none
- magic literals: none
- undeclared parts: none
- roles clean: true · variants clean: true
## Tests (F)
- exists: true · env: jsdom
- covers: Provider state transitions (indeterminate, loading, loaded); Value/min/max/orientation ARIA/data attributes; Label id registration and aria-labelledby wiring; ValueText snippet props and default text generation; Indicator state inheritance; Vertical orientation support
- untested:

@ -0,0 +1,19 @@
# Audit: qr-code
audit-version: 1
audited-at: 2026-06-26
scope: eidos (eidos-only primitive)
method: theming-coherence category sweep (visual-elements), adversarially verified; roles + `:Morfo` lead-verified CLEAN across all eidos (no success/warning/danger/info; only alert-dialog uses `:Morfo`).
## Summary
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 0 · LOW 0. Theming-clean.
conformance: Contract solid (as const), display-only (no events), cell-shape variant (square/rounded/dots), fixed-tone by design for QR scannability, no drift.
as-const: true · roles clean: true · variants clean: true · has-recipe: true
## Findings
_No findings (theming-clean primitive)._
## Theming facts (E-bis)
- magic literals: #18181b @ qr-code recipe:1948 -> fg (INTENTIONAL fixed-tone for scanability) | #ffffff @ qr-code recipe:1949 -> bg (INTENTIONAL fixed-tone for scanability) | #ffffff @ qr-code recipe:1958 -> overlay-bg (INTENTIONAL fixed-tone per quiet zone)
- magic z-index: none
- non-canonical names: none
- roles clean: true · variants clean: true

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save

Powered by TurnKey Linux.