uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
/ * *
* Component completion audit .
*
* Walks every UIX component ( morfo + eidos wrapper + recipe CSS + demo )
* and scores it against the rules in
* ` src/uix/COMPONENT_COMPLETION_CHECKLIST.md ` .
*
* Output : ` tmp/component-audit.md ` — markdown report with summary table
* + per - component findings .
*
* Usage :
* node -- import tsx / esm scripts / component - audit . ts
* node -- import tsx / esm scripts / component - audit . ts -- only rating - group , calendar
* node -- import tsx / esm scripts / component - audit . ts -- severity error
*
* Approach : text / regex parsing of source files . Doesn ' t dynamically import
* morfos ( avoids full TS resolution + circular dep risk ) . The morfo files
* are declarative ` as const satisfies Morfo ` exports , so regex catches
* the structural facts we need . Cross - validation against canonical
* vocabularies via static imports of ` INTENTS ` , ` SEMA_FAMILIES ` ,
* ` SEMA_VERBS ` , ` ARCHETYPE_VOCABULARY ` .
* /
import { existsSync , readFileSync , readdirSync , mkdirSync , writeFileSync } from 'node:fs' ;
import { dirname , join } from 'node:path' ;
import { fileURLToPath } from 'node:url' ;
import { INTENTS } from '../src/uix/intent.ts' ;
import { SEMA_FAMILIES } from '../src/uix/sema/event.ts' ;
import { SEMA_VERBS } from '../src/uix/sema/verbs.ts' ;
import { ARCHETYPE_VOCABULARY } from '../src/uix/morfo/types.ts' ;
// ─── Paths ──────────────────────────────────────────────────────────────────
const __dirname = dirname ( fileURLToPath ( import . meta . url ) ) ;
const REPO = join ( __dirname , '..' ) ;
const MORFO_DIR = join ( REPO , 'src/uix/morfo/components' ) ;
const EIDOS_DIR = join ( REPO , 'src/uix/eidos/components' ) ;
const DEMO_DIR = join ( REPO , 'web/routes/uix/components' ) ;
const OUT = join ( REPO , 'tmp/component-audit.md' ) ;
// ─── Types ──────────────────────────────────────────────────────────────────
type Severity = 'error' | 'warn' | 'info' ;
interface CheckResult {
rule : string ;
severity : Severity ;
passed : boolean ;
detail? : string ;
}
interface ComponentReport {
component : string ;
kebab : string ;
exists : { morfo : boolean ; eidos : boolean ; demo : boolean } ;
interactive : boolean ;
passive : boolean ;
checks : CheckResult [ ] ;
}
// ─── CLI flags ──────────────────────────────────────────────────────────────
const args = process . argv . slice ( 2 ) ;
const onlyArg = args . find ( ( a ) = > a . startsWith ( '--only' ) ) ;
const onlyList = onlyArg
? ( onlyArg . includes ( '=' ) ? onlyArg . split ( '=' ) [ 1 ] : args [ args . indexOf ( onlyArg ) + 1 ] )
? . split ( ',' )
. map ( ( s ) = > s . trim ( ) )
: undefined ;
const severityArg = args . find ( ( a ) = > a . startsWith ( '--severity' ) ) ;
const minSeverity = ( severityArg
? severityArg . includes ( '=' )
? severityArg . split ( '=' ) [ 1 ]
: args [ args . indexOf ( severityArg ) + 1 ]
: undefined ) as Severity | undefined ;
// ─── Helpers ────────────────────────────────────────────────────────────────
function tryRead ( path : string ) : string | null {
try {
return readFileSync ( path , 'utf8' ) ;
} catch {
return null ;
}
}
function pass ( rule : string , severity : Severity , detail? : string ) : CheckResult {
return { rule , severity , passed : true , . . . ( detail ? { detail } : { } ) } ;
}
function fail ( rule : string , severity : Severity , detail : string ) : CheckResult {
return { rule , severity , passed : false , detail } ;
}
// Match `name: '...'` or `name: "..."` inside morfo event blocks.
const EVENT_NAME_RE = /name:\s*['"]([\w-]+)['"]/g ;
// Match family field
const EVENT_FAMILY_RE = /family:\s*['"]([\w-]+)['"]/g ;
// Match verb field
const EVENT_VERB_RE = /verb:\s*['"]([\w-]+)['"]/g ;
// Sequence field
const EVENT_SEQUENCE_RE = /sequence:\s*['"](pre|coincident|post)['"]/g ;
// Part kebab
const PART_KEBAB_RE = /kebab:\s*['"]([\w-]+)['"]/g ;
// Archetype
const PART_ARCH_RE = /archetype:\s*['"]([\w-]+)['"]/g ;
// keyboard keys
const KEYBOARD_KEY_RE = /key:\s*['"]([^'"]+)['"]/g ;
const VALENCED_FAMILIES = new Set ( [ 'commit' , 'signal' ] ) ;
const ALL_VERBS = new Set < string > (
( Object . values ( SEMA_VERBS as Record < string , readonly string [ ] > ) as readonly string [ ] [ ] ) . flat ( )
) ;
const VERBS_BY_FAMILY = SEMA_VERBS as Record < string , readonly string [ ] > ;
const ARCHETYPE_SET = new Set ( ARCHETYPE_VOCABULARY ) ;
const INTENT_SET = new Set ( INTENTS ) ;
// ─── Component discovery ────────────────────────────────────────────────────
function listComponents ( ) : { kebab : string ; morfoPath : string } [ ] {
const entries = readdirSync ( MORFO_DIR ) ;
return entries
. filter ( ( e ) = > e . endsWith ( '.ts' ) && ! e . endsWith ( '.test.ts' ) )
. map ( ( e ) = > ( { kebab : e.replace ( /\.ts$/ , '' ) , morfoPath : join ( MORFO_DIR , e ) } ) )
. filter ( ( c ) = > ( onlyList ? onlyList . includes ( c . kebab ) : true ) )
. sort ( ( a , b ) = > a . kebab . localeCompare ( b . kebab ) ) ;
}
// ─── Checks ─────────────────────────────────────────────────────────────────
function checkMorfo ( kebab : string , src : string , info : ComponentReport ) : CheckResult [ ] {
const out : CheckResult [ ] = [ ] ;
// A-1.1: Morfo const export
const morfoVarMatch = src . match ( /export const (\w+Morfo)\b/ ) ;
if ( ! morfoVarMatch ) {
out . push ( fail ( 'A-1.1' , 'error' , 'No `export const {Name}Morfo` found' ) ) ;
return out ;
}
out . push ( pass ( 'A-1.1' , 'error' ) ) ;
// A-1.2: name + kebab + scope
const hasName = /\bname:\s*['"][\w-]+['"]/ . test ( src ) ;
const hasKebab = /\bkebab:\s*['"][\w-]+['"]/ . test ( src ) ;
const hasScope = /\bscope:\s*\[/ . test ( src ) ;
if ( hasName && hasKebab && hasScope ) out . push ( pass ( 'A-1.2' , 'error' ) ) ;
else
out . push (
fail (
'A-1.2' ,
'error' ,
` Missing: ${ [ ! hasName && 'name' , ! hasKebab && 'kebab' , ! hasScope && 'scope' ] . filter ( Boolean ) . join ( ', ' ) } `
)
) ;
uix: rename morfo.translations → morfo.texts (idlangref discipline)
Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`,
not literal multilingual records. Multilingual content moves out of morfo entirely
into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged
globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure
abstract surface; localization lives in its own layer.
- Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in
`src/uix/morfo/types.ts`. Doc and test fixtures updated.
- Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`),
`components/` (one file per migrated morfo, plus a barrel), and an aggregate
`uixLangs`. `ActiveUix` registers both namespaces at boot.
- Registry: `registerMorfo` no longer carries a translations fingerprint and
no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and
`MorfoTranslationTarget` deleted from the public morfo barrel.
- Validation: schema.ts drops the relative-translationRef-in-morfo.translations
check; catalog presence is enforced by the new `scripts/translations-check.ts`
(wired as `npm run translations:check`).
- Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label`
idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors
on any surviving legacy `translations:` field.
- Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node
script that produced the 42 morfo rewrites and 42 catalog files.
- Tests: `morfo/registry.test.ts` rewritten around the simpler contract;
`active-uix.svelte.test.ts` reflects boot-time catalog availability instead
of lazy per-morfo extension.
Verification: `npm run check` (0 errors), `npm run translations:check`
(0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures
in `test` are unrelated (date-picker types, css regen, range-calendar
clear, color contract).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// A-1.3: morfo.texts.label is a valid component-scoped idlangref.
// Old `translations: { label: { es, en } }` is illegal — caught by tsc, but
// flag it here too for explicit migration error messaging.
const hasLegacyTranslations = /^\ttranslations:\s*\{/m . test ( src ) ;
if ( hasLegacyTranslations ) {
out . push (
fail (
'A-1.3' ,
'error' ,
'Legacy `translations:` field present. Migrate to `texts:` idlangrefs (see translations:check).'
)
) ;
} else {
const labelRefMatch = src . match ( /^\t\ttexts:[\s\S]*?\blabel:\s*['"`](#\?[^'"`]+)['"`]/m ) ;
// Use compact form when texts block exists; the indentation is one tab
// deeper than `\t`.
const compactLabel = src . match (
/^\ttexts:\s*\{[\s\S]*?\blabel:\s*['"`](#\?[^'"`]+)['"`]/m
) ;
const hit = labelRefMatch ? ? compactLabel ;
if ( hit ) {
const ref = hit [ 1 ] ;
const validShape = /^#\?(?:components\.[a-z][a-z0-9-]*|common)\..+\|.+$/ . test ( ref ) ;
if ( validShape ) out . push ( pass ( 'A-1.3' , 'error' ) ) ;
else
out . push (
fail (
'A-1.3' ,
'error' ,
` texts.label idlangref malformed: ${ ref } . Expected '#?components.{kebab}.label|Fallback' or '#?common.…|Fallback'. `
)
) ;
} else {
// Pure visual primitives (scope=['eidos'] only, no events, no
// interactive surface) don't own text slots — the label lives on
// the consumer (e.g. the button hosting the icon). Skip A-1.3 for
// them.
const scopeMatch = src . match ( /scope:\s*\[([^\]]+)\]/ ) ;
const scopes = scopeMatch
? scopeMatch [ 1 ] . split ( ',' ) . map ( ( s ) = > s . trim ( ) . replace ( /['"]/g , '' ) )
: [ ] ;
const isEidosOnlyLeaf =
scopes . length === 1 && scopes [ 0 ] === 'eidos' && ! /events:\s*\[/ . test ( src ) ;
if ( isEidosOnlyLeaf ) {
out . push ( pass ( 'A-1.3' , 'error' , 'eidos-only passive primitive — no text slot' ) ) ;
} else {
out . push (
fail (
'A-1.3' ,
'error' ,
'No `texts.label` declared. Add `texts: { label: \"#?components.{kebab}.label|Fallback\" }` and ship the entry in src/uix/langs/components/{kebab}.ts.'
)
) ;
}
uix: rename morfo.translations → morfo.texts (idlangref discipline)
Morfo now declares text slots as absolute idlangrefs in `texts: Record<string, LangRef>`,
not literal multilingual records. Multilingual content moves out of morfo entirely
into per-component catalogs under `src/uix/langs/components/{kebab}.ts`, merged
globally by `ActiveUix` via the new `componentLangs` barrel. Morfo stays a pure
abstract surface; localization lives in its own layer.
- Type: `translations?: LangNode` → `texts?: Record<string, LangRef>` in
`src/uix/morfo/types.ts`. Doc and test fixtures updated.
- Catalog: new `src/uix/langs/` directory with `common.ts` (was `langs.ts`),
`components/` (one file per migrated morfo, plus a barrel), and an aggregate
`uixLangs`. `ActiveUix` registers both namespaces at boot.
- Registry: `registerMorfo` no longer carries a translations fingerprint and
no longer extends `ActiveLangs` dynamically. `connectMorfoTranslations` and
`MorfoTranslationTarget` deleted from the public morfo barrel.
- Validation: schema.ts drops the relative-translationRef-in-morfo.translations
check; catalog presence is enforced by the new `scripts/translations-check.ts`
(wired as `npm run translations:check`).
- Audit: `scripts/component-audit.ts` rule A-1.3 now validates `texts.label`
idlangref shape (`#?components.{kebab}.label|Fallback`) and explicitly errors
on any surviving legacy `translations:` field.
- Migration: `scripts/migrate-translations-to-texts.ts` is the one-off Node
script that produced the 42 morfo rewrites and 42 catalog files.
- Tests: `morfo/registry.test.ts` rewritten around the simpler contract;
`active-uix.svelte.test.ts` reflects boot-time catalog availability instead
of lazy per-morfo extension.
Verification: `npm run check` (0 errors), `npm run translations:check`
(0/0), `npm run morfo:check` (36/36 routed). The 4 pre-existing failures
in `test` are unrelated (date-picker types, css regen, range-calendar
clear, color contract).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
}
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// A-1.4: apg URL for interactive components
const hasApg = /\bapg:\s*['"]https?:/ . test ( src ) ;
if ( info . interactive ) {
if ( hasApg ) out . push ( pass ( 'A-1.4' , 'warn' ) ) ;
else out . push ( fail ( 'A-1.4' , 'warn' , 'No apg URL declared for interactive component' ) ) ;
}
// A-2.1: at least one Provider part — kebab='provider'. Archetype can vary
// (icon uses 'image', viewport uses 'provider', etc.) so don't enforce that.
const hasProvider = /kebab:\s*['"]provider['"]/m . test ( src ) ;
if ( hasProvider ) out . push ( pass ( 'A-2.1' , 'error' ) ) ;
else out . push ( fail ( 'A-2.1' , 'error' , 'No part with kebab: "provider" found' ) ) ;
// A-2.5: archetypes ∈ ARCHETYPE_VOCABULARY
const archetypes = [ . . . src . matchAll ( PART_ARCH_RE ) ] . map ( ( m ) = > m [ 1 ] ) ;
const invalidArchs = archetypes . filter ( ( a ) = > ! ARCHETYPE_SET . has ( a as never ) ) ;
if ( invalidArchs . length === 0 ) out . push ( pass ( 'A-2.5' , 'error' ) ) ;
else out . push ( fail ( 'A-2.5' , 'error' , ` Invented archetypes: ${ [ . . . new Set ( invalidArchs ) ] . join ( ', ' ) } ` ) ) ;
// A-3.1: events.length >= 1 if interactive — only count EVENT blocks, not part names
const eventBlocks = extractEventBlocks ( src ) ;
const eventNames = eventBlocks
. map ( ( b ) = > b . match ( /name:\s*['"]([\w-]+)['"]/ ) ? . [ 1 ] )
. filter ( ( n ) : n is string = > Boolean ( n ) ) ;
const declaredEvents = eventBlocks . length ;
if ( info . interactive && declaredEvents === 0 ) {
out . push ( fail ( 'A-3.1' , 'error' , 'Interactive component has 0 events' ) ) ;
} else {
out . push ( pass ( 'A-3.1' , 'error' , ` ${ declaredEvents } events declared ` ) ) ;
}
// A-3.3 + A-3.4: family + verb canonical — only inside event blocks
const families = eventBlocks
. map ( ( b ) = > b . match ( /family:\s*['"]([\w-]+)['"]/ ) ? . [ 1 ] )
. filter ( ( f ) : f is string = > Boolean ( f ) ) ;
const verbs = eventBlocks
. map ( ( b ) = > b . match ( /verb:\s*['"]([\w-]+)['"]/ ) ? . [ 1 ] )
. filter ( ( v ) : v is string = > Boolean ( v ) ) ;
const invalidFamilies = families . filter ( ( f ) = > ! SEMA_FAMILIES . includes ( f as never ) ) ;
if ( invalidFamilies . length === 0 ) out . push ( pass ( 'A-3.3' , 'error' ) ) ;
else out . push ( fail ( 'A-3.3' , 'error' , ` Invented families: ${ [ . . . new Set ( invalidFamilies ) ] . join ( ', ' ) } ` ) ) ;
const invalidVerbs = verbs . filter ( ( v ) = > ! ALL_VERBS . has ( v ) ) ;
if ( invalidVerbs . length === 0 ) out . push ( pass ( 'A-3.4' , 'error' ) ) ;
else out . push ( fail ( 'A-3.4' , 'error' , ` Verbs not in SEMA_VERBS: ${ [ . . . new Set ( invalidVerbs ) ] . join ( ', ' ) } ` ) ) ;
// Cross-family verb integrity (verb must belong to declared family in same event)
const familyVerbMismatches : string [ ] = [ ] ;
for ( const block of eventBlocks ) {
const fm = block . match ( /family:\s*['"]([\w-]+)['"]/ ) ? . [ 1 ] ;
const vm = block . match ( /verb:\s*['"]([\w-]+)['"]/ ) ? . [ 1 ] ;
if ( fm && vm && VERBS_BY_FAMILY [ fm ] && ! VERBS_BY_FAMILY [ fm ] . includes ( vm ) ) {
familyVerbMismatches . push ( ` ${ fm } . ${ vm } ` ) ;
}
}
if ( familyVerbMismatches . length === 0 ) out . push ( pass ( 'A-3.4b' , 'warn' ) ) ;
else out . push ( fail ( 'A-3.4b' , 'warn' , ` Verb/family mismatch: ${ familyVerbMismatches . join ( ', ' ) } ` ) ) ;
// A-3.5: sequence declared — only inside event blocks
const sequences = eventBlocks
. map ( ( b ) = > b . match ( /sequence:\s*['"](pre|coincident|post)['"]/ ) ? . [ 1 ] )
. filter ( ( s ) : s is string = > Boolean ( s ) ) ;
if ( info . interactive && declaredEvents > 0 ) {
if ( sequences . length === declaredEvents ) {
out . push ( pass ( 'A-3.5' , 'warn' ) ) ;
} else {
out . push (
fail (
'A-3.5' ,
'warn' ,
` ${ declaredEvents } events, ${ sequences . length } sequences declared (some events missing sequence) `
)
) ;
}
}
popover: PASS audit + accept bare canonical verbs in A-3.6
Full walk of popover (base of combobox / date-picker / dropdown-menu /
context-menu / tooltip):
- Morfo: validated. 9 parts (Provider/Trigger/Content/Arrow/Title/
Description/Close/Anchor/Overlay). 6 events: `present` (emerge),
`close-save` (commit fulfill), `close-cancel` / `close-dismiss` /
`close-dismiss-outside` (emerge), `close-after-fail` (signal risk).
Focus trap + return-to-trigger + restore. Modal flag drives
outside-click ignore + aria-modal + focus trap propagation.
- `texts.label` added with catalog entry. New `popover` entry in the
componentLangs barrel.
- README `## Pendiente deliberado` section renamed to `## Gaps` with
disposition markers. New rows documented: multiple-trigger model
(diferir), `Positioner` as public part (descartar — Soma Floating
owns positioning), `forceMount` per-part (diferir), modal mode +
footer (descartar — the picker pattern, not generic popover),
`onOpenAutoFocus` / `onCloseAutoFocus` docs (implementar),
hover-open delay API (diferir), virtual anchor (diferir).
Audit script refinement (A-3.6):
- Bare canonical verbs are now accepted as valid event names. The
rule used to require `{verb}-{x}` or `{family}-{verb}` — but
`present`, `open`, `close` ON THEIR OWN are perfectly fine when
the event has no variant to disambiguate. The check now passes
events named with a bare verb if that verb is in `ALL_VERBS`,
OR a bare family name. Popover's `present` event no longer
generates a warn.
Audit: PASS 8 → 9. Popover flips to PASS clean. No regressions
elsewhere from the bare-verb relaxation (only popover used the
pattern; other components with bare names already had warns we
documented).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// A-3.6: event name follows `{verb}-{x}`, `{family}-{verb}`, OR is a
// bare canonical verb on its own (e.g. `present`, `open`, `close`).
// Bare verbs are valid when the event has no variant to disambiguate.
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const badNames = eventNames . filter ( ( n ) = > {
popover: PASS audit + accept bare canonical verbs in A-3.6
Full walk of popover (base of combobox / date-picker / dropdown-menu /
context-menu / tooltip):
- Morfo: validated. 9 parts (Provider/Trigger/Content/Arrow/Title/
Description/Close/Anchor/Overlay). 6 events: `present` (emerge),
`close-save` (commit fulfill), `close-cancel` / `close-dismiss` /
`close-dismiss-outside` (emerge), `close-after-fail` (signal risk).
Focus trap + return-to-trigger + restore. Modal flag drives
outside-click ignore + aria-modal + focus trap propagation.
- `texts.label` added with catalog entry. New `popover` entry in the
componentLangs barrel.
- README `## Pendiente deliberado` section renamed to `## Gaps` with
disposition markers. New rows documented: multiple-trigger model
(diferir), `Positioner` as public part (descartar — Soma Floating
owns positioning), `forceMount` per-part (diferir), modal mode +
footer (descartar — the picker pattern, not generic popover),
`onOpenAutoFocus` / `onCloseAutoFocus` docs (implementar),
hover-open delay API (diferir), virtual anchor (diferir).
Audit script refinement (A-3.6):
- Bare canonical verbs are now accepted as valid event names. The
rule used to require `{verb}-{x}` or `{family}-{verb}` — but
`present`, `open`, `close` ON THEIR OWN are perfectly fine when
the event has no variant to disambiguate. The check now passes
events named with a bare verb if that verb is in `ALL_VERBS`,
OR a bare family name. Popover's `present` event no longer
generates a warn.
Audit: PASS 8 → 9. Popover flips to PASS clean. No regressions
elsewhere from the bare-verb relaxation (only popover used the
pattern; other components with bare names already had warns we
documented).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if ( ! n . includes ( '-' ) ) {
// Accept bare canonical verbs and bare family names.
return ! ALL_VERBS . has ( n ) && ! SEMA_FAMILIES . includes ( n as never ) ;
}
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const [ head ] = n . split ( '-' ) ;
return ! ALL_VERBS . has ( head ) && ! SEMA_FAMILIES . includes ( head as never ) ;
} ) ;
if ( badNames . length === 0 ) out . push ( pass ( 'A-3.6' , 'warn' ) ) ;
else
out . push (
fail (
'A-3.6' ,
'warn' ,
` Event names not matching {verb}-X or {family}-X: ${ badNames . join ( ', ' ) } `
)
) ;
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// A-3.7: event/keyboard coverage heuristic.
//
// Per the COMPONENT_GUIDE: "every distinct keyboard action that mutates
// state has a corresponding semantic event. Pure focus moves don't need
// an event." So we COUNT only the actions that mutate state — arrow
// navigation, page/month/year shifts, home/end inside a grid are pure
// focus moves and must not inflate the ratio.
const allKeyboardActions = [ . . . src . matchAll ( /action:\s*['"]([\w-]+)['"]/g ) ] . map ( ( m ) = > m [ 1 ] ) ;
const focusMoveActions = new Set ( [
combobox: PASS audit + extend focus-move list for listbox/menu nav
Full walk of combobox (popover + editable input + listbox):
- Morfo: validated. 10 parts (Provider/Input/Trigger/Content/Viewport/
Item/Group/GroupHeading/Separator/Arrow). Single + multiple via
`data-type` on Provider, `aria-multiselectable` conditional on
`prop:multiple`. Item declares `data-state` checked/unchecked,
`data-value`, `data-highlighted`, `data-disabled`. Keyboard: 6 keys
but only 2 mutate state (Enter→select, Escape→close-delegated).
- Decision documented: combobox does NOT emit emerge events for
open/close/typing/filtering/highlighting — these are navigation
mechanics, not commits. Escape close is delegated to the popover
layer's `close-dismiss` event. The 2 `commit-*` events cover the
actual user intent (choosing an item).
- `texts.label` added with catalog entry. The toggle key already
existed.
Audit script refinements:
- Extended focus-move action list to cover listbox/menu/tablist
navigation (`next-item`, `prev-item`, `first-item`, `last-item`,
`next-tab`, `prev-tab`, `first-tab`, `last-tab`) plus the popover/
dialog actions that are delegated to the containing overlay layer
(`open`, `close`, `dismiss`, `toggle`). A combobox / select / menu
that hosts Escape→close is not under-declared if the overlay layer
owns the sema event.
README rewritten with canonical sections:
- `## Baseline` summarizing air + soma + morfo coverage.
- `## Comparativa` (was `## Comparison`) — table preserved.
- `## Decisiones` documenting the no-emerge-events policy, focus-only
keys, Control composition rationale, multi-select aria propagation.
- `## Gaps` (was `## Out of scope`) rewritten with disposition tokens
(implementar / diferir / descartar) per row. ClearTrigger and
Validation state get `implementar`; Modal+footer gets `descartar`
(combobox is inline by design, not a confirm-style picker).
Audit: PASS 6 → 7. No regressions; the relaxed focus-move list
benefits other listbox/menu components too (will surface on their
audits).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// Calendar / date grid navigation
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
'next-day' ,
'prev-day' ,
'next-week' ,
'prev-week' ,
'next-month' ,
'prev-month' ,
'next-year' ,
'prev-year' ,
'first-day-of-week' ,
'last-day-of-week' ,
combobox: PASS audit + extend focus-move list for listbox/menu nav
Full walk of combobox (popover + editable input + listbox):
- Morfo: validated. 10 parts (Provider/Input/Trigger/Content/Viewport/
Item/Group/GroupHeading/Separator/Arrow). Single + multiple via
`data-type` on Provider, `aria-multiselectable` conditional on
`prop:multiple`. Item declares `data-state` checked/unchecked,
`data-value`, `data-highlighted`, `data-disabled`. Keyboard: 6 keys
but only 2 mutate state (Enter→select, Escape→close-delegated).
- Decision documented: combobox does NOT emit emerge events for
open/close/typing/filtering/highlighting — these are navigation
mechanics, not commits. Escape close is delegated to the popover
layer's `close-dismiss` event. The 2 `commit-*` events cover the
actual user intent (choosing an item).
- `texts.label` added with catalog entry. The toggle key already
existed.
Audit script refinements:
- Extended focus-move action list to cover listbox/menu/tablist
navigation (`next-item`, `prev-item`, `first-item`, `last-item`,
`next-tab`, `prev-tab`, `first-tab`, `last-tab`) plus the popover/
dialog actions that are delegated to the containing overlay layer
(`open`, `close`, `dismiss`, `toggle`). A combobox / select / menu
that hosts Escape→close is not under-declared if the overlay layer
owns the sema event.
README rewritten with canonical sections:
- `## Baseline` summarizing air + soma + morfo coverage.
- `## Comparativa` (was `## Comparison`) — table preserved.
- `## Decisiones` documenting the no-emerge-events policy, focus-only
keys, Control composition rationale, multi-select aria propagation.
- `## Gaps` (was `## Out of scope`) rewritten with disposition tokens
(implementar / diferir / descartar) per row. ClearTrigger and
Validation state get `implementar`; Modal+footer gets `descartar`
(combobox is inline by design, not a confirm-style picker).
Audit: PASS 6 → 7. No regressions; the relaxed focus-move list
benefits other listbox/menu components too (will surface on their
audits).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// Generic listbox / menu / tablist navigation
'next-item' ,
'prev-item' ,
'first-item' ,
'last-item' ,
'next-tab' ,
'prev-tab' ,
'first-tab' ,
'last-tab' ,
// Generic focus moves
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
'focus-next' ,
'focus-prev' ,
'focus-first' ,
'focus-last' ,
'focus-up' ,
'focus-down' ,
'focus-left' ,
combobox: PASS audit + extend focus-move list for listbox/menu nav
Full walk of combobox (popover + editable input + listbox):
- Morfo: validated. 10 parts (Provider/Input/Trigger/Content/Viewport/
Item/Group/GroupHeading/Separator/Arrow). Single + multiple via
`data-type` on Provider, `aria-multiselectable` conditional on
`prop:multiple`. Item declares `data-state` checked/unchecked,
`data-value`, `data-highlighted`, `data-disabled`. Keyboard: 6 keys
but only 2 mutate state (Enter→select, Escape→close-delegated).
- Decision documented: combobox does NOT emit emerge events for
open/close/typing/filtering/highlighting — these are navigation
mechanics, not commits. Escape close is delegated to the popover
layer's `close-dismiss` event. The 2 `commit-*` events cover the
actual user intent (choosing an item).
- `texts.label` added with catalog entry. The toggle key already
existed.
Audit script refinements:
- Extended focus-move action list to cover listbox/menu/tablist
navigation (`next-item`, `prev-item`, `first-item`, `last-item`,
`next-tab`, `prev-tab`, `first-tab`, `last-tab`) plus the popover/
dialog actions that are delegated to the containing overlay layer
(`open`, `close`, `dismiss`, `toggle`). A combobox / select / menu
that hosts Escape→close is not under-declared if the overlay layer
owns the sema event.
README rewritten with canonical sections:
- `## Baseline` summarizing air + soma + morfo coverage.
- `## Comparativa` (was `## Comparison`) — table preserved.
- `## Decisiones` documenting the no-emerge-events policy, focus-only
keys, Control composition rationale, multi-select aria propagation.
- `## Gaps` (was `## Out of scope`) rewritten with disposition tokens
(implementar / diferir / descartar) per row. ClearTrigger and
Validation state get `implementar`; Modal+footer gets `descartar`
(combobox is inline by design, not a confirm-style picker).
Audit: PASS 6 → 7. No regressions; the relaxed focus-move list
benefits other listbox/menu components too (will surface on their
audits).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
'focus-right' ,
// Popover/dialog open/close delegated to the containing overlay layer:
// the surrounding popover / dialog morfo already declares the
// emerge events. A combobox / select / menu that hosts Escape →
// close shouldn't be flagged for under-declaration when the
// overlay layer owns the sema event.
'open' ,
'close' ,
'dismiss' ,
'toggle'
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
] ) ;
const mutatingKeyboardActions = allKeyboardActions . filter ( ( a ) = > ! focusMoveActions . has ( a ) ) ;
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const keys = [ . . . src . matchAll ( KEYBOARD_KEY_RE ) ] . length ;
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const mutatingKeyCount = mutatingKeyboardActions . length ;
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if ( info . interactive ) {
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// Only count MUTATING actions toward the ratio. A calendar with 10
// keys / 2 mutating (Enter, Space) / 1 commit-select event passes
// because Enter and Space both map to the same `select` action.
if (
mutatingKeyCount >= 4 &&
declaredEvents > 0 &&
mutatingKeyCount >= declaredEvents * 2
) {
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
out . push (
fail (
'A-3.7' ,
'error' ,
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
` ${ mutatingKeyCount } mutating keyboard actions but only ${ declaredEvents } semantic events — likely under-declared (total keys ${ keys } ) `
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
)
) ;
} else {
calendar: README sections + data-readonly recipe + audit A-3.7 refinement
Closes the calendar audit findings:
- README sections renamed to canonical headers: `## Baseline` (was
"Baseline audit"), `## Comparativa` (was "Reference comparison").
Added `## Decisiones` documenting the affirm-on-select choice, the
collapsed `shift-navigate` event for all month/year movement, why
arrow keys don't get their own events (pure focus moves), and the
read-only treatment. Rewrote `## Gaps` with disposition tokens
(implementar/diferir/descartar) for each entry.
- Recipe CSS gains `[data-calendar][data-readonly]` styling: cells
drop their hover affordance and the cursor stays neutral. Selection
+ navigation remain interactive (matches `aria-readonly` semantics).
- Audit A-3.7 refined per the COMPONENT_GUIDE rule "pure focus moves
don't need an event". The check now compares MUTATING keyboard
actions to declared events. Focus-move actions
(next-day/prev-day/next-week/prev-week/next-month/prev-month/
next-year/prev-year/first-day-of-week/last-day-of-week/
focus-{next,prev,first,last,up,down,left,right}) are excluded from
the ratio. Calendar's 10 keys / 2 mutating (Enter+Space → `select`)
/ 1 `commit-select` event now passes correctly.
Project audit: PASS 3 → 5, no new regressions. `range-calendar` also
flips to PASS as a side effect of the focus-move filter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
out . push (
pass (
'A-3.7' ,
'error' ,
` ${ keys } keys ( ${ mutatingKeyCount } mutating) / ${ declaredEvents } events `
)
) ;
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
}
// A-3.10: intent valenced families — only inside event blocks
const intentMatches = eventBlocks
. map ( ( b ) = > b . match ( /intent:\s*['"]([\w-]+)['"]/ ) ? . [ 1 ] )
. filter ( ( i ) : i is string = > Boolean ( i ) ) ;
const invalidIntents = intentMatches . filter ( ( i ) = > ! INTENT_SET . has ( i as never ) ) ;
if ( invalidIntents . length === 0 ) out . push ( pass ( 'A-3.10' , 'warn' ) ) ;
else out . push ( fail ( 'A-3.10' , 'warn' , ` Invalid intents: ${ [ . . . new Set ( invalidIntents ) ] . join ( ', ' ) } ` ) ) ;
// A-4.4: keyboard key normalization
const allKeys = [ . . . src . matchAll ( KEYBOARD_KEY_RE ) ] . map ( ( m ) = > m [ 1 ] ) ;
const badKeys = allKeys . filter ( ( k ) = > /^(Spacebar|Esc|Del|Ins|PgUp|PgDn)$/ . test ( k ) ) ;
if ( badKeys . length === 0 ) out . push ( pass ( 'A-4.4' , 'error' ) ) ;
else
out . push (
fail (
'A-4.4' ,
'error' ,
` Non-standard key values: ${ [ . . . new Set ( badKeys ) ] . join ( ', ' ) } . Use KeyboardEvent.key names: ' '/Escape/Delete/... `
)
) ;
return out ;
}
function countEventBlocks ( src : string ) : number {
// Count top-level entries in events: [...] array. We use the named matches —
// each event has a `name:` field, so count those that appear inside the
// events array. Heuristic: count `name:` occurrences that aren't immediately
// nested inside another known nested structure.
// Simpler approach: events: [ ... ] block extraction + counting top-level
// objects.
return extractEventBlocks ( src ) . length ;
}
function extractEventBlocks ( src : string ) : string [ ] {
// Find `events: [` then walk brackets until matching `]`. Then split top-
// level `{ ... }` blocks.
const eventsIdx = src . indexOf ( 'events:' ) ;
if ( eventsIdx === - 1 ) return [ ] ;
const arrStart = src . indexOf ( '[' , eventsIdx ) ;
if ( arrStart === - 1 ) return [ ] ;
let depth = 0 ;
let i = arrStart ;
for ( ; i < src . length ; i ++ ) {
const ch = src [ i ] ;
if ( ch === '[' ) depth ++ ;
else if ( ch === ']' ) {
depth -- ;
if ( depth === 0 ) break ;
}
}
const arrContent = src . slice ( arrStart + 1 , i ) ;
const blocks : string [ ] = [ ] ;
let blockDepth = 0 ;
let blockStart = - 1 ;
for ( let j = 0 ; j < arrContent . length ; j ++ ) {
const ch = arrContent [ j ] ;
if ( ch === '{' ) {
if ( blockDepth === 0 ) blockStart = j ;
blockDepth ++ ;
} else if ( ch === '}' ) {
blockDepth -- ;
if ( blockDepth === 0 && blockStart !== - 1 ) {
blocks . push ( arrContent . slice ( blockStart , j + 1 ) ) ;
blockStart = - 1 ;
}
}
}
return blocks ;
}
function checkEidos ( kebab : string , info : ComponentReport ) : CheckResult [ ] {
const out : CheckResult [ ] = [ ] ;
const dir = join ( EIDOS_DIR , kebab ) ;
if ( ! existsSync ( dir ) ) {
out . push ( fail ( 'E-existence' , 'error' , ` No eidos wrapper directory at ${ dir } ` ) ) ;
return out ;
}
// E-1.1: has {name}.svelte root file
const rootSveltePath = join ( dir , ` ${ kebab } .svelte ` ) ;
if ( existsSync ( rootSveltePath ) ) out . push ( pass ( 'E-1.1' , 'error' ) ) ;
else out . push ( fail ( 'E-1.1' , 'error' , ` Missing root ${ kebab } .svelte ` ) ) ;
// E-1.2 + E-1.3 + E-1.4: index.ts shape
const indexPath = join ( dir , 'index.ts' ) ;
const idx = tryRead ( indexPath ) ;
if ( ! idx ) {
out . push ( fail ( 'E-1.2' , 'error' , 'Missing index.ts' ) ) ;
} else {
// E-1.2: NO Object.assign on wrapper components
const usesObjectAssign = /Object\.assign\s*\(\s*\w+Component\b/ . test ( idx ) || /Object\.assign\s*\(\s*\w+Root\b/ . test ( idx ) ;
if ( ! usesObjectAssign ) out . push ( pass ( 'E-1.2' , 'error' ) ) ;
else
out . push (
fail (
'E-1.2' ,
'error' ,
'Uses Object.assign on component constructor — hydration glitch. Use explicit .Part = Part assignment.'
)
) ;
// E-1.3: named + default export of {Name}
// Allow: `export { X }`, `export const X`, `export type X`, `export default X`,
// `export * from ...`, and re-exports via `export { default as X }`.
const namedExport =
/export\s*\{\s*[A-Z]\w*\s*[,}]/ . test ( idx ) ||
/export\s+const\s+[A-Z]/ . test ( idx ) ||
/export\s*\*\s*from/ . test ( idx ) ||
/export\s*\{\s*default\s+as/ . test ( idx ) ;
const defaultExport =
/export\s+default\s+\w+/ . test ( idx ) ||
/export\s+default\s+function/ . test ( idx ) ||
/export\s*\{\s*[A-Z]\w*\s+as\s+default\b/ . test ( idx ) ||
// Re-exporting the default of another module: `export { default } from './x.svelte';`
/export\s*\{\s*default\s*\}\s*from/ . test ( idx ) ;
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if ( namedExport && defaultExport ) out . push ( pass ( 'E-1.3' , 'error' ) ) ;
else
out . push (
fail (
'E-1.3' ,
'error' ,
` Missing ${ [ ! namedExport && 'named' , ! defaultExport && 'default' ] . filter ( Boolean ) . join ( ' + ' ) } export `
)
) ;
// E-1.4: no Provider/Base/Root/Parts exports
const badExports = [ 'Provider' , 'Base' , 'Parts' ] . filter ( ( tag ) = >
new RegExp ( ` \\ bexport \\ s* \\ {[^}]* \\ b ${ tag } \\ b ` ) . test ( idx )
) ;
if ( badExports . length === 0 ) out . push ( pass ( 'E-1.4' , 'error' ) ) ;
else out . push ( fail ( 'E-1.4' , 'error' , ` Forbidden exports: ${ badExports . join ( ', ' ) } ` ) ) ;
}
// E-2.1: types.ts exists
const typesPath = join ( dir , 'types.ts' ) ;
if ( existsSync ( typesPath ) ) out . push ( pass ( 'E-2.1' , 'error' ) ) ;
else out . push ( fail ( 'E-2.1' , 'error' , 'Missing types.ts' ) ) ;
// E-2.2: {name}.css exists + imported from eidos/index.css
const recipePath = join ( dir , ` ${ kebab } .css ` ) ;
if ( existsSync ( recipePath ) ) {
const eidosIndexCss = tryRead ( join ( REPO , 'src/uix/eidos/index.css' ) ) ;
if ( eidosIndexCss && eidosIndexCss . includes ( ` components/ ${ kebab } / ${ kebab } .css ` ) ) {
out . push ( pass ( 'E-2.2' , 'error' ) ) ;
} else {
out . push ( fail ( 'E-2.2' , 'error' , ` ${ kebab } .css exists but not imported from eidos/index.css ` ) ) ;
}
} else {
out . push ( fail ( 'E-2.2' , 'error' , ` Missing ${ kebab } .css ` ) ) ;
}
// E-2.3: README.md exists
const readmePath = join ( dir , 'README.md' ) ;
if ( existsSync ( readmePath ) ) out . push ( pass ( 'E-2.3' , 'error' ) ) ;
else out . push ( fail ( 'E-2.3' , 'error' , 'Missing README.md' ) ) ;
return out ;
}
function checkRecipe ( kebab : string , info : ComponentReport ) : CheckResult [ ] {
const out : CheckResult [ ] = [ ] ;
const recipePath = join ( EIDOS_DIR , kebab , ` ${ kebab } .css ` ) ;
const css = tryRead ( recipePath ) ;
if ( ! css ) return out ; // E-2.2 will catch missing file
// R-1.1: root selector
const hasRoot = new RegExp ( ` \\ [data- ${ kebab } \\ b ` ) . test ( css ) ;
if ( hasRoot ) out . push ( pass ( 'R-1.1' , 'error' ) ) ;
else out . push ( fail ( 'R-1.1' , 'error' , ` Missing root selector [data- ${ kebab } ] ` ) ) ;
form: PASS audit + refine R-1.2 to require morfo declaration
Full walk of Form (largest finding set in the project):
- Morfo: validated. 14 parts — Provider/Submit/Reset/ErrorSummary +
9 AutoFields parts for the reflective renderer. Provider declares
data-pending/dirty/touched/invalid/submitted on the contract. Sema
events `commit-submit` (fulfill), `signal-invalid` (risk),
`commit-reset` (neutral) are correct.
- `texts.label` added with catalog entry (`label = 'Formulario' / 'Form'`).
- Recipe CSS: `[data-form][data-invalid]` rule added. Low-emphasis
affordance — the ErrorSummary picks up the risk border but field
cells keep their own `[data-invalid]` styling via the Field recipe.
- README rewritten with canonical sections:
- `## Baseline` summarizing air / soma / morfo coverage
- `## Comparativa` (was `## Reference Comparison`) — extended to
include shadcn-svelte plus AutoFields differentiators
(discriminated unions, array fields, first-error focus,
validation timing modes)
- `## Decisiones` documenting the small-wrapper rule, AutoFields
exception, validation timing ownership, the no-noise-on-load
default, sema event placement, and the low-emphasis invalid
treatment
- `## Gaps` (new) with disposition markers — apg is `descartar`
(no APG for "Form" — APG covers individual widgets), AutoFields
i18n is `implementar`, multi-step / submission feedback / etc.
are `diferir`, auto-save / optimistic UI are `descartar`
Audit script refinement:
- R-1.2 (data-disabled styles) now only fires when the morfo
*declares* `data-disabled` on any part. Form's Provider doesn't
emit a disabled state at the root (individual fields handle it
themselves), so demanding defensive CSS for a state the contract
never emits was a false-positive. The rule still fires correctly
for components that DO declare `data-disabled` in their morfo.
Audit: PASS 7 → 8. Form flips to PASS with only one remaining
warn (`A-1.4` no apg URL — `descartar` documented as a gap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// R-1.2: data-disabled state styled. Only required when the morfo
// actually declares `data-disabled` on any part — otherwise the audit
// would demand defensive styling for state the contract never emits.
const morfoDeclaresDisabled = ( tryRead ( join ( MORFO_DIR , ` ${ kebab } .ts ` ) ) ? ? '' ) . includes (
'data-disabled'
) ;
if ( info . interactive && morfoDeclaresDisabled ) {
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
const hasDisabled = /\[data-disabled\b/ . test ( css ) ;
if ( hasDisabled ) out . push ( pass ( 'R-1.2' , 'error' ) ) ;
else out . push ( fail ( 'R-1.2' , 'error' , 'No [data-disabled] styles' ) ) ;
form: PASS audit + refine R-1.2 to require morfo declaration
Full walk of Form (largest finding set in the project):
- Morfo: validated. 14 parts — Provider/Submit/Reset/ErrorSummary +
9 AutoFields parts for the reflective renderer. Provider declares
data-pending/dirty/touched/invalid/submitted on the contract. Sema
events `commit-submit` (fulfill), `signal-invalid` (risk),
`commit-reset` (neutral) are correct.
- `texts.label` added with catalog entry (`label = 'Formulario' / 'Form'`).
- Recipe CSS: `[data-form][data-invalid]` rule added. Low-emphasis
affordance — the ErrorSummary picks up the risk border but field
cells keep their own `[data-invalid]` styling via the Field recipe.
- README rewritten with canonical sections:
- `## Baseline` summarizing air / soma / morfo coverage
- `## Comparativa` (was `## Reference Comparison`) — extended to
include shadcn-svelte plus AutoFields differentiators
(discriminated unions, array fields, first-error focus,
validation timing modes)
- `## Decisiones` documenting the small-wrapper rule, AutoFields
exception, validation timing ownership, the no-noise-on-load
default, sema event placement, and the low-emphasis invalid
treatment
- `## Gaps` (new) with disposition markers — apg is `descartar`
(no APG for "Form" — APG covers individual widgets), AutoFields
i18n is `implementar`, multi-step / submission feedback / etc.
are `diferir`, auto-save / optimistic UI are `descartar`
Audit script refinement:
- R-1.2 (data-disabled styles) now only fires when the morfo
*declares* `data-disabled` on any part. Form's Provider doesn't
emit a disabled state at the root (individual fields handle it
themselves), so demanding defensive CSS for a state the contract
never emits was a false-positive. The rule still fires correctly
for components that DO declare `data-disabled` in their morfo.
Audit: PASS 7 → 8. Form flips to PASS with only one remaining
warn (`A-1.4` no apg URL — `descartar` documented as a gap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
}
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
form: PASS audit + refine R-1.2 to require morfo declaration
Full walk of Form (largest finding set in the project):
- Morfo: validated. 14 parts — Provider/Submit/Reset/ErrorSummary +
9 AutoFields parts for the reflective renderer. Provider declares
data-pending/dirty/touched/invalid/submitted on the contract. Sema
events `commit-submit` (fulfill), `signal-invalid` (risk),
`commit-reset` (neutral) are correct.
- `texts.label` added with catalog entry (`label = 'Formulario' / 'Form'`).
- Recipe CSS: `[data-form][data-invalid]` rule added. Low-emphasis
affordance — the ErrorSummary picks up the risk border but field
cells keep their own `[data-invalid]` styling via the Field recipe.
- README rewritten with canonical sections:
- `## Baseline` summarizing air / soma / morfo coverage
- `## Comparativa` (was `## Reference Comparison`) — extended to
include shadcn-svelte plus AutoFields differentiators
(discriminated unions, array fields, first-error focus,
validation timing modes)
- `## Decisiones` documenting the small-wrapper rule, AutoFields
exception, validation timing ownership, the no-noise-on-load
default, sema event placement, and the low-emphasis invalid
treatment
- `## Gaps` (new) with disposition markers — apg is `descartar`
(no APG for "Form" — APG covers individual widgets), AutoFields
i18n is `implementar`, multi-step / submission feedback / etc.
are `diferir`, auto-save / optimistic UI are `descartar`
Audit script refinement:
- R-1.2 (data-disabled styles) now only fires when the morfo
*declares* `data-disabled` on any part. Form's Provider doesn't
emit a disabled state at the root (individual fields handle it
themselves), so demanding defensive CSS for a state the contract
never emits was a false-positive. The rule still fires correctly
for components that DO declare `data-disabled` in their morfo.
Audit: PASS 7 → 8. Form flips to PASS with only one remaining
warn (`A-1.4` no apg URL — `descartar` documented as a gap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if ( info . interactive ) {
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// R-1.5: focus-visible
const hasFocus = /:focus-visible\b/ . test ( css ) ;
if ( hasFocus ) out . push ( pass ( 'R-1.5' , 'error' ) ) ;
else out . push ( fail ( 'R-1.5' , 'error' , 'No :focus-visible styles' ) ) ;
}
// R-1.3: data-readonly if applicable
const morfoSrc = tryRead ( join ( MORFO_DIR , ` ${ kebab } .ts ` ) ) ? ? '' ;
if ( morfoSrc . includes ( 'data-readonly' ) ) {
const hasReadonly = /\[data-readonly\b/ . test ( css ) ;
if ( hasReadonly ) out . push ( pass ( 'R-1.3' , 'warn' ) ) ;
else out . push ( fail ( 'R-1.3' , 'warn' , '[data-readonly] declared in morfo but not styled' ) ) ;
}
// R-1.4: data-invalid if applicable
if ( morfoSrc . includes ( 'data-invalid' ) ) {
const hasInvalid = /\[data-invalid\b/ . test ( css ) ;
if ( hasInvalid ) out . push ( pass ( 'R-1.4' , 'warn' ) ) ;
else out . push ( fail ( 'R-1.4' , 'warn' , '[data-invalid] declared in morfo but not styled' ) ) ;
}
// R-2.1: no raw colors (hex/rgb/named)
const rawColors : string [ ] = [ ] ;
const HEX_RE = /#[0-9a-fA-F]{3,8}\b/g ;
for ( const m of css . matchAll ( HEX_RE ) ) {
// Allow rgba(0,0,0,0) and white/black in shorthand? Just flag all hex.
rawColors . push ( m [ 0 ] ) ;
}
const RGB_RE = /\b(rgba?\(\s*\d)/g ;
for ( const m of css . matchAll ( RGB_RE ) ) rawColors . push ( m [ 1 ] ) ;
// Allow `currentColor`, `transparent`, `inherit`
if ( rawColors . length === 0 ) out . push ( pass ( 'R-2.1' , 'error' ) ) ;
else
out . push (
fail (
'R-2.1' ,
'error' ,
` Raw colors detected ( ${ rawColors . length } ): ${ rawColors . slice ( 0 , 5 ) . join ( ', ' ) } ${ rawColors . length > 5 ? '...' : '' } `
)
) ;
// R-2.5: no --eidos-* or --soma-* invented vars
const badVars : string [ ] = [ ] ;
for ( const m of css . matchAll ( /--(eidos|soma|air)-[\w-]+/g ) ) badVars . push ( m [ 0 ] ) ;
if ( badVars . length === 0 ) out . push ( pass ( 'R-2.5' , 'error' ) ) ;
else out . push ( fail ( 'R-2.5' , 'error' , ` Invented vars: ${ [ . . . new Set ( badVars ) ] . slice ( 0 , 5 ) . join ( ', ' ) } ` ) ) ;
// R-3.2: no legacy color names
const legacy : string [ ] = [ ] ;
for ( const cls of [ 'success' , 'warning' , 'danger' , 'info' ] ) {
const re = new RegExp ( ` \\ [data-color=['"] ${ cls } ['"] \\ ] ` ) ;
if ( re . test ( css ) ) legacy . push ( cls ) ;
}
if ( legacy . length === 0 ) out . push ( pass ( 'R-3.2' , 'error' ) ) ;
else out . push ( fail ( 'R-3.2' , 'error' , ` Legacy color names: ${ legacy . join ( ', ' ) } ` ) ) ;
return out ;
}
function checkDemo ( kebab : string , info : ComponentReport ) : CheckResult [ ] {
const out : CheckResult [ ] = [ ] ;
const demoPath = join ( DEMO_DIR , kebab , '+page.svelte' ) ;
const src = tryRead ( demoPath ) ;
if ( ! src ) {
out . push ( fail ( 'D-existence' , 'error' , ` No demo page at ${ demoPath } ` ) ) ;
return out ;
}
// D-1.1: data-uix-canvas-inner outer
if ( /<div\s+data-uix-canvas-inner\b/ . test ( src ) ) out . push ( pass ( 'D-1.1' , 'error' ) ) ;
else out . push ( fail ( 'D-1.1' , 'error' , 'Missing outer <div data-uix-canvas-inner>' ) ) ;
// D-1.2: Tab union exact
const tabUnionRe = /type Tab\s*=\s*'live'\s*\|\s*'api'\s*\|\s*'morfo'\s*\|\s*'sema'\s*\|\s*'recipe'\s*\|\s*'a11y'/ ;
if ( tabUnionRe . test ( src ) ) out . push ( pass ( 'D-1.2' , 'error' ) ) ;
else out . push ( fail ( 'D-1.2' , 'error' , 'Tab union not matching canonical 6-tab template' ) ) ;
// D-1.3: imports compileMorfo
if ( /import\s*\{\s*compileMorfo\b/ . test ( src ) ) out . push ( pass ( 'D-1.3' , 'error' ) ) ;
else out . push ( fail ( 'D-1.3' , 'error' , 'No `import { compileMorfo }`' ) ) ;
// D-1.5: MutationObserver
if ( info . interactive ) {
if ( /new\s+MutationObserver/ . test ( src ) ) out . push ( pass ( 'D-1.5' , 'error' ) ) ;
else out . push ( fail ( 'D-1.5' , 'error' , 'No MutationObserver for trace strip' ) ) ;
}
// D-1.6: stage between header and tablist
if ( /data-uix-stage\b/ . test ( src ) ) out . push ( pass ( 'D-1.6' , 'error' ) ) ;
else out . push ( fail ( 'D-1.6' , 'error' , 'No <div data-uix-stage>' ) ) ;
// D-1.7: trace strip for interactive
if ( info . interactive ) {
if ( /data-uix-stage-trace\b/ . test ( src ) ) out . push ( pass ( 'D-1.7' , 'error' ) ) ;
else out . push ( fail ( 'D-1.7' , 'error' , 'No <div data-uix-stage-trace>' ) ) ;
}
// D-2.x: header
const hasEyebrow = /data-uix-eyebrow\b/ . test ( src ) ;
const hasTitle = /data-uix-page-title\b/ . test ( src ) ;
const hasLede = /data-uix-page-lede\b/ . test ( src ) ;
const hasMeta = /data-uix-page-meta\b/ . test ( src ) ;
if ( hasEyebrow ) out . push ( pass ( 'D-2.1' , 'error' ) ) ;
else out . push ( fail ( 'D-2.1' , 'error' , 'Missing data-uix-eyebrow' ) ) ;
if ( hasTitle && hasLede ) out . push ( pass ( 'D-2.2' , 'error' ) ) ;
else out . push ( fail ( 'D-2.2' , 'error' , 'Missing title or lede in header' ) ) ;
if ( hasMeta ) out . push ( pass ( 'D-2.3' , 'error' ) ) ;
else out . push ( fail ( 'D-2.3' , 'error' , 'Missing data-uix-page-meta pills' ) ) ;
// D-3.1: both snippets derived
const hasSomaSnip = /somaSnippet\b/ . test ( src ) ;
const hasEidosSnip = /eidosSnippet\b/ . test ( src ) ;
if ( hasSomaSnip && hasEidosSnip ) out . push ( pass ( 'D-3.1' , 'error' ) ) ;
else out . push ( fail ( 'D-3.1' , 'error' , ` Missing ${ ! hasSomaSnip ? 'somaSnippet' : '' } ${ ! hasEidosSnip ? 'eidosSnippet' : '' } ` . trim ( ) ) ) ;
// D-4.3: Play buttons emit via uix.events.emit
if ( info . interactive ) {
const hasPlay = /uix\.events\??\.emit/ . test ( src ) || /events\.emit\(/ . test ( src ) ;
if ( hasPlay ) out . push ( pass ( 'D-4.3' , 'error' ) ) ;
else out . push ( fail ( 'D-4.3' , 'warn' , 'No uix.events.emit(...) — Play buttons may not be wired' ) ) ;
}
return out ;
}
function checkReadme ( kebab : string , info : ComponentReport ) : CheckResult [ ] {
const out : CheckResult [ ] = [ ] ;
const path = join ( EIDOS_DIR , kebab , 'README.md' ) ;
const src = tryRead ( path ) ;
if ( ! src ) return out ; // E-2.3 catches
// F-1.1 Baseline
if ( /##\s*Baseline\b/m . test ( src ) ) out . push ( pass ( 'F-1.1' , 'error' ) ) ;
else out . push ( fail ( 'F-1.1' , 'error' , 'Missing ## Baseline section' ) ) ;
date-range-picker: expand morfo + README → PASS audit
Closes incidencia 2026-05-20 #7 plus the remaining audit gaps:
- Morfo now declares the full event surface for the picker layer:
`open` (emerge, with intent fromProp), `close-range-commit` (commit
fulfill), `close-cancel` / `close-dismiss` / `close-dismiss-outside`
(emerge), and `commit-clear` (commit reset). Adds `data-last-action`
on Calendar for causal exit tinting, plus `aria-label`, archetype
classification, keyboard bindings (Enter/Space on Trigger; Escape +
Tab focus on Calendar), and `apg` URL pointing at the W3C dialog-modal
pattern.
- `texts.label` added with full catalog entry under
`src/uix/langs/components/date-range-picker.ts` so A-1.3 passes.
- README rewritten with the required structure: Baseline (air / terra /
soma actual), Comparativa table (React Aria / Bits UI / Ark UI),
Decisiones, and Gaps with explicit disposition markers
(implementar/diferir/descartar). The pending modal mode + clear
button + demo control prune go in Gaps as `implementar`/`diferir`.
- Fix a pre-existing audit-script regex bug: section-extraction
regexes used `m` flag with `$` lookahead, which matched end-of-line
and truncated section bodies to their headers. Switched to
`\n## ` as the explicit section boundary.
Audit: date-range-picker BROKEN → PASS. Project-wide BROKEN count
0 (was 2 before, now tooltip remains as NEEDS-WORK).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// F-1.2 Comparativa (3+ external references).
// Use `\n## ` as the section terminator. The previous regex used `$` with
// the `m` flag, which matched end-of-line and truncated the section body
// to just the header.
const compMatch = src . match ( /##\s*Comparativa\b[\s\S]*?(?=\n##\s|$)/ ) ;
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if ( compMatch ) {
// Count rows in the comparison table
const rows = compMatch [ 0 ] . match ( /^\|/gm ) || [ ] ;
const refCount = Math . max ( 0 , rows . length - 2 ) ; // header + separator
if ( refCount >= 3 ) out . push ( pass ( 'F-1.2' , 'error' , ` ${ refCount } references ` ) ) ;
else out . push ( fail ( 'F-1.2' , 'error' , ` Only ${ refCount } references in ## Comparativa (need ≥3) ` ) ) ;
} else {
out . push ( fail ( 'F-1.2' , 'error' , 'Missing ## Comparativa section' ) ) ;
}
// F-1.3 Decisiones
if ( /##\s*Decisiones\b/m . test ( src ) ) out . push ( pass ( 'F-1.3' , 'warn' ) ) ;
else out . push ( fail ( 'F-1.3' , 'warn' , 'Missing ## Decisiones section' ) ) ;
date-range-picker: expand morfo + README → PASS audit
Closes incidencia 2026-05-20 #7 plus the remaining audit gaps:
- Morfo now declares the full event surface for the picker layer:
`open` (emerge, with intent fromProp), `close-range-commit` (commit
fulfill), `close-cancel` / `close-dismiss` / `close-dismiss-outside`
(emerge), and `commit-clear` (commit reset). Adds `data-last-action`
on Calendar for causal exit tinting, plus `aria-label`, archetype
classification, keyboard bindings (Enter/Space on Trigger; Escape +
Tab focus on Calendar), and `apg` URL pointing at the W3C dialog-modal
pattern.
- `texts.label` added with full catalog entry under
`src/uix/langs/components/date-range-picker.ts` so A-1.3 passes.
- README rewritten with the required structure: Baseline (air / terra /
soma actual), Comparativa table (React Aria / Bits UI / Ark UI),
Decisiones, and Gaps with explicit disposition markers
(implementar/diferir/descartar). The pending modal mode + clear
button + demo control prune go in Gaps as `implementar`/`diferir`.
- Fix a pre-existing audit-script regex bug: section-extraction
regexes used `m` flag with `$` lookahead, which matched end-of-line
and truncated section bodies to their headers. Switched to
`\n## ` as the explicit section boundary.
Audit: date-range-picker BROKEN → PASS. Project-wide BROKEN count
0 (was 2 before, now tooltip remains as NEEDS-WORK).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
// F-1.4 Gaps with disposition. Same regex fix as Comparativa above.
const gapsMatch = src . match ( /##\s*Gaps\b[\s\S]*?(?=\n##\s|$)/ ) ;
uix: date-picker + date-range-picker components + component audit infra
Two new Eidos components shipped end-to-end (wrapper + recipe + demo +
Soma provider hardening) plus a checklist-driven audit pipeline that
scores all 67 morfo components against doctrinal completion criteria.
New components:
- date-picker: full popover-anchored picker over date-field + calendar,
with calendar/content/trigger parts and demo route.
- date-range-picker: standalone wrapper with own calendar/grid/segment
surface, demo route, and recipe CSS.
- Both wrappers follow Option C disciplined (root + parts attached via
explicit assignment, no Object.assign).
Supporting Soma changes:
- range-calendar provider tightened (211 LOC of behavior, 167 LOC of
tests), README brought up to component doctrine.
- date-field, date-picker, date-range-field, date-range-picker Soma
providers + READMEs updated for new wrappers.
- popover provider/close gain props needed by the picker wrappers.
Morfo updates:
- date-picker / date-range-picker / range-calendar morfos refined for
the new APIs (parts, events, ARIA).
Audit infrastructure (new):
- src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across
morfo / eidos wrapper / recipe CSS / demo / README / cross-layer
scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION
and DEMO_AUTHORING_GUIDE.
- scripts/component-audit.ts + `npm run component:audit` — regex parser
over all 67 components, emits tmp/component-audit.md with summary
scoreboard + per-component findings. Validates against canonical
SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS.
- Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip,
date-range-picker). Top systemic gaps: translations.label (49),
README Gaps/Comparativa/Baseline sections (87 combined), keyboard
/event ratio under-declaration (15), apg URL absent (19).
Misc:
- src/uix/kimi-audit-eidos.md — supplementary audit notes.
- .gitignore: ignore .codex-* agent scratch artifacts at repo root.
- continue.md + READMEs updated through the migration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
if ( gapsMatch ) {
const dispositions = ( gapsMatch [ 0 ] . match ( /\b(implementar|diferir|descartar)\b/gi ) || [ ] ) . length ;
if ( dispositions >= 1 ) out . push ( pass ( 'F-1.4' , 'error' , ` ${ dispositions } disposition markers ` ) ) ;
else out . push ( fail ( 'F-1.4' , 'error' , '## Gaps lacks disposition markers (implementar/diferir/descartar)' ) ) ;
} else {
out . push ( fail ( 'F-1.4' , 'error' , 'Missing ## Gaps section' ) ) ;
}
// F-1.5: passive components must justify
if ( info . passive ) {
if ( /##\s*Passive\s+justification\b/im . test ( src ) ) {
out . push ( pass ( 'F-1.5' , 'error' ) ) ;
} else {
out . push ( fail ( 'F-1.5' , 'error' , 'Passive component lacks ## Passive justification' ) ) ;
}
}
return out ;
}
// ─── Build report per component ─────────────────────────────────────────────
function buildReport ( kebab : string ) : ComponentReport {
const morfoPath = join ( MORFO_DIR , ` ${ kebab } .ts ` ) ;
const eidosDir = join ( EIDOS_DIR , kebab ) ;
const demoPath = join ( DEMO_DIR , kebab , '+page.svelte' ) ;
const morfoSrc = tryRead ( morfoPath ) ? ? '' ;
// Classify: interactive if has events or keyboard keys
const eventCount = countEventBlocks ( morfoSrc ) ;
const keyCount = [ . . . morfoSrc . matchAll ( KEYBOARD_KEY_RE ) ] . length ;
const interactive = eventCount > 0 || keyCount > 0 ;
const passive = ! interactive ;
const report : ComponentReport = {
component : morfoSrc.match ( /\bname:\s*['"]([^'"]+)['"]/ ) ? . [ 1 ] ? ? kebab ,
kebab ,
exists : {
morfo : morfoSrc.length > 0 ,
eidos : existsSync ( eidosDir ) ,
demo : existsSync ( demoPath )
} ,
interactive ,
passive ,
checks : [ ]
} ;
if ( morfoSrc ) report . checks . push ( . . . checkMorfo ( kebab , morfoSrc , report ) ) ;
if ( report . exists . eidos ) {
report . checks . push ( . . . checkEidos ( kebab , report ) ) ;
report . checks . push ( . . . checkRecipe ( kebab , report ) ) ;
report . checks . push ( . . . checkReadme ( kebab , report ) ) ;
}
if ( report . exists . demo ) report . checks . push ( . . . checkDemo ( kebab , report ) ) ;
return report ;
}
// ─── Verdict ────────────────────────────────────────────────────────────────
function verdict ( r : ComponentReport ) : 'PASS' | 'NEEDS-WORK' | 'BROKEN' {
const errors = r . checks . filter ( ( c ) = > ! c . passed && c . severity === 'error' ) . length ;
const warns = r . checks . filter ( ( c ) = > ! c . passed && c . severity === 'warn' ) . length ;
if ( errors > 5 ) return 'BROKEN' ;
if ( errors > 0 || warns > 3 ) return 'NEEDS-WORK' ;
return 'PASS' ;
}
// ─── Markdown rendering ─────────────────────────────────────────────────────
function renderMarkdown ( reports : ComponentReport [ ] ) : string {
const lines : string [ ] = [ ] ;
lines . push ( '# UIX component audit report' ) ;
lines . push ( '' ) ;
lines . push ( ` > Generated by \` scripts/component-audit.ts \` against \` src/uix/COMPONENT_COMPLETION_CHECKLIST.md \` . ` ) ;
lines . push ( ` > Date: ${ new Date ( ) . toISOString ( ) } ` ) ;
lines . push ( '' ) ;
// Summary table
const passCount = reports . filter ( ( r ) = > verdict ( r ) === 'PASS' ) . length ;
const needsCount = reports . filter ( ( r ) = > verdict ( r ) === 'NEEDS-WORK' ) . length ;
const brokenCount = reports . filter ( ( r ) = > verdict ( r ) === 'BROKEN' ) . length ;
lines . push ( '## Summary' ) ;
lines . push ( '' ) ;
lines . push ( ` - **Total components audited**: ${ reports . length } ` ) ;
lines . push ( ` - **PASS**: ${ passCount } ` ) ;
lines . push ( ` - **NEEDS-WORK**: ${ needsCount } ` ) ;
lines . push ( ` - **BROKEN**: ${ brokenCount } ` ) ;
lines . push ( '' ) ;
// Sorted: BROKEN first, then NEEDS-WORK, then PASS
const order = { BROKEN : 0 , 'NEEDS-WORK' : 1 , PASS : 2 } ;
reports . sort ( ( a , b ) = > order [ verdict ( a ) ] - order [ verdict ( b ) ] || a . kebab . localeCompare ( b . kebab ) ) ;
lines . push ( '## Component scoreboard' ) ;
lines . push ( '' ) ;
lines . push ( '| Component | Verdict | Errors | Warns | Demo | Eidos | Type |' ) ;
lines . push ( '| --- | --- | ---: | ---: | :-: | :-: | --- |' ) ;
for ( const r of reports ) {
const errors = r . checks . filter ( ( c ) = > ! c . passed && c . severity === 'error' ) . length ;
const warns = r . checks . filter ( ( c ) = > ! c . passed && c . severity === 'warn' ) . length ;
const v = verdict ( r ) ;
const demo = r . exists . demo ? '✓' : '—' ;
const eidos = r . exists . eidos ? '✓' : '—' ;
const type = r . passive ? 'passive' : 'interactive' ;
lines . push ( ` | \` ${ r . kebab } \` | ** ${ v } ** | ${ errors } | ${ warns } | ${ demo } | ${ eidos } | ${ type } | ` ) ;
}
lines . push ( '' ) ;
// Detail per component
lines . push ( '## Detail per component' ) ;
lines . push ( '' ) ;
for ( const r of reports ) {
const v = verdict ( r ) ;
const errors = r . checks . filter ( ( c ) = > ! c . passed && c . severity === 'error' ) ;
const warns = r . checks . filter ( ( c ) = > ! c . passed && c . severity === 'warn' ) ;
if ( v === 'PASS' && errors . length === 0 && warns . length === 0 ) continue ;
lines . push ( ` ### ${ r . kebab } — ${ v } ` ) ;
lines . push ( '' ) ;
lines . push (
` - Morfo: ${ r . exists . morfo ? '✓' : '—' } · Eidos wrapper: ${ r . exists . eidos ? '✓' : '—' } · Demo: ${ r . exists . demo ? '✓' : '—' } · Type: ${ r . interactive ? 'interactive' : 'passive' } `
) ;
lines . push ( '' ) ;
if ( errors . length ) {
lines . push ( '**Errors:**' ) ;
lines . push ( '' ) ;
for ( const c of errors ) {
const filtered = minSeverity && severityRank ( minSeverity ) > severityRank ( 'error' ) ;
if ( filtered ) continue ;
lines . push ( ` - \` ${ c . rule } \` — ${ c . detail } ` ) ;
}
lines . push ( '' ) ;
}
if ( warns . length && ( ! minSeverity || severityRank ( minSeverity ) <= severityRank ( 'warn' ) ) ) {
lines . push ( '**Warnings:**' ) ;
lines . push ( '' ) ;
for ( const c of warns ) {
lines . push ( ` - \` ${ c . rule } \` — ${ c . detail } ` ) ;
}
lines . push ( '' ) ;
}
}
return lines . join ( '\n' ) ;
}
function severityRank ( s : Severity ) : number {
return s === 'error' ? 0 : s === 'warn' ? 1 : 2 ;
}
// ─── Main ───────────────────────────────────────────────────────────────────
function main ( ) : void {
const components = listComponents ( ) ;
const reports : ComponentReport [ ] = [ ] ;
for ( const c of components ) {
try {
reports . push ( buildReport ( c . kebab ) ) ;
} catch ( err ) {
console . error ( ` [component-audit] Failed on ${ c . kebab } : ` , err ) ;
}
}
const md = renderMarkdown ( reports ) ;
mkdirSync ( dirname ( OUT ) , { recursive : true } ) ;
writeFileSync ( OUT , md ) ;
// Console summary
const broken = reports . filter ( ( r ) = > verdict ( r ) === 'BROKEN' ) . length ;
const needs = reports . filter ( ( r ) = > verdict ( r ) === 'NEEDS-WORK' ) . length ;
const pass = reports . filter ( ( r ) = > verdict ( r ) === 'PASS' ) . length ;
console . log ( ` [component-audit] ${ reports . length } components ` ) ;
console . log ( ` PASS: ${ pass } ` ) ;
console . log ( ` NEEDS-WORK: ${ needs } ` ) ;
console . log ( ` BROKEN: ${ broken } ` ) ;
console . log ( ` Report: ${ OUT } ` ) ;
// Exit non-zero if any BROKEN
process . exit ( broken > 0 ? 1 : 0 ) ;
}
main ( ) ;