diff --git a/render-wasm/docs/text_editor.md b/render-wasm/docs/text_editor.md
new file mode 100644
index 0000000000..8b65fb1f22
--- /dev/null
+++ b/render-wasm/docs/text_editor.md
@@ -0,0 +1,217 @@
+# Text Editor Architecture
+
+## Overview (Simplified)
+
+```mermaid
+flowchart TB
+ subgraph Browser["Browser / DOM"]
+ CE[contenteditable]
+ Events[DOM Events]
+ end
+
+ subgraph CLJS["ClojureScript"]
+ InputHandler[text_editor_input.cljs]
+ Bindings[text_editor.cljs]
+ ContentCache[(content cache)]
+ end
+
+ subgraph WASM["WASM Boundary"]
+ FFI["_text_editor_* functions"]
+ end
+
+ subgraph Rust["Rust"]
+ subgraph StateModule["state/text_editor.rs"]
+ TES[TextEditorState]
+ Selection[TextSelection]
+ Cursor[TextCursor]
+ end
+
+ subgraph WASMImpl["wasm/text_editor.rs"]
+ StateOps[start / stop]
+ CursorOps[cursor / selection]
+ EditOps[insert / delete]
+ ExportOps[export content]
+ end
+
+ subgraph RenderMod["render/text_editor.rs"]
+ RenderOverlay[render_overlay]
+ end
+
+ Shapes[(ShapesPool)]
+ end
+
+ subgraph Skia["Skia"]
+ Canvas[Canvas]
+ Paragraph[Paragraph layout]
+ end
+
+ %% Flow
+ CE --> Events
+ Events --> InputHandler
+ InputHandler --> Bindings
+ Bindings --> FFI
+ FFI --> StateOps & CursorOps & EditOps & ExportOps
+
+ StateOps --> TES
+ CursorOps --> TES
+ EditOps --> TES
+ EditOps --> Shapes
+ ExportOps --> Shapes
+ TES --> Selection --> Cursor
+
+ RenderOverlay --> TES
+ RenderOverlay --> Shapes
+ Shapes --> Paragraph
+ RenderOverlay --> Canvas
+ Paragraph --> Canvas
+
+ ExportOps --> ContentCache
+ ContentCache --> InputHandler
+```
+
+---
+
+## Detailed Architecture
+
+```mermaid
+flowchart TB
+ subgraph Browser["Browser / DOM"]
+ CE[contenteditable element]
+ KeyEvents[keydown / keyup]
+ MouseEvents[mousedown / mousemove]
+ IME[compositionstart / end]
+ end
+
+ subgraph CLJS["ClojureScript Layer"]
+ subgraph InputMod["text_editor_input.cljs"]
+ EventHandler[Event Handler]
+ BlinkLoop[RAF Blink Loop]
+ SyncFn[sync-content!]
+ end
+
+ subgraph BindingsMod["text_editor.cljs"]
+ direction TB
+ StartStop[start / stop]
+ CursorFns[set-cursor / move]
+ SelectFns[select-all / extend]
+ EditFns[insert / delete]
+ ExportFns[export-content]
+ StyleFns[apply-style]
+ end
+
+ ContentCache[(shape-text-contents
atom)]
+ end
+
+ subgraph WASM["WASM Boundary"]
+ direction TB
+ FFI_State["_text_editor_start
_text_editor_stop
_text_editor_is_active"]
+ FFI_Cursor["_text_editor_set_cursor_from_point
_text_editor_move_cursor
_text_editor_select_all"]
+ FFI_Edit["_text_editor_insert_text
_text_editor_delete_backward
_text_editor_insert_paragraph"]
+ FFI_Query["_text_editor_export_content
_text_editor_get_selection
_text_editor_poll_event"]
+ FFI_Render["_text_editor_render_overlay
_text_editor_update_blink"]
+ end
+
+ subgraph Rust["Rust Layer"]
+ subgraph StateMod["state/text_editor.rs"]
+ TES[TextEditorState]
+ Selection[TextSelection]
+ Cursor[TextCursor]
+ Events[EditorEvent queue]
+ end
+
+ subgraph WASMMod["wasm/text_editor.rs"]
+ direction TB
+ WStateOps[State ops]
+ WCursorOps[Cursor ops]
+ WEditOps[Edit ops]
+ WQueryOps[Query ops]
+ end
+
+ subgraph RenderMod["render/text_editor.rs"]
+ RenderOverlay[render_overlay]
+ RenderCursor[render_cursor]
+ RenderSelection[render_selection]
+ end
+
+ Shapes[(ShapesPool
TextContent)]
+ end
+
+ subgraph Skia["Skia"]
+ Canvas[Canvas]
+ SkParagraph[textlayout::Paragraph]
+ TextBoxes[get_rects_for_range]
+ end
+
+ %% Browser to CLJS
+ CE --> KeyEvents & MouseEvents & IME
+ KeyEvents --> EventHandler
+ MouseEvents --> EventHandler
+ IME --> EventHandler
+
+ %% CLJS internal
+ EventHandler --> StartStop & CursorFns & EditFns & SelectFns
+ BlinkLoop --> FFI_Render
+ SyncFn --> ExportFns
+ ExportFns --> ContentCache
+ ContentCache --> SyncFn
+ StyleFns --> ContentCache
+
+ %% CLJS to WASM
+ StartStop --> FFI_State
+ CursorFns --> FFI_Cursor
+ SelectFns --> FFI_Cursor
+ EditFns --> FFI_Edit
+ ExportFns --> FFI_Query
+
+ %% WASM to Rust impl
+ FFI_State --> WStateOps
+ FFI_Cursor --> WCursorOps
+ FFI_Edit --> WEditOps
+ FFI_Query --> WQueryOps
+ FFI_Render --> RenderOverlay
+
+ %% Rust internal
+ WStateOps --> TES
+ WCursorOps --> TES
+ WEditOps --> TES
+ WEditOps --> Shapes
+ WQueryOps --> TES
+ WQueryOps --> Shapes
+
+ TES --> Selection
+ Selection --> Cursor
+ TES --> Events
+
+ %% Render flow
+ RenderOverlay --> RenderCursor & RenderSelection
+ RenderCursor --> TES
+ RenderSelection --> TES
+ RenderCursor --> Shapes
+ RenderSelection --> Shapes
+
+ %% Skia
+ Shapes --> SkParagraph
+ SkParagraph --> TextBoxes
+ RenderCursor --> Canvas
+ RenderSelection --> Canvas
+```
+
+---
+
+## Key Files
+
+| Layer | File | Purpose |
+|-------|------|---------|
+| DOM | - | contenteditable captures keyboard/IME input |
+| CLJS | `text_editor_input.cljs` | Event handling, blink loop, content sync |
+| CLJS | `text_editor.cljs` | WASM bindings, content cache, style application |
+| Rust | `state/text_editor.rs` | TextEditorState, TextSelection, TextCursor |
+| Rust | `wasm/text_editor.rs` | WASM exported functions |
+| Rust | `render/text_editor.rs` | Cursor & selection overlay rendering |
+
+## Data Flow
+
+1. **Input**: DOM events → ClojureScript handler → WASM function → Rust state
+2. **Edit**: Rust modifies TextContent in ShapesPool → triggers layout
+3. **Sync**: Export content → merge with cached styles → update shape
+4. **Render**: RAF loop → render_overlay → Skia draws cursor/selection