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.
svelte-kit-vice/STUMBLES.md

11 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: 8 de 9 cerrados; de #7 (contrato CSS-vars soma→eidos) se cerró la dirección recipe→tema con un guard (opción C, cazó 3 fantasmas reales), y queda solo el sub-contrato provider→recipe (opción A, opcional).

# 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 🟡 CASI dirección recipe→tema guardada (opción C: guard de fantasmas de tema en recipe-css-contract — pilló 3 bugs reales); queda solo el sub-contrato provider→recipe (opción A, opcional)
8 Docs imprescindibles fuera del paquete ✅ RESUELTO component-audit.md §0 lista el paquete mínimo como archivos exactos
9 Fricciones menores (docs) ✅ RESUELTO ### Authoring notes en soma.md §6: state<T>() vs $state, role opcional en Provider, Without<>/PrimitiveDivAttributes, ownership de pointermove/up del gesture

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.

Cerrada la dirección recipe→tema (opción C, 2026-07-03). Guard nuevo en recipe-css-contract.test.ts: toda var(--x) sin fallback de un recipe debe resolver a un token declarado en algún sitio del árbol CSS de eidos (foundation + capas + auto-declaraciones del componente); las var(--x, default) son runtime-opcionales (vars provider/floating con default) y quedan exentas por construcción. El guard cazó 3 fantasmas reales además de los del Knob — card-group --color-content-default→-primary, link-preview --leading-body→-normal, textarea --font-family-body→-primary — arreglados. Queda solo la dirección provider→recipe (opción A).

A2 (grep) descartado — probado ambiguo (2026-07-03). Un var(--{c}-x) que el recipe lee es indistinguible entre (1) var funcional escrita-por-provider y (2) alias override-por-consumidor: idénticas en el CSS. Y muchos providers publican vars como hook de API que el recipe por defecto NO consume (--drawer-progress, --dialog-depth, --toast-swipe-*, --scroll-area-* — 17, ninguna leída por var() en componentes). Así que ni "provider escribe → recipe lee" ni el inverso distinguen un rename de un hook legítimo; un grep cross-check da ~17 falsos positivos. Solo A1 lo cierra: declarar cssVars: [...] en el morfo (fuente de verdad de qué vars SON el contrato) + guard "el provider escribe cada cssVar declarada" (un rename deja de escribir la declarada → se detecta). Coste: morfo types+schema+compile + declarar en cada morfo con vars publicadas + guard. Pendiente de decisión.

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.

Powered by TurnKey Linux.