diff --git a/docs.md b/docs.md
index c539517d7..ebb500b0a 100644
--- a/docs.md
+++ b/docs.md
@@ -63,6 +63,7 @@ theme: parchment
Works With Your Keyboard
KindaVim
Neovim in the Terminal
+Why Text Tools Break in Electron Apps
diff --git a/guides/electron-text-parity.md b/guides/electron-text-parity.md
new file mode 100644
index 000000000..e7f8d03e0
--- /dev/null
+++ b/guides/electron-text-parity.md
@@ -0,0 +1,211 @@
+---
+layout: default
+title: "Why Text Tools Break in Electron Apps"
+description: "Why vim modes and text automation break in Slack, Discord, and VS Code — the accessibility gaps, and who can fix each one"
+theme: parchment
+permalink: /guides/electron-text-parity/
+---
+
+
+# Why Text Tools Break in Electron Apps
+
+If you use [KindaVim]({{ '/guides/kindavim/' | relative_url }}), a text expander, or any other
+keyboard-driven text tool, you have probably noticed the same thing: it works beautifully in Mail,
+Notes, and Xcode, then falls apart in Slack.
+
+This is a research note explaining why. It is written for people who want to fix the problem
+upstream, not for people configuring KeyPath — if you just want KindaVim working today, the
+[KindaVim guide]({{ '/guides/kindavim/' | relative_url }}) covers the practical workarounds.
+
+**Short version:** the failure is mostly Chromium's, not Apple's, and most of it is ordinary
+bug-fixing rather than missing API.
+
+It is also the reason KindaVim runs its degraded Keyboard strategy in apps like Slack instead of the
+full Accessibility strategy — the accessibility data it would need there isn't trustworthy. See
+[KindaVim]({{ '/guides/kindavim/' | relative_url }}) for what that costs you in practice.
+
+---
+
+## Five Things Have to Work
+
+Parity with a native macOS text field is not one capability, it is a chain:
+
+| # | Capability | Native field | Electron field |
+|---|------------|--------------|----------------|
+| 1 | Accessibility tree exists | Works | Manual, per app |
+| 2 | Tree is trustworthy | Works | Falsely claims support |
+| 3 | Reads are correct | Works | Offsets wrong |
+| 4 | Writes propagate to the app | Works | Model desync |
+| 5 | Changes are observable | Works | Timing unreliable |
+
+Stage 2 is the load-bearing failure. Because Electron answers "yes, I support accessibility" and
+then returns wrong data, no tool can auto-detect its way out — which is why every assistive tool
+surveyed ships a hand-curated per-app blocklist instead.
+
+---
+
+## The Gap Register
+
+Item IDs are stable and intended to be quotable in upstream bug reports.
+
+### Chromium — 6 gaps, the bulk of the work
+
+Almost everything that makes Electron text fields unusable lives here, and almost all of it is
+ordinary bug-fixing. No Apple dependency.
+
+**C1 — Text offset mapping is wrong.** A line starting with whitespace reports a character range
+off by one. Selecting exactly to end-of-line silently swallows the following line break. This is
+Blink's mapping between DOM positions and flat macOS character indices. The reporter shipped a
+reproducible Swift test case noting native apps don't behave this way.
+
+For a navigation tool this misplaces the cursor. For a tool that *replaces* a range, it eats the
+user's characters — same bug, an order of magnitude more damage. **This is the highest-value fix.**
+
+**C2 — Rich `contenteditable` has no coherent linearization.** Slack's composer, Notion, and
+Discord are DOM trees, not strings. Mentions, emoji, and code blocks become opaque nodes. There is
+no stable contract for how a rich editor projects onto a flat `AXValue` plus character offsets, so
+the offsets don't correspond to what the user sees. WebKit handles this better, so it's tractable,
+but this is design work rather than a bug fix — the hardest item on the board.
+
+**C3 — Write-path fidelity.** Setting `AXValue` or `AXSelectedTextRange` needs to route through
+Blink's editing pipeline so `beforeinput` and `input` fire and the page's JavaScript observes the
+change. If it mutates the DOM without that, editors built on React, Lexical, or ProseMirror
+desync: the visible text changes, the app's model doesn't, the message sends stale, and undo
+breaks.
+
+*Unverified — this is reasoning from observed symptoms, not from reading Blink. Check the source
+before filing.*
+
+**C4 — No granular `AXMode` for runtime clients.** The `form-controls` bundle already exists, but
+only as the `--force-renderer-accessibility` startup flag. A client enabling accessibility at
+runtime gets all-or-nothing `complete`. A text tool wants exactly `form-controls` — cheap enough
+that the "off by default for performance" objection evaporates. This is what makes always-on
+politically possible rather than merely technically possible.
+
+**C5 — No tree-readiness signal.** Activation is asynchronous. Enable accessibility, query
+immediately, get an empty or partial tree. There's no documented "ready" notification, so every
+tool polls or guesses.
+
+**C6 — No conformance tests for the Mac text APIs.** Without round-trip range tests, C1 regresses
+forever. The boring item that decides whether the others stay fixed.
+
+### Electron — 4 gaps, small surface, decisive impact
+
+**E1 — Stop advertising accessibility support until the tree is real.** The cheapest
+high-leverage change available. It is the reason every assistive tool ships a curated blocklist.
+Fix it and auto-detection starts working everywhere, for every tool, immediately — no Apple
+involvement, no performance trade-off, small diff.
+
+**E2 — `AXManualAccessibility` robustness.** The attribute was silently unsettable for roughly two
+years: handled internally but never advertised via `accessibilityAttributeNames`, so setting it
+returned `kAXErrorAttributeUnsupported`. Fixed by
+[electron#38102 ↗](https://github.com/electron/electron/pull/38102), merged April 2023 and
+backported to the 23/24/25 branches. Still needs a regression test.
+
+**E3 — Expose the granular mode.** `app.setAccessibilitySupportEnabled` is a boolean. Once C4
+exists it should take a level.
+
+**E4 — Own the text layer.** The offset bug was closed as not planned with no determination of
+whether it was an Electron or a Chromium defect. That triage vacuum — not the difficulty of the
+fix — is why these sit for years.
+
+### Apple — 4 gaps, genuinely nobody else can
+
+These aren't bugs, they're missing contracts.
+
+**A1 — There is no documented text-position API.** `AXTextMarker` and `AXTextMarkerRange` are
+private, undocumented functions in the HIServices framework. Browsers implement them for VoiceOver
+precisely because the public character-index APIs are insufficient for web content. The working
+path is undocumented and the documented path is the broken one. This sets the ceiling on how well
+any third-party tool can ever handle rich editors.
+
+**A2 — No first-class assistive-technology activation.** `AXEnhancedUserInterface` means "VoiceOver
+is running," not "a tool needs data," and it carries side effects — Vimac removed it after it
+caused window-positioning problems with window managers like Magnet. `AXManualAccessibility` is
+Electron's private invention that other frameworks don't honor.
+
+**A3 — Custom attributes have no modern equivalent.** `accessibilityAttributeNames` and
+`accessibilitySetValue:forAttribute:` belong to the deprecated informal protocol, with no
+`NSAccessibility` replacement. A2's workaround is built on API Apple deprecated without a
+successor — which is exactly what made E2's two-year bug possible.
+
+**A4 — No capability negotiation.** Nothing lets an app declare "my text ranges are correct." That's
+why E1 can only ever be a convention Electron chooses to honor, not a contract the system enforces.
+
+---
+
+## Four Tools Hit This Independently
+
+None of them fixed it. They all routed around it — and that convergence is the strongest evidence
+for where the gaps actually are.
+
+| Tool | What they found | Route around |
+|------|-----------------|--------------|
+| KindaVim | Slack returns line numbers where caret offsets belong; Firefox reports accessibility enabled when it isn't | Two strategies + curated plist |
+| Vimac | `AXEnhancedUserInterface` broke third-party window managers; filed with Apple, Chromium, and Firefox | Manual attribute, frontmost only |
+| Espanso | Keystroke injection fails in VS Code, Slack, and Discord | `force_mode: clipboard` |
+| Shortcat | "Effectiveness varies depending on the application's accessibility implementation" | Documented caveat |
+
+Every project reached the same three conclusions without coordinating: force-enable the tree, don't
+trust the reads, and mutate through the clipboard or synthesized keys rather than through
+accessibility writes.
+
+The contrast with the one serious attempt to fix it upstream is the real finding. Electron
+[#36337 ↗](https://github.com/electron/electron/issues/36337) reported the offset bug with a
+reproducible Swift test case and was closed as not planned. Working around the gap succeeds;
+fixing it does not get traction.
+
+---
+
+## Shortest Path to Parity
+
+**C1 + C3 + E1** gets you correct reads, writes that stick, and honest detection — roughly 90% of
+the gap, with zero Apple dependency.
+
+**C4** turns that from a per-app opt-in into something shippable on by default. **A1** is the
+ceiling on rich `contenteditable`; everything else on Apple's side is ergonomics.
+
+The practical read for today, while none of this is fixed: Espanso's finding points squarely at
+paste-based mutation. It routes around C3 entirely, because the app handles paste through its own
+native path — so the JavaScript model updates correctly without anyone touching Blink.
+
+---
+
+## Confidence
+
+- **Cited** — C1, C4, C5, E1–E4, and A1–A2 each rest on a primary source listed below.
+- **Inference** — Five items are reasoning rather than citation, and should be verified before
+ anyone quotes them upstream. **C3** (write-path fidelity) argues from observed symptoms rather
+ than from reading Blink. **C2**'s claim that WebKit linearizes rich editors better is domain
+ judgement, not a measured comparison. **C6** (absence of conformance tests) is inferred from C1
+ persisting, not confirmed against Chromium's test suite. **A3**'s claim that `NSAccessibility`
+ offers no replacement for custom attributes traces to a search-engine summary, not to anything in
+ the source list — electron#10305 itself does not discuss it. **A4** (no capability negotiation) is
+ an observation that no such API exists — easy to falsify if one does.
+- **Stale** — Most recent activity found is 2023–24. Nothing surfaced for 2025–26 on the
+ text-range side, but that's absence of search results, not proof of inactivity. Check the
+ Chromium tracker before filing against C1.
+
+---
+
+## Sources
+
+- [electron#36337 ↗](https://github.com/electron/electron/issues/36337) — offset bug, closed not planned
+- [electron#37465 ↗](https://github.com/electron/electron/issues/37465) — attribute unsettable
+- [electron#10305 ↗](https://github.com/electron/electron/pull/10305) — `AXManualAccessibility` added
+- [vimac#78 ↗](https://github.com/nchudleigh/vimac/issues/78) — activation side effects
+- [kindaVim#70 ↗](https://github.com/godbout/kindaVim.blahblah/issues/70) — Firefox false positive
+- [kindaVim: reaching inputs ↗](https://github.com/godbout/kindaVim.blahblah/discussions/43) — author's diagnosis
+- [Espanso backends ↗](https://espanso.org/docs/matches/basics/) — inject vs clipboard
+- [Chromium accessibility overview ↗](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/accessibility/overview.md) — AXMode bundles
+- [Chromium Mac accessibility ↗](https://www.chromium.org/developers/accessibility/mac-accessibility/) — activation model
+- [hs.axuielement.axtextmarker ↗](https://www.hammerspoon.org/docs/hs.axuielement.axtextmarker.html) — private HIServices API
+- [Chromium accessibility performance ↗](https://developer.chrome.com/blog/chromium-accessibility-performance) — the default-off rationale
+- [Shortcat ↗](https://shortcat.app/) — compatibility caveat
+
+---
+
+## Where to Go Next
+
+- **[KindaVim]({{ '/guides/kindavim/' | relative_url }})** — Real vim modes system-wide, and the practical workarounds for apps like Slack
+- **[Back to Docs](https://malpern.github.io/KeyPath/docs)**
diff --git a/guides/kindavim.md b/guides/kindavim.md
index 1a34d87ec..788434756 100644
--- a/guides/kindavim.md
+++ b/guides/kindavim.md
@@ -115,6 +115,7 @@ The overlay vanishes back to its normal state the instant KindaVim flips to Inse
- **[Keyboard Concepts]({{ '/guides/concepts/' | relative_url }})** — Layers, modifiers, and dual-role keys explained
- **[Shortcuts Without Reaching]({{ '/guides/home-row-mods/' | relative_url }})** — Combine KindaVim with home row modifiers
- **[Neovim in the Terminal]({{ '/guides/neovim-terminal/' | relative_url }})** — Lighter alternative: Leader-layer shortcuts with a HUD reference card
+- **[Why Text Tools Break in Electron Apps]({{ '/guides/electron-text-parity/' | relative_url }})** — Research note on why the Keyboard fallback is needed in Slack, and what would have to change upstream
- **[One Key, Multiple Actions]({{ '/guides/tap-hold/' | relative_url }})** — Tap-hold for the rare key you want to remap *outside* of vim mode
- **[Back to Docs](https://malpern.github.io/KeyPath/docs)**