`morfo.md` §Step 2 — la guía de AUTORÍA, donde el autor decide — documentaba
sólo `'public'` y `'virtual'` y omitía `'private'`. La sección de referencia sí
tenía los tres, pero nadie declara partes leyendo la referencia.
El criterio que queda escrito: **lo que hace pública a una parte es que el
consumidor la decida, con independencia de la sintaxis**. La composición
(`<Dialog.Trigger>`) es la vía habitual, no la única: un primitivo de API plana
gobierna sus partes por props y snippets — `<Badge dot>` decide la parte Dot,
`icon` la parte Icon. Ambas son públicas. `'private'` queda para lo que nada de
lo que escribe el consumidor alcanza (el Track de `Spinner`, el Sentinel de
`Sticky`).
Importa el matiz porque la primera versión de esta nota decía lo contrario —
que el test era mecánico y que sin `<Componente.Parte>` la parte no es pública
— y eso habría reclasificado mal a Badge y a todo primitivo de API plana.
## Handoff: tres hallazgos retirados
Los tres salieron de heurísticas de una sola vía dadas por hechas sin abrir el
código. Es el mismo fallo que la auditoría denuncia en los guards, cometido al
redactar el informe:
- **D-3 `AgentTimersPort.once`** — el puerto SÍ está satisfecho:
`defineActiveAgent()` adapta `core.timers.schedule` a `once` en la raíz de
composición, que es el patrón previsto. No había nada roto.
- **«5 derivas de scope»** — `avatar-group`, `path-trace` y `rotate-align` están
implementados; el framework co-loca sub-componentes dentro del directorio del
padre y la heurística asumía «directorio == kebab».
- **«29 morfos con `kind:'public'` irreal»** — medía si existe
`<Componente.Parte>` e ignoraba props y snippets. Sin recuento fiable hasta
cruzar las tres vías.
Añadido: `npm run lint` nunca ha pasado. Separando el ruido CRLF del checkout
Windows (`--end-of-line auto`) quedan 1544 ficheros con deriva real de
contenido. Es decisión del usuario porque el arreglo toca todo el repo de golpe.
Y la lección de método: **un analizador que no puede resolver algo tiene que
decir «no verificado», no emitir un hallazgo.**
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes the provider→recipe direction of the CSS-var drift stumble the
architecture-faithful way: the morfo is the source of truth. A component now
declares the functional custom properties its soma provider writes and its eidos
recipe reads:
cssVars: [{ name: 'progress', … }, { name: 'angle', … }] // → --knob-progress, --knob-angle
- types: `MorfoCssVar` + optional `cssVars` on `Morfo` (additive; the 133
existing morfos are unaffected).
- schema: validates `cssVars` (array of `{ name, description? }`).
- compile: exposes `contracts.cssVars` as full names `--{kebab}-{name}`.
- knob morfo declares its two contract vars.
- guard (recipe-css-contract.test.ts): enumerates morfos via `import.meta.glob`
and verifies BOTH sides honour each declaration — the provider writes
`--{kebab}-{name}` AND the recipe reads it — so renaming one side without the
other fails loudly. Proven non-vacuous (a bogus cssVar is flagged on both
sides).
Why A1 and not a grep cross-check (A2): in the CSS a provider-written functional
var and a consumer-override alias are indistinguishable, and providers publish
hook vars the recipe doesn't consume — a grep gives ~17 false positives (proven
earlier). The morfo declaration is what disambiguates.
npm run check 59 (baseline, 0 in touched files); eidos recipe-css-contract 24/24;
morfo suite green (only the pre-existing dialog-role test fails); docs:check 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The common ARIA pattern "emit aria-label only when there is no Label part"
was not declarable. Add `{ when: 'part-absent', part }` as the logical
inverse of `part-present`, plumbed through the four condition sites:
- types.ts: MorfoCondition union member + JSDoc.
- schema.ts: discriminated-union member (structural accept) + extend the
checkCondition unknown-part guard to cover part-absent (throws on unknown
part, same as part-present — the prior guard only discriminated part-present).
- compile.ts: collectConditionDeps adds the referenced part to partRefs, so
the runtime re-evaluates when the part's presence toggles.
- resolver.ts: shouldEmitMorfoEntry returns !bindings.parts?.[part] — emit
when the part is absent/falsy, suppress when present.
Runtime + keyboard paths delegate to these helpers (evalAttrPlan /
shouldEmitMorfoEntry), so no runtime change is needed. Correct by symmetry
with part-present, which reads the same presence-accurate bindings.parts.
Tests: schema accepts a part-absent condition on a known part and throws on
an unknown part; the compiler collects the part dep; the resolver emits the
value when the part is absent and undefined when present.
Docs: morfo.md condition list gains the part-absent form.
Non-breaking: no existing morfo uses it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An agent building a component from the docs-book alone could not assign
archetypes / holds / haptic kinds: the closed sets live only in code consts
and the corpus (correctly) forbids copying them into prose, so they were
invisible from the docs. STUMBLES #1, the top stumble.
Fix: docs/canon/vocabularies.md is GENERATED from the consts by
'npm run docs:vocabularies' — the ONE sanctioned place the lists are spelled
out (generated = no drift objection), which every other doc links to. It
covers part archetypes (with a one-line role each), sema families with their
default hold/persistence + per-intent overrides, verbs by family, intents,
the perceptual-duration scale, haptic kinds, the 33 palette scales, sizes,
variant archetypes, and the shared common.* strings.
- scripts/docs-vocabularies.ts imports the consts (robust — no fragile
JSDoc/regex parsing) and formats them; the generation is an exported
function so docs-check can compare.
- Added ARCHETYPE_DESCRIPTIONS to morfo/types.ts (co-located with
ARCHETYPE_VOCABULARY, satisfies Record<MorfoArchetype,string>) as the
authoritative one-liners the appendix reads — descriptions become
first-class data instead of inline union JSDoc a tool would have to parse.
- docs-check gains I7: it regenerates in-memory and fails if the committed
file drifts from the consts (negative-tested: corrupt -> error, regen ->
green). The appendix's own counts pass I1, so I1 doubles as a second guard.
- Linked from README (E2 canon row + two 'I want to' shortcuts), CANON.md
(doctrine here, enumerated lists there), architecture/morfo.md (pick a
part's archetype), and the component-audit minimum-package list (STUMBLES
#8).
docs:check 0 errors; npm run check unchanged in my files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two user findings from the Knob build exercise (an agent building a new
component from the docs alone — STUMBLES.md).
Quarantine: the superseded fossils no longer share shelf space with the
live corpus. docs/old-deprecated/ (with an index README explaining what
lands there and pointing readers at docs/README.md) now holds the
executed audits and fix plans: fable_audit, fable-eidos-audit,
inherit_audit + inherit_fix_plan, ARCHETYPE_COHERENCE_AUDIT_2026-06-19
(still citable — the component-guide banner and the eidos components
README repoint to it), COMPONENT_COHERENCE_AUDIT. pendiente.md (a live
pending list, not a fossil) moved to docs/process/. docs-check treats
the folder as sealed chronicle (I1/I2 exempt; I6 skips its internal
links, as its README promises). Root-level *.md is now: README, CLAUDE,
AGENTS + the user's own working files.
STUMBLES fixes applied on the spot (the doc-class ones):
- #2 kind drift: the REAL enum is 'public' | 'private' | 'virtual'
(MorfoPartKind, 671/9/14 uses) — morfo.md omitted 'private', the
checklist invented 'internal' (0 uses). Both fixed; I2 gains the
phantom-'internal' guard. A-2.1's row now says what the audit script
actually checks (kebab only — the archetype may vary, the Toggle
provider-IS-trigger doctrine).
- #6: the langs catalog SHAPE (flat keys, per-language leaves, named
export, index registration) is now shown in morfo.md instead of only
its location.
- #8: component-audit s0 defines the minimum brief package as an
explicit 8-file list.
The engineering-class stumbles are registered as plan batches S1-S6
(generated vocabularies appendix, continuous-gesture trigger doctrine,
part-absent condition, Gesture.rotate, the soma->eidos CSS-var
contract, minor frictions). docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The global sweep caught the last corpus links still pointing at
pre-book paths: morfo chapter's s2.bis cross-ref (src/uix/README ->
overview.md), authoring's E1 stratum row (active_architecture ->
docs/architecture/), the related: frontmatter of decisions.md and
canon/recipe-contract.md (THEMING/THEMING_GUIDE -> theming/reference +
guide), and component-audit's typographic-vertebration pointer (eidos
README -> architecture/eidos.md with the translated heading). README
map completed as the book TOC: every chapter now has a row —
theming/motion.md (the model) and theming/changelog.md (the chronicle)
were the two missing. Remaining old-path references live only in
chronicle docs (theming/changelog, process/) and resolve via stubs;
the 11-warn baseline is all foreign targets (MOTION_SERVICE_RFC demo
routes, deleted THEMING_AUDIT/PENDIENTES).
F7.6 complete. The book tree is fully populated: architecture/ (8) +
canon/ (2) + theming/ (7) + rfcs/ (7) + guides/ (4) + decisions/ (2).
Next and last: F7.7 CLAUDE.md thinning — diff to the user first.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LIBRO_VARIACIONES_Y_EXTENSIONES -> decisions/book-deviations.md and
GUIA_IMPLEMENTACION_SEMAUIX -> decisions/guia-semantica-historica.md,
both moved AS-IS in Spanish: the deviations registry is a logbook of the
author's literal decisions ('transcrita literal') and carries proposed
doctrinal text destined for the Spanish book — translating it would
destroy that function (s G calls itself bitacora); the guia was already
status: historical (Fase 6) and the plan exempts it explicitly. English
frontmatter added to both; internal cross-links repointed (CANON,
theming/reference, book-deviations D.11). docs-check's I2 phantom-field
exemption follows the moved file (it matched by the LIBRO_VARIACIONES
filename; now also matches decisions/book-deviations.md). Corpus swept:
CANON x3, README (E2/E3 strata + tables — also fixed the pre-Fase-6
leftover row still calling the guia 'authoritative for any new wrapper'
and the unswept eidos/TSC.md stratum mention), building-a-component D.4,
completion-checklist G-1.1 + header, theming/reference, architecture
x5 (active-architecture, eidos, morfo, overview, sema x2).
docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
src/uix/soma/COMPONENT_GUIDE.md (1449 L, English body) moved to
docs/guides/component-guide.md. The only Spanish passage — the
archetype-canon banner (2026-06-19) — translated in the move; the rest
is verbatim. Banner's ARCHETYPE_COHERENCE_AUDIT link reaches into src
(audit fossil, stays with the code); the checklist cross-link is now
the sibling ./completion-checklist.md and the 'soma/README s9' mention
now names architecture/soma.md (its post-F7.2 home). Corpus swept:
authoring + README E4 stratum rows (also stale THEMING_GUIDE name),
building-a-component phases 0/2, architecture/{soma-architecture s4/
s13-tree/s17, overview, morfo, active-architecture}.
F7.5 complete: docs/guides/ = component-guide, completion-checklist,
demo-authoring, component-audit. docs:check 0 errors, warns back at
the 11 baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>