Commit 00324124a5
00324124a55ed4242ddff67847c1b3d26c3bc112
parent: 419f6dfdc5
Verified · cmc ci/build: success ci/test: success
cmc <hello@cleberg.net> · 2026-09-22 03:35 UTC
web: widen the accounts page, and dress the logout confirmation
/admin/users is wide. The Actions cell carries up to two forms, each an
11rem confirm field and a button, which is about 1100px of row; 48rem
clipped the second form's button and left its confirm field sitting
after the button before it. On an active row that read as Promote
wanting the name typed, while the admin and pending rows happened to
show a correct pairing in the width they had. Nothing about those rows
was different — only which end of the cell survived the clip. It fits
uncliped down to about a 1170px window, and .tablewrap scrolls below
that.
The logout page drops the clause about the mailed link, the button is
danger, and the cancel sits 16px off it rather than 8. The gap is a
rule on .btngroup rather than a class for the one page: at 8px the
cancel's underline lands against a bordered button's edge, which is
worth avoiding wherever the button is the destructive one.
Ref #247
CHANGELOG.org
+4 −1
| @@ -45,7 +45,10 @@ Eleven web findings, mostly alignment and affordance (#247). |
| 45 | 45 | - The email rows on =/settings= put their buttons and confirm field on |
| 46 | 46 | the right, so they line up however long an address is. |
| 47 | 47 | - =/admin/users= draws a disabled Promote on an account that is not |
| 48 | | active, rather than leaving the cell short. |
| 48 | active, rather than leaving the cell short, and the page is wide. At |
| 49 | 48rem the Actions cell clipped the second form's button, which left |
| 50 | its confirm field looking like it belonged to the button before it — |
| 51 | an active row read as though Promote wanted the name typed. |
| 49 | 52 | - The profile's inactive tabs draw their underline in =--line=, so the |
| 50 | 53 | strip reads as a track the current tab is marked in. |
| 51 | 54 | - =.listhead= sheds its children's block margins, which is what kept a |
internal/web/static/style.css
+3
| @@ -606,6 +606,9 @@ button.danger, .button.danger { |
| 606 | 606 | button.danger:hover, .button.danger:hover { background: var(--bad); color: var(--canvas); filter: none; } |
| 607 | 607 | button:disabled { opacity: 0.55; cursor: default; filter: none; } |
| 608 | 608 | .btngroup { display: inline-flex; gap: var(--sp-2); flex-wrap: wrap; } |
| 609 | /* a destructive button holds the way out at arm's length: 8px puts the |
| 610 | cancel's underline against the button's border */ |
| 611 | .btngroup:has(> button.danger) { gap: var(--sp-4); } |
| 609 | 612 | form.inline { display: inline; } |
| 610 | 613 | .act { text-align: right; } |
| 611 | 614 | |
internal/web/templates/adminusers.html
+5 −1
| @@ -1,4 +1,8 @@ |
| 1 | | {{define "width"}}bounded{{end}} |
| 1 | {{/* wide, because the Actions cell carries up to two forms, each an |
| 2 | 11rem confirm field and a button: about 1100px of row, which 48rem |
| 3 | clipped. What it clipped was the second form's button, leaving its |
| 4 | confirm field looking like it belonged to the button before it. */}} |
| 5 | {{define "width"}}wide{{end}} |
| 2 | 6 | {{define "title"}}accounts · admin · {{.Site}}{{end}} |
| 3 | 7 | {{define "content"}} |
| 4 | 8 | <h1>Accounts</h1> |
internal/web/templates/logout.html
+2 −2
| @@ -2,8 +2,8 @@ |
| 2 | 2 | {{define "title"}}log out · {{.Site}}{{end}} |
| 3 | 3 | {{define "content"}} |
| 4 | 4 | <h1>Log out</h1> |
| 5 | | <p class="meta">Ends this browser's session as <a href="/{{.Viewer}}">{{.Viewer}}</a>. Keys and tokens are untouched, and signing back in is a mailed link.</p> |
| 5 | <p class="meta">Ends this browser's session as <a href="/{{.Viewer}}">{{.Viewer}}</a>. Keys and tokens are untouched.</p> |
| 6 | 6 | <form method="post" action="/logout"> |
| 7 | | <p><button type="submit">Log out</button> <a href="/">Cancel</a></p> |
| 7 | <p class="btngroup"><button type="submit" class="danger">Log out</button> <a href="/">Cancel</a></p> |
| 8 | 8 | </form> |
| 9 | 9 | {{end}} |
internal/web/web_test.go
+2 −2
| @@ -89,8 +89,8 @@ func TestWhenNamesTheZone(t *testing.T) { |
| 89 | 89 | // none. The merge request page picks wide for its diff view, so it gets |
| 90 | 90 | // a per-view define instead of a fixed one. |
| 91 | 91 | func 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, "dashboard.html": true, "issues.html": true, "mrs.html": true, "explore.html": true, "notifications.html": true, "settings.html": true, "account.html": true, "admin.html": true} |
| 93 | | bounded := map[string]bool{"landing.html": true, "fork.html": true, "login.html": true, "logout.html": true, "register.html": true, "registered.html": true, "new.html": true, "issuenew.html": true, "mrnew.html": true, "adminusers.html": true, "snippetnew.html": true, "privacy.html": true, "404.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, "settings.html": true, "account.html": true, "admin.html": true, "adminusers.html": true} |
| 93 | bounded := map[string]bool{"landing.html": true, "fork.html": true, "login.html": true, "logout.html": true, "register.html": true, "registered.html": true, "new.html": true, "issuenew.html": true, "mrnew.html": true, "snippetnew.html": true, "privacy.html": true, "404.html": true} |
| 94 | 94 | perView := map[string]string{"mr.html": `{{define "width"}}{{if eq .View "diff"}}wide{{else}}reading{{end}}{{end}}`} |
| 95 | 95 | for _, name := range Pages() { |
| 96 | 96 | src, err := TemplateSource(name) |