TL;DR: Modern rich-text editors (Lexical, Slate, Draft.js with anti-bot guards) reject DOM mutations from outside their internal state machine. This is a feature, not a bug — but it makes legitimate automation pipelines (CDP-driven publish flows, accessibility tools, paste-from-source workflows) unusually painful. Here's what I tried and what actually works.
The setup
I'm running a 60-day indie iOS dev experiment. Distribution requires posting milestone updates to Reddit r/indiehackers, IndieHackers.com, and Twitter (X). All three use modern rich-text editors with anti-bot defense.
Goal: from a single command, populate the title + body + hit Submit. Three platforms, ~5 minutes wall-clock.
Reality: 30+ minutes per platform, 60% success rate, and the body usually ends up empty.
What's actually happening
Lexical (Meta's open-source rich-text editor, which Reddit uses) has its own state model. The visible DOM is a projection of that state, not the source of truth. When you do this:
const editor = document.querySelector('[contenteditable=true]');
editor.innerText = "Hello world";
You modify the DOM. Lexical's reconciler sees a state mismatch and either reverts your change or ignores your text entirely. The character count stays at 0.
document.execCommand('insertText', false, "Hello world");
This appears to work — execCommand returns true. But Lexical's input listener filters out non-trusted events (no isTrusted: true flag from a real keypress).
editor.dispatchEvent(new InputEvent('input', { data: "Hello", inputType: "insertText", bubbles: true }));
Same problem. Synthetic events lack isTrusted: true. Lexical drops them.
What about Playwright's keyboard.type?
page.keyboard.type("Hello world", delay=20)
Playwright dispatches synthetic keyboard events at the browser process level. These do have isTrusted: true because they're synthesized by Chromium's input pipeline, not the page's JS context.
This works for Slate (used by older Reddit, Notion) and Draft.js (Twitter's older composer, now deprecated).
For Lexical with active anti-bot, Playwright's keyboard.type still fails for me. Reddit's specific Lexical instance has additional guards: it watches for specific event sequences (real keydown → keypress → keyup with realistic timing) and rejects programmatic-looking patterns.
What about clipboard paste?
page.evaluate("(text) => navigator.clipboard.writeText(text)", body_text)
page.keyboard.press("Control+V")
In theory: this dispatches a real paste event with clipboardData, which Lexical should accept.
In practice: navigator.clipboard.writeText requires user permission for clipboard access. CDP-launched Chrome with --user-data-dir=... may have stale permissions or the page may have permissions denied. The write fails silently.
If it works, the paste injects raw text. But for a Lexical with markdown support, you may need to inject as HTML or as Lexical's internal serialization format.
What actually works (in 2026)
1. Lexical's internal API
If you can get a reference to the Lexical editor instance (it's usually attached to a DOM node via React props), you can do:
const editor = document.querySelector('[contenteditable=true]')._editor; // varies
editor.update(() => {
const root = $getRoot();
root.append($createParagraphNode().append($createTextNode("Hello")));
});
The challenge: the editor instance isn't always exposed. Reddit specifically obfuscates this.
2. Real keystrokes via Chrome DevTools Protocol
Skip Playwright's high-level API and use raw CDP:
await page.client().send('Input.dispatchKeyEvent', {
type: 'keyDown',
key: 'h',
text: 'h',
});
await page.client().send('Input.dispatchKeyEvent', {
type: 'keyUp',
key: 'h',
});
These come from Chrome's input subsystem, not Playwright. Reddit's anti-bot has fewer signatures to detect them.
But: 1000-character body × 3 events per char = 3000 CDP round-trips. Slow.
3. Just give up and hand the user one click
This is what I ended up doing. CC fills the title (which is a normal <input>, no Lexical), writes the body to a paste-ready file, opens the URL with the title pre-filled, and the user does:
- Click body editor
- Ctrl+V (with body in clipboard)
- Click Post
3-second user action. 99% reliable. No anti-bot fight.
The lesson
Modern web platform features work against automation, even legitimate automation, when anti-bot is a priority. This affects:
- Distribution automation for indie devs
- Accessibility tools that auto-fill forms for impaired users
- "Paste-from-source" workflows in CMSs
- Internal team tools that batch-post to social
If you're building automation against a Lexical-based platform in 2026, plan for ~70% completion via API + 30% user click. Don't promise full automation.
What I'd love to see
A standardized "automation handshake" protocol where authenticated users can grant their own scripts trusted-event privileges to specific platforms. We have OAuth for API access. We need an equivalent for browser automation that doesn't pretend to be human.
Until then: paste-ready files + 1 user click is the honest workflow.
Source
The actual scripts I tried: github.com/jiejuefuyou/autoapp-toolkit/tree/main/dashboard — reddit_post*.py, wechat_*.py, zhihu_publish_*.py. All MIT, all fail in interesting ways.
Building a 60-day indie iOS dev experiment with one autonomous Claude Code agent. iOS Indie Launch Playbook on Gumroad — real numbers from shipping 4 apps including the distribution-automation chapter.
Top comments (0)