Knob now built across all 5 layers, validating the exercise. #7 confirmed first-hand: the Knob build hit phantom theme tokens + uncontracted provider CSS vars (silent drift), exactly as predicted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>menubar-v4-safe
parent
627bf00da0
commit
8b161c1f59
@ -0,0 +1,148 @@
|
||||
# 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.
|
||||
Loading…
Reference in new issue