9.4 KiB
Gitea TUI (fjtui) System Architecture
This document provides a comprehensive overview of the Gitea/Forgejo TUI Dashboard's system architecture, component layout, and user flows. It uses Mermaid.js diagrams to detail the application structure and page lifecycle transitions.
1. High-Level Architecture
The Gitea TUI client is a modular CLI application written in TypeScript and Node.js. It operates as a local state machine that communicates with a Gitea or Forgejo instance REST API over HTTPS.
graph TD
subgraph Client ["Gitea TUI Application Context"]
EP["index.ts (CLI Entry Point)"]
ConfigManager["config.ts (Configuration Manager)"]
TuiEngine["tui.ts (TuiEngine Controller)"]
APILayer["api.ts (API Service Layer)"]
Types["types.ts (State & Entity Types)"]
end
subgraph External ["External Resources"]
LocalConfig["Local Config (./fjtui.json)"]
GlobalConfig["Global Config (~/.config/fjtui/fjtui.json)"]
GiteaAPI["Gitea/Forgejo Instance REST API"]
end
subgraph IO ["Terminal User Interface"]
Stdin["stdin (Raw Keypress Listeners)"]
Stdout["stdout (Chalk Styled Rendering)"]
end
%% Key Relationships
EP -->|Reads Config / Parses Args| ConfigManager
EP -->|Direct Connect Check| APILayer
EP -->|Initializes & Starts| TuiEngine
ConfigManager <-->|Read / Write| LocalConfig
ConfigManager <-->|Read / Write| GlobalConfig
TuiEngine -->|Subscribes to Events| Stdin
TuiEngine -->|Paints Screens| Stdout
TuiEngine -->|Fetches & Mutates Data| APILayer
TuiEngine -->|Saves Config| ConfigManager
APILayer -->|HTTP / REST Requests (Axios)| GiteaAPI
TuiEngine -.->|Operates on AppState| Types
2. Screen & Page Transition Flow
The terminal application operates as a single-page terminal application (SPTA) with a central screen router state. Below is the transition flow (state machine) representing how keypress actions route the user between screens.
stateDiagram-v2
[*] --> Launch : Run gitea-tui (Bootstrap or Direct Connect)
Launch --> Setup : No config / finished or skipped
Launch --> List : Config exists / direct connect (finished or skipped)
state Setup {
[*] --> EnteringCredentials
EnteringCredentials --> Validating : Enter/Submit Form
Validating --> EnteringCredentials : Connection Error
}
Setup --> RepoPicker : Validated & Repositories Loaded
state RepoPicker {
[*] --> BrowsingRepos
BrowsingRepos --> BrowsingRepos : Filter / Search
}
RepoPicker --> Setup : Escape (Go Back)
RepoPicker --> List : Select Repo (Enter) & Load Issues
state List {
[*] --> DisplayingIssues
DisplayingIssues --> DisplayingIssues : Toggle Filters / Sort / Paginate / Search
}
List --> Setup : Change Connection ('o' key)
List --> CreateIssue : Create Issue ('c' key)
List --> Details : View Issue Details (Enter)
state CreateIssue {
[*] --> FillingIssueForm
FillingIssueForm --> SavingIssue : Ctrl+S (Submit)
SavingIssue --> DisplayingIssues : Success (List reloads)
SavingIssue --> FillingIssueForm : Failure (Shows error)
}
CreateIssue --> List : Escape (Cancel)
state Details {
[*] --> ViewingDetails
ViewingDetails --> ViewingDetails : Scroll description & comments
}
Details --> List : Escape / Backspace / Q (Go Back)
Details --> Setup : Change Connection ('o' key)
Details --> AddComment : Add Comment ('c' key)
Details --> EditIssue : Edit Issue ('e' key)
Details --> AddTime : Add Tracked Time ('t' key)
Details --> SetAssignees : Set Assignees ('a' key)
Details --> ConfirmStateChange : Toggle Issue State ('x' key)
state AddComment {
[*] --> EnteringComment
EnteringComment --> SubmittingComment : Ctrl+S (Submit)
SubmittingComment --> ViewingDetails : Success (Comments reload)
SubmittingComment --> EnteringComment : Failure (Shows error)
}
AddComment --> Details : Escape (Cancel)
state EditIssue {
[*] --> EditingIssueForm
EditingIssueForm --> SubmittingEdit : Ctrl+S (Submit)
SubmittingEdit --> ViewingDetails : Success (Details reload)
SubmittingEdit --> EditingIssueForm : Failure (Shows error)
}
EditIssue --> Details : Escape (Cancel)
state AddTime {
[*] --> EnteringTime
EnteringTime --> SubmittingTime : Ctrl+S (Submit)
SubmittingTime --> ViewingDetails : Success (Time reloads)
SubmittingTime --> EnteringTime : Failure (Shows error)
}
AddTime --> Details : Escape (Cancel)
state SetAssignees {
[*] --> EnteringAssignees
EnteringAssignees --> SubmittingAssignees : Enter (Submit)
SubmittingAssignees --> ViewingDetails : Success (Assignees reload)
SubmittingAssignees --> EnteringAssignees : Failure (Shows error)
}
SetAssignees --> Details : Escape (Cancel)
state ConfirmStateChange {
[*] --> AwaitingConfirmation
AwaitingConfirmation --> Details : No / Escape (Cancel)
AwaitingConfirmation --> AnimatingChange : Yes ('y' key)
state AnimatingChange {
[*] --> AnimatingClose : If Closing (Gravestones)
[*] --> AnimatingReopen : If Reopening (Zombies)
AnimatingClose --> ExecutingStateChangeAPI : Finish Frames (1.5s)
AnimatingReopen --> ExecutingStateChangeAPI : Finish Frames (1.8s)
}
ExecutingStateChangeAPI --> ViewingDetails : Done & Reloaded
}
3. Data Interaction & Rerender Lifecycle
When the user interacts with the interface, keyboard events are processed by the active screen key handler, state variables are updated, API calls are made if necessary, and a full screen redraw is triggered on the terminal buffer.
The following sequence diagram details the lifecyle of adding a comment to an issue:
sequenceDiagram
autonumber
actor User as User Terminal
participant TUI as TuiEngine (tui.ts)
participant API as API Layer (api.ts)
participant Gitea as Gitea REST API
User->>TUI: Keypress (e.g. 'c' on Details screen)
TUI->>TUI: Update screen state to 'add-comment'
TUI->>TUI: Render text input area (addCommentForm)
TUI-->>User: Display empty comment field
User->>TUI: Type comment text + Ctrl+S
TUI->>TUI: Set state.loading = true & render spinner
TUI->>API: createIssueComment(config, issueNumber, commentBody)
API->>Gitea: POST /repos/{owner}/{repo}/issues/{index}/comments
Gitea-->>API: 201 Created (Comment entity JSON)
API-->>TUI: Comment Object
TUI->>TUI: Set screen state to 'details'
TUI->>TUI: Trigger loadComments(issue) (Async)
TUI->>API: fetchIssueComments(config, issueNumber)
API->>Gitea: GET /repos/{owner}/{repo}/issues/{index}/comments
Gitea-->>API: 200 OK (Comments list JSON)
API-->>TUI: Comment[] Array
TUI->>TUI: Set state.loading = false
TUI->>TUI: Render details screen + updated comments
TUI-->>User: Display issue details and new comment
4. Module Directories & Code Structure
The project code is organized into a clean multi-file architecture located under src:
-
- Sets up Command-line parsing with the
commanderpackage. - Automatically bootstraps the configuration and constructs the default
AppState. - Checks CLI credentials. If valid, validates them directly, bypassing the setup page and going straight to the issue dashboard.
- Instantiates the
TuiEngineand starts the app loop.
- Sets up Command-line parsing with the
-
- Contains the core
TuiEngineclass which manages key listeners onprocess.stdin(in raw mode). - Manages loading spinners and UI animations (Gravestones for closed issues and Zombies for reopened issues).
- Performs terminal buffer manipulation (e.g. entering alternate screen buffer
\x1B[?1049h, hiding cursor\x1B[?25l). - Delegates the rendering of specific screens using modular helper functions (e.g.,
renderSetupScreen,renderListScreen,renderDetailsScreen).
- Contains the core
-
- Hosts the REST API layer built on top of
axios. - Interacts with Forgejo/Gitea endpoints such as
/repos,/issues,/comments, and/times. - Handles network errors and formats them into user-friendly diagnostic messages.
- Hosts the REST API layer built on top of
-
- Manages persistence of credentials.
- Reads configuration locally from
fjtui.jsonand globally from~/.config/fjtui/fjtui.json. Local configurations take priority. - Writes updated credentials back to the user's home configuration safely.
-
- Declares the TypeScript contracts and interfaces representing the application state (
AppState), target forms (SetupForm,CreateIssueForm, etc.), and returned entities (User,Issue,Label,Comment,RepoItem).
- Declares the TypeScript contracts and interfaces representing the application state (
-
ASCII Art Assets:
- ascii-art.txt: ASCII art loaded at startup to perform the color-shifting launch animation.