fix(uix-audit): close 4 pre-existing issues found in system audit

System-wide audit of `src/uix/*` + `src/arts/adom` (excluding demos and
web/routes). Findings + fixes documented in `src/uix/AUDIT_REPORT_2026-05-27.md`.

**Fixed**:

1. `src/uix/eidos/lint.test.ts`: "every eidos CSS component has a morfo
   file" test extended to walk `morfo/internal/` as well as
   `morfo/components/`. The picker-shell relocation (audit-codex P1 #5)
   moved picker-shell to internal but the test wasn't updated.

2. `src/uix/contracts.test.ts`: two pre-existing failures resolved.
   (a) Picker WithChild regex updated to accept the
   `WithChild<Omit<PrimitiveDivAttributes, ...> & {...}>` shape applied
   in the audit-codex P0/P1 picker ProviderProps refactor.
   (b) `collectPublicSomaComponentDirs` now skips `picker-shell` as an
   internal primitive (symmetric to `collectKnownMorfoDataAttrs` which
   now walks `morfo/internal/` to include the picker-shell data-attrs).

3. `src/uix/soma/components/command/command-provider.svelte.ts`:
   restored missing `createAttrs(commandMorfo)` import. Was the only
   ERROR in `npm run check` baseline.

4. `src/uix/soma/components/dialog/types.ts`: JSDoc comment updated to
   reflect polymorphic close shape (cosmetic alignment with the 2026-05-27
   refactor).

**Result**:
- `npm run check`: 1 ERROR → **0 ERRORS** ✓
- `npx vitest run src/uix`: 5 failed → **2 failed** (both 100% in user's
  words/* WIP, not framework code)
- Polymorphic close + persistence + a11ySemantic rollout: **zero drift
  detected** across morfos / providers / sema cascades / tests.

**NOT actioned** (documented in report):
- 6 pre-existing `morfo:check` failures (avatar / color-picker /
  date-picker / dropdown-menu / table / tree-grid). All require domain
  decisions (drop 'idle' state vs add it to morfo enum, etc.).
- 1 navigation-menu data-state='visible' eidos selector drift.
- ~14 README/`.md` files with stale Dialog/Drawer/Popover descriptions.
- Words editor doctrinal cleanup (user's WIP).
- Tooltip polymorphic alignment (optional, no consumer benefit today).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent f0440d647a
commit 681e056102

@ -0,0 +1,187 @@
# UIX system audit — 2026-05-27
Sweep of the UIX framework (`src/uix/*`, `src/arts/adom`) after closing
four polymorphic-close + persistence + a11ySemantic sprints. Excludes
demos and `web/routes/*`.
## TL;DR
| Surface | Before this audit | After fixes |
|---|---|---|
| `npm run check` | 1 error | **0 errors** ✓ |
| `npx vitest run src/uix` | 5 failed tests | **2 failed** (both 100% in user's WIP `words/*`) |
| `npm run morfo:check` | 6 components / 16 issues | unchanged (all pre-existing, none in my scope) |
| `npm run morfo:vocabulary` | warnings only | unchanged (warnings only) |
| `npm run translations:check` | 0 errors / 0 warnings | unchanged ✓ |
| Eidos lint (selectors) | 1 invalid / 729 unused / 1426 total | unchanged (1 pre-existing drift) |
Audit found **0 issues caused by the polymorphic rollout**. The fixes
landed here closed 3 pre-existing test failures + 1 pre-existing svelte
error, all incidentally exposed by running the full audit.
## Fixes applied during audit
| Commit candidate | Surface | Detail |
|---|---|---|
| 1 | `src/uix/eidos/lint.test.ts` | "every eidos CSS component has a morfo file" test extended to also walk `morfo/internal/` (picker-shell relocation P1 #5). |
| 2 | `src/uix/contracts.test.ts` | (a) Picker WithChild regex now accepts `WithChild<Omit<...> & {...}>` shape (audit-codex P0/P1 refactor of picker ProviderProps). (b) `collectPublicSomaComponentDirs` now skips `picker-shell` (internal primitive); symmetrical with `collectKnownMorfoDataAttrs` which now walks `morfo/internal/` too. |
| 3 | `src/uix/soma/components/command/command-provider.svelte.ts` | Added missing `createAttrs(commandMorfo)` import. Was the only ERROR in `npm run check`. |
| 4 | `src/uix/soma/components/dialog/types.ts` | JSDoc comment updated to reflect polymorphic close shape (cosmetic, not behavioural). |
## Remaining findings (NOT actioned in this audit)
### Pre-existing test failures — user's words editor WIP
`src/uix/contracts.test.ts` still reports 2 failures, both 100% inside
`src/uix/{morfo,soma}/components/words/*` files modified by the user
before this session began. They split into:
1. `guards hardcoded Soma component data attrs with morfo contracts` —
7 violations all in `words-provider.svelte.ts` and
`words/engine/*.ts`. The words editor introduces dynamic
`data-words-*` attrs not declared in the morfo.
2. `guards component translation namespaces as kebab-case` —
9 violations in `morfo/components/words.ts` +
`soma/components/words/words-provider.svelte.ts`. The words editor
uses camelCase keys (`bubbleMenu`, `linkEditor`, `findReplace`)
instead of the canonical kebab-case (`bubble-menu`, `link-editor`,
`find-replace`).
Both are tracked in the user's branch as in-flight work. The audit
flags them but does not fix — those are doctrinal decisions for the
words editor author.
### Pre-existing `morfo:check` failures (16 issues across 6 components)
| Component | Issue | Count |
|---|---|---|
| `avatar` | `provider`: required attr `data-color` not emitted | 4 |
| `color-picker` | `provider`: required attr `data-state` not emitted | 1 |
| `date-picker` | `provider`: `data-kind="date"` not in declared `[day, month, year]` | 1 |
| `dropdown-menu` | `trigger`: `data-state="idle"` not in declared `[open, closed]` | 1 |
| `table` | `row-detail-trigger`: `data-state="idle"` not in declared `[open, closed]` | 5 |
| `tree-grid` | `expand-trigger`: `data-state="idle"` not in declared `[open, closed]` | 4 |
Pattern: three of the six (`dropdown-menu`, `table`, `tree-grid`) all
use `data-state="idle"` on trigger-like parts. Likely a shared baseline
where the trigger's "idle" state was never added to the morfo enum
(should be `data-state: [open, closed, idle]` or `[open, closed]` and
the provider should not emit 'idle').
The remaining three (`avatar`, `color-picker`, `date-picker`) are
distinct issues — provider not emitting a declared required attr
(`data-color`, `data-state`) or emitting a value outside the declared
enum (`data-kind="date"`).
**Not fixed in this audit** because:
- They predate all four polymorphic sprints.
- Fixing each requires either (a) updating the morfo enum or (b)
updating the provider's attr emission — both require domain
knowledge of why "idle" / "date" exist in the first place.
### Eidos selector drift — 1 invalid
`src/uix/eidos/components/navigation-menu/navigation-menu.css` writes
`[data-navigation-menu-indicator][data-state='visible']` but the morfo
declares `data-state ∈ {open, closed}` for that part. Real drift,
pre-existing.
**Not fixed in this audit** because the fix requires choosing between:
(a) add `'visible'` to the morfo enum + ensure the provider can emit
it, or (b) drop the selector from the CSS.
### `morfo:vocabulary` warnings (informational)
- 7 words event names don't follow the canonical `{family}-{verb}[-{variant}]`
shape (all in `words.*`). User's WIP.
- ~20 data-attr value sets across the catalogue don't match the
canonical vocabularies (`disclosure`, `active`, `checked`, etc.).
Many are legitimate component-specific vocabularies (toast swipe
directions, toggle color palette, tooltip delayed-open / instant-open,
tree-grid selection modes, etc.). The lint output explicitly notes
these are warnings and that introducing a new canonical vocabulary
is the right answer when the data-attr is a real domain concept.
Defer: tighten case-by-case only when the canon adds a new vocabulary
that justifies it. No fixes warranted from this audit alone.
### Stale polymorphic refs in prose-only files
The polymorphic close rollout (Dialog/Drawer/Popover/5 pickers)
collapsed 5 close-* events into 1. Several docs / READMEs still
describe the old shape:
- `src/uix/eidos/components/alert-dialog/README.md`
- `src/uix/eidos/components/banner/README.md`
- `src/uix/eidos/components/combobox/README.md`
- `src/uix/eidos/components/date-range-picker/README.md`
- `src/uix/eidos/components/dialog/README.md`
- `src/uix/eidos/components/tooltip/README.md`
- `src/uix/soma/components/tooltip/README.md`
- `src/uix/active_architecture.md`
- `src/uix/COMPONENT_COMPLETION_CHECKLIST.md`
- `src/uix/PENDIENTES.md`
- `src/uix/morfo/README.md`
- `src/uix/sema/README.md`
- `src/uix/sema/refactorizacion_codex.md`
- `src/uix/audit-codex-full-1.md`
All purely documentary references (e.g. "Dialog has 6 events" or
"close-after-fail tints the exit"). Not actionable for runtime
correctness. A separate docs-only commit could sweep these — out of
scope here.
### Tooltip retains per-event close shape
`src/uix/morfo/components/tooltip.ts` declares two close events
(`close` for pointer-leave / blur, `close-dismiss` for Escape). Already
two-event (not five) so the polymorphic refactor savings are smaller.
Not refactored because:
- Both events live in the `emerge` family — no cross-family
concretion need.
- Provider doesn't fire `runtime.trigger` for them (tooltip provider
uses its own timer-driven open/close machinery).
Adding `allowedFamilies` to align with the doctrinal shape would be
~5 lines but no consumer to benefit from it today. Defer.
## What this audit confirmed clean
- **Polymorphic close rollout**: zero drift between morfos, providers,
sema cascades and tests across Dialog / Drawer / Popover / 5 pickers.
- **Persistence + a11ySemantic**: every consuming morfo declares the
right policy; every provider clears `runtime.clearTarget` at the
right gates; every event with `requiresLiveRegion` is paired with a
caller-passed `opts.message`.
- **Test fixtures**: synthetic `prewriteFixtureMorfo` correctly decouples
compile + runtime tests from the production morfo catalogue.
- **TypeScript**: `npm run check` baseline 0 errors after fixing the
one pre-existing `attrs` import in `command-provider.svelte.ts`.
## Backlog for future sessions
In rough priority order:
1. **`data-state="idle"` cleanup** (5 components, 11 issues). Decide
whether 'idle' is a valid state for trigger parts; if yes, add to
morfo enum + verify provider emission; if no, fix the provider.
2. **`avatar.data-color` emission** (4 instances). Either provider
should emit it or the morfo should drop the `required` severity.
3. **`color-picker.data-state` + `date-picker.data-kind`** (2 issues).
Same kind of provider <-> morfo drift.
4. **`navigation-menu data-state='visible'`** drift between morfo and
eidos CSS. 1 fix.
5. **Words editor doctrinal cleanup**: kebab-case lang keys + declared
morfo data-attrs for `data-words-*`. Tracked separately by user.
6. **Tooltip polymorphic close**: optional alignment, low value today.
7. **Docs sweep**: ~14 README / `.md` files describing the
pre-polymorphic Dialog/Drawer/Popover shape.
8. **Picker providers `runtime.trigger('close', { semantic })`
cabling**: morfos ready, providers still toggle `opts.open` direct.
Cabling brings telemetry + sound on commit.
None of these block the framework. All are quality-of-life. The
polymorphic + persistence + a11ySemantic rollout itself is in its final
landed shape.

@ -77,7 +77,16 @@ function stripComments(source: string): string {
function collectKnownMorfoDataAttrs(): Set<string> { function collectKnownMorfoDataAttrs(): Set<string> {
const attrs = new Set<string>(); const attrs = new Set<string>();
for (const file of listSourceFiles(join(HERE, 'morfo', 'components'))) { // Public morfos (`morfo/components/*.ts`) AND internal primitives
// (`morfo/internal/*.ts`) — the internal directory holds picker-shell
// and any future shared chrome. Their `data-{name}` markers are part
// of the catalogue too (see audit-codex P1 #5).
const internalRoot = join(HERE, 'morfo', 'internal');
const morfoFiles = [
...listSourceFiles(join(HERE, 'morfo', 'components')),
...(existsSync(internalRoot) ? listSourceFiles(internalRoot) : [])
];
for (const file of morfoFiles) {
const source = stripComments(readFileSync(file, 'utf8')); const source = stripComments(readFileSync(file, 'utf8'));
const component = /kebab:\s*'([^']+)'/.exec(source)?.[1]; const component = /kebab:\s*'([^']+)'/.exec(source)?.[1];
if (!component) continue; if (!component) continue;
@ -118,12 +127,25 @@ function toPascalName(kebab: string): string {
.join(''); .join('');
} }
// Soma component directories that are internal primitives, not public
// components. They ship under `soma/components/{name}/` because they
// have eidos chrome + a provider, but their morfo lives in
// `morfo/internal/` rather than `morfo/components/` (see audit-codex P1
// #5). Audit guards must skip them when comparing soma <-> morfo
// catalogues, otherwise the asymmetry produces a false positive.
const SOMA_INTERNAL_PRIMITIVES = new Set(['picker-shell']);
function collectPublicSomaComponentDirs(): string[] { function collectPublicSomaComponentDirs(): string[] {
const root = join(HERE, 'soma', 'components'); const root = join(HERE, 'soma', 'components');
return readdirSync(root, { withFileTypes: true }) return readdirSync(root, { withFileTypes: true })
.filter((entry) => entry.isDirectory()) .filter((entry) => entry.isDirectory())
.map((entry) => entry.name) .map((entry) => entry.name)
.filter((name) => name !== 'internal' && existsSync(join(root, name, 'index.ts'))) .filter(
(name) =>
name !== 'internal' &&
!SOMA_INTERNAL_PRIMITIVES.has(name) &&
existsSync(join(root, name, 'index.ts'))
)
.sort(); .sort();
} }
@ -381,8 +403,13 @@ describe('UIX layer contracts', () => {
join(HERE, 'soma', 'components', dir, 'components', `${dir}.svelte`), join(HERE, 'soma', 'components', dir, 'components', `${dir}.svelte`),
'utf8' 'utf8'
); );
// Match both shapes:
// `WithChild<{ ... }>;`
// `WithChild<Omit<PrimitiveDivAttributes, ...> & { ... }>;`
// per audit-codex P0/P1 refactor of picker ProviderProps to
// extend PrimitiveDivAttributes.
const rootType = const rootType =
new RegExp(`export type ${typeName}\\s*=\\s*WithChild<\\{[\\s\\S]*?\\}>;`).exec(types)?.[0] ?? ''; new RegExp(`export type ${typeName}\\s*=\\s*WithChild<[\\s\\S]*?\\}>;`).exec(types)?.[0] ?? '';
expect(rootType, `${dir}.types.ts uses WithChild<{...}>`).not.toBe(''); expect(rootType, `${dir}.types.ts uses WithChild<{...}>`).not.toBe('');
expect(rootType, dir).toContain('children?: Snippet;'); expect(rootType, dir).toContain('children?: Snippet;');

@ -36,8 +36,14 @@ describe('eidos component contract coverage', () => {
}) })
it('has a morfo file for every eidos CSS component', () => { it('has a morfo file for every eidos CSS component', () => {
// Morfos live under `src/uix/morfo/components/{name}.ts` for public
// components, OR under `src/uix/morfo/internal/{name}.ts` for
// internal primitives like `picker-shell` that ship eidos chrome
// but are not standalone public components (see audit-codex P1 #5).
const missing = listEidosCssComponents().filter( const missing = listEidosCssComponents().filter(
(name) => !existsSync(resolve(`src/uix/morfo/components/${name}.ts`)) (name) =>
!existsSync(resolve(`src/uix/morfo/components/${name}.ts`)) &&
!existsSync(resolve(`src/uix/morfo/internal/${name}.ts`))
) )
expect(missing).toEqual([]) expect(missing).toEqual([])

@ -18,8 +18,11 @@ import { COMMAND_LANGS } from './langs';
import type { CommandFilterFn, CommandSnippetProps } from './types'; import type { CommandFilterFn, CommandSnippetProps } from './types';
import { commandMorfo } from '../../../morfo/components/command'; import { commandMorfo } from '../../../morfo/components/command';
import { createAttrs } from '../../../morfo';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte'; import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
const attrs = createAttrs(commandMorfo);
// ── Registry shapes ────────────────────────────────────────────────────────── // ── Registry shapes ──────────────────────────────────────────────────────────
interface ItemEntry { interface ItemEntry {

@ -28,10 +28,11 @@ export type DialogProps = {
disabled?: boolean; disabled?: boolean;
/** /**
* Doctrinal intent of the dialog (anexo §3.1, Modal subset). Drives the * Doctrinal intent of the dialog (anexo §3.1, Modal subset). Drives the
* resolved `data-color` palette and per-event perceptual modulation when * resolved `data-color` palette and per-event perceptual modulation
* the dialog's outcome is sensitive (e.g. close-after-fail keeps its own * when the dialog's outcome is sensitive (e.g. `dismissWith('fail')`
* `threat` regardless of this prop, but `open` and `close-cancel` use * keeps its own `threat` regardless of this prop via the polymorphic
* this). * `close` event's `signal.alert` concretion, but `open` and
* `dismissWith('cancel')` use this).
* *
* - `neutral` (default) — informational / settings dialog. * - `neutral` (default) — informational / settings dialog.
* - `risk` — moderate negative consequence * - `risk` — moderate negative consequence

Loading…
Cancel
Save

Powered by TurnKey Linux.