Phase 1a of PLAN-docs-reconciliation (fable_audit D1). The Morfo field is
texts (idlangrefs); the multilingual catalog lives in langs/components/*.
Fixed every example and mechanism claim that still documented the removed
translations: field: morfo/README (contains-list, anatomy, Step 4.5),
SOMA_ARCHITECTURE (Texto funcional + checklist), soma/README (morfo field
list + runtime registration), COMPONENT_GUIDE (A1/A3 + common mistakes +
smoke fixes), COMPONENT_COMPLETION_CHECKLIST M-1.3, active-uix/README,
uix/README, 4 component READMEs, and 3 stale JSDoc blocks in code
(morfo/types.ts, morfo/schema.ts invariant 7, active-uix/types.ts
registerDefaultLangs).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| M-1.1 | Exports a single `{Name}Morfo` const satisfying `Morfo` | error | all |
| M-1.2 | Has `name`, `kebab`, `scope: ['soma', ...]` declared | error | all |
| M-1.3 | Has `translations.label` with at least `es` + `en` | error | all |
| M-1.3 | Has `texts.label` as a valid idlangref (`#?components.{kebab}.label\|Fallback`), catalog entry in `langs/components/{kebab}.ts` | error | all |
| M-1.4 | If interactive: `apg` URL declared pointing at the relevant W3C ARIA pattern | warn | interactive |
Morfo is the single source of truth for a component's parts, data-attrs, ARIA contract, keyboard shortcuts, focus policy, and public event contract. The same morfo is consumed by soma (to wire the headless provider), by eidos (to generate CSS selectors), by sema (to bind perceptual channels), and by the docs site (to render part tables).
**One file per component**, at `src/uix/morfo/components/{kebab}.ts`. No prose — that's the component's README. No props — those live in `types.ts` with JSDoc. Component-owned translations may live in `translations` when they are part of ARIA labels, live-region text, or internal functional labels. Everything else is the machine-readable contract.
**One file per component**, at `src/uix/morfo/components/{kebab}.ts`. No prose — that's the component's README. No props — those live in `types.ts` with JSDoc. Component-owned text slots may live in `texts` (idlangrefs) when they are part of ARIA labels, live-region text, or internal functional labels — the multilingual catalog itself lives in `src/uix/langs/components/{kebab}.ts`. Everything else is the machine-readable contract.
## Why morfo exists
@ -34,7 +34,7 @@ A `Morfo` is a plain TypeScript constant that describes:
- **`apg`** — optional URL to the WAI-ARIA APG pattern when the component implements a formal one.
- **`focus`** — optional focus policy for overlays / composites.
- **`events`** — the component's public event surface: which semantic occurrences it may emit and expose to cross-layer consumers.
- **`translations`** — optional component-owned translation catalog. Registered under `components.{kebab}` by the morfo registry.
- **`texts`** — optional component-owned text slots, declared as idlangrefs (`'#?components.{kebab}.{key}|Fallback'`). The multilingual catalog itself lives in `src/uix/langs/components/{kebab}.ts` and is registered by `ActiveUix` under `components.{kebab}.*`.
- **`parts`** — the part tree (recursive). Each part declares:
- `name`, `kebab`, `kind` (`public` / `virtual`).
- `archetype` — optional cross-component classification (see "Archetypes" below).
@ -57,7 +57,7 @@ See [`types.ts`](./types.ts) for the full TypeScript shape.
| Props (names, types, defaults) | `{component}/types.ts` with JSDoc | Canonical source is TS + JSDoc |
| Shared/common translations | app/langs catalog under `common.*` | Shared vocabulary should not be duplicated per component |
| Provider-only id constants | optional `{component}/langs.ts` | Constants are code ergonomics; the catalog lives in morfo or app langs |
| Provider-only id constants | optional `{component}/langs.ts` | Constants are code ergonomics; the catalog lives in `langs/components/{kebab}.ts` or app langs |
| Event handlers / runtime wiring, state machines | `{component}-provider.svelte.ts` | Execution logic, not contract data |
8. **Redeclaring field types** — defining prop types in both `types.ts` and the provider Opts interface. Define canonical shapes once in `types.ts`, reference with `StateProps<>` / `ActiveProps<>`.
9. **Hardcoding aria strings** — declare component-owned text in `morfo.translations` and reference it with `v.translationRef`; use `v.commonRef` / idlangref constants for shared imperative labels. Never inline strings in providers.
9. **Hardcoding aria strings** — declare component-owned text slots in `morfo.texts` and reference them with `v.translationRef`; use `v.commonRef` / idlangref constants for shared imperative labels. Never inline strings in providers.
10. **Gesture capturing child clicks** — `setPointerCapture` must be deferred until moveBuffer is exceeded. Immediate capture on pointerdown steals click events from buttons inside the draggable area.
@ -611,8 +611,8 @@ These rules were extracted from a full audit of all 25 soma components. Every is
### A1. Register the morfo through the runtime
Every component MUST register its morfo before it relies on `assertContract`
or morfo-owned translations. The canonical path is to create a runtime:
Every component MUST register its morfo before it relies on `assertContract`.
**Soma access:** Declare `readonly soma = Soma.get()` in the root provider when the component needs prefs-derived services or imperative translations. Sub-parts access soma via `this.provider.soma`.
**Translations:** Component-owned text lives in the morfo, not in a per-component catalog file:
**Texts:** The morfo declares component-owned text slots as idlangrefs; the
multilingual catalog lives in `src/uix/langs/components/{kebab}.ts`:
- Common keys live under `common.*` at the lang root — not under soma
- Component keys live under `components.{name}.*`
- `ActiveUix` registers `commonLangs` defaults without overwriting user-provided leaves
- `ActiveUix` registers `morfo.translations` dynamically when `registerMorfo(morfo)` runs through the runtime
- There is no global per-component catalog. Component-owned strings live in `morfo.translations`; shared strings live in `common.*`.
- `ActiveUix` registers the per-component catalogs from `src/uix/langs/components/*` under `components.{kebab}.*`
- The morfo only declares slots (`morfo.texts`, idlangrefs); the multilingual records live in `langs/components/{kebab}.ts`. Shared strings live in `common.*`.
- `langs.ts()` with idlangref for simple strings. `langs.t()` only for interpolated templates (e.g., `Page {{value}}`)
Do NOT:
- Create `translate()` helper methods in providers
- Use `?? 'fallback'` — the fallback belongs inside the langref (`#?path|fallback`)
- Hardcode aria strings — use `morfo.translations` + `v.translationRef`, `v.commonRef`, or an explicit idlangref constant
- Hardcode aria strings — use `morfo.texts` + `v.translationRef`, `v.commonRef`, or an explicit idlangref constant
- Put common keys (close, open, cancel) under component namespaces — they belong in `common.*`
**Imports within soma:** Use relative paths, not `$soma/` aliases. Relative paths make the library portable without requiring alias configuration in the consumer's build. soma does not import from `$lib` — trivial utilities (like empty callbacks) are inline (`() => {}`).
@ -1234,7 +1235,7 @@ Three classes of bug cannot be caught by `svelte-check` or HTTP 200 — they all
grep -rn "['\"]soma\.[a-z-]" src --include=!*.md
```
Fixes: route component-owned text through `morfo.translations` + `v.translationRef`; route shared text through `v.commonRef` or an explicit idlangref constant. Don't build translation paths via template strings in demos or providers. If a demo needs a dynamic path helper (like `tt('columns', 'Columns')`), hard-code the namespace prefix `components.{name}.` correctly.
Fixes: route component-owned text through `morfo.texts` + `v.translationRef`; route shared text through `v.commonRef` or an explicit idlangref constant. Don't build translation paths via template strings in demos or providers. If a demo needs a dynamic path helper (like `tt('columns', 'Columns')`), hard-code the namespace prefix `components.{name}.` correctly.
2. **DOM topology vs `.require()` audit.** Svelte's context (via `getContext`) flows only to descendants. Every `X.require()` call must be reachable from a descendant of the component that set the context. The trap is HTML: `<tr>` cannot nest `<tr>`, so `Table.RowDetail` (rendered as a sibling `<tr>` of `Table.Row`) cannot `TableRowProvider.require()`. Use one of:
- Receive the object via a prop (consumer passes `{row}` or similar explicitly). This is consistent with `<Table.Row {row}>` / `<Table.Cell {cell}>` — Table already requires explicit objects.
@ -129,7 +129,7 @@ Cada componente tiene un archivo en `src/uix/morfo/components/{kebab}.ts` que de
- **aria** — qué atributos ARIA emite cada parte, con la fuente del valor tipada vía tagged union (`v.literal`, `v.stateRef`, `v.partRef`, `v.propRef`, `v.translationRef`) y condición de emisión opcional.
- **keyboard** — los atajos de teclado relevantes por parte.
- **focus** — política de foco para overlays (`initial`, `trap`, `return`, `restore`).
- **translations** — catálogo de traducciones propio del componente, registrado bajo `components.{kebab}`.
- **texts** — slots de texto propios del componente, declarados como idlangrefs (`'#?components.{kebab}.{key}|Fallback'`). El catálogo multilingüe vive en `src/uix/langs/components/{kebab}.ts`.
- **apg** — URL al patrón WAI-ARIA APG cuando aplica.
- **scope** — las capas que implementan el componente: `['soma']`, `['soma', 'eidos']`, etc.
@ -140,9 +140,9 @@ vive en [`src/uix/morfo/README.md`](../morfo/README.md).
### Cómo soma consume un morfo
Cada provider raíz crea un runtime con su morfo. Ese paso registra el contrato
`data-*`, compila la declaración y publica `morfo.translations` en los `ActiveLangs`
conectados por `ActiveUix`:
Cada provider raíz crea un runtime con su morfo. Ese paso compila la
declaración y registra el contrato `data-*` (los catálogos de texto por
componente los registra `ActiveUix` desde `src/uix/langs/components/*`):
```ts
import { dialogMorfo } from '../../../morfo/components/dialog';
@ -145,7 +145,7 @@ del componente (checklist item 27).
soma resuelve ARIA por defecto. El consumidor no necesita añadir `role`, `aria-modal`, `aria-expanded`, `aria-controls`, etc. — el Provider los genera.
Texto funcional se resuelve via `langs.ts()` con idlangref. El catalogo propio del componente vive en `morfo.translations` y se referencia con `v.translationRef(...)`; texto compartido como close/cancel/save vive en `common.*` y se referencia con `v.commonRef(...)` o un idlangref absoluto. `langs.ts` por componente queda como comodidad opcional para constantes imperativas, no como catalogo canonico.
Texto funcional se resuelve via `langs.ts()` con idlangref. El morfo declara sus slots de texto en `morfo.texts` (idlangrefs) y los referencia con `v.translationRef(...)`; el catalogo multilingue vive en `src/uix/langs/components/{kebab}.ts`. Texto compartido como close/cancel/save vive en `common.*` y se referencia con `v.commonRef(...)` o un idlangref absoluto. `langs.ts` por componente queda como comodidad opcional para constantes imperativas, no como catalogo canonico.
### 3.7 DOM global via ActiveDom
@ -685,14 +685,16 @@ standalone functions. No `from()`. No `ctx` exposed.
### Texto funcional
Las traducciones propias del componente se declaran en el morfo:
Los slots de texto propios del componente se declaran en el morfo como
@ -117,7 +117,7 @@ Focus lands on the first focusable child when the dialog opens (`Cancel` by DOM
## i18n
Default button labels resolve through the UIX lang system. The component catalog is owned by `morfo.translations` under `components.alert-dialog`.
Default button labels resolve through the UIX lang system. The morfo declares the slots in `texts`; the catalog lives in `src/uix/langs/components/alert-dialog.ts` under `components.alert-dialog`.
| Droppable | `tabindex` | `0` during drag if accepting; `-1` otherwise |
Each drag step is announced via the `Announce` global API. Component-owned announcement strings live in the drag-drop `morfo.translations` catalog. Disable with `announceEnabled={false}` on the Provider when wiring your own.
Each drag step is announced via the `Announce` global API. Component-owned announcement slots are declared in the drag-drop `morfo.texts`; the catalog lives in `src/uix/langs/components/drag-drop.ts`. Disable with `announceEnabled={false}` on the Provider when wiring your own.
@ -71,7 +71,7 @@ The per-endpoint slider / toggle wrappers read their endpoint from the required
## ARIA
- Sliders emit `role="slider"` with per-endpoint `aria-label` resolved through UIX lang refs (`morfo.translations` for component-owned labels, `langs.ts` only for legacy imperative constants).
- Sliders emit `role="slider"` with per-endpoint `aria-label` resolved through UIX lang refs (`morfo.texts` for component-owned labels, `langs.ts` only for legacy imperative constants).
- `DayPeriodToggle` emits `role="radiogroup"` with two `<input type="radio">` items.