| F4.1 ✅ | **Upload adapter** (D-5): opción `onUploadImage?` en el provider (File → Promise<url>); data-URL SOLO como fallback documentado; `imageStatus` pending/error ya existe. **Hecho 2026-07-14** (`31d49c88`): `uploadImage(file, apply)` en el snippet + picker de palabras ruteado | insertar imagen con adapter simulado → el JSON guarda URL, no base64 ✅ (test provider) |
| F4.2 ✅ | **Paste Word/GDocs**: pre-filtro en `parse-html` (mso-*, `<o:p>`, sopa de spans, listas de Word) + fixtures reales de Word/GDocs en tests. **Hecho 2026-07-14** (`8444dd58`) | pegar fixture → estructura limpia, sin basura ✅ (tests + navegador) |
| F4.3 ◐ | **Tablas**: comandos `mergeCells`/`splitCell` (los types ya llevan colspan/rowspan sin consumidor) + resize de columna por arrastre (tiradores espaciales, spec Fase 3) + UI en panel de tabla. **Motor hecho 2026-07-14** (`bdb8971b`: merge/split span-aware + parse de spans); resize + UI = post-FD | tests de table-ops ✅ + navegador (spans render) ✅; UI pendiente |
| F4.4 ◐ | **Listas** (DG-7): `start` + tipo de numeración; panel de lista los expone. **Motor hecho 2026-07-14** (`a025681d`: modelo+validate+render+serialize+parse); el panel = F3.1 post-FD | test ✅ + navegador ✅; panel pendiente |
| F4.3 ✅ | **Tablas**: `mergeCells`/`splitCell` (motor `bdb8971b`) + resize de columna por arrastre. **UI HECHA 2026-07-19**: merge/split en **menú Editar + pie del panel** (`b4666336a`; el `blockFoot` se oculta para celdas structural → fila `cellFoot` propia; **ajuste motor `at:[block,row,cell]` explícito** porque el chrome pierde `state.selection` al abrirse fuera del editor + test `selection:null`; merge sin multi-selección = derecha `cols:2`/abajo `rows:2`). **Resize v1 SOLO columnas** (`86a3386fc`, `palabras-table-resize.svelte`: tiradores en el borde superior al hover, drag con feedback CSS en vivo + commit `cell.width`**STRING `"{n}px"`** al soltar, doble-clic=auto). **Filas + snap a fracciones = v1.1.** | table-ops ✅ (test `at`) + Playwright (merge colspan=2, drag 234→314, doble-clic auto) ✅ |
| F4.4 ✅ | **Listas** (DG-7): `start` + tipo de numeración; panel de lista los expone. **Motor hecho 2026-07-14** (`a025681d`). **PANEL HECHO 2026-07-19** (`85d6a4858`): los campos ya existían desde F3.1 (`5411becb5`, escriben por `updateBlockAtPath`) — faltaba **gatearlos a `ordered`** (`kinds:['ordered']`; salían en viñetas/tareas) | test ✅ + navegador (`<olstart="3"type="i">`→`lower-roman`) ✅ |
| F4.5 ✅ | **Bloque embed/vídeo** (DG-9) **como plugin** — prueba de fuego del API (`PalabrasBlockMap` merging + registry): iframe saneado con allowlist. **Hecho 2026-07-14** (`b9f76740`: hook `fromPastedUrl` + fix normalize-plugin-blocks; allowlist configurable, DG-9 default) | plugin registrado SIN tocar el core ✅; paste de URL de YouTube crea embed ✅ (test + navegador) |
| F4.6 | **Menciones @ / emoji :** como segundo plugin (reutiliza la maquinaria del slash menu). **Pospuesta ENTERA a después de FD**: el popup es superficie de interacción nueva (pariente del slash en la spec) — no se improvisa sin firma | popup filtra, inserta inline, exporta |
| F4.7 ◐ | **Word/char count** en `Status` (deriva de `plainText`) + **TOC** generado de headings (el ancla ya existe en el panel). **Motor hecho 2026-07-14** (`4264f7f8`: wordCount/charCount/tableOfContents en snippet + fix plainText-columnas); widget visual = F3 post-FD | derivados vivos ✅ (test reactivo); TOC navega = UI pendiente |
| F4.7 ✅ | **Word/char count** + **TOC** de headings. **Motor hecho 2026-07-14** (`4264f7f8`). **WIDGET HECHO 2026-07-19** (`a97d8db74`, `palabras-status.svelte`): barra de estado al pie (3 modos) con «N palabras · M caracteres» + botón «Índice» → `<Popover>` de encabezados (sangrado por `level`, clic → `scrollIntoView`). Decisión del usuario: affordance PROPIA, NO el panel de propiedades | derivados ✅ + Playwright (45 palabras, 2 encabezados niveles 1/2, salto) + screenshot ✅ |
| F4.8 ✅ | **Alineación lógica** (DG-8): el render traduce left/right → start/end bajo RTL. **Hecho 2026-07-14** (`5b1e228a`: proyección en `blockStyle`, único punto render+export; DG-8 registrada) | demo RTL: aligns coherentes ✅ (geometría en navegador: end voltea bajo dir=rtl) |
| F4.9 | **Demos v2 + guards** (DG-10): demo palabras a plantilla v2 (9 tabs); adopción de recipe-contract → audit por lotes (la demo de words murió en FW) | `component:audit --only palabras` sin errores nuevos del lote adoptado |
| F4.10 | **Touch/móvil de la spec FD.2**: drawer inferior pulido, targets ≥44 px (`pointer: coarse`), grip anclado sin hover, drag táctil de bloques, panel usable a una mano | viewport móvil real: recorrido de edición COMPLETO sin ratón ni teclado, contra la columna touch de la spec |