clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

List reviews as two-line items #6

4 files changed, +102 -45
Msite/_includes/macros.njk62+++++++++++++++++++-----------
Msite/assets/keys.ts5+-
Msite/repo/reviews.njk34++++++----------
Atest/scenarios/review-list.sh46++++++++++++++++++++++
M site/_includes/macros.njk +40 -22
@@ -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 %}">&#10004; {{ 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">&#8627;</span>{% endif %}<a href="{{ repo.urls.review(r.id).href }}">{{ r.title }}</a></td><td class="mono hide-sm">{{ r.branch }} &rarr; {{ 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 }} &rarr; {{ 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">&minus;{{ 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. #}
M site/assets/keys.ts +3 -2
M site/repo/reviews.njk +13 -21
@@ -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 %}
A test/scenarios/review-list.sh +46 -0
@@ -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 &rarr; main</span><span>dami</span><span>1 commit</span><span class="num"><span class="add">\+2</span> <span class="del">&minus;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">'