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) {
9797 "Waiting on your review", "Assigned to you", "Recent activity",
9898 "from bob", "alice/app!1", "todo one", "alice/app#1",
9999 `href="/alice/app/mrs/1"`, `href="/alice/app/issues/1"`,
100 "Yours anywhere, and every one in a repository you can write to.",
100101 } {
101102 if !strings.Contains(body, want) {
102103 t.Errorf("dashboard missing %q", want)
103104 }
104105 }
105106
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
106120 // The diff has its own view rather than a fold at the foot of the
107121 // conversation: the default view offers it, and asking for it renders
108122 // the stat line and the patch.
@@ -338,8 +352,7 @@ func TestDashboardQueues(t *testing.T) {
338352 t.Fatalf("review: %s", errOut)
339353 }
340354 _, 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)
344357 }
345358}
internal/web/static/style.css +1
@@ -1393,6 +1393,7 @@ pre.quickstart { margin: 0 0 var(--sp-4); }
13931393.profilehead .meta { margin: var(--sp-1) 0 0; }
13941394.activity { margin-bottom: var(--sp-5); }
13951395h2 .count { background: none; color: var(--muted); font-weight: 400; font-size: var(--fs-2); margin-left: var(--sp-2); padding: 0; }
1396h2.empty { color: var(--muted); font-weight: 500; font-size: var(--fs-3); margin: var(--sp-2) 0; }
13961397/* 53 week columns x 7 day rows, tinted with the link blue */
13971398.actgraph-scroll { overflow-x: auto; padding-bottom: var(--sp-1); }
13981399.actgraph { display: flex; gap: 3px; width: max-content; padding-top: 18px; }
internal/web/templates/dashboard.html +13 −10
@@ -10,23 +10,26 @@
1010{{else}}<li class="empty">{{$.Empty}}</li>{{end}}
1111</ul>
1212{{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}}
1318{{define "content"}}
1419<h1>Dashboard</h1>
1520
1621<div class="withaside">
1722<div class="mainside">
1823
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}}
2728
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}}
3033
3134</div>
3235