You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/words
dev 194ca208f9
feat(words): R3 — render-v2 + HTML + MD serializers + 56 tests
4 months ago
..
components fix(words): close 12 type-drift errors from F2/F3/COLOR sprints 4 months ago
engine feat(words): R3 — render-v2 + HTML + MD serializers + 56 tests 4 months ago
extensions fix(words): normalize handles image blocks + 3 image tests (F3.11 partial) 4 months ago
test Words: text alignment, heading picker, ToolPopover cleanup, word boundary fixes 5 months ago
ARCHITECTURE_PROPOSAL.md feat(words): R1 — V2 architecture proposal + types-v2 + validator + 50 tests 4 months ago
README.md feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage 4 months ago
exports.ts refactor(words): drop vertical toolbar orientation 4 months ago
index.ts Build TextArea component + fix locale propagation bug across soma providers 5 months ago
types.ts refactor(words): drawer reinvented as Insert ↔ Inspect 4 months ago
words-content.svelte.test.ts Improve words editor tooling 5 months ago
words-provider.svelte.test.ts feat(words): slash command /image + paste/drop with onUploadImage (F3.7, F3.8) 4 months ago
words-provider.svelte.ts fix(words): silence canvas truly, drag for list/table, no overlay hovers + day-end docs 4 months ago

README.md

Words Soma

Soma sera el propietario del motor headless de Words.

Este directorio esta creado como staging documental. La implementacion no debe empezar hasta cerrar contrato Morfo y alcance V1.

Estado

2026-05-24:

  • Alcance V1 firmado.
  • Slice 4 de motor puro implementado en engine/.
  • Slice 5 implementa el primer Provider Soma real y el puente contenteditable.
  • Slice 6 cierra wrapper Eidos, receta visual, pack Sema y demo UIX de 6 tabs.
  • Tests focalizados en engine/engine.test.ts, words-provider.svelte.test.ts y words-content.svelte.test.ts.

Responsabilidad

  • Documento canonico JSON.
  • Estado editable/readonly/disabled/focused/empty/invalid.
  • Seleccion DOM <-> seleccion de documento.
  • Pipeline de input: beforeinput, input, composition/IME, paste, drop.
  • Comandos.
  • History.
  • Normalizacion de documento.
  • Plugins internos.
  • Serializacion JSON/HTML/Markdown.
  • Integracion con logger, clipboard, format y events de ActiveUIX.

Submodulos propuestos

words/
  README.md
  types.ts
  index.ts
  exports.ts
  words-provider.svelte.ts
  words-provider.svelte.test.ts
  engine/
    document.ts
    dom.ts
    dom-selection.ts
    selection.ts
    commands.ts
    code-highlight.ts
    history.ts
    input.ts
    normalize.ts
    operations.ts
    render.ts
    clipboard.ts
    serialize-json.ts
    serialize-html.ts
    serialize-markdown.ts
    logger.ts
  plugins/
    basic.ts
    marks.ts
    blocks.ts
    lists.ts
    links.ts
    history.ts
    markdown.ts
  components/
    words.svelte
    words-content.svelte
    words-placeholder.svelte
    words-toolbar.svelte
    words-toolbar-group.svelte
    words-command-button.svelte
    words-bubble-menu.svelte
    words-slash-menu.svelte
    words-link-editor.svelte
    words-bubble-menu.svelte
    words-link-popover.svelte
    words-hidden-input.svelte
    words-status.svelte

Modelo minimo V1

type WordsDocument = {
	version: 1;
	children: WordsBlock[];
};

type WordsBlock =
	| { type: 'paragraph'; children: WordsInline[] }
	| { type: 'heading'; level: 1 | 2 | 3; children: WordsInline[] }
	| { type: 'quote'; children: WordsInline[] }
	| { type: 'code'; language?: string; children: WordsInline[] }
	| { type: 'list'; kind: 'ordered' | 'unordered' | 'check'; children: WordsListItem[] }
	| { type: 'table'; striped?: boolean; compact?: boolean; children: WordsTableRow[] };

type WordsTableRow = { type: 'table-row'; children: WordsTableCell[] };
type WordsTableCell = {
	type: 'table-cell';
	header?: boolean;
	textAlign?: 'left' | 'center' | 'right' | 'justify';
	verticalAlign?: 'top' | 'middle' | 'bottom';
	tone?: 'default' | 'muted' | 'accent';
	children: WordsInline[];
};

type WordsInline =
	| { type: 'text'; text: string; marks?: WordsMark[] }
	| { type: 'link'; href: string; title?: string; children: WordsInline[] };

type WordsMark = 'bold' | 'italic' | 'underline' | 'strike' | 'code';

Comandos V1

  • focus
  • blur
  • undo
  • redo
  • toggleMark
  • setBlock
  • toggleList
  • toggleCheckItem
  • insertText
  • applyMarkdownShortcut
  • insertLineBreak
  • insertTable
  • insertTableRow
  • insertTableColumn
  • deleteTableRow
  • deleteTableColumn
  • moveTableCell
  • insertLink
  • unlink
  • setCodeLanguage
  • commitSlashCommand
  • clearFormatting
  • replaceDocument
  • importContent
  • exportContent

Slice implementado

Archivo Estado
engine/document.ts Modelo JSON V1, factories y helpers de marcas/texto/tablas.
engine/dom.ts Contrato data-words-*, path encode/decode y selection targets.
engine/dom-selection.ts Adaptador Selection API para leer/restaurar seleccion real del DOM.
engine/normalize.ts Normaliza documentos, bloques, inline nodes, marks y links inseguros.
engine/selection.ts Modelo de seleccion path + offset, rangos y text leaves.
engine/path.ts Navegacion de arbol, paths profundos, replace/set helpers.
engine/operations.ts Rangos, insert, marks, blocks, listas, tablas, links y teclado.
engine/commands.ts Dispatcher tipado de comandos V1 parciales.
engine/code-highlight.ts Tokenizer ligero para pintar code blocks en runtime sin dependencia.
engine/history.ts Undo/redo por snapshots con batching y limite de stack.
engine/input.ts Traduccion de beforeinput a comandos o accion de history.
engine/render.ts Descriptor de render headless con tags y attrs para contenteditable.
engine/serialize-json.ts Parse normalizado y serialize sin mutacion implicita.
engine/serialize-html.ts Export HTML limpio sin data-words-* ni zero-width placeholders.
engine/serialize-text.ts Import seguro de texto plano a parrafos canonicos.
engine/engine.test.ts Cobertura de normalizacion, listas, rangos, links, history y teclado.

Correcciones cerradas en slice 5

  • src/uix/morfo/components/words.ts declara contrato V1: Provider, Content, Placeholder, Toolbar, ToolbarGroup, CommandButton, LinkEditor, HiddenInput y Status.
  • Morfo incluye data-focused, data-empty, data-dirty, data-disabled, data-readonly, data-required, data-invalid, data-composing, data-selection, data-block, data-command, data-state y data-status.
  • Eventos declarados con familias Sema validas: contact-focus, commit-content, commit-format, commit-link, commit-unlink, commit-set-replace, commit-slash-command, commit-check, commit-history, commit-clear y signal-invalid-input.
  • Provider Soma mantiene documento, seleccion, history, active marks, focused, dirty, disabled, readonly, required, invalid e integracion con FieldProvider.
  • Content renderiza HTML seguro desde descriptores propios del motor, escucha beforeinput, paste, shortcuts de teclado y composition events.
  • HiddenInput serializa JSON canonico para formularios.
  • CommandButton ejecuta comandos de formato, listas, bloques, undo, redo, clear y commit; expone estado activo/deshabilitado por Morfo.
  • LinkEditor aplica insertLink y unlink sobre la seleccion estable del documento, expone selectedLink, canUnlink, captureSelection y feedback de URL invalida.
  • FindReplace mantiene query, matches, regex validada, navegacion, find-in-selection, replace-current, replace-all y replacePreserveCase para adaptar el replacement al casing del match (word, Word, WORD).
  • Paste HTML sin texto plano se rechaza y se reporta por el logger UIX como soma.words.
  • IME/composition queda conectado al Provider: compositionend aplica el comando final.
  • deleteWord* y deleteLine* tienen comandos propios; los beforeinput no soportados no se cancelan cuando el motor no los posee.
  • Las listas desindentan un nivel con Enter en item vacio anidado, salen de la lista con Enter en item vacio raiz y Backspace al inicio del primer item; partir y unir items conserva indentacion, y setBlock sobre listas produce un bloque por item.
  • Las checklist renderizan un marcador interactivo; click sobre el marcador o Ctrl/Cmd+Enter en el item actual alterna checked mediante el comando toggleCheckItem y emite commit-check.
  • Las citas continuan como cita al pulsar Enter con contenido; Enter sobre una cita vacia sale a un parrafo normal.
  • Los botones de lista se activan por listKind, no por cualquier bloque list.
  • Los links anidados se aplanan en normalizacion y insertLink no anida dentro de un link existente.
  • exportContent expone JSON, texto plano y HTML limpio; importContent acepta JSON, texto plano y HTML seguro conservador.
  • exportContent tambien expone Markdown V1 sin dependencia externa; importContent acepta Markdown conservador para headings, quote, listas, links y marcas inline basicas.
  • Los bloques code preservan language normalizado en JSON/HTML/Markdown; el Provider expone currentCodeLanguage y setCodeLanguage() para toolbars o selectores externos sin acoplar Soma a una UI concreta.
  • El render runtime de bloques code tokeniza lenguajes comunes (ts/js, css, html, json, bash, python) con data-words-code-token, mientras serializeWordsHtml mantiene un export limpio sin spans de highlighting.
  • La lectura de Selection API calcula offsets dentro de descendientes anidados, de modo que seleccionar texto dentro de mark o tokens de codigo no rompe el path canonico.
  • Words.Content renderiza el HTML seguro del motor como contenido inicial de Svelte y usa un efecto posterior solo para restaurar la seleccion cuando el editor mantenia foco interno. La rama gestionada no monta snippets internos, de modo que wrappers Eidos/demos no apagan accidentalmente el render del documento. Tiene cobertura montando el componente Svelte real.
  • WordsProvider sincroniza cambios externos de value y selection sin remount, normaliza el documento entrante y no emite callbacks de edicion de usuario para cambios controlados desde fuera.
  • El Provider usa batch: 'typing' de beforeinput para agrupar escritura, borrado e IME contiguos en una sola entrada de undo; seleccion externa, blur, commit, undo/redo o comandos de formato cierran el batch.
  • El espacio tras un marcador inicial aplica atajos Markdown de escritura: #, ##, ###, >, -, *, 1., 1), - [ ], - [x] y fences de tres backticks con lenguaje opcional se transforman en bloques canonicos sin insertar el espacio.
  • El mismo trigger de espacio aplica atajos Markdown inline al final del texto actual: **bold**, *italic*, _italic_, ~~strike~~, `code` y [label](url) se convierten en marcas o links canonicos conservando el espacio para seguir escribiendo.
  • Las URLs desnudas con protocolo permitido (http(s), mailto, tel) se autoconvierten a links al escribir el espacio siguiente; puntuacion final comun queda fuera del enlace.
  • Pegar una unica URL segura crea un link directamente. Si hay texto seleccionado, la URL pegada se usa como href y el texto seleccionado se conserva como etiqueta del enlace.
  • La seleccion de rango se sincroniza desde selectionchange, teclado, puntero y select; CommandButton preserva la seleccion del editor antes de aplicar comandos de toolbar sobre el rango seleccionado.
  • La resincronizacion de la misma seleccion colapsada conserva activeMarks. Esto permite activar una marca desde toolbar y seguir escribiendo texto con esa marca, tambien dentro de celdas de tabla.
  • Ctrl+A usa selectAll() dentro del Provider para seleccionar todo el documento en el modelo Soma y restaurar la Selection API con data-selection="range".
  • BubbleMenu calcula su posicion desde el rectangulo de Selection API, expone data-open, data-positioned, data-side y data-selection, y evita que el pointerdown del toolbar contextual colapse la seleccion antes de ejecutar comandos.
  • SlashMenu detecta /query en selecciones colapsadas fuera de code blocks, filtra comandos de bloque/lista, mantiene indice activo, permite navegar con flechas/Home/End y aplica Enter/Tab borrando el rango del slash antes de ejecutar el comando en una sola entrada de history.
  • Las tablas tienen nodo canonico type: 'table' con filas y celdas editables por paths profundos ([block,row,cell,text]). El comando insertTable inserta una tabla 2x2 y un parrafo posterior, el render DOM emite data-words-node="table|table-row|table-cell", y HTML preserva tablas con headers, align, vertical align, tone, striped y compact. Markdown import/export conserva pipe tables simples. insertTableRow, insertTableColumn, deleteTableRow, deleteTableColumn, toggleTableHeaderRow, toggleTableHeaderColumn, setTableCellTone, setTableCellVerticalAlign, toggleTableStriped, toggleTableCompact y moveTableCell permiten editar estructura y formato desde la celda activa; Tab avanza entre celdas y crea una fila nueva al llegar al final.
  • La sincronizacion de seleccion distingue selectionchange automatico de senales explicitas (pointerup, teclado, input). Un colapso transitorio durante el cruce del contenido al BubbleMenu no sobrescribe el rango del modelo; los botones restauran la seleccion DOM antes de aplicar comandos.
  • Words.Content intercepta clicks sobre links en modo editable para evitar navegacion accidental fuera del editor; en modo readonly no cancela el comportamiento nativo.
  • Words.LinkEditor recibe el formulario real como target de commit-link y commit-unlink, de forma que Sema puede proyectar los eventos sobre la parte declarada en Morfo.

Correcciones cerradas en slice 2

  • Paths de profundidad variable para parrafos, listas y texto dentro de links.
  • getNodeAtPath, resolveTextAtPath, replaceInlineAtPath y helpers de hijos inline.
  • Operaciones de rango multi-inline y multi-bloque para reemplazo de texto.
  • Edicion real dentro de list items.
  • insertParagraph, insertLineBreak, deleteBackward, deleteForward y deleteRange.
  • unlink desde el texto interior de un link.
  • Links relativos internos (/ruta, ?query, #anchor) permitidos.
  • History con batchHistoryCommands y limite por defecto de 200 snapshots.
  • serializeWordsJson ya no normaliza ni muta silenciosamente.

Correcciones cerradas en slice 3

  • Contrato DOM estable basado en data-words-node y data-words-path.
  • Encode/decode de paths para no depender de indices inventados por el DOM.
  • Mapping WordsSelection <-> targets DOM serializables y clampados.
  • Render headless de bloques, listas, items, links y text leaves.
  • Texto vacio renderizable mediante placeholder zero-width y data-words-empty-text.
  • Traduccion de beforeinput para texto, Enter, line break, delete, formato, history undo/redo y composition guard.

Correcciones cerradas en slice 4

  • Lectura de Selection API desde DOM real mediante data-words-path.
  • Soporte de rangos que cruzan texto normal y texto dentro de link.
  • Restauracion de Selection API desde targets DOM codificados.
  • Clamp de offsets DOM contra texto renderizado y placeholders vacios.
  • Primitivas de paste seguro: texto plano con normalizacion de saltos de linea, HTML ignorado hasta tener importador seguro.
  • Primitivas IME/composition: begin, update y commit final como comando explicito.

Pendiente despues de Provider

  • Importador HTML avanzado para imagenes y HTML arbitrario completo.
  • Drop/clipboard avanzado a traves de servicios ActiveUIX.
  • Popover flotante de links resuelto en Eidos como Words.ToolPopover local para no depender del Soma/Popover compartido mientras ese componente tiene bloqueo propio documentado en src/uix/PENDIENTES.md. Soma mantiene la seleccion estable y la validacion/comando de LinkEditor.
  • Cobertura de componentes Svelte renderizados para el resto de partes compuestas.
  • Eidos recipe y demo bajo web/routes/uix/components/words ya existen; queda auditoria visual iterativa conforme crezcan BubbleMenu/import/export.

Reglas de seguridad

  • HTML importado debe validarse y normalizarse antes de entrar al documento.
  • Links solo con protocolos permitidos.
  • javascript:, data: y protocolos desconocidos deben rechazarse o convertirse en error logueado.
  • No aceptar nodos desconocidos silenciosamente.
  • Paste desde HTML debe tener fallback a texto plano si la importacion falla.

Eventos

Soma dispara runtime.trigger(...) solo para eventos declarados en Morfo. Cambios internos frecuentes deben agruparse y convertirse en commit-content cuando proceda.

Powered by TurnKey Linux.