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
Unified text input system — replaced three separate text input implementations (FormWizard text-step, InputPanel, command-bar) with a shared architecture inspired by Claude Code:
text-cursor.ts — immutable Cursor class with NFC normalization and Intl.Segmenter-based grapheme navigation (CJK, emoji, Cyrillic, combining marks)
hooks/useTextInput.ts — keyboard logic hook with undo stack (Ctrl+Z / Cmd+Z), kill ring (Ctrl+K/U/W/Y), word navigation (Option+Left/Right), and all terminal editing shortcuts
components/TextInput.tsx — display component with sliding viewport that keeps cursor visible on text overflow
Bug Fixes
Cursor jumping on keyboard language switch — NFC normalization in Cursor constructor prevents position drift when IME sends NFD-encoded characters during layout switching
Text overflow at screen edge — single-line text inputs now use a sliding viewport instead of truncating from the end, keeping the cursor always visible
Undo stack over-drain — debounced undo snapshots captured current state, causing Ctrl+Z to pop identical text with no visible effect; fixed by skipping one matching snapshot before applying undo
React anti-pattern in textarea — setTaCursorCol was called inside a setTaLines updater function; moved out to prevent potential double-fire in concurrent mode
Timer leak on unmount — undo debounce timer is now cleared via useEffect cleanup when the component unmounts mid-debounce
Performance
Zero-cost cursor navigation — arrow keys, Home/End, word navigation reuse existing grapheme segments via Cursor._withPos() factory instead of re-running Intl.Segmenter on unchanged text
Single-pass insert — Cursor.insert() uses graphemeSegments() once instead of graphemeLength() + constructor re-segmentation
Render-path optimization — TextInput reads cursor.beforeSegs / cursor.afterSegs directly, avoiding join + re-segmentation on every render