11 KiB
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 deWordsDocument→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,compactycell-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'desdeengine/types-v2.- Provider (
words-provider.svelte.ts):importContent(text + json),exportContent(json / text / html / markdown),plainTextderivado víarenderWordsPlainText. - Serializers:
serializeHtmlV2(doc),serializeMarkdownV2(doc),renderWordsPlainText(doc),renderWordsDocumentV2,renderWordsDomHtml/Pretty. - Comando central:
applyWordsCommand(state, WordsCommandV2)enoperations-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 usaalign, notextAlign. - Lista:
{ type:'list', kind, items }; item{ children, checked?, indent? }.items, nochildren. Sintype:'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.tsexports.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 aoperations-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, walkersreadPlainText/blockToPlainText/readInlineTextsobre el union V2,exportWordsDocument+importDraftDocumentreescritos, 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
<th>). README actualizado a la realidad V2 (tabla lista solo formato
vivo; HTML/MD export-only; import JSON + texto). ARCHITECTURE_PROPOSAL.md
intacto: documenta la decisión de eliminarlos, no features stale.
Verificado en browser (tabla 2×2 renderiza, <th> conserva su tinte vía
:where(th)). Commit 4a0661a5.
✅ R5 — POLISH-1b (image visuals)
El panel de imagen del drawer
(eidos/components/words/words-drawer.svelte > imagePanel) ya tenía radius
slider + shadow/border toggles. Añadido lo que faltaba: shadow blur
slider, shadow color picker, border color picker y background
color picker, vía un snippet reutilizable imageColorField (presets +
<input type=color> + clear opcional), mismo patrón que el color de texto
del panel format. Todos despachan setBlockVisual → block.visual.* →
visualToStyle → inline style en el <figure>. Verificado en browser: el
panel renderiza los 4 controles nuevos; click en swatch de Background
actualiza el figure (background-color:#22c55e) preservando
radius/border/shadow (shallow-merge correcto). npm run check 0 errors.
✅ P1 — Drag handle sobre <code> (NO reproduce)
Investigado: NO reproduce en el código actual. El "rail" al que se
refería el bug fue ELIMINADO en el rediseño de gutter (commit
5450d5f7, "rail out"), posterior al sprint EV donde se reportó.
measureFromEl (words-block-handle.svelte) ancla el handle a
contentRect.left + 8 — block-type-agnostic. Verificado en browser:
sobre code y sobre paragraph el gutter cae en left: 350.333 idéntico
(solo difiere top). No hay nada que arreglar; bug obsoleto.
✅ P1 — Heading inline level change (h1 ↔ h2 ↔ h3)
Añadido al Block panel del drawer
(eidos/components/words/words-drawer.svelte > blockPanel): cuando
currentBlock === 'heading', un ToggleGroup H1/H2/H3 (value =
currentHeadingLevel) despacha applyCommand({ type: 'setBlock', block: 'heading', level }). runCommand / WordsCommandName no tiene
id por-nivel (heading-1/2/3 son ids de slash / insert-menu), por eso
se usa el comando estructurado setBlock con el nivel numérico (as const mantiene cada level como literal 1|2|3). Verificado en
browser: caret en heading → el panel muestra "Heading level"; el toggle
refleja el nivel actual; el click convierte bidireccional (H1→H3→H2) y
el toggle se actualiza. npm run check 0 errors.
Rework del editor Words (2026-05-29 #2 — redirect del usuario)
El usuario redirigió a un nuevo bloque de trabajo sobre el editor. Plan en 3 pasos (acordado: overlays a eidos POR FASES, con revisión entre cada una):
- Paso 1 — gutter izquierdo (✅ hecho): quitado el botón "+" del
gutter; "insertar bloque" integrado en el menú del drag handle como
"Insert below" (vía
act('insertBelow')). Gutter convertido en banda silver estrecha:--_words-gutter-size3.5rem→2.5rem, fondo--words-rail-bg#ededed (muy suave) + borde derecho 1px--words-rail-border#cfcfcf (un poco más fuerte), pintado comolinear-gradienten[data-words-content](solo background, no toca el contenteditable). Tokens del recipe actualizados +generate:eidos-css. El inserter de SEAM (words-block-inserter.svelte, el "+" entre bloques) NO se tocó — es otro overlay; preguntar si también debe irse. - Paso 2 — props de bloque (#3, pendiente): añadir
color(texto) aBlockVisual+ per-type subsets +visualToStyle+ validador; y sección común (margen/espaciado/fondo/color-texto) en el block panel. Construir esos color controls con eidos ColorPicker y migrar los<input type=color>nativos existentes (foreground/background marks + panel de imagen) a ColorPicker. (Cubre #3 + la parte "color pickers → eidos" de #4-fase1.) - Paso 3 — menú del drag → eidos (#4-fase1 resto) + #4-fase2: migrar el menú del block-handle a eidos Menu/Popover; luego link editor / find / status a Popover+Tooltip.
Pendiente para mañana
P2 — Heredados
- POLISH-3: topbar del demo (breadcrumb, Edit/Preview, Saved).
- WORDS-DRAGDROP-SOMA: migrar drag-drop a soma
DragDrop.Provideren vez de HTML5 nativo (intento previo falló — requiere reestructurar el render del engine).
Notas de infra
sema-parity.test.tsflakea por timeout de 5s sólo bajo carga paralela (full-suite). Aislado pasa 6/6 en <500ms. Si molesta, subir eltestTimeoutde ese archivo; no es regresión..claude/scheduled_tasks.lockes un artefacto transitorio — NO commitear.