A tip, checkout or committed pin that has diverged from the pin gets its
own lane, which joins the main lane at a "base" node: the merge base of
the two, with its SHA and subject. Only one diverged commit is drawn as a
fork; with two, or without a merge base, the nodes stay in a line with
the "diverged" gap.
Add Status.Bases, filled by the git provider with `merge-base` for each
diverged commit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Replace the Pinned/Checkout/Tip rows and the list of commits between pin
and tip with one line of history, newest on top: a node per commit with
its roles (pin, tip, you, staged, HEAD) and the number of commits between
two nodes. A staged pin shows the committed pin as its own node and notes
when it moves back. All three on one commit read "in sync".
Add Status.Drift, Committed and CommittedDrift and Status.Spine(); drop
Status.Between and Repo.FetchedAt. The fetch age moves from the header to
the detail pane (right-aligned in the name's line). The header summary is
right-aligned, ordered by importance and cut with an ellipsis if too narrow.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A click on a branch switches to it, the wheel moves the selection, a click
outside closes the popup. The popup is hit-tested by its own position, as it
is drawn after zone.Scan.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
b opens a centered popup with a search line and the local and remote
branches of the selection. Enter stages the branch tip as the pin of a
submodule, restores it (and everything below) to it, and puts HEAD on the
branch; a remote branch gets a local tracking branch unless one exists
elsewhere. Works on the superproject too. Losses are asked like restore.
The backdrop behind the popup is dimmed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staged pins survive a restore at every depth and are the targets of the
submodules below. For a submodule with a staged pin, x asks whether to
restore to the staged or the committed pin, the latter unstaging it.
Staged pins of a repository that moves count as a loss.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restores the selection and all submodules below it recursively, asking
only if work would be lost. May clone/fetch missing objects (D23).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing is fetched on startup any more; the header and detail pane show
the last fetch and f fetches everything. Records the change as D22.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`!` reduces the tree to submodules with issues (or unknown state) plus
their ancestors, reusing the filter's reduction. Adds docs/actions.md as
a draft spec for restore and stage pin.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The header stands for the root repository: it is selected on startup,
shows the root's own state word, and the detail pane shows its state
like a submodule's (HEAD vs. upstream, changes, unpushed, leftover
submodule clones as untracked repositories). Submodules are ignored in
the root's changes; their rows show them.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A staged pin was only visible in the detail pane, so the header's
"n pin staged" could not be traced to a row. The status column now shows
a blue "staged" instead of "in sync", a blue marker after an issue word,
and the marker on collapsed rows with a staged pin below. It is still not an
issue and not counted in +n.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A text input above the footer filters submodules by path (case-insensitive).
Matches keep their ancestors. A filtered-out selection moves to the next
visible submodule; esc closes the input and removes the filter.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A pin on a pushed feature branch showed as diverged from origin/main
because the .gitmodules branch won before any distance was compared.
Now the branch containing the pin with the fewest commits after it wins;
the .gitmodules branch, the upstream and the remote default branch only
break ties and are always compared. An unpushed pin compares just those
preferred refs by commits on either side.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
c copies the absolute directory of the selected submodule via OSC 52.
Keys are grouped into navigation (footer left), global (footer right)
and submodule (detail pane palette, footer if the pane can't show it).
Adds a key reference table to the UX guide.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Releases are built locally (no runner) and uploaded to the Gitea generic
package registry. install.sh installs submarine plus a sub symlink;
`submarine update` replaces the binary after verifying its checksum.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An empty status cell could be read as missing data. The muted word
confirms the check ran without drawing attention. The word is defined
once in the theme and shared with the detail pane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- On startup, after the local state is shown, fetch the superproject and
every initialized submodule (all remotes, prune, at most 8 at once).
Fetches never prompt; failures show git's message. --no-fetch skips it,
f fetches again.
- The header shows "fetching n/m", then the fetch age and failures; the
detail pane shows each submodule's last fetch and fetch errors.
- The git provider polls each repository's metadata (refs, HEAD, index,
.gitmodules) every 2 s and reports changes. The app reloads only what
changed, keeping old values, expansion and cursor; loads are
single-flight with coalesced reruns. Refresh is driven by the
repositories, so a fetch in another shell updates the view like ours.
- r reloads everything (worktree edits are not watched).
- Docs: F50-F53, N1/N8 rewritten, D18-D20 (D12 superseded).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For a dirty submodule, list the changed files so the user can remember
what they were doing there:
Changes 5 files · 2M 1A 1D 1R · +37 −12
──────────────────────────────────
M render.go src/render/ +20 −4 3d ago
? scratch.txt 5m ago
- Status.Changes: files changed against HEAD (staged and unstaged
combined) with kind, line counts and modification time; sorted by kind,
then by size; capped at 200 with totals over all files.
- Git provider: parse `status --porcelain=v2 -z` and one
`diff HEAD --numstat -z -M`, only for dirty submodules.
- Detail pane: flat list with a colored status letter per file, like IDE
source-control views; counts and age in compact columns; up to 10 files,
every kind keeps a line, the rest is summarized as "n more".
- Fixture gains libs/work with every kind of change; mock data extended.
- Docs: F24, change preview in the UX guide, D17.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implement submodule.Provider with the git CLI so submarine reads real
repositories: `submarine [dir]`, with `--mock` for the built-in data.
- Tree reads .gitmodules recursively; Status derives presence, checkout,
worktree, staged pin, pushed state and pin -> ref resolution from local
git commands only (no network).
- Concurrent Status calls are bounded by runtime.NumCPU().
- scripts/make-fixture.sh builds a superproject covering every state; the
integration tests run against it.
- theme.Ago shows "never" for a zero time.
- Docs updated; decisions D14-D16 added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>