fix(web): rail nits: no idle scroll bar on files, merge rule as a note, latest tag in About, wider commit subjects (#55)
- commit
- 8e01339264a048783f64b93f1dd183137258853b
- author
- brice · · 2026-10-18 06:07 UTC
- committer
- bnema · · 2026-10-18 06:07 UTC
- parent
- 032b9ef
- signature
- SSH Unverified
SHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivAThe key is unknown
9 files · +473 -423
M internal/adapters/web/actions.templ +1 -1
| @@ -462,7 +462,7 @@ templ MRChecks(c ChecksSection) { | |||
| 462 | 462 | </ul> | |
| 463 | 463 | } | |
| 464 | 464 | if c.Note != "" { | |
| 465 | - | <p class="mute">{ c.Note }</p> | |
| 465 | + | <p class="rule-note"><span class="sr">note: </span>{ c.Note }</p> | |
| 466 | 466 | } | |
| 467 | 467 | </section> | |
| 468 | 468 | } | |
| @@ -462,7 +462,7 @@ templ MRChecks(c ChecksSection) { | |||||
| 462 | </ul> | 462 | </ul> | ||
| 463 | } | 463 | } | ||
| 464 | if c.Note != "" { | 464 | if c.Note != "" { | ||
| 465 | - | <p class="mute">{ c.Note }</p> | 465 | + | <p class="rule-note"><span class="sr">note: </span>{ c.Note }</p> |
| 466 | } | 466 | } | ||
| 467 | </section> | 467 | </section> | ||
| 468 | } | 468 | } | ||
M internal/adapters/web/actions_templ.go +2 -2 generated file: collapsed
M internal/adapters/web/pages.templ +27 -18
| @@ -54,28 +54,37 @@ templ Summary(l Layout, p SummaryPage) { | |||
| 54 | 54 | } | |
| 55 | 55 | } | |
| 56 | 56 | ||
| 57 | + | // latestTagFact is the "latest tag" row of About: the newest tag (its | |
| 58 | + | // commit subject on hover), how old it is, and the count of tags, a link to | |
| 59 | + | // all of them. With too many tags to date them, the count only. | |
| 60 | + | templ latestTagFact(t LatestTag) { | |
| 61 | + | <dt>latest tag</dt> | |
| 62 | + | <dd class="latest-tag"> | |
| 63 | + | if t.Name != "" { | |
| 64 | + | <a class="tag-name" href={ templ.SafeURL(t.Href) } title={ t.Subject }><span class="ico i-tag" aria-hidden="true"></span>{ t.Name }</a> | |
| 65 | + | if t.WhenAt != "" { | |
| 66 | + | <time class="dim" datetime={ t.WhenAt } title={ t.WhenAt }>{ t.When }</time> | |
| 67 | + | } | |
| 68 | + | } | |
| 69 | + | <a class="all-tags" href={ templ.SafeURL(t.RefsHref) }>{ plural(t.Count, "tag", "tags") }</a> | |
| 70 | + | </dd> | |
| 71 | + | } | |
| 72 | + | ||
| 57 | 73 | templ summaryRail(p SummaryPage) { | |
| 58 | 74 | <section aria-labelledby="r-clone"><h2 id="r-clone">Clone</h2>@CloneBlock(p.Clone) | |
| 59 | 75 | </section> | |
| 60 | - | <section aria-labelledby="r-facts"><h2 id="r-facts">About</h2>@Facts(p.Facts) | |
| 61 | - | </section> | |
| 62 | - | if p.Latest != nil { | |
| 63 | - | <section aria-labelledby="r-tag"> | |
| 64 | - | <h2 id="r-tag">Tags <span class="count">{ itoa(p.Latest.Count) }</span> <a href={ templ.SafeURL(p.Latest.RefsHref) }>View all</a></h2> | |
| 65 | - | if p.Latest.Name != "" { | |
| 66 | - | <div class="latest-tag"> | |
| 67 | - | <a class="tag-name" href={ templ.SafeURL(p.Latest.Href) }><span class="ico i-tag" aria-hidden="true"></span>{ p.Latest.Name }</a> | |
| 68 | - | <span class="pill">latest</span> | |
| 69 | - | <time class="dim" datetime={ p.Latest.WhenAt } title={ p.Latest.WhenAt }>{ p.Latest.When }</time> | |
| 70 | - | if p.Latest.Subject != "" { | |
| 71 | - | <p class="tag-sub dim">{ p.Latest.Subject }</p> | |
| 72 | - | } | |
| 73 | - | </div> | |
| 74 | - | } else { | |
| 75 | - | <p class="dim">{ plural(p.Latest.Count, "tag", "tags") }</p> | |
| 76 | + | <section aria-labelledby="r-facts"> | |
| 77 | + | <h2 id="r-facts">About</h2> | |
| 78 | + | <dl class="kv"> | |
| 79 | + | for _, f := range p.Facts { | |
| 80 | + | <dt>{ f.Key }</dt> | |
| 81 | + | @FactValue(f) | |
| 76 | 82 | } | |
| 77 | - | </section> | |
| 78 | - | } | |
| 83 | + | if p.Latest != nil { | |
| 84 | + | @latestTagFact(*p.Latest) | |
| 85 | + | } | |
| 86 | + | </dl> | |
| 87 | + | </section> | |
| 79 | 88 | if len(p.Recent) > 0 { | |
| 80 | 89 | <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent) | |
| 81 | 90 | </section> | |
| @@ -54,28 +54,37 @@ templ Summary(l Layout, p SummaryPage) { | |||||
| 54 | } | 54 | } | ||
| 55 | } | 55 | } | ||
| 56 | 56 | ||||
| 57 | + | // latestTagFact is the "latest tag" row of About: the newest tag (its | |||
| 58 | + | // commit subject on hover), how old it is, and the count of tags, a link to | |||
| 59 | + | // all of them. With too many tags to date them, the count only. | |||
| 60 | + | templ latestTagFact(t LatestTag) { | |||
| 61 | + | <dt>latest tag</dt> | |||
| 62 | + | <dd class="latest-tag"> | |||
| 63 | + | if t.Name != "" { | |||
| 64 | + | <a class="tag-name" href={ templ.SafeURL(t.Href) } title={ t.Subject }><span class="ico i-tag" aria-hidden="true"></span>{ t.Name }</a> | |||
| 65 | + | if t.WhenAt != "" { | |||
| 66 | + | <time class="dim" datetime={ t.WhenAt } title={ t.WhenAt }>{ t.When }</time> | |||
| 67 | + | } | |||
| 68 | + | } | |||
| 69 | + | <a class="all-tags" href={ templ.SafeURL(t.RefsHref) }>{ plural(t.Count, "tag", "tags") }</a> | |||
| 70 | + | </dd> | |||
| 71 | + | } | |||
| 72 | + | ||||
| 57 | templ summaryRail(p SummaryPage) { | 73 | templ summaryRail(p SummaryPage) { | ||
| 58 | <section aria-labelledby="r-clone"><h2 id="r-clone">Clone</h2>@CloneBlock(p.Clone) | 74 | <section aria-labelledby="r-clone"><h2 id="r-clone">Clone</h2>@CloneBlock(p.Clone) | ||
| 59 | </section> | 75 | </section> | ||
| 60 | - | <section aria-labelledby="r-facts"><h2 id="r-facts">About</h2>@Facts(p.Facts) | 76 | + | <section aria-labelledby="r-facts"> |
| 61 | - | </section> | 77 | + | <h2 id="r-facts">About</h2> |
| 62 | - | if p.Latest != nil { | 78 | + | <dl class="kv"> |
| 63 | - | <section aria-labelledby="r-tag"> | 79 | + | for _, f := range p.Facts { |
| 64 | - | <h2 id="r-tag">Tags <span class="count">{ itoa(p.Latest.Count) }</span> <a href={ templ.SafeURL(p.Latest.RefsHref) }>View all</a></h2> | 80 | + | <dt>{ f.Key }</dt> |
| 65 | - | if p.Latest.Name != "" { | 81 | + | @FactValue(f) |
| 66 | - | <div class="latest-tag"> | |||
| 67 | - | <a class="tag-name" href={ templ.SafeURL(p.Latest.Href) }><span class="ico i-tag" aria-hidden="true"></span>{ p.Latest.Name }</a> | |||
| 68 | - | <span class="pill">latest</span> | |||
| 69 | - | <time class="dim" datetime={ p.Latest.WhenAt } title={ p.Latest.WhenAt }>{ p.Latest.When }</time> | |||
| 70 | - | if p.Latest.Subject != "" { | |||
| 71 | - | <p class="tag-sub dim">{ p.Latest.Subject }</p> | |||
| 72 | - | } | |||
| 73 | - | </div> | |||
| 74 | - | } else { | |||
| 75 | - | <p class="dim">{ plural(p.Latest.Count, "tag", "tags") }</p> | |||
| 76 | } | 82 | } | ||
| 77 | - | </section> | 83 | + | if p.Latest != nil { |
| 78 | - | } | 84 | + | @latestTagFact(*p.Latest) |
| 85 | + | } | |||
| 86 | + | </dl> | |||
| 87 | + | </section> | |||
| 79 | if len(p.Recent) > 0 { | 88 | if len(p.Recent) > 0 { | ||
| 80 | <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent) | 89 | <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent) | ||
| 81 | </section> | 90 | </section> | ||
M internal/adapters/web/pages_templ.go +398 -369 generated file: collapsed
M internal/adapters/web/static/css/20-shell.css +8 -6
| @@ -134,13 +134,15 @@ | |||
| 134 | 134 | .rail section > .list { margin: var(--s2) 0; } | |
| 135 | 135 | .rail section > p + p { margin-top: calc(-1 * var(--s2)); } | |
| 136 | 136 | ||
| 137 | - | /* newest tag in the summary rail */ | |
| 138 | - | .latest-tag { | |
| 139 | - | display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); font-size: var(--fs-s); | |
| 140 | - | & .tag-name { display: inline-flex; align-items: center; gap: 6px; font: 700 var(--fs) / 20px var(--mono); color: var(--ink-hi); text-decoration: none; } | |
| 137 | + | /* newest tag, a row of About in the summary rail */ | |
| 138 | + | .kv dd.latest-tag { | |
| 139 | + | display: flex; align-items: baseline; gap: var(--s2); | |
| 140 | + | & .tag-name { display: inline-flex; align-items: center; gap: 4px; min-width: 0; overflow: hidden; font-weight: 700; font-family: var(--mono); color: var(--ink-hi); text-decoration: none; } | |
| 141 | + | & .tag-name .ico { flex: none; } | |
| 141 | 142 | & .tag-name:hover { text-decoration: underline; } | |
| 142 | - | & time { margin-left: auto; } | |
| 143 | - | & .tag-sub { flex-basis: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| 143 | + | & time { white-space: nowrap; } | |
| 144 | + | & .all-tags { margin-left: auto; white-space: nowrap; color: var(--ink-dim); text-decoration: none; } | |
| 145 | + | & .all-tags:hover { color: var(--ink-hi); text-decoration: underline; } | |
| 144 | 146 | } | |
| 145 | 147 | ||
| 146 | 148 | .kv { | |
| @@ -134,13 +134,15 @@ | |||||
| 134 | .rail section > .list { margin: var(--s2) 0; } | 134 | .rail section > .list { margin: var(--s2) 0; } | ||
| 135 | .rail section > p + p { margin-top: calc(-1 * var(--s2)); } | 135 | .rail section > p + p { margin-top: calc(-1 * var(--s2)); } | ||
| 136 | 136 | ||||
| 137 | - | /* newest tag in the summary rail */ | 137 | + | /* newest tag, a row of About in the summary rail */ |
| 138 | - | .latest-tag { | 138 | + | .kv dd.latest-tag { |
| 139 | - | display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s2); font-size: var(--fs-s); | 139 | + | display: flex; align-items: baseline; gap: var(--s2); |
| 140 | - | & .tag-name { display: inline-flex; align-items: center; gap: 6px; font: 700 var(--fs) / 20px var(--mono); color: var(--ink-hi); text-decoration: none; } | 140 | + | & .tag-name { display: inline-flex; align-items: center; gap: 4px; min-width: 0; overflow: hidden; font-weight: 700; font-family: var(--mono); color: var(--ink-hi); text-decoration: none; } |
| 141 | + | & .tag-name .ico { flex: none; } | |||
| 141 | & .tag-name:hover { text-decoration: underline; } | 142 | & .tag-name:hover { text-decoration: underline; } | ||
| 142 | - | & time { margin-left: auto; } | 143 | + | & time { white-space: nowrap; } |
| 143 | - | & .tag-sub { flex-basis: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | 144 | + | & .all-tags { margin-left: auto; white-space: nowrap; color: var(--ink-dim); text-decoration: none; } |
| 145 | + | & .all-tags:hover { color: var(--ink-hi); text-decoration: underline; } | |||
| 144 | } | 146 | } | ||
| 145 | 147 | ||||
| 146 | .kv { | 148 | .kv { | ||
M internal/adapters/web/static/css/50-code.css +15 -9
| @@ -115,7 +115,9 @@ | |||
| 115 | 115 | font-size: var(--fs-s); position: relative; /* the offsets of its links count from it (ermite.js) */ | |
| 116 | 116 | & li[hidden] { display: none; } | |
| 117 | 117 | & a { | |
| 118 | - | display: grid; grid-template-columns: 1.5ch minmax(0, 1fr) auto auto; column-gap: var(--s2); align-items: baseline; | |
| 118 | + | /* centred, not on the baseline: the smaller counts on a baseline sit | |
| 119 | + | a pixel below the row, enough to give the list a scroll bar */ | |
| 120 | + | display: grid; grid-template-columns: 1.5ch minmax(0, 1fr) auto auto; column-gap: var(--s2); align-items: center; | |
| 119 | 121 | height: 26px; line-height: 26px; padding: 0 var(--s2); margin: 0 calc(-1 * var(--s2)); | |
| 120 | 122 | border-radius: 4px; border-left: 2px solid transparent; text-decoration: none; font-family: var(--mono); color: var(--ink); | |
| 121 | 123 | } | |
| @@ -127,9 +129,11 @@ | |||
| 127 | 129 | & .st[data-st="A"] { color: var(--teal); } | |
| 128 | 130 | & .st[data-st="D"] { color: var(--red); } | |
| 129 | 131 | & .nm { overflow: hidden; white-space: nowrap; display: flex; min-width: 0; } | |
| 130 | - | & .nm .p { flex: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; } | |
| 132 | + | /* the name gives way last: the directory shrinks first, down to its | |
| 133 | + | ellipsis, before the name is cut */ | |
| 134 | + | & .nm .p { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; } | |
| 131 | 135 | /* cut from the start (rtl), the path itself kept in its order (bdi) */ | |
| 132 | - | & .nm .d { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; color: var(--ink-mute); padding-left: 1ch; } | |
| 136 | + | & .nm .d { flex: 0 1000 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; color: var(--ink-mute); padding-left: 1ch; } | |
| 133 | 137 | & .nm .d bdi { direction: ltr; } | |
| 134 | 138 | & .cn { font: 600 var(--fs-xs) / 16px var(--font); padding: 0 6px; border-radius: 999px; background: var(--ink-hi); color: var(--on-ink); align-self: center; } | |
| 135 | 139 | & .cn.done { background: var(--bg-strong); color: var(--ink-dim); } | |
| @@ -148,18 +152,20 @@ | |||
| 148 | 152 | & .c-sha { width: 96px; } | |
| 149 | 153 | & .c-sub { width: auto; } | |
| 150 | 154 | } | |
| 151 | - | /* the commit table inside a rail block: no frame of its own */ | |
| 155 | + | /* the commit table inside a rail block: no frame of its own. The id, the | |
| 156 | + | checks and the date take the width of their text (an empty checks cell | |
| 157 | + | next to nothing); the subject takes the rest, cut at its end only. */ | |
| 152 | 158 | .rail .list.commits { | |
| 153 | - | border: 0; border-radius: 0; background: none; | |
| 154 | - | & td { height: 34px; padding: 0 var(--s2); } | |
| 159 | + | border: 0; border-radius: 0; background: none; table-layout: auto; | |
| 160 | + | & td { height: 34px; padding: 0 var(--s2); width: 1%; white-space: nowrap; } | |
| 155 | 161 | & td.cur { width: 3px; padding: 0; } | |
| 156 | 162 | & td:nth-child(2) { padding-left: var(--s3); } | |
| 157 | 163 | & td:last-child { padding-right: var(--s4); } | |
| 158 | 164 | & .c-who { display: none; } | |
| 159 | - | & .c-sha { width: 72px; } | |
| 160 | - | & .c-subject { font-size: var(--fs-s); } | |
| 165 | + | & .c-subject { width: auto; max-width: 0; font-size: var(--fs-s); } | |
| 161 | 166 | & .c-subject .pill { display: none; } | |
| 162 | - | & .c-when { width: 70px; text-align: right; } | |
| 167 | + | & .c-ck:empty { padding: 0; } | |
| 168 | + | & .c-when { text-align: right; } | |
| 163 | 169 | } | |
| 164 | 170 | .rail section > p { overflow-wrap: anywhere; } | |
| 165 | 171 | @media (max-width: 700px) { | |
| @@ -115,7 +115,9 @@ | |||||
| 115 | font-size: var(--fs-s); position: relative; /* the offsets of its links count from it (ermite.js) */ | 115 | font-size: var(--fs-s); position: relative; /* the offsets of its links count from it (ermite.js) */ | ||
| 116 | & li[hidden] { display: none; } | 116 | & li[hidden] { display: none; } | ||
| 117 | & a { | 117 | & a { | ||
| 118 | - | display: grid; grid-template-columns: 1.5ch minmax(0, 1fr) auto auto; column-gap: var(--s2); align-items: baseline; | 118 | + | /* centred, not on the baseline: the smaller counts on a baseline sit |
| 119 | + | a pixel below the row, enough to give the list a scroll bar */ | |||
| 120 | + | display: grid; grid-template-columns: 1.5ch minmax(0, 1fr) auto auto; column-gap: var(--s2); align-items: center; | |||
| 119 | height: 26px; line-height: 26px; padding: 0 var(--s2); margin: 0 calc(-1 * var(--s2)); | 121 | height: 26px; line-height: 26px; padding: 0 var(--s2); margin: 0 calc(-1 * var(--s2)); | ||
| 120 | border-radius: 4px; border-left: 2px solid transparent; text-decoration: none; font-family: var(--mono); color: var(--ink); | 122 | border-radius: 4px; border-left: 2px solid transparent; text-decoration: none; font-family: var(--mono); color: var(--ink); | ||
| 121 | } | 123 | } | ||
| @@ -127,9 +129,11 @@ | |||||
| 127 | & .st[data-st="A"] { color: var(--teal); } | 129 | & .st[data-st="A"] { color: var(--teal); } | ||
| 128 | & .st[data-st="D"] { color: var(--red); } | 130 | & .st[data-st="D"] { color: var(--red); } | ||
| 129 | & .nm { overflow: hidden; white-space: nowrap; display: flex; min-width: 0; } | 131 | & .nm { overflow: hidden; white-space: nowrap; display: flex; min-width: 0; } | ||
| 130 | - | & .nm .p { flex: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; } | 132 | + | /* the name gives way last: the directory shrinks first, down to its |
| 133 | + | ellipsis, before the name is cut */ | |||
| 134 | + | & .nm .p { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; } | |||
| 131 | /* cut from the start (rtl), the path itself kept in its order (bdi) */ | 135 | /* cut from the start (rtl), the path itself kept in its order (bdi) */ | ||
| 132 | - | & .nm .d { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; color: var(--ink-mute); padding-left: 1ch; } | 136 | + | & .nm .d { flex: 0 1000 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; color: var(--ink-mute); padding-left: 1ch; } |
| 133 | & .nm .d bdi { direction: ltr; } | 137 | & .nm .d bdi { direction: ltr; } | ||
| 134 | & .cn { font: 600 var(--fs-xs) / 16px var(--font); padding: 0 6px; border-radius: 999px; background: var(--ink-hi); color: var(--on-ink); align-self: center; } | 138 | & .cn { font: 600 var(--fs-xs) / 16px var(--font); padding: 0 6px; border-radius: 999px; background: var(--ink-hi); color: var(--on-ink); align-self: center; } | ||
| 135 | & .cn.done { background: var(--bg-strong); color: var(--ink-dim); } | 139 | & .cn.done { background: var(--bg-strong); color: var(--ink-dim); } | ||
| @@ -148,18 +152,20 @@ | |||||
| 148 | & .c-sha { width: 96px; } | 152 | & .c-sha { width: 96px; } | ||
| 149 | & .c-sub { width: auto; } | 153 | & .c-sub { width: auto; } | ||
| 150 | } | 154 | } | ||
| 151 | - | /* the commit table inside a rail block: no frame of its own */ | 155 | + | /* the commit table inside a rail block: no frame of its own. The id, the |
| 156 | + | checks and the date take the width of their text (an empty checks cell | |||
| 157 | + | next to nothing); the subject takes the rest, cut at its end only. */ | |||
| 152 | .rail .list.commits { | 158 | .rail .list.commits { | ||
| 153 | - | border: 0; border-radius: 0; background: none; | 159 | + | border: 0; border-radius: 0; background: none; table-layout: auto; |
| 154 | - | & td { height: 34px; padding: 0 var(--s2); } | 160 | + | & td { height: 34px; padding: 0 var(--s2); width: 1%; white-space: nowrap; } |
| 155 | & td.cur { width: 3px; padding: 0; } | 161 | & td.cur { width: 3px; padding: 0; } | ||
| 156 | & td:nth-child(2) { padding-left: var(--s3); } | 162 | & td:nth-child(2) { padding-left: var(--s3); } | ||
| 157 | & td:last-child { padding-right: var(--s4); } | 163 | & td:last-child { padding-right: var(--s4); } | ||
| 158 | & .c-who { display: none; } | 164 | & .c-who { display: none; } | ||
| 159 | - | & .c-sha { width: 72px; } | 165 | + | & .c-subject { width: auto; max-width: 0; font-size: var(--fs-s); } |
| 160 | - | & .c-subject { font-size: var(--fs-s); } | |||
| 161 | & .c-subject .pill { display: none; } | 166 | & .c-subject .pill { display: none; } | ||
| 162 | - | & .c-when { width: 70px; text-align: right; } | 167 | + | & .c-ck:empty { padding: 0; } |
| 168 | + | & .c-when { text-align: right; } | |||
| 163 | } | 169 | } | ||
| 164 | .rail section > p { overflow-wrap: anywhere; } | 170 | .rail section > p { overflow-wrap: anywhere; } | ||
| 165 | @media (max-width: 700px) { | 171 | @media (max-width: 700px) { | ||
M internal/adapters/web/static/css/65-ci.css +8 -1
| @@ -20,7 +20,8 @@ | |||
| 20 | 20 | the refs list has the mark next to the sha. */ | |
| 21 | 21 | .c-ck { width: 1%; white-space: nowrap; } | |
| 22 | 22 | .list .c-ck { width: 92px; } | |
| 23 | - | .rail .list.commits .c-ck { width: 76px; } | |
| 23 | + | /* the commit table of a rail lays out on its content (50-code.css) */ | |
| 24 | + | .rail .list.commits .c-ck { width: 1%; } | |
| 24 | 25 | .list.refs .c-sha { width: 150px; } | |
| 25 | 26 | ||
| 26 | 27 | /* the table is fixed: the status column is the icon, its margin and the | |
| @@ -59,6 +60,12 @@ | |||
| 59 | 60 | .attempts { padding: 0; margin: var(--s2) 0 0; list-style: none; font-size: var(--fs-s); } | |
| 60 | 61 | .checks { display: grid; gap: var(--s1); padding: 0; margin: 0; list-style: none; font-size: var(--fs-s); } | |
| 61 | 62 | .checks li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1); } | |
| 63 | + | /* the merge rule under the checks: a note about them, not one of them */ | |
| 64 | + | .rail section > .rule-note { | |
| 65 | + | display: flex; gap: 6px; margin-top: var(--s3); padding-top: var(--s2); border-top: 1px dashed var(--border); | |
| 66 | + | font-size: var(--fs-xs); font-style: italic; line-height: 1.4; color: var(--ink-mute); | |
| 67 | + | &::before { content: "i"; flex: none; width: 14px; height: 14px; margin-top: 1px; border: 1px solid currentColor; border-radius: 50%; font: 700 9px / 12px var(--mono); font-style: normal; text-align: center; } | |
| 68 | + | } | |
| 62 | 69 | ||
| 63 | 70 | /* annotations and summaries of a run */ | |
| 64 | 71 | .annots { margin: 0 0 var(--s4); } | |
| @@ -20,7 +20,8 @@ | |||||
| 20 | the refs list has the mark next to the sha. */ | 20 | the refs list has the mark next to the sha. */ | ||
| 21 | .c-ck { width: 1%; white-space: nowrap; } | 21 | .c-ck { width: 1%; white-space: nowrap; } | ||
| 22 | .list .c-ck { width: 92px; } | 22 | .list .c-ck { width: 92px; } | ||
| 23 | - | .rail .list.commits .c-ck { width: 76px; } | 23 | + | /* the commit table of a rail lays out on its content (50-code.css) */ |
| 24 | + | .rail .list.commits .c-ck { width: 1%; } | |||
| 24 | .list.refs .c-sha { width: 150px; } | 25 | .list.refs .c-sha { width: 150px; } | ||
| 25 | 26 | ||||
| 26 | /* the table is fixed: the status column is the icon, its margin and the | 27 | /* the table is fixed: the status column is the icon, its margin and the | ||
| @@ -59,6 +60,12 @@ | |||||
| 59 | .attempts { padding: 0; margin: var(--s2) 0 0; list-style: none; font-size: var(--fs-s); } | 60 | .attempts { padding: 0; margin: var(--s2) 0 0; list-style: none; font-size: var(--fs-s); } | ||
| 60 | .checks { display: grid; gap: var(--s1); padding: 0; margin: 0; list-style: none; font-size: var(--fs-s); } | 61 | .checks { display: grid; gap: var(--s1); padding: 0; margin: 0; list-style: none; font-size: var(--fs-s); } | ||
| 61 | .checks li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1); } | 62 | .checks li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1); } | ||
| 63 | + | /* the merge rule under the checks: a note about them, not one of them */ | |||
| 64 | + | .rail section > .rule-note { | |||
| 65 | + | display: flex; gap: 6px; margin-top: var(--s3); padding-top: var(--s2); border-top: 1px dashed var(--border); | |||
| 66 | + | font-size: var(--fs-xs); font-style: italic; line-height: 1.4; color: var(--ink-mute); | |||
| 67 | + | &::before { content: "i"; flex: none; width: 14px; height: 14px; margin-top: 1px; border: 1px solid currentColor; border-radius: 50%; font: 700 9px / 12px var(--mono); font-style: normal; text-align: center; } | |||
| 68 | + | } | |||
| 62 | 69 | ||||
| 63 | /* annotations and summaries of a run */ | 70 | /* annotations and summaries of a run */ | ||
| 64 | .annots { margin: 0 0 var(--s4); } | 71 | .annots { margin: 0 0 var(--s4); } | ||
M internal/app/testdata/web/summary.html +6 -11
| @@ -147,20 +147,15 @@ | |||
| 147 | 147 | <dd> | |
| 148 | 148 | <time datetime="STAMP">STAMP</time> | |
| 149 | 149 | </dd> | |
| 150 | - | </dl> | |
| 151 | - | </section> | |
| 152 | - | <section aria-labelledby="r-tag"> | |
| 153 | - | <h2 id="r-tag">Tags <span class="count">2</span> | |
| 154 | - | <a href="/demo/refs">View all</a> | |
| 155 | - | </h2> | |
| 156 | - | <div class="latest-tag"> | |
| 157 | - | <a class="tag-name" href="/demo/tree/v0.2.0"> | |
| 150 | + | <dt>latest tag</dt> | |
| 151 | + | <dd class="latest-tag"> | |
| 152 | + | <a class="tag-name" href="/demo/tree/v0.2.0" title="add main.go"> | |
| 158 | 153 | <span class="ico i-tag" aria-hidden="true"> | |
| 159 | 154 | </span>v0.2.0</a> | |
| 160 | - | <span class="pill">latest</span> | |
| 161 | 155 | <time class="dim" datetime="STAMP" title="STAMP">AGO</time> | |
| 162 | - | <p class="tag-sub dim">add main.go</p> | |
| 163 | - | </div> | |
| 156 | + | <a class="all-tags" href="/demo/refs">2 tags</a> | |
| 157 | + | </dd> | |
| 158 | + | </dl> | |
| 164 | 159 | </section> | |
| 165 | 160 | <section aria-labelledby="r-recent"> | |
| 166 | 161 | <h2 id="r-recent">Recent commits <a href="/demo/commits/main">View all</a> | |
| @@ -147,20 +147,15 @@ | |||||
| 147 | <dd> | 147 | <dd> | ||
| 148 | <time datetime="STAMP">STAMP</time> | 148 | <time datetime="STAMP">STAMP</time> | ||
| 149 | </dd> | 149 | </dd> | ||
| 150 | - | </dl> | 150 | + | <dt>latest tag</dt> |
| 151 | - | </section> | 151 | + | <dd class="latest-tag"> |
| 152 | - | <section aria-labelledby="r-tag"> | 152 | + | <a class="tag-name" href="/demo/tree/v0.2.0" title="add main.go"> |
| 153 | - | <h2 id="r-tag">Tags <span class="count">2</span> | |||
| 154 | - | <a href="/demo/refs">View all</a> | |||
| 155 | - | </h2> | |||
| 156 | - | <div class="latest-tag"> | |||
| 157 | - | <a class="tag-name" href="/demo/tree/v0.2.0"> | |||
| 158 | <span class="ico i-tag" aria-hidden="true"> | 153 | <span class="ico i-tag" aria-hidden="true"> | ||
| 159 | </span>v0.2.0</a> | 154 | </span>v0.2.0</a> | ||
| 160 | - | <span class="pill">latest</span> | |||
| 161 | <time class="dim" datetime="STAMP" title="STAMP">AGO</time> | 155 | <time class="dim" datetime="STAMP" title="STAMP">AGO</time> | ||
| 162 | - | <p class="tag-sub dim">add main.go</p> | 156 | + | <a class="all-tags" href="/demo/refs">2 tags</a> |
| 163 | - | </div> | 157 | + | </dd> |
| 158 | + | </dl> | |||
| 164 | </section> | 159 | </section> | ||
| 165 | <section aria-labelledby="r-recent"> | 160 | <section aria-labelledby="r-recent"> | ||
| 166 | <h2 id="r-recent">Recent commits <a href="/demo/commits/main">View all</a> | 161 | <h2 id="r-recent">Recent commits <a href="/demo/commits/main">View all</a> | ||
M internal/app/web_test.go +8 -6
| @@ -264,14 +264,16 @@ func TestWebContentSecurity(t *testing.T) { | |||
| 264 | 264 | } | |
| 265 | 265 | } | |
| 266 | 266 | ||
| 267 | - | // The summary rail names the newest tag; with more tags than it dates, it | |
| 268 | - | // gives their count instead of an empty block. | |
| 267 | + | // About, in the summary rail, names the newest tag; with more tags than it | |
| 268 | + | // dates, it gives their count only. | |
| 269 | 269 | func TestWebSummaryLatestTag(t *testing.T) { | |
| 270 | 270 | h := newHarness(t, harnessOpts{}) | |
| 271 | 271 | h.webRepo("demo") | |
| 272 | 272 | _, _, body := h.get("/demo") | |
| 273 | - | assert.Contains(t, body, `<a class="tag-name" href="/demo/tree/v0.2.0">`) | |
| 274 | - | assert.Contains(t, body, `<p class="tag-sub dim">add main.go</p>`) | |
| 273 | + | assert.Contains(t, body, `<dt>latest tag</dt>`) | |
| 274 | + | assert.Contains(t, body, `<a class="tag-name" href="/demo/tree/v0.2.0" title="add main.go">`) | |
| 275 | + | assert.Contains(t, body, `<a class="all-tags" href="/demo/refs">2 tags</a>`) | |
| 276 | + | assert.NotContains(t, body, `id="r-tag"`, "no Tags block of its own: the tag is a row of About") | |
| 275 | 277 | ||
| 276 | 278 | h.seed("many", domain.VisibilityPublic, "a.txt") | |
| 277 | 279 | wt := t.TempDir() | |
| @@ -281,6 +283,6 @@ func TestWebSummaryLatestTag(t *testing.T) { | |||
| 281 | 283 | } | |
| 282 | 284 | h.mustGit(wt, "push", "-q", "--tags", "origin") | |
| 283 | 285 | _, _, body = h.get("/many") | |
| 284 | - | assert.NotContains(t, body, `class="latest-tag"`) | |
| 285 | - | assert.Contains(t, body, `<p class="dim">21 tags</p>`) | |
| 286 | + | assert.NotContains(t, body, `class="tag-name"`) | |
| 287 | + | assert.Contains(t, body, `<a class="all-tags" href="/many/refs">21 tags</a>`) | |
| 286 | 288 | } | |
| @@ -264,14 +264,16 @@ func TestWebContentSecurity(t *testing.T) { | |||||
| 264 | } | 264 | } | ||
| 265 | } | 265 | } | ||
| 266 | 266 | ||||
| 267 | - | // The summary rail names the newest tag; with more tags than it dates, it | 267 | + | // About, in the summary rail, names the newest tag; with more tags than it |
| 268 | - | // gives their count instead of an empty block. | 268 | + | // dates, it gives their count only. |
| 269 | func TestWebSummaryLatestTag(t *testing.T) { | 269 | func TestWebSummaryLatestTag(t *testing.T) { | ||
| 270 | h := newHarness(t, harnessOpts{}) | 270 | h := newHarness(t, harnessOpts{}) | ||
| 271 | h.webRepo("demo") | 271 | h.webRepo("demo") | ||
| 272 | _, _, body := h.get("/demo") | 272 | _, _, body := h.get("/demo") | ||
| 273 | - | assert.Contains(t, body, `<a class="tag-name" href="/demo/tree/v0.2.0">`) | 273 | + | assert.Contains(t, body, `<dt>latest tag</dt>`) |
| 274 | - | assert.Contains(t, body, `<p class="tag-sub dim">add main.go</p>`) | 274 | + | assert.Contains(t, body, `<a class="tag-name" href="/demo/tree/v0.2.0" title="add main.go">`) |
| 275 | + | assert.Contains(t, body, `<a class="all-tags" href="/demo/refs">2 tags</a>`) | |||
| 276 | + | assert.NotContains(t, body, `id="r-tag"`, "no Tags block of its own: the tag is a row of About") | |||
| 275 | 277 | ||||
| 276 | h.seed("many", domain.VisibilityPublic, "a.txt") | 278 | h.seed("many", domain.VisibilityPublic, "a.txt") | ||
| 277 | wt := t.TempDir() | 279 | wt := t.TempDir() | ||
| @@ -281,6 +283,6 @@ func TestWebSummaryLatestTag(t *testing.T) { | |||||
| 281 | } | 283 | } | ||
| 282 | h.mustGit(wt, "push", "-q", "--tags", "origin") | 284 | h.mustGit(wt, "push", "-q", "--tags", "origin") | ||
| 283 | _, _, body = h.get("/many") | 285 | _, _, body = h.get("/many") | ||
| 284 | - | assert.NotContains(t, body, `class="latest-tag"`) | 286 | + | assert.NotContains(t, body, `class="tag-name"`) |
| 285 | - | assert.Contains(t, body, `<p class="dim">21 tags</p>`) | 287 | + | assert.Contains(t, body, `<a class="all-tags" href="/many/refs">21 tags</a>`) |
| 286 | } | 288 | } | ||