Skip to content

Architecture

revision is a Go module with a layered, lazygit-inspired architecture. The layering is not a convention — a test enforces it.

Package Responsibility
cmd/revision The CLI entry point: flag parsing, working-copy detection, launching the TUI.
cmd/gallery A standalone gallery that renders each reusable UI component in isolation.
internal/svn A thin wrapper over the svn binary that parses --xml output into typed values. Always runs --non-interactive.
internal/tui The domain-agnostic UI foundation: reusable components plus theme, keymap, focus, layout, and messages.
internal/config The settings file: location, load, reconcile, persist.
internal/selfupdate Release checks and binary replacement.
internal/sshagent ssh-agent inspection and key loading.
internal/cache A size-bounded LRU, told how to weigh a value by whoever fills it.
internal/app The composition layer that adapts SVN data into components and arranges the lazygit layout.
Dependency direction
                         cmd/revision
                            │
                            ▼
                      internal/app  ── the only layer that knows both sides
                            │
    ┌─────────────┬─────────┼─────────┬──────────┬────────┐
    ▼             ▼         ▼         ▼          ▼        ▼
internal/svn  internal/tui  config  selfupdate  sshagent  cache
(domain)      (UI only)   └────────  infrastructure  ─────────┘

internal/tui must never import internal/svn or internal/app. A reusability-guard test in internal/tui/guard_test.go fails the build if it does.

internal/config, internal/selfupdate, internal/sshagent and internal/cache are self-contained infrastructure and are domain-agnostic for the same reason: any layer can use them without creating an import cycle. internal/cache in particular knows nothing of diffs or revisions — the caller supplies the key, the value and how to weigh it.

internal/app is the only package that knows both sides. If a change needs SVN knowledge and UI knowledge, it belongs there.

Inside internal/app, a few files carry most of the domain:

File What it owns
app.go The Bubble Tea model, layout, focus and key dispatch.
status.go Mapping SVN working-copy state onto theme colours.
filetree.go Building the collapsible directory tree from a flat status list.
changelist.go Grouping items into changelists, including the staged bucket.
diff.go / splitdiff.go Interpreting diff structure — the only places that parse a patch.
filter.go Parsing key:value filter queries and matching rows.
messages.go Every tea.Cmd that shells out, and the messages they return.
session.go The caches that live as long as the process: diffs, history pages, and work the screen would otherwise re-derive on every keystroke.
watch.go Fingerprinting the working copy on a tick so an edit made outside revision is noticed.
optimistic.go Moving a file between changelists in the model before svn confirms it, and putting it back when it fails.
pending.go Marking the rows of an action that has been asked for but not yet confirmed.
sourcepath.go Re-scoping the session to another directory inside the working copy.

Every reusable component under internal/tui/component follows the same three conventions:

  1. A compile-time interface assertion.
  2. A golden test over View(), with the expected output in testdata/.
  3. A teatest harness driving it as a real Bubble Tea program.

That triple is what lets the documentation screens on this site be read straight out of the golden files: they are the app’s own rendered output, not screenshots.

internal/svn never links a Subversion library. It runs the svn binary, asks for --xml where the subcommand supports it, and parses the result into typed values. Every invocation carries --non-interactive, so a missing credential is an error rather than a hidden prompt — see Authentication.

Commands that change the working copy are recorded and surfaced in the Command Log panel; read-only queries are not, so the log stays a record of what actually changed.