Split hidden issue counts by level

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 20:21:00 +02:00
co-authored by Claude Sonnet 5.5
parent aec4ec6e3b
commit 17151a578d
8 changed files with 57 additions and 28 deletions
+19 -9
View File
@@ -60,21 +60,31 @@ func (s *Status) Issues() []Issue {
return out
}
// HiddenIssues counts the issues of all loaded descendants and returns the
// most severe level among them. A descendant whose load failed counts as one
// danger, since its state is unknown.
func (m *Submodule) HiddenIssues() (count int, worst Level) {
// Hidden counts issues below a submodule per level.
type Hidden struct{ Warn, Danger int }
// Total is the number of issues of all levels.
func (h Hidden) Total() int { return h.Warn + h.Danger }
// HiddenIssues counts the issues of all loaded descendants per level. A
// descendant whose load failed counts as one danger, since its state is
// unknown.
func (m *Submodule) HiddenIssues() Hidden {
var h Hidden
Walk(m.Children, func(c *Submodule) {
if c.Status == nil && c.Err != nil {
count++
worst = max(worst, LevelDanger)
h.Danger++
}
for _, is := range c.Status.Issues() {
count++
worst = max(worst, is.Level)
switch is.Level {
case LevelDanger:
h.Danger++
case LevelWarn:
h.Warn++
}
}
})
return count, worst
return h
}
// Walk calls fn for every submodule in depth-first order.
+4 -5
View File
@@ -46,9 +46,8 @@ func TestHiddenIssues(t *testing.T) {
loading := &Submodule{}
root := &Submodule{Status: ok, Children: []*Submodule{child, {Status: ok}, loading}}
count, worst := root.HiddenIssues()
if count != 2 || worst != LevelDanger {
t.Errorf("HiddenIssues() = %d, %v; want 2, LevelDanger", count, worst)
if got := root.HiddenIssues(); got != (Hidden{Warn: 1, Danger: 1}) {
t.Errorf("HiddenIssues() = %+v; want 1 warn, 1 danger", got)
}
}
@@ -75,7 +74,7 @@ func TestHiddenIssuesCountLoadErrors(t *testing.T) {
bad := &Submodule{Err: errors.New("boom")}
loading := &Submodule{}
parent := &Submodule{Children: []*Submodule{bad, loading}}
if n, w := parent.HiddenIssues(); n != 1 || w != LevelDanger {
t.Errorf("got %d, %v; want 1, danger", n, w)
if got := parent.HiddenIssues(); got != (Hidden{Danger: 1}) {
t.Errorf("got %+v; want 1 danger", got)
}
}
+10 -2
View File
@@ -138,8 +138,16 @@ func (m Model) lines(s *submodule.Submodule) []string {
out = append(out, "")
if n := len(s.Children); n > 0 {
children := fmt.Sprintf("%d direct", n)
if k, worst := s.HiddenIssues(); k > 0 {
children += t.LevelStyle(worst, lipgloss.NewStyle()).Render(fmt.Sprintf(" · %d issue(s) below", k))
h := s.HiddenIssues()
var parts []string
if h.Danger > 0 {
parts = append(parts, t.LevelStyle(submodule.LevelDanger, lipgloss.NewStyle()).Render(fmt.Sprintf("%d error(s)", h.Danger)))
}
if h.Warn > 0 {
parts = append(parts, t.LevelStyle(submodule.LevelWarn, lipgloss.NewStyle()).Render(fmt.Sprintf("%d warning(s)", h.Warn)))
}
if len(parts) > 0 {
children += muted.Render(" · ") + strings.Join(parts, muted.Render(", ")) + muted.Render(" below")
}
out = append(out, label("Children")+children)
}
+13 -5
View File
@@ -15,7 +15,7 @@ import (
)
const (
statusWidth = 14 // "unpushed +1 ·2"
statusWidth = 16 // "unpushed +1 ·2 ·3"
deltaWidth = 6
maxRefWidth = 22
)
@@ -414,11 +414,19 @@ func (m Model) nodeLine(r row, nameColW, refW int) string {
default:
status = seg(t.Muted, theme.InSync)
}
if k, worst := s.HiddenIssues(); collapsed && k > 0 {
if status != "" {
status += pad(1)
if h := s.HiddenIssues(); collapsed && h.Total() > 0 {
for _, c := range []struct {
level submodule.Level
n int
}{{submodule.LevelDanger, h.Danger}, {submodule.LevelWarn, h.Warn}} {
if c.n == 0 {
continue
}
if status != "" {
status += pad(1)
}
status += t.LevelStyle(c.level, base).Render(fmt.Sprintf("%s%d", t.Glyphs.Problems, c.n))
}
status += t.LevelStyle(worst, base).Render(fmt.Sprintf("%s%d", t.Glyphs.Problems, k))
}
status = ansi.Truncate(status, statusWidth, "")
status += pad(statusWidth - lipgloss.Width(status))