Summary
On X Draft.js compose, type_text / insert_text with clear_first: false (append) can duplicate text (body + body) or wipe/replace the existing draft. Append-without-wipe is the hard invariant for multi-step compose (body, then URL line).
Repro
- Context: x.com compose, Draft.js
[data-testid=tweetTextarea_0], existing draft text already present.
- Steps:
- Insert body text once (CDP
insert_text or selector type_text).
- Append a second string with
clear_first: false / omit clear.
- Read draft contents before send.
- Observed: doubled text (e.g. body repeated) or second call replaces first;
clear_first / select-all paths also flake.
- Partial mitigation on branch:
draft_fast_path caret-at-end + clearFirst only when explicitly true (~actions.js L2967–3005); quirks remain under Draft.js / CDP mix.
Code pointers (verified against /workspace/FSB)
extension/content/actions.js — draft_fast_path clear/append (~L2967–3008); placeCaretAtEnd (~L3492+); clear_first / clearFirst handling (~L3473–3490, ~L3729–3730); append-safe verify comment (~L3667).
extension/background.js — cdpInsertText clearFirst (~L19131+, ~L20055–20093) via Input.insertText.
extension/ai/tool-definitions.js — insert_text / type_text schemas.
Proposed fix
Single root cause: append path must never select-all or replace Draft editor state when clear_first is false. Unify selector draft_fast_path and CDP cdpInsertText so both (1) place caret at end, (2) insert once, (3) verify post-text contains prior content + new suffix exactly once. Prefer one insertion API end-to-end for compose (see related paste issue).
Acceptance / regression
- Given draft
Hello, append \nhttps://example.com/ → draft is exactly body + newline + URL once.
- Never doubles; never clears unless
clear_first: true.
- Automated regression for clear_first false/true on a Draft-like contenteditable fixture.
Owner
extension
Priority: P0 · Owner: extension
Summary
On X Draft.js compose,
type_text/insert_textwithclear_first: false(append) can duplicate text (body+body) or wipe/replace the existing draft. Append-without-wipe is the hard invariant for multi-step compose (body, then URL line).Repro
[data-testid=tweetTextarea_0], existing draft text already present.insert_textor selectortype_text).clear_first: false/ omit clear.clear_first/ select-all paths also flake.draft_fast_pathcaret-at-end +clearFirstonly when explicitly true (~actions.js L2967–3005); quirks remain under Draft.js / CDP mix.Code pointers (verified against /workspace/FSB)
extension/content/actions.js— draft_fast_path clear/append (~L2967–3008);placeCaretAtEnd(~L3492+);clear_first/clearFirsthandling (~L3473–3490, ~L3729–3730); append-safe verify comment (~L3667).extension/background.js—cdpInsertTextclearFirst(~L19131+, ~L20055–20093) viaInput.insertText.extension/ai/tool-definitions.js—insert_text/type_textschemas.Proposed fix
Single root cause: append path must never select-all or replace Draft editor state when
clear_firstis false. Unify selector draft_fast_path and CDPcdpInsertTextso both (1) place caret at end, (2) insert once, (3) verify post-text contains prior content + new suffix exactly once. Prefer one insertion API end-to-end for compose (see related paste issue).Acceptance / regression
Hello, append\nhttps://example.com/→ draft is exactly body + newline + URL once.clear_first: true.Owner
extension
Priority: P0 · Owner: extension