Commit 86007ab29b

86007ab29bc331fa3da95e3ffc0712d2feaaa63a

parent: 52243f111a

Verified · cmc

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

web: the repository header is two rows

Description, topics and website sit beside the name; the toggles hint is
title text on the buttons.

Ref #226
internal/httpd/repohead_test.go added +53
@@ -0,0 +1,53 @@
1package httpd
2
3import (
4 "strings"
5 "testing"
6
7 "gitbay.org/gitbay/internal/control"
8 "gitbay.org/gitbay/internal/web"
9)
10
11// The repository header is two rows: identity with the description and
12// the buttons, then the tabs. The toggles hint is title text on the
13// buttons, not a line of its own (desktop layout spec).
14func TestRepoHeaderTwoRows(t *testing.T) {
15 var sb strings.Builder
16 p := testRepoPage()
17 p.Viewer = "alice"
18 p.Desc = "A CLI-first git forge."
19 p.Topics = []string{"cli"}
20 p.Tab = "files"
21 err := web.Render(&sb, "builds.html", struct {
22 repoPage
23 Builds []control.BuildOut
24 Jobs []control.JobOut
25 Runs []buildRun
26 Filter buildFilter
27 FilterLinks []buildFilterLink
28 Refs []string
29 CanWrite bool
30 Notice string
31 }{p, nil, nil, nil, buildFilter{}, nil, nil, true, ""})
32 if err != nil {
33 t.Fatal(err)
34 }
35 out := sb.String()
36 if strings.Contains(out, `class="toggles"`) || strings.Contains(out, "Pinned shows on your dashboard.") {
37 t.Error("the toggles hint still renders as a line")
38 }
39 for _, want := range []string{
40 `title="Pinned repositories show on your dashboard"`,
41 `title="Watching sends every issue, request and build to your inbox"`,
42 `title="Bookmarked lists it under Bookmarks"`,
43 `<p class="repodesc">A CLI-first git forge.`,
44 } {
45 if !strings.Contains(out, want) {
46 t.Errorf("header lacks %q", want)
47 }
48 }
49 // the description sits inside the identity row, before the buttons
50 if strings.Index(out, `class="repodesc"`) > strings.Index(out, `action="/krz/gitbay/pin"`) {
51 t.Error("description renders after the buttons; it belongs in the identity row")
52 }
53}
internal/web/static/style.css +8 −4
@@ -412,13 +412,15 @@ footer a:hover { color: var(--link); }
412412.repotitle a:hover { color: var(--link); text-decoration: none; }
413413.repotitle .sep { color: var(--muted); margin: 0 2px; font-weight: 400; }
414414.repodesc {
415 margin: var(--sp-2) 0 0;
416 color: var(--fg);
415 margin: 0 0 0 var(--sp-2);
416 color: var(--muted);
417417 font-size: var(--fs-2);
418 display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
418 display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
419 min-width: 0;
419420}
421.repodesc a.site { color: var(--muted); }
422.repodesc a.site:hover { color: var(--link); }
420423.repometa { margin: var(--sp-1) 0 0; color: var(--muted); font-size: var(--fs-1); }
421.toggles { margin: var(--sp-2) 0 0; color: var(--muted); font-size: var(--fs-1); }
422424
423425nav.tabs {
424426 display: flex;
@@ -1518,6 +1520,8 @@ svg.icon { vertical-align: -0.125em; }
15181520 ones past the edge look absent. Two rows show all of them. */
15191521 nav.tabs { flex-wrap: wrap; overflow: visible; }
15201522 .facts { grid-template-columns: 1fr; }
1523 /* a phone shows the description under the name, not beside it */
1524 .repodesc { flex-basis: 100%; margin-left: 0; }
15211525}
15221526
15231527@media (max-width: 40rem) {
internal/web/templates/layout.html +8 −12
@@ -38,26 +38,22 @@
3838{{with field . "Repo"}}
3939<header class="repohead">
4040<div class="wrap">
41 {{$top := topTab (str $ "Tab")}}
4142 <div class="identity">
4243 {{if field $ "RepoHome"}}<h1 class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span>{{.Name}}</h1>
4344 {{else}}<p class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a></p>{{end}}
4445 {{if eq .Visibility "private"}}<span class="chip">Private</span>{{end}}
4546 {{if .Settings.Archived}}<span class="chip">Archived</span>{{end}}
47 {{/* Description, topics and website belong to the code tab, inline
48 with the name so the header is two rows on every page. */}}
49 {{if eq $top "code"}}{{if or (field $ "Desc") (field $ "Topics") $.Repo.Settings.Website}}<p class="repodesc">{{with field $ "Desc"}}{{.}}{{end}} {{with field $ "Topics"}}{{range .}}<a class="chip topic" href="/explore?q={{.}}">{{.}}</a> {{end}}{{end}}{{with $.Repo.Settings.Website}}<a class="site" href="{{.}}" rel="nofollow">{{.}}</a>{{end}}</p>{{end}}{{end}}
4650 <span class="grow"></span>
47 {{if $.Viewer}}<form method="post" action="/{{.OwnerName}}/{{.Name}}/pin" class="inline"><button type="submit" class="btn" aria-pressed="{{if field $ "Pinned"}}true{{else}}false{{end}}"><span aria-hidden="true">{{if field $ "Pinned"}}★{{else}}☆{{end}}</span> {{if field $ "Pinned"}}Pinned{{else}}Pin{{end}}</button></form>
48 <form method="post" action="/{{.OwnerName}}/{{.Name}}/watch" class="inline"><button type="submit" class="btn" aria-pressed="{{if eq (str $ "Watch") "watching"}}true{{else}}false{{end}}">{{if eq (str $ "Watch") "watching"}}Watching{{else}}Watch{{end}}</button></form>
49 <form method="post" action="/{{.OwnerName}}/{{.Name}}/bookmark" class="inline"><button type="submit" class="btn" aria-pressed="{{if field $ "Marked"}}true{{else}}false{{end}}">{{if field $ "Marked"}}Bookmarked{{else}}Bookmark{{end}}</button></form>
51 {{if $.Viewer}}<form method="post" action="/{{.OwnerName}}/{{.Name}}/pin" class="inline"><button type="submit" class="btn" aria-pressed="{{if field $ "Pinned"}}true{{else}}false{{end}}" title="Pinned repositories show on your dashboard"><span aria-hidden="true">{{if field $ "Pinned"}}★{{else}}☆{{end}}</span> {{if field $ "Pinned"}}Pinned{{else}}Pin{{end}}</button></form>
52 <form method="post" action="/{{.OwnerName}}/{{.Name}}/watch" class="inline"><button type="submit" class="btn" aria-pressed="{{if eq (str $ "Watch") "watching"}}true{{else}}false{{end}}" title="Watching sends every issue, request and build to your inbox">{{if eq (str $ "Watch") "watching"}}Watching{{else}}Watch{{end}}</button></form>
53 <form method="post" action="/{{.OwnerName}}/{{.Name}}/bookmark" class="inline"><button type="submit" class="btn" aria-pressed="{{if field $ "Marked"}}true{{else}}false{{end}}" title="Bookmarked lists it under Bookmarks">{{if field $ "Marked"}}Bookmarked{{else}}Bookmark{{end}}</button></form>
5054 <form method="post" action="/{{.OwnerName}}/{{.Name}}/fork" class="inline"><button type="submit" class="btn">Fork</button></form>{{end}}
5155 </div>
52 {{$top := topTab (str $ "Tab")}}
53 {{/* Description, topics and metadata belong to the code tab. On task
54 tabs only the identity row and the tab bar render, and the header
55 is identical on every page within a tab. */}}
56 {{if eq $top "code"}}
57 {{if or (field $ "Desc") (field $ "Topics")}}<p class="repodesc">{{with field $ "Desc"}}{{.}}{{end}} {{with field $ "Topics"}}{{range .}}<a class="chip topic" href="/explore?q={{.}}">{{.}}</a> {{end}}{{end}}</p>{{end}}
58 {{if or $.Repo.Settings.Website (field $ "Mirrors")}}<p class="repometa">{{with $.Repo.Settings.Website}}<a href="{{.}}" rel="nofollow">{{.}}</a>{{end}}{{range $i, $m := field $ "Mirrors"}}{{if or $i $.Repo.Settings.Website}} · {{end}}{{if eq $m.Direction "push"}}mirrors to{{else}}mirrors from{{end}} <a href="{{$m.URL}}" rel="nofollow">{{$m.Target}}</a>{{if $m.Error}}, <span class="bad">sync error: {{$m.Error}}</span>{{else if $m.Synced}}, synced {{$m.Synced}}{{end}}{{end}}</p>{{end}}
59 {{if $.Viewer}}<p class="toggles">Pinned shows on your dashboard. Watching sends every issue, request and build to your inbox. Bookmarked lists it under Bookmarks.</p>{{end}}
60 {{end}}
56 {{if eq $top "code"}}{{if field $ "Mirrors"}}<p class="repometa">{{range $i, $m := field $ "Mirrors"}}{{if $i}} · {{end}}{{if eq $m.Direction "push"}}mirrors to{{else}}mirrors from{{end}} <a href="{{$m.URL}}" rel="nofollow">{{$m.Target}}</a>{{if $m.Error}}, <span class="bad">sync error: {{$m.Error}}</span>{{else if $m.Synced}}, synced {{$m.Synced}}{{end}}{{end}}</p>{{end}}{{end}}
6157 <nav class="tabs" aria-label="Repository">
6258 <a {{if eq $top "code"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}">Code</a>
6359 <a {{if eq $top "issues"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/issues">Issues{{with field $ "OpenIssues"}}{{if .}} <i>{{.}}</i>{{end}}{{end}}</a>