Repository navigation
Replies: 1 comment
|
Independent reproduction on 0.1.6-alpha.2 ( 1. It is deterministic, not intermittent — the collapsed selection's
Why: the claim and the arrow-key moves are Lexical-driven DOM selection updates, so 2. The rethrow is what turns one bad keystroke into a dead composer. Headless ( Fix check. Clearing the copied style on the overflow node — const [tokenNode, overflow] = node.splitText(token.length)
if (tokenNode !== undefined && tokenNode.getStyle() !== TOKEN_STYLE) tokenNode.setStyle(TOKEN_STYLE)
if (overflow !== undefined && overflow.getStyle() === TOKEN_STYLE) overflow.setStyle('')— makes both in-node paths (selection-driven Minimal headless repro (drop into import { createHeadlessEditor } from '@lexical/headless'
import { $createParagraphNode, $createRangeSelection, $createTextNode, $getRoot, $setSelection } from 'lexical'
import { registerClaimDecoration } from './src/client/input/editor/claim-decor.ts'
const editor = createHeadlessEditor({ namespace: 'r', nodes: [], onError: (e) => { throw e } })
registerClaimDecoration(editor, () => '/goal ')
editor.update(() => { const p = $createParagraphNode(); p.append($createTextNode('/goal ')); $getRoot().append(p) }, { discrete: true })
editor.update(() => {
const leaf = $getRoot().getFirstChild()!.getFirstChild()! // already carries TOKEN_STYLE
const sel = $createRangeSelection()
sel.anchor.set(leaf.getKey(), 6, 'text'); sel.focus.set(leaf.getKey(), 6, 'text')
sel.style = leaf.getStyle() // what a mouse click / IME leaves on the collapsed selection
$setSelection(sel); sel.insertText('x') // → "One or more transforms are endlessly triggering additional transforms"
}, { discrete: true })(Also filed on the internal tracker.) |
Uh oh!
There was an error while loading. Please reload this page.
Environment
0.1.5-alpha.1» (@deepseek-ai/dsh-client-ui-conversation@0.1.5-alpha.1», npm `alpha» dist-tag, published 2026-09-08)Summary
In the Web composer, typing after a claimed slash-command token (e.g. `/plan» followed by text) intermittently:
Minified Lexical error #14» from the composer'sonError: (error) => { throw error }», leaving the composer broken until a page refresh.Screenshot — the seat where it happens (claimed `/plan» token in amber, followed by typed text):
The screenshot shows the normal state; in the faulty runs the text after `/plan» also turns amber, or disappears.
Steps to reproduce
Observed error
Frame mapping (against
dsh-client-ui-conversation/lib/client.js» +lexical@0.49.0»):errorOnInfiniteTransforms» —infiniteTransformCount > 99 && invariant(false)»TextNode.getWritable» /setTextContent» / `mergeWithSibling»client.js:7533» /Mi»Note: #14 is the "transforms endlessly triggering additional transforms" guard, not "Unable to find an active editor state" (that is #195 in the same build).
Root cause
packages/client/ui-conversation/src/client/input/editor/claim-decor.ts» (registerClaimDecoration», introduced with the Lexical composer rewrite `b519cb87»), lines 43-47:TextNode.splitText» copiesstyle» to every part (lexical/src/nodes/LexicalTextNode.ts:1017-1019» readsstyle» once;:1040-1052» applies it to each part). When the token leaf is **already** styled withTOKEN_STYLE» and then gains trailing text — the normal incremental-typing path — both halves keepTOKEN_STYLE».$normalizeTextNode» sees equalmode»/format»/`style» and merges them back into one styled node; the transform splits again on the next pass; after 100 reconciliation passes Lexical throws #14.Which symptom appears depends on dirty-leaf processing order:
Chinese IME makes it frequent and intermittent because composition updates repeatedly replace the same text node, flipping the dirty-leaf order from keystroke to keystroke.
The existing test
packages/client/ui-conversation/tests/lexical-editor-core.client.spec.tsx:428-465» inserts/plan rest» in a single update (leaf starts unstyled, so it splits once and converges). It never exercises the incremental path (style first, then append), which is why this slipped through.Minimal headless repro (no DSH needed)
Run with `lexical@0.49.0»; it reproduces the exact 10-frame stack:
Result:
Minified Lexical error #14» witht → Ur → li → getWritable → setTextContent → mergeWithSibling → St → Tt → anon → ss» — the same shape as the shipped stack.Proposed fix
Make the overflow segment drop `TOKEN_STYLE» after the split, so normalization cannot re-merge the halves:
A regression test should drive the incremental path (create the claimed token, then insert one character) and assert: token amber, overflow unstyled, no #14.
Notes
0.1.5-alpha.1» is the currentalpha» tag;lib/client.js:12036-12068» matchesmaster»'s `claim-decor.ts», so this is not fixed as of that build.中文摘要
Web 输入框在已 claim 的斜杠命令 token 之后继续输入时(如
/plan» 后接着输入中文),偶现三种表现:新输入的文字变成琥珀色(claim token 的颜色)、刚输入的字丢失、或直接抛Minified Lexical error #14»(`errorOnInfiniteTransforms»,transform 死循环 100 轮保护)。根因在
packages/client/ui-conversation/src/client/input/editor/claim-decor.ts:43-47»:splitText(token.length)» 会把style» 复制给前后两段,于是 token 段和溢出段都带TOKEN_STYLE»;Lexical 的$normalizeTextNode» 认为两段可合并,又把它们并回一个染色节点,transform 下一轮再拆 → 死循环。谁先跑决定结果:溢出段先清样式就正常(黑字),首叶先合并就变黄/报错;提交中断则丢字。中文 IME 组合输入反复改写同一文本节点,所以偶现。现有测试tests/lexical-editor-core.client.spec.tsx:428-465» 是一次性插入 `/plan rest»,没有覆盖“先染色再增量输入”的路径。最小复现脚本与修复建议见上文;截图见帖子顶部。
All reactions