Commit graph

7 commits

Author SHA1 Message Date
brooklyn!
75b428c852
feat(ui-tui): resolve markdown links to readable page titles (#24013)
* feat(ui-tui): resolve links to readable page titles

Mirror desktop pretty-link behavior in the TUI by resolving HTTP links to page titles with shared caching and safe fetch filters, plus slug-based fallbacks so chat links stay readable even when title fetch fails.

* refactor(ui-tui): tighten link-title fallback handling

Clean up the link-title resolver by hardening in-flight cleanup and clarifying title length limits, while adding focused coverage for HTML entity decoding and markdown-label fallback behavior.

* fix(ui-tui): block private-network targets in title fetches

Prevent automatic link-title resolution from requesting local or private hosts by rejecting RFC1918, link-local, ULA, and intranet-style hostnames before fetch, and add regression coverage for blocked host patterns.
2026-05-11 14:16:31 -07:00
Teknium
1d00716754
fix(cli,tui): align CJK / wide-char markdown tables (#23863)
CJK and emoji glyphs render as two terminal cells but JS String#length
and the model's own padding count them as one, so any markdown table
with Chinese / Japanese / Korean cells drifts right per row when a
real terminal renders it. Both surfaces fix this with a display-cell
width measurement (wcswidth on the Python side, stringWidth on the
TUI side).

Changes:
- agent/markdown_tables.py: new helper. realign_markdown_tables(text)
  detects markdown table blocks (header + |---| divider) and
  rewrites the row padding using wcwidth.wcswidth so every pipe and
  dash lines up across rows. No-op on text without tables.
- cli.py: hook the helper into _render_final_assistant_content for
  strip / render modes (raw passes through untouched), and into the
  streaming line emitter so live token-by-token rendering also
  produces aligned tables. A small two-buffer state machine in
  _emit_stream_text holds table rows until the block ends, then
  flushes them through the realigner so all rows pad to a single
  per-column width.
- ui-tui/src/components/markdown.tsx: renderTable now uses
  stringWidth (Bun.stringWidth fast path + East-Asian-width-aware
  fallback, already memoised in @hermes/ink) instead of UTF-16
  String#length for both column-width measurement and per-cell
  padding. Drops the comment that documented the bug as a deliberate
  limitation.

Validation:
- New tests/agent/test_markdown_tables.py (11): every rebuilt block
  shares pipe column offsets across rows for pure CJK, mixed
  CJK+emoji, ragged-row, and multi-table inputs.
- Updated tests/cli/test_cli_markdown_rendering.py: the existing
  strip-mode test asserted exact whitespace; rewritten to assert the
  alignment contract (cell content survives + every rendered row
  shares pipe offsets).
- New ui-tui markdown.test.ts case (1): rendered column-2 start
  offset is identical for the header + every body row, including
  the CJK row that drifted before the fix.
- Live: hermes chat -q with the user-reported screenshot prompt now
  produces a perfectly aligned table on the wire (header, divider,
  4 body rows including '通义千问', all pipes at identical columns).
2026-05-11 11:13:06 -07:00
brooklyn!
a7e7921dbc
fix(tui): trim markdown wrap spaces (#22062)
* fix(tui): trim markdown wrap spaces

Use trim-aware wrapping for markdown prose so word-wrapped continuation lines do not keep boundary spaces.

* fix(tui): simplify markdown wrap nodes

Keep trim-aware wrapping on the rendered markdown text node while leaving nested inline segments as plain virtual text.

* fix(tui): trim definition row wrapping

Apply trim-aware wrapping to markdown definition rows so continuation lines match other prose rows.

* fix(tui): trim list and quote wrapping

Put trim-aware wrapping on the rendered list and quote rows that own markdown inline layout.

* fix(tui): preserve markdown nesting with trim wrap

Move list and quote indentation into layout padding so trim-aware wrapping does not erase nested markdown structure.

* fix(tui): trim only soft wrap spaces

Change trim-aware wrapping to remove whitespace only at soft-wrap boundaries so original leading inline spaces stay verbatim.

* fix(tui): preserve extra boundary whitespace

Trim only one soft-wrap boundary whitespace character so wrap-trim avoids leading continuations without collapsing intentional spacing.

* fix(tui): align styled wrap-trim mapping

Update styled text remapping to skip the single whitespace removed at soft-wrap boundaries without dropping preserved indentation.

* fix(tui): clean wrap trim test helpers

Clarify boundary-trim wording and strip OSC escapes from markdown render test output.

* fix(tui): strip osc before ansi in markdown tests

Remove OSC escapes from raw render output before SGR/CSI cleanup so markdown render assertions stay plain text.
2026-05-08 20:51:34 -07:00
Austin Pickett
c3d39feb3a feat(latex): latex in tui 2026-04-28 19:08:11 -04:00
Brooklyn Nicholson
43eb1153e9 fix(tui): don't swallow Kimi/Qwen ~! ~? kaomoji as subscript spans
The inline markdown regex had `~([^~\s][^~]*?)~` for Pandoc-style subscript
(H~2~O, CO~2~). On models that decorate prose with kaomoji like `thing ~!`
and `cool ~?` — Kimi especially — the opener `~!` paired with the next
stray `~` on the line and dim-formatted everything between them with a
leading `_` character, mangling markdown output.

Tighten the pattern to short alphanumeric-only content (`~[A-Za-z0-9]{1,8}~`)
since real subscript never contains punctuation, spaces, or long runs.
Same tightening applied to stripInlineMarkup so width measurement stays
consistent. Classic CLI was unaffected because it renders these literally.
2026-04-21 17:34:48 -05:00
Brooklyn Nicholson
97c2da2112 fix(tui): render MEDIA: as a clickable file chip, drop audio directive
The agent emits `MEDIA:<path>` to signal file delivery to the gateway,
and `[[audio_as_voice]]` as a voice-delivery hint. The gateway strips
both before sending to Telegram/Discord/Slack, but the TUI was rendering
them raw through markdown — which is also how the intraword underscore
bug originally surfaced (`browser_screenshot_ecc…`).

At the `Md` layer, detect both sentinels on their own line:
- `MEDIA:<path>` → `▸ <path>` with the path rendered literal and wrapped
  in a `Link` for OSC 8 hyperlink support (absolute paths get a
  `file://` URL, so modern terminals make them click-to-open).
- `[[audio_as_voice]]` → dropped silently; it has no meaning in TUI.

Covers tests for quoted/backticked MEDIA variants, Windows drive paths,
whitespace, and the inline-in-prose case (left untouched — still
protected by the intraword-underscore guard).
2026-04-20 17:11:54 -05:00
Brooklyn Nicholson
b17eb94907 fix(tui): don't italicize intraword underscores in markdown
The inline markdown regex matched `_..._` / `__...__` anywhere, so file
paths like `browser_screenshot_ecc1c3feab.png` got mid-path italics.

Require non-word flanking (`(?<!\w)` / `(?!\w)`) on underscore emphasis
so snake_case identifiers and paths render literally, matching the
CommonMark intraword rule. `*` / `**` keep intraword semantics.
2026-04-20 17:04:09 -05:00