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,71 @@
|
||||
// Package header renders the superproject summary line.
|
||||
package header
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"submarine/internal/submodule"
|
||||
"submarine/internal/tui/theme"
|
||||
)
|
||||
|
||||
// View renders repo info, state counts and fetch age on one line.
|
||||
func View(t theme.Theme, repo *submodule.Repo, width int) string {
|
||||
bar := lipgloss.NewStyle().Background(t.Bar)
|
||||
seg := func(c lipgloss.TerminalColor, s string) string { return bar.Foreground(c).Render(s) }
|
||||
|
||||
if repo == nil {
|
||||
return bar.Width(width).Render(seg(t.Muted, " loading…"))
|
||||
}
|
||||
|
||||
left := seg(t.Text, " "+repo.Path) + seg(t.Muted, " ") + seg(t.Primary, repo.Branch)
|
||||
if d := t.Delta(submodule.PinInfo{Ahead: repo.Ahead, Behind: repo.Behind, Pushed: true}, bar); d != "" {
|
||||
left += seg(t.Text, " ") + d
|
||||
}
|
||||
|
||||
// count submodules per issue kind (most severe first), plus staged pins
|
||||
var total, loading, staged int
|
||||
perKind := map[submodule.IssueKind]int{}
|
||||
level := map[submodule.IssueKind]submodule.Level{}
|
||||
submodule.Walk(repo.Submodules, func(s *submodule.Submodule) {
|
||||
total++
|
||||
if s.Status == nil {
|
||||
loading++
|
||||
return
|
||||
}
|
||||
if s.Status.PinStaged {
|
||||
staged++
|
||||
}
|
||||
for _, is := range s.Status.Issues() {
|
||||
perKind[is.Kind]++
|
||||
level[is.Kind] = is.Level
|
||||
}
|
||||
})
|
||||
|
||||
sep := seg(t.Muted, " · ")
|
||||
mid := seg(t.Muted, fmt.Sprintf("%d submodules", total))
|
||||
for _, k := range []submodule.IssueKind{
|
||||
submodule.IssueUnpushed, submodule.IssueConflict, submodule.IssueMissing,
|
||||
submodule.IssueOffPin, submodule.IssueChanged,
|
||||
} {
|
||||
if n := perKind[k]; n > 0 {
|
||||
mid += sep + t.LevelStyle(level[k], bar).Render(fmt.Sprintf("%d %s", n, theme.IssueWord(k)))
|
||||
}
|
||||
}
|
||||
if staged > 0 {
|
||||
mid += sep + seg(t.Info, fmt.Sprintf("%d pin staged", staged))
|
||||
}
|
||||
if loading > 0 {
|
||||
mid += sep + seg(t.Muted, fmt.Sprintf("%d loading", loading))
|
||||
}
|
||||
|
||||
right := seg(t.Muted, "fetched "+theme.Ago(repo.FetchedAt)+" ")
|
||||
|
||||
gap := width - lipgloss.Width(left) - lipgloss.Width(mid) - lipgloss.Width(right)
|
||||
if gap < 4 {
|
||||
return bar.Width(width).MaxWidth(width).Render(left + seg(t.Text, " ") + mid)
|
||||
}
|
||||
sp := func(n int) string { return bar.Render(fmt.Sprintf("%*s", n, "")) }
|
||||
return left + sp(gap/2) + mid + sp(gap-gap/2) + right
|
||||
}
|
||||
Reference in New Issue
Block a user