Skip to content

fix(tui-image): keep a herdr pane off iTerm2 and Sixel - #227

Merged
nikomatt69 merged 1 commit into
live-mainfrom
fix/tui-image-herdr-protocols
Aug 18, 2026
Merged

fix(tui-image): keep a herdr pane off iTerm2 and Sixel#227
nikomatt69 merged 1 commit into
live-mainfrom
fix/tui-image-herdr-protocols

Conversation

@SandroHub013

Copy link
Copy Markdown
Collaborator

Issue for this PR

Closes #226

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Detection assumed the environment describes the terminal we write to. Under a multiplexer it does not: herdr leaves WEZTERM_EXECUTABLE, TERM_PROGRAM, ITERM_SESSION_ID and friends in the child environment while the bytes actually land in its own VT, libghostty. So nikcli sent OSC 1337 inline images into a VT that parses them and drops them as unimplemented, and the user got an empty box.

insideHerdr() checks HERDR_PANE_ID / HERDR_ENV / HERDR_SOCKET_PATH. When it is true:

  • detectCapabilities pins the set to Kitty — the one protocol libghostty implements — and reports terminal: "herdr"
  • applyLiveCapabilities drops Sixel and iTerm2 from the live answer, because that answer is libghostty's DA1 reply and cannot speak for the host terminal
  • supportsKittyUnicodePlaceholders returns true, since placeholders are what herdr re-emits to the host clipped to the pane rectangle; a raw placement would not survive its repaints

Note the predicate is deliberately looser than HerdrBridge.isInHerdrPane, which requires all three variables because it is about to open a socket. A pane that only exports HERDR_PANE_ID still cannot render iTerm2 images, so for protocol negotiation the OR is the correct test.

How did you verify your code works?

Reproduced on Windows 11 with herdr 0.8.0-preview hosted in WezTerm: before the change the preview frame is empty, after it the image draws through Kitty placeholders. The leaked environment is in #226.

Added two tests in packages/tui-image/test/capabilities.test.ts — one feeding HERDR_PANE_ID together with a full WezTerm/iTerm2 environment and asserting only Kitty survives, one asserting a sixel: true live answer is ignored inside a pane — plus one in kitty-placeholder.test.ts. bun test packages/tui-image is 83/83, tsc --noEmit clean, prettier clean.

Screenshots / recordings

Terminal rendering, before/after described above; the empty-frame case is in #226.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

herdr is a multiplexer: the bytes we write land in its own VT (libghostty),
not in the terminal that launched it. It overrides TERM and COLORTERM but
leaves the host's identity variables behind, so detection saw
WEZTERM_EXECUTABLE and negotiated iTerm2 inline images with a VT that parses
OSC 1337 and discards it as unimplemented. Nothing was drawn.

Detect the pane from HERDR_PANE_ID / HERDR_ENV / HERDR_SOCKET_PATH and pin the
capability set to Kitty, which libghostty does implement, including the
Unicode placeholders herdr needs to re-emit a placement clipped to the pane
rectangle. The live DA1 answer is filtered the same way, since it comes from
libghostty and cannot speak for the host.

The bridge's isInHerdrPane requires all three variables because it is about to
open a socket; here the loose check is the correct one — a pane started with
only HERDR_PANE_ID still cannot render iTerm2 images.
@github-actions github-actions Bot added the size/M PR touches 74 lines label Aug 18, 2026
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

sixel: detected.has(Protocol.SIXEL),
iterm2: detected.has(Protocol.ITERM2),
terminal: termProgram ?? term,
terminal: insideHerdr(env) ? "herdr" : (termProgram ?? term),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor consistency nit: applyLiveCapabilities caches the predicate as const inHerdr = insideHerdr(env) (line 149), but detectCapabilities calls insideHerdr(env) twice — once at line 224 in the clear/add branch and again at line 238 in the returned terminal field. Hoisting it to a local would match the other function and skip one env-var scan; the call is cheap so this is purely stylistic.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@nikomatt69 nikomatt69 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (local verification, 2026-08-18)

Correct diagnosis for #226. herdr leaks host identity (WEZTERM_*, ITERM_SESSION_ID) while the pane VT is libghostty, so pinning to Kitty and forcing Unicode placeholders is the protocol that actually survives the multiplexer.

NIKCLI_KITTY_PLACEHOLDERS=0 is still checked first in supportsKittyUnicodePlaceholders, so the explicit override still wins.

insideHerdr() is deliberately looser than HerdrBridge.isInHerdrPane() (HERDR_PANE_ID || HERDR_ENV || HERDR_SOCKET_PATH vs all three plus HERDR_ENV==="1"). That is the right test for protocol negotiation: a pane that only exports HERDR_PANE_ID still cannot render OSC 1337.

Verified

  • cd packages/tui-image && bun test on this branch → 83 pass, 0 fail (matches the PR)
  • CI typecheck passed
  • Merges clean onto current live-main (no conflicts)

CI that is red is not this PR — same Windows Bun remap 255 / duplicate-pr nikcli missing / linux test queued as on #225.

Nit only (not blocking): detectCapabilities calls insideHerdr(env) twice; applyLiveCapabilities already caches it. Cheap, skip.

Merge: yes, after #225 (no file overlap; order is only so the TUI crash lands first).

@nikomatt69
nikomatt69 merged commit 2e66a95 into live-main Aug 18, 2026
20 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M PR touches 74 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Images render as an empty box inside a herdr pane (host terminal identity leaks into detection)

2 participants