|
|
# Continue tomorrow
|
|
|
|
|
|
Fecha de corte: **2026-05-28**. Rama: `active-uix`. 7 commits por
|
|
|
delante de `gita/active-uix` (push pendiente al cerrar la sesión).
|
|
|
|
|
|
## TL;DR de la sesión
|
|
|
|
|
|
Sesión doble — primera mitad **arquitectónica** (toggle-group
|
|
|
estructural), segunda mitad **UX del editor Words** (silencio del
|
|
|
canvas, rail visual, scroll interno, toolbar mínima, popovers, drag
|
|
|
sobre listas/tablas).
|
|
|
|
|
|
### Parte A — toggle-group structural identity
|
|
|
|
|
|
Cierra la deuda del hand-off 2026-05-27 #5: el item de toggle-group
|
|
|
ahora DECLARA `data-toggle` en su morfo y la receta de Toggle pinta
|
|
|
end-to-end. Cero duplicación de derivaciones.
|
|
|
|
|
|
- `morfo/components/toggle-group.ts`: item part añade
|
|
|
`{ attr: 'data-toggle', value: v.literal(''), severity: 'required' }`.
|
|
|
- `eidos/components/toggle-group/context.ts` (nuevo): contexto
|
|
|
Svelte tipado con getters reactivos para `variant` + `size`.
|
|
|
- `eidos/.../toggle-group.svelte`: setea el contexto en el root.
|
|
|
- `eidos/.../toggle-group-item.svelte`: lee el contexto y escribe
|
|
|
`data-variant` + `data-size` en el button del item.
|
|
|
- `eidos/.../toggle-group.css`: ~150 líneas borradas (derivaciones,
|
|
|
variant cascades, size cascade, focus/disabled/icon-only
|
|
|
duplicados). Solo queda layout/orientation/attached/block/focus
|
|
|
z-index/group-disabled.
|
|
|
- TSC v2.2 composition (palette overrides en `[data-toggle-group]
|
|
|
[data-color='X'] [data-toggle-group-item]`) sigue activa — palette
|
|
|
+ toggle's derivation chain hace el trabajo.
|
|
|
|
|
|
Verificación: 12 combinaciones (4 colores × 3 variants × 2 estados)
|
|
|
con computed values bit-a-bit idénticos al baseline pre-refactor.
|
|
|
|
|
|
### Parte B — auditoría npm check verde
|
|
|
|
|
|
`npm run check` pasó de 16 errores a **0 errors** en esta sesión.
|
|
|
|
|
|
- 4 errores `orientation` drift en `soma/components/words-toolbar*`
|
|
|
(deuda mía del sprint anterior).
|
|
|
- 12 errores en el sprint Words activo:
|
|
|
- `onUploadImage` propagado en soma `Words.Provider`.
|
|
|
- `'insert-image'` añadido a 2 Records `WordsToolbarButtonCommandName`.
|
|
|
- `leafItem` snippet hoisted FUERA de `<SomaWords.Provider>` (en
|
|
|
Svelte 5, snippets dentro de un component element se pasan como
|
|
|
props del componente — el snippet era helper local, debe estar
|
|
|
a nivel root de template).
|
|
|
- 5 arrays inline `{id: ...}` en `words-drawer.svelte` extraídos
|
|
|
a constantes typed `as const satisfies readonly { id:
|
|
|
WordsCommandName; ... }[]` (FORMAT_MARKS / TEXT_ALIGN_ACTIONS /
|
|
|
CELL_TONE_ACTIONS / CELL_VERTICAL_ACTIONS / CELL_TEXT_ALIGN_ACTIONS).
|
|
|
|
|
|
### Parte C — Words editor: fixes UX en cascada (commits EV-*)
|
|
|
|
|
|
Orden en el que el usuario fue pidiendo:
|
|
|
|
|
|
**EV-A — spam de eventos**: `isInsideWordsTool` extendido a drawer,
|
|
|
block-handle, block-handle-menu, block-inserter, image-float-bar.
|
|
|
`contact-focus` target movido de `content` a `provider` en morfo +
|
|
|
sema cascade. (Más tarde EV-F1 lo desactivó del todo).
|
|
|
|
|
|
**EV-B — toolbar slim**: presets reducidos a `[history-menu,
|
|
|
insert-menu, link-menu, tools-menu, find-replace]` quitando
|
|
|
text/block/list/align/table que ya viven en el drawer.
|
|
|
|
|
|
**EV-C — drag handle UX**: borrado `setDragImage(hoverBlockEl)`
|
|
|
para que el ghost sea solo el grip (no el bloque entero). Nuevo
|
|
|
`data-dragging` + CSS fade del ancla estático.
|
|
|
|
|
|
**EV-D — inserter al bottom edge**: `seam.y = a.bottom` en vez del
|
|
|
midpoint. Half-open interval `[top, bottom)` para que el píxel
|
|
|
exacto del bottom pertenezca al seam (no al bloque).
|
|
|
|
|
|
**EV-E — scroll interno**: nuevo token `content-max-block-size`
|
|
|
(50/60/70vh) + `overflow-y: auto` en `[data-words-content]`.
|
|
|
Listeners de scroll en handle/inserter/float-bar pasados a
|
|
|
**capture phase** (scroll no burbujea — para captar scroll dentro
|
|
|
del content).
|
|
|
|
|
|
**EV-F (6 fixes en uno)**:
|
|
|
1. Cascade sema de `contact-focus` REMOVIDA → canvas mudo en focus.
|
|
|
2. Rail bg → silver flat (#d4d4d4) + borde derecho darker (#9a9a9a),
|
|
|
sin dot pattern. **Tokens** `words.rail-bg/rail-border` (fixed
|
|
|
tone, NOT theme-aware — emulan margen de papel).
|
|
|
3. Toolbar: `undo` + `redo` DIRECTOS (no popover); `insert-menu`
|
|
|
eliminado. Resultado: 5 items (undo, redo, Links, Tools,
|
|
|
Find/replace).
|
|
|
4. Family-panel base 12.5 → 16rem · tools 17rem · link 20rem.
|
|
|
5. Link popover scroll: consecuencia del #4, ya no aparece.
|
|
|
6. Engine `insertParagraph` con guarda sobre bloque vacío:
|
|
|
paragraph → no-op, heading → demote a paragraph.
|
|
|
|
|
|
**EV-G (final pass, parcial)**:
|
|
|
- Sound STILL aparecía después de EV-F1. Causa real: `contact` family
|
|
|
tiene BASE signature en SEMA_MAP que suena aunque no haya cascade
|
|
|
per-component. **Solución**: comentar el `runtime.trigger
|
|
|
('contact-focus')` en soma. Event no se emite. Telemetry vacía
|
|
|
(aceptado).
|
|
|
- Listas + tablas no mostraban drag handle. Selectores en
|
|
|
block-handle + block-inserter ampliados de
|
|
|
`[data-words-node="block"][data-words-path]` a también
|
|
|
`[data-words-node="list"]` y `[data-words-node="table"]`.
|
|
|
- Hover removido del inserter button (CSS rule borrada) y del
|
|
|
block-handle (también removed). Gutter overlays son ahora
|
|
|
AMBIENT — no se iluminan en hover.
|
|
|
|
|
|
## Sprint Words V2 architecture — R1 cerrado
|
|
|
|
|
|
Tras la discusión de arquitectura (separación contenido/diseño, P1-P8 + decisiones D-SEM-1/2/3 + Q1-Q6), R1 está cerrado.
|
|
|
|
|
|
**Doctrina firmada** (full text en `src/uix/soma/components/words/ARCHITECTURE_PROPOSAL.md`):
|
|
|
- P1-P8 principios firmados (incluido P6 Single Source of Truth + P7 Vocabulario evaluativo único + P8 reglas para qué entra al modelo)
|
|
|
- D-SEM-2 → `CalloutBlock.intent` usa `SemaIntent` canónico (no `warning/info/success/danger`)
|
|
|
- D-Q1-Q6 → id opcional autogenerado, MD lossy total, no presets en core, engine package en R5 (opcional)
|
|
|
|
|
|
**Implementado en R1**:
|
|
|
- `engine/types-v2.ts` (~300 líneas): WordsDocumentV2 v2.0.0, BlockBase con `id` + `visual` opcionales, BlockVisual RAW (números px + hex), per-type `Pick<>` (ParagraphVisual/HeadingVisual/QuoteVisual/CodeVisual/ListVisual/TableVisual/ImageVisual/DividerVisual/CalloutVisual), 9 bloques (paragraph/heading/quote/code/list/table/image + **divider** y **callout** nuevos), marks structured (`{type:'color'|'background', value:'#hex'}` en vez de template literal V1), `WordsEvalIntent` declarado localmente para evitar dependency a `$uix/sema` (con plan de runtime parity assertion).
|
|
|
- `engine/validate-v2.ts` (~500 líneas): validador puro `validateWordsDocument(input) → ValidationResult`. 7 reglas duras + 11 códigos de error. Path JSON-pointer en cada error.
|
|
|
- `engine/validate-v2.test.ts` (50 tests): cubre topo, todos los block types, marks (boolean + structured + rechazo del format V1 + rechazo de tokens), visual con whitelist per-type (P8), id uniqueness, callout SemaIntent + rechazo de "warning"/"info"/etc., divider, quote.cite, link nesting + URL parseability, table headerRow/headerCol/per-cell+per-row visual.
|
|
|
|
|
|
**Estado tras R1**: 135/135 engine tests (85 V1 + 50 V2) · `npm run check` 0 errors. V1 sigue intacto, V2 en paralelo.
|
|
|
|
|
|
## Roadmap V2 pendiente
|
|
|
|
|
|
| Sprint | Estado | Qué hace |
|
|
|
|---|---|---|
|
|
|
| **R1 — Audit + tipos + validador** | ✅ Cerrado | types-v2.ts + validate-v2.ts + 50 tests |
|
|
|
| **R2 — Engine refactor (no breaking)** | ⏳ Pendiente | Migrator v1→v2 (drop image.status / cell-tone / table-striped / table-compact; structured marks; autogenerar ids). Runtime sidecar para image upload status (Map<blockId, status>). Tests del migrator. |
|
|
|
| **R3 — Visual sidecar implementation** | ⏳ Pendiente | Render emite inline styles desde block.visual. POLISH-1b (image radius/shadow/border) implementable bajo este modelo. Drawer image panel con controles. |
|
|
|
| **R4 — Sweep tokens del contenido** | ⏳ Pendiente | Eliminar referencias `cell-tone` / `table-striped` / `table-compact` del rest del codebase (provider, render, eidos, serializers). Drawer panels actualizados. Audit script: 0 referencias a tokens en engine/extensions. |
|
|
|
| **R5 — Engine as package (opcional)** | ⏳ Diferible | Mover engine/ a paquete propio. Eidos consume vía path normal. |
|
|
|
|
|
|
## Lo que QUEDÓ PENDIENTE para mañana
|
|
|
|
|
|
Reportado por el usuario al final, no resuelto en el commit
|
|
|
final por presión de cierre:
|
|
|
|
|
|
### P1 — Drag handle aparece FUERA del rail sobre bloques `<code>`
|
|
|
|
|
|
El usuario reporta que el grip se va fuera de la rail cuando hovers
|
|
|
un code block. Posibles causas:
|
|
|
|
|
|
- `<pre>` tiene `border: var(--words-border-width)` + padding propio
|
|
|
(1.25rem) + `overflow-x: auto`. El `rect.left` debería seguir
|
|
|
siendo el outer edge del `<pre>` (igual que cualquier `<p>`), pero
|
|
|
podría haber un shift inesperado.
|
|
|
- Sospecha alterna: el code block tiene un margen-inline negativo
|
|
|
(no encontrado en mi grep, pero vale la pena reverificar).
|
|
|
- Lugar: `src/uix/eidos/components/words/words-block-handle.svelte
|
|
|
> measureFromEl()` líneas 85-108.
|
|
|
|
|
|
Pasos para mañana:
|
|
|
1. Ir a `/uix/components/words`, hover sobre el code block,
|
|
|
inspector → log de `block.getBoundingClientRect()` para `<pre>`
|
|
|
vs `<p>`. Comparar `rect.left`.
|
|
|
2. Si difieren, hay un shift en CSS — buscar margen/padding/
|
|
|
transform en `[data-words-content] :where(pre)` (línea 1970 de
|
|
|
`words.css`).
|
|
|
3. Si NO difieren pero el handle igual sale mal, el bug es en la
|
|
|
resta de `cssToPx` o en `rememberRailMetrics` cuando el host
|
|
|
tiene padding por overflow.
|
|
|
|
|
|
### P1 — Heading inline level change (h1 ↔ h2 ↔ h3)
|
|
|
|
|
|
El usuario quiere que al editar un heading pueda cambiar de nivel
|
|
|
INLINE (sin abrir drawer/menu). Opciones:
|
|
|
|
|
|
- A. **Drawer's Block panel** ya tiene un HeadingPicker; verificar
|
|
|
que se muestra cuando `currentBlock === 'heading'`. Si no, añadir.
|
|
|
- B. **Block-handle menu** podría tener un submenu "Transform to..."
|
|
|
con h1/h2/h3/paragraph/quote. Patrón Notion.
|
|
|
- C. **Floating menu** sobre el heading (similar al image float bar)
|
|
|
con 3 chips H1/H2/H3.
|
|
|
|
|
|
Recomendación: empezar por A (menor scope), evaluar UX.
|
|
|
|
|
|
### P2 — Heredados antes de hoy
|
|
|
|
|
|
- **POLISH-1b** (#145): Slider para image radius + toggle switches
|
|
|
en la float bar. REQUIRE extender `WordsImageBlock` con `radius` +
|
|
|
campos boolean (`shadow`, `bordered`). Toca engine + serializers +
|
|
|
render + CSS + float bar — sprint propio.
|
|
|
- **POLISH-3** (#148): Topbar del demo (breadcrumb, Edit/Preview,
|
|
|
Saved indicator).
|
|
|
- **WORDS-F2.3-F2.6** (#99-102): Migración table + code-block a
|
|
|
`extensions/*`. Sprint multi-sesión documentado.
|
|
|
- **WORDS-DRAGDROP-SOMA** (#151): Migrar drag-drop a soma
|
|
|
DragDrop.Provider en vez de HTML5 nativo. Requiere restructurar el
|
|
|
engine render (intento previo falló).
|
|
|
|
|
|
## Estado de los gates
|
|
|
|
|
|
| Gate | Status | Notas |
|
|
|
|---|---|---|
|
|
|
| `npm run check` | **0 errors** | 26 warnings (pre-existentes en demos) |
|
|
|
| `morfo-check` | exit 0 | |
|
|
|
| Tests morfo + sema + eidos + soma/words | **467/467 pass** | |
|
|
|
| Tests engine (`soma/words/engine`) | **85/85 pass** | |
|
|
|
| `npm run morfo:vocabulary` | no corrido en última verif | rerun mañana |
|
|
|
| `translations:check` | no corrido | rerun mañana |
|
|
|
|
|
|
## Commits de hoy (7 ahead)
|
|
|
|
|
|
```
|
|
|
125c62cc fix(words): silence canvas, silver rail, direct undo/redo, popover widths, Enter empty
|
|
|
ce954732 feat(words): internal scroll for the content area + capture-phase scroll sync
|
|
|
c4be6dbe fix(words): silence body event spam + slim top toolbar + reposition handle/inserter
|
|
|
c250f7d7 fix(words): close 12 type-drift errors from F2/F3/COLOR sprints
|
|
|
d2f184da refactor(toggle-group): item is structurally a Toggle (zero derivation duplication)
|
|
|
+ commit EV-G (en curso al cierre, contiene: silenciar contact-focus trigger, drag para list/table, eliminar hovers de overlays)
|
|
|
```
|
|
|
|
|
|
## Decisiones arquitectónicas (afectan al ecosistema)
|
|
|
|
|
|
### 1. Wrappers visuales declaran su DNA con el morfo del componente reutilizado
|
|
|
|
|
|
**Doctrina nueva** (toggle-group sprint): cuando un wrapper componente
|
|
|
reusa visualmente otro (como toggle-group reusa Toggle), la respuesta
|
|
|
canónica NO es duplicar el cascade ni extender TSC con un tercer
|
|
|
feature. Es DECLARAR LA IDENTIDAD ESTRUCTURAL en el morfo del wrapper
|
|
|
añadiendo el atributo del componente reusado.
|
|
|
|
|
|
Ejemplo: el `Item` de toggle-group declara `data-toggle` porque ES un
|
|
|
toggle estructuralmente. La receta de Toggle entonces lo pinta entero.
|
|
|
|
|
|
Patrón aplicable si emerge: button-group, link-group, nav-menu, etc.
|
|
|
|
|
|
Documentado en CLAUDE.md hand-off 2026-05-28.
|
|
|
|
|
|
### 2. Words: tokens de la rail son FIXED-TONE (no theme-aware)
|
|
|
|
|
|
Las decisiones de visual del editor para la gutter rail son: silver
|
|
|
plano `#d4d4d4` + borde `#9a9a9a`. Estos NO son theme-aware — la
|
|
|
intención es emular un margen físico de cuaderno, que debe verse
|
|
|
igual en dark/light theme.
|
|
|
|
|
|
Tokens declarados en `lib/recipes/base.ts > words.rail-bg / rail-border`.
|
|
|
|
|
|
### 3. Words: canvas perceptualmente silencioso
|
|
|
|
|
|
`contact-focus` event ya NO se dispara desde soma. Telemetry para
|
|
|
focus está vacía (aceptado por el usuario: "no puede emitir eventos
|
|
|
es demencial"). Si en el futuro un consumidor necesita el evento,
|
|
|
descomentar `void this.runtime.trigger('contact-focus', ...)` en
|
|
|
`words-provider.svelte.ts` (cerca de `onfocus`).
|
|
|
|
|
|
### 4. Words: gutter overlays son AMBIENT (sin hover)
|
|
|
|
|
|
block-handle y block-inserter button NO tienen `:hover` styles.
|
|
|
Estado visual cambia solo en data attrs (`data-open`, `data-grabbed`,
|
|
|
`data-dragging`). Los overlays son afordancias ambientes, no botones
|
|
|
interactivos que se iluminen.
|
|
|
|
|
|
### 5. Words: drag handle + inserter cubren listas y tablas
|
|
|
|
|
|
Los selectores de top-level blocks en block-handle y block-inserter
|
|
|
ahora aceptan `data-words-node="block" | "list" | "table"`. Listas
|
|
|
ordenadas/desordenadas/check y tablas tienen drag handle + inserter
|
|
|
seam por debajo.
|
|
|
|
|
|
### 6. EnterParagraph: bloque vacío
|
|
|
|
|
|
Engine `insertParagraph` con guarda explícita:
|
|
|
- paragraph vacío + Enter → no-op (no crea otro bloque vacío)
|
|
|
- heading vacío + Enter → demote a paragraph (canonical Notion UX)
|
|
|
- otros tipos vacíos: sin cambio (mantiene comportamiento existente
|
|
|
documentado en `operations.ts` para code/image/table/list/quote)
|