Preview uncommitted changes in the detail pane

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>
This commit is contained in:
Simon Franken
2026-10-21 15:55:46 +02:00
co-authored by Claude Opus 5.5
parent 64fe34b5b0
commit 19c6f17eb2
17 changed files with 882 additions and 32 deletions
+36
View File
@@ -25,6 +25,42 @@ func IssueWord(k submodule.IssueKind) string {
}
}
// ChangeLetter is the one-letter label of a changed file.
func ChangeLetter(k submodule.ChangeKind) string {
switch k {
case submodule.ChangeConflicted:
return "!"
case submodule.ChangeModified:
return "M"
case submodule.ChangeAdded:
return "A"
case submodule.ChangeRemoved:
return "D"
case submodule.ChangeRenamed:
return "R"
default:
return "?"
}
}
// ChangeStyle returns base with the color of a change kind.
func (t Theme) ChangeStyle(k submodule.ChangeKind, base lipgloss.Style) lipgloss.Style {
switch k {
case submodule.ChangeConflicted:
return base.Foreground(t.Danger).Bold(true)
case submodule.ChangeModified:
return base.Foreground(t.Warn)
case submodule.ChangeAdded:
return base.Foreground(t.Added)
case submodule.ChangeRemoved:
return base.Foreground(t.Danger)
case submodule.ChangeRenamed:
return base.Foreground(t.Primary)
default:
return base.Foreground(t.Muted)
}
}
// LevelStyle returns base with the text color (and weight) for a level.
func (t Theme) LevelStyle(l submodule.Level, base lipgloss.Style) lipgloss.Style {
switch l {