diff --git a/src/uix/soma/components/words/engine/blocks/built-ins.ts b/src/uix/soma/components/words/engine/blocks/built-ins.ts index 90a2b0ffd..c9a91033a 100644 --- a/src/uix/soma/components/words/engine/blocks/built-ins.ts +++ b/src/uix/soma/components/words/engine/blocks/built-ins.ts @@ -361,9 +361,23 @@ const tableSpec: WordsBlockSpec = { group: 'structure', create: () => ({ type: 'table', + // Cells hold BLOCKS (P5m), not inlines: seed each with an empty + // paragraph. Passing bare `emptyText()` (inline nodes) here makes + // `normalizeBlock` return undefined for the unrecognized 'text' + // type, corrupting the cell (crashes the mark-walk, breaks editing). rows: [ - { cells: [{ children: emptyText() }, { children: emptyText() }] }, - { cells: [{ children: emptyText() }, { children: emptyText() }] } + { + cells: [ + { children: [{ type: 'paragraph', children: emptyText() }] }, + { children: [{ type: 'paragraph', children: emptyText() }] } + ] + }, + { + cells: [ + { children: [{ type: 'paragraph', children: emptyText() }] }, + { children: [{ type: 'paragraph', children: emptyText() }] } + ] + } ] }) } diff --git a/src/uix/soma/components/words/engine/operations/normalize.ts b/src/uix/soma/components/words/engine/operations/normalize.ts index 2fe1d684d..9a3b21aa4 100644 --- a/src/uix/soma/components/words/engine/operations/normalize.ts +++ b/src/uix/soma/components/words/engine/operations/normalize.ts @@ -207,12 +207,16 @@ function normalizeTableCells( } let mutated = false; const next = cells.map((cell) => { - // Cells hold blocks (like callout): normalize each child block; seed - // an empty paragraph when the cell has none. - const children = - cell.children.length > 0 - ? cell.children.map((child) => normalizeBlock(child, idGen)) - : seedCellChildren(idGen); + // Cells hold blocks (like callout): normalize each child block, then + // DROP any that failed to normalize. A stray inline slot — e.g. a cell + // seeded with bare `emptyText()` by the pre-fix table factory — makes + // `normalizeBlock` return undefined; keeping it would crash the + // mark-walk and break cell editing. Re-seed an empty paragraph when + // nothing valid remains, so a cell always holds at least one block. + const mapped = cell.children + .map((child) => normalizeBlock(child, idGen)) + .filter((b) => b != null); + const children = mapped.length > 0 ? mapped : seedCellChildren(idGen); const cellId = cell.id ?? idGen(); const childrenChanged = children.length !== cell.children.length || children.some((c, i) => c !== cell.children[i]);