Commit b019fa0b10
b019fa0b1055377bc0a361ddba7069053f851055
parent: 348f348253
Verified · cmc ci/build: success ci/test: success
cmc <hello@cleberg.net> · 2026-09-18 04:34 UTC
web: dashboard sections condense empty and label the open lists
Populated sections (Waiting on your review, Assigned to you, Open merge
requests, Open issues) render first; sections with nothing in them
collapse to a single muted heading with a zero count instead of an
empty list box. The two "Open" headings gain a hint explaining what
they cover: yours anywhere, plus every one in a repository you can
write to.
Ref #222
e2e/dashboard_test.go
+16 −3
| @@ -97,12 +97,26 @@ func TestDashboard(t *testing.T) { |
| 97 | 97 | "Waiting on your review", "Assigned to you", "Recent activity", |
| 98 | 98 | "from bob", "alice/app!1", "todo one", "alice/app#1", |
| 99 | 99 | `href="/alice/app/mrs/1"`, `href="/alice/app/issues/1"`, |
| 100 | "Yours anywhere, and every one in a repository you can write to.", |
| 100 | 101 | } { |
| 101 | 102 | if !strings.Contains(body, want) { |
| 102 | 103 | t.Errorf("dashboard missing %q", want) |
| 103 | 104 | } |
| 104 | 105 | } |
| 105 | 106 | |
| 107 | // D01: alice has nothing assigned to her, so that section condenses to |
| 108 | // an empty heading, and populated sections (she has a review waiting) |
| 109 | // sort before it. |
| 110 | emptyHeading := `<h2 class="empty">Assigned to you <span class="count">0</span></h2>` |
| 111 | if !strings.Contains(body, emptyHeading) { |
| 112 | t.Fatalf("dashboard missing condensed empty heading %q:\n%s", emptyHeading, body) |
| 113 | } |
| 114 | reviewIdx := strings.Index(body, "Waiting on your review") |
| 115 | assignedIdx := strings.Index(body, emptyHeading) |
| 116 | if reviewIdx < 0 || assignedIdx < 0 || reviewIdx > assignedIdx { |
| 117 | t.Fatalf("populated heading should come before the empty one: review=%d assigned=%d", reviewIdx, assignedIdx) |
| 118 | } |
| 119 | |
| 106 | 120 | // The diff has its own view rather than a fold at the foot of the |
| 107 | 121 | // conversation: the default view offers it, and asking for it renders |
| 108 | 122 | // the stat line and the patch. |
| @@ -338,8 +352,7 @@ func TestDashboardQueues(t *testing.T) { |
| 338 | 352 | t.Fatalf("review: %s", errOut) |
| 339 | 353 | } |
| 340 | 354 | _, after := browserGet(t, inst.login(t, aliceKey), inst.base()+"/") |
| 341 | | queue := after[strings.Index(after, "Waiting on your review"):strings.Index(after, "Assigned to you")] |
| 342 | | if strings.Contains(queue, "needs a look") { |
| 343 | | t.Fatalf("reviewed MR still waiting:\n%s", queue) |
| 355 | if !strings.Contains(after, `<h2 class="empty">Waiting on your review <span class="count">0</span></h2>`) { |
| 356 | t.Fatalf("reviewed MR still waiting:\n%s", after) |
| 344 | 357 | } |
| 345 | 358 | } |
internal/web/static/style.css
+1
| @@ -1393,6 +1393,7 @@ pre.quickstart { margin: 0 0 var(--sp-4); } |
| 1393 | 1393 | .profilehead .meta { margin: var(--sp-1) 0 0; } |
| 1394 | 1394 | .activity { margin-bottom: var(--sp-5); } |
| 1395 | 1395 | h2 .count { background: none; color: var(--muted); font-weight: 400; font-size: var(--fs-2); margin-left: var(--sp-2); padding: 0; } |
| 1396 | h2.empty { color: var(--muted); font-weight: 500; font-size: var(--fs-3); margin: var(--sp-2) 0; } |
| 1396 | 1397 | /* 53 week columns x 7 day rows, tinted with the link blue */ |
| 1397 | 1398 | .actgraph-scroll { overflow-x: auto; padding-bottom: var(--sp-1); } |
| 1398 | 1399 | .actgraph { display: flex; gap: 3px; width: max-content; padding-top: 18px; } |
internal/web/templates/dashboard.html
+13 −10
| @@ -10,23 +10,26 @@ |
| 10 | 10 | {{else}}<li class="empty">{{$.Empty}}</li>{{end}} |
| 11 | 11 | </ul> |
| 12 | 12 | {{end}} |
| 13 | {{define "queue"}} |
| 14 | <h2>{{$.Title}} <span class="count">{{len $.Items}}</span></h2> |
| 15 | {{if $.Hint}}<p class="hint">{{$.Hint}}</p>{{end}} |
| 16 | {{template "itemlist" dict "Items" $.Items "Kind" $.Kind "Empty" $.Empty}} |
| 17 | {{end}} |
| 13 | 18 | {{define "content"}} |
| 14 | 19 | <h1>Dashboard</h1> |
| 15 | 20 | |
| 16 | 21 | <div class="withaside"> |
| 17 | 22 | <div class="mainside"> |
| 18 | 23 | |
| 19 | | <h2>Waiting on your review <span class="count">{{len .Reviews}}</span></h2> |
| 20 | | {{template "itemlist" dict "Items" .Reviews "Kind" "mrs" "Empty" "Nothing waiting on you"}} |
| 21 | | |
| 22 | | <h2>Assigned to you <span class="count">{{len .Assigned}}</span></h2> |
| 23 | | {{template "itemlist" dict "Items" .Assigned "Kind" "issues" "Empty" "Nothing assigned to you"}} |
| 24 | | |
| 25 | | <h2>Open merge requests <span class="count">{{len .MRs}}</span></h2> |
| 26 | | {{template "itemlist" dict "Items" .MRs "Kind" "mrs" "Empty" "No open merge requests"}} |
| 24 | {{if .Reviews}}{{template "queue" dict "Title" "Waiting on your review" "Items" .Reviews "Kind" "mrs" "Empty" "Nothing waiting on you"}}{{end}} |
| 25 | {{if .Assigned}}{{template "queue" dict "Title" "Assigned to you" "Items" .Assigned "Kind" "issues" "Empty" "Nothing assigned to you"}}{{end}} |
| 26 | {{if .MRs}}{{template "queue" dict "Title" "Open merge requests" "Items" .MRs "Kind" "mrs" "Empty" "No open merge requests" "Hint" "Yours anywhere, and every one in a repository you can write to."}}{{end}} |
| 27 | {{if .Issues}}{{template "queue" dict "Title" "Open issues" "Items" .Issues "Kind" "issues" "Empty" "No open issues" "Hint" "Yours anywhere, and every one in a repository you can write to."}}{{end}} |
| 27 | 28 | |
| 28 | | <h2>Open issues <span class="count">{{len .Issues}}</span></h2> |
| 29 | | {{template "itemlist" dict "Items" .Issues "Kind" "issues" "Empty" "No open issues"}} |
| 29 | {{if not .Reviews}}<h2 class="empty">Waiting on your review <span class="count">0</span></h2>{{end}} |
| 30 | {{if not .Assigned}}<h2 class="empty">Assigned to you <span class="count">0</span></h2>{{end}} |
| 31 | {{if not .MRs}}<h2 class="empty">Open merge requests <span class="count">0</span></h2>{{end}} |
| 32 | {{if not .Issues}}<h2 class="empty">Open issues <span class="count">0</span></h2>{{end}} |
| 30 | 33 | |
| 31 | 34 | </div> |
| 32 | 35 | |