Skip to main content
Version: 0.2.3

Terminal UI Architecture

This document covers the technical implementation of the Sercha TUI.

Framework

The TUI is built with Bubbletea, a Go framework for building terminal UIs based on the Elm Architecture.

Why Bubbletea?

FeatureBenefit
Elm ArchitecturePredictable state management
ComposableBuild complex UIs from simple components
TestablePure functions are easy to test
Go-nativeNo CGO, easy cross-compilation

Supporting Libraries

LibraryPurpose
BubblesPre-built components (text input, viewport)
LipglossStyling and layout

Directory Structure

internal/adapters/driving/tui/
├── app.go # Main application (tea.Model)
├── ports.go # Port aggregation for DI
├── errors.go # TUI-specific errors
├── views/
│ ├── menu/ # Main menu view
│ │ └── menu.go
│ ├── search/ # Search view
│ │ └── search.go
│ ├── sources/ # Sources list view
│ │ └── sources.go
│ ├── sourcedetail/ # Source detail view
│ │ └── sourcedetail.go
│ ├── documents/ # Documents list view
│ │ └── documents.go
│ ├── doccontent/ # Document content view
│ │ └── doccontent.go
│ └── docdetails/ # Document details view
│ └── docdetails.go
├── components/
│ ├── input/ # Text input component
│ ├── list/ # Result list component
│ └── status/ # Status bar component
├── styles/
│ └── theme.go # Colors and styling
├── keymap/
│ └── keymap.go # Keyboard bindings
└── messages/
└── messages.go # Message types

Core Concepts

Model-Update-View

Every Bubbletea component implements tea.Model:

MethodPurpose
Init()Returns initial command (e.g., start cursor blink)
Update(msg)Handles messages, returns new model + commands
View()Returns string to render

Message Flow

Messages flow through the component hierarchy:

Custom Messages

The TUI defines custom message types for internal communication:

MessagePurpose
QueryChangedSearch query was modified
SearchCompletedSearch finished (with results or error)
ResultSelectedUser selected a result
ViewChangedActive view changed
ErrorOccurredAn error occurred
SourcesLoadedSources list loaded
SourceSelectedUser selected a source
SourceRemovedSource was deleted
DocumentsLoadedDocuments list loaded
DocumentSelectedUser selected a document
DocumentContentLoadedDocument content fetched
DocumentDetailsLoadedDocument details fetched
QuitUser requested quit

Component Details

App (app.go)

The root component that:

  • Owns all state
  • Routes messages to child views
  • Manages view transitions
  • Coordinates with driving ports

Search View (views/search/)

Composes three components:

  • TextInput: Query entry field
  • ResultList: Scrollable results
  • StatusBar: State and help hints

Sources View (views/sources/)

Displays list of configured sources:

  • Source List: Scrollable list with type indicators
  • Delete Action: Remove source with confirmation

Source Detail View (views/sourcedetail/)

Detail panel for a single source:

  • Source Info: Name, type, ID
  • Document Count: Number of indexed documents
  • Options Menu: View docs, sync, delete, back

Documents View (views/documents/)

Lists documents for a source:

  • Document List: Scrollable with titles
  • Action Menu: Popup for document actions

Document Content View (views/doccontent/)

Full-text content display:

  • Text Area: Scrollable content with line wrapping
  • Navigation: Vim-style scroll keys

Document Details View (views/docdetails/)

Document metadata display:

  • Property List: Key-value pairs
  • Scrollable: For long metadata lists

Components (components/)

Reusable UI elements:

ComponentResponsibility
SearchInputWraps bubbles/textinput with styling
ResultListDisplays and navigates search results
StatusBarShows state, result count, key hints

Styles (styles/)

Centralized theming using Lipgloss:

StyleUsage
TitleHeaders and labels
SelectedHighlighted items
ErrorError messages
MutedSecondary text
InputFieldText input borders

Port Integration

The TUI is a driving adapter in the hexagonal architecture.

Ports Struct

The TUI aggregates required ports:

PortInterfacePurpose
Searchdriving.SearchServiceExecute searches
Sourcedriving.SourceServiceManage sources
Syncdriving.SyncOrchestratorTrigger syncs
Documentdriving.DocumentServiceManage documents

Dependency Injection

Services are injected via cli.SetTUIConfig() before the CLI executes:

No Direct Service Imports

The TUI only imports:

  • core/ports/driving (interfaces)
  • core/domain (entities)

It never imports core/services directly, maintaining hexagonal architecture purity.

Testing

Components are tested using:

  • Unit tests: Test Update() and View() in isolation
  • Mock ports: Inject mock implementations of driving ports
  • Message assertions: Verify correct messages are produced

Key test files:

  • app_test.go - App behavior tests
  • ports_test.go - Mock service definitions
  • views/search/search_test.go - Search view tests
  • components/*/..._test.go - Component tests

Key Source Files

FileDescription
internal/adapters/driving/tui/app.goMain TUI application
internal/adapters/driving/tui/ports.goPort aggregation
internal/adapters/driving/tui/views/menu/menu.goMain menu view
internal/adapters/driving/tui/views/search/search.goSearch view
internal/adapters/driving/tui/views/sources/sources.goSources list
internal/adapters/driving/tui/views/sourcedetail/sourcedetail.goSource detail
internal/adapters/driving/tui/views/documents/documents.goDocuments list
internal/adapters/driving/tui/views/doccontent/doccontent.goDocument content
internal/adapters/driving/tui/views/docdetails/docdetails.goDocument details
internal/adapters/driving/tui/styles/theme.goTheming
internal/adapters/driving/cli/tui.goCLI command integration