diff --git a/src/docs/LIBRO_VARIACIONES_Y_EXTENSIONES.md b/src/docs/LIBRO_VARIACIONES_Y_EXTENSIONES.md new file mode 100644 index 000000000..c00ad2bda --- /dev/null +++ b/src/docs/LIBRO_VARIACIONES_Y_EXTENSIONES.md @@ -0,0 +1,279 @@ +# Variaciones del canon UIX respecto al libro + +> Documento vivo. Captura las decisiones del proyecto UIX que **se desvían**, **extienden**, o **interpretan** el libro «Diseñando lo que ocurre — Una gramática de eventos para interfaces de usuario» en su lectura literal. +> +> Propósito: facilitar al autor decidir qué casos merecen pasar al libro como ampliación, qué deben quedar como extensión local del proyecto, y qué deberían replantearse en uno o en otro. +> +> Cada sección sigue un patrón: **Status** del cambio, **Lectura literal del libro**, **Decisión tomada**, **Argumento**, **Decisión pendiente** (lo que el libro podría aclarar/incorporar). + +--- + +## A. Verbos añadidos al canon (extensiones de vocabulario) + +Estos verbos están en `src/uix/sema/verbs.ts:SEMA_VERBS` pero NO aparecen en las listas canónicas del libro. + +### A.1 `handle.scroll` + +- **Status**: Adoptado pre-Plan B, mantenido y usado activamente en Capa 2 (virtual-list, virtual-grid). +- **Libro Cap 25 §1** lista 7 verbos para `handle`: `pick, carry, drop, drag, resize, rotate, reorder`. **`scroll` no aparece.** +- **Decisión**: añadido a canon como verbo válido de `handle`. +- **Argumento**: + - Cap 25 §1: "Handle es la familia semántica de la **manipulación directa**... una relación continua entre el usuario y un objeto." + - Cap 8 §1: "no todo debe ser familia. Algunas diferencias pertenecen al verbo". + - Scrollear un viewport (gesture continuo, control directo, respuesta inmediata) cumple las propiedades canónicas de handle (Cap 25 §3): responde inmediatamente, sigue el gesto, conserva identidad, comportamiento coherente. +- **Decisión pendiente para el libro**: + - (a) Incluir `scroll` en los ejemplos de verbos de handle (Cap 25 §1). + - (b) Dejar fuera y considerar scroll un verbo de "extensión local" sin estatuto canónico. + - (c) Crear nueva sub-familia (`handle.navigate` con propiedades scroll-específicas). + +### A.2 `commit.acknowledge` + +- **Status**: Heredado del canon pre-Plan B. Sin uso activo en morfos hoy. +- **Libro Cap 23 §5** lista 12 verbos de commit: `select, toggle, save, submit, complete, fail, delete, restore, cancel, reset, expire, discard`. **`acknowledge` no aparece.** +- **Decisión**: mantenido en canon. +- **Argumento (especulativo)**: cerrar una decisión pendiente sin afirmar ni rechazar (ej. "got it" en onboarding, "I understand" en un disclaimer). Solapa parcialmente con `cancel` y `submit`. +- **Decisión pendiente**: + - (a) ¿Es un caso doctrinalmente distinto? ¿Tiene su propia firma perceptiva (menor activación que submit, sin la reversibilidad de cancel)? + - (b) ¿O es un sinónimo redundante? Si es así, removerlo del canon. + +### A.3 `commit.remove` vs `commit.delete` + +- **Status**: `remove` heredado del canon pre-libro. Usado en muchos morfos (calendar, combobox, listbox, select, tag-group, range-calendar...). +- **Libro Cap 23 §5** lista `delete` (destrucción consumada). **`remove` no aparece.** Cap 23 §6.3: "commit.delete = consecuencia irreversible o crítica". +- **Decisión**: distinción introducida en el canon: + - `remove` = quitar de una colección sin destruir el elemento (deseleccionar opción, quitar tag, retirar de set) + - `delete` = destrucción persistente (borrar archivo, eliminar registro) +- **Argumento**: el libro Cap 23 §6 separa "fijación de estado" (select/toggle) de "consecuencia irreversible" (delete). Quitar de una multi-selección NO es destrucción del item (sigue existiendo en el catálogo), es cambio de estado de pertenencia a un set. +- **Decisión pendiente para el libro**: + - (a) Formalizar `remove` como verbo distinto de `delete`, con su propia firma perceptiva (sin loss intent, neutral/affirm según contexto). + - (b) Unificar bajo `delete` (acepta el matiz "del set") y eliminar `remove` del canon. + +### A.4 `commit.confirm` + +- **Status**: Heredado, sin uso activo. +- **Libro Cap 23 §5** lista `submit, complete`. **`confirm` no aparece.** +- **Decisión**: mantenido en canon. +- **Argumento**: posible solapamiento con submit. "Confirmar una acción" puede no ser lo mismo que "submitear un formulario". +- **Decisión pendiente**: + - (a) Mantener si tiene nicho propio (confirmación de diálogo destructivo distinta del submit de formulario). + - (b) Eliminar del canon como redundante con submit/complete. + +### A.5 `commit.set` + +- **Status**: Usado activamente en Capa 2 (table.sort, virtual-list.resize, virtual-grid.resize). +- **Libro Cap 23 §5** lista 12 verbos, `set` no entre ellos. Pero Cap 25 §7 da el ejemplo: `handle.drag → commit.set + affirm` (slider). +- **Decisión**: `set` es canónico del proyecto, usado para "aplicar un valor". +- **Argumento**: el libro lo usa implícitamente (Cap 25 ejemplo) pero no lo formaliza. Hay decenas de casos donde "el usuario fijó un valor" sin caer en save/submit/complete (sliders, sort criteria, color channels, picker selections). +- **Decisión pendiente**: + - (a) Formalizar `set` en Cap 23 §5 como verbo canónico (su nicho: aplicar un valor sin que sea persistencia/submit/completion). + - (b) Mapear set a `apply` (también en uso para delegate.act). + +### A.6 `commit.apply / partial / block / move / upload` + +- **Status**: añadidos en Plan B commit 1 desde el libro mismo. +- **Libro**: estos verbs aparecen en casos de estudio (Cap 29 delegate, Cap 30 composición, Apéndice C). No estaban en la lista canónica de Cap 23 §5. +- **Decisión**: incluidos en el canon como verbos commit. +- **Argumento**: el libro los usa como verbos canónicos en ejemplos; sólo faltan en la lista enumerada. +- **Decisión pendiente para el libro**: + - **Recomendado**: incluir explícitamente estos verbos en la lista de Cap 23 §5 para que sean discoverables. El libro ya los usa, sólo no los lista. + +--- + +## B. Adopciones por interpretación (no literales en el libro) + +### B.1 Cancelación de drag como `commit.cancel` + +- **Status**: Aplicado en Cluster 3 (drag-drop.commit-cancel). +- **Libro Cap 25 §4** cubre fases pick → carry → drop. **No aborda explícitamente qué pasa si el usuario presiona Escape durante carry.** +- **Decisión**: cancelar un drag = `commit.cancel + neutral`. +- **Argumento**: el usuario inició una operación (pick) y la revoca antes de consecuencia. Per Cap 23, `commit.cancel` cubre "el usuario revoca". El drag nunca llegó a su outcome. +- **Decisión pendiente para el libro**: + - (a) Añadir caso de estudio o párrafo en Cap 25 sobre cancelación de drag/carry. + - (b) Reconocer que `commit.cancel` puede componerse con `handle.*` cuando una manipulación se aborta. + +### B.2 Scroll programático como `shift.navigate` + +- **Status**: Aplicado en Cluster 2 (virtual-list.shift-navigate-to-index, virtual-grid.shift-navigate-to-cell). +- **Libro Cap 27 §5** lista `shift.navigate` con ejemplos: "Lista → detalle. Página A → página B." — escala de "lugar" o pantalla completa. +- **Decisión**: cuando el sistema mueve el viewport a una posición específica (programáticamente, no por gesto del usuario), `shift.navigate`. +- **Argumento**: la lectura perceptiva es "estoy en otro lugar de la lista" (Cap 27). No es handle (no hay control directo). No es emerge (la lista no aparece, solo cambia su offset). La escala es menor (intra-lista vs. inter-página) pero la estructura es la misma. +- **Decisión pendiente para el libro**: + - (a) Aclarar que `shift.navigate` cubre cambios de posición a CUALQUIER escala (intra-lista, inter-vista, inter-pantalla). + - (b) O introducir un sub-verbo (`shift.scrollto`, `shift.position`) para el caso intra-lista programático. + +### B.3 Cambio de tamaño del modelo de datos como `commit.set` + +- **Status**: Aplicado en Cluster 2 (virtual-list.commit-set-resize, virtual-grid.commit-set-resize). Fires cuando `count`/`rowCount`/`columnCount` cambia y el viewport recomputa. +- **Libro**: NO cubre eventos auto-disparados por cambio interno del modelo de datos del componente (sin acción del usuario, sin resultado de proceso explícito). +- **Decisión**: el sistema "set" un nuevo tamaño → `commit.set + neutral`. +- **Argumento**: útil como sema hook para "nuevos items disponibles" cues. Pero es un stretch — no hay un actor humano ni un proceso terminando; sólo state interno cambió. +- **Decisión pendiente para el libro**: + - (a) Reconocer que ciertos eventos auto-disparados (data changed, model recomputed) pueden necesitar estatuto sema para ser sonificables. + - (b) O establecer doctrinalmente que estos NO son eventos significativos y deben quedar como state-only (data-attr, sin sema event). + +### B.4 Sort de tabla como `commit.set` + +- **Status**: Aplicado en Cluster 6 (table.commit-set-sort). +- **Libro**: NO tiene caso de estudio de sort de columnas. +- **Decisión**: ordenar = fijar un criterio de orden → `commit.set + neutral`. +- **Argumento**: el usuario fija una axis/dirección de ordenamiento. El reorder visual es consecuencia. `commit.reorder` no encaja porque no es reordenamiento manual de items individuales; es aplicar un criterio. +- **Decisión pendiente para el libro**: + - (a) Añadir caso de estudio en Cap 30 o Cap 23: "sort = commit.set; reorder manual = handle.reorder + commit.reorder". + - (b) Distinguir formalmente sort-by-criterion vs manual-reorder. + +### B.5 Eliminación de eventos sin perceptibilidad (clipboard timer reset) + +- **Status**: Aplicado en Cluster 7 (clipboard `shift-reset` eliminado). +- **Libro Cap 4 §1**: "Un evento no es cualquier cambio perceptible. Es un cambio perceptible **con relevancia operativa**." +- **Decisión**: el timer interno que revierte `copied=false` tras N ms NO es evento sema. Removido del morfo. +- **Argumento**: el cambio visible (label "Copied" → "Copy") se comunica vía `data-copied` (atributo de estado), no necesita evento. Sin acción del usuario, sin consecuencia significativa. +- **Decisión pendiente para el libro**: + - **Recomendado**: añadir nota en Cap 4 sobre "estados que vuelven automáticamente sin necesidad de evento sema". Hoy se infiere pero no se explicita. + +### B.6 Toggle con dos eventos (check vs uncheck) + +- **Status**: Aplicado en Cluster 5 (checkbox), **decisión explícita del usuario** sobre dos opciones (A: un evento, B: dos eventos). +- **Libro Cap 22 §10** ejemplo literal: "Toggle: contact.press → **commit.toggle + affirm**." — UN evento. +- **Decisión**: DOS eventos `commit-toggle-check` (intent `affirm`) y `commit-toggle-uncheck` (intent `neutral`). Mismo verbo canónico, intents distintos según dirección. +- **Argumento del usuario (Plan B Capa 2)**: la diferenciación perceptiva entre check y uncheck importa operativamente — check anuncia inclusión (affirm), uncheck revoca sin carga (neutral). El verbo se mantiene canónico (`toggle`); la diferencia vive en el intent. +- **Decisión pendiente para el libro**: + - (a) Reconocer en Cap 22 §10 que un toggle puede tener dos fases con intents distintos (check=affirm, uncheck=neutral) sin perder canonicidad. + - (b) Mantener "un solo evento" como dogma; la dirección se comunica por `data-state`. (Sería contradecir la decisión del proyecto.) + +### B.7 Intent en `contact` solo como anticipación visual + +- **Status**: Aplicado en Plan B commit 2 (Button). +- **Libro Cap 22 §11**: "Normalmente: `contact + neutral` o incluso sin intent explícito. En acciones graves, la interfaz puede anticipar el peso mediante **forma, color o señal previa**, pero el intent fuerte no debería vivir en el contacto." +- **Decisión**: el `intent` prop del Button NO entra en el morfo event (`contact-activate` no lleva intent). Vive solo en `data-color` y la variante visual del chip. +- **Argumento**: lectura estricta del libro. La anticipación de peso es visual; el sonido/háptica del click debe ser uniforme. La diferenciación perceptual entre save y delete vive en los eventos commit/signal DOWNSTREAM, no en el click del Button. +- **Decisión pendiente para el libro**: + - **Tensión real**: el libro lista `contact: intentPolicy 'allowed'` (puede declarar) pero el spirit es "normalmente no". Nuestra implementación (Cap 22 §11 estricta) puede generar regresión perceptiva si los consumidores no cablean los downstream events. ¿El libro debe dar guía explícita sobre cómo el sistema completo (no sólo el Button) suple la diferenciación? + +--- + +## C. Decisiones doctrinales pendientes (Capa 3, no resueltas) + +Hoy ~35 events siguen como warns en `morfo:vocabulary` porque el verbo declarado es canónico pero el `name` no parsea a `{family}-{verb}`. Detrás de los renames cosméticos hay PREGUNTAS DOCTRINALES: + +### C.1 "Clear" como concepto + +- **10 componentes** usan eventos llamados `commit-clear`: color-picker, date-field, date-picker, date-range-picker, file-upload, range-calendar, tag-group, tags-input, time-picker, time-range-picker. +- **Libro Cap 23 §5** lista para "revocación/cancelación": `cancel, reset, discard, expire`. **`clear` no aparece.** +- **Posibles mapeos**: + - `reset` = volver a estado inicial (date-field con default vuelve al default) + - `discard` = tirar el valor actual (tag-group con tags se queda sin tags) + - `cancel` = revocar operación en curso (no encaja para clear) +- **Pregunta para el libro**: + - (a) ¿Añadir `clear` como verbo canónico distinto de reset/discard? + - (b) ¿Mantener tres verbos y obligar a consumidores a elegir reset vs discard según contexto? + +### C.2 "Unselect" como concepto + +- **6 componentes** usan `commit-unselect`: calendar, combobox, grid-list, listbox, select, tag-group. +- **Libro** tiene: `select` (Cap 23, fija una opción), `toggle` (flip binario), `remove` (en canon UIX, no en libro literal). +- **Posibles mapeos**: + - Single-select donde el mismo click deselecciona: `commit.toggle` (estado on/off del item) + - Multi-select donde el click quita uno: `commit.remove` (quitar de la lista de seleccionados) +- **Pregunta para el libro**: + - (a) ¿`unselect` es siempre composición `commit.toggle` o `commit.remove`? + - (b) ¿El libro debe dar guía sobre cuándo es uno u otro? + +### C.3 Otros nombres no canónicos (~19 events individuales) + +Eventos con declaración canónica pero name no calza con `{family}-{verb}` (warns lint): +- `carousel.shift-slide` (declared `navigate`) — slide change, escala intra-componente +- `command.commit-invoke` (declared `submit`) — invocar comando del palette +- `file-upload.commit-add` (declared `set`) — añadir archivos +- `file-upload.signal-reject` (declared `warn`) — rechazar archivo inválido +- `form.signal-invalid` (declared `warn`) — validación fallida +- `number-field.handle-scrub` (declared `drag`) — gesto scrub del number field +- `range-calendar.commit-start` (declared `select`) — primer endpoint del rango +- `range-calendar.commit-range` (declared `select`) — rango completo +- (... y otros) + +La mayoría son renames cosméticos. Pero algunos esconden preguntas doctrinales (¿es `command.commit-invoke` realmente `commit.submit` o `commit.select`?). + +--- + +## D. Decisiones arquitecturales globales (relevantes para el libro) + +### D.1 Composición vs sobrecarga de un único morfo + +- **Aprendizaje de Plan B commit 2 (Button)**: cada evento vive en SU morfo, no en otro. El Button no declara `commit.delete + loss` porque el Button no hace el delete. +- **Implicación**: la diferenciación perceptiva en flujos complejos requiere MÚLTIPLES morfos cada uno con sus eventos canónicos. +- **Decisión pendiente para el libro**: + - **Recomendado**: el libro Cap 30 (composición) podría dedicar una sección al patrón arquitectural "cada actor declara sus eventos". Hoy se infiere de Cap 30 pero no se nombra explícitamente. + +### D.2 Eventos del contrato que no se disparan + +- **Hallazgo en Capa 2**: muchos morfos declaran events que la capa soma NUNCA dispara (announce, tree-view, tree-grid, drag-drop, virtual-list, virtual-grid, clipboard tenían/tienen events morfo-only sin emisor en soma). +- **Implicación**: el morfo es **declaración de POSIBILIDAD del contrato semántico**, no requisito de que la implementación lo emita. Permite a sistemas externos (testing, analytics, accesibilidad) suscribirse a eventos que la implementación canónica puede no fire. +- **Decisión pendiente para el libro**: + - (a) Establecer que un morfo puede declarar eventos no implementados (forward compatibility, contract surface). + - (b) O exigir que todo evento declarado tenga emisor (cuidado: 7 componentes hoy declaran eventos sin emitirlos). + +### D.3 Family policy "allowed" vs "normalmente" + +- **Libro Cap 22 §11**: "**Normalmente**: contact + neutral o incluso sin intent explícito." +- **Código `SEMA_FAMILY_POLICY`** tiene `contact: 'allowed'` (puede llevar intent libremente). +- **Tensión**: "normalmente" del libro implica "preferiblemente no", pero `'allowed'` no comunica eso. El Button terminó NO llevando intent — alineado con el libro pero no con la policy. +- **Decisión pendiente**: + - (a) Añadir nivel intermedio en `SEMA_FAMILY_POLICY`: `'discouraged'` (puede pero no recomendado) — más fiel al libro. + - (b) El libro podría explicitar este nivel en Cap 22 (cuándo puede usarse y cuándo se considera mala práctica). + +### D.4 Tabla de adopciones por morfo (resumen) + +| Componente | Verbos del libro usados | Verbos extendidos | Decisiones interpretativas | +|---|---|---|---| +| Button | `contact.activate` | — | intent prop visual-only (B.7) | +| Checkbox | `commit.toggle` | — | 2 eventos vs 1 (B.6) | +| Announce | `signal.announce`, `signal.alert`, `commit.reset` | — | morfo declara, soma no emite (D.2) | +| Tree-view/grid | `emerge.expand/collapse` | — | morfo declara, soma no emite (D.2) | +| Drag-drop | `handle.pick`, `handle.drop`, `commit.cancel` | — | drag cancellation = commit.cancel (B.1) | +| Table | `emerge.expand`, `commit.set` | — | sort = commit.set (B.4) | +| Virtual-list/grid | `commit.set`, `shift.navigate` | `handle.scroll` (A.1) | programmatic scroll = shift (B.2), data resize = commit.set (B.3) | +| Clipboard | `commit.save`, `commit.fail` | — | timer-reset eliminado (B.5) | +| Month-grid / Year-grid | `shift.navigate`, `commit.set` | — | — | +| Password-field | `commit.toggle`, `signal.notify` | — | — | +| Textarea | `signal.warn`, `commit.submit` | — | — | + +--- + +## E. Resumen ejecutivo de cambios al canon respecto al libro literal + +**Familias** (libro Cap 8): 8 — sin cambios. (`contact, commit, signal, handle, emerge, shift, sustain, delegate`) + +**Intents** (libro Cap 10): 6 — sin cambios. (`neutral, affirm, fulfill, risk, threat, loss`) + +**Verbos añadidos al canon respecto a las listas literales del libro**: +- `handle.scroll` (A.1) — extensión justificada por spirit +- `commit.acknowledge` (A.2) — heredado, status incierto +- `commit.remove` (A.3) — distinción de `delete` +- `commit.confirm` (A.4) — heredado, posible redundancia +- `commit.set` (A.5) — el libro lo usa pero no lo lista +- `commit.apply / partial / block / move / upload` (A.6) — el libro los usa en ejemplos + +**Composiciones interpretadas no explícitas en el libro**: +- Drag cancellation (B.1) +- Scroll programático intra-lista (B.2) +- Data size auto-change (B.3) +- Sort de columnas (B.4) +- Estados que vuelven solos sin evento (B.5) +- Toggle con dos eventos por dirección (B.6) +- Intent en contact estrictamente visual (B.7) + +**Pendientes doctrinales**: +- "Clear" (C.1) y "Unselect" (C.2) cubren 16 morfos sin verbo canónico claro + +--- + +## F. Cómo proceder + +Para cada bullet arriba el autor del libro puede: + +1. **Aceptar como ampliación al libro**: pasar la decisión a una próxima edición/anexo. +2. **Rechazar y revertir en el proyecto**: el canon del proyecto debe alinear con el libro, no al revés. +3. **Reformular**: la decisión necesita un matiz distinto. +4. **Dejar como extensión local**: el proyecto puede tener vocabulario específico que no escala al libro. + +El proyecto seguirá la disposición del autor en cada caso. Hasta entonces, este documento es la fuente de verdad sobre dónde el proyecto se ha desviado del libro literal.