skip to content
all projects

Terminal tool / 2026

tbook

An EPUB reader in the terminal. Text, images and reading state in one Go binary.

context
Personal project, public code
role
Go reader development
state
Functional; no published release
stack
Go / Bubble Tea / Glamour / EPUB
view code
tbook library with three books and an Alice in Wonderland cover rendered as ANSI blocks.
The actual tbook library: keyboard selection and a cover extracted from the EPUB. Capture documented in public/shots/README.md.

what it is

tbook opens DRM-free EPUBs, renders covers and illustrations as ANSI blocks, and provides search, bookmarks and themes. Project Gutenberg integration brings book search and downloads into the CLI.

the problem

Reading in a terminal means translating a document format into a character grid. Alongside text, the reader needs to preserve images, navigation and a position to resume between sessions.

the constraints

A character grid
Images are reduced to at most 48×24 and drawn with ▀, using truecolor or a 256-colour fallback.
Local state
Configuration, bookmarks, chapter and reading percentage are stored in JSON. There is no cross-device sync.

the architecture

System responsibilities. The session can be tested without opening the interface.

  1. Input and library

    internal/cli

    Commands, search and Project Gutenberg downloads.

  2. Document

    internal/epub

    EPUB, chapters, images and Markdown conversion.

  3. Session

    internal/core

    Chapter, percentage, bookmarks and reading time.

  4. Interface

    internal/ui

    Bubble Tea and rendering per chapter/width.

  5. Persistence

    internal/store

    config.json and state.json in the user directory.

A component map, not a call sequence: the TUI uses the session; core imports epub and store without importing Bubble Tea.

Pride and Prejudice in the terminal with text, an ANSI illustration and chapter progress.
Text and illustration in one terminal. The interface consumes a reading session that owns its state.

technical decisions

  1. A core independent of the TUI

    why
    Session owns navigation and state, with tests for resuming, chapter boundaries, bookmarks and persistence.
    instead of
    Keeping these rules inside interface handlers.
    the price
    CLI and TUI must coordinate the session and saving.
  2. Images as ANSI characters

    why
    Rendering does not rely on emulator-specific graphics protocols.
    instead of
    Using kitty, sixel or another protocol to display the original image.
    the price
    Resolution is limited; illustrations remain recognisable but lose detail.
  3. JSON persistence

    why
    State is small and local. The tool starts without a database or helper process.
    instead of
    Adding an embedded database and structured queries.
    the price
    No sync; the local file is the source of state.

limits and lessons

Resuming has a defined precision

The code stores chapter and percentage, not a paragraph anchor. Width changes can affect the visible passage when resuming.

Distribution is unfinished

A goreleaser configuration and release workflow exist, but no tags were published at review time. The repository also has no license file.

current state

The go test ./... suite passed on the linked revision on 1 October 2026. The screenshots are actual binary captures already recorded in the portfolio repository.

A local test is not an adoption metric. This case makes no user-count or performance claims.

next questions

  • Publish a release and specify the license.
  • Evaluate a reading position independent of interface width.

evidence

go test ./... · 2026-10-01

?   github.com/bernardofernandezz/tui-ebook-reader/cmd/tbook  [no test files]
ok  github.com/bernardofernandezz/tui-ebook-reader/internal/cli    0.059s
ok  github.com/bernardofernandezz/tui-ebook-reader/internal/core   0.027s
ok  github.com/bernardofernandezz/tui-ebook-reader/internal/epub   0.011s
ok  github.com/bernardofernandezz/tui-ebook-reader/internal/store  0.006s
ok  github.com/bernardofernandezz/tui-ebook-reader/internal/ui     0.038s
Local output at commit 03186d4; these execution times are not a benchmark.

sources and review

next: Scheduling API