Architecture¶
An overview of LazyWorktree's internal design for contributors who need to understand where behaviour lives and how the main subsystems fit together.
Use this page when: you want to understand package ownership, TUI versus CLI responsibilities, or where to make architectural changes.
Note
This page is a curated summary. The root DESIGN.md remains the authoritative architecture note for the repository.
Overview¶
LazyWorktree is a Git worktree manager with two entry paths built on shared subsystems:
- an interactive Bubble Tea TUI for day-to-day workflow
- CLI subcommands for scripting and non-interactive operations
At a high level:
cmd/lazyworktreeis the process entrypoint.internal/bootstrapbuilds the command graph and selects TUI or CLI mode.internal/appowns the Bubble Tea model, rendering, modal screens, and interactive workflows.internal/cliowns non-interactive orchestration for commands such as create, rename, delete, list, exec, and note.- Shared packages such as
internal/git,internal/config,internal/security,internal/theme, andinternal/multiplexersupport both modes.
Runtime Shape¶
cmd/lazyworktree/main.go
|
v
internal/bootstrap
|-----------------------------|
| |
v v
TUI path CLI path
internal/app internal/cli
| |
|---- uses shared subsystems --|
|
v
internal/config / internal/git / internal/security
internal/theme / internal/multiplexer / internal/models
Repository Map¶
| Path | Responsibility |
|---|---|
cmd/lazyworktree/ |
Process entrypoint |
internal/bootstrap/ |
CLI graph, flags, mode selection, TUI startup |
internal/app/ |
Bubble Tea model, updates, rendering, worktree workflows |
internal/app/screen/ |
Modal screens such as help, palette, trust, commit, note, checklist |
internal/app/services/ |
Shared TUI helpers for notes, status tree, CI cache, env expansion, pager, and watch support |
internal/app/state/ |
Small typed state containers for view and pending action state |
internal/cli/ |
Non-interactive operations and interactive selectors for subcommands |
internal/git/ |
Git, GitHub, and GitLab integration plus diff/pager helpers |
internal/config/ |
App config loading, CLI override merging, .wt repo config loading |
internal/security/ |
TOFU trust persistence for .wt files |
internal/theme/ |
Built-in themes, custom theme support, terminal background detection |
internal/multiplexer/ |
tmux, zellij, shell, and container command assembly |
internal/models/ |
Shared data types such as worktrees, PRs, issues, CI checks, and status files |
internal/commands/ |
Helper commands such as symlink setup |
TUI Architecture¶
The default lazyworktree command launches the Bubble Tea program from
internal/bootstrap.
internal/app follows the Model-Update-View pattern:
- Model coordinates configuration, theme, caches, selection state, async work, and shared services.
- Update logic lives primarily in
handlers.goplus focused feature files such asworktree_operations.go,app_git.go,ci.go, andworktree_sync.go. - View rendering is split across
render_*.go,layout.go,renderer.go, and table style helpers. - Modal screens are handled by
internal/app/screenthrough a stack-based screen manager.
The model state is intentionally split by concern:
- UI widgets and active screen stack
- worktree, status, notes, and log data currently on screen
- view and layout state
- injected services such as git, trust, status tree, and watch support
CLI Architecture¶
The CLI layer is not a thin wrapper over the TUI. internal/cli has its own
orchestration for:
- worktree creation and removal
- PR and issue selection
- trust checks for
.wtlifecycle hooks - interactive fallback with
fzfor prompt-based selection - editor, pager, and command execution helpers
This separation keeps the TUI responsive while still allowing automation workflows to reuse the same domain services and config rules.
Key Design Decisions¶
Bootstrap owns command wiring¶
The root command graph is assembled in internal/bootstrap using
urfave/cli/v3, not in the entrypoint package. This keeps main.go trivial and
centralises startup behaviour:
- global flags are defined once
- subcommands share the same config-loading path
- TUI startup and CLI execution apply overrides consistently
The TUI is stateful, but screens are modular¶
The main model is large because it coordinates panes, caches, selections, async
effects, and external integrations. To keep that manageable, modal behaviour is
pushed into internal/app/screen.
The screen manager is a simple stack:
- push a modal over the main view
- update only the active screen while it is open
- pop back to the previous screen on dismissal
Current screen types include confirm, info, input, textarea, note view, help, trust, welcome, commit, palette, diff, PR selection, issue selection, generic list selection, loading, commit files, checklist, taskboard, and commit message.
Git integration is intentionally CLI-first¶
internal/git.Service wraps external commands rather than embedding a pure Go
Git implementation.
Reasons:
- exact Git behaviour matters more than abstraction purity
- user credentials and forge tooling already live in the shell environment
- the same service can call
git,gh, andglab - errors and output are easier to align with real command-line behaviour
The service uses a buffered-channel semaphore to cap concurrent git work at
NumCPU * 2, clamped to the range 4..32.
Configuration is layered through files, git config, and runtime overrides¶
The current config model is:
- built-in defaults from
DefaultConfig() - YAML config from the standard config location or an explicit config file
- Git global config (
git config --global lw.*) - Git local config for the current repository (
git config --local lw.*) - runtime CLI overrides applied by bootstrap flags and
--config
Important details:
- terminal background detection chooses a default theme only when no theme was set by config sources
- repository lifecycle hooks are loaded separately from a
.wtfile, not from a repo-local YAML app config - there is no
LAZYWORKTREE_*application config cascade
External tooling is a first-class capability¶
LazyWorktree is designed to cooperate with the surrounding development environment instead of replacing it.
That shows up in several places:
- custom commands run in the shell with worktree-specific environment variables
- tmux and zellij session scripts are generated from config rather than hardcoded
- optional container execution wraps commands for Docker or Podman
- diff and CI output can flow through configured pager commands
- note and branch name generation can be delegated to external scripts
The command environment contract is shared across TUI and CLI paths so both modes speak the same worktree-aware variable vocabulary.
.wt lifecycle hooks use TOFU¶
Repository hooks are loaded from a .wt file in the repo root. Because these
commands can execute arbitrary shell code, LazyWorktree stores a hash-based
trust decision in:
$XDG_DATA_HOME/lazyworktree/trusted.json, or~/.local/share/lazyworktree/trusted.json
Supported trust modes are:
tofu: prompt on first use and when the file changesnever: refuse to run.wtcommandsalways: run without prompting
Data and Control Flow¶
TUI flow¶
- Bootstrap loads config, applies runtime overrides, and starts Bubble Tea.
app.NewModelcreates the git service, trust manager, status helpers, theme, and initial UI state.- User input becomes Bubble Tea messages.
- The active screen handles input first when a modal is open.
- Otherwise the main update loop routes the event to worktree, git, status, log, notes, CI, or palette handlers.
- Long-running work runs via
tea.Cmdand returns typed messages. - Render functions turn the current model state into the pane layout.
CLI flow¶
- Bootstrap loads config and applies CLI overrides.
- A git service is created with the configured diff pager behaviour.
internal/cliperforms validation, selection, trust checks, and worktree operations.- Shared helpers are reused for notes, command environments, repo config, and multiplexer integration where relevant.
Working on the Codebase¶
When adding a new feature:
- prefer the most specific package rather than growing a catch-all helper file
- keep rendering theme-backed; avoid hardcoded colours
- keep command environment and escaping rules centralised so TUI and CLI do not drift apart
- update the root
DESIGN.mdwhen a top-level subsystem, precedence rule, or cross-package ownership boundary changes
Testing Strategy¶
The test suite is broad and close to the implementation packages.
The current emphasis is:
- unit tests for config parsing, theme behaviour, trust logic, and helper functions
- service tests for git and multiplexer behaviour
- application tests for screen handling, layout, worktree operations, notes, diff flows, and command execution
- integration-style tests for end-to-end TUI and CLI flows
For new work, prefer targeted tests near the changed package before reaching for broader integration coverage.