Add inline snapshot rendering for supported terminals (#125)#143
Draft
Add inline snapshot rendering for supported terminals (#125)#143
Conversation
Renders snapshot PNGs directly in the terminal via iTerm2's OSC 1337
inline-image protocol when stdout is a TTY and the terminal is
iTerm2/WezTerm/Ghostty. Falls back to the existing path-only output
everywhere else (Terminal.app, pipes/redirects, --json). Adds
`--inline {auto,always,never}` (default auto) and honors
`PREVIEWSMCP_INLINE`.
Handles tmux: when `$TMUX` is set, probes `tmux show-options -gv
allow-passthrough` and wraps the OSC in the DCS passthrough envelope
(`\ePtmux;…\e\\`) when passthrough is on; otherwise emits a one-line
stderr hint telling the user how to enable it. Large images (>1200px)
are downscaled to 1000px via CoreGraphics for display only — the file
on disk stays full resolution.
Decision logic is a pure function with injected env + tmux probe +
TTY flag, so the 28 new tests cover the full behavior matrix
(json/mode/tty × capability × tmux state) deterministically without a
real terminal. Golden-byte tests lock the iTerm2 and tmux-wrap
framings.
Kitty graphics protocol and Sixel are out of scope for this change;
filing as a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/test-inline-image.sh emits Sources/PreviewsIOS/AppIcon.png through the same OSC 1337 / DCS-wrap encoding the CLI uses, so a reviewer can verify their terminal setup in one command without spinning up the daemon + a simulator + a real preview file. Detects tmux and warns when allow-passthrough is off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Changes from the five-axis review:
- --inline always on an unrecognized terminal now surfaces a stderr
hint ("note: emitting inline image on an unrecognized terminal
because --inline always") so garbled output doesn't look like a
silent bug. Previously emitted bytes unconditionally.
- Move MockTerminalEnvironment out of Sources/ into the test file.
@testable import already exposes the protocol; shipping the mock in
the release binary was a mistake.
- Extract tmuxPassthroughOffHint and forcedOnUnsupportedHint as static
constants on TerminalCapabilityDetector so tests assert equality
instead of substring containment.
- DownscaleCG now logs a one-line stderr note on genuine failure
branches (CGImageSource / properties / thumbnail / destination /
finalize). Silent short-circuit on the under-threshold path
unchanged.
- Switch ITerm2Encoder to Data(header.utf8) + Data([0x07]) — drops two
force-unwraps without changing emitted bytes (golden-byte test
still passes).
- Name the 0x0A newline as a static constant in SnapshotCommand.
- Tighten existing hint assertions from `.contains(...)` to `==
constant`.
- Add 9-case parameterized test for all PREVIEWSMCP_INLINE string
variants ("0"/"1"/"true"/"false"/"never"/"always"/"auto"/""/garbage)
— previously only "0" and "1" were covered.
All 29 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
310408b to
d3371b4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #125.
Summary
preview snapshotnow renders the PNG inline in the terminal via iTerm2's OSC 1337 protocol when stdout is a TTY and the terminal is iTerm2, WezTerm, or Ghostty. The on-disk file behavior is unchanged.--inline {auto,always,never}(defaultauto); honorsPREVIEWSMCP_INLINE.$TMUXis set, probesallow-passthroughand either DCS-wraps the escape (if on) or prints a one-line stderr hint (if off).--outputstays full-resolution.Behavior matrix
--jsonneveralwaysTest plan
Tests/PreviewsCLITests/TerminalImagesTests.swift— all pass. Covers: golden-byte iTerm2 encoding, golden-byte tmux DCS wrap with ESC-doubling, 8-case capability classification, tmux passthrough state transitions, full decision matrix (mode × TTY × capability × env-var overrides × json).swift buildclean.previewsmcp snapshot Foo.swift— expect image inline, path below.cat— expect path only, no escape bytes.--json— expect JSON only.--inline never— expect path only.allow-passthrough on, local iTerm2 — expect image inline.allow-passthrough off, local iTerm2 — expect path + stderr hint, no garbage.Notes for reviewer
TerminalEnvironment,TmuxProbe, andstdoutIsTTYflag — unit tests run without a real terminal. The one real I/O point (ProcessTmuxProbe) is swapped out in tests viaStubTmuxProbe.🤖 Generated with Claude Code