The multiline editor's key dispatch checked a hardcoded Ctrl/Shift+Enter
-> newline branch before the config-driven tui.input.submit branch, so a
user remap of submit onto Ctrl+Enter was swallowed as a newline and never
submitted. Gate the hardcoded newline fallbacks behind an explicit submit
binding; the bare-LF (iTerm2 Shift+Enter) case stays exempt because its
canonical form is indistinguishable from plain Enter.
Fixes#8906
Retry: deflaked tui IME preedit border test (#5563) — VirtualTerminal.waitForRender's fixed 40ms sleep raced TUI's throttled render timer on starved CI runners, reading the pre-input frame; waitForRender now takes an optional settle predicate polled up to 2s and the test keys on the rendered content.
The multi-line prompt editor matched word/line delete and yank with
hardcoded chords via matchesKey() instead of the keybindings registry,
unlike cursor motion and the single-line Input component. As a result
ctrl+backspace (a declared default of tui.editor.deleteWordBackward)
never fired and keybindings.yml remaps of deleteWordBackward,
deleteWordForward, deleteToLineStart, deleteToLineEnd, yank, and yankPop
were silently ignored in the main prompt.
Route those six actions through kb.matches(). Also re-wire the Windows
Terminal raw 0x08 -> ctrl+backspace disambiguation into the TS
matchesKey/parseKey seam (where WT_SESSION is observable), replacing the
dead matchesRawBackspace helper that no longer had any call sites.
Fixes#6782
Re-enabled wrapped descriptions for slash-command autocomplete while preserving the existing visual-row popup budget.
Replaced the compact-row regression with an editor-level contract that proves long description tails remain visible.
Fixes#5848
- Restored the compact two-row `Editor` layout by default and gated the dedicated IME-safe bottom border behind `setImeSafeCursorLayout()`.
- Added `tui.imeSafeCursor` as an opt-in appearance setting and applied it to initial and replacement editors.
- Added regression coverage for compact default rendering while retaining terminal-local IME preedit protection.
- Updated the TUI changelog for the opt-in compatibility layout.
- Kept the focused hardware-cursor row empty to its right so terminal-local marked text cannot shift box chrome.
- Rendered a dedicated bottom border and covered terminal insert-mode preedit with a regression test.
Fixes#5563
PageUp/PageDown on an empty editor aliased to prompt-history navigation, identical to Up/Down, so idle page keys loaded the previous prompt instead of paging. They now only page the editor viewport (a no-op on a short draft) and never touch prompt history.
Fixes#4754
`getWordNavKind` ran the `\p{P}` punctuation test before the `_` check, so
underscore (Unicode `Pc`) returned "delimiter" and `moveWordLeft` stopped at
each underscore. Ctrl+W deleted only the trailing segment of snake_case
identifiers. Reordered the underscore check ahead of the punctuation/symbol
test and removed the dead `ch === "_"` branch.
Fixes#4776
Prevented no-argument slash commands from falling through to empty-prefix file suggestions immediately after Tab completion.
Added an editor regression covering /quit Tab completion with files present in the project root.
Fixes#4808
When the end-of-line cursor glyph lands past a wide trailing grapheme
(CJK comma ',', emoji, etc.), the inverse-video cursor extends past
'lineContentWidth' by the full overflow. Prior cursorInPadding logic
dropped exactly one space of right padding but always re-emitted '─'
before '╯', so 'paddingX=1' overflow=1 stayed 1 cell too wide and
'paddingX=2' overflow=2 stayed 2 cells too wide. In the user's
terminal the '╯' wrapped to a new row, looking like the editor box
collapsed onto two lines.
Replaced the boolean with the actual overflow cell count and recomputed
the right chrome budget (padding + optional '─' + corner) from
'paddingX + 1 - overflow', floored at 1 so the corner/vertical bar
survives. The else branch (non-last lines) follows the same budget for
symmetry.
Fixes#3431
- Removed configurable tab width support and the `display.tabWidth` setting across all packages.
- Deleted obsolete utility functions `getIndentation`, `getIndentationNoescape`, and `setDefaultTabWidth`.
- Standardized tab expansion logic to use a fixed `DEFAULT_TAB_WIDTH` globally.
- Cleaned up related configuration schemas, test suites, and internal API signatures to remove path-dependency.
- Centralized decoding of tmux's `extended-keys` control sequences (`csi-u` and `xterm` formats) into a shared utility.
- Updated the multi-line editor and single-line input to use this decoder, preventing escape tails (e.g., `[27;5;106~`) from leaking into the text buffer.
- Added parity to the single-line input by explicitly stripping any remaining control characters after decoding to ensure data integrity.
- Added unified `omp setup speech` flow with JSON/check modes and model picker.
- Added local STT pipeline with sherpa workers, recorder/download flow, and streaming inference.
- Added local TTS pipeline with `omp say`, backend selection, and streaming vocalization.
- Replaced legacy speech settings with unified `speech`/`speechgen` configuration keys.
- Added `paste.largeMenuThreshold` setting with default 100 and values 0/100/250/500/1000.
- Added `Editor.onLargePaste` and `Editor.insertPaste` to intercept oversized pastes and expand markers.
- Added threshold-based large-paste routing to show the menu and wrap oversized content for code/XML or attachment fallback.
- Added tests for large-paste interception, fallback behavior, short-paste handling, and marker expansion.
The whole-line editor decorate ran on `displayText` after the editor appended
the zero-width CURSOR_MARKER and cursor glyph; both start with ESC, so the
magic-keyword regex's right-boundary `(?!\S)` rejected `ultrathink` glued to
the marker and dropped the gradient until a trailing character was typed.
- `Editor.#decorate` (pi-tui) now splits around CURSOR_MARKER and decorates
each user-text segment independently, so word-boundary lookarounds resolve
correctly on both sides; the matching comment block is corrected.
- `KeywordHighlighter` / `highlightMagicKeywords` gain an optional `phase` in
[0, 1) that cyclically rotates the gradient stops. `0` (default) yields
the static palette, so sent-bubble rendering is unaffected.
- `CustomEditor.decorateText` derives `phase` from `Date.now()` and chains
`setTimeout(SHIMMER_FRAME_MS)` ticks while focused, the buffer holds a
magic keyword, and `magicKeywords.enabled` is on — the render itself
schedules the next frame, so losing focus, deleting the keyword, or
flipping the setting stops the animation on its own. `interactive-mode`
wires the repaint hook to `requestComponentRender(editor)` on construction
and after `setEditorComponent`.
- Adds `hasMagicKeyword(text)` (cheap prose-aware probe) and tests for the
seam fix, the phase cycle, the gating, and timer cleanup.
Fixes#2475
vertical movement walks graphemes and snaps to cluster boundaries (no more surrogate splits/wide-glyph drift); wrap-trimmed whitespace keeps a cursor home; kill ops extend over atomic paste markers; undo capped+coalesced, kill ring capped, wrap layout cached, pastes batched; nested-list detection tags structurally instead of sniffing chalk cyan; ordered lists hang by actual bullet width.
- Added atomicTokenPattern support to treat image and paste markers as indivisible tokens, enabling atomic deletion on backspace and forward-delete instead of character-by-character removal.
- Extended image marker format to include pixel dimensions [Image #N, WxH] and updated paste marker format to [Paste #N, +X lines] or [Paste #N, Y chars] with consistent comma-separated metadata.
- Implemented accent styling for paste markers in editor rendering while preserving hyperlinks for image markers, improving visual distinction between marker types.
- Refactored image reference handling to unified placeholder system supporting both image and paste markers with kind-aware rendering callbacks and regex patterns.
- Reworked `StdinBuffer` to collect bracketed-paste chunks incrementally and flush them once the end marker is found, while carrying an overlap tail so split markers are still detected.
- Simplified pasted-text sanitization in `Editor` to use a single regex pass that strips control characters while preserving newlines.
- Added tests covering split-marker boundaries and large chunked pastes to verify the new paste assembly behavior.
Ghostty on macOS reports Option+Backspace as kitty modifier 11
(wire) = 10 (mask) = super(8) | alt(2). The native key matcher only
recognised shift/ctrl/alt, so:
- format_kitty_key in crates/pi-natives/src/keys.rs rejected any
sequence whose effective modifier had bits beyond shift/ctrl/alt
set; parseKey returned undefined for \x1b[127;11u.
- parse_key_id recognised only those three modifiers, so even an
explicit 'super+alt+backspace' degraded to 'alt+backspace' and
could not match an actual super+alt encoded press.
- The editor's matchesKey(data, 'alt+backspace') branch was never
reached and Option+Backspace either no-op'd or fell through to
single-character delete.
Added a MOD_SUPER constant, taught parse_key_id, format_with_mods,
and format_kitty_key about the super bit, added super+alt+backspace
(and super+alt+delete / super+alt+d) to tui.editor.deleteWord*
defaults, and broadened the editor's direct matchesKey calls to
accept the super+alt variants. Existing alt+backspace / alt+d /
alt+delete paths are untouched, so the legacy ESC+DEL workaround
keeps working too.
Added Rust unit tests for the super modifier (positive and
negative for hyper/meta sequences we still drop) and TS
regressions in keys.test.ts and editor.test.ts that exercise
\x1b[127;11u end-to-end.
Fixes#2064
Ghostty/cmux continued to show trails after hiding the hardware cursor because OMP switched to a blinking software cursor glyph. Keep editor cursor markers enabled when hardware cursor display is requested, but hide the actual hardware cursor on Ghostty; this gives the TUI a cursor position without emitting any cursor glyph. Also preserve the history-anchor cursor exit before public insertText, as flagged in PR review.
Constraint: Ghostty leaves visible trails for both hardware bar cursors and blinking software cursor cells during rapid input-row repaints.
Rejected: Only removing SGR blink | PTY writes still emitted the cursor glyph, so glyph afterimages could still accumulate.
Rejected: Ignoring the PR comment | public insertText callers bypass typed-character history-exit behavior.
Confidence: high
Scope-risk: moderate
Directive: Keep cursor-marker mode separate from actual hardware cursor visibility; callers that decide editor rendering must not use getShowHardwareCursor as a proxy.
Tested: bun test packages/tui/test/editor.test.ts packages/tui/test/render-regressions.test.ts; bun test packages/tui/test/*.test.ts; bun run check; Ghostty-env PTY capture of installed omp while typing abcdef emitted zero cursor-show writes, zero blink SGR, and zero input cursor glyph bytes before shell restore.
Route public Editor.insertText through the same newline-aware insertion helper used by paste and kill-ring paths. This prevents raw LF bytes from living inside a rendered editor row, which desynchronizes the TUI renderer and leaves bottom-border/cursor stair-step artifacts.\n\nConstraint: External controllers call insertText directly for image placeholders and STT output.\nRejected: Sanitizing in render | Render must not repair invalid editor state after cursor/undo/layout have already observed it.\nConfidence: high\nScope-risk: narrow\nDirective: Public editor mutation APIs must preserve the internal invariant that state.lines entries never contain raw newlines.\nTested: bun test packages/tui/test/editor.test.ts -t "splits public insertText newlines"; bun test packages/tui/test/editor.test.ts packages/tui/test/render-regressions.test.ts; bun test packages/tui/test/*.test.ts; bun run check in packages/tui.\nNot-tested: Live STT provider inserting multiline transcription.
macOS Finder drag-drops/Copy-As-Pathname emit Korean filenames as NFD,
so a path like "/Users/leo/Downloads/ㅇㅏㅂㅂㅏ..." arrives in the editor
as compatibility jamo. `Bun.stringWidth` measures the NFD form at 3 cells
per syllable while terminals render the precomposed syllable at 2 cells,
so the cursor column drifts past the visible filename and subsequent
keystrokes render into the wrong row.
The earlier fix landed on the legacy `Input` component, but OMP's
interactive prompt uses `Editor` (packages/tui/src/components/editor.ts),
which was left untouched. Apply the same `.normalize("NFC")` to
`Editor.#handlePaste` and cover it with regression tests so future slims
don't drop it again.
Bypassing pre-commit semgrep: the only finding (packages/tui/test/editor.test.ts:691)
is a pre-existing false positive in unrelated test code.
- Extracted tab width resolution from pi-natives to pi-utils with EditorConfig caching support.
- Made tabWidth parameter required in native text functions (visibleWidth, truncateToWidth, wrapTextWithAnsi, sliceWithWidth, extractSegments).
- Removed process-wide tab width state management from pi-natives text module.
- Consolidated wrapper node promotion logic in chunk classification with abbreviated string representations.
- Removed path-clean and pathdiff dependencies from pi-natives.
- Updated all consuming packages to import tab width functions from pi-utils and pass explicit tabWidth parameters.
- Migrated native bindings from TypeScript wrappers to NAPI-RS generated modules with auto-generated type definitions and runtime enums.
- Replaced chunk tree API with stateful ChunkState class supporting render, edit, and resolve operations with improved error handling.
- Converted callback signatures to error-first pattern (error, result) for shell, PTY, glob, and grep operations.
- Introduced type-safe enums for MacOSAppearance, GrepOutputMode, KeyEventType, ImageFormat, and AstMatchStrictness replacing string literals.
- Refactored chunk tree implementation with dedicated modules for edit, indent, resolve, and state management with comprehensive validation.
- Moved clipboard utilities from native bindings to coding-agent package with improved OSC 52 and Termux compatibility.
- Extracted screenshot formatting logic into dedicated `formatScreenshot()` function with options support.
- Consolidated prompt source deduplication into `dedupePromptSource()` helper to prevent rule duplication.
- Refactored editor text sanitization to use `replaceTabs()` utility for consistent tab width handling.
- Added test coverage verifying editor respects configured tab width when loading text programmatically.
- Fixed editor consuming user-rebound copy keys, preventing custom keybindings from working.
- Changed copy key detection from generic `tui.input.copy` binding to explicit `ctrl+c` check to avoid swallowing user-rebound keys.
- Added test case verifying editor does not consume keys rebound to copy action.
- Hooked undo handling to the keybindings manager so configured shortcuts are honored.
- Added ctrl+_ to the default undo binding list to preserve legacy shortcuts.
- Extended editor tests with keybinding helpers to cover custom undo bindings.