detached 79e542a

feat(web): even home cards, one-line topics, teal accent (#66)

commit
79e542a4b7a364f1bbadb0b42151ab0ab4d836b4
author
brice · · 2026-10-18 14:22 UTC
committer
bnema · · 2026-10-18 14:22 UTC
parent
166837a
signature
SSH UnverifiedSHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivA The key is unknown
12 files · +626 -365
M internal/adapters/web/components.templ +18 -15
@@ -54,14 +54,18 @@ templ HomeRowView(r HomeRow) {
5454 <p class="repo-desc mute">no description</p>
5555 }
5656 if len(r.Topics) > 0 {
57- <ul class="repo-topics" aria-label={ "Topics of " + r.Name }>
57+ <ul class="repo-topics" aria-label={ "Topics of " + r.Name } data-fit>
5858 for _, t := range r.Topics {
5959 <li>{ t }</li>
6060 }
6161 </ul>
62+ } else {
63+ <div class="repo-topics" aria-hidden="true"></div>
6264 }
6365 if r.Stats != nil && r.Stats.Pulse != nil {
6466 @pulse(r.Stats.Pulse)
67+ } else {
68+ <div class="repo-pulse" aria-hidden="true"></div>
6569 }
6670 </div>
6771 <div class="repo-head">
@@ -69,16 +73,6 @@ templ HomeRowView(r HomeRow) {
6973 if s := r.Stats; s != nil {
7074 <div><dt>commits</dt><dd>{ s.Commits }</dd></div>
7175 <div><dt>last 30 days</dt><dd>{ s.Recent }</dd></div>
72- if s.Tag != "" {
73- <div class="rs-tag">
74- <dt>latest tag</dt>
75- if s.TagHref != "" {
76- <dd><a href={ templ.SafeURL(s.TagHref) } title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</a></dd>
77- } else {
78- <dd title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</dd>
79- }
80- </div>
81- }
8276 }
8377 <div>
8478 <dt>last activity</dt>
@@ -88,6 +82,16 @@ templ HomeRowView(r HomeRow) {
8882 <dd class="mute">never</dd>
8983 }
9084 </div>
85+ if s := r.Stats; s != nil && s.Tag != "" {
86+ <div class="rs-tag">
87+ <dt>last tag</dt>
88+ if s.TagHref != "" {
89+ <dd><a href={ templ.SafeURL(s.TagHref) } title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</a></dd>
90+ } else {
91+ <dd title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</dd>
92+ }
93+ </div>
94+ }
9195 </dl>
9296 if r.CI != nil {
9397 <a class="repo-ci" href={ templ.SafeURL(r.CI.Href) } title={ r.CI.Title }>
@@ -119,8 +123,9 @@ templ HomeRowView(r HomeRow) {
119123 }
120124
121125 // pulse is the activity curve of a home row: a smooth line over a fading
122-// area on a faint grid, and a dot at today that beats while the repository
123-// is active. The SVG scales evenly with its column, so the dot stays round.
126+// area on a faint grid, and a dot at today, in colour while the repository
127+// is active. Nothing moves: the page stays idle. The SVG scales evenly with
128+// its column, so the dot stays round.
124129 templ pulse(h *Heartbeat) {
125130 <figure class="repo-pulse" data-live?={ h.Live }>
126131 <svg viewBox="0 0 600 64" role="img" aria-label={ h.Label } focusable="false">
@@ -133,10 +138,8 @@ templ pulse(h *Heartbeat) {
133138 <path class="pulse-grid" d="M0 21.5 H600 M0 42.5 H600 M0 63.5 H600" vector-effect="non-scaling-stroke"></path>
134139 <path class="pulse-area" d={ h.Area } fill={ "url(#" + h.GradID + ")" }></path>
135140 <path class="pulse-line" d={ h.Line } vector-effect="non-scaling-stroke"></path>
136- <circle class="pulse-ring" cx={ h.X } cy={ h.Y } r="4"></circle>
137141 <circle class="pulse-dot" cx={ h.X } cy={ h.Y } r="3.5"></circle>
138142 </svg>
139- <figcaption class="pulse-cap" aria-hidden="true"><span>90 days ago</span><span>today</span></figcaption>
140143 </figure>
141144 }
142145
@@ -54,14 +54,18 @@ templ HomeRowView(r HomeRow) {
54 <p class="repo-desc mute">no description</p>54 <p class="repo-desc mute">no description</p>
55 }55 }
56 if len(r.Topics) > 0 {56 if len(r.Topics) > 0 {
57- <ul class="repo-topics" aria-label={ "Topics of " + r.Name }>57+ <ul class="repo-topics" aria-label={ "Topics of " + r.Name } data-fit>
58 for _, t := range r.Topics {58 for _, t := range r.Topics {
59 <li>{ t }</li>59 <li>{ t }</li>
60 }60 }
61 </ul>61 </ul>
62+ } else {
63+ <div class="repo-topics" aria-hidden="true"></div>
62 }64 }
63 if r.Stats != nil && r.Stats.Pulse != nil {65 if r.Stats != nil && r.Stats.Pulse != nil {
64 @pulse(r.Stats.Pulse)66 @pulse(r.Stats.Pulse)
67+ } else {
68+ <div class="repo-pulse" aria-hidden="true"></div>
65 }69 }
66 </div>70 </div>
67 <div class="repo-head">71 <div class="repo-head">
@@ -69,16 +73,6 @@ templ HomeRowView(r HomeRow) {
69 if s := r.Stats; s != nil {73 if s := r.Stats; s != nil {
70 <div><dt>commits</dt><dd>{ s.Commits }</dd></div>74 <div><dt>commits</dt><dd>{ s.Commits }</dd></div>
71 <div><dt>last 30 days</dt><dd>{ s.Recent }</dd></div>75 <div><dt>last 30 days</dt><dd>{ s.Recent }</dd></div>
72- if s.Tag != "" {
73- <div class="rs-tag">
74- <dt>latest tag</dt>
75- if s.TagHref != "" {
76- <dd><a href={ templ.SafeURL(s.TagHref) } title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</a></dd>
77- } else {
78- <dd title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</dd>
79- }
80- </div>
81- }
82 }76 }
83 <div>77 <div>
84 <dt>last activity</dt>78 <dt>last activity</dt>
@@ -88,6 +82,16 @@ templ HomeRowView(r HomeRow) {
88 <dd class="mute">never</dd>82 <dd class="mute">never</dd>
89 }83 }
90 </div>84 </div>
85+ if s := r.Stats; s != nil && s.Tag != "" {
86+ <div class="rs-tag">
87+ <dt>last tag</dt>
88+ if s.TagHref != "" {
89+ <dd><a href={ templ.SafeURL(s.TagHref) } title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</a></dd>
90+ } else {
91+ <dd title={ s.Tag + " · made " + s.TagWhen }>{ s.Tag }</dd>
92+ }
93+ </div>
94+ }
91 </dl>95 </dl>
92 if r.CI != nil {96 if r.CI != nil {
93 <a class="repo-ci" href={ templ.SafeURL(r.CI.Href) } title={ r.CI.Title }>97 <a class="repo-ci" href={ templ.SafeURL(r.CI.Href) } title={ r.CI.Title }>
@@ -119,8 +123,9 @@ templ HomeRowView(r HomeRow) {
119 }123 }
120 124
121 // pulse is the activity curve of a home row: a smooth line over a fading125 // pulse is the activity curve of a home row: a smooth line over a fading
122-// area on a faint grid, and a dot at today that beats while the repository126+// area on a faint grid, and a dot at today, in colour while the repository
123-// is active. The SVG scales evenly with its column, so the dot stays round.127+// is active. Nothing moves: the page stays idle. The SVG scales evenly with
128+// its column, so the dot stays round.
124 templ pulse(h *Heartbeat) {129 templ pulse(h *Heartbeat) {
125 <figure class="repo-pulse" data-live?={ h.Live }>130 <figure class="repo-pulse" data-live?={ h.Live }>
126 <svg viewBox="0 0 600 64" role="img" aria-label={ h.Label } focusable="false">131 <svg viewBox="0 0 600 64" role="img" aria-label={ h.Label } focusable="false">
@@ -133,10 +138,8 @@ templ pulse(h *Heartbeat) {
133 <path class="pulse-grid" d="M0 21.5 H600 M0 42.5 H600 M0 63.5 H600" vector-effect="non-scaling-stroke"></path>138 <path class="pulse-grid" d="M0 21.5 H600 M0 42.5 H600 M0 63.5 H600" vector-effect="non-scaling-stroke"></path>
134 <path class="pulse-area" d={ h.Area } fill={ "url(#" + h.GradID + ")" }></path>139 <path class="pulse-area" d={ h.Area } fill={ "url(#" + h.GradID + ")" }></path>
135 <path class="pulse-line" d={ h.Line } vector-effect="non-scaling-stroke"></path>140 <path class="pulse-line" d={ h.Line } vector-effect="non-scaling-stroke"></path>
136- <circle class="pulse-ring" cx={ h.X } cy={ h.Y } r="4"></circle>
137 <circle class="pulse-dot" cx={ h.X } cy={ h.Y } r="3.5"></circle>141 <circle class="pulse-dot" cx={ h.X } cy={ h.Y } r="3.5"></circle>
138 </svg>142 </svg>
139- <figcaption class="pulse-cap" aria-hidden="true"><span>90 days ago</span><span>today</span></figcaption>
140 </figure>143 </figure>
141 }144 }
142 145
M internal/adapters/web/components_templ.go +295 -306
@@ -279,7 +279,7 @@ func HomeRowView(r HomeRow) templ.Component {
279279 if templ_7745c5c3_Err != nil {
280280 return templ_7745c5c3_Err
281281 }
282- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
282+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" data-fit>")
283283 if templ_7745c5c3_Err != nil {
284284 return templ_7745c5c3_Err
285285 }
@@ -306,234 +306,248 @@ func HomeRowView(r HomeRow) templ.Component {
306306 if templ_7745c5c3_Err != nil {
307307 return templ_7745c5c3_Err
308308 }
309+ } else {
310+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"repo-topics\" aria-hidden=\"true\"></div>")
311+ if templ_7745c5c3_Err != nil {
312+ return templ_7745c5c3_Err
313+ }
309314 }
310315 if r.Stats != nil && r.Stats.Pulse != nil {
311316 templ_7745c5c3_Err = pulse(r.Stats.Pulse).Render(ctx, templ_7745c5c3_Buffer)
312317 if templ_7745c5c3_Err != nil {
313318 return templ_7745c5c3_Err
314319 }
320+ } else {
321+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"repo-pulse\" aria-hidden=\"true\"></div>")
322+ if templ_7745c5c3_Err != nil {
323+ return templ_7745c5c3_Err
324+ }
315325 }
316- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</div><div class=\"repo-head\"><dl class=\"repo-stats\" aria-label=\"")
326+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</div><div class=\"repo-head\"><dl class=\"repo-stats\" aria-label=\"")
317327 if templ_7745c5c3_Err != nil {
318328 return templ_7745c5c3_Err
319329 }
320330 var templ_7745c5c3_Var14 string
321331 templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue("Figures of " + r.Name)
322332 if templ_7745c5c3_Err != nil {
323- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 68, Col: 61}
333+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 72, Col: 61}
324334 }
325335 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
326336 if templ_7745c5c3_Err != nil {
327337 return templ_7745c5c3_Err
328338 }
329- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
339+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\">")
330340 if templ_7745c5c3_Err != nil {
331341 return templ_7745c5c3_Err
332342 }
333343 if s := r.Stats; s != nil {
334- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<div><dt>commits</dt><dd>")
344+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div><dt>commits</dt><dd>")
335345 if templ_7745c5c3_Err != nil {
336346 return templ_7745c5c3_Err
337347 }
338348 var templ_7745c5c3_Var15 string
339349 templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(s.Commits)
340350 if templ_7745c5c3_Err != nil {
341- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 70, Col: 41}
351+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 74, Col: 41}
342352 }
343353 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
344354 if templ_7745c5c3_Err != nil {
345355 return templ_7745c5c3_Err
346356 }
347- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</dd></div><div><dt>last 30 days</dt><dd>")
357+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</dd></div><div><dt>last 30 days</dt><dd>")
348358 if templ_7745c5c3_Err != nil {
349359 return templ_7745c5c3_Err
350360 }
351361 var templ_7745c5c3_Var16 string
352362 templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(s.Recent)
353363 if templ_7745c5c3_Err != nil {
354- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 71, Col: 45}
364+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 75, Col: 45}
355365 }
356366 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
357367 if templ_7745c5c3_Err != nil {
358368 return templ_7745c5c3_Err
359369 }
360- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</dd></div>")
370+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</dd></div>")
361371 if templ_7745c5c3_Err != nil {
362372 return templ_7745c5c3_Err
363373 }
364- if s.Tag != "" {
365- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"rs-tag\"><dt>latest tag</dt>")
366- if templ_7745c5c3_Err != nil {
367- return templ_7745c5c3_Err
368- }
369- if s.TagHref != "" {
370- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<dd><a href=\"")
371- if templ_7745c5c3_Err != nil {
372- return templ_7745c5c3_Err
373- }
374- var templ_7745c5c3_Var17 templ.SafeURL
375- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.TagHref))
376- if templ_7745c5c3_Err != nil {
377- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 76, Col: 46}
378- }
379- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
380- if templ_7745c5c3_Err != nil {
381- return templ_7745c5c3_Err
382- }
383- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" title=\"")
384- if templ_7745c5c3_Err != nil {
385- return templ_7745c5c3_Err
386- }
387- var templ_7745c5c3_Var18 string
388- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
389- if templ_7745c5c3_Err != nil {
390- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 76, Col: 88}
391- }
392- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
393- if templ_7745c5c3_Err != nil {
394- return templ_7745c5c3_Err
395- }
396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\">")
397- if templ_7745c5c3_Err != nil {
398- return templ_7745c5c3_Err
399- }
400- var templ_7745c5c3_Var19 string
401- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
402- if templ_7745c5c3_Err != nil {
403- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 76, Col: 98}
404- }
405- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
406- if templ_7745c5c3_Err != nil {
407- return templ_7745c5c3_Err
408- }
409- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</a></dd>")
410- if templ_7745c5c3_Err != nil {
411- return templ_7745c5c3_Err
412- }
413- } else {
414- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<dd title=\"")
415- if templ_7745c5c3_Err != nil {
416- return templ_7745c5c3_Err
417- }
418- var templ_7745c5c3_Var20 string
419- templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
420- if templ_7745c5c3_Err != nil {
421- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 78, Col: 51}
422- }
423- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20)
424- if templ_7745c5c3_Err != nil {
425- return templ_7745c5c3_Err
426- }
427- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\">")
428- if templ_7745c5c3_Err != nil {
429- return templ_7745c5c3_Err
430- }
431- var templ_7745c5c3_Var21 string
432- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
433- if templ_7745c5c3_Err != nil {
434- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 78, Col: 61}
435- }
436- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
437- if templ_7745c5c3_Err != nil {
438- return templ_7745c5c3_Err
439- }
440- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</dd>")
441- if templ_7745c5c3_Err != nil {
442- return templ_7745c5c3_Err
443- }
444- }
445- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div>")
446- if templ_7745c5c3_Err != nil {
447- return templ_7745c5c3_Err
448- }
449- }
450374 }
451- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div><dt>last activity</dt>")
375+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div><dt>last activity</dt>")
452376 if templ_7745c5c3_Err != nil {
453377 return templ_7745c5c3_Err
454378 }
455379 if r.ActiveAt != "" {
456- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<dd><time datetime=\"")
380+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<dd><time datetime=\"")
457381 if templ_7745c5c3_Err != nil {
458382 return templ_7745c5c3_Err
459383 }
460- var templ_7745c5c3_Var22 string
461- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)
384+ var templ_7745c5c3_Var17 string
385+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)
462386 if templ_7745c5c3_Err != nil {
463- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 86, Col: 37}
387+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 37}
464388 }
465- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22)
389+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
466390 if templ_7745c5c3_Err != nil {
467391 return templ_7745c5c3_Err
468392 }
469- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" title=\"")
393+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" title=\"")
470394 if templ_7745c5c3_Err != nil {
471395 return templ_7745c5c3_Err
472396 }
473- var templ_7745c5c3_Var23 string
474- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)
397+ var templ_7745c5c3_Var18 string
398+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)
475399 if templ_7745c5c3_Err != nil {
476- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 86, Col: 58}
400+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 58}
477401 }
478- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
402+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
479403 if templ_7745c5c3_Err != nil {
480404 return templ_7745c5c3_Err
481405 }
482- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\">")
406+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\">")
483407 if templ_7745c5c3_Err != nil {
484408 return templ_7745c5c3_Err
485409 }
486- var templ_7745c5c3_Var24 string
487- templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(r.Active)
410+ var templ_7745c5c3_Var19 string
411+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(r.Active)
488412 if templ_7745c5c3_Err != nil {
489- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 86, Col: 71}
413+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 71}
490414 }
491- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
415+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
492416 if templ_7745c5c3_Err != nil {
493417 return templ_7745c5c3_Err
494418 }
495- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</time></dd>")
419+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</time></dd>")
496420 if templ_7745c5c3_Err != nil {
497421 return templ_7745c5c3_Err
498422 }
499423 } else {
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<dd class=\"mute\">never</dd>")
424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<dd class=\"mute\">never</dd>")
501425 if templ_7745c5c3_Err != nil {
502426 return templ_7745c5c3_Err
503427 }
504428 }
505- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</div></dl>")
429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div>")
430+ if templ_7745c5c3_Err != nil {
431+ return templ_7745c5c3_Err
432+ }
433+ if s := r.Stats; s != nil && s.Tag != "" {
434+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div class=\"rs-tag\"><dt>last tag</dt>")
435+ if templ_7745c5c3_Err != nil {
436+ return templ_7745c5c3_Err
437+ }
438+ if s.TagHref != "" {
439+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<dd><a href=\"")
440+ if templ_7745c5c3_Err != nil {
441+ return templ_7745c5c3_Err
442+ }
443+ var templ_7745c5c3_Var20 templ.SafeURL
444+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.TagHref))
445+ if templ_7745c5c3_Err != nil {
446+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 45}
447+ }
448+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
449+ if templ_7745c5c3_Err != nil {
450+ return templ_7745c5c3_Err
451+ }
452+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" title=\"")
453+ if templ_7745c5c3_Err != nil {
454+ return templ_7745c5c3_Err
455+ }
456+ var templ_7745c5c3_Var21 string
457+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
458+ if templ_7745c5c3_Err != nil {
459+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 87}
460+ }
461+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
462+ if templ_7745c5c3_Err != nil {
463+ return templ_7745c5c3_Err
464+ }
465+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\">")
466+ if templ_7745c5c3_Err != nil {
467+ return templ_7745c5c3_Err
468+ }
469+ var templ_7745c5c3_Var22 string
470+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
471+ if templ_7745c5c3_Err != nil {
472+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 97}
473+ }
474+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
475+ if templ_7745c5c3_Err != nil {
476+ return templ_7745c5c3_Err
477+ }
478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</a></dd>")
479+ if templ_7745c5c3_Err != nil {
480+ return templ_7745c5c3_Err
481+ }
482+ } else {
483+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<dd title=\"")
484+ if templ_7745c5c3_Err != nil {
485+ return templ_7745c5c3_Err
486+ }
487+ var templ_7745c5c3_Var23 string
488+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
489+ if templ_7745c5c3_Err != nil {
490+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 91, Col: 50}
491+ }
492+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
493+ if templ_7745c5c3_Err != nil {
494+ return templ_7745c5c3_Err
495+ }
496+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "\">")
497+ if templ_7745c5c3_Err != nil {
498+ return templ_7745c5c3_Err
499+ }
500+ var templ_7745c5c3_Var24 string
501+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
502+ if templ_7745c5c3_Err != nil {
503+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 91, Col: 60}
504+ }
505+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
506+ if templ_7745c5c3_Err != nil {
507+ return templ_7745c5c3_Err
508+ }
509+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</dd>")
510+ if templ_7745c5c3_Err != nil {
511+ return templ_7745c5c3_Err
512+ }
513+ }
514+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</div>")
515+ if templ_7745c5c3_Err != nil {
516+ return templ_7745c5c3_Err
517+ }
518+ }
519+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</dl>")
506520 if templ_7745c5c3_Err != nil {
507521 return templ_7745c5c3_Err
508522 }
509523 if r.CI != nil {
510- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<a class=\"repo-ci\" href=\"")
524+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<a class=\"repo-ci\" href=\"")
511525 if templ_7745c5c3_Err != nil {
512526 return templ_7745c5c3_Err
513527 }
514528 var templ_7745c5c3_Var25 templ.SafeURL
515529 templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CI.Href))
516530 if templ_7745c5c3_Err != nil {
517- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 93, Col: 54}
531+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 97, Col: 54}
518532 }
519533 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
520534 if templ_7745c5c3_Err != nil {
521535 return templ_7745c5c3_Err
522536 }
523- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\" title=\"")
537+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" title=\"")
524538 if templ_7745c5c3_Err != nil {
525539 return templ_7745c5c3_Err
526540 }
527541 var templ_7745c5c3_Var26 string
528542 templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CI.Title)
529543 if templ_7745c5c3_Err != nil {
530- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 93, Col: 75}
544+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 97, Col: 75}
531545 }
532546 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
533547 if templ_7745c5c3_Err != nil {
534548 return templ_7745c5c3_Err
535549 }
536- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\">")
550+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\">")
537551 if templ_7745c5c3_Err != nil {
538552 return templ_7745c5c3_Err
539553 }
@@ -542,7 +556,7 @@ func HomeRowView(r HomeRow) templ.Component {
542556 if templ_7745c5c3_Err != nil {
543557 return templ_7745c5c3_Err
544558 }
545- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<span class=\"")
559+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<span class=\"")
546560 if templ_7745c5c3_Err != nil {
547561 return templ_7745c5c3_Err
548562 }
@@ -555,174 +569,174 @@ func HomeRowView(r HomeRow) templ.Component {
555569 if templ_7745c5c3_Err != nil {
556570 return templ_7745c5c3_Err
557571 }
558- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" aria-hidden=\"true\"></span> <span class=\"sr\">CI: </span>")
572+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "\" aria-hidden=\"true\"></span> <span class=\"sr\">CI: </span>")
559573 if templ_7745c5c3_Err != nil {
560574 return templ_7745c5c3_Err
561575 }
562576 var templ_7745c5c3_Var29 string
563577 templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(r.CI.Label)
564578 if templ_7745c5c3_Err != nil {
565- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 95, Col: 45}
579+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 99, Col: 45}
566580 }
567581 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
568582 if templ_7745c5c3_Err != nil {
569583 return templ_7745c5c3_Err
570584 }
571- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</a>")
585+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</a>")
572586 if templ_7745c5c3_Err != nil {
573587 return templ_7745c5c3_Err
574588 }
575589 }
576- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</div>")
590+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</div>")
577591 if templ_7745c5c3_Err != nil {
578592 return templ_7745c5c3_Err
579593 }
580594 if len(r.Commits) > 0 {
581- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<ul class=\"repo-commits\"")
595+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<ul class=\"repo-commits\"")
582596 if templ_7745c5c3_Err != nil {
583597 return templ_7745c5c3_Err
584598 }
585599 if len(r.Commits) == homeRecent {
586- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " data-full")
600+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " data-full")
587601 if templ_7745c5c3_Err != nil {
588602 return templ_7745c5c3_Err
589603 }
590604 }
591- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, " aria-label=\"")
605+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " aria-label=\"")
592606 if templ_7745c5c3_Err != nil {
593607 return templ_7745c5c3_Err
594608 }
595609 var templ_7745c5c3_Var30 string
596610 templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue("Recent commits of " + r.Name)
597611 if templ_7745c5c3_Err != nil {
598- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 103, Col: 46}
612+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 107, Col: 46}
599613 }
600614 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30)
601615 if templ_7745c5c3_Err != nil {
602616 return templ_7745c5c3_Err
603617 }
604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\">")
618+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "\">")
605619 if templ_7745c5c3_Err != nil {
606620 return templ_7745c5c3_Err
607621 }
608622 for _, c := range r.Commits {
609- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<li><a class=\"rc-sha\" href=\"")
623+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<li><a class=\"rc-sha\" href=\"")
610624 if templ_7745c5c3_Err != nil {
611625 return templ_7745c5c3_Err
612626 }
613627 var templ_7745c5c3_Var31 templ.SafeURL
614628 templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
615629 if templ_7745c5c3_Err != nil {
616- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 107, Col: 53}
630+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 111, Col: 53}
617631 }
618632 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
619633 if templ_7745c5c3_Err != nil {
620634 return templ_7745c5c3_Err
621635 }
622- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "\">")
636+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\">")
623637 if templ_7745c5c3_Err != nil {
624638 return templ_7745c5c3_Err
625639 }
626640 var templ_7745c5c3_Var32 string
627641 templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
628642 if templ_7745c5c3_Err != nil {
629- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 107, Col: 65}
643+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 111, Col: 65}
630644 }
631645 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
632646 if templ_7745c5c3_Err != nil {
633647 return templ_7745c5c3_Err
634648 }
635- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</a> <span class=\"rc-subject\" title=\"")
649+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</a> <span class=\"rc-subject\" title=\"")
636650 if templ_7745c5c3_Err != nil {
637651 return templ_7745c5c3_Err
638652 }
639653 var templ_7745c5c3_Var33 string
640654 templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Subject)
641655 if templ_7745c5c3_Err != nil {
642- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 108, Col: 48}
656+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 112, Col: 48}
643657 }
644658 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
645659 if templ_7745c5c3_Err != nil {
646660 return templ_7745c5c3_Err
647661 }
648- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\">")
662+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "\">")
649663 if templ_7745c5c3_Err != nil {
650664 return templ_7745c5c3_Err
651665 }
652666 var templ_7745c5c3_Var34 string
653667 templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
654668 if templ_7745c5c3_Err != nil {
655- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 108, Col: 62}
669+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 112, Col: 62}
656670 }
657671 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
658672 if templ_7745c5c3_Err != nil {
659673 return templ_7745c5c3_Err
660674 }
661- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</span> ")
675+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</span> ")
662676 if templ_7745c5c3_Err != nil {
663677 return templ_7745c5c3_Err
664678 }
665679 if c.WhenAt != "" {
666- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<time class=\"dim\" datetime=\"")
680+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<time class=\"dim\" datetime=\"")
667681 if templ_7745c5c3_Err != nil {
668682 return templ_7745c5c3_Err
669683 }
670684 var templ_7745c5c3_Var35 string
671685 templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
672686 if templ_7745c5c3_Err != nil {
673- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 110, Col: 44}
687+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 114, Col: 44}
674688 }
675689 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
676690 if templ_7745c5c3_Err != nil {
677691 return templ_7745c5c3_Err
678692 }
679- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "\" title=\"")
693+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "\" title=\"")
680694 if templ_7745c5c3_Err != nil {
681695 return templ_7745c5c3_Err
682696 }
683697 var templ_7745c5c3_Var36 string
684698 templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
685699 if templ_7745c5c3_Err != nil {
686- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 110, Col: 63}
700+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 114, Col: 63}
687701 }
688702 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
689703 if templ_7745c5c3_Err != nil {
690704 return templ_7745c5c3_Err
691705 }
692- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "\">")
706+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\">")
693707 if templ_7745c5c3_Err != nil {
694708 return templ_7745c5c3_Err
695709 }
696710 var templ_7745c5c3_Var37 string
697711 templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
698712 if templ_7745c5c3_Err != nil {
699- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 110, Col: 74}
713+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 114, Col: 74}
700714 }
701715 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
702716 if templ_7745c5c3_Err != nil {
703717 return templ_7745c5c3_Err
704718 }
705- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "</time>")
719+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</time>")
706720 if templ_7745c5c3_Err != nil {
707721 return templ_7745c5c3_Err
708722 }
709723 }
710- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</li>")
724+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</li>")
711725 if templ_7745c5c3_Err != nil {
712726 return templ_7745c5c3_Err
713727 }
714728 }
715- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</ul>")
729+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</ul>")
716730 if templ_7745c5c3_Err != nil {
717731 return templ_7745c5c3_Err
718732 }
719733 } else if r.NoCommits {
720- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<p class=\"repo-commits mute\">no commits yet</p>")
734+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<p class=\"repo-commits mute\">no commits yet</p>")
721735 if templ_7745c5c3_Err != nil {
722736 return templ_7745c5c3_Err
723737 }
724738 }
725- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</li>")
739+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</li>")
726740 if templ_7745c5c3_Err != nil {
727741 return templ_7745c5c3_Err
728742 }
@@ -731,8 +745,9 @@ func HomeRowView(r HomeRow) templ.Component {
731745 }
732746
733747 // pulse is the activity curve of a home row: a smooth line over a fading
734-// area on a faint grid, and a dot at today that beats while the repository
735-// is active. The SVG scales evenly with its column, so the dot stays round.
748+// area on a faint grid, and a dot at today, in colour while the repository
749+// is active. Nothing moves: the page stays idle. The SVG scales evenly with
750+// its column, so the dot stays round.
736751 func pulse(h *Heartbeat) templ.Component {
737752 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
738753 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -754,134 +769,108 @@ func pulse(h *Heartbeat) templ.Component {
754769 templ_7745c5c3_Var38 = templ.NopComponent
755770 }
756771 ctx = templ.ClearChildren(ctx)
757- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "<figure class=\"repo-pulse\"")
772+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<figure class=\"repo-pulse\"")
758773 if templ_7745c5c3_Err != nil {
759774 return templ_7745c5c3_Err
760775 }
761776 if h.Live {
762- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, " data-live")
777+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, " data-live")
763778 if templ_7745c5c3_Err != nil {
764779 return templ_7745c5c3_Err
765780 }
766781 }
767- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "><svg viewBox=\"0 0 600 64\" role=\"img\" aria-label=\"")
782+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "><svg viewBox=\"0 0 600 64\" role=\"img\" aria-label=\"")
768783 if templ_7745c5c3_Err != nil {
769784 return templ_7745c5c3_Err
770785 }
771786 var templ_7745c5c3_Var39 string
772787 templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Label)
773788 if templ_7745c5c3_Err != nil {
774- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 59}
789+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 131, Col: 59}
775790 }
776791 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var39)
777792 if templ_7745c5c3_Err != nil {
778793 return templ_7745c5c3_Err
779794 }
780- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "\" focusable=\"false\"><defs><linearGradient id=\"")
795+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\" focusable=\"false\"><defs><linearGradient id=\"")
781796 if templ_7745c5c3_Err != nil {
782797 return templ_7745c5c3_Err
783798 }
784799 var templ_7745c5c3_Var40 string
785800 templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.GradID)
786801 if templ_7745c5c3_Err != nil {
787- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 128, Col: 33}
802+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 133, Col: 33}
788803 }
789804 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)
790805 if templ_7745c5c3_Err != nil {
791806 return templ_7745c5c3_Err
792807 }
793- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop class=\"pulse-stop-top\" offset=\"0\"></stop> <stop class=\"pulse-stop-bottom\" offset=\"1\"></stop></linearGradient></defs> <path class=\"pulse-grid\" d=\"M0 21.5 H600 M0 42.5 H600 M0 63.5 H600\" vector-effect=\"non-scaling-stroke\"></path> <path class=\"pulse-area\" d=\"")
808+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop class=\"pulse-stop-top\" offset=\"0\"></stop> <stop class=\"pulse-stop-bottom\" offset=\"1\"></stop></linearGradient></defs> <path class=\"pulse-grid\" d=\"M0 21.5 H600 M0 42.5 H600 M0 63.5 H600\" vector-effect=\"non-scaling-stroke\"></path> <path class=\"pulse-area\" d=\"")
794809 if templ_7745c5c3_Err != nil {
795810 return templ_7745c5c3_Err
796811 }
797812 var templ_7745c5c3_Var41 string
798813 templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Area)
799814 if templ_7745c5c3_Err != nil {
800- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 134, Col: 38}
815+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 139, Col: 38}
801816 }
802817 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)
803818 if templ_7745c5c3_Err != nil {
804819 return templ_7745c5c3_Err
805820 }
806- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\" fill=\"")
821+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "\" fill=\"")
807822 if templ_7745c5c3_Err != nil {
808823 return templ_7745c5c3_Err
809824 }
810825 var templ_7745c5c3_Var42 string
811826 templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue("url(#" + h.GradID + ")")
812827 if templ_7745c5c3_Err != nil {
813- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 134, Col: 72}
828+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 139, Col: 72}
814829 }
815830 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)
816831 if templ_7745c5c3_Err != nil {
817832 return templ_7745c5c3_Err
818833 }
819- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\"></path> <path class=\"pulse-line\" d=\"")
834+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\"></path> <path class=\"pulse-line\" d=\"")
820835 if templ_7745c5c3_Err != nil {
821836 return templ_7745c5c3_Err
822837 }
823838 var templ_7745c5c3_Var43 string
824839 templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Line)
825840 if templ_7745c5c3_Err != nil {
826- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 135, Col: 38}
841+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 140, Col: 38}
827842 }
828843 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var43)
829844 if templ_7745c5c3_Err != nil {
830845 return templ_7745c5c3_Err
831846 }
832- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" vector-effect=\"non-scaling-stroke\"></path> <circle class=\"pulse-ring\" cx=\"")
847+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\" vector-effect=\"non-scaling-stroke\"></path> <circle class=\"pulse-dot\" cx=\"")
833848 if templ_7745c5c3_Err != nil {
834849 return templ_7745c5c3_Err
835850 }
836851 var templ_7745c5c3_Var44 string
837852 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.X)
838853 if templ_7745c5c3_Err != nil {
839- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 136, Col: 38}
854+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 141, Col: 37}
840855 }
841856 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var44)
842857 if templ_7745c5c3_Err != nil {
843858 return templ_7745c5c3_Err
844859 }
845- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "\" cy=\"")
860+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" cy=\"")
846861 if templ_7745c5c3_Err != nil {
847862 return templ_7745c5c3_Err
848863 }
849864 var templ_7745c5c3_Var45 string
850865 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Y)
851866 if templ_7745c5c3_Err != nil {
852- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 136, Col: 49}
867+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 141, Col: 48}
853868 }
854869 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var45)
855870 if templ_7745c5c3_Err != nil {
856871 return templ_7745c5c3_Err
857872 }
858- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\" r=\"4\"></circle> <circle class=\"pulse-dot\" cx=\"")
859- if templ_7745c5c3_Err != nil {
860- return templ_7745c5c3_Err
861- }
862- var templ_7745c5c3_Var46 string
863- templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.X)
864- if templ_7745c5c3_Err != nil {
865- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 137, Col: 37}
866- }
867- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var46)
868- if templ_7745c5c3_Err != nil {
869- return templ_7745c5c3_Err
870- }
871- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\" cy=\"")
872- if templ_7745c5c3_Err != nil {
873- return templ_7745c5c3_Err
874- }
875- var templ_7745c5c3_Var47 string
876- templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Y)
877- if templ_7745c5c3_Err != nil {
878- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 137, Col: 48}
879- }
880- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var47)
881- if templ_7745c5c3_Err != nil {
882- return templ_7745c5c3_Err
883- }
884- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" r=\"3.5\"></circle></svg><figcaption class=\"pulse-cap\" aria-hidden=\"true\"><span>90 days ago</span><span>today</span></figcaption></figure>")
873+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\" r=\"3.5\"></circle></svg></figure>")
885874 if templ_7745c5c3_Err != nil {
886875 return templ_7745c5c3_Err
887876 }
@@ -906,66 +895,66 @@ func Outline(headings []Heading) templ.Component {
906895 }()
907896 }
908897 ctx = templ.InitializeContext(ctx)
909- templ_7745c5c3_Var48 := templ.GetChildren(ctx)
910- if templ_7745c5c3_Var48 == nil {
911- templ_7745c5c3_Var48 = templ.NopComponent
898+ templ_7745c5c3_Var46 := templ.GetChildren(ctx)
899+ if templ_7745c5c3_Var46 == nil {
900+ templ_7745c5c3_Var46 = templ.NopComponent
912901 }
913902 ctx = templ.ClearChildren(ctx)
914- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<nav class=\"outline\" aria-label=\"Outline\">")
903+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<nav class=\"outline\" aria-label=\"Outline\">")
915904 if templ_7745c5c3_Err != nil {
916905 return templ_7745c5c3_Err
917906 }
918907 for _, h := range headings {
919- var templ_7745c5c3_Var49 = []any{"l" + itoa(h.Level)}
920- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var49...)
908+ var templ_7745c5c3_Var47 = []any{"l" + itoa(h.Level)}
909+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var47...)
921910 if templ_7745c5c3_Err != nil {
922911 return templ_7745c5c3_Err
923912 }
924- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<a class=\"")
913+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<a class=\"")
925914 if templ_7745c5c3_Err != nil {
926915 return templ_7745c5c3_Err
927916 }
928- var templ_7745c5c3_Var50 string
929- templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var49).String())
917+ var templ_7745c5c3_Var48 string
918+ templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var47).String())
930919 if templ_7745c5c3_Err != nil {
931920 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 1, Col: 0}
932921 }
933- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var50)
922+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var48)
934923 if templ_7745c5c3_Err != nil {
935924 return templ_7745c5c3_Err
936925 }
937- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "\" href=\"")
926+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" href=\"")
938927 if templ_7745c5c3_Err != nil {
939928 return templ_7745c5c3_Err
940929 }
941- var templ_7745c5c3_Var51 templ.SafeURL
942- templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))
930+ var templ_7745c5c3_Var49 templ.SafeURL
931+ templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))
943932 if templ_7745c5c3_Err != nil {
944- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 147, Col: 68}
933+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 150, Col: 68}
945934 }
946- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
935+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
947936 if templ_7745c5c3_Err != nil {
948937 return templ_7745c5c3_Err
949938 }
950- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\">")
939+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "\">")
951940 if templ_7745c5c3_Err != nil {
952941 return templ_7745c5c3_Err
953942 }
954- var templ_7745c5c3_Var52 string
955- templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)
943+ var templ_7745c5c3_Var50 string
944+ templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)
956945 if templ_7745c5c3_Err != nil {
957- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 147, Col: 79}
946+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 150, Col: 79}
958947 }
959- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
948+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
960949 if templ_7745c5c3_Err != nil {
961950 return templ_7745c5c3_Err
962951 }
963- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</a>")
952+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</a>")
964953 if templ_7745c5c3_Err != nil {
965954 return templ_7745c5c3_Err
966955 }
967956 }
968- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</nav>")
957+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</nav>")
969958 if templ_7745c5c3_Err != nil {
970959 return templ_7745c5c3_Err
971960 }
@@ -990,25 +979,25 @@ func ReadmeCard(r Readme) templ.Component {
990979 }()
991980 }
992981 ctx = templ.InitializeContext(ctx)
993- templ_7745c5c3_Var53 := templ.GetChildren(ctx)
994- if templ_7745c5c3_Var53 == nil {
995- templ_7745c5c3_Var53 = templ.NopComponent
982+ templ_7745c5c3_Var51 := templ.GetChildren(ctx)
983+ if templ_7745c5c3_Var51 == nil {
984+ templ_7745c5c3_Var51 = templ.NopComponent
996985 }
997986 ctx = templ.ClearChildren(ctx)
998- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")
987+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")
999988 if templ_7745c5c3_Err != nil {
1000989 return templ_7745c5c3_Err
1001990 }
1002- var templ_7745c5c3_Var54 string
1003- templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
991+ var templ_7745c5c3_Var52 string
992+ templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
1004993 if templ_7745c5c3_Err != nil {
1005- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 157, Col: 11}
994+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 160, Col: 11}
1006995 }
1007- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
996+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
1008997 if templ_7745c5c3_Err != nil {
1009998 return templ_7745c5c3_Err
1010999 }
1011- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</h2>")
1000+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</h2>")
10121001 if templ_7745c5c3_Err != nil {
10131002 return templ_7745c5c3_Err
10141003 }
@@ -1016,7 +1005,7 @@ func ReadmeCard(r Readme) templ.Component {
10161005 if templ_7745c5c3_Err != nil {
10171006 return templ_7745c5c3_Err
10181007 }
1019- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</section>")
1008+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</section>")
10201009 if templ_7745c5c3_Err != nil {
10211010 return templ_7745c5c3_Err
10221011 }
@@ -1042,43 +1031,43 @@ func ReadmeView(r Readme) templ.Component {
10421031 }()
10431032 }
10441033 ctx = templ.InitializeContext(ctx)
1045- templ_7745c5c3_Var55 := templ.GetChildren(ctx)
1046- if templ_7745c5c3_Var55 == nil {
1047- templ_7745c5c3_Var55 = templ.NopComponent
1034+ templ_7745c5c3_Var53 := templ.GetChildren(ctx)
1035+ if templ_7745c5c3_Var53 == nil {
1036+ templ_7745c5c3_Var53 = templ.NopComponent
10481037 }
10491038 ctx = templ.ClearChildren(ctx)
1050- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "<article class=\"prose\" aria-label=\"")
1039+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<article class=\"prose\" aria-label=\"")
10511040 if templ_7745c5c3_Err != nil {
10521041 return templ_7745c5c3_Err
10531042 }
1054- var templ_7745c5c3_Var56 string
1055- templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)
1043+ var templ_7745c5c3_Var54 string
1044+ templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)
10561045 if templ_7745c5c3_Err != nil {
1057- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 166, Col: 43}
1046+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 169, Col: 43}
10581047 }
1059- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)
1048+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54)
10601049 if templ_7745c5c3_Err != nil {
10611050 return templ_7745c5c3_Err
10621051 }
1063- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "\" hx-ignore>")
1052+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\" hx-ignore>")
10641053 if templ_7745c5c3_Err != nil {
10651054 return templ_7745c5c3_Err
10661055 }
10671056 if r.Note != "" {
1068- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<p class=\"dim\">")
1057+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<p class=\"dim\">")
10691058 if templ_7745c5c3_Err != nil {
10701059 return templ_7745c5c3_Err
10711060 }
1072- var templ_7745c5c3_Var57 string
1073- templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)
1061+ var templ_7745c5c3_Var55 string
1062+ templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)
10741063 if templ_7745c5c3_Err != nil {
1075- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 168, Col: 26}
1064+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 171, Col: 26}
10761065 }
1077- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
1066+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
10781067 if templ_7745c5c3_Err != nil {
10791068 return templ_7745c5c3_Err
10801069 }
1081- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "</p>")
1070+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</p>")
10821071 if templ_7745c5c3_Err != nil {
10831072 return templ_7745c5c3_Err
10841073 }
@@ -1088,7 +1077,7 @@ func ReadmeView(r Readme) templ.Component {
10881077 return templ_7745c5c3_Err
10891078 }
10901079 }
1091- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</article>")
1080+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "</article>")
10921081 if templ_7745c5c3_Err != nil {
10931082 return templ_7745c5c3_Err
10941083 }
@@ -1114,133 +1103,133 @@ func CommitRows(rows []CommitRow) templ.Component {
11141103 }()
11151104 }
11161105 ctx = templ.InitializeContext(ctx)
1117- templ_7745c5c3_Var58 := templ.GetChildren(ctx)
1118- if templ_7745c5c3_Var58 == nil {
1119- templ_7745c5c3_Var58 = templ.NopComponent
1106+ templ_7745c5c3_Var56 := templ.GetChildren(ctx)
1107+ if templ_7745c5c3_Var56 == nil {
1108+ templ_7745c5c3_Var56 = templ.NopComponent
11201109 }
11211110 ctx = templ.ClearChildren(ctx)
1122- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")
1111+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")
11231112 if templ_7745c5c3_Err != nil {
11241113 return templ_7745c5c3_Err
11251114 }
11261115 for _, c := range rows {
1127- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "<tr")
1116+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "<tr")
11281117 if templ_7745c5c3_Err != nil {
11291118 return templ_7745c5c3_Err
11301119 }
11311120 if c.ID != "" {
1132- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, " id=\"")
1121+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, " id=\"")
11331122 if templ_7745c5c3_Err != nil {
11341123 return templ_7745c5c3_Err
11351124 }
1136- var templ_7745c5c3_Var59 string
1137- templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)
1125+ var templ_7745c5c3_Var57 string
1126+ templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)
11381127 if templ_7745c5c3_Err != nil {
1139- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 183, Col: 15}
1128+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 186, Col: 15}
11401129 }
1141- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)
1130+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
11421131 if templ_7745c5c3_Err != nil {
11431132 return templ_7745c5c3_Err
11441133 }
1145- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "\"")
1134+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "\"")
11461135 if templ_7745c5c3_Err != nil {
11471136 return templ_7745c5c3_Err
11481137 }
11491138 }
11501139 if c.Cursor {
1151- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, " data-cursor")
1140+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, " data-cursor")
11521141 if templ_7745c5c3_Err != nil {
11531142 return templ_7745c5c3_Err
11541143 }
11551144 }
1156- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "><td class=\"c-sha mute\">")
1145+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "><td class=\"c-sha mute\">")
11571146 if templ_7745c5c3_Err != nil {
11581147 return templ_7745c5c3_Err
11591148 }
1160- var templ_7745c5c3_Var60 string
1161- templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
1149+ var templ_7745c5c3_Var58 string
1150+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
11621151 if templ_7745c5c3_Err != nil {
1163- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 187, Col: 37}
1152+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 190, Col: 37}
11641153 }
1165- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
1154+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
11661155 if templ_7745c5c3_Err != nil {
11671156 return templ_7745c5c3_Err
11681157 }
1169- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</td><td class=\"c-subject\"><a href=\"")
1158+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</td><td class=\"c-subject\"><a href=\"")
11701159 if templ_7745c5c3_Err != nil {
11711160 return templ_7745c5c3_Err
11721161 }
1173- var templ_7745c5c3_Var61 templ.SafeURL
1174- templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
1162+ var templ_7745c5c3_Var59 templ.SafeURL
1163+ templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
11751164 if templ_7745c5c3_Err != nil {
1176- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 189, Col: 38}
1165+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 192, Col: 38}
11771166 }
1178- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
1167+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
11791168 if templ_7745c5c3_Err != nil {
11801169 return templ_7745c5c3_Err
11811170 }
1182- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "\">")
1171+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">")
11831172 if templ_7745c5c3_Err != nil {
11841173 return templ_7745c5c3_Err
11851174 }
1186- var templ_7745c5c3_Var62 string
1187- templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
1175+ var templ_7745c5c3_Var60 string
1176+ templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
11881177 if templ_7745c5c3_Err != nil {
1189- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 189, Col: 52}
1178+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 192, Col: 52}
11901179 }
1191- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
1180+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
11921181 if templ_7745c5c3_Err != nil {
11931182 return templ_7745c5c3_Err
11941183 }
1195- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "</a> ")
1184+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</a> ")
11961185 if templ_7745c5c3_Err != nil {
11971186 return templ_7745c5c3_Err
11981187 }
11991188 if c.Merge {
1200- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "<span class=\"pill\">merge</span> ")
1189+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<span class=\"pill\">merge</span> ")
12011190 if templ_7745c5c3_Err != nil {
12021191 return templ_7745c5c3_Err
12031192 }
12041193 }
12051194 for _, b := range c.Branches {
1206- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<span class=\"pill\">")
1195+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "<span class=\"pill\">")
12071196 if templ_7745c5c3_Err != nil {
12081197 return templ_7745c5c3_Err
12091198 }
1210- var templ_7745c5c3_Var63 string
1211- templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(b)
1199+ var templ_7745c5c3_Var61 string
1200+ templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(b)
12121201 if templ_7745c5c3_Err != nil {
1213- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 194, Col: 29}
1202+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 197, Col: 29}
12141203 }
1215- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
1204+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
12161205 if templ_7745c5c3_Err != nil {
12171206 return templ_7745c5c3_Err
12181207 }
1219- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "</span> ")
1208+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "</span> ")
12201209 if templ_7745c5c3_Err != nil {
12211210 return templ_7745c5c3_Err
12221211 }
12231212 }
12241213 for _, t := range c.Tags {
1225- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")
1214+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")
12261215 if templ_7745c5c3_Err != nil {
12271216 return templ_7745c5c3_Err
12281217 }
1229- var templ_7745c5c3_Var64 string
1230- templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(t)
1218+ var templ_7745c5c3_Var62 string
1219+ templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(t)
12311220 if templ_7745c5c3_Err != nil {
1232- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 197, Col: 61}
1221+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 200, Col: 61}
12331222 }
1234- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64))
1223+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
12351224 if templ_7745c5c3_Err != nil {
12361225 return templ_7745c5c3_Err
12371226 }
1238- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</span>")
1227+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "</span>")
12391228 if templ_7745c5c3_Err != nil {
12401229 return templ_7745c5c3_Err
12411230 }
12421231 }
1243- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "</td><td class=\"c-ck\">")
1232+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</td><td class=\"c-ck\">")
12441233 if templ_7745c5c3_Err != nil {
12451234 return templ_7745c5c3_Err
12461235 }
@@ -1248,64 +1237,64 @@ func CommitRows(rows []CommitRow) templ.Component {
12481237 if templ_7745c5c3_Err != nil {
12491238 return templ_7745c5c3_Err
12501239 }
1251- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</td><td class=\"c-who dim\">")
1240+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</td><td class=\"c-who dim\">")
12521241 if templ_7745c5c3_Err != nil {
12531242 return templ_7745c5c3_Err
12541243 }
1255- var templ_7745c5c3_Var65 string
1256- templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
1244+ var templ_7745c5c3_Var63 string
1245+ templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
12571246 if templ_7745c5c3_Err != nil {
1258- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 201, Col: 37}
1247+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 204, Col: 37}
12591248 }
1260- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
1249+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
12611250 if templ_7745c5c3_Err != nil {
12621251 return templ_7745c5c3_Err
12631252 }
1264- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")
1253+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")
12651254 if templ_7745c5c3_Err != nil {
12661255 return templ_7745c5c3_Err
12671256 }
1268- var templ_7745c5c3_Var66 string
1269- templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
1257+ var templ_7745c5c3_Var64 string
1258+ templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
12701259 if templ_7745c5c3_Err != nil {
1271- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 202, Col: 60}
1260+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 205, Col: 60}
12721261 }
1273- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
1262+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
12741263 if templ_7745c5c3_Err != nil {
12751264 return templ_7745c5c3_Err
12761265 }
1277- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "\" title=\"")
1266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\" title=\"")
12781267 if templ_7745c5c3_Err != nil {
12791268 return templ_7745c5c3_Err
12801269 }
1281- var templ_7745c5c3_Var67 string
1282- templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
1270+ var templ_7745c5c3_Var65 string
1271+ templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
12831272 if templ_7745c5c3_Err != nil {
1284- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 202, Col: 79}
1273+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 205, Col: 79}
12851274 }
1286- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var67)
1275+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var65)
12871276 if templ_7745c5c3_Err != nil {
12881277 return templ_7745c5c3_Err
12891278 }
1290- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\">")
1279+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "\">")
12911280 if templ_7745c5c3_Err != nil {
12921281 return templ_7745c5c3_Err
12931282 }
1294- var templ_7745c5c3_Var68 string
1295- templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
1283+ var templ_7745c5c3_Var66 string
1284+ templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
12961285 if templ_7745c5c3_Err != nil {
1297- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 202, Col: 90}
1286+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 205, Col: 90}
12981287 }
1299- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
1288+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
13001289 if templ_7745c5c3_Err != nil {
13011290 return templ_7745c5c3_Err
13021291 }
1303- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</time></td></tr>")
1292+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</time></td></tr>")
13041293 if templ_7745c5c3_Err != nil {
13051294 return templ_7745c5c3_Err
13061295 }
13071296 }
1308- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</tbody></table>")
1297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</tbody></table>")
13091298 if templ_7745c5c3_Err != nil {
13101299 return templ_7745c5c3_Err
13111300 }
@@ -279,7 +279,7 @@ func HomeRowView(r HomeRow) templ.Component {
279 if templ_7745c5c3_Err != nil {279 if templ_7745c5c3_Err != nil {
280 return templ_7745c5c3_Err280 return templ_7745c5c3_Err
281 }281 }
282- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")282+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" data-fit>")
283 if templ_7745c5c3_Err != nil {283 if templ_7745c5c3_Err != nil {
284 return templ_7745c5c3_Err284 return templ_7745c5c3_Err
285 }285 }
@@ -306,234 +306,248 @@ func HomeRowView(r HomeRow) templ.Component {
306 if templ_7745c5c3_Err != nil {306 if templ_7745c5c3_Err != nil {
307 return templ_7745c5c3_Err307 return templ_7745c5c3_Err
308 }308 }
309+ } else {
310+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"repo-topics\" aria-hidden=\"true\"></div>")
311+ if templ_7745c5c3_Err != nil {
312+ return templ_7745c5c3_Err
313+ }
309 }314 }
310 if r.Stats != nil && r.Stats.Pulse != nil {315 if r.Stats != nil && r.Stats.Pulse != nil {
311 templ_7745c5c3_Err = pulse(r.Stats.Pulse).Render(ctx, templ_7745c5c3_Buffer)316 templ_7745c5c3_Err = pulse(r.Stats.Pulse).Render(ctx, templ_7745c5c3_Buffer)
312 if templ_7745c5c3_Err != nil {317 if templ_7745c5c3_Err != nil {
313 return templ_7745c5c3_Err318 return templ_7745c5c3_Err
314 }319 }
320+ } else {
321+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"repo-pulse\" aria-hidden=\"true\"></div>")
322+ if templ_7745c5c3_Err != nil {
323+ return templ_7745c5c3_Err
324+ }
315 }325 }
316- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</div><div class=\"repo-head\"><dl class=\"repo-stats\" aria-label=\"")326+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</div><div class=\"repo-head\"><dl class=\"repo-stats\" aria-label=\"")
317 if templ_7745c5c3_Err != nil {327 if templ_7745c5c3_Err != nil {
318 return templ_7745c5c3_Err328 return templ_7745c5c3_Err
319 }329 }
320 var templ_7745c5c3_Var14 string330 var templ_7745c5c3_Var14 string
321 templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue("Figures of " + r.Name)331 templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue("Figures of " + r.Name)
322 if templ_7745c5c3_Err != nil {332 if templ_7745c5c3_Err != nil {
323- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 68, Col: 61}333+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 72, Col: 61}
324 }334 }
325 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)335 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
326 if templ_7745c5c3_Err != nil {336 if templ_7745c5c3_Err != nil {
327 return templ_7745c5c3_Err337 return templ_7745c5c3_Err
328 }338 }
329- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")339+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\">")
330 if templ_7745c5c3_Err != nil {340 if templ_7745c5c3_Err != nil {
331 return templ_7745c5c3_Err341 return templ_7745c5c3_Err
332 }342 }
333 if s := r.Stats; s != nil {343 if s := r.Stats; s != nil {
334- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<div><dt>commits</dt><dd>")344+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div><dt>commits</dt><dd>")
335 if templ_7745c5c3_Err != nil {345 if templ_7745c5c3_Err != nil {
336 return templ_7745c5c3_Err346 return templ_7745c5c3_Err
337 }347 }
338 var templ_7745c5c3_Var15 string348 var templ_7745c5c3_Var15 string
339 templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(s.Commits)349 templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(s.Commits)
340 if templ_7745c5c3_Err != nil {350 if templ_7745c5c3_Err != nil {
341- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 70, Col: 41}351+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 74, Col: 41}
342 }352 }
343 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))353 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
344 if templ_7745c5c3_Err != nil {354 if templ_7745c5c3_Err != nil {
345 return templ_7745c5c3_Err355 return templ_7745c5c3_Err
346 }356 }
347- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</dd></div><div><dt>last 30 days</dt><dd>")357+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</dd></div><div><dt>last 30 days</dt><dd>")
348 if templ_7745c5c3_Err != nil {358 if templ_7745c5c3_Err != nil {
349 return templ_7745c5c3_Err359 return templ_7745c5c3_Err
350 }360 }
351 var templ_7745c5c3_Var16 string361 var templ_7745c5c3_Var16 string
352 templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(s.Recent)362 templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(s.Recent)
353 if templ_7745c5c3_Err != nil {363 if templ_7745c5c3_Err != nil {
354- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 71, Col: 45}364+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 75, Col: 45}
355 }365 }
356 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))366 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
357 if templ_7745c5c3_Err != nil {367 if templ_7745c5c3_Err != nil {
358 return templ_7745c5c3_Err368 return templ_7745c5c3_Err
359 }369 }
360- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</dd></div>")370+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</dd></div>")
361 if templ_7745c5c3_Err != nil {371 if templ_7745c5c3_Err != nil {
362 return templ_7745c5c3_Err372 return templ_7745c5c3_Err
363 }373 }
364- if s.Tag != "" {
365- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"rs-tag\"><dt>latest tag</dt>")
366- if templ_7745c5c3_Err != nil {
367- return templ_7745c5c3_Err
368- }
369- if s.TagHref != "" {
370- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<dd><a href=\"")
371- if templ_7745c5c3_Err != nil {
372- return templ_7745c5c3_Err
373- }
374- var templ_7745c5c3_Var17 templ.SafeURL
375- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.TagHref))
376- if templ_7745c5c3_Err != nil {
377- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 76, Col: 46}
378- }
379- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
380- if templ_7745c5c3_Err != nil {
381- return templ_7745c5c3_Err
382- }
383- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" title=\"")
384- if templ_7745c5c3_Err != nil {
385- return templ_7745c5c3_Err
386- }
387- var templ_7745c5c3_Var18 string
388- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
389- if templ_7745c5c3_Err != nil {
390- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 76, Col: 88}
391- }
392- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
393- if templ_7745c5c3_Err != nil {
394- return templ_7745c5c3_Err
395- }
396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\">")
397- if templ_7745c5c3_Err != nil {
398- return templ_7745c5c3_Err
399- }
400- var templ_7745c5c3_Var19 string
401- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
402- if templ_7745c5c3_Err != nil {
403- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 76, Col: 98}
404- }
405- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
406- if templ_7745c5c3_Err != nil {
407- return templ_7745c5c3_Err
408- }
409- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</a></dd>")
410- if templ_7745c5c3_Err != nil {
411- return templ_7745c5c3_Err
412- }
413- } else {
414- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<dd title=\"")
415- if templ_7745c5c3_Err != nil {
416- return templ_7745c5c3_Err
417- }
418- var templ_7745c5c3_Var20 string
419- templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
420- if templ_7745c5c3_Err != nil {
421- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 78, Col: 51}
422- }
423- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20)
424- if templ_7745c5c3_Err != nil {
425- return templ_7745c5c3_Err
426- }
427- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\">")
428- if templ_7745c5c3_Err != nil {
429- return templ_7745c5c3_Err
430- }
431- var templ_7745c5c3_Var21 string
432- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
433- if templ_7745c5c3_Err != nil {
434- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 78, Col: 61}
435- }
436- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
437- if templ_7745c5c3_Err != nil {
438- return templ_7745c5c3_Err
439- }
440- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</dd>")
441- if templ_7745c5c3_Err != nil {
442- return templ_7745c5c3_Err
443- }
444- }
445- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div>")
446- if templ_7745c5c3_Err != nil {
447- return templ_7745c5c3_Err
448- }
449- }
450 }374 }
451- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div><dt>last activity</dt>")375+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div><dt>last activity</dt>")
452 if templ_7745c5c3_Err != nil {376 if templ_7745c5c3_Err != nil {
453 return templ_7745c5c3_Err377 return templ_7745c5c3_Err
454 }378 }
455 if r.ActiveAt != "" {379 if r.ActiveAt != "" {
456- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<dd><time datetime=\"")380+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<dd><time datetime=\"")
457 if templ_7745c5c3_Err != nil {381 if templ_7745c5c3_Err != nil {
458 return templ_7745c5c3_Err382 return templ_7745c5c3_Err
459 }383 }
460- var templ_7745c5c3_Var22 string384+ var templ_7745c5c3_Var17 string
461- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)385+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)
462 if templ_7745c5c3_Err != nil {386 if templ_7745c5c3_Err != nil {
463- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 86, Col: 37}387+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 37}
464 }388 }
465- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22)389+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
466 if templ_7745c5c3_Err != nil {390 if templ_7745c5c3_Err != nil {
467 return templ_7745c5c3_Err391 return templ_7745c5c3_Err
468 }392 }
469- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" title=\"")393+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" title=\"")
470 if templ_7745c5c3_Err != nil {394 if templ_7745c5c3_Err != nil {
471 return templ_7745c5c3_Err395 return templ_7745c5c3_Err
472 }396 }
473- var templ_7745c5c3_Var23 string397+ var templ_7745c5c3_Var18 string
474- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)398+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ActiveAt)
475 if templ_7745c5c3_Err != nil {399 if templ_7745c5c3_Err != nil {
476- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 86, Col: 58}400+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 58}
477 }401 }
478- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)402+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
479 if templ_7745c5c3_Err != nil {403 if templ_7745c5c3_Err != nil {
480 return templ_7745c5c3_Err404 return templ_7745c5c3_Err
481 }405 }
482- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\">")406+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\">")
483 if templ_7745c5c3_Err != nil {407 if templ_7745c5c3_Err != nil {
484 return templ_7745c5c3_Err408 return templ_7745c5c3_Err
485 }409 }
486- var templ_7745c5c3_Var24 string410+ var templ_7745c5c3_Var19 string
487- templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(r.Active)411+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(r.Active)
488 if templ_7745c5c3_Err != nil {412 if templ_7745c5c3_Err != nil {
489- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 86, Col: 71}413+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 71}
490 }414 }
491- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))415+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
492 if templ_7745c5c3_Err != nil {416 if templ_7745c5c3_Err != nil {
493 return templ_7745c5c3_Err417 return templ_7745c5c3_Err
494 }418 }
495- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</time></dd>")419+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</time></dd>")
496 if templ_7745c5c3_Err != nil {420 if templ_7745c5c3_Err != nil {
497 return templ_7745c5c3_Err421 return templ_7745c5c3_Err
498 }422 }
499 } else {423 } else {
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<dd class=\"mute\">never</dd>")424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<dd class=\"mute\">never</dd>")
501 if templ_7745c5c3_Err != nil {425 if templ_7745c5c3_Err != nil {
502 return templ_7745c5c3_Err426 return templ_7745c5c3_Err
503 }427 }
504 }428 }
505- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</div></dl>")429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div>")
430+ if templ_7745c5c3_Err != nil {
431+ return templ_7745c5c3_Err
432+ }
433+ if s := r.Stats; s != nil && s.Tag != "" {
434+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<div class=\"rs-tag\"><dt>last tag</dt>")
435+ if templ_7745c5c3_Err != nil {
436+ return templ_7745c5c3_Err
437+ }
438+ if s.TagHref != "" {
439+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<dd><a href=\"")
440+ if templ_7745c5c3_Err != nil {
441+ return templ_7745c5c3_Err
442+ }
443+ var templ_7745c5c3_Var20 templ.SafeURL
444+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.TagHref))
445+ if templ_7745c5c3_Err != nil {
446+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 45}
447+ }
448+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
449+ if templ_7745c5c3_Err != nil {
450+ return templ_7745c5c3_Err
451+ }
452+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" title=\"")
453+ if templ_7745c5c3_Err != nil {
454+ return templ_7745c5c3_Err
455+ }
456+ var templ_7745c5c3_Var21 string
457+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
458+ if templ_7745c5c3_Err != nil {
459+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 87}
460+ }
461+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
462+ if templ_7745c5c3_Err != nil {
463+ return templ_7745c5c3_Err
464+ }
465+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\">")
466+ if templ_7745c5c3_Err != nil {
467+ return templ_7745c5c3_Err
468+ }
469+ var templ_7745c5c3_Var22 string
470+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
471+ if templ_7745c5c3_Err != nil {
472+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 97}
473+ }
474+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
475+ if templ_7745c5c3_Err != nil {
476+ return templ_7745c5c3_Err
477+ }
478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</a></dd>")
479+ if templ_7745c5c3_Err != nil {
480+ return templ_7745c5c3_Err
481+ }
482+ } else {
483+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<dd title=\"")
484+ if templ_7745c5c3_Err != nil {
485+ return templ_7745c5c3_Err
486+ }
487+ var templ_7745c5c3_Var23 string
488+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(s.Tag + " · made " + s.TagWhen)
489+ if templ_7745c5c3_Err != nil {
490+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 91, Col: 50}
491+ }
492+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
493+ if templ_7745c5c3_Err != nil {
494+ return templ_7745c5c3_Err
495+ }
496+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "\">")
497+ if templ_7745c5c3_Err != nil {
498+ return templ_7745c5c3_Err
499+ }
500+ var templ_7745c5c3_Var24 string
501+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(s.Tag)
502+ if templ_7745c5c3_Err != nil {
503+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 91, Col: 60}
504+ }
505+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
506+ if templ_7745c5c3_Err != nil {
507+ return templ_7745c5c3_Err
508+ }
509+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</dd>")
510+ if templ_7745c5c3_Err != nil {
511+ return templ_7745c5c3_Err
512+ }
513+ }
514+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</div>")
515+ if templ_7745c5c3_Err != nil {
516+ return templ_7745c5c3_Err
517+ }
518+ }
519+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</dl>")
506 if templ_7745c5c3_Err != nil {520 if templ_7745c5c3_Err != nil {
507 return templ_7745c5c3_Err521 return templ_7745c5c3_Err
508 }522 }
509 if r.CI != nil {523 if r.CI != nil {
510- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<a class=\"repo-ci\" href=\"")524+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<a class=\"repo-ci\" href=\"")
511 if templ_7745c5c3_Err != nil {525 if templ_7745c5c3_Err != nil {
512 return templ_7745c5c3_Err526 return templ_7745c5c3_Err
513 }527 }
514 var templ_7745c5c3_Var25 templ.SafeURL528 var templ_7745c5c3_Var25 templ.SafeURL
515 templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CI.Href))529 templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CI.Href))
516 if templ_7745c5c3_Err != nil {530 if templ_7745c5c3_Err != nil {
517- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 93, Col: 54}531+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 97, Col: 54}
518 }532 }
519 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))533 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
520 if templ_7745c5c3_Err != nil {534 if templ_7745c5c3_Err != nil {
521 return templ_7745c5c3_Err535 return templ_7745c5c3_Err
522 }536 }
523- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\" title=\"")537+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" title=\"")
524 if templ_7745c5c3_Err != nil {538 if templ_7745c5c3_Err != nil {
525 return templ_7745c5c3_Err539 return templ_7745c5c3_Err
526 }540 }
527 var templ_7745c5c3_Var26 string541 var templ_7745c5c3_Var26 string
528 templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CI.Title)542 templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CI.Title)
529 if templ_7745c5c3_Err != nil {543 if templ_7745c5c3_Err != nil {
530- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 93, Col: 75}544+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 97, Col: 75}
531 }545 }
532 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)546 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
533 if templ_7745c5c3_Err != nil {547 if templ_7745c5c3_Err != nil {
534 return templ_7745c5c3_Err548 return templ_7745c5c3_Err
535 }549 }
536- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\">")550+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\">")
537 if templ_7745c5c3_Err != nil {551 if templ_7745c5c3_Err != nil {
538 return templ_7745c5c3_Err552 return templ_7745c5c3_Err
539 }553 }
@@ -542,7 +556,7 @@ func HomeRowView(r HomeRow) templ.Component {
542 if templ_7745c5c3_Err != nil {556 if templ_7745c5c3_Err != nil {
543 return templ_7745c5c3_Err557 return templ_7745c5c3_Err
544 }558 }
545- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<span class=\"")559+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<span class=\"")
546 if templ_7745c5c3_Err != nil {560 if templ_7745c5c3_Err != nil {
547 return templ_7745c5c3_Err561 return templ_7745c5c3_Err
548 }562 }
@@ -555,174 +569,174 @@ func HomeRowView(r HomeRow) templ.Component {
555 if templ_7745c5c3_Err != nil {569 if templ_7745c5c3_Err != nil {
556 return templ_7745c5c3_Err570 return templ_7745c5c3_Err
557 }571 }
558- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" aria-hidden=\"true\"></span> <span class=\"sr\">CI: </span>")572+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "\" aria-hidden=\"true\"></span> <span class=\"sr\">CI: </span>")
559 if templ_7745c5c3_Err != nil {573 if templ_7745c5c3_Err != nil {
560 return templ_7745c5c3_Err574 return templ_7745c5c3_Err
561 }575 }
562 var templ_7745c5c3_Var29 string576 var templ_7745c5c3_Var29 string
563 templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(r.CI.Label)577 templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(r.CI.Label)
564 if templ_7745c5c3_Err != nil {578 if templ_7745c5c3_Err != nil {
565- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 95, Col: 45}579+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 99, Col: 45}
566 }580 }
567 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))581 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
568 if templ_7745c5c3_Err != nil {582 if templ_7745c5c3_Err != nil {
569 return templ_7745c5c3_Err583 return templ_7745c5c3_Err
570 }584 }
571- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</a>")585+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</a>")
572 if templ_7745c5c3_Err != nil {586 if templ_7745c5c3_Err != nil {
573 return templ_7745c5c3_Err587 return templ_7745c5c3_Err
574 }588 }
575 }589 }
576- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</div>")590+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</div>")
577 if templ_7745c5c3_Err != nil {591 if templ_7745c5c3_Err != nil {
578 return templ_7745c5c3_Err592 return templ_7745c5c3_Err
579 }593 }
580 if len(r.Commits) > 0 {594 if len(r.Commits) > 0 {
581- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<ul class=\"repo-commits\"")595+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<ul class=\"repo-commits\"")
582 if templ_7745c5c3_Err != nil {596 if templ_7745c5c3_Err != nil {
583 return templ_7745c5c3_Err597 return templ_7745c5c3_Err
584 }598 }
585 if len(r.Commits) == homeRecent {599 if len(r.Commits) == homeRecent {
586- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " data-full")600+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " data-full")
587 if templ_7745c5c3_Err != nil {601 if templ_7745c5c3_Err != nil {
588 return templ_7745c5c3_Err602 return templ_7745c5c3_Err
589 }603 }
590 }604 }
591- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, " aria-label=\"")605+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " aria-label=\"")
592 if templ_7745c5c3_Err != nil {606 if templ_7745c5c3_Err != nil {
593 return templ_7745c5c3_Err607 return templ_7745c5c3_Err
594 }608 }
595 var templ_7745c5c3_Var30 string609 var templ_7745c5c3_Var30 string
596 templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue("Recent commits of " + r.Name)610 templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue("Recent commits of " + r.Name)
597 if templ_7745c5c3_Err != nil {611 if templ_7745c5c3_Err != nil {
598- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 103, Col: 46}612+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 107, Col: 46}
599 }613 }
600 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30)614 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30)
601 if templ_7745c5c3_Err != nil {615 if templ_7745c5c3_Err != nil {
602 return templ_7745c5c3_Err616 return templ_7745c5c3_Err
603 }617 }
604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\">")618+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "\">")
605 if templ_7745c5c3_Err != nil {619 if templ_7745c5c3_Err != nil {
606 return templ_7745c5c3_Err620 return templ_7745c5c3_Err
607 }621 }
608 for _, c := range r.Commits {622 for _, c := range r.Commits {
609- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<li><a class=\"rc-sha\" href=\"")623+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<li><a class=\"rc-sha\" href=\"")
610 if templ_7745c5c3_Err != nil {624 if templ_7745c5c3_Err != nil {
611 return templ_7745c5c3_Err625 return templ_7745c5c3_Err
612 }626 }
613 var templ_7745c5c3_Var31 templ.SafeURL627 var templ_7745c5c3_Var31 templ.SafeURL
614 templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))628 templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
615 if templ_7745c5c3_Err != nil {629 if templ_7745c5c3_Err != nil {
616- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 107, Col: 53}630+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 111, Col: 53}
617 }631 }
618 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))632 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
619 if templ_7745c5c3_Err != nil {633 if templ_7745c5c3_Err != nil {
620 return templ_7745c5c3_Err634 return templ_7745c5c3_Err
621 }635 }
622- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "\">")636+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\">")
623 if templ_7745c5c3_Err != nil {637 if templ_7745c5c3_Err != nil {
624 return templ_7745c5c3_Err638 return templ_7745c5c3_Err
625 }639 }
626 var templ_7745c5c3_Var32 string640 var templ_7745c5c3_Var32 string
627 templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)641 templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
628 if templ_7745c5c3_Err != nil {642 if templ_7745c5c3_Err != nil {
629- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 107, Col: 65}643+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 111, Col: 65}
630 }644 }
631 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))645 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
632 if templ_7745c5c3_Err != nil {646 if templ_7745c5c3_Err != nil {
633 return templ_7745c5c3_Err647 return templ_7745c5c3_Err
634 }648 }
635- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</a> <span class=\"rc-subject\" title=\"")649+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</a> <span class=\"rc-subject\" title=\"")
636 if templ_7745c5c3_Err != nil {650 if templ_7745c5c3_Err != nil {
637 return templ_7745c5c3_Err651 return templ_7745c5c3_Err
638 }652 }
639 var templ_7745c5c3_Var33 string653 var templ_7745c5c3_Var33 string
640 templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Subject)654 templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Subject)
641 if templ_7745c5c3_Err != nil {655 if templ_7745c5c3_Err != nil {
642- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 108, Col: 48}656+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 112, Col: 48}
643 }657 }
644 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)658 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
645 if templ_7745c5c3_Err != nil {659 if templ_7745c5c3_Err != nil {
646 return templ_7745c5c3_Err660 return templ_7745c5c3_Err
647 }661 }
648- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\">")662+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "\">")
649 if templ_7745c5c3_Err != nil {663 if templ_7745c5c3_Err != nil {
650 return templ_7745c5c3_Err664 return templ_7745c5c3_Err
651 }665 }
652 var templ_7745c5c3_Var34 string666 var templ_7745c5c3_Var34 string
653 templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)667 templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
654 if templ_7745c5c3_Err != nil {668 if templ_7745c5c3_Err != nil {
655- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 108, Col: 62}669+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 112, Col: 62}
656 }670 }
657 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))671 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
658 if templ_7745c5c3_Err != nil {672 if templ_7745c5c3_Err != nil {
659 return templ_7745c5c3_Err673 return templ_7745c5c3_Err
660 }674 }
661- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</span> ")675+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</span> ")
662 if templ_7745c5c3_Err != nil {676 if templ_7745c5c3_Err != nil {
663 return templ_7745c5c3_Err677 return templ_7745c5c3_Err
664 }678 }
665 if c.WhenAt != "" {679 if c.WhenAt != "" {
666- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<time class=\"dim\" datetime=\"")680+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<time class=\"dim\" datetime=\"")
667 if templ_7745c5c3_Err != nil {681 if templ_7745c5c3_Err != nil {
668 return templ_7745c5c3_Err682 return templ_7745c5c3_Err
669 }683 }
670 var templ_7745c5c3_Var35 string684 var templ_7745c5c3_Var35 string
671 templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)685 templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
672 if templ_7745c5c3_Err != nil {686 if templ_7745c5c3_Err != nil {
673- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 110, Col: 44}687+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 114, Col: 44}
674 }688 }
675 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)689 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
676 if templ_7745c5c3_Err != nil {690 if templ_7745c5c3_Err != nil {
677 return templ_7745c5c3_Err691 return templ_7745c5c3_Err
678 }692 }
679- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "\" title=\"")693+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "\" title=\"")
680 if templ_7745c5c3_Err != nil {694 if templ_7745c5c3_Err != nil {
681 return templ_7745c5c3_Err695 return templ_7745c5c3_Err
682 }696 }
683 var templ_7745c5c3_Var36 string697 var templ_7745c5c3_Var36 string
684 templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)698 templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
685 if templ_7745c5c3_Err != nil {699 if templ_7745c5c3_Err != nil {
686- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 110, Col: 63}700+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 114, Col: 63}
687 }701 }
688 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)702 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
689 if templ_7745c5c3_Err != nil {703 if templ_7745c5c3_Err != nil {
690 return templ_7745c5c3_Err704 return templ_7745c5c3_Err
691 }705 }
692- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "\">")706+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\">")
693 if templ_7745c5c3_Err != nil {707 if templ_7745c5c3_Err != nil {
694 return templ_7745c5c3_Err708 return templ_7745c5c3_Err
695 }709 }
696 var templ_7745c5c3_Var37 string710 var templ_7745c5c3_Var37 string
697 templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)711 templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
698 if templ_7745c5c3_Err != nil {712 if templ_7745c5c3_Err != nil {
699- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 110, Col: 74}713+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 114, Col: 74}
700 }714 }
701 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))715 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
702 if templ_7745c5c3_Err != nil {716 if templ_7745c5c3_Err != nil {
703 return templ_7745c5c3_Err717 return templ_7745c5c3_Err
704 }718 }
705- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "</time>")719+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</time>")
706 if templ_7745c5c3_Err != nil {720 if templ_7745c5c3_Err != nil {
707 return templ_7745c5c3_Err721 return templ_7745c5c3_Err
708 }722 }
709 }723 }
710- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</li>")724+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</li>")
711 if templ_7745c5c3_Err != nil {725 if templ_7745c5c3_Err != nil {
712 return templ_7745c5c3_Err726 return templ_7745c5c3_Err
713 }727 }
714 }728 }
715- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</ul>")729+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</ul>")
716 if templ_7745c5c3_Err != nil {730 if templ_7745c5c3_Err != nil {
717 return templ_7745c5c3_Err731 return templ_7745c5c3_Err
718 }732 }
719 } else if r.NoCommits {733 } else if r.NoCommits {
720- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<p class=\"repo-commits mute\">no commits yet</p>")734+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<p class=\"repo-commits mute\">no commits yet</p>")
721 if templ_7745c5c3_Err != nil {735 if templ_7745c5c3_Err != nil {
722 return templ_7745c5c3_Err736 return templ_7745c5c3_Err
723 }737 }
724 }738 }
725- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</li>")739+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</li>")
726 if templ_7745c5c3_Err != nil {740 if templ_7745c5c3_Err != nil {
727 return templ_7745c5c3_Err741 return templ_7745c5c3_Err
728 }742 }
@@ -731,8 +745,9 @@ func HomeRowView(r HomeRow) templ.Component {
731 }745 }
732 746
733 // pulse is the activity curve of a home row: a smooth line over a fading747 // pulse is the activity curve of a home row: a smooth line over a fading
734-// area on a faint grid, and a dot at today that beats while the repository748+// area on a faint grid, and a dot at today, in colour while the repository
735-// is active. The SVG scales evenly with its column, so the dot stays round.749+// is active. Nothing moves: the page stays idle. The SVG scales evenly with
750+// its column, so the dot stays round.
736 func pulse(h *Heartbeat) templ.Component {751 func pulse(h *Heartbeat) templ.Component {
737 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {752 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
738 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context753 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -754,134 +769,108 @@ func pulse(h *Heartbeat) templ.Component {
754 templ_7745c5c3_Var38 = templ.NopComponent769 templ_7745c5c3_Var38 = templ.NopComponent
755 }770 }
756 ctx = templ.ClearChildren(ctx)771 ctx = templ.ClearChildren(ctx)
757- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "<figure class=\"repo-pulse\"")772+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<figure class=\"repo-pulse\"")
758 if templ_7745c5c3_Err != nil {773 if templ_7745c5c3_Err != nil {
759 return templ_7745c5c3_Err774 return templ_7745c5c3_Err
760 }775 }
761 if h.Live {776 if h.Live {
762- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, " data-live")777+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, " data-live")
763 if templ_7745c5c3_Err != nil {778 if templ_7745c5c3_Err != nil {
764 return templ_7745c5c3_Err779 return templ_7745c5c3_Err
765 }780 }
766 }781 }
767- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "><svg viewBox=\"0 0 600 64\" role=\"img\" aria-label=\"")782+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "><svg viewBox=\"0 0 600 64\" role=\"img\" aria-label=\"")
768 if templ_7745c5c3_Err != nil {783 if templ_7745c5c3_Err != nil {
769 return templ_7745c5c3_Err784 return templ_7745c5c3_Err
770 }785 }
771 var templ_7745c5c3_Var39 string786 var templ_7745c5c3_Var39 string
772 templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Label)787 templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Label)
773 if templ_7745c5c3_Err != nil {788 if templ_7745c5c3_Err != nil {
774- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 59}789+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 131, Col: 59}
775 }790 }
776 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var39)791 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var39)
777 if templ_7745c5c3_Err != nil {792 if templ_7745c5c3_Err != nil {
778 return templ_7745c5c3_Err793 return templ_7745c5c3_Err
779 }794 }
780- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "\" focusable=\"false\"><defs><linearGradient id=\"")795+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\" focusable=\"false\"><defs><linearGradient id=\"")
781 if templ_7745c5c3_Err != nil {796 if templ_7745c5c3_Err != nil {
782 return templ_7745c5c3_Err797 return templ_7745c5c3_Err
783 }798 }
784 var templ_7745c5c3_Var40 string799 var templ_7745c5c3_Var40 string
785 templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.GradID)800 templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.GradID)
786 if templ_7745c5c3_Err != nil {801 if templ_7745c5c3_Err != nil {
787- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 128, Col: 33}802+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 133, Col: 33}
788 }803 }
789 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)804 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)
790 if templ_7745c5c3_Err != nil {805 if templ_7745c5c3_Err != nil {
791 return templ_7745c5c3_Err806 return templ_7745c5c3_Err
792 }807 }
793- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop class=\"pulse-stop-top\" offset=\"0\"></stop> <stop class=\"pulse-stop-bottom\" offset=\"1\"></stop></linearGradient></defs> <path class=\"pulse-grid\" d=\"M0 21.5 H600 M0 42.5 H600 M0 63.5 H600\" vector-effect=\"non-scaling-stroke\"></path> <path class=\"pulse-area\" d=\"")808+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop class=\"pulse-stop-top\" offset=\"0\"></stop> <stop class=\"pulse-stop-bottom\" offset=\"1\"></stop></linearGradient></defs> <path class=\"pulse-grid\" d=\"M0 21.5 H600 M0 42.5 H600 M0 63.5 H600\" vector-effect=\"non-scaling-stroke\"></path> <path class=\"pulse-area\" d=\"")
794 if templ_7745c5c3_Err != nil {809 if templ_7745c5c3_Err != nil {
795 return templ_7745c5c3_Err810 return templ_7745c5c3_Err
796 }811 }
797 var templ_7745c5c3_Var41 string812 var templ_7745c5c3_Var41 string
798 templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Area)813 templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Area)
799 if templ_7745c5c3_Err != nil {814 if templ_7745c5c3_Err != nil {
800- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 134, Col: 38}815+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 139, Col: 38}
801 }816 }
802 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)817 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var41)
803 if templ_7745c5c3_Err != nil {818 if templ_7745c5c3_Err != nil {
804 return templ_7745c5c3_Err819 return templ_7745c5c3_Err
805 }820 }
806- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\" fill=\"")821+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "\" fill=\"")
807 if templ_7745c5c3_Err != nil {822 if templ_7745c5c3_Err != nil {
808 return templ_7745c5c3_Err823 return templ_7745c5c3_Err
809 }824 }
810 var templ_7745c5c3_Var42 string825 var templ_7745c5c3_Var42 string
811 templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue("url(#" + h.GradID + ")")826 templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue("url(#" + h.GradID + ")")
812 if templ_7745c5c3_Err != nil {827 if templ_7745c5c3_Err != nil {
813- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 134, Col: 72}828+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 139, Col: 72}
814 }829 }
815 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)830 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)
816 if templ_7745c5c3_Err != nil {831 if templ_7745c5c3_Err != nil {
817 return templ_7745c5c3_Err832 return templ_7745c5c3_Err
818 }833 }
819- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "\"></path> <path class=\"pulse-line\" d=\"")834+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\"></path> <path class=\"pulse-line\" d=\"")
820 if templ_7745c5c3_Err != nil {835 if templ_7745c5c3_Err != nil {
821 return templ_7745c5c3_Err836 return templ_7745c5c3_Err
822 }837 }
823 var templ_7745c5c3_Var43 string838 var templ_7745c5c3_Var43 string
824 templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Line)839 templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Line)
825 if templ_7745c5c3_Err != nil {840 if templ_7745c5c3_Err != nil {
826- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 135, Col: 38}841+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 140, Col: 38}
827 }842 }
828 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var43)843 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var43)
829 if templ_7745c5c3_Err != nil {844 if templ_7745c5c3_Err != nil {
830 return templ_7745c5c3_Err845 return templ_7745c5c3_Err
831 }846 }
832- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" vector-effect=\"non-scaling-stroke\"></path> <circle class=\"pulse-ring\" cx=\"")847+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\" vector-effect=\"non-scaling-stroke\"></path> <circle class=\"pulse-dot\" cx=\"")
833 if templ_7745c5c3_Err != nil {848 if templ_7745c5c3_Err != nil {
834 return templ_7745c5c3_Err849 return templ_7745c5c3_Err
835 }850 }
836 var templ_7745c5c3_Var44 string851 var templ_7745c5c3_Var44 string
837 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.X)852 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.X)
838 if templ_7745c5c3_Err != nil {853 if templ_7745c5c3_Err != nil {
839- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 136, Col: 38}854+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 141, Col: 37}
840 }855 }
841 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var44)856 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var44)
842 if templ_7745c5c3_Err != nil {857 if templ_7745c5c3_Err != nil {
843 return templ_7745c5c3_Err858 return templ_7745c5c3_Err
844 }859 }
845- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "\" cy=\"")860+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" cy=\"")
846 if templ_7745c5c3_Err != nil {861 if templ_7745c5c3_Err != nil {
847 return templ_7745c5c3_Err862 return templ_7745c5c3_Err
848 }863 }
849 var templ_7745c5c3_Var45 string864 var templ_7745c5c3_Var45 string
850 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Y)865 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Y)
851 if templ_7745c5c3_Err != nil {866 if templ_7745c5c3_Err != nil {
852- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 136, Col: 49}867+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 141, Col: 48}
853 }868 }
854 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var45)869 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var45)
855 if templ_7745c5c3_Err != nil {870 if templ_7745c5c3_Err != nil {
856 return templ_7745c5c3_Err871 return templ_7745c5c3_Err
857 }872 }
858- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\" r=\"4\"></circle> <circle class=\"pulse-dot\" cx=\"")873+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\" r=\"3.5\"></circle></svg></figure>")
859- if templ_7745c5c3_Err != nil {
860- return templ_7745c5c3_Err
861- }
862- var templ_7745c5c3_Var46 string
863- templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.X)
864- if templ_7745c5c3_Err != nil {
865- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 137, Col: 37}
866- }
867- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var46)
868- if templ_7745c5c3_Err != nil {
869- return templ_7745c5c3_Err
870- }
871- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\" cy=\"")
872- if templ_7745c5c3_Err != nil {
873- return templ_7745c5c3_Err
874- }
875- var templ_7745c5c3_Var47 string
876- templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.ResolveAttributeValue(h.Y)
877- if templ_7745c5c3_Err != nil {
878- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 137, Col: 48}
879- }
880- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var47)
881- if templ_7745c5c3_Err != nil {
882- return templ_7745c5c3_Err
883- }
884- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" r=\"3.5\"></circle></svg><figcaption class=\"pulse-cap\" aria-hidden=\"true\"><span>90 days ago</span><span>today</span></figcaption></figure>")
885 if templ_7745c5c3_Err != nil {874 if templ_7745c5c3_Err != nil {
886 return templ_7745c5c3_Err875 return templ_7745c5c3_Err
887 }876 }
@@ -906,66 +895,66 @@ func Outline(headings []Heading) templ.Component {
906 }()895 }()
907 }896 }
908 ctx = templ.InitializeContext(ctx)897 ctx = templ.InitializeContext(ctx)
909- templ_7745c5c3_Var48 := templ.GetChildren(ctx)898+ templ_7745c5c3_Var46 := templ.GetChildren(ctx)
910- if templ_7745c5c3_Var48 == nil {899+ if templ_7745c5c3_Var46 == nil {
911- templ_7745c5c3_Var48 = templ.NopComponent900+ templ_7745c5c3_Var46 = templ.NopComponent
912 }901 }
913 ctx = templ.ClearChildren(ctx)902 ctx = templ.ClearChildren(ctx)
914- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<nav class=\"outline\" aria-label=\"Outline\">")903+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<nav class=\"outline\" aria-label=\"Outline\">")
915 if templ_7745c5c3_Err != nil {904 if templ_7745c5c3_Err != nil {
916 return templ_7745c5c3_Err905 return templ_7745c5c3_Err
917 }906 }
918 for _, h := range headings {907 for _, h := range headings {
919- var templ_7745c5c3_Var49 = []any{"l" + itoa(h.Level)}908+ var templ_7745c5c3_Var47 = []any{"l" + itoa(h.Level)}
920- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var49...)909+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var47...)
921 if templ_7745c5c3_Err != nil {910 if templ_7745c5c3_Err != nil {
922 return templ_7745c5c3_Err911 return templ_7745c5c3_Err
923 }912 }
924- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<a class=\"")913+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<a class=\"")
925 if templ_7745c5c3_Err != nil {914 if templ_7745c5c3_Err != nil {
926 return templ_7745c5c3_Err915 return templ_7745c5c3_Err
927 }916 }
928- var templ_7745c5c3_Var50 string917+ var templ_7745c5c3_Var48 string
929- templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var49).String())918+ templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var47).String())
930 if templ_7745c5c3_Err != nil {919 if templ_7745c5c3_Err != nil {
931 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 1, Col: 0}920 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 1, Col: 0}
932 }921 }
933- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var50)922+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var48)
934 if templ_7745c5c3_Err != nil {923 if templ_7745c5c3_Err != nil {
935 return templ_7745c5c3_Err924 return templ_7745c5c3_Err
936 }925 }
937- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "\" href=\"")926+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" href=\"")
938 if templ_7745c5c3_Err != nil {927 if templ_7745c5c3_Err != nil {
939 return templ_7745c5c3_Err928 return templ_7745c5c3_Err
940 }929 }
941- var templ_7745c5c3_Var51 templ.SafeURL930+ var templ_7745c5c3_Var49 templ.SafeURL
942- templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))931+ templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))
943 if templ_7745c5c3_Err != nil {932 if templ_7745c5c3_Err != nil {
944- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 147, Col: 68}933+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 150, Col: 68}
945 }934 }
946- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))935+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
947 if templ_7745c5c3_Err != nil {936 if templ_7745c5c3_Err != nil {
948 return templ_7745c5c3_Err937 return templ_7745c5c3_Err
949 }938 }
950- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\">")939+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "\">")
951 if templ_7745c5c3_Err != nil {940 if templ_7745c5c3_Err != nil {
952 return templ_7745c5c3_Err941 return templ_7745c5c3_Err
953 }942 }
954- var templ_7745c5c3_Var52 string943+ var templ_7745c5c3_Var50 string
955- templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)944+ templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)
956 if templ_7745c5c3_Err != nil {945 if templ_7745c5c3_Err != nil {
957- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 147, Col: 79}946+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 150, Col: 79}
958 }947 }
959- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))948+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
960 if templ_7745c5c3_Err != nil {949 if templ_7745c5c3_Err != nil {
961 return templ_7745c5c3_Err950 return templ_7745c5c3_Err
962 }951 }
963- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</a>")952+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</a>")
964 if templ_7745c5c3_Err != nil {953 if templ_7745c5c3_Err != nil {
965 return templ_7745c5c3_Err954 return templ_7745c5c3_Err
966 }955 }
967 }956 }
968- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</nav>")957+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</nav>")
969 if templ_7745c5c3_Err != nil {958 if templ_7745c5c3_Err != nil {
970 return templ_7745c5c3_Err959 return templ_7745c5c3_Err
971 }960 }
@@ -990,25 +979,25 @@ func ReadmeCard(r Readme) templ.Component {
990 }()979 }()
991 }980 }
992 ctx = templ.InitializeContext(ctx)981 ctx = templ.InitializeContext(ctx)
993- templ_7745c5c3_Var53 := templ.GetChildren(ctx)982+ templ_7745c5c3_Var51 := templ.GetChildren(ctx)
994- if templ_7745c5c3_Var53 == nil {983+ if templ_7745c5c3_Var51 == nil {
995- templ_7745c5c3_Var53 = templ.NopComponent984+ templ_7745c5c3_Var51 = templ.NopComponent
996 }985 }
997 ctx = templ.ClearChildren(ctx)986 ctx = templ.ClearChildren(ctx)
998- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")987+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")
999 if templ_7745c5c3_Err != nil {988 if templ_7745c5c3_Err != nil {
1000 return templ_7745c5c3_Err989 return templ_7745c5c3_Err
1001 }990 }
1002- var templ_7745c5c3_Var54 string991+ var templ_7745c5c3_Var52 string
1003- templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)992+ templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
1004 if templ_7745c5c3_Err != nil {993 if templ_7745c5c3_Err != nil {
1005- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 157, Col: 11}994+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 160, Col: 11}
1006 }995 }
1007- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))996+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
1008 if templ_7745c5c3_Err != nil {997 if templ_7745c5c3_Err != nil {
1009 return templ_7745c5c3_Err998 return templ_7745c5c3_Err
1010 }999 }
1011- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</h2>")1000+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</h2>")
1012 if templ_7745c5c3_Err != nil {1001 if templ_7745c5c3_Err != nil {
1013 return templ_7745c5c3_Err1002 return templ_7745c5c3_Err
1014 }1003 }
@@ -1016,7 +1005,7 @@ func ReadmeCard(r Readme) templ.Component {
1016 if templ_7745c5c3_Err != nil {1005 if templ_7745c5c3_Err != nil {
1017 return templ_7745c5c3_Err1006 return templ_7745c5c3_Err
1018 }1007 }
1019- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</section>")1008+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</section>")
1020 if templ_7745c5c3_Err != nil {1009 if templ_7745c5c3_Err != nil {
1021 return templ_7745c5c3_Err1010 return templ_7745c5c3_Err
1022 }1011 }
@@ -1042,43 +1031,43 @@ func ReadmeView(r Readme) templ.Component {
1042 }()1031 }()
1043 }1032 }
1044 ctx = templ.InitializeContext(ctx)1033 ctx = templ.InitializeContext(ctx)
1045- templ_7745c5c3_Var55 := templ.GetChildren(ctx)1034+ templ_7745c5c3_Var53 := templ.GetChildren(ctx)
1046- if templ_7745c5c3_Var55 == nil {1035+ if templ_7745c5c3_Var53 == nil {
1047- templ_7745c5c3_Var55 = templ.NopComponent1036+ templ_7745c5c3_Var53 = templ.NopComponent
1048 }1037 }
1049 ctx = templ.ClearChildren(ctx)1038 ctx = templ.ClearChildren(ctx)
1050- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "<article class=\"prose\" aria-label=\"")1039+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<article class=\"prose\" aria-label=\"")
1051 if templ_7745c5c3_Err != nil {1040 if templ_7745c5c3_Err != nil {
1052 return templ_7745c5c3_Err1041 return templ_7745c5c3_Err
1053 }1042 }
1054- var templ_7745c5c3_Var56 string1043+ var templ_7745c5c3_Var54 string
1055- templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)1044+ templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)
1056 if templ_7745c5c3_Err != nil {1045 if templ_7745c5c3_Err != nil {
1057- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 166, Col: 43}1046+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 169, Col: 43}
1058 }1047 }
1059- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)1048+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54)
1060 if templ_7745c5c3_Err != nil {1049 if templ_7745c5c3_Err != nil {
1061 return templ_7745c5c3_Err1050 return templ_7745c5c3_Err
1062 }1051 }
1063- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "\" hx-ignore>")1052+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\" hx-ignore>")
1064 if templ_7745c5c3_Err != nil {1053 if templ_7745c5c3_Err != nil {
1065 return templ_7745c5c3_Err1054 return templ_7745c5c3_Err
1066 }1055 }
1067 if r.Note != "" {1056 if r.Note != "" {
1068- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<p class=\"dim\">")1057+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<p class=\"dim\">")
1069 if templ_7745c5c3_Err != nil {1058 if templ_7745c5c3_Err != nil {
1070 return templ_7745c5c3_Err1059 return templ_7745c5c3_Err
1071 }1060 }
1072- var templ_7745c5c3_Var57 string1061+ var templ_7745c5c3_Var55 string
1073- templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)1062+ templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)
1074 if templ_7745c5c3_Err != nil {1063 if templ_7745c5c3_Err != nil {
1075- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 168, Col: 26}1064+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 171, Col: 26}
1076 }1065 }
1077- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))1066+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
1078 if templ_7745c5c3_Err != nil {1067 if templ_7745c5c3_Err != nil {
1079 return templ_7745c5c3_Err1068 return templ_7745c5c3_Err
1080 }1069 }
1081- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "</p>")1070+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</p>")
1082 if templ_7745c5c3_Err != nil {1071 if templ_7745c5c3_Err != nil {
1083 return templ_7745c5c3_Err1072 return templ_7745c5c3_Err
1084 }1073 }
@@ -1088,7 +1077,7 @@ func ReadmeView(r Readme) templ.Component {
1088 return templ_7745c5c3_Err1077 return templ_7745c5c3_Err
1089 }1078 }
1090 }1079 }
1091- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</article>")1080+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "</article>")
1092 if templ_7745c5c3_Err != nil {1081 if templ_7745c5c3_Err != nil {
1093 return templ_7745c5c3_Err1082 return templ_7745c5c3_Err
1094 }1083 }
@@ -1114,133 +1103,133 @@ func CommitRows(rows []CommitRow) templ.Component {
1114 }()1103 }()
1115 }1104 }
1116 ctx = templ.InitializeContext(ctx)1105 ctx = templ.InitializeContext(ctx)
1117- templ_7745c5c3_Var58 := templ.GetChildren(ctx)1106+ templ_7745c5c3_Var56 := templ.GetChildren(ctx)
1118- if templ_7745c5c3_Var58 == nil {1107+ if templ_7745c5c3_Var56 == nil {
1119- templ_7745c5c3_Var58 = templ.NopComponent1108+ templ_7745c5c3_Var56 = templ.NopComponent
1120 }1109 }
1121 ctx = templ.ClearChildren(ctx)1110 ctx = templ.ClearChildren(ctx)
1122- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")1111+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")
1123 if templ_7745c5c3_Err != nil {1112 if templ_7745c5c3_Err != nil {
1124 return templ_7745c5c3_Err1113 return templ_7745c5c3_Err
1125 }1114 }
1126 for _, c := range rows {1115 for _, c := range rows {
1127- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "<tr")1116+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "<tr")
1128 if templ_7745c5c3_Err != nil {1117 if templ_7745c5c3_Err != nil {
1129 return templ_7745c5c3_Err1118 return templ_7745c5c3_Err
1130 }1119 }
1131 if c.ID != "" {1120 if c.ID != "" {
1132- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, " id=\"")1121+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, " id=\"")
1133 if templ_7745c5c3_Err != nil {1122 if templ_7745c5c3_Err != nil {
1134 return templ_7745c5c3_Err1123 return templ_7745c5c3_Err
1135 }1124 }
1136- var templ_7745c5c3_Var59 string1125+ var templ_7745c5c3_Var57 string
1137- templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)1126+ templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)
1138 if templ_7745c5c3_Err != nil {1127 if templ_7745c5c3_Err != nil {
1139- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 183, Col: 15}1128+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 186, Col: 15}
1140 }1129 }
1141- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)1130+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
1142 if templ_7745c5c3_Err != nil {1131 if templ_7745c5c3_Err != nil {
1143 return templ_7745c5c3_Err1132 return templ_7745c5c3_Err
1144 }1133 }
1145- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "\"")1134+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "\"")
1146 if templ_7745c5c3_Err != nil {1135 if templ_7745c5c3_Err != nil {
1147 return templ_7745c5c3_Err1136 return templ_7745c5c3_Err
1148 }1137 }
1149 }1138 }
1150 if c.Cursor {1139 if c.Cursor {
1151- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, " data-cursor")1140+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, " data-cursor")
1152 if templ_7745c5c3_Err != nil {1141 if templ_7745c5c3_Err != nil {
1153 return templ_7745c5c3_Err1142 return templ_7745c5c3_Err
1154 }1143 }
1155 }1144 }
1156- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "><td class=\"c-sha mute\">")1145+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "><td class=\"c-sha mute\">")
1157 if templ_7745c5c3_Err != nil {1146 if templ_7745c5c3_Err != nil {
1158 return templ_7745c5c3_Err1147 return templ_7745c5c3_Err
1159 }1148 }
1160- var templ_7745c5c3_Var60 string1149+ var templ_7745c5c3_Var58 string
1161- templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)1150+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
1162 if templ_7745c5c3_Err != nil {1151 if templ_7745c5c3_Err != nil {
1163- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 187, Col: 37}1152+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 190, Col: 37}
1164 }1153 }
1165- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))1154+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
1166 if templ_7745c5c3_Err != nil {1155 if templ_7745c5c3_Err != nil {
1167 return templ_7745c5c3_Err1156 return templ_7745c5c3_Err
1168 }1157 }
1169- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</td><td class=\"c-subject\"><a href=\"")1158+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</td><td class=\"c-subject\"><a href=\"")
1170 if templ_7745c5c3_Err != nil {1159 if templ_7745c5c3_Err != nil {
1171 return templ_7745c5c3_Err1160 return templ_7745c5c3_Err
1172 }1161 }
1173- var templ_7745c5c3_Var61 templ.SafeURL1162+ var templ_7745c5c3_Var59 templ.SafeURL
1174- templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))1163+ templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
1175 if templ_7745c5c3_Err != nil {1164 if templ_7745c5c3_Err != nil {
1176- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 189, Col: 38}1165+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 192, Col: 38}
1177 }1166 }
1178- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))1167+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
1179 if templ_7745c5c3_Err != nil {1168 if templ_7745c5c3_Err != nil {
1180 return templ_7745c5c3_Err1169 return templ_7745c5c3_Err
1181 }1170 }
1182- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "\">")1171+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">")
1183 if templ_7745c5c3_Err != nil {1172 if templ_7745c5c3_Err != nil {
1184 return templ_7745c5c3_Err1173 return templ_7745c5c3_Err
1185 }1174 }
1186- var templ_7745c5c3_Var62 string1175+ var templ_7745c5c3_Var60 string
1187- templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)1176+ templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
1188 if templ_7745c5c3_Err != nil {1177 if templ_7745c5c3_Err != nil {
1189- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 189, Col: 52}1178+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 192, Col: 52}
1190 }1179 }
1191- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))1180+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
1192 if templ_7745c5c3_Err != nil {1181 if templ_7745c5c3_Err != nil {
1193 return templ_7745c5c3_Err1182 return templ_7745c5c3_Err
1194 }1183 }
1195- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "</a> ")1184+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</a> ")
1196 if templ_7745c5c3_Err != nil {1185 if templ_7745c5c3_Err != nil {
1197 return templ_7745c5c3_Err1186 return templ_7745c5c3_Err
1198 }1187 }
1199 if c.Merge {1188 if c.Merge {
1200- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "<span class=\"pill\">merge</span> ")1189+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<span class=\"pill\">merge</span> ")
1201 if templ_7745c5c3_Err != nil {1190 if templ_7745c5c3_Err != nil {
1202 return templ_7745c5c3_Err1191 return templ_7745c5c3_Err
1203 }1192 }
1204 }1193 }
1205 for _, b := range c.Branches {1194 for _, b := range c.Branches {
1206- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<span class=\"pill\">")1195+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "<span class=\"pill\">")
1207 if templ_7745c5c3_Err != nil {1196 if templ_7745c5c3_Err != nil {
1208 return templ_7745c5c3_Err1197 return templ_7745c5c3_Err
1209 }1198 }
1210- var templ_7745c5c3_Var63 string1199+ var templ_7745c5c3_Var61 string
1211- templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(b)1200+ templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(b)
1212 if templ_7745c5c3_Err != nil {1201 if templ_7745c5c3_Err != nil {
1213- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 194, Col: 29}1202+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 197, Col: 29}
1214 }1203 }
1215- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))1204+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
1216 if templ_7745c5c3_Err != nil {1205 if templ_7745c5c3_Err != nil {
1217 return templ_7745c5c3_Err1206 return templ_7745c5c3_Err
1218 }1207 }
1219- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "</span> ")1208+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "</span> ")
1220 if templ_7745c5c3_Err != nil {1209 if templ_7745c5c3_Err != nil {
1221 return templ_7745c5c3_Err1210 return templ_7745c5c3_Err
1222 }1211 }
1223 }1212 }
1224 for _, t := range c.Tags {1213 for _, t := range c.Tags {
1225- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")1214+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")
1226 if templ_7745c5c3_Err != nil {1215 if templ_7745c5c3_Err != nil {
1227 return templ_7745c5c3_Err1216 return templ_7745c5c3_Err
1228 }1217 }
1229- var templ_7745c5c3_Var64 string1218+ var templ_7745c5c3_Var62 string
1230- templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(t)1219+ templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(t)
1231 if templ_7745c5c3_Err != nil {1220 if templ_7745c5c3_Err != nil {
1232- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 197, Col: 61}1221+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 200, Col: 61}
1233 }1222 }
1234- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64))1223+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
1235 if templ_7745c5c3_Err != nil {1224 if templ_7745c5c3_Err != nil {
1236 return templ_7745c5c3_Err1225 return templ_7745c5c3_Err
1237 }1226 }
1238- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</span>")1227+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "</span>")
1239 if templ_7745c5c3_Err != nil {1228 if templ_7745c5c3_Err != nil {
1240 return templ_7745c5c3_Err1229 return templ_7745c5c3_Err
1241 }1230 }
1242 }1231 }
1243- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "</td><td class=\"c-ck\">")1232+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</td><td class=\"c-ck\">")
1244 if templ_7745c5c3_Err != nil {1233 if templ_7745c5c3_Err != nil {
1245 return templ_7745c5c3_Err1234 return templ_7745c5c3_Err
1246 }1235 }
@@ -1248,64 +1237,64 @@ func CommitRows(rows []CommitRow) templ.Component {
1248 if templ_7745c5c3_Err != nil {1237 if templ_7745c5c3_Err != nil {
1249 return templ_7745c5c3_Err1238 return templ_7745c5c3_Err
1250 }1239 }
1251- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</td><td class=\"c-who dim\">")1240+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</td><td class=\"c-who dim\">")
1252 if templ_7745c5c3_Err != nil {1241 if templ_7745c5c3_Err != nil {
1253 return templ_7745c5c3_Err1242 return templ_7745c5c3_Err
1254 }1243 }
1255- var templ_7745c5c3_Var65 string1244+ var templ_7745c5c3_Var63 string
1256- templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)1245+ templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
1257 if templ_7745c5c3_Err != nil {1246 if templ_7745c5c3_Err != nil {
1258- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 201, Col: 37}1247+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 204, Col: 37}
1259 }1248 }
1260- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))1249+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
1261 if templ_7745c5c3_Err != nil {1250 if templ_7745c5c3_Err != nil {
1262 return templ_7745c5c3_Err1251 return templ_7745c5c3_Err
1263 }1252 }
1264- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")1253+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")
1265 if templ_7745c5c3_Err != nil {1254 if templ_7745c5c3_Err != nil {
1266 return templ_7745c5c3_Err1255 return templ_7745c5c3_Err
1267 }1256 }
1268- var templ_7745c5c3_Var66 string1257+ var templ_7745c5c3_Var64 string
1269- templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)1258+ templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
1270 if templ_7745c5c3_Err != nil {1259 if templ_7745c5c3_Err != nil {
1271- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 202, Col: 60}1260+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 205, Col: 60}
1272 }1261 }
1273- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)1262+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
1274 if templ_7745c5c3_Err != nil {1263 if templ_7745c5c3_Err != nil {
1275 return templ_7745c5c3_Err1264 return templ_7745c5c3_Err
1276 }1265 }
1277- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "\" title=\"")1266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\" title=\"")
1278 if templ_7745c5c3_Err != nil {1267 if templ_7745c5c3_Err != nil {
1279 return templ_7745c5c3_Err1268 return templ_7745c5c3_Err
1280 }1269 }
1281- var templ_7745c5c3_Var67 string1270+ var templ_7745c5c3_Var65 string
1282- templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)1271+ templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
1283 if templ_7745c5c3_Err != nil {1272 if templ_7745c5c3_Err != nil {
1284- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 202, Col: 79}1273+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 205, Col: 79}
1285 }1274 }
1286- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var67)1275+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var65)
1287 if templ_7745c5c3_Err != nil {1276 if templ_7745c5c3_Err != nil {
1288 return templ_7745c5c3_Err1277 return templ_7745c5c3_Err
1289 }1278 }
1290- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\">")1279+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "\">")
1291 if templ_7745c5c3_Err != nil {1280 if templ_7745c5c3_Err != nil {
1292 return templ_7745c5c3_Err1281 return templ_7745c5c3_Err
1293 }1282 }
1294- var templ_7745c5c3_Var68 string1283+ var templ_7745c5c3_Var66 string
1295- templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)1284+ templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
1296 if templ_7745c5c3_Err != nil {1285 if templ_7745c5c3_Err != nil {
1297- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 202, Col: 90}1286+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 205, Col: 90}
1298 }1287 }
1299- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))1288+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
1300 if templ_7745c5c3_Err != nil {1289 if templ_7745c5c3_Err != nil {
1301 return templ_7745c5c3_Err1290 return templ_7745c5c3_Err
1302 }1291 }
1303- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</time></td></tr>")1292+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</time></td></tr>")
1304 if templ_7745c5c3_Err != nil {1293 if templ_7745c5c3_Err != nil {
1305 return templ_7745c5c3_Err1294 return templ_7745c5c3_Err
1306 }1295 }
1307 }1296 }
1308- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "</tbody></table>")1297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</tbody></table>")
1309 if templ_7745c5c3_Err != nil {1298 if templ_7745c5c3_Err != nil {
1310 return templ_7745c5c3_Err1299 return templ_7745c5c3_Err
1311 }1300 }
M internal/adapters/web/home_test.go +12 -1
@@ -80,10 +80,21 @@ func TestHomeRowFigures(t *testing.T) {
8080 assert.Contains(t, out, `<a href="/proj/tree/`+head.String()+`" title="v1.2.0 · made 3h ago">v1.2.0</a>`, "by its commit: a branch may share its name")
8181 assert.Contains(t, out, "<dt>last activity</dt>")
8282 assert.NotContains(t, out, "i-branch", "the default branch is not shown")
83- assert.Contains(t, out, `<ul class="repo-topics" aria-label="Topics of proj"><li>go</li><li>git</li></ul>`)
83+ assert.Contains(t, out, `<ul class="repo-topics" aria-label="Topics of proj" data-fit><li>go</li><li>git</li></ul>`)
8484 assert.Contains(t, out, `role="img" aria-label="Activity: 0 commits in the last 90 days"`)
8585 assert.Contains(t, out, `<linearGradient id="hb-proj"`)
86+ assert.NotContains(t, out, "pulse-ring", "nothing moves")
87+ assert.NotContains(t, out, "90 days ago")
8688 assert.NotContains(t, out, "pushed")
89+ // The tag comes last, after the last activity.
90+ assert.Regexp(t, `<dt>last activity</dt>.*<dt>last tag</dt>`, out)
91+
92+ // Without topics nor figures, the row keeps their room: every card has the
93+ // same height.
94+ bare := renderRow(t, HomeRow{ID: "repo-bare", Name: "bare", Href: "/bare"})
95+ assert.Contains(t, bare, `<div class="repo-topics" aria-hidden="true"></div>`)
96+ assert.Contains(t, bare, `<div class="repo-pulse" aria-hidden="true"></div>`)
97+ assert.NotContains(t, bare, "last tag")
8798
8899 // Past the cap of the walk, the figures are lower bounds.
89100 browse.EXPECT().RepoStats(mock.Anything, mock.Anything, repo, head, now).Return(ports.RepoStats{
@@ -80,10 +80,21 @@ func TestHomeRowFigures(t *testing.T) {
80 assert.Contains(t, out, `<a href="/proj/tree/`+head.String()+`" title="v1.2.0 · made 3h ago">v1.2.0</a>`, "by its commit: a branch may share its name")80 assert.Contains(t, out, `<a href="/proj/tree/`+head.String()+`" title="v1.2.0 · made 3h ago">v1.2.0</a>`, "by its commit: a branch may share its name")
81 assert.Contains(t, out, "<dt>last activity</dt>")81 assert.Contains(t, out, "<dt>last activity</dt>")
82 assert.NotContains(t, out, "i-branch", "the default branch is not shown")82 assert.NotContains(t, out, "i-branch", "the default branch is not shown")
83- assert.Contains(t, out, `<ul class="repo-topics" aria-label="Topics of proj"><li>go</li><li>git</li></ul>`)83+ assert.Contains(t, out, `<ul class="repo-topics" aria-label="Topics of proj" data-fit><li>go</li><li>git</li></ul>`)
84 assert.Contains(t, out, `role="img" aria-label="Activity: 0 commits in the last 90 days"`)84 assert.Contains(t, out, `role="img" aria-label="Activity: 0 commits in the last 90 days"`)
85 assert.Contains(t, out, `<linearGradient id="hb-proj"`)85 assert.Contains(t, out, `<linearGradient id="hb-proj"`)
86+ assert.NotContains(t, out, "pulse-ring", "nothing moves")
87+ assert.NotContains(t, out, "90 days ago")
86 assert.NotContains(t, out, "pushed")88 assert.NotContains(t, out, "pushed")
89+ // The tag comes last, after the last activity.
90+ assert.Regexp(t, `<dt>last activity</dt>.*<dt>last tag</dt>`, out)
91+
92+ // Without topics nor figures, the row keeps their room: every card has the
93+ // same height.
94+ bare := renderRow(t, HomeRow{ID: "repo-bare", Name: "bare", Href: "/bare"})
95+ assert.Contains(t, bare, `<div class="repo-topics" aria-hidden="true"></div>`)
96+ assert.Contains(t, bare, `<div class="repo-pulse" aria-hidden="true"></div>`)
97+ assert.NotContains(t, bare, "last tag")
87 98
88 // Past the cap of the walk, the figures are lower bounds.99 // Past the cap of the walk, the figures are lower bounds.
89 browse.EXPECT().RepoStats(mock.Anything, mock.Anything, repo, head, now).Return(ports.RepoStats{100 browse.EXPECT().RepoStats(mock.Anything, mock.Anything, repo, head, now).Return(ports.RepoStats{
A internal/adapters/web/jstest/fit.test.mjs +150 -0
@@ -0,0 +1,150 @@
1+// Tests of the one-line chip lists of static/js/ermite.js (window.ermiteFit).
2+// Only that block is evaluated, on a list whose layout is computed here:
3+// chips flow left to right in a row of a given width and wrap, as flexbox
4+// does. Run with
5+// node --test 'internal/adapters/web/jstest/*.test.mjs'
6+import assert from 'node:assert/strict';
7+import { readFileSync } from 'node:fs';
8+import { test } from 'node:test';
9+
10+const src = readFileSync(new URL('../static/js/ermite.js', import.meta.url), 'utf8');
11+const start = src.indexOf(' window.ermiteFit = ');
12+const block = src.slice(start, src.indexOf('})();', start) + 5);
13+
14+const GAP = 6;
15+const CHAR = 8;
16+const PAD = 20;
17+
18+// A row of chips of the given width (ul.width, which a test may change); a
19+// chip is as wide as its text. Only what the block uses is there.
20+function list(width, names) {
21+ const attrs = {};
22+ const ul = {
23+ width,
24+ children: [],
25+ hasAttribute: (k) => k in attrs,
26+ setAttribute: (k, v) => { attrs[k] = v; },
27+ querySelector(sel) {
28+ assert.equal(sel, ':scope > li.more');
29+ return this.children.find((c) => c.className === 'more') || null;
30+ },
31+ append(li) { li.parent = this; this.children.push(li); },
32+ };
33+ const layout = () => {
34+ let x = 0, y = 0;
35+ for (const c of ul.children) {
36+ if (c.hidden) { c.top = 0; continue; }
37+ const w = c.textContent.length * CHAR + PAD;
38+ if (!('data-open' in attrs) && x > 0 && x + w > ul.width) { x = 0; y += 28; }
39+ c.top = y;
40+ x += w + GAP;
41+ }
42+ };
43+ const el = (tag, text = '') => {
44+ const e = {
45+ tag, className: '', hidden: false, title: '', attrs: {}, kids: [], parent: null, focused: false,
46+ get textContent() { return this.kids.length ? this.kids.map((k) => k.textContent).join('') : text; },
47+ set textContent(v) { text = v; },
48+ get offsetTop() { layout(); return this.top; },
49+ setAttribute(k, v) { this.attrs[k] = v; },
50+ get firstElementChild() { return this.kids[0] || null; },
51+ append(k) { k.parent = this; this.kids.push(k); },
52+ remove() { ul.children.splice(ul.children.indexOf(this), 1); },
53+ focus() { this.focused = true; globalThis.document.activeElement = this; },
54+ closest(sel) {
55+ if (sel === '[data-fit]') return ul;
56+ if (sel === 'li') return tag === 'li' ? this : this.parent;
57+ return null;
58+ },
59+ };
60+ return e;
61+ };
62+ for (const n of names) ul.append(el('li', n));
63+ globalThis.document = { createElement: (tag) => el(tag), activeElement: null };
64+ return ul;
65+}
66+
67+function load() {
68+ globalThis.window = {};
69+ new Function(block)();
70+ return globalThis.window.ermiteFit;
71+}
72+
73+const shown = (ul) => ul.children.filter((c) => !c.hidden).map((c) => c.textContent);
74+const moreBtn = (ul) => ul.children.find((c) => c.className === 'more')?.kids[0];
75+
76+test('a list that fits is left alone', () => {
77+ const fit = load();
78+ const ul = list(400, ['go', 'git', 'forge']);
79+ fit.fit(ul);
80+ assert.deepEqual(shown(ul), ['go', 'git', 'forge']);
81+});
82+
83+test('the chips past the line give way to a … button that names them', () => {
84+ const fit = load();
85+ // each chip is 6 chars: 68px, 74px with the gap. In 380px, 5 fit (364px)
86+ // but not with the 28px … chip after them: the 5th makes way.
87+ const ul = list(380, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg']);
88+ fit.fit(ul);
89+ assert.deepEqual(shown(ul), ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', '…']);
90+ const btn = moreBtn(ul);
91+ assert.equal(btn.tag, 'button');
92+ assert.equal(btn.type, 'button');
93+ assert.equal(btn.title, 'eeeeee, ffffff, gggggg');
94+ assert.equal(btn.attrs['aria-label'], 'show 3 more: eeeeee, ffffff, gggggg');
95+ for (const c of ul.children.filter((c) => !c.hidden)) assert.equal(c.offsetTop, 0, 'all on the first line');
96+
97+ // In 400px, the 5th chip and the … chip both fit.
98+ const roomy = list(400, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff']);
99+ fit.fit(roomy);
100+ assert.deepEqual(shown(roomy), ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', '…']);
101+});
102+
103+test('fitting again after a resize shows what fits now, with one … button', () => {
104+ const fit = load();
105+ const names = ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg'];
106+ const ul = list(380, names);
107+ fit.fit(ul);
108+ fit.fit(ul);
109+ assert.equal(ul.children.filter((c) => c.className === 'more').length, 1, 'idempotent');
110+
111+ ul.width = 2000; // wider: everything shows, no … button
112+ fit.fit(ul);
113+ assert.deepEqual(shown(ul), names);
114+ assert.equal(moreBtn(ul), undefined);
115+
116+ ul.width = 250; // narrower again: fewer chips, the label follows
117+ fit.fit(ul);
118+ assert.deepEqual(shown(ul), ['aaaaaa', 'bbbbbb', 'cccccc', '…']);
119+ assert.equal(moreBtn(ul).attrs['aria-label'], 'show 4 more: dddddd, eeeeee, ffffff, gggggg');
120+});
121+
122+test('a refit keeps the … button, and its focus', () => {
123+ const fit = load();
124+ const names = ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg'];
125+ const ul = list(380, names);
126+ fit.fit(ul);
127+ const btn = moreBtn(ul);
128+ btn.focus();
129+ ul.width = 250;
130+ fit.fit(ul);
131+ assert.equal(moreBtn(ul), btn, 'the same button');
132+ assert.equal(document.activeElement, btn);
133+ ul.width = 2000; // all fit: the focus goes to the last chip
134+ fit.fit(ul);
135+ assert.equal(document.activeElement.textContent, 'gggggg');
136+});
137+
138+test('pressing … shows every chip and moves the focus to the first one it showed', () => {
139+ const fit = load();
140+ const names = ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg'];
141+ const ul = list(380, names);
142+ fit.fit(ul);
143+ fit.open(moreBtn(ul));
144+ assert.deepEqual(shown(ul), names, 'all shown, no … left');
145+ const e = ul.children.find((c) => c.textContent === 'eeeeee');
146+ assert.ok(e.focused);
147+ assert.equal(e.attrs.tabindex, '-1');
148+ fit.fit(ul); // a resize keeps it open
149+ assert.deepEqual(shown(ul), names);
150+});
@@ -0,0 +1,150 @@
1+// Tests of the one-line chip lists of static/js/ermite.js (window.ermiteFit).
2+// Only that block is evaluated, on a list whose layout is computed here:
3+// chips flow left to right in a row of a given width and wrap, as flexbox
4+// does. Run with
5+// node --test 'internal/adapters/web/jstest/*.test.mjs'
6+import assert from 'node:assert/strict';
7+import { readFileSync } from 'node:fs';
8+import { test } from 'node:test';
9+
10+const src = readFileSync(new URL('../static/js/ermite.js', import.meta.url), 'utf8');
11+const start = src.indexOf(' window.ermiteFit = ');
12+const block = src.slice(start, src.indexOf('})();', start) + 5);
13+
14+const GAP = 6;
15+const CHAR = 8;
16+const PAD = 20;
17+
18+// A row of chips of the given width (ul.width, which a test may change); a
19+// chip is as wide as its text. Only what the block uses is there.
20+function list(width, names) {
21+ const attrs = {};
22+ const ul = {
23+ width,
24+ children: [],
25+ hasAttribute: (k) => k in attrs,
26+ setAttribute: (k, v) => { attrs[k] = v; },
27+ querySelector(sel) {
28+ assert.equal(sel, ':scope > li.more');
29+ return this.children.find((c) => c.className === 'more') || null;
30+ },
31+ append(li) { li.parent = this; this.children.push(li); },
32+ };
33+ const layout = () => {
34+ let x = 0, y = 0;
35+ for (const c of ul.children) {
36+ if (c.hidden) { c.top = 0; continue; }
37+ const w = c.textContent.length * CHAR + PAD;
38+ if (!('data-open' in attrs) && x > 0 && x + w > ul.width) { x = 0; y += 28; }
39+ c.top = y;
40+ x += w + GAP;
41+ }
42+ };
43+ const el = (tag, text = '') => {
44+ const e = {
45+ tag, className: '', hidden: false, title: '', attrs: {}, kids: [], parent: null, focused: false,
46+ get textContent() { return this.kids.length ? this.kids.map((k) => k.textContent).join('') : text; },
47+ set textContent(v) { text = v; },
48+ get offsetTop() { layout(); return this.top; },
49+ setAttribute(k, v) { this.attrs[k] = v; },
50+ get firstElementChild() { return this.kids[0] || null; },
51+ append(k) { k.parent = this; this.kids.push(k); },
52+ remove() { ul.children.splice(ul.children.indexOf(this), 1); },
53+ focus() { this.focused = true; globalThis.document.activeElement = this; },
54+ closest(sel) {
55+ if (sel === '[data-fit]') return ul;
56+ if (sel === 'li') return tag === 'li' ? this : this.parent;
57+ return null;
58+ },
59+ };
60+ return e;
61+ };
62+ for (const n of names) ul.append(el('li', n));
63+ globalThis.document = { createElement: (tag) => el(tag), activeElement: null };
64+ return ul;
65+}
66+
67+function load() {
68+ globalThis.window = {};
69+ new Function(block)();
70+ return globalThis.window.ermiteFit;
71+}
72+
73+const shown = (ul) => ul.children.filter((c) => !c.hidden).map((c) => c.textContent);
74+const moreBtn = (ul) => ul.children.find((c) => c.className === 'more')?.kids[0];
75+
76+test('a list that fits is left alone', () => {
77+ const fit = load();
78+ const ul = list(400, ['go', 'git', 'forge']);
79+ fit.fit(ul);
80+ assert.deepEqual(shown(ul), ['go', 'git', 'forge']);
81+});
82+
83+test('the chips past the line give way to a … button that names them', () => {
84+ const fit = load();
85+ // each chip is 6 chars: 68px, 74px with the gap. In 380px, 5 fit (364px)
86+ // but not with the 28px … chip after them: the 5th makes way.
87+ const ul = list(380, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg']);
88+ fit.fit(ul);
89+ assert.deepEqual(shown(ul), ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', '…']);
90+ const btn = moreBtn(ul);
91+ assert.equal(btn.tag, 'button');
92+ assert.equal(btn.type, 'button');
93+ assert.equal(btn.title, 'eeeeee, ffffff, gggggg');
94+ assert.equal(btn.attrs['aria-label'], 'show 3 more: eeeeee, ffffff, gggggg');
95+ for (const c of ul.children.filter((c) => !c.hidden)) assert.equal(c.offsetTop, 0, 'all on the first line');
96+
97+ // In 400px, the 5th chip and the … chip both fit.
98+ const roomy = list(400, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff']);
99+ fit.fit(roomy);
100+ assert.deepEqual(shown(roomy), ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', '…']);
101+});
102+
103+test('fitting again after a resize shows what fits now, with one … button', () => {
104+ const fit = load();
105+ const names = ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg'];
106+ const ul = list(380, names);
107+ fit.fit(ul);
108+ fit.fit(ul);
109+ assert.equal(ul.children.filter((c) => c.className === 'more').length, 1, 'idempotent');
110+
111+ ul.width = 2000; // wider: everything shows, no … button
112+ fit.fit(ul);
113+ assert.deepEqual(shown(ul), names);
114+ assert.equal(moreBtn(ul), undefined);
115+
116+ ul.width = 250; // narrower again: fewer chips, the label follows
117+ fit.fit(ul);
118+ assert.deepEqual(shown(ul), ['aaaaaa', 'bbbbbb', 'cccccc', '…']);
119+ assert.equal(moreBtn(ul).attrs['aria-label'], 'show 4 more: dddddd, eeeeee, ffffff, gggggg');
120+});
121+
122+test('a refit keeps the … button, and its focus', () => {
123+ const fit = load();
124+ const names = ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg'];
125+ const ul = list(380, names);
126+ fit.fit(ul);
127+ const btn = moreBtn(ul);
128+ btn.focus();
129+ ul.width = 250;
130+ fit.fit(ul);
131+ assert.equal(moreBtn(ul), btn, 'the same button');
132+ assert.equal(document.activeElement, btn);
133+ ul.width = 2000; // all fit: the focus goes to the last chip
134+ fit.fit(ul);
135+ assert.equal(document.activeElement.textContent, 'gggggg');
136+});
137+
138+test('pressing … shows every chip and moves the focus to the first one it showed', () => {
139+ const fit = load();
140+ const names = ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', 'gggggg'];
141+ const ul = list(380, names);
142+ fit.fit(ul);
143+ fit.open(moreBtn(ul));
144+ assert.deepEqual(shown(ul), names, 'all shown, no … left');
145+ const e = ul.children.find((c) => c.textContent === 'eeeeee');
146+ assert.ok(e.focused);
147+ assert.equal(e.attrs.tabindex, '-1');
148+ fit.fit(ul); // a resize keeps it open
149+ assert.deepEqual(shown(ul), names);
150+});
M internal/adapters/web/static/css/00-tokens.css +8 -5
@@ -20,9 +20,11 @@
2020 font-weight: 200 800; font-style: normal; font-display: swap;
2121 }
2222
23-/* Palette: grays only, in four surface levels, so that blocks read apart
24- without hue. Dark: each level is lighter than the one below it; light: each
25- is whiter. Hue is kept for code, diffs and verdicts. */
23+/* Palette: grays, in four surface levels, so that blocks read apart without
24+ hue. Dark: each level is lighter than the one below it; light: each is
25+ whiter. Hue is kept for code, diffs and verdicts, and one accent, the teal
26+ of Despair, marks where you are and what is alive: the current tab, the row
27+ cursor, links in prose, open folders. */
2628 @layer tokens {
2729 :root {
2830 color-scheme: light dark;
@@ -46,9 +48,10 @@
4648 --focus: light-dark(#000000, #ffffff);
4749 --on-ink: light-dark(#ffffff, #050505); /* text on a filled ink button */
4850
49- /* Colour, sparingly, from the owner's Despair theme: only code, diffs and
50- verdicts carry hue. Light values are the same hues darkened to AA. */
51+ /* Colour, sparingly, from the owner's Despair theme. Light values are the
52+ same hues darkened to AA. */
5153 --teal: light-dark(#2f6b6b, #7db5b5);
54+ --accent: var(--teal); /* the one accent of the interface */
5255 --teal-2: light-dark(#355f5f, #5a8585);
5356 --red: light-dark(#a8404b, #d96c75);
5457 --rose: light-dark(#6b6570, #a7a2a9);
@@ -20,9 +20,11 @@
20 font-weight: 200 800; font-style: normal; font-display: swap;20 font-weight: 200 800; font-style: normal; font-display: swap;
21 }21 }
22 22
23-/* Palette: grays only, in four surface levels, so that blocks read apart23+/* Palette: grays, in four surface levels, so that blocks read apart without
24- without hue. Dark: each level is lighter than the one below it; light: each24+ hue. Dark: each level is lighter than the one below it; light: each is
25- is whiter. Hue is kept for code, diffs and verdicts. */25+ whiter. Hue is kept for code, diffs and verdicts, and one accent, the teal
26+ of Despair, marks where you are and what is alive: the current tab, the row
27+ cursor, links in prose, open folders. */
26 @layer tokens {28 @layer tokens {
27 :root {29 :root {
28 color-scheme: light dark;30 color-scheme: light dark;
@@ -46,9 +48,10 @@
46 --focus: light-dark(#000000, #ffffff);48 --focus: light-dark(#000000, #ffffff);
47 --on-ink: light-dark(#ffffff, #050505); /* text on a filled ink button */49 --on-ink: light-dark(#ffffff, #050505); /* text on a filled ink button */
48 50
49- /* Colour, sparingly, from the owner's Despair theme: only code, diffs and51+ /* Colour, sparingly, from the owner's Despair theme. Light values are the
50- verdicts carry hue. Light values are the same hues darkened to AA. */52+ same hues darkened to AA. */
51 --teal: light-dark(#2f6b6b, #7db5b5);53 --teal: light-dark(#2f6b6b, #7db5b5);
54+ --accent: var(--teal); /* the one accent of the interface */
52 --teal-2: light-dark(#355f5f, #5a8585);55 --teal-2: light-dark(#355f5f, #5a8585);
53 --red: light-dark(#a8404b, #d96c75);56 --red: light-dark(#a8404b, #d96c75);
54 --rose: light-dark(#6b6570, #a7a2a9);57 --rose: light-dark(#6b6570, #a7a2a9);
M internal/adapters/web/static/css/10-base.css +1 -1
@@ -42,7 +42,7 @@
4242 small { font-size: var(--fs-s); }
4343 code, pre, kbd, samp { font-family: var(--mono); }
4444 a { color: inherit; text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 3px; }
45- a:hover { text-decoration-color: currentColor; color: var(--ink-hi); }
45+ a:hover { text-decoration-color: var(--accent); color: var(--ink-hi); }
4646 hr { border: 0; border-top: 1px solid var(--border); margin: var(--s5) 0; }
4747 ::selection { background: var(--ink-hi); color: var(--on-ink); }
4848
@@ -42,7 +42,7 @@
42 small { font-size: var(--fs-s); }42 small { font-size: var(--fs-s); }
43 code, pre, kbd, samp { font-family: var(--mono); }43 code, pre, kbd, samp { font-family: var(--mono); }
44 a { color: inherit; text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 3px; }44 a { color: inherit; text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 3px; }
45- a:hover { text-decoration-color: currentColor; color: var(--ink-hi); }45+ a:hover { text-decoration-color: var(--accent); color: var(--ink-hi); }
46 hr { border: 0; border-top: 1px solid var(--border); margin: var(--s5) 0; }46 hr { border: 0; border-top: 1px solid var(--border); margin: var(--s5) 0; }
47 ::selection { background: var(--ink-hi); color: var(--on-ink); }47 ::selection { background: var(--ink-hi); color: var(--on-ink); }
48 48
M internal/adapters/web/static/css/20-shell.css +1 -1
@@ -99,7 +99,7 @@
9999 & a:hover { color: var(--ink-hi); }
100100 & a:hover::after { background: var(--border-strong); }
101101 & a[aria-current] { color: var(--ink-hi); }
102- & a[aria-current]::after { background: var(--ink-hi); }
102+ & a[aria-current]::after { background: var(--accent); }
103103 & kbd { opacity: .55; }
104104 & a:hover kbd, & a:focus-visible kbd, & a[aria-current] kbd { opacity: 1; }
105105 }
@@ -99,7 +99,7 @@
99 & a:hover { color: var(--ink-hi); }99 & a:hover { color: var(--ink-hi); }
100 & a:hover::after { background: var(--border-strong); }100 & a:hover::after { background: var(--border-strong); }
101 & a[aria-current] { color: var(--ink-hi); }101 & a[aria-current] { color: var(--ink-hi); }
102- & a[aria-current]::after { background: var(--ink-hi); }102+ & a[aria-current]::after { background: var(--accent); }
103 & kbd { opacity: .55; }103 & kbd { opacity: .55; }
104 & a:hover kbd, & a:focus-visible kbd, & a[aria-current] kbd { opacity: 1; }104 & a:hover kbd, & a:focus-visible kbd, & a[aria-current] kbd { opacity: 1; }
105 }105 }
M internal/adapters/web/static/css/25-panel.css +2 -2
@@ -40,7 +40,7 @@
4040 font-family: var(--mono); font-size: var(--fs-s); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
4141 }
4242 & li a:hover, & li a:focus-visible { background: var(--bg-raised); color: var(--ink-hi); }
43- & li a[aria-current] { font-weight: 700; color: var(--ink-hi); }
43+ & li a[aria-current] { font-weight: 700; color: var(--ink-hi); box-shadow: inset 2px 0 0 var(--accent); }
4444 & .mark { display: inline-block; width: 1ch; color: var(--teal); }
4545 }
4646
@@ -50,7 +50,7 @@
5050 text-decoration: none; color: var(--ink);
5151 }
5252 & li a:hover { background: var(--bg-raised); }
53- & li[data-cursor] a { background: var(--bg-strong); color: var(--ink-hi); }
53+ & li[data-cursor] a { background: var(--bg-strong); color: var(--ink-hi); box-shadow: inset 2px 0 0 var(--accent); }
5454 & .lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: var(--fs-s); }
5555 & .k { flex: none; font-size: var(--fs-xs); color: var(--ink-mute); }
5656 & .dir { color: var(--ink-dim); }
@@ -40,7 +40,7 @@
40 font-family: var(--mono); font-size: var(--fs-s); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;40 font-family: var(--mono); font-size: var(--fs-s); text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
41 }41 }
42 & li a:hover, & li a:focus-visible { background: var(--bg-raised); color: var(--ink-hi); }42 & li a:hover, & li a:focus-visible { background: var(--bg-raised); color: var(--ink-hi); }
43- & li a[aria-current] { font-weight: 700; color: var(--ink-hi); }43+ & li a[aria-current] { font-weight: 700; color: var(--ink-hi); box-shadow: inset 2px 0 0 var(--accent); }
44 & .mark { display: inline-block; width: 1ch; color: var(--teal); }44 & .mark { display: inline-block; width: 1ch; color: var(--teal); }
45 }45 }
46 46
@@ -50,7 +50,7 @@
50 text-decoration: none; color: var(--ink);50 text-decoration: none; color: var(--ink);
51 }51 }
52 & li a:hover { background: var(--bg-raised); }52 & li a:hover { background: var(--bg-raised); }
53- & li[data-cursor] a { background: var(--bg-strong); color: var(--ink-hi); }53+ & li[data-cursor] a { background: var(--bg-strong); color: var(--ink-hi); box-shadow: inset 2px 0 0 var(--accent); }
54 & .lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: var(--fs-s); }54 & .lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: var(--fs-s); }
55 & .k { flex: none; font-size: var(--fs-xs); color: var(--ink-mute); }55 & .k { flex: none; font-size: var(--fs-xs); color: var(--ink-mute); }
56 & .dir { color: var(--ink-dim); }56 & .dir { color: var(--ink-dim); }
M internal/adapters/web/static/css/30-list.css +31 -19
@@ -16,7 +16,7 @@
1616 & tbody tr:first-child td { border-top: 0; }
1717 & tbody tr:hover { background: var(--bg-raised); }
1818 /* the cursor: the row raised, framed by one even line */
19- & tbody tr[data-cursor] { background: var(--bg-raised); outline: 1px solid var(--ink-mute); outline-offset: -1px; }
19+ & tbody tr[data-cursor] { background: var(--bg-raised); outline: 1px solid var(--accent); outline-offset: -1px; }
2020 & tbody tr[hidden] { display: none; }
2121 & td > a { text-decoration: none; }
2222 & td > a:first-child { color: var(--ink-hi); }
@@ -53,41 +53,53 @@
5353 padding: var(--s4); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);
5454 }
5555 & .repo:hover { background: var(--bg-raised); border-color: var(--border-strong); }
56- & .repo[data-cursor] { background: var(--bg-raised); border-color: var(--ink-mute); }
56+ & .repo[data-cursor] { background: var(--bg-raised); border-color: var(--accent); }
5757 & .repo[hidden] { display: none; }
5858 & .repo-main { grid-area: main; display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
5959 & .repo-name { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1) var(--s2); font-family: var(--mono); font-size: 17px; line-height: 26px; overflow-wrap: anywhere; }
6060 & .repo-name a { color: var(--ink-hi); text-decoration: none; }
6161 & .repo:hover .repo-name a, & .repo[data-cursor] .repo-name a { text-decoration: underline; }
6262 & .repo-name .pill { margin-left: 0; }
63+ /* Every card has the same height: two lines of description, one of topics
64+ (reserved when there are none), five of commits. */
6365 & .repo-desc {
64- color: var(--ink-dim);
66+ color: var(--ink-dim); min-height: calc(2 * var(--lh));
6567 display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
6668 }
6769 & .repo-desc.mute { color: var(--ink-mute); font-style: italic; }
68- /* topics: small quiet chips, as on GitHub */
69- & .repo-topics { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }
70+ /* topics: small quiet chips, as on GitHub, on one line. The chips that do
71+ not fit wrap out of sight; ermite.fit (ermite.js) puts a … chip after
72+ the last one shown, the hidden topics in its title. */
73+ & .repo-topics {
74+ display: flex; flex-wrap: wrap; gap: 6px; height: 22px; overflow: hidden;
75+ margin: 0; padding: 0; list-style: none;
76+ }
7077 & .repo-topics li {
71- padding: 1px 9px; border: 1px solid var(--border-strong); border-radius: 999px;
78+ flex: none; padding: 1px 9px; border: 1px solid var(--border-strong); border-radius: 999px;
7279 font-family: var(--mono); font-size: 12px; line-height: 18px; color: var(--teal); background: var(--bg-raised);
7380 }
81+ & .repo-topics li[hidden] { display: none; }
82+ & .repo-topics li.more { padding: 0; }
83+ & .repo-topics li.more button {
84+ padding: 1px 7px; border: 0; border-radius: 999px; background: none; cursor: pointer;
85+ font: inherit; line-height: 18px; color: var(--ink-mute);
86+ }
87+ & .repo-topics li.more button:hover { color: var(--accent); }
88+ & .repo-topics[data-open] { height: auto; }
7489 /* the pulse: the commits per day over 90 days, smoothed, under the text */
75- & .repo-pulse { position: relative; margin: auto 0 0; padding-top: var(--s2); }
90+ & .repo-pulse { margin: auto 0 0; padding-top: var(--s2); }
91+ /* as tall as a drawn pulse: the padding comes on top of the ratio */
92+ & div.repo-pulse { aspect-ratio: 600 / 64; box-sizing: content-box; }
7693 & .repo-pulse svg { display: block; width: 100%; height: auto; overflow: visible; }
7794 & .pulse-grid { fill: none; stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 4; }
7895 & .pulse-stop-top { stop-color: var(--teal); stop-opacity: .32; }
7996 & .pulse-stop-bottom { stop-color: var(--teal); stop-opacity: 0; }
8097 & .pulse-line { fill: none; stroke: var(--teal); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
8198 & .pulse-dot { fill: var(--teal); stroke: var(--bg-surface); stroke-width: 1.5; }
82- & .pulse-ring { fill: none; stroke: var(--teal); stroke-width: 1.5; opacity: 0; transform-box: fill-box; transform-origin: center; }
8399 & .repo:hover .pulse-dot, & .repo[data-cursor] .pulse-dot { stroke: var(--bg-raised); }
84- & .pulse-cap { display: flex; justify-content: space-between; margin-top: 2px; font-size: 10px; line-height: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-mute); }
85100 & .repo-pulse:not([data-live]) .pulse-line { stroke: var(--ink-mute); }
86101 & .repo-pulse:not([data-live]) .pulse-dot { fill: var(--ink-mute); }
87102 & .repo-pulse:not([data-live]) .pulse-stop-top { stop-color: var(--ink-mute); stop-opacity: .18; }
88- @media (prefers-reduced-motion: no-preference) {
89- & .repo-pulse[data-live] .pulse-ring { animation: pulse-beat 2.4s cubic-bezier(.2, .6, .3, 1) infinite; }
90- }
91103 /* the right column: figures, then the CI state, over the commits */
92104 & .repo-head { grid-area: head; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3) var(--s5); min-width: 0; }
93105 /* the figures: a small label over a mono value, side by side */
@@ -105,7 +117,10 @@
105117 }
106118 & .repo-ci:hover { color: var(--ink-hi); text-decoration: underline; }
107119 & .repo-ci .ck { margin: 0; }
108- & .repo-commits { grid-area: recent; min-width: 0; margin: 0; padding: 0; list-style: none; font-size: var(--fs-s); }
120+ & .repo-commits {
121+ grid-area: recent; min-width: 0; margin: 0; padding: 0; list-style: none; font-size: var(--fs-s);
122+ min-height: calc(5 * (var(--lh) + 4px)); /* five rows, full or not */
123+ }
109124 & p.repo-commits { font-style: italic; }
110125 & .repo-commits li { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; padding: 2px 0; }
111126 & .rc-sha { flex: none; font-family: var(--mono); color: var(--ink-dim); text-decoration: none; }
@@ -117,12 +132,9 @@
117132 @media (max-width: 720px) {
118133 & .repo { grid-template-columns: minmax(0, 1fr); grid-template-areas: "main" "head" "recent"; grid-template-rows: none; }
119134 & .repo-head { flex-wrap: wrap; }
135+ & .repo-commits { min-height: 0; }
120136 }
121137 }
122- @keyframes pulse-beat {
123- 0% { opacity: .9; transform: scale(1); }
124- 70%, 100% { opacity: 0; transform: scale(3.2); }
125- }
126138 .list.passkeys {
127139 & .c-name { width: auto; font-weight: 600; }
128140 & .c-when { width: 120px; font-size: var(--fs-s); }
@@ -204,7 +216,7 @@
204216 min-height: var(--rowh); padding-right: var(--s4); font-size: var(--fs-s);
205217 }
206218 & > li:hover, & > li[data-cursor] { background: var(--bg-raised); }
207- & > li[data-cursor] { outline: 1px solid var(--ink-mute); outline-offset: -1px; }
219+ & > li[data-cursor] { outline: 1px solid var(--accent); outline-offset: -1px; }
208220 & > li:focus-visible { outline-offset: -2px; border-radius: 0; }
209221 & .t-main { display: flex; align-items: stretch; min-width: 0; min-height: var(--rowh); padding-left: var(--s3); }
210222 & .t-twig {
@@ -233,7 +245,7 @@
233245 width: 16px; height: 16px; margin: -8px 0 0 -8px; border: 0; border-radius: 0; background: var(--ink-dim);
234246 mask: url(icons/chevron.svg) center / contain no-repeat; transform: rotate(-90deg);
235247 }
236- & > li[data-kind="dir"][aria-expanded="true"] .t-node::after { transform: none; background: var(--ink-hi); }
248+ & > li[data-kind="dir"][aria-expanded="true"] .t-node::after { transform: none; background: var(--accent); }
237249 & > li[data-kind="link"] .t-node::after { border-radius: 1px; transform: rotate(45deg); }
238250 & > li[data-kind="module"] .t-node::after { border-radius: 1px; }
239251 & > li[aria-busy="true"] .t-node { opacity: .4; }
@@ -16,7 +16,7 @@
16 & tbody tr:first-child td { border-top: 0; }16 & tbody tr:first-child td { border-top: 0; }
17 & tbody tr:hover { background: var(--bg-raised); }17 & tbody tr:hover { background: var(--bg-raised); }
18 /* the cursor: the row raised, framed by one even line */18 /* the cursor: the row raised, framed by one even line */
19- & tbody tr[data-cursor] { background: var(--bg-raised); outline: 1px solid var(--ink-mute); outline-offset: -1px; }19+ & tbody tr[data-cursor] { background: var(--bg-raised); outline: 1px solid var(--accent); outline-offset: -1px; }
20 & tbody tr[hidden] { display: none; }20 & tbody tr[hidden] { display: none; }
21 & td > a { text-decoration: none; }21 & td > a { text-decoration: none; }
22 & td > a:first-child { color: var(--ink-hi); }22 & td > a:first-child { color: var(--ink-hi); }
@@ -53,41 +53,53 @@
53 padding: var(--s4); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);53 padding: var(--s4); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);
54 }54 }
55 & .repo:hover { background: var(--bg-raised); border-color: var(--border-strong); }55 & .repo:hover { background: var(--bg-raised); border-color: var(--border-strong); }
56- & .repo[data-cursor] { background: var(--bg-raised); border-color: var(--ink-mute); }56+ & .repo[data-cursor] { background: var(--bg-raised); border-color: var(--accent); }
57 & .repo[hidden] { display: none; }57 & .repo[hidden] { display: none; }
58 & .repo-main { grid-area: main; display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }58 & .repo-main { grid-area: main; display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
59 & .repo-name { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1) var(--s2); font-family: var(--mono); font-size: 17px; line-height: 26px; overflow-wrap: anywhere; }59 & .repo-name { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1) var(--s2); font-family: var(--mono); font-size: 17px; line-height: 26px; overflow-wrap: anywhere; }
60 & .repo-name a { color: var(--ink-hi); text-decoration: none; }60 & .repo-name a { color: var(--ink-hi); text-decoration: none; }
61 & .repo:hover .repo-name a, & .repo[data-cursor] .repo-name a { text-decoration: underline; }61 & .repo:hover .repo-name a, & .repo[data-cursor] .repo-name a { text-decoration: underline; }
62 & .repo-name .pill { margin-left: 0; }62 & .repo-name .pill { margin-left: 0; }
63+ /* Every card has the same height: two lines of description, one of topics
64+ (reserved when there are none), five of commits. */
63 & .repo-desc {65 & .repo-desc {
64- color: var(--ink-dim);66+ color: var(--ink-dim); min-height: calc(2 * var(--lh));
65 display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;67 display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
66 }68 }
67 & .repo-desc.mute { color: var(--ink-mute); font-style: italic; }69 & .repo-desc.mute { color: var(--ink-mute); font-style: italic; }
68- /* topics: small quiet chips, as on GitHub */70+ /* topics: small quiet chips, as on GitHub, on one line. The chips that do
69- & .repo-topics { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none; }71+ not fit wrap out of sight; ermite.fit (ermite.js) puts a … chip after
72+ the last one shown, the hidden topics in its title. */
73+ & .repo-topics {
74+ display: flex; flex-wrap: wrap; gap: 6px; height: 22px; overflow: hidden;
75+ margin: 0; padding: 0; list-style: none;
76+ }
70 & .repo-topics li {77 & .repo-topics li {
71- padding: 1px 9px; border: 1px solid var(--border-strong); border-radius: 999px;78+ flex: none; padding: 1px 9px; border: 1px solid var(--border-strong); border-radius: 999px;
72 font-family: var(--mono); font-size: 12px; line-height: 18px; color: var(--teal); background: var(--bg-raised);79 font-family: var(--mono); font-size: 12px; line-height: 18px; color: var(--teal); background: var(--bg-raised);
73 }80 }
81+ & .repo-topics li[hidden] { display: none; }
82+ & .repo-topics li.more { padding: 0; }
83+ & .repo-topics li.more button {
84+ padding: 1px 7px; border: 0; border-radius: 999px; background: none; cursor: pointer;
85+ font: inherit; line-height: 18px; color: var(--ink-mute);
86+ }
87+ & .repo-topics li.more button:hover { color: var(--accent); }
88+ & .repo-topics[data-open] { height: auto; }
74 /* the pulse: the commits per day over 90 days, smoothed, under the text */89 /* the pulse: the commits per day over 90 days, smoothed, under the text */
75- & .repo-pulse { position: relative; margin: auto 0 0; padding-top: var(--s2); }90+ & .repo-pulse { margin: auto 0 0; padding-top: var(--s2); }
91+ /* as tall as a drawn pulse: the padding comes on top of the ratio */
92+ & div.repo-pulse { aspect-ratio: 600 / 64; box-sizing: content-box; }
76 & .repo-pulse svg { display: block; width: 100%; height: auto; overflow: visible; }93 & .repo-pulse svg { display: block; width: 100%; height: auto; overflow: visible; }
77 & .pulse-grid { fill: none; stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 4; }94 & .pulse-grid { fill: none; stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 4; }
78 & .pulse-stop-top { stop-color: var(--teal); stop-opacity: .32; }95 & .pulse-stop-top { stop-color: var(--teal); stop-opacity: .32; }
79 & .pulse-stop-bottom { stop-color: var(--teal); stop-opacity: 0; }96 & .pulse-stop-bottom { stop-color: var(--teal); stop-opacity: 0; }
80 & .pulse-line { fill: none; stroke: var(--teal); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }97 & .pulse-line { fill: none; stroke: var(--teal); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
81 & .pulse-dot { fill: var(--teal); stroke: var(--bg-surface); stroke-width: 1.5; }98 & .pulse-dot { fill: var(--teal); stroke: var(--bg-surface); stroke-width: 1.5; }
82- & .pulse-ring { fill: none; stroke: var(--teal); stroke-width: 1.5; opacity: 0; transform-box: fill-box; transform-origin: center; }
83 & .repo:hover .pulse-dot, & .repo[data-cursor] .pulse-dot { stroke: var(--bg-raised); }99 & .repo:hover .pulse-dot, & .repo[data-cursor] .pulse-dot { stroke: var(--bg-raised); }
84- & .pulse-cap { display: flex; justify-content: space-between; margin-top: 2px; font-size: 10px; line-height: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-mute); }
85 & .repo-pulse:not([data-live]) .pulse-line { stroke: var(--ink-mute); }100 & .repo-pulse:not([data-live]) .pulse-line { stroke: var(--ink-mute); }
86 & .repo-pulse:not([data-live]) .pulse-dot { fill: var(--ink-mute); }101 & .repo-pulse:not([data-live]) .pulse-dot { fill: var(--ink-mute); }
87 & .repo-pulse:not([data-live]) .pulse-stop-top { stop-color: var(--ink-mute); stop-opacity: .18; }102 & .repo-pulse:not([data-live]) .pulse-stop-top { stop-color: var(--ink-mute); stop-opacity: .18; }
88- @media (prefers-reduced-motion: no-preference) {
89- & .repo-pulse[data-live] .pulse-ring { animation: pulse-beat 2.4s cubic-bezier(.2, .6, .3, 1) infinite; }
90- }
91 /* the right column: figures, then the CI state, over the commits */103 /* the right column: figures, then the CI state, over the commits */
92 & .repo-head { grid-area: head; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3) var(--s5); min-width: 0; }104 & .repo-head { grid-area: head; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3) var(--s5); min-width: 0; }
93 /* the figures: a small label over a mono value, side by side */105 /* the figures: a small label over a mono value, side by side */
@@ -105,7 +117,10 @@
105 }117 }
106 & .repo-ci:hover { color: var(--ink-hi); text-decoration: underline; }118 & .repo-ci:hover { color: var(--ink-hi); text-decoration: underline; }
107 & .repo-ci .ck { margin: 0; }119 & .repo-ci .ck { margin: 0; }
108- & .repo-commits { grid-area: recent; min-width: 0; margin: 0; padding: 0; list-style: none; font-size: var(--fs-s); }120+ & .repo-commits {
121+ grid-area: recent; min-width: 0; margin: 0; padding: 0; list-style: none; font-size: var(--fs-s);
122+ min-height: calc(5 * (var(--lh) + 4px)); /* five rows, full or not */
123+ }
109 & p.repo-commits { font-style: italic; }124 & p.repo-commits { font-style: italic; }
110 & .repo-commits li { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; padding: 2px 0; }125 & .repo-commits li { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; padding: 2px 0; }
111 & .rc-sha { flex: none; font-family: var(--mono); color: var(--ink-dim); text-decoration: none; }126 & .rc-sha { flex: none; font-family: var(--mono); color: var(--ink-dim); text-decoration: none; }
@@ -117,12 +132,9 @@
117 @media (max-width: 720px) {132 @media (max-width: 720px) {
118 & .repo { grid-template-columns: minmax(0, 1fr); grid-template-areas: "main" "head" "recent"; grid-template-rows: none; }133 & .repo { grid-template-columns: minmax(0, 1fr); grid-template-areas: "main" "head" "recent"; grid-template-rows: none; }
119 & .repo-head { flex-wrap: wrap; }134 & .repo-head { flex-wrap: wrap; }
135+ & .repo-commits { min-height: 0; }
120 }136 }
121 }137 }
122- @keyframes pulse-beat {
123- 0% { opacity: .9; transform: scale(1); }
124- 70%, 100% { opacity: 0; transform: scale(3.2); }
125- }
126 .list.passkeys {138 .list.passkeys {
127 & .c-name { width: auto; font-weight: 600; }139 & .c-name { width: auto; font-weight: 600; }
128 & .c-when { width: 120px; font-size: var(--fs-s); }140 & .c-when { width: 120px; font-size: var(--fs-s); }
@@ -204,7 +216,7 @@
204 min-height: var(--rowh); padding-right: var(--s4); font-size: var(--fs-s);216 min-height: var(--rowh); padding-right: var(--s4); font-size: var(--fs-s);
205 }217 }
206 & > li:hover, & > li[data-cursor] { background: var(--bg-raised); }218 & > li:hover, & > li[data-cursor] { background: var(--bg-raised); }
207- & > li[data-cursor] { outline: 1px solid var(--ink-mute); outline-offset: -1px; }219+ & > li[data-cursor] { outline: 1px solid var(--accent); outline-offset: -1px; }
208 & > li:focus-visible { outline-offset: -2px; border-radius: 0; }220 & > li:focus-visible { outline-offset: -2px; border-radius: 0; }
209 & .t-main { display: flex; align-items: stretch; min-width: 0; min-height: var(--rowh); padding-left: var(--s3); }221 & .t-main { display: flex; align-items: stretch; min-width: 0; min-height: var(--rowh); padding-left: var(--s3); }
210 & .t-twig {222 & .t-twig {
@@ -233,7 +245,7 @@
233 width: 16px; height: 16px; margin: -8px 0 0 -8px; border: 0; border-radius: 0; background: var(--ink-dim);245 width: 16px; height: 16px; margin: -8px 0 0 -8px; border: 0; border-radius: 0; background: var(--ink-dim);
234 mask: url(icons/chevron.svg) center / contain no-repeat; transform: rotate(-90deg);246 mask: url(icons/chevron.svg) center / contain no-repeat; transform: rotate(-90deg);
235 }247 }
236- & > li[data-kind="dir"][aria-expanded="true"] .t-node::after { transform: none; background: var(--ink-hi); }248+ & > li[data-kind="dir"][aria-expanded="true"] .t-node::after { transform: none; background: var(--accent); }
237 & > li[data-kind="link"] .t-node::after { border-radius: 1px; transform: rotate(45deg); }249 & > li[data-kind="link"] .t-node::after { border-radius: 1px; transform: rotate(45deg); }
238 & > li[data-kind="module"] .t-node::after { border-radius: 1px; }250 & > li[data-kind="module"] .t-node::after { border-radius: 1px; }
239 & > li[aria-busy="true"] .t-node { opacity: .4; }251 & > li[aria-busy="true"] .t-node { opacity: .4; }
M internal/adapters/web/static/css/40-summary.css +3 -1
@@ -28,6 +28,8 @@
2828 & ol { list-style: decimal; }
2929 & li + li { margin-top: var(--s1); }
3030 & li::marker { color: var(--ink-mute); }
31+ & a { color: var(--accent); text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent); }
32+ & a:hover { color: var(--accent); text-decoration-color: currentColor; }
3133 & li > ul, & li > ol, & li > p { margin-top: var(--s1); }
3234 & pre {
3335 padding: var(--s3) var(--s4); overflow-x: auto;
@@ -38,7 +40,7 @@
3840 padding: 1px 5px; font-size: .88em; overflow-wrap: anywhere;
3941 background: var(--bg-raised); border: 1px solid var(--border); border-radius: 4px;
4042 }
41- & blockquote { padding-left: var(--s4); border-left: 3px solid var(--border-strong); color: var(--ink-dim); }
43+ & blockquote { padding-left: var(--s4); border-left: 3px solid color-mix(in srgb, var(--accent) 45%, var(--border-strong)); color: var(--ink-dim); }
4244 & table { display: block; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-s); font-size: var(--fs-s); line-height: 20px; }
4345 & th, & td { padding: 6px var(--s3); white-space: nowrap; vertical-align: top; border-top: 1px solid var(--border); }
4446 & th { background: var(--bg-raised); border-top: 0; font-weight: 700; }
@@ -28,6 +28,8 @@
28 & ol { list-style: decimal; }28 & ol { list-style: decimal; }
29 & li + li { margin-top: var(--s1); }29 & li + li { margin-top: var(--s1); }
30 & li::marker { color: var(--ink-mute); }30 & li::marker { color: var(--ink-mute); }
31+ & a { color: var(--accent); text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent); }
32+ & a:hover { color: var(--accent); text-decoration-color: currentColor; }
31 & li > ul, & li > ol, & li > p { margin-top: var(--s1); }33 & li > ul, & li > ol, & li > p { margin-top: var(--s1); }
32 & pre {34 & pre {
33 padding: var(--s3) var(--s4); overflow-x: auto;35 padding: var(--s3) var(--s4); overflow-x: auto;
@@ -38,7 +40,7 @@
38 padding: 1px 5px; font-size: .88em; overflow-wrap: anywhere;40 padding: 1px 5px; font-size: .88em; overflow-wrap: anywhere;
39 background: var(--bg-raised); border: 1px solid var(--border); border-radius: 4px;41 background: var(--bg-raised); border: 1px solid var(--border); border-radius: 4px;
40 }42 }
41- & blockquote { padding-left: var(--s4); border-left: 3px solid var(--border-strong); color: var(--ink-dim); }43+ & blockquote { padding-left: var(--s4); border-left: 3px solid color-mix(in srgb, var(--accent) 45%, var(--border-strong)); color: var(--ink-dim); }
42 & table { display: block; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-s); font-size: var(--fs-s); line-height: 20px; }44 & table { display: block; overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-s); font-size: var(--fs-s); line-height: 20px; }
43 & th, & td { padding: 6px var(--s3); white-space: nowrap; vertical-align: top; border-top: 1px solid var(--border); }45 & th, & td { padding: 6px var(--s3); white-space: nowrap; vertical-align: top; border-top: 1px solid var(--border); }
44 & th { background: var(--bg-raised); border-top: 0; font-weight: 700; }46 & th { background: var(--bg-raised); border-top: 0; font-weight: 700; }
M internal/adapters/web/static/js/ermite.js +93 -2
@@ -12,6 +12,7 @@
1212 // 5. A handler that fails is shown in the hint badge.
1313 // 6. The lines a diff hides load in place (ermiteGaps; e and E).
1414 // 7. The service worker (ermiteInstall).
15+// 8. Chip lists kept on one line, with a … chip (ermiteFit).
1516 (() => {
1617 // Marks the page as scripted: what does nothing without scripts (the filters,
1718 // the "go to" and help buttons) is hidden until this class is set (see the
@@ -155,7 +156,12 @@
155156 const sel = input.dataset.filter || input.dataset.pick;
156157 if (!sel) return [];
157158 const rows = $$(sel);
158- for (const r of rows) r.hidden = !r.textContent.toLowerCase().includes(t);
159+ for (const r of rows) {
160+ const was = r.hidden;
161+ r.hidden = !r.textContent.toLowerCase().includes(t);
162+ // A row shown again lays out its chips again (it had no width hidden).
163+ if (was && !r.hidden) for (const ul of $$('[data-fit]', r)) window.ermiteFit?.fit(ul);
164+ }
159165 if (input.dataset.groups) {
160166 for (const g of $$(input.dataset.groups)) g.hidden = !rows.some((r) => !r.hidden && g.contains(r));
161167 }
@@ -641,6 +647,88 @@
641647 };
642648 }
643649
650+ // 8. A list of chips on one line ([data-fit], the topics of a home row):
651+ // the chips that wrap out of sight are hidden and a "…" button after the
652+ // last one shown names them; pressing it opens the list (data-open) on as
653+ // many lines as it needs. Self-contained: jstest/fit.test.mjs evaluates
654+ // this block alone.
655+ window.ermiteFit = (() => {
656+ const fit = (ul) => {
657+ if (ul.hasAttribute('data-open')) return;
658+ // The … button is kept across fits, so that a refit (a resize) does not
659+ // take the focus from it.
660+ let more = ul.querySelector(':scope > li.more');
661+ let btn = more?.firstElementChild;
662+ const focused = !!btn && document.activeElement === btn;
663+ more?.remove();
664+ const chips = [...ul.children];
665+ for (const li of chips) li.hidden = false;
666+ const top = chips[0]?.offsetTop;
667+ if (top === undefined || chips.at(-1).offsetTop === top) {
668+ // Everything fits now: a focused … button hands the focus to the
669+ // last chip.
670+ if (focused && chips.length) {
671+ chips.at(-1).setAttribute('tabindex', '-1');
672+ chips.at(-1).focus({ preventScroll: true });
673+ }
674+ return;
675+ }
676+ if (!more) {
677+ more = document.createElement('li');
678+ more.className = 'more';
679+ btn = document.createElement('button');
680+ btn.type = 'button';
681+ btn.textContent = '…';
682+ more.append(btn);
683+ }
684+ ul.append(more);
685+ if (focused) btn.focus({ preventScroll: true });
686+ // Keep the chips of the first line, then drop from the end until the
687+ // … chip fits there too.
688+ let shown = chips.findIndex((li) => li.offsetTop !== top);
689+ for (const li of chips.slice(shown)) li.hidden = true;
690+ while (shown > 0 && more.offsetTop !== top) chips[--shown].hidden = true;
691+ const rest = chips.slice(shown).map((li) => li.textContent);
692+ btn.title = rest.join(', ');
693+ btn.setAttribute('aria-label', 'show ' + rest.length + ' more: ' + rest.join(', '));
694+ };
695+ // open shows every chip of the list of the … button pressed.
696+ const open = (btn) => {
697+ const ul = btn.closest('[data-fit]');
698+ if (!ul) return;
699+ const item = btn.closest('li');
700+ // The focus goes to the first chip that was hidden, now shown.
701+ const next = [...ul.children].find((li) => li.hidden);
702+ ul.setAttribute('data-open', '');
703+ item.remove();
704+ for (const li of ul.children) li.hidden = false;
705+ if (next) {
706+ next.setAttribute('tabindex', '-1');
707+ next.focus({ preventScroll: true });
708+ }
709+ };
710+ const all = (root = document) => {
711+ for (const ul of root.querySelectorAll('[data-fit]')) fit(ul);
712+ };
713+ return { fit, all, open };
714+ })();
715+ addEventListener('click', (e) => {
716+ const btn = e.target.closest?.('[data-fit] > li.more > button');
717+ if (btn) window.ermiteFit.open(btn);
718+ });
719+ {
720+ window.ermiteFit.all();
721+ // Widths change with the window and when the fonts arrive; one frame per
722+ // burst. Always listening: a live update can bring the first list.
723+ let pending = 0;
724+ const again = () => {
725+ if (pending) return;
726+ pending = requestAnimationFrame(() => { pending = 0; window.ermiteFit.all(); });
727+ };
728+ addEventListener('resize', again);
729+ document.fonts?.ready.then(again);
730+ }
731+
644732 // 5. A failure of a handler or of a request (htmx logs it to the console
645733 // already) is shown, so that a key that does nothing is not a mystery.
646734 addEventListener('htmx:error', (e) => {
@@ -712,6 +800,8 @@ window.ermiteLive = (() => {
712800 document.getElementById('main')?.focus({ preventScroll: true });
713801 };
714802 const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; };
803+ // fitIn lays out the one-line chip lists of a fragment now in the page.
804+ const fitIn = (el) => { for (const ul of el.querySelectorAll('[data-fit]')) window.ermiteFit?.fit(ul); };
715805 return {
716806 // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows
717807 // only if the stream is still down LOST_AFTER later.
@@ -741,7 +831,7 @@ window.ermiteLive = (() => {
741831 }
742832 if (!old) {
743833 const rows = document.getElementById('repo-rows');
744- if (rows && el.id.startsWith('repo-')) { rows.prepend(el); window.htmx?.process(el); }
834+ if (rows && el.id.startsWith('repo-')) { rows.prepend(el); window.htmx?.process(el); fitIn(el); }
745835 continue;
746836 }
747837 if (old.hasAttribute('data-slot')) {
@@ -752,6 +842,7 @@ window.ermiteLive = (() => {
752842 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');
753843 old.replaceWith(el);
754844 window.htmx?.process(el);
845+ fitIn(el);
755846 if (had) refocus(el, had);
756847 }
757848 // The rows may have changed under an active filter: apply it again and
@@ -12,6 +12,7 @@
12 // 5. A handler that fails is shown in the hint badge.12 // 5. A handler that fails is shown in the hint badge.
13 // 6. The lines a diff hides load in place (ermiteGaps; e and E).13 // 6. The lines a diff hides load in place (ermiteGaps; e and E).
14 // 7. The service worker (ermiteInstall).14 // 7. The service worker (ermiteInstall).
15+// 8. Chip lists kept on one line, with a … chip (ermiteFit).
15 (() => {16 (() => {
16 // Marks the page as scripted: what does nothing without scripts (the filters,17 // Marks the page as scripted: what does nothing without scripts (the filters,
17 // the "go to" and help buttons) is hidden until this class is set (see the18 // the "go to" and help buttons) is hidden until this class is set (see the
@@ -155,7 +156,12 @@
155 const sel = input.dataset.filter || input.dataset.pick;156 const sel = input.dataset.filter || input.dataset.pick;
156 if (!sel) return [];157 if (!sel) return [];
157 const rows = $$(sel);158 const rows = $$(sel);
158- for (const r of rows) r.hidden = !r.textContent.toLowerCase().includes(t);159+ for (const r of rows) {
160+ const was = r.hidden;
161+ r.hidden = !r.textContent.toLowerCase().includes(t);
162+ // A row shown again lays out its chips again (it had no width hidden).
163+ if (was && !r.hidden) for (const ul of $$('[data-fit]', r)) window.ermiteFit?.fit(ul);
164+ }
159 if (input.dataset.groups) {165 if (input.dataset.groups) {
160 for (const g of $$(input.dataset.groups)) g.hidden = !rows.some((r) => !r.hidden && g.contains(r));166 for (const g of $$(input.dataset.groups)) g.hidden = !rows.some((r) => !r.hidden && g.contains(r));
161 }167 }
@@ -641,6 +647,88 @@
641 };647 };
642 }648 }
643 649
650+ // 8. A list of chips on one line ([data-fit], the topics of a home row):
651+ // the chips that wrap out of sight are hidden and a "…" button after the
652+ // last one shown names them; pressing it opens the list (data-open) on as
653+ // many lines as it needs. Self-contained: jstest/fit.test.mjs evaluates
654+ // this block alone.
655+ window.ermiteFit = (() => {
656+ const fit = (ul) => {
657+ if (ul.hasAttribute('data-open')) return;
658+ // The … button is kept across fits, so that a refit (a resize) does not
659+ // take the focus from it.
660+ let more = ul.querySelector(':scope > li.more');
661+ let btn = more?.firstElementChild;
662+ const focused = !!btn && document.activeElement === btn;
663+ more?.remove();
664+ const chips = [...ul.children];
665+ for (const li of chips) li.hidden = false;
666+ const top = chips[0]?.offsetTop;
667+ if (top === undefined || chips.at(-1).offsetTop === top) {
668+ // Everything fits now: a focused … button hands the focus to the
669+ // last chip.
670+ if (focused && chips.length) {
671+ chips.at(-1).setAttribute('tabindex', '-1');
672+ chips.at(-1).focus({ preventScroll: true });
673+ }
674+ return;
675+ }
676+ if (!more) {
677+ more = document.createElement('li');
678+ more.className = 'more';
679+ btn = document.createElement('button');
680+ btn.type = 'button';
681+ btn.textContent = '…';
682+ more.append(btn);
683+ }
684+ ul.append(more);
685+ if (focused) btn.focus({ preventScroll: true });
686+ // Keep the chips of the first line, then drop from the end until the
687+ // … chip fits there too.
688+ let shown = chips.findIndex((li) => li.offsetTop !== top);
689+ for (const li of chips.slice(shown)) li.hidden = true;
690+ while (shown > 0 && more.offsetTop !== top) chips[--shown].hidden = true;
691+ const rest = chips.slice(shown).map((li) => li.textContent);
692+ btn.title = rest.join(', ');
693+ btn.setAttribute('aria-label', 'show ' + rest.length + ' more: ' + rest.join(', '));
694+ };
695+ // open shows every chip of the list of the … button pressed.
696+ const open = (btn) => {
697+ const ul = btn.closest('[data-fit]');
698+ if (!ul) return;
699+ const item = btn.closest('li');
700+ // The focus goes to the first chip that was hidden, now shown.
701+ const next = [...ul.children].find((li) => li.hidden);
702+ ul.setAttribute('data-open', '');
703+ item.remove();
704+ for (const li of ul.children) li.hidden = false;
705+ if (next) {
706+ next.setAttribute('tabindex', '-1');
707+ next.focus({ preventScroll: true });
708+ }
709+ };
710+ const all = (root = document) => {
711+ for (const ul of root.querySelectorAll('[data-fit]')) fit(ul);
712+ };
713+ return { fit, all, open };
714+ })();
715+ addEventListener('click', (e) => {
716+ const btn = e.target.closest?.('[data-fit] > li.more > button');
717+ if (btn) window.ermiteFit.open(btn);
718+ });
719+ {
720+ window.ermiteFit.all();
721+ // Widths change with the window and when the fonts arrive; one frame per
722+ // burst. Always listening: a live update can bring the first list.
723+ let pending = 0;
724+ const again = () => {
725+ if (pending) return;
726+ pending = requestAnimationFrame(() => { pending = 0; window.ermiteFit.all(); });
727+ };
728+ addEventListener('resize', again);
729+ document.fonts?.ready.then(again);
730+ }
731+
644 // 5. A failure of a handler or of a request (htmx logs it to the console732 // 5. A failure of a handler or of a request (htmx logs it to the console
645 // already) is shown, so that a key that does nothing is not a mystery.733 // already) is shown, so that a key that does nothing is not a mystery.
646 addEventListener('htmx:error', (e) => {734 addEventListener('htmx:error', (e) => {
@@ -712,6 +800,8 @@ window.ermiteLive = (() => {
712 document.getElementById('main')?.focus({ preventScroll: true });800 document.getElementById('main')?.focus({ preventScroll: true });
713 };801 };
714 const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; };802 const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; };
803+ // fitIn lays out the one-line chip lists of a fragment now in the page.
804+ const fitIn = (el) => { for (const ul of el.querySelectorAll('[data-fit]')) window.ermiteFit?.fit(ul); };
715 return {805 return {
716 // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows806 // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows
717 // only if the stream is still down LOST_AFTER later.807 // only if the stream is still down LOST_AFTER later.
@@ -741,7 +831,7 @@ window.ermiteLive = (() => {
741 }831 }
742 if (!old) {832 if (!old) {
743 const rows = document.getElementById('repo-rows');833 const rows = document.getElementById('repo-rows');
744- if (rows && el.id.startsWith('repo-')) { rows.prepend(el); window.htmx?.process(el); }834+ if (rows && el.id.startsWith('repo-')) { rows.prepend(el); window.htmx?.process(el); fitIn(el); }
745 continue;835 continue;
746 }836 }
747 if (old.hasAttribute('data-slot')) {837 if (old.hasAttribute('data-slot')) {
@@ -752,6 +842,7 @@ window.ermiteLive = (() => {
752 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');842 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');
753 old.replaceWith(el);843 old.replaceWith(el);
754 window.htmx?.process(el);844 window.htmx?.process(el);
845+ fitIn(el);
755 if (had) refocus(el, had);846 if (had) refocus(el, had);
756 }847 }
757 // The rows may have changed under an active filter: apply it again and848 // The rows may have changed under an active filter: apply it again and
M internal/app/testdata/web/home.html +12 -12
@@ -72,6 +72,8 @@
7272 <a href="/demo">demo</a>
7373 </h2>
7474 <p class="repo-desc">A demo repository</p>
75+<div class="repo-topics" aria-hidden="true">
76+</div>
7577 <figure class="repo-pulse">
7678 <svg viewBox="0 0 600 64" role="img" aria-label="Activity: 0 commits in the last 90 days" focusable="false">
7779 <defs>
@@ -88,15 +90,9 @@
8890 </path>
8991 <path class="pulse-line" d="M0 58 C98.3 58 491.7 58 590 58" vector-effect="non-scaling-stroke">
9092 </path>
91-<circle class="pulse-ring" cx="590" cy="58" r="4">
92-</circle>
9393 <circle class="pulse-dot" cx="590" cy="58" r="3.5">
9494 </circle>
9595 </svg>
96-<figcaption class="pulse-cap" aria-hidden="true">
97-<span>90 days ago</span>
98-<span>today</span>
99-</figcaption>
10096 </figure>
10197 </div>
10298 <div class="repo-head">
@@ -109,18 +105,18 @@
109105 <dt>last 30 days</dt>
110106 <dd>0</dd>
111107 </div>
112-<div class="rs-tag">
113-<dt>latest tag</dt>
114-<dd>
115-<a href="/demo/tree/SHA40" title="v0.2.0 · made AGO">v0.2.0</a>
116-</dd>
117-</div>
118108 <div>
119109 <dt>last activity</dt>
120110 <dd>
121111 <time datetime="STAMP" title="STAMP">AGO</time>
122112 </dd>
123113 </div>
114+<div class="rs-tag">
115+<dt>last tag</dt>
116+<dd>
117+<a href="/demo/tree/SHA40" title="v0.2.0 · made AGO">v0.2.0</a>
118+</dd>
119+</div>
124120 </dl>
125121 </div>
126122 <ul class="repo-commits" aria-label="Recent commits of demo">
@@ -142,6 +138,10 @@
142138 <a href="/quiet">quiet</a>
143139 </h2>
144140 <p class="repo-desc mute">no description</p>
141+<div class="repo-topics" aria-hidden="true">
142+</div>
143+<div class="repo-pulse" aria-hidden="true">
144+</div>
145145 </div>
146146 <div class="repo-head">
147147 <dl class="repo-stats" aria-label="Figures of quiet">
@@ -72,6 +72,8 @@
72 <a href="/demo">demo</a>72 <a href="/demo">demo</a>
73 </h2>73 </h2>
74 <p class="repo-desc">A demo repository</p>74 <p class="repo-desc">A demo repository</p>
75+<div class="repo-topics" aria-hidden="true">
76+</div>
75 <figure class="repo-pulse">77 <figure class="repo-pulse">
76 <svg viewBox="0 0 600 64" role="img" aria-label="Activity: 0 commits in the last 90 days" focusable="false">78 <svg viewBox="0 0 600 64" role="img" aria-label="Activity: 0 commits in the last 90 days" focusable="false">
77 <defs>79 <defs>
@@ -88,15 +90,9 @@
88 </path>90 </path>
89 <path class="pulse-line" d="M0 58 C98.3 58 491.7 58 590 58" vector-effect="non-scaling-stroke">91 <path class="pulse-line" d="M0 58 C98.3 58 491.7 58 590 58" vector-effect="non-scaling-stroke">
90 </path>92 </path>
91-<circle class="pulse-ring" cx="590" cy="58" r="4">
92-</circle>
93 <circle class="pulse-dot" cx="590" cy="58" r="3.5">93 <circle class="pulse-dot" cx="590" cy="58" r="3.5">
94 </circle>94 </circle>
95 </svg>95 </svg>
96-<figcaption class="pulse-cap" aria-hidden="true">
97-<span>90 days ago</span>
98-<span>today</span>
99-</figcaption>
100 </figure>96 </figure>
101 </div>97 </div>
102 <div class="repo-head">98 <div class="repo-head">
@@ -109,18 +105,18 @@
109 <dt>last 30 days</dt>105 <dt>last 30 days</dt>
110 <dd>0</dd>106 <dd>0</dd>
111 </div>107 </div>
112-<div class="rs-tag">
113-<dt>latest tag</dt>
114-<dd>
115-<a href="/demo/tree/SHA40" title="v0.2.0 · made AGO">v0.2.0</a>
116-</dd>
117-</div>
118 <div>108 <div>
119 <dt>last activity</dt>109 <dt>last activity</dt>
120 <dd>110 <dd>
121 <time datetime="STAMP" title="STAMP">AGO</time>111 <time datetime="STAMP" title="STAMP">AGO</time>
122 </dd>112 </dd>
123 </div>113 </div>
114+<div class="rs-tag">
115+<dt>last tag</dt>
116+<dd>
117+<a href="/demo/tree/SHA40" title="v0.2.0 · made AGO">v0.2.0</a>
118+</dd>
119+</div>
124 </dl>120 </dl>
125 </div>121 </div>
126 <ul class="repo-commits" aria-label="Recent commits of demo">122 <ul class="repo-commits" aria-label="Recent commits of demo">
@@ -142,6 +138,10 @@
142 <a href="/quiet">quiet</a>138 <a href="/quiet">quiet</a>
143 </h2>139 </h2>
144 <p class="repo-desc mute">no description</p>140 <p class="repo-desc mute">no description</p>
141+<div class="repo-topics" aria-hidden="true">
142+</div>
143+<div class="repo-pulse" aria-hidden="true">
144+</div>
145 </div>145 </div>
146 <div class="repo-head">146 <div class="repo-head">
147 <dl class="repo-stats" aria-label="Figures of quiet">147 <dl class="repo-stats" aria-label="Figures of quiet">

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 44

↑↓ moveenter openesc close