diff --git a/continue.md b/continue.md index c742568ad..87bd03bad 100644 --- a/continue.md +++ b/continue.md @@ -157,6 +157,24 @@ Actualizacion Eidos recipe contract 2026-05-16: - `npx vitest run src/uix/eidos` -> 5 archivos, 59 tests OK. - `npm run check` -> 0 errores, 0 warnings. +Actualizacion Eidos component API 2026-05-16: + +- Corregida documentacion stale de `Collapsible`: ya no describe dos shapes, + ni flat con snippet `trigger`, ni `` publico. La + superficie vigente es `` + `.Trigger` + `.Content`; el provider + de Soma queda como detalle interno. +- Corregido comentario stale en `Dialog.Close`: no existe un auto-render flat + de ``. +- Guardia nueva `src/uix/eidos/component-api-contract.test.ts`: + - barrels de componentes sin `Object.assign`; + - sin export publico de `Provider`; + - sin imports publicos desde `*-provider.svelte`; + - cada componente publico, salvo `svg` primitives, tiene root + `{component}.svelte`. +- Validacion: + - `npx vitest run src/uix/eidos` -> 6 archivos, 61 tests OK. + - `npm run check` -> 0 errores, 0 warnings. + Actualizacion 2026-05-16: - Barrido de wrappers publicos Soma frente a providers cerrado: diff --git a/src/uix/eidos/component-api-contract.test.ts b/src/uix/eidos/component-api-contract.test.ts new file mode 100644 index 000000000..5b4a1a4df --- /dev/null +++ b/src/uix/eidos/component-api-contract.test.ts @@ -0,0 +1,51 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' + +const COMPONENTS_DIR = 'src/uix/eidos/components' +const ROOT_FILE_EXCEPTIONS = new Set(['svg']) + +function stripComments(source: string): string { + return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1') +} + +function readComponentDirs(): readonly string[] { + return readdirSync(COMPONENTS_DIR, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .sort() +} + +function readIndexSource(component: string): string { + return stripComments(readFileSync(join(COMPONENTS_DIR, component, 'index.ts'), 'utf8')) +} + +describe('Eidos component API contract', () => { + it('keeps component barrels on the disciplined option C surface', () => { + const violations: string[] = [] + + for (const component of readComponentDirs()) { + const source = readIndexSource(component) + + if (/\bObject\.assign\s*\(/.test(source)) { + violations.push(`${component}: Object.assign`) + } + if (/export\s+(?:type\s+)?\{[^}]*\bProvider\b[^}]*\}/s.test(source)) { + violations.push(`${component}: public Provider export`) + } + if (/from\s+['"]\.\/.*provider\.svelte['"]/.test(source)) { + violations.push(`${component}: provider component import`) + } + } + + expect(violations).toEqual([]) + }) + + it('keeps every public Eidos component rooted at {component}.svelte', () => { + const missingRoots = readComponentDirs() + .filter((component) => !ROOT_FILE_EXCEPTIONS.has(component)) + .filter((component) => !existsSync(join(COMPONENTS_DIR, component, `${component}.svelte`))) + + expect(missingRoots).toEqual([]) + }) +}) diff --git a/src/uix/eidos/components/collapsible/README.md b/src/uix/eidos/components/collapsible/README.md index 84c4841e3..317c27517 100644 --- a/src/uix/eidos/components/collapsible/README.md +++ b/src/uix/eidos/components/collapsible/README.md @@ -1,53 +1,29 @@ # `eidos/components/collapsible/` -Disclosure pattern: trigger + content with open/closed state. +Disclosure pattern: trigger + content with open/closed state. Eidos exposes +one public shape: root plus attached child parts. The root wraps Soma's +provider internally, but `Provider` is not part of the Eidos API. -## Dos shapes (per guide §13) - -**Flat ergonomic — el 90% case.** Default export. Auto-compone Provider - -- Trigger + Content vía un `trigger` snippet: +## API pública ```svelte - {#snippet trigger()}Show details{/snippet} -

The disclosure body.

-
-``` - -**Compound — casos avanzados.** Cuando los parts viven en distintos -subtrees del DOM, hay múltiples triggers, o el content se renderea -condicionalmente: - -```svelte - - -
- - Show details - -
- +
``` -Y si ningún shape encaja dentro de Eidos, baja al Soma -`$soma/components/collapsible` y al contrato morfo correspondiente -para crear un wrapper nuevo. **Headless + morfo son la primitiva -universal**; eidos es el atajo opinionado del design system, no un -wrapper bloqueante. +Si esta composición no encaja, baja al Soma `$soma/components/collapsible` +y al contrato morfo correspondiente para crear otro wrapper. Headless + +morfo son la primitiva universal; Eidos es la capa visual, no un fork del +comportamiento. ### Props @@ -66,8 +42,8 @@ Pure pass-through from the Soma. No eidos-specific props. - **No `intent` / `color`** — disclosure es no-evaluativo per guide §3.1 (Accordion no aparece en la tabla de subsets, default neutral). - **No `size`** — air no lo tenía; sin regresión. -- **Multi-part API**: `Provider + Trigger + Content`. NO flat default - per doctrine §10 (solo single-part exports flat). +- **Multi-part API**: `` + `Trigger` + `Content`. Sin `Provider` + publico y sin flat con snippet slots. ## Eventos sema @@ -101,11 +77,11 @@ Ambos `emerge` (transitional family — sin intent), `sequence='pre'` ``` collapsible/ collapsible.css → recipe (selectores [data-collapsible*]) - collapsible-provider.svelte → wrapper sobre headless Provider + collapsible.svelte → root visual sobre el provider headless collapsible-trigger.svelte → wrapper sobre headless Trigger collapsible-content.svelte → wrapper sobre headless Content types.ts → re-exports headless + JSDoc - index.ts → barrel multi-part (Provider/Trigger/Content) + index.ts → barrel Option C (Root/Trigger/Content) README.md → este archivo ``` diff --git a/src/uix/eidos/components/collapsible/collapsible.svelte b/src/uix/eidos/components/collapsible/collapsible.svelte index 9658e70ba..7dfef28a3 100644 --- a/src/uix/eidos/components/collapsible/collapsible.svelte +++ b/src/uix/eidos/components/collapsible/collapsible.svelte @@ -1,8 +1,8 @@