# Continue tomorrow Fecha de corte: **2026-05-29**. Rama: `active-uix`. ## TL;DR de la sesión **V1 del editor Words ELIMINADO. V2 es ahora el único motor.** La sesión arrancó con una auditoría del componente Words (`codex-full-audit.md` en la raíz) y la pregunta del usuario: *"elimina v1 y continua v2, ¿qué sentido tiene v1?"*. Decisión firmada y ejecutada de cabo a rabo (F1 → F6). Ya no hay coexistencia V1/V2 ni migrador: el modelo de documento público es `WordsDocumentV2` y punto. ### Decisiones firmadas (vinculantes) - **D1 = "V2 puro, sin legacy"**: borrados `engine/document.ts` + `engine/migrate-v2.ts`. **SIN** importador de retrocompatibilidad. La API pública pasa de `WordsDocument` → `WordsDocumentV2`. Los docs persistidos en V1 **dejan de cargar** (ruptura aceptada por el usuario). - **D2 = "Aceptar el drop"**: eliminados por completo los controles / props / commands / atributos morfo de tabla `striped`, `compact` y `cell-tone`. NO se re-mapean ni se re-portan. ## Qué se borró (V1) y a dónde se movió (V2) **Borrado — motor V1 (`engine/*.ts`):** `commands.ts`, `document.ts`, `dom-selection.ts`, `engine.test.ts`, `find-replace.ts`, `history.ts`, `index.ts` (barrel V1), `input.ts`, `migrate-v2.ts` (+test), `operations.ts`, `render.ts`, `serialize-html.ts`, `serialize-json.ts`, `serialize-markdown.ts`, `serialize-text.ts`. **Borrado — árbol `extensions/` entero:** extension-registry (+test), extension-types, `image/*`, `table/*`. El modelo V2 no usa el sistema de extensiones; tabla/imagen/divider/callout son bloques nativos del union `WordsBlockV2`. **El motor V2 vive SOLO en `engine/operations-v2/`** + un puñado de `.ts` en la raíz de `engine/` (types-v2, validate-v2, normalize, render-v2, serialize-html-v2, serialize-markdown-v2, sema-parity, code-highlight, path, selection, dom). Esta tanda portó al namespace V2 lo que faltaba de V1: `commands`, `history`, `input`, `find-replace`, `render-dom`, `serialize-text`, `extra-ops`, `insert-block-types`, `markdown` (input-rule shortcuts), `factories`, `dom-helpers`, `dom-selection` — además de cerrar R4A.2→R4A.8 (text / delete / marks-ops / paragraph-ops / delete-ops / block-format / list-ops / link-ops / table-ops / selection-walkers / inline-split). **NO existe parser HTML / Markdown / JSON-schema en V2.** Las serializaciones HTML y Markdown son **export-only** (lossy → sin inverso fiel). El import sólo acepta **JSON** (`JSON.parse` + `validateWordsDocument`) y **texto plano** (`parseWordsPlainText`). ## Superficie pública V2 (lo que consume cualquier app) - Tipos: `WordsDocumentV2`, `WordsBlockV2`, `WordsInlineV2`, `WordsSelection`, `WordsExportFormat`, `WordsImportFormat` — todos re-exportados desde `$soma/components/words`. - `WORDS_DOCUMENT_VERSION_V2 = '2.0.0'` desde `engine/types-v2`. - Provider (`words-provider.svelte.ts`): `importContent` (text + json), `exportContent` (json / text / html / markdown), `plainText` derivado vía `renderWordsPlainText`. - Serializers: `serializeHtmlV2(doc)`, `serializeMarkdownV2(doc)`, `renderWordsPlainText(doc)`, `renderWordsDocumentV2`, `renderWordsDomHtml` / `Pretty`. - Comando central: `applyWordsCommand(state, WordsCommandV2)` en `operations-v2/commands.ts`. ## Forma de los contenedores V2 (recordatorio — NO hay discriminador `type` en sub-nodos) - **Tabla**: `{ type:'table', rows, headerRow?, headerCol? }`; fila `{ cells, visual? }`; celda `{ children, align?, verticalAlign?, colspan?, rowspan?, visual? }`. **Celda usa `align`**, no `textAlign`. - **Lista**: `{ type:'list', kind, items }`; item `{ children, checked?, indent? }`. **`items`**, no `children`. Sin `type:'list-item'`. - **Bloques con alineación**: paragraph/heading/quote llevan `textAlign?` (a nivel bloque). ## ⚠️ Decisión consecuente NO firmada — export HTML `serializeHtmlV2` emite **HTML semántico limpio**, no un formato roundtrip-lossless. Como V2 no tiene parser HTML, HTML y Markdown son export-only. Si el usuario quería export HTML roundtrip-friendly (re- importable), es un encargo aparte. **Pendiente de confirmar con él.** ## Verificación al cierre (todo verde) | Gate | Status | Notas | |---|---|---| | `npm run check` | **0 errors** / 5168 files | 26 warnings pre-existentes, ninguno en archivos tocados | | `npx vitest run src/uix/soma/components/words` | **455/456** | el único fallo es el flake de `sema-parity` por timeout 5s bajo carga paralela | | `sema-parity` aislado | **6/6 en 362ms** | confirma que el flake es de infra, no regresión | | `npm run morfo:vocabulary` | **exit 0** | sólo WARN suaves (vocabularios noveles legítimos) | | Browser smoke `/uix/components/words` | **OK** | mount limpio, 0 console errors | **Smoke detallado**: el doc sample V2 renderiza (tabla 2×2 con `headerRow`, 4 list-items incl. el indentado); export markdown emite tabla GFM válida; import texto round-trip ("imported text"); import JSON válido round-trip a través del gate ("imported json"); import JSON inválido (versión mala) → "invalid json" y **deja el doc intacto** (la razón de añadir el gate `validateWordsDocument`: un doc malo asignado al `value` bindeado rompería los `$derived`). ## Consumidores re-cableados a V2 - `words-provider.svelte.ts` + `words-provider.svelte.test.ts` - `exports.ts` + `types.ts` (superficie pública) - `morfo/components/words.ts` (drop de atributos striped/compact/tone) - eidos: `words.svelte`, `words-drawer.svelte`, `words-block-handle`, `words-block-inserter`, `words-command-icon`, `words-heading-picker`, `words-toolbar-family-menu`, `words.css`, `types.ts` - soma `components/words.svelte` - `test/words-content-harness.svelte` (importaba el barrel V1 borrado; reescrito a `operations-v2` + `types-v2` + `WordsDocumentV2`) - `web/routes/uix/components/words/+page.svelte` (último consumidor; imports V2, importFormats → `['json','text']`, docs sample/empty/long a forma V2, walkers `readPlainText`/`blockToPlainText`/`readInlineText` sobre el union V2, `exportWordsDocument` + `importDraftDocument` reescritos, prosa de la API-tab limpiada de striped/tone) ## Cerrado en la continuación (2026-05-29) ### ✅ Limpieza de huérfanos del drop D2 Borrados los selectores CSS muertos de `words.css` (`[data-command='table-cell-tone-*']`, `table[data-words-table-striped]`, `[data-words-table-compact]`, `[data-words-cell-tone]` + focus-within, y los `[data-words-cell-header]` huérfanos — los headers se renderizan como `
` (heredado)
El grip se va fuera de la rail al hacer hover sobre un code block.
Sin diagnosticar. Lugar: `eidos/components/words/words-block-handle.svelte
> measureFromEl()`. Pasos: comparar `getBoundingClientRect().left` de
`` vs ``; si difieren, buscar padding/margin/transform en
`[data-words-content] :where(pre)` en `words.css`.
### P1 — Heading inline level change (h1 ↔ h2 ↔ h3) (heredado)
Cambiar nivel de heading inline sin abrir drawer. Recomendación:
empezar por el Block panel del drawer (verificar que el HeadingPicker
se muestra cuando `currentBlock === 'heading'`).
### P2 — Heredados
- **POLISH-3**: topbar del demo (breadcrumb, Edit/Preview, Saved).
- **WORDS-DRAGDROP-SOMA**: migrar drag-drop a soma `DragDrop.Provider`
en vez de HTML5 nativo (intento previo falló — requiere reestructurar
el render del engine).
## Notas de infra
- `sema-parity.test.ts` flakea por timeout de 5s **sólo bajo carga
paralela** (full-suite). Aislado pasa 6/6 en <500ms. Si molesta,
subir el `testTimeout` de ese archivo; no es regresión.
- `.claude/scheduled_tasks.lock` es un artefacto transitorio — NO
commitear.