feat(ui): show inline images in the Details pane #78

Merged
deadstyle merged 8 commits from feature/71-inline-images-details into develop 2026-10-09 09:08:12 -05:00
Owner

Summary

Draws images from PR and issue bodies inline in the Details pane, in kitty and Ghostty (tmux needs allow-passthrough). Everything else keeps today's 🖼 alt link, silently. Part 3 of #60, on top of #65 (fetch/decode) and #66 (terminal layer).

Changes

  • Markdown (internal/ui/markdown): Images(body) lists block images (a top-level paragraph holding only images, each optionally wrapped in a link); RenderWithImages(body, width, blocks) splices prepared lines in. Render is the same call with nil, byte-identical to before.
  • Details (internal/ui/images.go, details.go, model.go): per-image state (loading, ready, failed), loads through core.Service.Image, transmit/place through termimg, memo keyed on an imgGen counter. Resize re-fits; pane height caps the image.
  • App: detection result and the setting are pushed into the session; the old session's images are deleted on host switch and on quit, and cmd/lazyforge/main.go deletes them after Run returns so SIGINT/SIGTERM exits are covered too.
  • Settings: new Images section with a Show images toggle, default on ([images] show in the config file).
  • Docs: docs/design.md records the behavior; ADR 0016 has the final signatures.
  • Make: make test now passes -timeout=2m so a spinning test fails fast.

Decisions

  • Opus design (see #60, ADR 0016, plan on #71): placeholders only; the allowlist is kitty and Ghostty.
  • Assumed (recorded on #71): only top-level paragraphs draw (a lone image in a list or quote keeps the link); images shrink to fit and are never enlarged; "leaving the session" means the session being replaced (host switch), not going to the picker or Settings; HTML <img> keeps today's rendering (it draws nothing now).
  • Signal-driven quits skip the UI's Update, so the release runs after Run returns.
  • Nothing loads while the repo list is showing, since the Details pane is hidden there.

Test plan

  • make build, make test (race, -timeout=2m, 13 packages, about 30 s), go vet, gofumpt, go mod tidy -diff, golangci-lint v2.14.0 (0 issues); go.mod/go.sum unchanged.
  • Mutation check: 45 deliberate code breaks, 43 caught by an assertion or a hang. Three tests that stayed green under their break were rewritten. One not caught: the post-Run release in main.go (no headless test).
  • A test helper looped forever under one break (per-occurrence reload); both run helpers now stop at 5000 steps.
  • Not testable here, manual before relying on it: kitty, Ghostty, tmux with passthrough on and off; scrolling half an image; resizing; no ghost image after quit or a host switch (including kill -TERM); a logged-in image download from a real forge.

Follow-ups

  • WebP, external hosts, HTML <img> and other terminals stay out of scope.

Refs #71

🤖 Generated with Claude Code

https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq

## Summary Draws images from PR and issue bodies inline in the Details pane, in kitty and Ghostty (tmux needs `allow-passthrough`). Everything else keeps today's `🖼 alt` link, silently. Part 3 of #60, on top of #65 (fetch/decode) and #66 (terminal layer). ## Changes - **Markdown** (`internal/ui/markdown`): `Images(body)` lists block images (a top-level paragraph holding only images, each optionally wrapped in a link); `RenderWithImages(body, width, blocks)` splices prepared lines in. `Render` is the same call with nil, byte-identical to before. - **Details** (`internal/ui/images.go`, `details.go`, `model.go`): per-image state (loading, ready, failed), loads through `core.Service.Image`, transmit/place through `termimg`, memo keyed on an `imgGen` counter. Resize re-fits; pane height caps the image. - **App**: detection result and the setting are pushed into the session; the old session's images are deleted on host switch and on quit, and `cmd/lazyforge/main.go` deletes them after `Run` returns so SIGINT/SIGTERM exits are covered too. - **Settings**: new `Images` section with a `Show images` toggle, default on (`[images] show` in the config file). - **Docs**: `docs/design.md` records the behavior; ADR 0016 has the final signatures. - **Make**: `make test` now passes `-timeout=2m` so a spinning test fails fast. ## Decisions - Opus design (see #60, ADR 0016, plan on #71): placeholders only; the allowlist is kitty and Ghostty. - Assumed (recorded on #71): only top-level paragraphs draw (a lone image in a list or quote keeps the link); images shrink to fit and are never enlarged; "leaving the session" means the session being replaced (host switch), not going to the picker or Settings; HTML `<img>` keeps today's rendering (it draws nothing now). - Signal-driven quits skip the UI's `Update`, so the release runs after `Run` returns. - Nothing loads while the repo list is showing, since the Details pane is hidden there. ## Test plan - `make build`, `make test` (race, -timeout=2m, 13 packages, about 30 s), `go vet`, `gofumpt`, `go mod tidy -diff`, golangci-lint v2.14.0 (0 issues); `go.mod`/`go.sum` unchanged. - Mutation check: 45 deliberate code breaks, 43 caught by an assertion or a hang. Three tests that stayed green under their break were rewritten. One not caught: the post-`Run` release in `main.go` (no headless test). - A test helper looped forever under one break (per-occurrence reload); both run helpers now stop at 5000 steps. - **Not testable here, manual before relying on it:** kitty, Ghostty, tmux with passthrough on and off; scrolling half an image; resizing; no ghost image after quit or a host switch (including `kill -TERM`); a logged-in image download from a real forge. ## Follow-ups - WebP, external hosts, HTML `<img>` and other terminals stay out of scope. Refs #71 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
Images that stand alone in a top-level paragraph of a PR or issue body draw inline in the Details pane when the terminal supports them. A Show images setting turns them off; every other case keeps the 🖼 alt link.

Refs #71

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
Covers the markdown seam (Images, RenderWithImages), the image set and its messages through Model, App wiring (detection, the Show images toggle, release on quit and on session switch), the config default, and the Settings row. The seams do not exist yet, so these packages fail to compile until the implementation lands.

Refs #71

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
The settings toggle test pressed one row short of Show images with two
hosts configured. The resize test required placeholder rows that sit
below the pane's bottom edge. The off-host link check did not strip the
OSC 8 markers the link renderer emits. The cancelled-load test missed
that the same Update's sync re-requests the image.

Refs #71

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
startSession applies the images setting to the fresh session directly
instead of routing it through App.toSession, which drops the contextcheck
finding and a needless Update pass. Test helpers lose the ctx parameter
and the always-constant key, and three dead assignments go.

Refs #71

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
Mutation checks showed three tests passed with their guard removed: the set-identity checks, the pick of the live host, and core's same-host check. They now set up the state each guard protects: a late load or placement for a key the new session also holds, a pick of the live host, and an off-host fetch that reaches the forge if core does not refuse it. The App run helpers also bound their message loops, so a feedback loop fails the test instead of hanging it.

Refs #71

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
The Settings test only toggled Show images from off, so forcing the setting on still passed it. It now toggles from on as well.

Refs #71

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
chore(make): fail a test package that runs longer than two minutes
All checks were successful
ci / check (pull_request) Successful in 1m2s
233eb1bbdd
A mutated test helper once spun for over an hour at full CPU. Go's default
10 minute cap is too long to notice; the suite takes about 35 s.

Refs #71

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APHERaPcnmg6YuAkRuoDYq
deadstyle merged commit 5fd375a661 into develop 2026-10-09 09:08:12 -05:00
deadstyle deleted branch feature/71-inline-images-details 2026-10-09 09:08:12 -05:00
Sign in to join this conversation.
No description provided.