The reviews table reworked into a list: title first, details on a quieter line, groups under headings.
| 1/1 | List reviews as two-line items, not a seven-column table | +102 | -45 | ✓ |
No comments yet: forge comment 6 adds one.
1/1 List reviews as two-line items, not a seven-column table
The reviews table put number, title, branch, author, time, CI and approvals side by side in every row, with each status group's label inside the table, so it read as a dense grid rather than a list. Each review is now one item: a status mark and the title, with CI and approvals at the right, then a quieter line with the number, branch and target, author, commit count, diff size, when and the latest revision. Status groups get a heading with a count, like the index's shelves; merged and closed groups past ten start folded. A stacked layer hangs under the review below it, joined by a rule. On a phone, CI and approvals move under the title. j and k walk the items, and the home page's open reviews use the same list.
| M | site/ | 62 | +++++++++++++++++++----------- |
| M | site/ | 5 | +- |
| M | site/ | 34 | ++++++---------- |
| A | test/ | 46 | ++++++++++++++++++++++ |
@@ -66,39 +66,57 @@
6666 {% for a in r.approvals %}<span class="appr{% if not a.current %} stale{% endif %}" title="{{ a.user }} approved{% if a.rev %} r{{ a.rev }}{% endif %}{% if not a.current %}, changed since{% endif %}">✔ {{ a.user }}{% if not a.current %}{% if a.rev %} r{{ a.rev }}{% endif %}, stale{% endif %}</span>{% endfor %}6767 {% endmacro %}6868 69-{% macro reviewHead() %}70-<thead><tr><th class="num">#</th><th>Review</th><th>Branch</th><th>Author</th><th>Updated</th><th>CI</th><th>Approved</th></tr></thead>71-{% endmacro %}72-73-{# depth: how far up a stack, shown nested under the layer below. A revised74- review links what changed in its latest push. #}75-{% macro reviewRow(r, repo, depth = 0) %}69+{# One review in a list: its title on the first line with CI and approvals70+ at the right, the rest on a quieter second line. depth: how far up a71+ stack, shown nested under the layer below with a rule joining them. A72+ revised review links what changed in its latest push. #}73+{% macro reviewItem(r, repo, depth = 0) %}7674 {% css %}77-/* Review lists: a layer hangs under the one below it in the stack. */78-tr.layer .depth { display: inline-block; width: 1.4em; margin-left: calc((var(--depth) - 1) * 1.4em); }79-table.list tr:has(+ tr.layer) td { border-bottom-color: transparent; }80-@media (max-width: 720px) {81- table.list.stack tr:has(+ tr.layer) { border-bottom-color: transparent; padding-bottom: 2px; }82- table.list.stack tr.layer { padding-left: calc(8px + var(--depth) * 1.4em); }83- table.list.stack tr.layer .depth { margin-left: -1.4em; }84-}75+.rv-list { list-style: none; margin: 0; padding: 0; }76+.rv { position: relative; display: grid; grid-template-columns: auto 1fr auto; column-gap: 10px; row-gap: 3px; align-items: baseline; padding: 11px 14px; border-top: 1px solid var(--border); }77+.rv:first-child { border-top: 0; }78+.rv:hover, .rv.sel { background: var(--hover); }79+.rv.sel { box-shadow: inset 2px 0 0 var(--accent); }80+.rv-mark { grid-row: 1 / 3; align-self: start; width: 9px; height: 9px; margin-top: 6px; border-radius: 50%; background: var(--muted); }81+.rv-mark.open { background: var(--pass); }82+.rv-mark.draft { background: none; box-shadow: inset 0 0 0 2px var(--muted); }83+.rv-mark.merged { background: var(--merged); }84+.rv-mark.closed { background: var(--fail); }85+.rv-title { min-width: 0; font-weight: 600; overflow-wrap: anywhere; }86+.rv-title a::after { content: ''; position: absolute; inset: 0; }87+.rv-side { display: flex; gap: 12px; align-items: baseline; justify-content: flex-end; white-space: nowrap; font-size: 0.9em; }88+.rv-meta { grid-column: 2 / 4; display: flex; flex-wrap: wrap; column-gap: 14px; row-gap: 2px; font-size: 0.85em; color: var(--muted); }89+.rv-meta .mono { font-size: 0.95em; }90+.rv-meta .num { font-variant-numeric: tabular-nums; }91+.rv-meta .add { color: var(--pass); }92+.rv-meta .del { color: var(--fail); }93+.rv-meta a, .rv-side a { position: relative; z-index: 1; }94+.rv.layer { margin-left: calc(var(--depth) * 22px); border-left: 2px solid var(--border); }8595 a.rev { font-size: 0.85em; color: var(--muted); border: 1px solid var(--border); border-radius: 4px; padding: 0 4px; }8696 .appr { white-space: nowrap; font-weight: 600; color: var(--pass); }8797 .appr + .appr { margin-left: 8px; }8898 .appr.stale { color: var(--warn); font-weight: normal; }99+@media (max-width: 720px) {100+ .rv { grid-template-columns: auto 1fr; }101+ .rv-side { grid-column: 2; justify-content: flex-start; }102+ .rv-meta { grid-column: 2; }103+ .rv.layer { margin-left: calc(var(--depth) * 12px); }104+}89105 {% endcss %}90106 {% set last = r.revisions[r.revisions.length - 1] %}91-<tr{% if depth %} class="layer" style="--depth: {{ depth }}"{% endif %}><td class="num id">#{{ r.id }}</td><td class="main">{% if depth %}<span class="depth">↳</span>{% endif %}<a href="{{ repo.urls.review(r.id).href }}">{{ r.title }}</a></td><td class="mono hide-sm">{{ r.branch }} → {{ r.target }}</td><td class="nowrap">{{ r.author or '' }}</td><td class="date">{{ r.updated | ago }}{% if last and last.view %} <a class="rev" href="{{ repo.urls.revision(r.id, last.n).href }}" title="what changed since r{{ last.prev }}">r{{ last.n }}</a>{% endif %}</td><td>{{ reviewci(r.ci) }}</td><td class="nowrap">{{ approvals(r) }}</td></tr>107+<li class="rv{% if depth %} layer{% endif %}"{% if depth %} style="--depth: {{ depth }}"{% endif %}>108+<span class="rv-mark {{ r.status }}" title="{{ r.status }}"></span>109+<span class="rv-title"><a href="{{ repo.urls.review(r.id).href }}">{{ r.title }}</a></span>110+<span class="rv-side">{{ reviewci(r.ci) }}{% if r.approvals.length %}<span>{{ approvals(r) }}</span>{% endif %}</span>111+<span class="rv-meta"><span class="num">#{{ r.id }}</span><span class="mono">{{ r.branch }} → {{ r.target }}</span>{% if r.author %}<span>{{ r.author }}</span>{% endif %}{% if r.commits.length %}<span>{{ r.commits.length | plural('commit') }}</span>{% endif %}{% if r.combinedStats and (r.combinedStats.add or r.combinedStats.del) %}<span class="num"><span class="add">+{{ r.combinedStats.add }}</span> <span class="del">−{{ r.combinedStats.del }}</span></span>{% endif %}<span class="date">{{ r.updated | ago }}</span>{% if last and last.view %}<a class="rev" href="{{ repo.urls.revision(r.id, last.n).href }}" title="what changed since r{{ last.prev }}">r{{ last.n }}</a>{% endif %}</span>112+</li>92113 {% endmacro %}93114 94115 {# list: reviews nested by stack, as {review, depth} (nest() in lib/reviews.ts). #}95116 {% macro reviews(list, repo) %}96-<table class="list stack nav reviews">97-{{ reviewHead() }}98-<tbody>99-{% for e in list %}{{ reviewRow(e.review, repo, e.depth) }}{% endfor %}100-</tbody>101-</table>117+<ul class="rv-list nav-list">118+{% for e in list %}{{ reviewItem(e.review, repo, e.depth) }}{% endfor %}119+</ul>102120 {% endmacro %}103121 104122 {# A directory listing: folders first, then files. #}@@ -24,9 +24,10 @@
2424 [['g'], 'all repositories'],2525 ];2626 27- // Table rows, and the repo cards on the index (filtered ones are hidden).27+ // Table rows, the repo cards on the index (filtered ones are hidden), and28+ // list items such as reviews.2829 const rows = (): HTMLElement[] =>29- [...document.querySelectorAll<HTMLElement>('table.nav tbody tr, .nav-cards > li')].filter((el) => el.querySelector('a') && el.getClientRects().length);30+ [...document.querySelectorAll<HTMLElement>('table.nav tbody tr, .nav-cards > li, .nav-list > li')].filter((el) => el.querySelector('a') && el.getClientRects().length);3031 let at = -1;3132 3233 function select(i: number): void {@@ -6,18 +6,15 @@ pagination:
66 {% import "macros.njk" as m with context %}77 {% block content %}88 {% css %}9-/* Reviews by status, each status a group of rows. */10-table.list tr.group td { border: 0; padding: 18px 8px 6px; }11-table.list tr.group:hover td { background: none; }12-tr.group h3 { margin: 0; font-size: 0.95rem; }13-tr.group.open h3 { color: var(--pass); }14-tr.group.draft h3 { color: var(--muted); }15-tr.group.merged h3 { color: var(--merged); }16-tr.group.closed h3 { color: var(--fail); }17-@media (max-width: 720px) {18- table.list.stack tr.group { padding: 18px 8px 4px; border: 0; }19- table.list.stack tr.group td { font-size: 1rem; padding: 0; }20-}9+/* Reviews by status: a heading per group, the list under it. Long merged10+ and closed groups start folded. */11+.rv-group { margin: 0 0 18px; }12+.rv-group > summary { display: flex; align-items: baseline; gap: 8px; margin: 0 0 8px; cursor: pointer; list-style: none; }13+.rv-group > summary::-webkit-details-marker { display: none; }14+.rv-group > summary h2 { margin: 0; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }15+.rv-group > summary .n { color: var(--faint); font-size: 0.78rem; }16+.rv-group > summary::after { content: ''; flex: 1; border-top: 1px solid var(--border); transform: translateY(-3px); }17+.rv-group:not([open]) > summary h2::before { content: '\25B8 '; }2118 {% endcss %}2219 {% if not p.groups.length %}2320 <div class="box"><div class="markdown">@@ -28,16 +25,11 @@ forge attest --stack # CI on your machine, signed per commit
2825 forge push -t "Title" # the branch and its CI results</pre>2926 </div></div>3027 {% else %}31-<div class="box">32-<table class="list stack nav reviews">33-{{ m.reviewHead() }}3428 {% for g in p.groups %}35-<tbody>36-<tr class="group {{ g.status }}"><td colspan="7"><h3>{{ g.status }} <span class="desc">{{ g.items.length }}</span></h3></td></tr>37-{% for e in g.items %}{{ m.reviewRow(e.review, p.repo, e.depth) }}{% endfor %}38-</tbody>29+<details class="rv-group {{ g.status }}"{% if g.status == 'open' or g.status == 'draft' or g.items.length <= 10 %} open{% endif %}>30+<summary><h2>{{ g.status }}</h2><span class="n">{{ g.items.length }}</span></summary>31+<div class="box">{{ m.reviews(g.items, p.repo) }}</div>32+</details>3933 {% endfor %}40-</table>41-</div>4234 {% endif %}4335 {% endblock %}@@ -0,0 +1,46 @@
1+# The reviews page lists reviews by status under a heading with a count,2+# each review two lines: its title (CI and approvals at the right), then3+# number, branch, author, commits, diff size and when. A stacked layer4+# hangs under the review below it. Long merged and closed groups start5+# folded; open and draft never do.6+. "$(dirname "$0")/../lib.sh"7+user dami8+user ana9+repo dami/greet10+clone dami dami/greet "$T/w/d"11+at "$T/w/d" commit "first"12+push_as dami "$T/w/d" origin main13+at "$T/w/d" git switch -q -c dami/base14+printf 'one\ntwo\n' >"$T/w/d/a.txt"15+at "$T/w/d" git add a.txt16+at "$T/w/d" git commit -q -m "base layer"17+push_as dami "$T/w/d" -o title=Base origin dami/base18+at "$T/w/d" git switch -q -c dami/top19+at "$T/w/d" commit "top layer"20+push_as dami "$T/w/d" -o title=Top origin dami/top21+# Eleven merged reviews: their group starts folded.22+i=023+while [ $i -lt 11 ]; do24+ i=$((i + 1))25+ at "$T/w/d" git switch -q -c "dami/m$i" main26+ at "$T/w/d" commit "merged $i"27+ push_as dami "$T/w/d" -o "title=Merged $i" origin "dami/m$i"28+ at "$T/w/d" git switch -q main29+ at "$T/w/d" git merge -q --ff-only "dami/m$i"30+ push_as dami "$T/w/d" origin main31+done32+export FORGE_STRICT=133+build dami/greet34+p=$FORGE_WWW/dami/greet/reviews/index.html35+lacks "$p" '<table'36+has "$p" '<details class="rv-group open" open>'37+has "$p" '<summary><h2>open</h2><span class="n">2</span></summary>'38+has "$p" '<details class="rv-group merged">'39+has "$p" '<summary><h2>merged</h2><span class="n">11</span></summary>'40+# The layer on top hangs under the base, one deep.41+has "$p" '<li class="rv layer" style="--depth: 1">'42+has "$p" '<span class="rv-title"><a href="/dami/greet/reviews/2/">Top</a></span>'43+# Base's second line: number, branch, author, commits, diff size.44+has "$p" '<span class="rv-meta"><span class="num">#1</span><span class="mono">dami/base → main</span><span>dami</span><span>1 commit</span><span class="num"><span class="add">\+2</span> <span class="del">−0</span></span>'45+# The home page's open reviews are the same list.46+has "$FORGE_WWW/dami/greet/index.html" '<ul class="rv-list nav-list">'