Commit 9807f90f60

9807f90f60b4876b5803f482647a41afadb6228e

parent: 86007ab29b

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-19 15:57 UTC

web: one-line rows on the list pages, at the container width

Ref #226
internal/web/static/style.css +23
@@ -897,6 +897,10 @@ ul.loglist .subject a { color: var(--fg); font-weight: 500; }
897897ul.loglist .subject a:hover { color: var(--link); }
898898ul.loglist .commitside { flex: none; display: flex; gap: var(--sp-2); align-items: center; white-space: nowrap; }
899899
900ul.loglist.rows li { align-items: center; padding: var(--sp-2) var(--sp-4); }
901ul.loglist.rows .commitmain { display: flex; align-items: baseline; gap: var(--sp-3); min-width: 0; }
902ul.loglist.rows .meta { flex: none; color: var(--muted); font-size: var(--fs-1); white-space: nowrap; }
903
900904ul.repolist { list-style: none; margin: var(--sp-3) 0 var(--sp-5); padding: 0; }
901905ul.repolist li { padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--faint); }
902906ul.repolist li:last-child { border-bottom: 0; }
@@ -909,6 +913,12 @@ ul.repolist .reponame .sep { color: var(--muted); margin: 0 2px; }
909913ul.repolist .desc { margin-top: 2px; }
910914ul.repolist .topics, ul.repolist .meta { margin-top: var(--sp-1); }
911915
916ul.repolist.rows li { display: flex; align-items: baseline; gap: var(--sp-3); padding: var(--sp-2) var(--sp-4); }
917ul.repolist.rows .reponame { flex: none; font-size: var(--fs-2); }
918ul.repolist.rows .desc { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: var(--fs-2); margin: 0; }
919ul.repolist.rows .topics, ul.repolist.rows .meta { flex: none; margin: 0; white-space: nowrap; }
920ul.repolist.rows .meta { font-size: var(--fs-1); }
921
912922/* pinned repos: a compact wrap of names, no description or meta */
913923ul.pinlist { list-style: none; margin: var(--sp-3) 0 var(--sp-5); padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
914924ul.pinlist li {
@@ -939,6 +949,15 @@ ul.issuelist .issueside { display: flex; gap: var(--sp-2); align-items: center;
939949ul.issuelist .title a { color: var(--fg); font-weight: 500; }
940950ul.issuelist .title a:hover { color: var(--link); }
941951
952/* one-line rows: title, labels, then the meta pushed right. Above 64rem
953 a list is a table, not prose (desktop layout spec). */
954ul.issuelist.rows li { align-items: center; padding: var(--sp-2) var(--sp-4); }
955ul.issuelist.rows .issuemain { display: flex; align-items: baseline; gap: var(--sp-3); min-width: 0; }
956ul.issuelist.rows .title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
957ul.issuelist.rows .title .chip { margin-left: var(--sp-1); }
958ul.issuelist.rows .meta { flex: none; color: var(--muted); font-size: var(--fs-1); white-space: nowrap; }
959ul.issuelist.rows .meta .repo { color: var(--fg); }
960
942961ul.milestonelist { list-style: none; margin: var(--sp-3) 0; padding: 0; }
943962ul.milestonelist li { padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--faint); }
944963ul.milestonelist li:last-child { border-bottom: 0; }
@@ -1509,6 +1528,10 @@ svg.icon { vertical-align: -0.125em; }
15091528 /* one column, the aside after the thread: a phone shows the
15101529 description first, checks and reviewers after it (#232) */
15111530 .withaside { grid-template-columns: 1fr; }
1531
1532 /* rows go back to two lines where one does not fit */
1533 ul.issuelist.rows .issuemain, ul.repolist.rows li, ul.loglist.rows .commitmain { display: block; }
1534 ul.issuelist.rows .title, ul.repolist.rows .desc { white-space: normal; }
15121535}
15131536
15141537@media (max-width: 52rem) {
internal/web/templates/dashboard.html +3 −2
@@ -1,10 +1,11 @@
1{{define "width"}}wide{{end}}
12{{define "title"}}dashboard · {{.Site}}{{end}}
23{{define "itemlist"}}
3<ul class="issuelist">
4<ul class="issuelist rows">
45{{range .Items}}<li>
56 <div class="issuemain">
67 <p class="title"><a href="/{{.RepoPath}}/{{$.Kind}}/{{.Number}}">{{.Title}}</a></p>
7 <p class="meta">{{.RepoPath}}{{if eq $.Kind "mrs"}}!{{else}}#{{end}}{{.Number}} · <a href="/{{.Author}}">{{.Author}}</a> · {{when .UpdatedAt}}{{if eq .State "source_gone"}} · <span class="chip chip-source_gone">source gone</span>{{end}}</p>
8 <p class="meta"><span class="repo">{{.RepoPath}}{{if eq $.Kind "mrs"}}!{{else}}#{{end}}{{.Number}}</span> · <a href="/{{.Author}}">{{.Author}}</a> · {{when .UpdatedAt}}{{if eq .State "source_gone"}} · <span class="chip chip-source_gone">source gone</span>{{end}}</p>
89 </div>
910</li>
1011{{else}}<li class="empty">{{$.Empty}}</li>{{end}}
internal/web/templates/explore.html +2 −1
@@ -1,3 +1,4 @@
1{{define "width"}}wide{{end}}
12{{define "title"}}explore · {{.Site}}{{end}}
23{{define "content"}}
34<div class="headrow">
@@ -8,7 +9,7 @@
89</form>
910<span class="spacer"></span>
1011</div>
11<ul class="repolist">
12<ul class="repolist rows">
1213{{range .Repos}}{{template "reporow" .}}
1314{{else}}<li class="empty">no public repositories yet</li>{{end}}
1415</ul>
internal/web/templates/globalsearch.html +1 −1
@@ -19,7 +19,7 @@
1919{{if .QueryErr}}<p class="error" role="alert">{{.QueryErr}}</p>
2020{{else if .Query}}
2121{{if .Results}}
22<ul class="issuelist">
22<ul class="issuelist rows">
2323{{range .Results}}<li>
2424 <div class="issuemain">
2525 <p class="title"><a href="{{.Href}}">{{if .Number}}{{.Title}}{{else}}{{.Repo}}{{end}}</a></p>
internal/web/templates/issues.html +2 −1
@@ -1,3 +1,4 @@
1{{define "width"}}wide{{end}}
12{{define "title"}}issues · {{.Repo.OwnerName}}/{{.Repo.Name}}{{end}}
23{{define "content"}}
34<div class="listhead">
@@ -16,7 +17,7 @@
1617 <span class="spacer"></span>
1718 <p class="meta"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/milestones">milestones</a> · <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/labels">labels</a>{{if .Viewer}} · <a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/issues/new">new issue</a>{{end}}</p>
1819</div>
19<ul class="issuelist">
20<ul class="issuelist rows">
2021{{range .Issues}}<li>
2122 <div class="issuemain">
2223 <p class="title"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/issues/{{.Number}}">{{.Title}}</a>
internal/web/templates/mrs.html +2 −1
@@ -1,3 +1,4 @@
1{{define "width"}}wide{{end}}
12{{define "title"}}merge requests · {{.Repo.OwnerName}}/{{.Repo.Name}}{{end}}
23{{define "content"}}
34<div class="listhead">
@@ -16,7 +17,7 @@
1617 {{range .Filters}}<p class="meta">{{.Key}}: {{if eq .Key "label"}}<span class="chip label" style="{{index $.LabelColors .Value}}">{{.Value}}</span>{{else}}<b>{{.Value}}</b>{{end}} <a href="{{.Clear}}">clear</a></p>{{end}}
1718</div>
1819{{if .Viewer}}<p class="meta"><a href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/mrs/new">New merge request</a></p>{{end}}
19<ul class="issuelist">
20<ul class="issuelist rows">
2021{{range .MRs}}{{$n := .Number}}<li>
2122 <div class="issuemain">
2223 <p class="title"><a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/mrs/{{.Number}}">{{.Title}}</a>
internal/web/templates/notifications.html +2 −1
@@ -1,3 +1,4 @@
1{{define "width"}}wide{{end}}
12{{define "title"}}notifications · {{.Site}}{{end}}
23{{define "content"}}
34<div class="listhead">
@@ -9,7 +10,7 @@
910 <span class="spacer"></span>
1011 {{if .Notices}}<form method="post" action="/notifications" class="inline"><button type="submit" class="btn">Mark all read</button></form>{{end}}
1112</div>
12<ul class="issuelist">
13<ul class="issuelist rows">
1314{{range .Notices}}<li>
1415 <div class="issuemain">
1516 <p class="title"><a href="{{.Href}}">{{.Actor}} {{.Summary}}</a></p>
internal/web/web_test.go +1 −1
@@ -89,7 +89,7 @@ func TestWhenNamesTheZone(t *testing.T) {
8989// none. The merge request page picks wide for its diff view, so it gets
9090// a per-view define instead of a fixed one.
9191func TestMainWidthClass(t *testing.T) {
92 wide := map[string]bool{"tree.html": true, "blob.html": true, "blame.html": true, "log.html": true, "commit.html": true, "compare.html": true, "builds.html": true, "build.html": true, "search.html": true, "globalsearch.html": true, "edit.html": true}
92 wide := map[string]bool{"tree.html": true, "blob.html": true, "blame.html": true, "log.html": true, "commit.html": true, "compare.html": true, "builds.html": true, "build.html": true, "search.html": true, "globalsearch.html": true, "edit.html": true, "dashboard.html": true, "issues.html": true, "mrs.html": true, "explore.html": true, "notifications.html": true}
9393 bounded := map[string]bool{"landing.html": true, "login.html": true, "register.html": true, "registered.html": true, "new.html": true, "issuenew.html": true, "mrnew.html": true, "settings.html": true, "account.html": true, "admin.html": true, "adminusers.html": true, "snippetnew.html": true, "privacy.html": true, "404.html": true}
9494 perView := map[string]string{"mr.html": `{{define "width"}}{{if eq .View "diff"}}wide{{else}}reading{{end}}{{end}}`}
9595 for _, name := range Pages() {
internal/web/widths_test.go added +36
@@ -0,0 +1,36 @@
1package web
2
3import (
4 "strings"
5 "testing"
6)
7
8// List pages and the dashboard render at the container width; text pages
9// keep the reading cap (desktop layout spec).
10func TestListPagesAreWide(t *testing.T) {
11 wide := []string{"dashboard.html", "issues.html", "mrs.html", "explore.html", "notifications.html", "globalsearch.html", "builds.html"}
12 for _, name := range wide {
13 src, err := templateFS.ReadFile("templates/" + name)
14 if err != nil {
15 t.Fatal(err)
16 }
17 if !strings.HasPrefix(string(src), `{{define "width"}}wide{{end}}`) {
18 t.Errorf("%s does not declare width wide", name)
19 }
20 }
21 for _, name := range []string{"issue.html", "wiki.html", "owner.html"} {
22 src, _ := templateFS.ReadFile("templates/" + name)
23 if strings.Contains(string(src), `{{define "width"}}wide{{end}}`) {
24 t.Errorf("%s is a text page and must not be wide", name)
25 }
26 }
27 for _, name := range []string{"issues.html", "mrs.html", "notifications.html", "globalsearch.html", "dashboard.html"} {
28 src, _ := templateFS.ReadFile("templates/" + name)
29 if !strings.Contains(string(src), `<ul class="issuelist rows">`) {
30 t.Errorf("%s does not use one-line rows", name)
31 }
32 }
33 if src, _ := templateFS.ReadFile("templates/explore.html"); !strings.Contains(string(src), `<ul class="repolist rows">`) {
34 t.Error("explore.html does not use one-line rows")
35 }
36}