Fold the unpublished projects into the overview table

They were a list underneath the table, which meant reading the same
project names in two different shapes. They are rows now, with the action
in an "init" column that only appears when some row needs it, and they
sort to the bottom as their own group: an un-inited directory is a
different kind of task and should not push the daily ones down.

Every directory under the base gets a row, not just the repositories --
`init` is exactly what turns a plain directory into a project, so leaving
those out would have hidden the ones the column is for. Such a row has no
git state to show and costs no subprocesses either, since projectStatus
now checks for .git before running any.

The count line gained "N to init"; the projects count still counts
repositories, so the two numbers stay meaningful side by side. An
unreachable server marks nothing at all, as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 17:49:59 +02:00
co-authored by Claude Opus 5
parent 8ca05d6ad2
commit 61a7059f61
5 changed files with 160 additions and 98 deletions
+18 -16
View File
@@ -830,6 +830,7 @@ func TestFormatProjStatus(t *testing.T) {
[]string{"g", "laptop", "→ hub gitea"}, nil},
}
for _, c := range cases {
c.s.isRepo = true // these all describe real repositories
got := formatProjStatus(c.s, w)
for _, sub := range c.contains {
if !strings.Contains(got, sub) {
@@ -878,23 +879,24 @@ func TestOverviewColumnsAlign(t *testing.T) {
}
func TestAttentionRank(t *testing.T) {
needs := []projStatus{
{dirty: true},
{ahead: 1},
{behind: 1},
ranks := []struct {
s projStatus
want int
}{
{projStatus{isRepo: true, dirty: true}, 0},
{projStatus{isRepo: true, ahead: 1}, 0},
{projStatus{isRepo: true, behind: 1}, 0},
{projStatus{isRepo: true, hasUpstream: true}, 1},
{projStatus{isRepo: true}, 1}, // clean, no upstream
// not on the server is a different kind of task and goes last, even
// when the working tree is dirty — it cannot be pushed anyway
{projStatus{isRepo: true, notOnServer: true}, 2},
{projStatus{isRepo: true, dirty: true, notOnServer: true}, 2},
{projStatus{notOnServer: true}, 2},
}
quiet := []projStatus{
{hasUpstream: true},
{}, // clean, no upstream: nothing to do about it here
}
for _, s := range needs {
if attentionRank(s) != 0 {
t.Errorf("%+v should sort to the top", s)
}
}
for _, s := range quiet {
if attentionRank(s) != 1 {
t.Errorf("%+v should sort below the ones needing action", s)
for _, c := range ranks {
if got := attentionRank(c.s); got != c.want {
t.Errorf("attentionRank(%+v) = %d, want %d", c.s, got, c.want)
}
}
}