Skip to content

Show article images (#99) by moving the TUI to Limoni - #201

Closed
thebanri wants to merge 1 commit into
guyfedwards:masterfrom
thebanri:limoni
Closed

thebanri wants to merge 1 commit into
guyfedwards:masterfrom
thebanri:limoni

Conversation

@thebanri

@thebanri thebanri commented Oct 4, 2026

Copy link
Copy Markdown

Closes #99.

Disclosure first: I maintain Limoni, the Go TUI library this PR moves nom onto. I picked nom because #99 is something you said you'd welcome, and doing it properly needed a renderer that can place pictures. Please treat this as a proposal. If you'd rather keep Bubble Tea, say so and I'll close it. I'm also happy to split it up or change anything below.

What changes for a reader

  • Pictures in articles (Any thoughts to support terminal image formats? #99). Images in an article are downloaded and drawn where the article puts them. The terminal's own protocol is used when it has one (kitty, iTerm2, Sixel). Anywhere else they are drawn in coloured half blocks. A picture that is partly scrolled off screen shows only the part still in view. 1×1 tracking pixels are skipped. images: false in the config turns all of this off, and the alt text is shown instead.
  • Clickable links. In terminals with OSC 8 hyperlinks (kitty, Ghostty, WezTerm, foot, Alacritty, Konsole, iTerm2, GNOME Terminal and other VTE ones), link text is clickable and the URL is no longer printed into the paragraph. Elsewhere the URL is printed after the text, as before.
  • Relative links and images are resolved against the article's own address, so /blog/x becomes https://go.dev/blog/x.
  • Mouse. The wheel scrolls the list and the article, and a click selects a row. Because the app now receives mouse events, selecting text with the mouse needs Shift held, the same as in other mouse-aware TUIs. If you'd rather not have this, I can make mouse capture optional.
  • Everything else should look and behave the same: same layout, same keys, same help, same colours. theme.glamour still accepts dark, light, dracula, pink, ascii and notty, using the colours glamour gives those styles. custom reads the colours from the glamour style file. Margins and the ## heading prefixes in the style files are not reproduced.

Small differences I'd like you to look at:

  • The list scrolls instead of paging, and shows 12/114 where the pagination dots were. h/l/PgUp/PgDn still move a page.
  • In the article view, f only favourites. Before, it also paged down, because bubbles' viewport binds f to page down too.
  • Next/prev in the article view now follow the filtered list when a filter is applied. Before, they indexed the unfiltered items.
  • Windows: E (edit config) and openers with takeover: true report on the status line that the terminal can't be handed to another program. Limoni can't hand the Windows console to a child process yet. I'd rather say so than hide it. Ctrl+Z was Unix-only before too.

Measurements

Same machine, same SQLite database (114 items from seven real feeds served locally), and the same scripted session. The session is 41 list moves, opening a long article (the Go blog's SIMD post), 30 line scrolls, 3 page-downs, end/top, 3 next-article, back, then a filter typed, applied and cleared.

Both binaries ran in a 100×30 pty with TERM=xterm-256color. The pty is answered by a terminal emulator (charmbracelet/x/vt), so capability queries get real answers. Each version ran three times; medians below.

  • Before: nom 7f38ccc with Bubble Tea v1.2.4, bubbles v0.18.0, lipgloss v1.1.1-0.20250404, glamour v0.10.0.
  • After: this branch with Limoni v0.10.0.
before after
bytes written to the terminal, whole session 572,069 45,976 (12.4× fewer)
… scrolling the article 30 lines 395,386 3,075
… moving through the list 41 rows 16,692 9,322
key press → frame complete, typical (p50) 12.7–14.3 ms 0.1–0.5 ms
Esc (back, clear filter) 12.6–13.7 ms 25.3–27.3 ms
opening an article 30.4 ms 9.7 ms
start → first full frame 33 ms 7 ms
CPU while idle, 10 s 30 ms 0 ms
peak RSS 66.6 MB 30.8 MB
binary (linux/amd64, go build) 30.1 MB 19.5 MB
modules in go.mod 47 20

How to read these:

  • Scrolling bytes. Most of the difference comes from scroll regions. Limoni scrolls the screen with DECSTBM + SU/SD and writes only the new line, while Bubble Tea v1's renderer repaints every changed line. Bubble Tea v2's renderer (Ultraviolet) has scroll regions too, so a v2 port would close part of that gap. This comparison is against v1.2.4, the version nom uses.
  • Latency. Bubble Tea v1 draws on a 60 fps ticker, so most of its ~13 ms is waiting for the next frame, not work.
  • Esc is slower. Limoni waits 25 ms after a lone ESC byte to tell the Esc key apart from the start of an escape sequence that arrived split. In terminals that speak the kitty keyboard protocol (kitty, foot, Ghostty and WezTerm among them) Esc arrives unambiguously and there is no wait. In the measurement terminal there was.
  • Pictures cost bytes. With images on, a picture costs what the protocol costs: the xkcd comic in kitty was about 105 KB, sent once. The table above was measured with images: false, so both sides render the same thing.

The harness (a pty driver plus emulator, ~200 lines of Go), the raw numbers and the screenshots are on a separate branch of my fork: https://github.com/thebanri/nom/tree/limoni-preview

Tests

nom had no TUI tests. This adds eight, using Limoni's uitest. They run the real model through the real message loop on an in-memory terminal, against an in-memory SQLite store, and drive it the way a user does: press keys, type into the filter, read rows and article text through the semantic tree.

  • opening an article from the list, next/prev, back
  • filtering as you type, applying, clearing, opening the one row left
  • marking read (it leaves the default view) and M
  • favourites and F
  • help, ?, and q quitting
  • E without a terminal to hand over reporting so instead of hanging
  • a picture fetched from a local server, its relative URL resolved against the article, and drawn
  • images: false fetching nothing

Plus unit tests for the colour and glamour-style mapping. go test -race ./... passes, and so do go vet and gofmt.

It was also checked by hand in kitty 0.49.2 and in a browser through xterm.js:

  • pictures, OSC 8 links
  • E handing the terminal to an editor that reads a line: the editor got the whole line and nom repainted after
  • Ctrl+Z / fg five times in a row under bash, with keys working after each
  • the shell's terminal modes after quitting

Dependencies

  • Removed: bubbletea, bubbles, lipgloss, glamour and what they pulled in (chroma, goldmark, termenv, reflow, bluemonday, …).
  • Added: github.com/thebanri/limoni and golang.org/x/image (WebP decoding).
  • The go directive moves from 1.24.0 to 1.25.0, which Limoni requires. Your CI already runs Go ≥ 1.25.

Files

  • internal/commands/tui.go, list.go, viewport.go, key.go: the TUI, same structure and behaviour as before.
  • internal/commands/images.go: downloads, limited to 4 at a time, 10 MB each, scaled to at most 1200 px wide.
  • internal/commands/theme.go: glamour style names to colours.
  • internal/commands/commands.go: the article is now Markdown for the TUI to draw, with relative URLs resolved. The store, feeds, filter logic and config are unchanged, apart from the new images key.
  • README.md: the theme note and the images option.

Articles show their pictures (guyfedwards#99): kitty, iTerm2 or Sixel where the
terminal has one, half blocks elsewhere. images: false turns it off.
Links are clickable where the terminal supports OSC 8, relative links
and images are resolved against the article, and the mouse wheel
scrolls. Layout, keys, help and theme colours stay as they were.

Adds the first TUI tests, driving the real program through uitest.
Copilot AI balanced review requested due to automatic review settings October 4, 2026 20:29

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@guyfedwards

Copy link
Copy Markdown
Owner

Hey @thebanri, thanks for the PR but not looking to move away from bubble tea. happy to accept any of the individual fixes separately though. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Any thoughts to support terminal image formats?

3 participants