Archived
Add submarine TUI prototype with mock data
Bubble Tea prototype for browsing repositories with many nested git submodules: collapsible tree, pins shown as ref + distance, summarized issue words with severity levels, detail pane, async loading via a Provider interface backed by mock data. Includes docs/ with requirements, state model, UX guide, architecture and decision log, plus AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# State model
|
||||
|
||||
Code: `internal/submodule/model.go`, `internal/submodule/status.go`.
|
||||
|
||||
## Three commits per submodule
|
||||
|
||||
Every submodule involves three commits. All state is derived from comparing
|
||||
them.
|
||||
|
||||
| Commit | Source |
|
||||
|---|---|
|
||||
| **Pinned** | The gitlink recorded in the superproject (HEAD or index) |
|
||||
| **Checked out** | The submodule's `HEAD` |
|
||||
| **Tip** | The tip of the ref the pin was resolved to, e.g. `origin/main` |
|
||||
|
||||
This gives two independent questions:
|
||||
|
||||
- **Is the checkout on the pin?** → `off pin` if not.
|
||||
- **Where is the pin relative to its ref?** → `↑n` / `↓n` / `↑n↓m`.
|
||||
|
||||
## Raw status dimensions
|
||||
|
||||
`submodule.Status` stores the raw, independent dimensions. A submodule can be
|
||||
in several at once.
|
||||
|
||||
| Field | Values |
|
||||
|---|---|
|
||||
| `Presence` | `PresenceOK`, `NotInitialized`, `DirMissing` |
|
||||
| `Checkout` | `CheckoutMatch`, `CheckoutDiffers`, `CheckoutDetached` (on the pin, detached HEAD; informational) |
|
||||
| `Worktree` | `WorktreeClean`, `WorktreeModified`, `WorktreeUntracked`, `WorktreeConflict` |
|
||||
| `PinStaged` | Pin changed in the superproject index but not committed |
|
||||
| `Pin` | `Ref`, `Source`, `Ahead`, `Behind`, `Pushed` |
|
||||
| `Pinned`, `CheckedOut`, `Tip` | Commits (SHA, subject, author, time) |
|
||||
| `Between` | Commits that make up the pin ↔ tip difference |
|
||||
|
||||
`Status == nil` means "still loading". `Submodule.Err` is set if loading
|
||||
failed.
|
||||
|
||||
## Issues: summarizing the dimensions
|
||||
|
||||
The UI never shows raw dimensions in the tree. `Status.Issues()` summarizes
|
||||
them into **issue kinds**, each with a **level**. This is the single source of
|
||||
truth for what counts as a problem.
|
||||
|
||||
| Issue (word) | Combines | Level |
|
||||
|---|---|---|
|
||||
| `unpushed` | Pin not reachable from any remote ref | Danger |
|
||||
| `conflict` | Merge conflict in the worktree | Danger |
|
||||
| `missing` | Not initialized; directory missing | Warn |
|
||||
| `off pin` | Checkout differs from pin | Warn |
|
||||
| `changed` | Uncommitted changes; untracked files | Warn |
|
||||
|
||||
Rules:
|
||||
|
||||
- If the submodule is **missing**, that is its only issue (nothing else can
|
||||
be determined).
|
||||
- Issues are returned **most severe first**: unpushed, conflict, off pin,
|
||||
changed.
|
||||
- **Not issues** (quiet): in sync, pin behind/ahead/diverged but pushed,
|
||||
staged pin, detached HEAD on the pin. They are shown as information only.
|
||||
- A loading error is shown as `error` (Danger) in the UI; it is not an issue
|
||||
kind.
|
||||
|
||||
### Levels
|
||||
|
||||
| Level | Meaning | Presentation |
|
||||
|---|---|---|
|
||||
| `LevelQuiet` | Nothing to do | Whole row muted, no word |
|
||||
| `LevelWarn` | Look at it | Yellow word |
|
||||
| `LevelDanger` | Act now | Red bold word |
|
||||
|
||||
### Hidden issues
|
||||
|
||||
`Submodule.HiddenIssues()` counts the issues of **all** loaded descendants
|
||||
and returns the most severe level. A collapsed submodule shows this as `·k`
|
||||
in that level's color, so a danger can never hide inside a collapsed subtree.
|
||||
|
||||
## Resolving a pin to a ref
|
||||
|
||||
The pin is displayed as a ref, not a hash. A commit can be on several
|
||||
branches, on none, or deep in the history, so the git provider resolves it by
|
||||
this order and records which rule applied in `PinInfo.Source`:
|
||||
|
||||
| # | Rule | `RefSource` |
|
||||
|---|---|---|
|
||||
| 1 | `branch = …` in `.gitmodules` | `RefFromGitmodules` |
|
||||
| 2 | Branch currently checked out in the submodule | `RefFromCurrentBranch` |
|
||||
| 3 | Remote default branch, if it contains the pin | `RefFromDefaultBranch` |
|
||||
| 4 | Any remote branch containing the pin, preferring the closest tip (`git branch -r --contains`) | `RefFromContains` |
|
||||
| 5 | A tag (`git describe`) | `RefFromTag` |
|
||||
| 6 | Short SHA | `RefFromSHA` |
|
||||
|
||||
Distances: `git rev-list --left-right --count <pin>...<ref>` gives
|
||||
`Ahead` (left) and `Behind` (right).
|
||||
|
||||
`Pushed` is true if the pin is reachable from any remote ref. A pin that is
|
||||
ahead of its ref can still be pushed (e.g. on another branch); only
|
||||
`Pushed == false` is dangerous.
|
||||
|
||||
## Distance notation
|
||||
|
||||
| Display | Meaning |
|
||||
|---|---|
|
||||
| `↓n` | The ref has n commits the pin doesn't have; the pin is behind |
|
||||
| `↑n` | The pin has n commits the ref doesn't have |
|
||||
| `↑n↓m` | Diverged |
|
||||
|
||||
The arrows follow the convention of `git status`, lazygit and shell prompts.
|
||||
Reference in New Issue
Block a user