You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In the sanitise pass's speaker break-out (js/editor-core.js), the non-startsWithSpeaker branch assumes the span containing the bracketed label always has a following word span:
// js/editor-core.js:628 (as of 2fdb333)letnextStart=walker.currentNode.parentNode.nextElementSibling.getAttribute("data-m");
If the label was typed after the last word of a paragraph — i.e. the bracket text sits at the end of the text of the paragraph's final word span — nextElementSibling is null and this line throws a TypeError, aborting the rest of the sanitise pass for that keystroke.
The caret-restore a few lines below has the same shape of assumption:
newSpan is inserted with span.after(newSpan), so when the label is at the end of the paragraph, newSpan.nextElementSibling is also null and setStartBefore(null) throws even if line 628 is survived.
The span's text does not start with [ (otherwise the startsWithSpeaker branch runs, which uses the parent's own data-m and is safe).
The text contains a complete [...] pair.
So the candidate repro is typing something like word [Alice] at the end of a paragraph's last word.
How to investigate
Load any transcript in the editor (the default demo transcript is fine).
Click at the end of the last word of a paragraph (immediately after its trailing character, inside the same span).
Type [Alice] and stop typing so the debounced sanitise pass runs.
Watch the console for TypeError: Cannot read properties of null (reading 'getAttribute') at editor-core.js:628 (or the setStartBefore failure at :640).
If step 3–4 doesn't reproduce, check where the browser actually puts the typed text (inspect the DOM around the caret): contenteditable may create a bare text node or a new span rather than appending to the word span's text — in that case document the actual node shape here and re-test with a paste (Cmd+V) of word [Alice] replacing the last word, which is likelier to land inside the span.
Also worth one test in each of Chrome/Safari/Firefox — contenteditable text-node placement differs by engine, and this branch's reachability depends on it.
If it reproduces — suggested fix shape
When there is no next sibling, fall back for nextStart to the parent span's own end (data-m + data-d) or, if the next paragraph's first span exists, its data-m; and guard the caret restore (if (newSpan.nextElementSibling) … else place the range after newSpan). A regression fixture (paragraph-final multi-word label) would fit the planned #58 Playwright suite.
If it does not reproduce
Document the node shapes observed per browser in a comment here and close — but consider still adding the two null guards, since the branch is one contenteditable behaviour change away from throwing.
Related: #413 (multi-word label extraction on the same code path, fixed in 2fdb333), #416 (guard that routes bracketed text to this branch), #394 (normalization that made sanitise run more often).
Summary
In the sanitise pass's speaker break-out (
js/editor-core.js), the non-startsWithSpeakerbranch assumes the span containing the bracketed label always has a following word span:If the label was typed after the last word of a paragraph — i.e. the bracket text sits at the end of the text of the paragraph's final word span —
nextElementSiblingisnulland this line throws a TypeError, aborting the rest of the sanitise pass for that keystroke.The caret-restore a few lines below has the same shape of assumption:
newSpanis inserted withspan.after(newSpan), so when the label is at the end of the paragraph,newSpan.nextElementSiblingis alsonullandsetStartBefore(null)throws even if line 628 is survived.Preconditions for reaching the branch
isSpeakerText()guard now keeps such text unsplit until sanitise sees it, which makes this branch more reachable than before, not less.[(otherwise thestartsWithSpeakerbranch runs, which uses the parent's owndata-mand is safe).[...]pair.So the candidate repro is typing something like
word [Alice]at the end of a paragraph's last word.How to investigate
[Alice]and stop typing so the debounced sanitise pass runs.TypeError: Cannot read properties of null (reading 'getAttribute')ateditor-core.js:628(or thesetStartBeforefailure at :640).Cmd+V) ofword [Alice]replacing the last word, which is likelier to land inside the span.If it reproduces — suggested fix shape
When there is no next sibling, fall back for
nextStartto the parent span's own end (data-m+data-d) or, if the next paragraph's first span exists, itsdata-m; and guard the caret restore (if (newSpan.nextElementSibling) …else place the range afternewSpan). A regression fixture (paragraph-final multi-word label) would fit the planned #58 Playwright suite.If it does not reproduce
Document the node shapes observed per browser in a comment here and close — but consider still adding the two null guards, since the branch is one contenteditable behaviour change away from throwing.
Related: #413 (multi-word label extraction on the same code path, fixed in 2fdb333), #416 (guard that routes bracketed text to this branch), #394 (normalization that made sanitise run more often).