- Added `textSizing` terminal capability and `tui.textSizing` setting for Kitty OSC66 scaling.
- Updated OSC66 range and visible-width handling to preserve grapheme slice behavior.
- Updated markdown rendering to apply heading text sizing only when enabled.
- Fixed DECCARA trailing-background fill tracking and DECRPM status 3/4 recognition.
- Added kitty-graphics module with Unicode placeholder and temp-file APIs.
- Refined terminal image handling to use rendered placeholder lines and detect placeholder output.
- Raised the max inline-image default from 3 to 8 in schema and image constants.
- Refined terminal capability flow for Kitty support, including screen-to-scrollback and text-sizing handling.
- Added integration tests for kitty placeholders, virtual placement, and temp-file rendering.
- Added OSC 66 text-sizing utilities and `encodeTextSized` for safe, width-aware span emission.
- Enabled scale-2 OSC 66 output for H1 headings when text sizing is enabled and width permits.
- Handled OSC 66 heading rows as raw output, skipping wrap/background and preserving the reserved blank row.
- Mapped inline heading tokens to plain text to gate OSC 66 heading rendering by measured width.
- Added `ImageBudget` keeping only N recent images live, demoting older ones to text via a full redraw plus Kitty graphics purge.
- Changed Kitty images to transmit-once + placement, so repaints re-send only tiny placement sequences instead of base64.
- Added `tui.maxInlineImages` setting (default 3, 0 disables) wired through the coding-agent renderers.
- Updated hex color validation to accept only 3-, 6-, and 8-digit forms as swatchable.
- Adjusted strict prose filtering to gate only 3-digit values without hex letters.
- Added a regression test confirming 4-digit #TAG snapshot tags do not render swatches in prose or code spans.
- Added fuzzy token matching in agent-dashboard, state-manager, and tree-selector, replacing lowercased checks.
- Added search-query state and fuzzy-filter helpers to hook, oauth, and user-message selectors for query filtering.
- Updated filtered selectors to render match results, status lines, no-match text, and move selection within results.
- Added `overflowSearch` and filter state to `SelectList`, switching overflowing list matching to fuzzy checks.
- Configured `SelectList` input flow and fixed cancel so Escape/Ctrl+C closes lists when no matches exist.
- Updated changelogs and added tests for fuzzy-filter behavior in hook, oauth, user-message, and list selectors.
- Enhanced markdown rendering to insert a painted theme swatch before matching inline hex colors in prose and code spans.
- Skipped short numeric-only 3- and 4-digit values in plain text so issue-like references do not get swatches.
- Added `colorSwatch` to symbol themes and added tests for swatch rendering and surrounding-style preservation.
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.
- Added a new ultrathink mode module with standalone, case-insensitive detection, rainbow editor highlighting, and a hidden notice payload.
- Updated `CustomEditor` and the shared TUI `Editor` to support optional zero-width text decoration and apply the `ultrathink` styling during input rendering.
- Extended `AgentSession` prompt handling to append the hidden ultrathink notice after user turns in both streaming and non-streaming message flows, excluding synthetic messages.
User-visible symptom: drag-dropping a file with Korean characters in its name (e.g., `/Users/leo/Downloads/화면 기록 2026-05-13 오후 8.55.24.mov`) into the OMP input prompt leaves the hardware cursor floating ~5–8 cells past the visible filename. Static — typing immediately after places the new character at the cursor (gap), not at the visible end of the text.
Root cause (verified by direct measurement on the user's pasted path):
- macOS Finder drag-drops file paths with Korean characters in NFD form (Conjoining Jamo, U+1100..U+11FF), not NFC (Hangul Syllables, U+AC00..U+D7A3).
- Korean syllable `화` is U+D654 in NFC: 1 char, Bun.stringWidth=2 cells.
- Same syllable in NFD is U+1112 (ᄒ) + U+116A (ᅪ): 2 chars, Bun.stringWidth=3 cells (each conjoining jamo is classified Wide=2 or Narrow=1 separately).
- Terminal emulators correctly combine the NFD sequence into a single visible syllable (2 cells) per Unicode rendering rules.
- Bun's count overcounts by 1–2 cells per syllable. For a path with 6 Korean syllables: ~9 cells overcount → cursor drifts 9 cells past the filename.
- This is the same JS/Rust/terminal width-mismatch class as the earlier Compatibility Jamo issue (U+3131..U+318E from IME composition), but in a different Unicode range — and triggered by macOS Finder, not the IME.
Fix: `Input.#handlePaste` now appends `.normalize("NFC")` to the paste-text cleaning chain. The pasted bytes are converted from NFD → NFC before being inserted into `#value`, so all downstream width arithmetic (cursor positioning, viewport scrolling, padding) sees the same canonical encoding the terminal renders.
Localized to paste-time only — does not change `visibleWidthRaw` semantics, doesn't add per-render overhead, and doesn't touch the Rust width tables. Cost: ~0.5 μs per paste (single `String.normalize("NFC")` call on the pasted content).
Tests: 2 new cases in `packages/tui/test/input.test.ts` covering (a) NFD-encoded path stored as NFC after paste, (b) cursor column matches `visibleWidth(value) + prompt-width` after pasting a Korean filename. Full tui suite: 8 pre-existing failures (overlay-scroll 4 + render-regressions 4) unchanged.
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.
- Added SettingsList#setItems to replace items and clamp selection to a valid index after updates.
- Updated SettingsSelector to rebuild active memory items on backend changes and skip refresh when appropriate.
- Switched MCP wizard and command spinners to theme frames with themed initial frame and 80ms updates.
- Reworked welcome intro animation for a 3-second eased sweep with optional shine blending.
- Added memory backend refresh tests and aligned package changelogs with the updated behavior.
- Added `display.shimmer` setting (`classic`, `kitt`, `disabled`) with default `classic` and UI metadata.
- Added shimmer mode resolution with defaulting plus classic/KITT intensity tier profiles and thresholds.
- Added `getFgAnsi` handling across shimmer compilation and progress-bar themes, with fallback ANSI output defaults.
- Reworked shimmer rendering to coalesce same-tier segments, cache palettes by symbol, and map disabled mode to mid-tier.
- Tuned loader timing to a 16ms render interval and 80ms spinner stepping for smoother ~60fps updates.
- Guard renderInlineMarkdown against non-string input: partial JSON during
streaming can leave option label fields as undefined, causing marked.lexer
to throw 'undefined is not an object (evaluating e.replace)'. The ask tool
renderer now silently falls back to an empty string or baseColor output.
- Sanitize normalizePlanTitle instead of hard-rejecting: models that produce
natural-language plan titles like 'My Improvement Plan' were getting a
ToolError on every resolve call, causing an infinite retry loop. Spaces are
now converted to hyphens, remaining invalid chars are dropped, and only
truly unresolvable titles (empty after sanitization, path separators) throw.
- Fix ask.md prompt example: the example showed the legacy single-question
format (question/options/recommended at the top level) while the schema
requires questions: [{id, question, options}]. Models that follow examples
closely (Qwen3) generated calls that always failed schema validation.
Fixes#1176
- Migrated per-object caches (chat/tool starts, model fingerprints, validation contexts, provider indexes, render IDs) from WeakMap to Symbol-keyed properties on the objects themselves.
- Rewrote SSE debug tee as a single-pass inline parser, eliminating the body.tee() + readSseEvents re-parse pipeline.
- Refactored MockModel from a factory function + external WeakMap state into a self-contained class.
- Added FIFO memoization caches for heuristic candidate expansion and namespace suffix lookups.
- Added bucketed emoji dataset with prefix-indexed lookup for O(log n) suggestions.
- Implemented `:name:` inline replace that fires on closing colon without popup.
- Wired emoji suggestions and completions into PromptActionAutocompleteProvider.
- Extended AutocompleteProvider interface with trySyncInlineReplace hook.
- Unified line-ending normalization to `replace(/\r\n?/g, "\\n")` in editor, scraper, benchmark, and utils modules.
- Added terminal-aware line sanitization in code-cell rendering to collapse inline carriage returns and avoid overwrite corruption.
- Tightened editor and paste sanitizers to trim control characters consistently after CR normalization.
- Removed export leakage by demoting many helper and const symbols to module-local scope.
- Renamed underscore-prefixed internals and cache fields, then updated related references and `satisfies never` checks.
- Deleted obsolete logic branches and helpers, including harmony-stream interruption flow and unused benchmark runtime helpers.
- Updated Biome config and manifests by broadening lint coverage and removing an unused `@napi-rs/cli` dev dependency.
- Adjusted tests and utilities to use renamed test helpers and remove redundant private test-only helpers/locals.
- Updated markdown cache keys with terminal capability and style-probe values so stale entries invalidated when image protocol, hyperlink mode, theme, or bgColor output changed.
- Trimmed dangling inline style prefixes in markdown rendering so trailing unmatched SGR escapes were removed before emission.
- Added per-line terminal resets and OSC8 closures in TUI output processing, including truncation, so rendered diffs match bytes actually written to the terminal.
- Added pre-processing for pasted CSI-u sequences before newline cleanup, converting Ctrl+letter codes back to literal control characters.
- Added array guards before reading autocomplete suggestion lists to avoid errors when suggestion items are missing or non-array values.
- Added a strict regex-backed strikethrough tokenizer to override the default `del` token parsing.
- Created a dedicated `Marked` parser configured with the custom tokenizer.
- Switched markdown tokenization to the configured parser so it uses the strict strikethrough rules.
The L2 render cache used objectId(this.#theme) as part of the key.
After setTheme(), the same Markdown instance still holds the old
theme reference (same objectId) but the closures now read a different
palette — so the cache returned pre-switch styled lines (F3).
Export clearRenderCache() from markdown.ts and call it from the
onThemeChange handler in interactive-mode.ts so all L2 entries are
dropped on every theme switch, forcing a clean re-render.
Per-instance #cachedLines is always cold when session-tree navigation
creates a fresh Markdown component. nativeHighlightCode (sync Rust FFI
in theme.ts) runs on every code block at every re-render for every new
instance, which dominates render cost during rapid navigation.
Add a module-level ordered-Map LRU (256 entries) keyed on:
normalizedText + width + paddingX + paddingY + codeBlockIndent
+ objectId(theme) + objectId(defaultTextStyle)
Theme identity uses a WeakMap<object,number> ID so GC can collect
orphaned themes without leaking. The per-instance #cachedLines field
is kept as L1 (fastest path for repeated renders of the same instance).
L2 is checked on L1 miss and populated on first render so subsequent
distinct instances with identical inputs skip marked.lexer + FFI entirely.
Output is byte-identical to the old code (cache is invisible to callers).
New test: two distinct Markdown instances with the same (text,width,theme)
invoke highlightCode exactly once total.
- Migrated hash API calls from Bun.hash.xxHash64() to Bun.hash() across TypeScript packages for simplified hash generation.
- Consolidated mermaid cache failure tracking by replacing separate failed Set with null values in cache Map.
- Refactored schema-based child extraction in Rust by introducing promotion_fields tracking and schema_wrapper_child() helper function.
- Updated TypeScript configuration files with reformatted arrays and added compiler options for consistency.
* feat(tui): make inline image max-width configurable via tui.maxInlineImageColumns
* fix(tui): handle 0 as unlimited in maxInlineImageColumns; drop || undefined coercion
- Refactored theme color validation to use single source of truth with THEME_COLOR_RECORD object.
- Simplified model registry to defer per-model overrides to dedicated method and use constant for role IDs.
- Refactored tree list rendering to pre-render items once for consistent line counts across phases.
- Refactored question result formatting to use early returns and consistently include question ID in output.
- Updated hook editor hint text to include ctrl+g external editor option when prompt style is enabled.
- Removed unused isLogicalLineStart property from LayoutLine interface in editor component.
- 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.
- Added renderInlineMarkdown() utility function to support inline markdown rendering with optional base color styling.
- Refactored ask tool to render questions and option labels with markdown formatting for improved text styling.
- Updated hook-input and hook-selector components to render titles as markdown with theme-aware styling.
- Implemented recursive token processing for nested markdown elements including bold, italic, code, links, and strikethrough.
Fixes#491
- 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.
- Added `moveCursorToMessageStart()` and `moveCursorToMessageEnd()` prompt actions for cursor navigation to message boundaries.
- Exposed editor methods for cursor movement to message start and end positions in InputController keybindings.
- Implemented message boundary navigation in Editor class with private helper methods for cursor positioning.
- Removed remote compaction settings test suite to align with feature deprecation.
- Added support for provider-level OpenAI compatibility configuration enabling reasoning effort mapping and streaming usage fallback across models.
- Added 10 new AI models (DeepSeek V3.2, Llama 3.1 405B, Mistral Large 3, Pixtral Large, and others) with updated pricing and context windows.
- Fixed autocomplete to preserve ./ prefix in relative file/directory path completions and paste marker expansion to handle regex tokens literally.
- Changed system prompt date format to ISO 8601 and tool download timeout from 15s to 120s for improved cross-platform compatibility.
- Refactored OpenAI completions provider to extract token parsing logic and support choice-level usage fallback with improved message serialization.