You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
9.3 KiB
149 lines
9.3 KiB
# STUMBLES — dónde tropieza un agente construyendo el Knob solo con docs
|
|
|
|
Este es el entregable real del ejercicio. Cada punto es un lugar donde tuve
|
|
que adivinar, donde dos docs se contradicen, o donde falta la pieza que un
|
|
agente necesitaría para producir código correcto al primer intento. Ordenado
|
|
por impacto.
|
|
|
|
## Estado de resolución (actualizado 2026-07-03)
|
|
|
|
El Knob del ejercicio **ya está construido de verdad** en las 5 capas
|
|
(morfo/langs/sema/soma/eidos, `c13cad74`) con demo (`8dfd3505`) — lo que valida
|
|
el ejercicio: 7 de los 9 tropiezos se cerraron; quedan 2.
|
|
|
|
| # | Tropiezo | Estado | Cómo |
|
|
|---|---|---|---|
|
|
| 1 | Vocabularios invisibles | ✅ RESUELTO | `npm run docs:vocabularies` → `docs/canon/vocabularies.md` desde los consts + guard docs-check I7 (`116594e0`) |
|
|
| 2 | Drift `kind` public/virtual vs internal | ✅ RESUELTO | `MorfoPartKind = 'public' \| 'private' \| 'virtual'` (types.ts) y checklist A-2.2 alineado |
|
|
| 3 | No hay capa de gesto radial | ✅ RESUELTO | `Gesture.rotate` 4ª especialización en `soma/layers/gesture` (`18b0d44e`); el Knob lo consume |
|
|
| 4 | Doctrina `trigger()` continuo sin cerrar | ✅ RESUELTO | sección `## Continuous components` en `sema.md` (`1da36ca6`), fact-check adversarial |
|
|
| 5 | Falta condición `part-absent` | ✅ RESUELTO | `{ when: 'part-absent', part }` en morfo (`11504043`); el morfo del Knob la usa |
|
|
| 6 | Formato de `langs/components/{kebab}.ts` | ✅ RESUELTO | forma `LangNode` (`{ key: { es, en } }`, anidable, `satisfies`) documentada en morfo/soma/checklist |
|
|
| 7 | CSS-vars del provider sin contrato | ⏳ PENDIENTE | **diseño abierto** — ver abajo |
|
|
| 8 | Docs imprescindibles fuera del paquete | ✅ RESUELTO | `component-audit.md §0` lista el paquete mínimo como archivos exactos |
|
|
| 9 | Fricciones menores (docs) | ⏳ PENDIENTE | 4 notas doc por escribir — ver abajo |
|
|
|
|
## 1. Vocabularios canónicos invisibles desde los docs
|
|
|
|
`ARCHETYPE_VOCABULARY` vive solo en `types.ts` y los docs prohíben
|
|
explícitamente copiar la lista ("a copied list survived at 24 entries while
|
|
the code grew to 26"). Correcto contra el drift humano — pero un agente que
|
|
trabaja desde el docs-book **no puede asignar archetypes**: no sé si existe
|
|
`control`, `handle` o `indicator` para las partes del Knob, y A-2.2 los pide
|
|
en *todas* las partes (error-level en el audit). Lo mismo pasa con
|
|
`SEMA_MAP.families[*].hold` (¿cuánto dura el hold de `handle`?), los `kind`
|
|
categóricos del HapticChannel, y el contenido de `commonLangs`.
|
|
|
|
**Fix sugerido**: apéndice *generado desde código* en el docs-book
|
|
(`npm run docs:vocabularies`). Generado = sin objeción de drift. Para un
|
|
agente, ese apéndice es la diferencia entre declarar y adivinar.
|
|
|
|
## 2. Drift entre docs: `kind: 'public' | 'virtual'` vs `'public' | 'internal'`
|
|
|
|
`morfo.md` define `kind` como `'public' / 'virtual'`; el completion-checklist
|
|
A-2.2 dice `kind: 'public' | 'internal'`. Uno de los dos miente. Un agente
|
|
elegirá según cuál doc leyó último. También: A-2.1 exige que el Provider
|
|
lleve `archetype: 'provider'`, pero morfo.md dice que cuando el Provider ES
|
|
el elemento interactivo (Toggle, Switch) su archetype es `'trigger'` — tal
|
|
como está escrita, la regla del audit marcaría error en Toggle.
|
|
|
|
## 3. No existe capa de gesto radial — gap de framework, flaggeado
|
|
|
|
`layers/` tiene `Gesture.base/drag/resize`, `AxialDrag`, `ZoomPan` — nada
|
|
angular. Siguiendo guide §4 ("flag the gap, never inline bespoke"), lo
|
|
correcto es proponer `Gesture.rotate` (centro + sweep + wrap + detents +
|
|
velocity angular). Lo reutilizarían: AngleSlider, color wheel (hue ring),
|
|
un time-picker de esfera, y este Knob. De momento la matemática vive en el
|
|
provider con un comentario FLAGGED GAP.
|
|
|
|
## 4. La doctrina de `trigger()` para gestos continuos está sin cerrar
|
|
|
|
El guide clasifica la forma "Continuous" (start/commit boundaries) pero no
|
|
hay ejemplo obrado de `handle` + gesture + `trigger()`. Preguntas que no
|
|
pude resolver desde los docs:
|
|
|
|
- Con `sequence: 'post'`, ¿`trigger()` sigue reteniendo el turno hasta que
|
|
el hold del canal visual termina (aunque el handler ya corrió)? Para
|
|
`handle-pick` en un pointerdown eso importa: si el `await` interno vive en
|
|
el mismo turno del gesto, hereda el problema del checkbox-lag.
|
|
- ¿O el camino santo para pick/drop es `provider.emitEvent()`
|
|
(fire-and-forget) en vez de `trigger()`? El guide dice "the provider must
|
|
route semantic actions through runtime.trigger(...)" — que parece prohibir
|
|
emitEvent para acciones perceptuales. Slider ya resolvió esto en código;
|
|
el doc no lo cuenta.
|
|
- ¿`commit-set` en cada paso de teclado satura el SoundChannel si el usuario
|
|
mantiene pulsada la flecha? ¿Hay throttling doctrinal o es responsabilidad
|
|
del pack sema? Ningún doc lo dice.
|
|
|
|
**Fix**: un párrafo "Continuous components" en sema.md con Slider como
|
|
ejemplo obrado, igual que overlays tienen su callout de `sequence: 'post'`.
|
|
|
|
## 5. Falta la condición `part-absent` en morfo
|
|
|
|
El patrón ARIA más común del mundo — "aria-label solo cuando no hay Label" —
|
|
no es declarable: las conditions son `always / part-present / state-equals /
|
|
prop-truthy / prop-falsy`. Tuve que declarar `aria-label` como optional y
|
|
dejar la exclusividad en lógica del provider, que es exactamente el tipo de
|
|
contrato-fuera-del-morfo que el framework existe para eliminar.
|
|
|
|
## 6. El formato del catálogo `langs/components/{kebab}.ts` nunca se muestra
|
|
|
|
Todos los docs referencian su *ubicación* (morfo.md, soma.md, guide A3,
|
|
checklist A-1.3) pero ninguno enseña su *forma*: ¿export nombrado?, ¿record
|
|
`{ key: { en, es } }` o `{ en: { key } }`?, ¿claves anidadas o planas con
|
|
puntos? Mi `knob.ts` de langs es una conjetura. A-1.3 es error-level: un
|
|
agente fallará el audit por un archivo cuyo formato no está documentado en
|
|
el paquete que le diste.
|
|
|
|
## 7. Las CSS custom properties del provider no tienen contrato
|
|
|
|
`--knob-progress` / `--knob-angle` siguen el patrón de `--drawer-progress`,
|
|
pero ese patrón vive solo en prosa (SOMA_ARCHITECTURE §9). El morfo no
|
|
modela CSS vars, eidos-lint no las verifica, y el recipe-css-contract solo
|
|
cubre los alias de `EidosConfig.recipes`. Es la única superficie
|
|
soma→eidos sin defensa contra drift: renombrar `--knob-angle` en el provider
|
|
rompe el recipe en silencio. Candidato a extensión del morfo — y pasa la
|
|
regla 2-de-3 (soma escribe, eidos consume, sema podría leerlas en cascade
|
|
selectors vía `style` no, pero soma+eidos ya son 2).
|
|
|
|
> **Confirmado al construir el Knob (2026-07-03).** El recipe del Knob consume
|
|
> `--knob-progress`/`--knob-angle` (que el provider publica) y además heredó de
|
|
> la plantilla de Fable **tokens de tema fantasma que no existen** —
|
|
> `--color-neutral-content`, `--state-hover`, `--color-neutral-bg` — que el CSS
|
|
> tragaba en silencio (fallback vacío / pointer transparente) y solo se
|
|
> detectaron **mirando el render**, no por ningún guard. Es exactamente el
|
|
> "drift silencioso" que este tropiezo predijo, en dos direcciones: vars
|
|
> provider→recipe **y** recipe→tema. Diseño abierto (S5): declaración `cssVars`
|
|
> en el morfo validada contra escrituras del provider + lecturas del recipe, o
|
|
> un cross-check de `eidos-lint` de vars escritas-por-provider vs consumidas.
|
|
> Decidir antes de implementar.
|
|
|
|
## 8. Dos docs imprescindibles no estaban en el paquete
|
|
|
|
`demo-authoring.md` (el template de 9 tabs está "locked" y es error-level en
|
|
D-1.x — imposible producir un demo conforme sin él) y `CANON.md` (sema.md se
|
|
declara subordinado a él: "if they ever disagree, the code + canon win").
|
|
Un agente con este paquete produce las 4 capas pero no el demo, y no puede
|
|
verificar que el vocabulario que usó es el vigente. Para briefs de agentes,
|
|
el "paquete mínimo" debería estar definido en el propio component-audit.md
|
|
§0 como lista de archivos, no de títulos.
|
|
|
|
## 9. Fricciones menores
|
|
|
|
- **`state<T>()` vs `$state`**: el sistema reactivo documenta `state<T>(initial) → State<T>` pero los ejemplos de provider usan `$state` a secas para campos internos. ¿Cuándo cada uno? Deduje que `State<T>` es para pasar por referencia (labelId) y `$state` para campos locales, pero es deducción, no doc.
|
|
- **`role` en el Provider raíz con DOM**: el ejemplo de Dialog tiene Provider virtual sin role; para un Provider-contenedor con DOM no hay guía de si omitir `role` es válido o si el campo es requerido por el schema. Lo omití.
|
|
- **`Without<...>` / `PrimitiveDivAttributes`**: nombres citados en ejemplos pero sin definición visible; los usé por imitación.
|
|
- **El wrapper de gesture**: A15 dice que `.props` del gesture es "only `onpointerdown`" — pero ¿quién registra pointermove/up globales, el layer vía `dom.listen` o el provider? Asumí el layer.
|
|
|
|
## Qué funcionó notablemente bien
|
|
|
|
Para ser justos, la mayor parte del camino estuvo pavimentada: el morfo fue
|
|
casi mecánico de escribir (los builders `v.*` + las conditions cubren el 90%
|
|
del contrato), `SEMA_VERBS` tenía exactamente los verbos que necesitaba
|
|
(`handle.rotate` existe, `commit.set` existe — el vocabulario del libro
|
|
aguanta un componente que no existía), la política de intent forzó una
|
|
decisión de diseño buena (¿qué intent lleva el commit de un knob? → prop
|
|
`intent`, y de ahí salió el caso de uso del gain a 0dB), y las reglas
|
|
A30/A31/A35/A36 me previnieron de tres bugs de reactividad que habría
|
|
escrito con toda seguridad. El framework corrige al agente. Ese era el punto.
|