Display math (`$$…$$`, `\[…\]`) renders as several 2D rows: numerator,
fraction bar and denominator are separate lines. When the layout was
wider than the Markdown content width, `render()` wrapped each row with
`wrapTextWithAnsi` on its own. Only the overflowing row broke, so the
rows below moved down and the equation no longer lined up. For example,
`dx` landed between the fraction bar and the denominators.
The `latexBlock` case in `Markdown.renderToken()` now checks the
rendered layout against the available width. If any row is too wide, it
uses the raw LaTeX source, the same fallback already used when parsing
fails. The raw source then wraps like normal text. Layouts that fit
render exactly as before.
**Testing**
- Added a regression test in `packages/tui/test/markdown.test.ts` using
the equation from the issue. At width 40 it renders the 2D layout
unchanged. At width 30 it falls back to the raw `\[ … \]` source, with
no fraction bars and no line wider than 30. The test fails without the
fix and passes with it.
- `node --test test/markdown.test.ts`: 93 pass. `node --test
test/latex.test.ts`: 111 pass.
- `npm run check` reports no errors in the touched files. The only
errors are 6 type errors in `packages/ai/test`, caused by stale
generated model data, and they are the same on `main` without this
change.
- Reproduction at width 30, before the fix:
```
∂² u ∂² u n 1 1
──── + ──── = ∑ ── +∫ f(x)
dx
∂ x² ∂ y² i=1 i² 0
```
After the fix, the raw source, wrapped:
```
\[
\frac{\partial^2 u}{\partial
x^2} + \frac{\partial^2
...
\]
```
Fixes#11483
Requested by @badlogic
Co-authored-by: triagent <triagent@localhost>
With the kitty keyboard protocol active, these function keys were not
recognized:
- F1, F2 and F4 arrive as `CSI P`/`Q`/`S`, or as `CSI 1;<mod> P/Q/S`.
- Any F-key pressed with a modifier, e.g. `CSI 15;5~` for Ctrl+F5.
`matchesKey` returned false whenever a modifier was set and only
compared against the legacy sequences. `parseKittySequence` did not know
any F-key forms. So shortcuts bound to keys like `f1` or `ctrl+f5` never
fired.
Separately, F13–F35 (and other kitty functional keys) arrive as CSI-u
codepoints in the Private Use Area. `decodeKittyPrintable` returned them
as text, so the editor inserted invisible U+E020+ characters into the
prompt.
Changes in `packages/tui/src/keys.ts`:
- Add F1–F12 internal codepoints. Map `~` numbers 11–15, 17–21, 23 and
24 to them.
- Parse the `CSI [1;mod[:event]] P/Q/S` form. F3 is left out because
`CSI 1;<n>R` collides with cursor position reports, so kitty sends F3 as
`CSI 13~`.
- Let the `f1`…`f12` cases in `matchesKey` fall back to
`matchesKittySequence` so modifiers work. Name the keys in `parseKey`
output.
- Make `isKeyRelease` and `isKeyRepeat` recognize `:3`/`:2` events on
P/Q/S, so F1/F2/F4 releases don't trigger shortcuts a second time.
- `decodeKittyPrintable` now rejects the kitty functional-key PUA range
(57344–63743). Keypad keys are normalized to ASCII first, so they still
decode.
Testing:
- Added tests in `packages/tui/test/keys.test.ts`. They cover kitty
F1–F12 with and without modifiers, F3 not being confused with a cursor
position report, release and repeat detection, and F13+ not decoding as
text. Two of the new tests fail without the change; all pass with it.
- Ran `npm test` in `packages/tui` and `npm run check`; both pass.
- Before the change: `parseKey("\x1b[P")` and `parseKey("\x1b[1;5P")`
returned `undefined`, `matchesKey("\x1b[15;5~", "ctrl+f5")` returned
false, and `decodePrintableKey("\x1b[57376u")` returned `"\ue020"`.
- After the change: these give `f1`, `ctrl+f1`, true and `undefined`.
Fixes#11419Fixes#11345
Requested by @badlogic
Co-authored-by: triagent <triagent@localhost>
- ai: OAuth logins only accept http(s) URLs from servers (shared
trustedHttpUrl, now also checked by Radius); pi-messages rejects
contentIndex values that are not the next or an existing block; the
CLI auth file is written with mode 0600
- ai: OpenCode DeepSeek V4.1 Flash (renamed from V4 Flash upstream) no
longer sends prompt_cache_retention
- tui: setTitle() replaces control characters; export
replaceControlCharacters()
- coding-agent: the login dialog only opens web URLs and never prints
control characters from sign-in, device-code, or info URLs
- durable: partial commits no longer merge a new __proto__ member into
Object.prototype
Placeholder rows below a Kitty image line can contain box padding,
borders, or side-by-side text. Stopping the backward search at the
first non-blank row dropped those images once their first row scrolled
above the viewport. Search back at most the tallest registered image
height instead; the existing hiddenRows check keeps results correct.
Image placeholder rows inside padded or colored boxes contain spaces and
SGR codes, which stopped the backward search for the image line, so the
whole image disappeared once its first row scrolled out of view.
**Problem.** A line of many `>` characters makes `marked` produce one
nested blockquote token per `>`. The blockquote branch of
`Markdown.renderToken` renders its children at `Math.max(1, width - 2)`
columns, then wraps each child line again at that width and adds a `│ `
prefix.
Once the nesting uses up the width (about 39 levels at 80 columns), the
width stays clamped at 1. From then on, each child line (`│ x`, 3
columns wide) is wrapped into about 3 lines at every further level, so
the output roughly triples per level. At 80 columns:
- 50 levels: 4096 lines, about 30 s to render;
- 55 levels: `push(...lines)` exceeds the JavaScript argument limit and
throws `RangeError: Maximum call stack size exceeded`, which crashes pi.
Reopening the session crashes it again.
**Fix.**
- When there is no room left for another border (`width < 3`), the
blockquote branch now renders its child tokens at the same width without
adding a border and without wrapping them again.
- Child lines are appended with a loop instead of a spread `push`.
Quotes that fit in the width render exactly as before.
**Testing.**
- Added a regression test in `packages/tui/test/markdown.test.ts`: 60
nested `>` rendered at width 80 give one line that keeps the content.
Without the fix the test fails with the RangeError; with it, all 92
tests in that file pass (`node --test test/markdown.test.ts`).
- `npm run check` passes.
- Reproduction script rendering `">".repeat(n) + "x"` at width 80:
- before: 50 levels gave 4096 lines in 29.5 s, and 55 or 60 levels threw
a RangeError;
- after: 40, 50, 60 and 200 levels each give 1 line in about 10 ms.
**Not covered.** A run of about 2000 `>` still overflows the stack
inside `marked`'s own lexer, before pi's renderer runs. That is a
separate limit and is not changed here.
Fixes#11272
Requested by @davidbrai
Co-authored-by: triagent <triagent@localhost>
fuzzyMatch scored only the greedy leftmost alignment, so a query could be
scattered over an earlier unrelated part of the text (opus via "openrouter",
astra via "radius radius/gpt") and the real match ranked last. It now also
scores every contiguous occurrence and keeps the best alignment.
/resume matched one concatenated id/name/messages/cwd string. Scattered
matches in megabytes of message text or in UUIDs were noise, and the
position penalty buried real hits deep in a transcript. Fields are now
matched separately: name and cwd fuzzy, id and messages exact only, with no
position penalty. Ranking is name text, then id/message/cwd text, then
scattered name/cwd matches. Lowercased message text is cached per session.
closes#11220
Move two post-release entries out of the 1.3.0 sections, add missing entries, duplicate user-facing ai and tui changes into coding-agent, and add New Features.
An unclosed $$ opener could pair with a later $$ across a GFM table, or
be treated as pending streaming math, hiding the table. Block-math
tokens no longer span a table start, and the pending-math fallback
skips content containing a table.
fixes#11159
Rex supports OSC 8 but was treated as an unknown terminal, so links fell back to the `text (url)` format.
Detect it via TERM_PROGRAM=rex and enable hyperlinks and truecolor. Images stay off because Rex only partially implements the Kitty graphics protocol (no Unicode placeholders). The check runs before the outer-terminal checks so leaked variables like KITTY_WINDOW_ID don't win.
Closes#11131
Wheel events that no component handles now scroll the transcript instead
of reaching the focused overlay's handleInput as raw input. Overlays that
scroll with the wheel must implement handleMouse.
fixes#10769
Preserve terminal reply strings until their terminator while retaining bounded recovery for ambiguous legacy Alt-key input.
Related: #10256, #10657. Missing OSC prefixes and fragmented DA1 replies remain separate issues.
Editor and Input wrap their drawn cursor in fake cursor markers. The TUI
resolves them to reverse video, or drops the focused one when the hardware
cursor is shown, so terminals that extend edge cell colors into the window
padding no longer stretch the drawn cursor at the first or last column.
Also keep CURSOR_MARKER when an overlay ends directly left of the cursor.
Report idle, working, blocked, done, and error to terminals that support
the Program Status Protocol. ProcessTerminal sends the support query with
the keyboard query and reports only after the terminal answers before DA1;
PI_PROGRAM_STATUS=1|0 overrides detection. Interactive mode reports runs,
compaction, extension dialogs, and logins. agent_settled now carries
aborted so cancelled runs report idle.
closes#10607
TERM_PROGRAM=herdr enables hyperlinks with image protocols off. Herdr is
checked before outer-terminal detection so leaked variables such as
KITTY_WINDOW_ID do not enable images inside Herdr panes.
closes#10573
Selection coordinates survived session switches and other transcript rebuilds, highlighting unrelated text in the new transcript. Reset selection and multi-click state before re-rendering session entries.
Based on #9310.
fixes#9311
Co-authored-by: xdagiz <xdagiz@protonmail.com>
Kitty-protocol terminals only accept PNG. pi-tui now exposes
setImageTranscoder(); Image converts JPEG/GIF/WebP through it and shows
the text fallback without one. coding-agent registers a photon-based
transcoder, so images from extensions and all ToolExecutionComponent
hosts render.
closes#10292