detached e95eebd

feat(web): the home page lists repositories as cards (#56)

commit
e95eebda317302f13d3d13c1fc33a5477e4bfa1b
author
brice · · 2026-10-18 06:17 UTC
committer
bnema · · 2026-10-18 06:17 UTC
parent
8e01339
signature
SSH UnverifiedSHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivA The key is unknown
14 files · +770 -672
M internal/adapters/web/components.templ +24 -15
@@ -32,26 +32,35 @@ templ CloneBlock(c Clone) {
3232 </div>
3333 }
3434
35-// HomeRow is one row of the home list. Its id is the target of SSE swaps.
35+// HomeRowView is the card of a repository in the home list. Its id is the
36+// target of SSE swaps; the name link comes first, `enter` follows it.
3637 templ HomeRowView(r HomeRow) {
37- <tr id={ r.ID } data-cursor?={ r.Cursor }>
38- <td class="cur" aria-hidden="true"></td>
39- <td class="c-name">
38+ <li id={ r.ID } class="repo" data-cursor?={ r.Cursor }>
39+ <h2 class="repo-name">
4040 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
41+ if r.Private {
42+ <span class="pill">private</span>
43+ }
4144 if r.Fresh {
4245 <span class="pill new">new push</span>
4346 }
44- </td>
45- <td class="c-desc dim">{ r.Desc }</td>
46- <td class="c-when nowrap">
47- <time
48- if r.PushedAt != "" {
49- datetime={ r.PushedAt }
50- }
51- >{ r.Pushed }</time>
52- </td>
53- <td class="c-ref">{ r.Branch }</td>
54- </tr>
47+ </h2>
48+ if r.Desc != "" {
49+ <p class="repo-desc">{ r.Desc }</p>
50+ } else {
51+ <p class="repo-desc mute">no description</p>
52+ }
53+ <p class="repo-meta">
54+ if r.Branch != "" {
55+ <span class="repo-branch"><span class="ico i-branch" aria-hidden="true"></span>{ r.Branch }</span>
56+ }
57+ if r.PushedAt != "" {
58+ <span>pushed <time datetime={ r.PushedAt } title={ r.PushedAt }>{ r.Pushed }</time></span>
59+ } else {
60+ <span>never pushed</span>
61+ }
62+ </p>
63+ </li>
5564 }
5665
5766 // Outline is the README outline; levels indent by 2ch, the h1 is bold.
@@ -32,26 +32,35 @@ templ CloneBlock(c Clone) {
32 </div>32 </div>
33 }33 }
34 34
35-// HomeRow is one row of the home list. Its id is the target of SSE swaps.35+// HomeRowView is the card of a repository in the home list. Its id is the
36+// target of SSE swaps; the name link comes first, `enter` follows it.
36 templ HomeRowView(r HomeRow) {37 templ HomeRowView(r HomeRow) {
37- <tr id={ r.ID } data-cursor?={ r.Cursor }>38+ <li id={ r.ID } class="repo" data-cursor?={ r.Cursor }>
38- <td class="cur" aria-hidden="true"></td>39+ <h2 class="repo-name">
39- <td class="c-name">
40 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>40 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
41+ if r.Private {
42+ <span class="pill">private</span>
43+ }
41 if r.Fresh {44 if r.Fresh {
42 <span class="pill new">new push</span>45 <span class="pill new">new push</span>
43 }46 }
44- </td>47+ </h2>
45- <td class="c-desc dim">{ r.Desc }</td>48+ if r.Desc != "" {
46- <td class="c-when nowrap">49+ <p class="repo-desc">{ r.Desc }</p>
47- <time50+ } else {
48- if r.PushedAt != "" {51+ <p class="repo-desc mute">no description</p>
49- datetime={ r.PushedAt }52+ }
50- }53+ <p class="repo-meta">
51- >{ r.Pushed }</time>54+ if r.Branch != "" {
52- </td>55+ <span class="repo-branch"><span class="ico i-branch" aria-hidden="true"></span>{ r.Branch }</span>
53- <td class="c-ref">{ r.Branch }</td>56+ }
54- </tr>57+ if r.PushedAt != "" {
58+ <span>pushed <time datetime={ r.PushedAt } title={ r.PushedAt }>{ r.Pushed }</time></span>
59+ } else {
60+ <span>never pushed</span>
61+ }
62+ </p>
63+ </li>
55 }64 }
56 65
57 // Outline is the README outline; levels indent by 2ch, the h1 is bold.66 // Outline is the README outline; levels indent by 2ch, the h1 is bold.
M internal/adapters/web/components_templ.go +206 -160
@@ -147,7 +147,8 @@ func CloneBlock(c Clone) templ.Component {
147147 })
148148 }
149149
150-// HomeRow is one row of the home list. Its id is the target of SSE swaps.
150+// HomeRowView is the card of a repository in the home list. Its id is the
151+// target of SSE swaps; the name link comes first, `enter` follows it.
151152 func HomeRowView(r HomeRow) templ.Component {
152153 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
153154 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -169,20 +170,20 @@ func HomeRowView(r HomeRow) templ.Component {
169170 templ_7745c5c3_Var7 = templ.NopComponent
170171 }
171172 ctx = templ.ClearChildren(ctx)
172- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<tr id=\"")
173+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<li id=\"")
173174 if templ_7745c5c3_Err != nil {
174175 return templ_7745c5c3_Err
175176 }
176177 var templ_7745c5c3_Var8 string
177178 templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ID)
178179 if templ_7745c5c3_Err != nil {
179- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 37, Col: 14}
180+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 38, Col: 14}
180181 }
181182 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
182183 if templ_7745c5c3_Err != nil {
183184 return templ_7745c5c3_Err
184185 }
185- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"")
186+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" class=\"repo\"")
186187 if templ_7745c5c3_Err != nil {
187188 return templ_7745c5c3_Err
188189 }
@@ -192,7 +193,7 @@ func HomeRowView(r HomeRow) templ.Component {
192193 return templ_7745c5c3_Err
193194 }
194195 }
195- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-name\"><a href=\"")
196+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "><h2 class=\"repo-name\"><a href=\"")
196197 if templ_7745c5c3_Err != nil {
197198 return templ_7745c5c3_Err
198199 }
@@ -222,75 +223,120 @@ func HomeRowView(r HomeRow) templ.Component {
222223 if templ_7745c5c3_Err != nil {
223224 return templ_7745c5c3_Err
224225 }
226+ if r.Private {
227+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span class=\"pill\">private</span> ")
228+ if templ_7745c5c3_Err != nil {
229+ return templ_7745c5c3_Err
230+ }
231+ }
225232 if r.Fresh {
226- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span class=\"pill new\">new push</span>")
233+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<span class=\"pill new\">new push</span>")
227234 if templ_7745c5c3_Err != nil {
228235 return templ_7745c5c3_Err
229236 }
230237 }
231- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td><td class=\"c-desc dim\">")
238+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</h2>")
232239 if templ_7745c5c3_Err != nil {
233240 return templ_7745c5c3_Err
234241 }
235- var templ_7745c5c3_Var11 string
236- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(r.Desc)
237- if templ_7745c5c3_Err != nil {
238- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 45, Col: 33}
239- }
240- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
241- if templ_7745c5c3_Err != nil {
242- return templ_7745c5c3_Err
242+ if r.Desc != "" {
243+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"repo-desc\">")
244+ if templ_7745c5c3_Err != nil {
245+ return templ_7745c5c3_Err
246+ }
247+ var templ_7745c5c3_Var11 string
248+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(r.Desc)
249+ if templ_7745c5c3_Err != nil {
250+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 49, Col: 32}
251+ }
252+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
253+ if templ_7745c5c3_Err != nil {
254+ return templ_7745c5c3_Err
255+ }
256+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</p>")
257+ if templ_7745c5c3_Err != nil {
258+ return templ_7745c5c3_Err
259+ }
260+ } else {
261+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<p class=\"repo-desc mute\">no description</p>")
262+ if templ_7745c5c3_Err != nil {
263+ return templ_7745c5c3_Err
264+ }
243265 }
244- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</td><td class=\"c-when nowrap\"><time")
266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p class=\"repo-meta\">")
245267 if templ_7745c5c3_Err != nil {
246268 return templ_7745c5c3_Err
247269 }
248- if r.PushedAt != "" {
249- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " datetime=\"")
270+ if r.Branch != "" {
271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<span class=\"repo-branch\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span>")
250272 if templ_7745c5c3_Err != nil {
251273 return templ_7745c5c3_Err
252274 }
253275 var templ_7745c5c3_Var12 string
254- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.PushedAt)
276+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(r.Branch)
255277 if templ_7745c5c3_Err != nil {
256- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 49, Col: 26}
278+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 55, Col: 93}
257279 }
258- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
280+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
259281 if templ_7745c5c3_Err != nil {
260282 return templ_7745c5c3_Err
261283 }
262- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"")
284+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</span> ")
263285 if templ_7745c5c3_Err != nil {
264286 return templ_7745c5c3_Err
265287 }
266288 }
267- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, ">")
268- if templ_7745c5c3_Err != nil {
269- return templ_7745c5c3_Err
270- }
271- var templ_7745c5c3_Var13 string
272- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(r.Pushed)
273- if templ_7745c5c3_Err != nil {
274- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 51, Col: 14}
275- }
276- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
277- if templ_7745c5c3_Err != nil {
278- return templ_7745c5c3_Err
279- }
280- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</time></td><td class=\"c-ref\">")
281- if templ_7745c5c3_Err != nil {
282- return templ_7745c5c3_Err
283- }
284- var templ_7745c5c3_Var14 string
285- templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(r.Branch)
286- if templ_7745c5c3_Err != nil {
287- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 53, Col: 30}
288- }
289- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
290- if templ_7745c5c3_Err != nil {
291- return templ_7745c5c3_Err
289+ if r.PushedAt != "" {
290+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<span>pushed <time datetime=\"")
291+ if templ_7745c5c3_Err != nil {
292+ return templ_7745c5c3_Err
293+ }
294+ var templ_7745c5c3_Var13 string
295+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.PushedAt)
296+ if templ_7745c5c3_Err != nil {
297+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 58, Col: 44}
298+ }
299+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
300+ if templ_7745c5c3_Err != nil {
301+ return templ_7745c5c3_Err
302+ }
303+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" title=\"")
304+ if templ_7745c5c3_Err != nil {
305+ return templ_7745c5c3_Err
306+ }
307+ var templ_7745c5c3_Var14 string
308+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.PushedAt)
309+ if templ_7745c5c3_Err != nil {
310+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 58, Col: 65}
311+ }
312+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
313+ if templ_7745c5c3_Err != nil {
314+ return templ_7745c5c3_Err
315+ }
316+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\">")
317+ if templ_7745c5c3_Err != nil {
318+ return templ_7745c5c3_Err
319+ }
320+ var templ_7745c5c3_Var15 string
321+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(r.Pushed)
322+ if templ_7745c5c3_Err != nil {
323+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 58, Col: 78}
324+ }
325+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
326+ if templ_7745c5c3_Err != nil {
327+ return templ_7745c5c3_Err
328+ }
329+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</time></span>")
330+ if templ_7745c5c3_Err != nil {
331+ return templ_7745c5c3_Err
332+ }
333+ } else {
334+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<span>never pushed</span>")
335+ if templ_7745c5c3_Err != nil {
336+ return templ_7745c5c3_Err
337+ }
292338 }
293- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</td></tr>")
339+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p></li>")
294340 if templ_7745c5c3_Err != nil {
295341 return templ_7745c5c3_Err
296342 }
@@ -315,66 +361,66 @@ func Outline(headings []Heading) templ.Component {
315361 }()
316362 }
317363 ctx = templ.InitializeContext(ctx)
318- templ_7745c5c3_Var15 := templ.GetChildren(ctx)
319- if templ_7745c5c3_Var15 == nil {
320- templ_7745c5c3_Var15 = templ.NopComponent
364+ templ_7745c5c3_Var16 := templ.GetChildren(ctx)
365+ if templ_7745c5c3_Var16 == nil {
366+ templ_7745c5c3_Var16 = templ.NopComponent
321367 }
322368 ctx = templ.ClearChildren(ctx)
323- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<nav class=\"outline\" aria-label=\"Outline\">")
369+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<nav class=\"outline\" aria-label=\"Outline\">")
324370 if templ_7745c5c3_Err != nil {
325371 return templ_7745c5c3_Err
326372 }
327373 for _, h := range headings {
328- var templ_7745c5c3_Var16 = []any{"l" + itoa(h.Level)}
329- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)
374+ var templ_7745c5c3_Var17 = []any{"l" + itoa(h.Level)}
375+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
330376 if templ_7745c5c3_Err != nil {
331377 return templ_7745c5c3_Err
332378 }
333- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<a class=\"")
379+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<a class=\"")
334380 if templ_7745c5c3_Err != nil {
335381 return templ_7745c5c3_Err
336382 }
337- var templ_7745c5c3_Var17 string
338- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var16).String())
383+ var templ_7745c5c3_Var18 string
384+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
339385 if templ_7745c5c3_Err != nil {
340386 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 1, Col: 0}
341387 }
342- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
388+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
343389 if templ_7745c5c3_Err != nil {
344390 return templ_7745c5c3_Err
345391 }
346- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" href=\"")
392+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" href=\"")
347393 if templ_7745c5c3_Err != nil {
348394 return templ_7745c5c3_Err
349395 }
350- var templ_7745c5c3_Var18 templ.SafeURL
351- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))
396+ var templ_7745c5c3_Var19 templ.SafeURL
397+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))
352398 if templ_7745c5c3_Err != nil {
353- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 61, Col: 68}
399+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 70, Col: 68}
354400 }
355- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
401+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
356402 if templ_7745c5c3_Err != nil {
357403 return templ_7745c5c3_Err
358404 }
359- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\">")
405+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\">")
360406 if templ_7745c5c3_Err != nil {
361407 return templ_7745c5c3_Err
362408 }
363- var templ_7745c5c3_Var19 string
364- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)
409+ var templ_7745c5c3_Var20 string
410+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)
365411 if templ_7745c5c3_Err != nil {
366- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 61, Col: 79}
412+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 70, Col: 79}
367413 }
368- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
414+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
369415 if templ_7745c5c3_Err != nil {
370416 return templ_7745c5c3_Err
371417 }
372- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</a>")
418+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</a>")
373419 if templ_7745c5c3_Err != nil {
374420 return templ_7745c5c3_Err
375421 }
376422 }
377- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</nav>")
423+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</nav>")
378424 if templ_7745c5c3_Err != nil {
379425 return templ_7745c5c3_Err
380426 }
@@ -399,25 +445,25 @@ func ReadmeCard(r Readme) templ.Component {
399445 }()
400446 }
401447 ctx = templ.InitializeContext(ctx)
402- templ_7745c5c3_Var20 := templ.GetChildren(ctx)
403- if templ_7745c5c3_Var20 == nil {
404- templ_7745c5c3_Var20 = templ.NopComponent
448+ templ_7745c5c3_Var21 := templ.GetChildren(ctx)
449+ if templ_7745c5c3_Var21 == nil {
450+ templ_7745c5c3_Var21 = templ.NopComponent
405451 }
406452 ctx = templ.ClearChildren(ctx)
407- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")
453+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")
408454 if templ_7745c5c3_Err != nil {
409455 return templ_7745c5c3_Err
410456 }
411- var templ_7745c5c3_Var21 string
412- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
457+ var templ_7745c5c3_Var22 string
458+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
413459 if templ_7745c5c3_Err != nil {
414- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 71, Col: 11}
460+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 11}
415461 }
416- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
462+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
417463 if templ_7745c5c3_Err != nil {
418464 return templ_7745c5c3_Err
419465 }
420- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</h2>")
466+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</h2>")
421467 if templ_7745c5c3_Err != nil {
422468 return templ_7745c5c3_Err
423469 }
@@ -425,7 +471,7 @@ func ReadmeCard(r Readme) templ.Component {
425471 if templ_7745c5c3_Err != nil {
426472 return templ_7745c5c3_Err
427473 }
428- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</section>")
474+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</section>")
429475 if templ_7745c5c3_Err != nil {
430476 return templ_7745c5c3_Err
431477 }
@@ -451,43 +497,43 @@ func ReadmeView(r Readme) templ.Component {
451497 }()
452498 }
453499 ctx = templ.InitializeContext(ctx)
454- templ_7745c5c3_Var22 := templ.GetChildren(ctx)
455- if templ_7745c5c3_Var22 == nil {
456- templ_7745c5c3_Var22 = templ.NopComponent
500+ templ_7745c5c3_Var23 := templ.GetChildren(ctx)
501+ if templ_7745c5c3_Var23 == nil {
502+ templ_7745c5c3_Var23 = templ.NopComponent
457503 }
458504 ctx = templ.ClearChildren(ctx)
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<article class=\"prose\" aria-label=\"")
505+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<article class=\"prose\" aria-label=\"")
460506 if templ_7745c5c3_Err != nil {
461507 return templ_7745c5c3_Err
462508 }
463- var templ_7745c5c3_Var23 string
464- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)
509+ var templ_7745c5c3_Var24 string
510+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)
465511 if templ_7745c5c3_Err != nil {
466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 43}
512+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 43}
467513 }
468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
514+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24)
469515 if templ_7745c5c3_Err != nil {
470516 return templ_7745c5c3_Err
471517 }
472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" hx-ignore>")
518+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\" hx-ignore>")
473519 if templ_7745c5c3_Err != nil {
474520 return templ_7745c5c3_Err
475521 }
476522 if r.Note != "" {
477- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<p class=\"dim\">")
523+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<p class=\"dim\">")
478524 if templ_7745c5c3_Err != nil {
479525 return templ_7745c5c3_Err
480526 }
481- var templ_7745c5c3_Var24 string
482- templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)
527+ var templ_7745c5c3_Var25 string
528+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)
483529 if templ_7745c5c3_Err != nil {
484- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 82, Col: 26}
530+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 91, Col: 26}
485531 }
486- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
532+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
487533 if templ_7745c5c3_Err != nil {
488534 return templ_7745c5c3_Err
489535 }
490- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</p>")
536+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</p>")
491537 if templ_7745c5c3_Err != nil {
492538 return templ_7745c5c3_Err
493539 }
@@ -497,7 +543,7 @@ func ReadmeView(r Readme) templ.Component {
497543 return templ_7745c5c3_Err
498544 }
499545 }
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</article>")
546+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</article>")
501547 if templ_7745c5c3_Err != nil {
502548 return templ_7745c5c3_Err
503549 }
@@ -523,133 +569,133 @@ func CommitRows(rows []CommitRow) templ.Component {
523569 }()
524570 }
525571 ctx = templ.InitializeContext(ctx)
526- templ_7745c5c3_Var25 := templ.GetChildren(ctx)
527- if templ_7745c5c3_Var25 == nil {
528- templ_7745c5c3_Var25 = templ.NopComponent
572+ templ_7745c5c3_Var26 := templ.GetChildren(ctx)
573+ if templ_7745c5c3_Var26 == nil {
574+ templ_7745c5c3_Var26 = templ.NopComponent
529575 }
530576 ctx = templ.ClearChildren(ctx)
531- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")
577+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")
532578 if templ_7745c5c3_Err != nil {
533579 return templ_7745c5c3_Err
534580 }
535581 for _, c := range rows {
536- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<tr")
582+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<tr")
537583 if templ_7745c5c3_Err != nil {
538584 return templ_7745c5c3_Err
539585 }
540586 if c.ID != "" {
541- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " id=\"")
587+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " id=\"")
542588 if templ_7745c5c3_Err != nil {
543589 return templ_7745c5c3_Err
544590 }
545- var templ_7745c5c3_Var26 string
546- templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)
591+ var templ_7745c5c3_Var27 string
592+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)
547593 if templ_7745c5c3_Err != nil {
548- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 97, Col: 15}
594+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 106, Col: 15}
549595 }
550- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
596+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var27)
551597 if templ_7745c5c3_Err != nil {
552598 return templ_7745c5c3_Err
553599 }
554- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\"")
600+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\"")
555601 if templ_7745c5c3_Err != nil {
556602 return templ_7745c5c3_Err
557603 }
558604 }
559605 if c.Cursor {
560- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " data-cursor")
606+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " data-cursor")
561607 if templ_7745c5c3_Err != nil {
562608 return templ_7745c5c3_Err
563609 }
564610 }
565- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-sha mute\">")
611+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-sha mute\">")
566612 if templ_7745c5c3_Err != nil {
567613 return templ_7745c5c3_Err
568614 }
569- var templ_7745c5c3_Var27 string
570- templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
615+ var templ_7745c5c3_Var28 string
616+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
571617 if templ_7745c5c3_Err != nil {
572- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 102, Col: 37}
618+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 111, Col: 37}
573619 }
574- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
620+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
575621 if templ_7745c5c3_Err != nil {
576622 return templ_7745c5c3_Err
577623 }
578- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</td><td class=\"c-subject\"><a href=\"")
624+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-subject\"><a href=\"")
579625 if templ_7745c5c3_Err != nil {
580626 return templ_7745c5c3_Err
581627 }
582- var templ_7745c5c3_Var28 templ.SafeURL
583- templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
628+ var templ_7745c5c3_Var29 templ.SafeURL
629+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
584630 if templ_7745c5c3_Err != nil {
585- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 104, Col: 38}
631+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 113, Col: 38}
586632 }
587- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
633+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
588634 if templ_7745c5c3_Err != nil {
589635 return templ_7745c5c3_Err
590636 }
591- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\">")
637+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\">")
592638 if templ_7745c5c3_Err != nil {
593639 return templ_7745c5c3_Err
594640 }
595- var templ_7745c5c3_Var29 string
596- templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
641+ var templ_7745c5c3_Var30 string
642+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
597643 if templ_7745c5c3_Err != nil {
598- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 104, Col: 52}
644+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 113, Col: 52}
599645 }
600- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
646+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
601647 if templ_7745c5c3_Err != nil {
602648 return templ_7745c5c3_Err
603649 }
604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</a> ")
650+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</a> ")
605651 if templ_7745c5c3_Err != nil {
606652 return templ_7745c5c3_Err
607653 }
608654 if c.Merge {
609- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<span class=\"pill\">merge</span> ")
655+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<span class=\"pill\">merge</span> ")
610656 if templ_7745c5c3_Err != nil {
611657 return templ_7745c5c3_Err
612658 }
613659 }
614660 for _, b := range c.Branches {
615- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<span class=\"pill\">")
661+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<span class=\"pill\">")
616662 if templ_7745c5c3_Err != nil {
617663 return templ_7745c5c3_Err
618664 }
619- var templ_7745c5c3_Var30 string
620- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(b)
665+ var templ_7745c5c3_Var31 string
666+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(b)
621667 if templ_7745c5c3_Err != nil {
622- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 109, Col: 29}
668+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 118, Col: 29}
623669 }
624- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
670+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
625671 if templ_7745c5c3_Err != nil {
626672 return templ_7745c5c3_Err
627673 }
628- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</span> ")
674+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</span> ")
629675 if templ_7745c5c3_Err != nil {
630676 return templ_7745c5c3_Err
631677 }
632678 }
633679 for _, t := range c.Tags {
634- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")
680+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")
635681 if templ_7745c5c3_Err != nil {
636682 return templ_7745c5c3_Err
637683 }
638- var templ_7745c5c3_Var31 string
639- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(t)
684+ var templ_7745c5c3_Var32 string
685+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(t)
640686 if templ_7745c5c3_Err != nil {
641- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 112, Col: 61}
687+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 121, Col: 61}
642688 }
643- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
689+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
644690 if templ_7745c5c3_Err != nil {
645691 return templ_7745c5c3_Err
646692 }
647- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</span>")
693+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</span>")
648694 if templ_7745c5c3_Err != nil {
649695 return templ_7745c5c3_Err
650696 }
651697 }
652- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</td><td class=\"c-ck\">")
698+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</td><td class=\"c-ck\">")
653699 if templ_7745c5c3_Err != nil {
654700 return templ_7745c5c3_Err
655701 }
@@ -657,64 +703,64 @@ func CommitRows(rows []CommitRow) templ.Component {
657703 if templ_7745c5c3_Err != nil {
658704 return templ_7745c5c3_Err
659705 }
660- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</td><td class=\"c-who dim\">")
706+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</td><td class=\"c-who dim\">")
661707 if templ_7745c5c3_Err != nil {
662708 return templ_7745c5c3_Err
663709 }
664- var templ_7745c5c3_Var32 string
665- templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
710+ var templ_7745c5c3_Var33 string
711+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
666712 if templ_7745c5c3_Err != nil {
667- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 116, Col: 37}
713+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 125, Col: 37}
668714 }
669- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
715+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
670716 if templ_7745c5c3_Err != nil {
671717 return templ_7745c5c3_Err
672718 }
673- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")
719+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")
674720 if templ_7745c5c3_Err != nil {
675721 return templ_7745c5c3_Err
676722 }
677- var templ_7745c5c3_Var33 string
678- templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
723+ var templ_7745c5c3_Var34 string
724+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
679725 if templ_7745c5c3_Err != nil {
680- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 117, Col: 60}
726+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 60}
681727 }
682- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)
728+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
683729 if templ_7745c5c3_Err != nil {
684730 return templ_7745c5c3_Err
685731 }
686- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "\" title=\"")
732+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" title=\"")
687733 if templ_7745c5c3_Err != nil {
688734 return templ_7745c5c3_Err
689735 }
690- var templ_7745c5c3_Var34 string
691- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
736+ var templ_7745c5c3_Var35 string
737+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
692738 if templ_7745c5c3_Err != nil {
693- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 117, Col: 79}
739+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 79}
694740 }
695- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
741+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
696742 if templ_7745c5c3_Err != nil {
697743 return templ_7745c5c3_Err
698744 }
699- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\">")
745+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\">")
700746 if templ_7745c5c3_Err != nil {
701747 return templ_7745c5c3_Err
702748 }
703- var templ_7745c5c3_Var35 string
704- templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
749+ var templ_7745c5c3_Var36 string
750+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
705751 if templ_7745c5c3_Err != nil {
706- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 117, Col: 90}
752+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 90}
707753 }
708- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
754+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
709755 if templ_7745c5c3_Err != nil {
710756 return templ_7745c5c3_Err
711757 }
712- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</time></td></tr>")
758+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</time></td></tr>")
713759 if templ_7745c5c3_Err != nil {
714760 return templ_7745c5c3_Err
715761 }
716762 }
717- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</tbody></table>")
763+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</tbody></table>")
718764 if templ_7745c5c3_Err != nil {
719765 return templ_7745c5c3_Err
720766 }
@@ -147,7 +147,8 @@ func CloneBlock(c Clone) templ.Component {
147 })147 })
148 }148 }
149 149
150-// HomeRow is one row of the home list. Its id is the target of SSE swaps.150+// HomeRowView is the card of a repository in the home list. Its id is the
151+// target of SSE swaps; the name link comes first, `enter` follows it.
151 func HomeRowView(r HomeRow) templ.Component {152 func HomeRowView(r HomeRow) templ.Component {
152 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {153 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
153 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context154 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -169,20 +170,20 @@ func HomeRowView(r HomeRow) templ.Component {
169 templ_7745c5c3_Var7 = templ.NopComponent170 templ_7745c5c3_Var7 = templ.NopComponent
170 }171 }
171 ctx = templ.ClearChildren(ctx)172 ctx = templ.ClearChildren(ctx)
172- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<tr id=\"")173+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<li id=\"")
173 if templ_7745c5c3_Err != nil {174 if templ_7745c5c3_Err != nil {
174 return templ_7745c5c3_Err175 return templ_7745c5c3_Err
175 }176 }
176 var templ_7745c5c3_Var8 string177 var templ_7745c5c3_Var8 string
177 templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ID)178 templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.ID)
178 if templ_7745c5c3_Err != nil {179 if templ_7745c5c3_Err != nil {
179- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 37, Col: 14}180+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 38, Col: 14}
180 }181 }
181 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)182 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
182 if templ_7745c5c3_Err != nil {183 if templ_7745c5c3_Err != nil {
183 return templ_7745c5c3_Err184 return templ_7745c5c3_Err
184 }185 }
185- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"")186+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" class=\"repo\"")
186 if templ_7745c5c3_Err != nil {187 if templ_7745c5c3_Err != nil {
187 return templ_7745c5c3_Err188 return templ_7745c5c3_Err
188 }189 }
@@ -192,7 +193,7 @@ func HomeRowView(r HomeRow) templ.Component {
192 return templ_7745c5c3_Err193 return templ_7745c5c3_Err
193 }194 }
194 }195 }
195- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-name\"><a href=\"")196+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "><h2 class=\"repo-name\"><a href=\"")
196 if templ_7745c5c3_Err != nil {197 if templ_7745c5c3_Err != nil {
197 return templ_7745c5c3_Err198 return templ_7745c5c3_Err
198 }199 }
@@ -222,75 +223,120 @@ func HomeRowView(r HomeRow) templ.Component {
222 if templ_7745c5c3_Err != nil {223 if templ_7745c5c3_Err != nil {
223 return templ_7745c5c3_Err224 return templ_7745c5c3_Err
224 }225 }
226+ if r.Private {
227+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span class=\"pill\">private</span> ")
228+ if templ_7745c5c3_Err != nil {
229+ return templ_7745c5c3_Err
230+ }
231+ }
225 if r.Fresh {232 if r.Fresh {
226- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span class=\"pill new\">new push</span>")233+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<span class=\"pill new\">new push</span>")
227 if templ_7745c5c3_Err != nil {234 if templ_7745c5c3_Err != nil {
228 return templ_7745c5c3_Err235 return templ_7745c5c3_Err
229 }236 }
230 }237 }
231- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td><td class=\"c-desc dim\">")238+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</h2>")
232 if templ_7745c5c3_Err != nil {239 if templ_7745c5c3_Err != nil {
233 return templ_7745c5c3_Err240 return templ_7745c5c3_Err
234 }241 }
235- var templ_7745c5c3_Var11 string242+ if r.Desc != "" {
236- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(r.Desc)243+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"repo-desc\">")
237- if templ_7745c5c3_Err != nil {244+ if templ_7745c5c3_Err != nil {
238- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 45, Col: 33}245+ return templ_7745c5c3_Err
239- }246+ }
240- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))247+ var templ_7745c5c3_Var11 string
241- if templ_7745c5c3_Err != nil {248+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(r.Desc)
242- return templ_7745c5c3_Err249+ if templ_7745c5c3_Err != nil {
250+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 49, Col: 32}
251+ }
252+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
253+ if templ_7745c5c3_Err != nil {
254+ return templ_7745c5c3_Err
255+ }
256+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</p>")
257+ if templ_7745c5c3_Err != nil {
258+ return templ_7745c5c3_Err
259+ }
260+ } else {
261+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<p class=\"repo-desc mute\">no description</p>")
262+ if templ_7745c5c3_Err != nil {
263+ return templ_7745c5c3_Err
264+ }
243 }265 }
244- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</td><td class=\"c-when nowrap\"><time")266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p class=\"repo-meta\">")
245 if templ_7745c5c3_Err != nil {267 if templ_7745c5c3_Err != nil {
246 return templ_7745c5c3_Err268 return templ_7745c5c3_Err
247 }269 }
248- if r.PushedAt != "" {270+ if r.Branch != "" {
249- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " datetime=\"")271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<span class=\"repo-branch\"><span class=\"ico i-branch\" aria-hidden=\"true\"></span>")
250 if templ_7745c5c3_Err != nil {272 if templ_7745c5c3_Err != nil {
251 return templ_7745c5c3_Err273 return templ_7745c5c3_Err
252 }274 }
253 var templ_7745c5c3_Var12 string275 var templ_7745c5c3_Var12 string
254- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.PushedAt)276+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(r.Branch)
255 if templ_7745c5c3_Err != nil {277 if templ_7745c5c3_Err != nil {
256- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 49, Col: 26}278+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 55, Col: 93}
257 }279 }
258- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)280+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
259 if templ_7745c5c3_Err != nil {281 if templ_7745c5c3_Err != nil {
260 return templ_7745c5c3_Err282 return templ_7745c5c3_Err
261 }283 }
262- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"")284+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</span> ")
263 if templ_7745c5c3_Err != nil {285 if templ_7745c5c3_Err != nil {
264 return templ_7745c5c3_Err286 return templ_7745c5c3_Err
265 }287 }
266 }288 }
267- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, ">")289+ if r.PushedAt != "" {
268- if templ_7745c5c3_Err != nil {290+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<span>pushed <time datetime=\"")
269- return templ_7745c5c3_Err291+ if templ_7745c5c3_Err != nil {
270- }292+ return templ_7745c5c3_Err
271- var templ_7745c5c3_Var13 string293+ }
272- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(r.Pushed)294+ var templ_7745c5c3_Var13 string
273- if templ_7745c5c3_Err != nil {295+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.PushedAt)
274- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 51, Col: 14}296+ if templ_7745c5c3_Err != nil {
275- }297+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 58, Col: 44}
276- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))298+ }
277- if templ_7745c5c3_Err != nil {299+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
278- return templ_7745c5c3_Err300+ if templ_7745c5c3_Err != nil {
279- }301+ return templ_7745c5c3_Err
280- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</time></td><td class=\"c-ref\">")302+ }
281- if templ_7745c5c3_Err != nil {303+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" title=\"")
282- return templ_7745c5c3_Err304+ if templ_7745c5c3_Err != nil {
283- }305+ return templ_7745c5c3_Err
284- var templ_7745c5c3_Var14 string306+ }
285- templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(r.Branch)307+ var templ_7745c5c3_Var14 string
286- if templ_7745c5c3_Err != nil {308+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.PushedAt)
287- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 53, Col: 30}309+ if templ_7745c5c3_Err != nil {
288- }310+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 58, Col: 65}
289- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))311+ }
290- if templ_7745c5c3_Err != nil {312+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
291- return templ_7745c5c3_Err313+ if templ_7745c5c3_Err != nil {
314+ return templ_7745c5c3_Err
315+ }
316+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\">")
317+ if templ_7745c5c3_Err != nil {
318+ return templ_7745c5c3_Err
319+ }
320+ var templ_7745c5c3_Var15 string
321+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(r.Pushed)
322+ if templ_7745c5c3_Err != nil {
323+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 58, Col: 78}
324+ }
325+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
326+ if templ_7745c5c3_Err != nil {
327+ return templ_7745c5c3_Err
328+ }
329+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</time></span>")
330+ if templ_7745c5c3_Err != nil {
331+ return templ_7745c5c3_Err
332+ }
333+ } else {
334+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<span>never pushed</span>")
335+ if templ_7745c5c3_Err != nil {
336+ return templ_7745c5c3_Err
337+ }
292 }338 }
293- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</td></tr>")339+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p></li>")
294 if templ_7745c5c3_Err != nil {340 if templ_7745c5c3_Err != nil {
295 return templ_7745c5c3_Err341 return templ_7745c5c3_Err
296 }342 }
@@ -315,66 +361,66 @@ func Outline(headings []Heading) templ.Component {
315 }()361 }()
316 }362 }
317 ctx = templ.InitializeContext(ctx)363 ctx = templ.InitializeContext(ctx)
318- templ_7745c5c3_Var15 := templ.GetChildren(ctx)364+ templ_7745c5c3_Var16 := templ.GetChildren(ctx)
319- if templ_7745c5c3_Var15 == nil {365+ if templ_7745c5c3_Var16 == nil {
320- templ_7745c5c3_Var15 = templ.NopComponent366+ templ_7745c5c3_Var16 = templ.NopComponent
321 }367 }
322 ctx = templ.ClearChildren(ctx)368 ctx = templ.ClearChildren(ctx)
323- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<nav class=\"outline\" aria-label=\"Outline\">")369+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<nav class=\"outline\" aria-label=\"Outline\">")
324 if templ_7745c5c3_Err != nil {370 if templ_7745c5c3_Err != nil {
325 return templ_7745c5c3_Err371 return templ_7745c5c3_Err
326 }372 }
327 for _, h := range headings {373 for _, h := range headings {
328- var templ_7745c5c3_Var16 = []any{"l" + itoa(h.Level)}374+ var templ_7745c5c3_Var17 = []any{"l" + itoa(h.Level)}
329- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)375+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
330 if templ_7745c5c3_Err != nil {376 if templ_7745c5c3_Err != nil {
331 return templ_7745c5c3_Err377 return templ_7745c5c3_Err
332 }378 }
333- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<a class=\"")379+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<a class=\"")
334 if templ_7745c5c3_Err != nil {380 if templ_7745c5c3_Err != nil {
335 return templ_7745c5c3_Err381 return templ_7745c5c3_Err
336 }382 }
337- var templ_7745c5c3_Var17 string383+ var templ_7745c5c3_Var18 string
338- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var16).String())384+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
339 if templ_7745c5c3_Err != nil {385 if templ_7745c5c3_Err != nil {
340 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 1, Col: 0}386 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 1, Col: 0}
341 }387 }
342- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)388+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
343 if templ_7745c5c3_Err != nil {389 if templ_7745c5c3_Err != nil {
344 return templ_7745c5c3_Err390 return templ_7745c5c3_Err
345 }391 }
346- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" href=\"")392+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" href=\"")
347 if templ_7745c5c3_Err != nil {393 if templ_7745c5c3_Err != nil {
348 return templ_7745c5c3_Err394 return templ_7745c5c3_Err
349 }395 }
350- var templ_7745c5c3_Var18 templ.SafeURL396+ var templ_7745c5c3_Var19 templ.SafeURL
351- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))397+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#" + h.ID))
352 if templ_7745c5c3_Err != nil {398 if templ_7745c5c3_Err != nil {
353- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 61, Col: 68}399+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 70, Col: 68}
354 }400 }
355- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))401+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
356 if templ_7745c5c3_Err != nil {402 if templ_7745c5c3_Err != nil {
357 return templ_7745c5c3_Err403 return templ_7745c5c3_Err
358 }404 }
359- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\">")405+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\">")
360 if templ_7745c5c3_Err != nil {406 if templ_7745c5c3_Err != nil {
361 return templ_7745c5c3_Err407 return templ_7745c5c3_Err
362 }408 }
363- var templ_7745c5c3_Var19 string409+ var templ_7745c5c3_Var20 string
364- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)410+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(h.Text)
365 if templ_7745c5c3_Err != nil {411 if templ_7745c5c3_Err != nil {
366- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 61, Col: 79}412+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 70, Col: 79}
367 }413 }
368- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))414+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
369 if templ_7745c5c3_Err != nil {415 if templ_7745c5c3_Err != nil {
370 return templ_7745c5c3_Err416 return templ_7745c5c3_Err
371 }417 }
372- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</a>")418+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</a>")
373 if templ_7745c5c3_Err != nil {419 if templ_7745c5c3_Err != nil {
374 return templ_7745c5c3_Err420 return templ_7745c5c3_Err
375 }421 }
376 }422 }
377- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</nav>")423+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</nav>")
378 if templ_7745c5c3_Err != nil {424 if templ_7745c5c3_Err != nil {
379 return templ_7745c5c3_Err425 return templ_7745c5c3_Err
380 }426 }
@@ -399,25 +445,25 @@ func ReadmeCard(r Readme) templ.Component {
399 }()445 }()
400 }446 }
401 ctx = templ.InitializeContext(ctx)447 ctx = templ.InitializeContext(ctx)
402- templ_7745c5c3_Var20 := templ.GetChildren(ctx)448+ templ_7745c5c3_Var21 := templ.GetChildren(ctx)
403- if templ_7745c5c3_Var20 == nil {449+ if templ_7745c5c3_Var21 == nil {
404- templ_7745c5c3_Var20 = templ.NopComponent450+ templ_7745c5c3_Var21 = templ.NopComponent
405 }451 }
406 ctx = templ.ClearChildren(ctx)452 ctx = templ.ClearChildren(ctx)
407- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")453+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<section class=\"card readme\" aria-labelledby=\"readme-h\"><h2 class=\"card-h\" id=\"readme-h\"><span class=\"ico i-file\" aria-hidden=\"true\"></span> ")
408 if templ_7745c5c3_Err != nil {454 if templ_7745c5c3_Err != nil {
409 return templ_7745c5c3_Err455 return templ_7745c5c3_Err
410 }456 }
411- var templ_7745c5c3_Var21 string457+ var templ_7745c5c3_Var22 string
412- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)458+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
413 if templ_7745c5c3_Err != nil {459 if templ_7745c5c3_Err != nil {
414- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 71, Col: 11}460+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 11}
415 }461 }
416- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))462+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
417 if templ_7745c5c3_Err != nil {463 if templ_7745c5c3_Err != nil {
418 return templ_7745c5c3_Err464 return templ_7745c5c3_Err
419 }465 }
420- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</h2>")466+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</h2>")
421 if templ_7745c5c3_Err != nil {467 if templ_7745c5c3_Err != nil {
422 return templ_7745c5c3_Err468 return templ_7745c5c3_Err
423 }469 }
@@ -425,7 +471,7 @@ func ReadmeCard(r Readme) templ.Component {
425 if templ_7745c5c3_Err != nil {471 if templ_7745c5c3_Err != nil {
426 return templ_7745c5c3_Err472 return templ_7745c5c3_Err
427 }473 }
428- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</section>")474+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</section>")
429 if templ_7745c5c3_Err != nil {475 if templ_7745c5c3_Err != nil {
430 return templ_7745c5c3_Err476 return templ_7745c5c3_Err
431 }477 }
@@ -451,43 +497,43 @@ func ReadmeView(r Readme) templ.Component {
451 }()497 }()
452 }498 }
453 ctx = templ.InitializeContext(ctx)499 ctx = templ.InitializeContext(ctx)
454- templ_7745c5c3_Var22 := templ.GetChildren(ctx)500+ templ_7745c5c3_Var23 := templ.GetChildren(ctx)
455- if templ_7745c5c3_Var22 == nil {501+ if templ_7745c5c3_Var23 == nil {
456- templ_7745c5c3_Var22 = templ.NopComponent502+ templ_7745c5c3_Var23 = templ.NopComponent
457 }503 }
458 ctx = templ.ClearChildren(ctx)504 ctx = templ.ClearChildren(ctx)
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<article class=\"prose\" aria-label=\"")505+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<article class=\"prose\" aria-label=\"")
460 if templ_7745c5c3_Err != nil {506 if templ_7745c5c3_Err != nil {
461 return templ_7745c5c3_Err507 return templ_7745c5c3_Err
462 }508 }
463- var templ_7745c5c3_Var23 string509+ var templ_7745c5c3_Var24 string
464- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)510+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.Name)
465 if templ_7745c5c3_Err != nil {511 if templ_7745c5c3_Err != nil {
466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 80, Col: 43}512+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 89, Col: 43}
467 }513 }
468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)514+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24)
469 if templ_7745c5c3_Err != nil {515 if templ_7745c5c3_Err != nil {
470 return templ_7745c5c3_Err516 return templ_7745c5c3_Err
471 }517 }
472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" hx-ignore>")518+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\" hx-ignore>")
473 if templ_7745c5c3_Err != nil {519 if templ_7745c5c3_Err != nil {
474 return templ_7745c5c3_Err520 return templ_7745c5c3_Err
475 }521 }
476 if r.Note != "" {522 if r.Note != "" {
477- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<p class=\"dim\">")523+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<p class=\"dim\">")
478 if templ_7745c5c3_Err != nil {524 if templ_7745c5c3_Err != nil {
479 return templ_7745c5c3_Err525 return templ_7745c5c3_Err
480 }526 }
481- var templ_7745c5c3_Var24 string527+ var templ_7745c5c3_Var25 string
482- templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)528+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(r.Note)
483 if templ_7745c5c3_Err != nil {529 if templ_7745c5c3_Err != nil {
484- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 82, Col: 26}530+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 91, Col: 26}
485 }531 }
486- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))532+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
487 if templ_7745c5c3_Err != nil {533 if templ_7745c5c3_Err != nil {
488 return templ_7745c5c3_Err534 return templ_7745c5c3_Err
489 }535 }
490- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</p>")536+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</p>")
491 if templ_7745c5c3_Err != nil {537 if templ_7745c5c3_Err != nil {
492 return templ_7745c5c3_Err538 return templ_7745c5c3_Err
493 }539 }
@@ -497,7 +543,7 @@ func ReadmeView(r Readme) templ.Component {
497 return templ_7745c5c3_Err543 return templ_7745c5c3_Err
498 }544 }
499 }545 }
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</article>")546+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</article>")
501 if templ_7745c5c3_Err != nil {547 if templ_7745c5c3_Err != nil {
502 return templ_7745c5c3_Err548 return templ_7745c5c3_Err
503 }549 }
@@ -523,133 +569,133 @@ func CommitRows(rows []CommitRow) templ.Component {
523 }()569 }()
524 }570 }
525 ctx = templ.InitializeContext(ctx)571 ctx = templ.InitializeContext(ctx)
526- templ_7745c5c3_Var25 := templ.GetChildren(ctx)572+ templ_7745c5c3_Var26 := templ.GetChildren(ctx)
527- if templ_7745c5c3_Var25 == nil {573+ if templ_7745c5c3_Var26 == nil {
528- templ_7745c5c3_Var25 = templ.NopComponent574+ templ_7745c5c3_Var26 = templ.NopComponent
529 }575 }
530 ctx = templ.ClearChildren(ctx)576 ctx = templ.ClearChildren(ctx)
531- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")577+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<table class=\"list commits\" aria-label=\"Commits\"><tbody data-rows>")
532 if templ_7745c5c3_Err != nil {578 if templ_7745c5c3_Err != nil {
533 return templ_7745c5c3_Err579 return templ_7745c5c3_Err
534 }580 }
535 for _, c := range rows {581 for _, c := range rows {
536- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<tr")582+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<tr")
537 if templ_7745c5c3_Err != nil {583 if templ_7745c5c3_Err != nil {
538 return templ_7745c5c3_Err584 return templ_7745c5c3_Err
539 }585 }
540 if c.ID != "" {586 if c.ID != "" {
541- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, " id=\"")587+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " id=\"")
542 if templ_7745c5c3_Err != nil {588 if templ_7745c5c3_Err != nil {
543 return templ_7745c5c3_Err589 return templ_7745c5c3_Err
544 }590 }
545- var templ_7745c5c3_Var26 string591+ var templ_7745c5c3_Var27 string
546- templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)592+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.ID)
547 if templ_7745c5c3_Err != nil {593 if templ_7745c5c3_Err != nil {
548- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 97, Col: 15}594+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 106, Col: 15}
549 }595 }
550- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)596+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var27)
551 if templ_7745c5c3_Err != nil {597 if templ_7745c5c3_Err != nil {
552 return templ_7745c5c3_Err598 return templ_7745c5c3_Err
553 }599 }
554- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\"")600+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\"")
555 if templ_7745c5c3_Err != nil {601 if templ_7745c5c3_Err != nil {
556 return templ_7745c5c3_Err602 return templ_7745c5c3_Err
557 }603 }
558 }604 }
559 if c.Cursor {605 if c.Cursor {
560- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " data-cursor")606+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " data-cursor")
561 if templ_7745c5c3_Err != nil {607 if templ_7745c5c3_Err != nil {
562 return templ_7745c5c3_Err608 return templ_7745c5c3_Err
563 }609 }
564 }610 }
565- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-sha mute\">")611+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "><td class=\"cur\" aria-hidden=\"true\"></td><td class=\"c-sha mute\">")
566 if templ_7745c5c3_Err != nil {612 if templ_7745c5c3_Err != nil {
567 return templ_7745c5c3_Err613 return templ_7745c5c3_Err
568 }614 }
569- var templ_7745c5c3_Var27 string615+ var templ_7745c5c3_Var28 string
570- templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)616+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
571 if templ_7745c5c3_Err != nil {617 if templ_7745c5c3_Err != nil {
572- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 102, Col: 37}618+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 111, Col: 37}
573 }619 }
574- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))620+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
575 if templ_7745c5c3_Err != nil {621 if templ_7745c5c3_Err != nil {
576 return templ_7745c5c3_Err622 return templ_7745c5c3_Err
577 }623 }
578- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</td><td class=\"c-subject\"><a href=\"")624+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-subject\"><a href=\"")
579 if templ_7745c5c3_Err != nil {625 if templ_7745c5c3_Err != nil {
580 return templ_7745c5c3_Err626 return templ_7745c5c3_Err
581 }627 }
582- var templ_7745c5c3_Var28 templ.SafeURL628+ var templ_7745c5c3_Var29 templ.SafeURL
583- templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))629+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
584 if templ_7745c5c3_Err != nil {630 if templ_7745c5c3_Err != nil {
585- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 104, Col: 38}631+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 113, Col: 38}
586 }632 }
587- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))633+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
588 if templ_7745c5c3_Err != nil {634 if templ_7745c5c3_Err != nil {
589 return templ_7745c5c3_Err635 return templ_7745c5c3_Err
590 }636 }
591- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\">")637+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\">")
592 if templ_7745c5c3_Err != nil {638 if templ_7745c5c3_Err != nil {
593 return templ_7745c5c3_Err639 return templ_7745c5c3_Err
594 }640 }
595- var templ_7745c5c3_Var29 string641+ var templ_7745c5c3_Var30 string
596- templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)642+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
597 if templ_7745c5c3_Err != nil {643 if templ_7745c5c3_Err != nil {
598- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 104, Col: 52}644+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 113, Col: 52}
599 }645 }
600- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))646+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
601 if templ_7745c5c3_Err != nil {647 if templ_7745c5c3_Err != nil {
602 return templ_7745c5c3_Err648 return templ_7745c5c3_Err
603 }649 }
604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</a> ")650+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</a> ")
605 if templ_7745c5c3_Err != nil {651 if templ_7745c5c3_Err != nil {
606 return templ_7745c5c3_Err652 return templ_7745c5c3_Err
607 }653 }
608 if c.Merge {654 if c.Merge {
609- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<span class=\"pill\">merge</span> ")655+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<span class=\"pill\">merge</span> ")
610 if templ_7745c5c3_Err != nil {656 if templ_7745c5c3_Err != nil {
611 return templ_7745c5c3_Err657 return templ_7745c5c3_Err
612 }658 }
613 }659 }
614 for _, b := range c.Branches {660 for _, b := range c.Branches {
615- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<span class=\"pill\">")661+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<span class=\"pill\">")
616 if templ_7745c5c3_Err != nil {662 if templ_7745c5c3_Err != nil {
617 return templ_7745c5c3_Err663 return templ_7745c5c3_Err
618 }664 }
619- var templ_7745c5c3_Var30 string665+ var templ_7745c5c3_Var31 string
620- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(b)666+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(b)
621 if templ_7745c5c3_Err != nil {667 if templ_7745c5c3_Err != nil {
622- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 109, Col: 29}668+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 118, Col: 29}
623 }669 }
624- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))670+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
625 if templ_7745c5c3_Err != nil {671 if templ_7745c5c3_Err != nil {
626 return templ_7745c5c3_Err672 return templ_7745c5c3_Err
627 }673 }
628- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</span> ")674+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</span> ")
629 if templ_7745c5c3_Err != nil {675 if templ_7745c5c3_Err != nil {
630 return templ_7745c5c3_Err676 return templ_7745c5c3_Err
631 }677 }
632 }678 }
633 for _, t := range c.Tags {679 for _, t := range c.Tags {
634- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")680+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<span class=\"pill tag\"><span class=\"sr\">tag </span>")
635 if templ_7745c5c3_Err != nil {681 if templ_7745c5c3_Err != nil {
636 return templ_7745c5c3_Err682 return templ_7745c5c3_Err
637 }683 }
638- var templ_7745c5c3_Var31 string684+ var templ_7745c5c3_Var32 string
639- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(t)685+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(t)
640 if templ_7745c5c3_Err != nil {686 if templ_7745c5c3_Err != nil {
641- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 112, Col: 61}687+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 121, Col: 61}
642 }688 }
643- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))689+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
644 if templ_7745c5c3_Err != nil {690 if templ_7745c5c3_Err != nil {
645 return templ_7745c5c3_Err691 return templ_7745c5c3_Err
646 }692 }
647- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</span>")693+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</span>")
648 if templ_7745c5c3_Err != nil {694 if templ_7745c5c3_Err != nil {
649 return templ_7745c5c3_Err695 return templ_7745c5c3_Err
650 }696 }
651 }697 }
652- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</td><td class=\"c-ck\">")698+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</td><td class=\"c-ck\">")
653 if templ_7745c5c3_Err != nil {699 if templ_7745c5c3_Err != nil {
654 return templ_7745c5c3_Err700 return templ_7745c5c3_Err
655 }701 }
@@ -657,64 +703,64 @@ func CommitRows(rows []CommitRow) templ.Component {
657 if templ_7745c5c3_Err != nil {703 if templ_7745c5c3_Err != nil {
658 return templ_7745c5c3_Err704 return templ_7745c5c3_Err
659 }705 }
660- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</td><td class=\"c-who dim\">")706+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</td><td class=\"c-who dim\">")
661 if templ_7745c5c3_Err != nil {707 if templ_7745c5c3_Err != nil {
662 return templ_7745c5c3_Err708 return templ_7745c5c3_Err
663 }709 }
664- var templ_7745c5c3_Var32 string710+ var templ_7745c5c3_Var33 string
665- templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)711+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
666 if templ_7745c5c3_Err != nil {712 if templ_7745c5c3_Err != nil {
667- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 116, Col: 37}713+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 125, Col: 37}
668 }714 }
669- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))715+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
670 if templ_7745c5c3_Err != nil {716 if templ_7745c5c3_Err != nil {
671 return templ_7745c5c3_Err717 return templ_7745c5c3_Err
672 }718 }
673- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")719+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</td><td class=\"c-when nowrap dim\"><time datetime=\"")
674 if templ_7745c5c3_Err != nil {720 if templ_7745c5c3_Err != nil {
675 return templ_7745c5c3_Err721 return templ_7745c5c3_Err
676 }722 }
677- var templ_7745c5c3_Var33 string723+ var templ_7745c5c3_Var34 string
678- templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)724+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
679 if templ_7745c5c3_Err != nil {725 if templ_7745c5c3_Err != nil {
680- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 117, Col: 60}726+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 60}
681 }727 }
682- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33)728+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
683 if templ_7745c5c3_Err != nil {729 if templ_7745c5c3_Err != nil {
684 return templ_7745c5c3_Err730 return templ_7745c5c3_Err
685 }731 }
686- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "\" title=\"")732+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" title=\"")
687 if templ_7745c5c3_Err != nil {733 if templ_7745c5c3_Err != nil {
688 return templ_7745c5c3_Err734 return templ_7745c5c3_Err
689 }735 }
690- var templ_7745c5c3_Var34 string736+ var templ_7745c5c3_Var35 string
691- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)737+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
692 if templ_7745c5c3_Err != nil {738 if templ_7745c5c3_Err != nil {
693- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 117, Col: 79}739+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 79}
694 }740 }
695- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)741+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
696 if templ_7745c5c3_Err != nil {742 if templ_7745c5c3_Err != nil {
697 return templ_7745c5c3_Err743 return templ_7745c5c3_Err
698 }744 }
699- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\">")745+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\">")
700 if templ_7745c5c3_Err != nil {746 if templ_7745c5c3_Err != nil {
701 return templ_7745c5c3_Err747 return templ_7745c5c3_Err
702 }748 }
703- var templ_7745c5c3_Var35 string749+ var templ_7745c5c3_Var36 string
704- templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)750+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
705 if templ_7745c5c3_Err != nil {751 if templ_7745c5c3_Err != nil {
706- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 117, Col: 90}752+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/components.templ`, Line: 126, Col: 90}
707 }753 }
708- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))754+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
709 if templ_7745c5c3_Err != nil {755 if templ_7745c5c3_Err != nil {
710 return templ_7745c5c3_Err756 return templ_7745c5c3_Err
711 }757 }
712- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</time></td></tr>")758+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</time></td></tr>")
713 if templ_7745c5c3_Err != nil {759 if templ_7745c5c3_Err != nil {
714 return templ_7745c5c3_Err760 return templ_7745c5c3_Err
715 }761 }
716 }762 }
717- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</tbody></table>")763+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</tbody></table>")
718 if templ_7745c5c3_Err != nil {764 if templ_7745c5c3_Err != nil {
719 return templ_7745c5c3_Err765 return templ_7745c5c3_Err
720 }766 }
M internal/adapters/web/events.go +1 -1
@@ -45,7 +45,7 @@ import (
4545 //
4646 // Events, each with the id of the Watch event it comes from:
4747 //
48-// home-list data: <tr id="repo-<name>">…</tr>, or <tr id="repo-<name>" data-gone></tr>
48+// home-list data: <li id="repo-<name>">…</li>, or <li id="repo-<name>" data-gone></li>
4949 // for a repository that is deleted or no longer visible
5050 // repo-last-push data: <dd id="last-push-<name>">…</dd>
5151 // refs-list data: <tbody id="ref-rows">…</tbody>, and again with
@@ -45,7 +45,7 @@ import (
45 //45 //
46 // Events, each with the id of the Watch event it comes from:46 // Events, each with the id of the Watch event it comes from:
47 //47 //
48-// home-list data: <tr id="repo-<name>">…</tr>, or <tr id="repo-<name>" data-gone></tr>48+// home-list data: <li id="repo-<name>">…</li>, or <li id="repo-<name>" data-gone></li>
49 // for a repository that is deleted or no longer visible49 // for a repository that is deleted or no longer visible
50 // repo-last-push data: <dd id="last-push-<name>">…</dd>50 // repo-last-push data: <dd id="last-push-<name>">…</dd>
51 // refs-list data: <tbody id="ref-rows">…</tbody>, and again with51 // refs-list data: <tbody id="ref-rows">…</tbody>, and again with
M internal/adapters/web/fragments.go +1 -1
@@ -116,7 +116,7 @@ func (l *liveStream) home(ctx context.Context, we ports.WatchEvent) ([]fragment,
116116 case err != nil:
117117 return nil, err
118118 }
119- row := homeRow(view, l.s.cfg.Now(), false)
119+ row := homeRow(view, l.s.cfg.Now(), false, SignedIn(ctx))
120120 row.Fresh = fresh
121121 f, err := renderFragment(ctx, "home-list", HomeRowView(row))
122122 return []fragment{f}, err
@@ -116,7 +116,7 @@ func (l *liveStream) home(ctx context.Context, we ports.WatchEvent) ([]fragment,
116 case err != nil:116 case err != nil:
117 return nil, err117 return nil, err
118 }118 }
119- row := homeRow(view, l.s.cfg.Now(), false)119+ row := homeRow(view, l.s.cfg.Now(), false, SignedIn(ctx))
120 row.Fresh = fresh120 row.Fresh = fresh
121 f, err := renderFragment(ctx, "home-list", HomeRowView(row))121 f, err := renderFragment(ctx, "home-list", HomeRowView(row))
122 return []fragment{f}, err122 return []fragment{f}, err
M internal/adapters/web/fragments.templ +2 -2
@@ -21,10 +21,10 @@ templ FactValue(f Fact) {
2121 </dd>
2222 }
2323
24-// RepoGone replaces the row of a repository that is deleted or not visible
24+// RepoGone replaces the card of a repository that is deleted or not visible
2525 // any more; the page removes the element.
2626 templ RepoGone(id string) {
27- <tr id={ id } data-gone hidden></tr>
27+ <li id={ id } data-gone hidden></li>
2828 }
2929
3030 // RefsBody is the rows of the refs table.
@@ -21,10 +21,10 @@ templ FactValue(f Fact) {
21 </dd>21 </dd>
22 }22 }
23 23
24-// RepoGone replaces the row of a repository that is deleted or not visible24+// RepoGone replaces the card of a repository that is deleted or not visible
25 // any more; the page removes the element.25 // any more; the page removes the element.
26 templ RepoGone(id string) {26 templ RepoGone(id string) {
27- <tr id={ id } data-gone hidden></tr>27+ <li id={ id } data-gone hidden></li>
28 }28 }
29 29
30 // RefsBody is the rows of the refs table.30 // RefsBody is the rows of the refs table.
M internal/adapters/web/fragments_templ.go +3 -3 generated file: collapsed
M internal/adapters/web/home.go +29 -21
@@ -11,6 +11,7 @@ import (
1111
1212 "github.com/a-h/templ"
1313
14+ "bnema.dev/ermite/internal/domain"
1415 "bnema.dev/ermite/internal/ports"
1516 )
1617
@@ -48,14 +49,16 @@ func (s *Server) listRepos(ctx context.Context) ([]ports.RepoView, error) {
4849 return repos, nil
4950 }
5051
51-// homeCols are the sortable columns of the home list, in order.
52-var homeCols = []struct{ key, label, class string }{
53- {"name", "name", "c-name"},
54- {"desc", "description", "c-desc"},
55- {"push", "last push", "c-when"},
56- {"branch", "branch", "c-ref"},
52+// homeSorts are the orders the home list offers, in order. The query also
53+// accepts "desc" and "branch", which the page does not link to.
54+var homeSorts = []struct{ key, label string }{
55+ {"push", "last push"},
56+ {"name", "name"},
5757 }
5858
59+// homeKeys are the keys of ?sort= that sortRepos knows.
60+var homeKeys = []string{"push", "name", "desc", "branch"}
61+
5962 // sortRepos orders repos by the column key; ties keep the order by name.
6063 func sortRepos(repos []ports.RepoView, key string, desc bool) {
6164 less := func(a, b ports.RepoView) bool {
@@ -85,9 +88,9 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
8588 return nil, 0, err
8689 }
8790 key, desc := "push", true
88- for _, c := range homeCols {
89- if r.URL.Query().Get("sort") == c.key {
90- key, desc = c.key, c.key == "push"
91+ for _, k := range homeKeys {
92+ if r.URL.Query().Get("sort") == k {
93+ key, desc = k, k == "push"
9194 }
9295 }
9396 if d := r.URL.Query().Get("dir"); d == "asc" || d == "desc" {
@@ -96,23 +99,26 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
9699 sortRepos(repos, key, desc)
97100
98101 now := s.cfg.Now()
102+ signed := SignedIn(ctx)
99103 p := HomePage{}
100- for _, c := range homeCols {
101- sc := SortCol{Label: c.label, Class: c.class, Dir: "none"}
104+ for _, c := range homeSorts {
105+ sl := SortLink{Label: c.label}
102106 next := "asc"
107+ if c.key == "push" {
108+ next = "desc"
109+ }
103110 if c.key == key {
104- sc.Dir, sc.Mark = "ascending", "▲"
111+ sl.Mark, sl.State = "▲", "ascending, select for descending"
112+ next = "desc"
105113 if desc {
106- sc.Dir, sc.Mark = "descending", "▼"
107- } else {
108- next = "desc"
114+ sl.Mark, sl.State, next = "▼", "descending, select for ascending", "asc"
109115 }
110116 }
111- sc.Href = "/?sort=" + c.key + "&dir=" + next
112- p.Cols = append(p.Cols, sc)
117+ sl.Href = "/?sort=" + c.key + "&dir=" + next
118+ p.Sorts = append(p.Sorts, sl)
113119 }
114120 for i, repo := range repos {
115- p.Rows = append(p.Rows, homeRow(repo, now, i == 0))
121+ p.Rows = append(p.Rows, homeRow(repo, now, i == 0, signed))
116122 }
117123
118124 l := s.base()
@@ -122,12 +128,14 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
122128 return Home(l, p), http.StatusOK, nil
123129 }
124130
125-// homeRow is the row of a repository in the home list.
126-func homeRow(r ports.RepoView, now time.Time, cursor bool) HomeRow {
131+// homeRow is the card of a repository in the home list. The private badge is
132+// for a signed-in visitor: an anonymous one never gets a private repository.
133+func homeRow(r ports.RepoView, now time.Time, cursor, signed bool) HomeRow {
127134 name := r.Name.String()
128135 row := HomeRow{
129136 ID: "repo-" + name, Name: name, Href: repoHref(name),
130- Desc: r.Description, Pushed: "never", Branch: r.DefaultBranch, Cursor: cursor,
137+ Desc: r.Description, Branch: r.DefaultBranch, Cursor: cursor,
138+ Private: signed && r.Visibility == domain.VisibilityPrivate,
131139 }
132140 if !r.PushedAt.IsZero() {
133141 row.Pushed, row.PushedAt = ago(now, r.PushedAt), rfc3339(r.PushedAt)
@@ -11,6 +11,7 @@ import (
11 11
12 "github.com/a-h/templ"12 "github.com/a-h/templ"
13 13
14+ "bnema.dev/ermite/internal/domain"
14 "bnema.dev/ermite/internal/ports"15 "bnema.dev/ermite/internal/ports"
15 )16 )
16 17
@@ -48,14 +49,16 @@ func (s *Server) listRepos(ctx context.Context) ([]ports.RepoView, error) {
48 return repos, nil49 return repos, nil
49 }50 }
50 51
51-// homeCols are the sortable columns of the home list, in order.52+// homeSorts are the orders the home list offers, in order. The query also
52-var homeCols = []struct{ key, label, class string }{53+// accepts "desc" and "branch", which the page does not link to.
53- {"name", "name", "c-name"},54+var homeSorts = []struct{ key, label string }{
54- {"desc", "description", "c-desc"},55+ {"push", "last push"},
55- {"push", "last push", "c-when"},56+ {"name", "name"},
56- {"branch", "branch", "c-ref"},
57 }57 }
58 58
59+// homeKeys are the keys of ?sort= that sortRepos knows.
60+var homeKeys = []string{"push", "name", "desc", "branch"}
61+
59 // sortRepos orders repos by the column key; ties keep the order by name.62 // sortRepos orders repos by the column key; ties keep the order by name.
60 func sortRepos(repos []ports.RepoView, key string, desc bool) {63 func sortRepos(repos []ports.RepoView, key string, desc bool) {
61 less := func(a, b ports.RepoView) bool {64 less := func(a, b ports.RepoView) bool {
@@ -85,9 +88,9 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
85 return nil, 0, err88 return nil, 0, err
86 }89 }
87 key, desc := "push", true90 key, desc := "push", true
88- for _, c := range homeCols {91+ for _, k := range homeKeys {
89- if r.URL.Query().Get("sort") == c.key {92+ if r.URL.Query().Get("sort") == k {
90- key, desc = c.key, c.key == "push"93+ key, desc = k, k == "push"
91 }94 }
92 }95 }
93 if d := r.URL.Query().Get("dir"); d == "asc" || d == "desc" {96 if d := r.URL.Query().Get("dir"); d == "asc" || d == "desc" {
@@ -96,23 +99,26 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
96 sortRepos(repos, key, desc)99 sortRepos(repos, key, desc)
97 100
98 now := s.cfg.Now()101 now := s.cfg.Now()
102+ signed := SignedIn(ctx)
99 p := HomePage{}103 p := HomePage{}
100- for _, c := range homeCols {104+ for _, c := range homeSorts {
101- sc := SortCol{Label: c.label, Class: c.class, Dir: "none"}105+ sl := SortLink{Label: c.label}
102 next := "asc"106 next := "asc"
107+ if c.key == "push" {
108+ next = "desc"
109+ }
103 if c.key == key {110 if c.key == key {
104- sc.Dir, sc.Mark = "ascending", "▲"111+ sl.Mark, sl.State = "▲", "ascending, select for descending"
112+ next = "desc"
105 if desc {113 if desc {
106- sc.Dir, sc.Mark = "descending", "▼"114+ sl.Mark, sl.State, next = "▼", "descending, select for ascending", "asc"
107- } else {
108- next = "desc"
109 }115 }
110 }116 }
111- sc.Href = "/?sort=" + c.key + "&dir=" + next117+ sl.Href = "/?sort=" + c.key + "&dir=" + next
112- p.Cols = append(p.Cols, sc)118+ p.Sorts = append(p.Sorts, sl)
113 }119 }
114 for i, repo := range repos {120 for i, repo := range repos {
115- p.Rows = append(p.Rows, homeRow(repo, now, i == 0))121+ p.Rows = append(p.Rows, homeRow(repo, now, i == 0, signed))
116 }122 }
117 123
118 l := s.base()124 l := s.base()
@@ -122,12 +128,14 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
122 return Home(l, p), http.StatusOK, nil128 return Home(l, p), http.StatusOK, nil
123 }129 }
124 130
125-// homeRow is the row of a repository in the home list.131+// homeRow is the card of a repository in the home list. The private badge is
126-func homeRow(r ports.RepoView, now time.Time, cursor bool) HomeRow {132+// for a signed-in visitor: an anonymous one never gets a private repository.
133+func homeRow(r ports.RepoView, now time.Time, cursor, signed bool) HomeRow {
127 name := r.Name.String()134 name := r.Name.String()
128 row := HomeRow{135 row := HomeRow{
129 ID: "repo-" + name, Name: name, Href: repoHref(name),136 ID: "repo-" + name, Name: name, Href: repoHref(name),
130- Desc: r.Description, Pushed: "never", Branch: r.DefaultBranch, Cursor: cursor,137+ Desc: r.Description, Branch: r.DefaultBranch, Cursor: cursor,
138+ Private: signed && r.Visibility == domain.VisibilityPrivate,
131 }139 }
132 if !r.PushedAt.IsZero() {140 if !r.PushedAt.IsZero() {
133 row.Pushed, row.PushedAt = ago(now, r.PushedAt), rfc3339(r.PushedAt)141 row.Pushed, row.PushedAt = ago(now, r.PushedAt), rfc3339(r.PushedAt)
M internal/adapters/web/pages.templ +39 -32
@@ -5,42 +5,49 @@ package web
55 // Home is the repository list.
66 templ Home(l Layout, p HomePage) {
77 @Shell(l, nil) {
8- <div class="filter">
9- <label for="home-filter" class="sr">filter</label>
10- <input
11- id="home-filter"
12- type="search"
13- data-filter="#repo-rows > tr"
14- placeholder="Filter repositories…"
15- autocomplete="off"
16- hx-on:input="ermite.filter(this)"
17- />
18- </div>
19- <table class="list home" aria-label="Repositories">
20- <thead>
21- <tr>
22- <th class="cur"></th>
23- for _, c := range p.Cols {
24- <th class={ c.Class } aria-sort={ c.Dir }>
25- <a href={ templ.SafeURL(c.Href) }>
26- { c.Label }
27- if c.Mark != "" {
28- <span aria-hidden="true">{ c.Mark }</span>
8+ <h1 class="sr">Repositories</h1>
9+ <div class="home">
10+ <section class="home-repos" aria-label="Repositories">
11+ <div class="home-bar">
12+ <div class="filter">
13+ <label for="home-filter" class="sr">filter</label>
14+ <input
15+ id="home-filter"
16+ type="search"
17+ data-filter="#repo-rows > li"
18+ placeholder="Filter repositories…"
19+ autocomplete="off"
20+ hx-on:input="ermite.filter(this)"
21+ />
22+ </div>
23+ <nav class="sort" aria-label="Sort repositories">
24+ <span class="mute">sort</span>
25+ for _, s := range p.Sorts {
26+ <a
27+ href={ templ.SafeURL(s.Href) }
28+ if s.Mark != "" {
29+ aria-current="true"
30+ }
31+ >
32+ { s.Label }
33+ if s.Mark != "" {
34+ <span aria-hidden="true">{ s.Mark }</span>
35+ <span class="sr">{ ", " + s.State }</span>
2936 }
3037 </a>
31- </th>
38+ }
39+ </nav>
40+ </div>
41+ <ul class="repos" id="repo-rows" data-rows>
42+ for _, r := range p.Rows {
43+ @HomeRowView(r)
3244 }
33- </tr>
34- </thead>
35- <tbody id="repo-rows" data-rows>
36- for _, r := range p.Rows {
37- @HomeRowView(r)
45+ </ul>
46+ if len(p.Rows) == 0 {
47+ <p class="lede dim">no repositories yet.</p>
3848 }
39- </tbody>
40- </table>
41- if len(p.Rows) == 0 {
42- <p class="lede dim">no repositories yet.</p>
43- }
49+ </section>
50+ </div>
4451 }
4552 }
4653
@@ -5,42 +5,49 @@ package web
5 // Home is the repository list.5 // Home is the repository list.
6 templ Home(l Layout, p HomePage) {6 templ Home(l Layout, p HomePage) {
7 @Shell(l, nil) {7 @Shell(l, nil) {
8- <div class="filter">8+ <h1 class="sr">Repositories</h1>
9- <label for="home-filter" class="sr">filter</label>9+ <div class="home">
10- <input10+ <section class="home-repos" aria-label="Repositories">
11- id="home-filter"11+ <div class="home-bar">
12- type="search"12+ <div class="filter">
13- data-filter="#repo-rows > tr"13+ <label for="home-filter" class="sr">filter</label>
14- placeholder="Filter repositories…"14+ <input
15- autocomplete="off"15+ id="home-filter"
16- hx-on:input="ermite.filter(this)"16+ type="search"
17- />17+ data-filter="#repo-rows > li"
18- </div>18+ placeholder="Filter repositories…"
19- <table class="list home" aria-label="Repositories">19+ autocomplete="off"
20- <thead>20+ hx-on:input="ermite.filter(this)"
21- <tr>21+ />
22- <th class="cur"></th>22+ </div>
23- for _, c := range p.Cols {23+ <nav class="sort" aria-label="Sort repositories">
24- <th class={ c.Class } aria-sort={ c.Dir }>24+ <span class="mute">sort</span>
25- <a href={ templ.SafeURL(c.Href) }>25+ for _, s := range p.Sorts {
26- { c.Label }26+ <a
27- if c.Mark != "" {27+ href={ templ.SafeURL(s.Href) }
28- <span aria-hidden="true">{ c.Mark }</span>28+ if s.Mark != "" {
29+ aria-current="true"
30+ }
31+ >
32+ { s.Label }
33+ if s.Mark != "" {
34+ <span aria-hidden="true">{ s.Mark }</span>
35+ <span class="sr">{ ", " + s.State }</span>
29 }36 }
30 </a>37 </a>
31- </th>38+ }
39+ </nav>
40+ </div>
41+ <ul class="repos" id="repo-rows" data-rows>
42+ for _, r := range p.Rows {
43+ @HomeRowView(r)
32 }44 }
33- </tr>45+ </ul>
34- </thead>46+ if len(p.Rows) == 0 {
35- <tbody id="repo-rows" data-rows>47+ <p class="lede dim">no repositories yet.</p>
36- for _, r := range p.Rows {
37- @HomeRowView(r)
38 }48 }
39- </tbody>49+ </section>
40- </table>50+ </div>
41- if len(p.Rows) == 0 {
42- <p class="lede dim">no repositories yet.</p>
43- }
44 }51 }
45 }52 }
46 53
M internal/adapters/web/pages_templ.go +369 -373 generated file: collapsed
M internal/adapters/web/static/css/30-list.css +40 -6
@@ -28,13 +28,47 @@
2828 & .c-sub { color: var(--ink-dim); }
2929 }
3030
31+ /* home: the repositories as cards in a grid (one column on a phone); the
32+ page is a stack of sections, so others can sit beside or below */
3133 .home {
32- & .c-name { width: 26%; font-family: var(--mono); font-weight: 700; }
33- & th.c-name { font-family: var(--font); }
34- & .c-desc { width: auto; }
35- & .c-when { width: 130px; }
36- & .c-ref { width: 120px; }
37- & .c-lang { width: 120px; }
34+ display: flex; flex-direction: column; gap: var(--s5);
35+ & .home-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s3) var(--s4); margin-bottom: var(--s4); }
36+ & .home-bar .filter { flex: 1 1 260px; }
37+ & .sort { display: flex; align-items: center; gap: var(--s1); font-size: var(--fs-s); }
38+ & .sort > .mute { margin-right: var(--s1); }
39+ & .sort a {
40+ display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 var(--s2);
41+ color: var(--ink-dim); text-decoration: none; border: 1px solid transparent; border-radius: var(--r-s);
42+ }
43+ & .sort a:hover { color: var(--ink-hi); background: var(--bg-raised); }
44+ & .sort a[aria-current="true"] { color: var(--ink-hi); background: var(--bg-raised); border-color: var(--border-strong); }
45+ }
46+ .repos {
47+ display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 380px), 1fr)); gap: var(--s3);
48+ margin: 0; padding: 0; list-style: none;
49+ & .repo {
50+ position: relative; display: flex; flex-direction: column; gap: var(--s2); min-width: 0;
51+ padding: var(--s4); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);
52+ }
53+ & .repo:hover { background: var(--bg-raised); border-color: var(--border-strong); }
54+ & .repo[data-cursor] { background: var(--bg-raised); border-color: var(--ink-mute); box-shadow: inset 3px 0 0 var(--ink-hi); }
55+ & .repo[hidden] { display: none; }
56+ & .repo-name { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1) var(--s2); font-size: 17px; line-height: 26px; overflow-wrap: anywhere; }
57+ & .repo-name a { color: var(--ink-hi); text-decoration: none; }
58+ /* the whole card is the target of the name link */
59+ & .repo-name a::after { content: ""; position: absolute; inset: 0; border-radius: var(--r); }
60+ & .repo-name a:focus-visible { outline: none; }
61+ & .repo-name a:focus-visible::after { outline: 2px solid var(--focus); outline-offset: 1px; }
62+ & .repo:hover .repo-name a, & .repo[data-cursor] .repo-name a { text-decoration: underline; }
63+ & .repo-name .pill { margin-left: 0; }
64+ & .repo-desc {
65+ flex: 1; color: var(--ink-dim);
66+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
67+ }
68+ & .repo-desc.mute { color: var(--ink-mute); font-style: italic; }
69+ & .repo-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s4); font-size: var(--fs-s); color: var(--ink-mute); }
70+ & .repo-meta time { color: var(--ink-dim); }
71+ & .repo-branch { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); color: var(--ink-dim); }
3872 }
3973 .list.tree {
4074 & .c-kind { width: 36px; color: var(--ink-mute); padding-right: 0; text-align: center; }
@@ -28,13 +28,47 @@
28 & .c-sub { color: var(--ink-dim); }28 & .c-sub { color: var(--ink-dim); }
29 }29 }
30 30
31+ /* home: the repositories as cards in a grid (one column on a phone); the
32+ page is a stack of sections, so others can sit beside or below */
31 .home {33 .home {
32- & .c-name { width: 26%; font-family: var(--mono); font-weight: 700; }34+ display: flex; flex-direction: column; gap: var(--s5);
33- & th.c-name { font-family: var(--font); }35+ & .home-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s3) var(--s4); margin-bottom: var(--s4); }
34- & .c-desc { width: auto; }36+ & .home-bar .filter { flex: 1 1 260px; }
35- & .c-when { width: 130px; }37+ & .sort { display: flex; align-items: center; gap: var(--s1); font-size: var(--fs-s); }
36- & .c-ref { width: 120px; }38+ & .sort > .mute { margin-right: var(--s1); }
37- & .c-lang { width: 120px; }39+ & .sort a {
40+ display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 var(--s2);
41+ color: var(--ink-dim); text-decoration: none; border: 1px solid transparent; border-radius: var(--r-s);
42+ }
43+ & .sort a:hover { color: var(--ink-hi); background: var(--bg-raised); }
44+ & .sort a[aria-current="true"] { color: var(--ink-hi); background: var(--bg-raised); border-color: var(--border-strong); }
45+ }
46+ .repos {
47+ display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 380px), 1fr)); gap: var(--s3);
48+ margin: 0; padding: 0; list-style: none;
49+ & .repo {
50+ position: relative; display: flex; flex-direction: column; gap: var(--s2); min-width: 0;
51+ padding: var(--s4); background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r);
52+ }
53+ & .repo:hover { background: var(--bg-raised); border-color: var(--border-strong); }
54+ & .repo[data-cursor] { background: var(--bg-raised); border-color: var(--ink-mute); box-shadow: inset 3px 0 0 var(--ink-hi); }
55+ & .repo[hidden] { display: none; }
56+ & .repo-name { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s1) var(--s2); font-size: 17px; line-height: 26px; overflow-wrap: anywhere; }
57+ & .repo-name a { color: var(--ink-hi); text-decoration: none; }
58+ /* the whole card is the target of the name link */
59+ & .repo-name a::after { content: ""; position: absolute; inset: 0; border-radius: var(--r); }
60+ & .repo-name a:focus-visible { outline: none; }
61+ & .repo-name a:focus-visible::after { outline: 2px solid var(--focus); outline-offset: 1px; }
62+ & .repo:hover .repo-name a, & .repo[data-cursor] .repo-name a { text-decoration: underline; }
63+ & .repo-name .pill { margin-left: 0; }
64+ & .repo-desc {
65+ flex: 1; color: var(--ink-dim);
66+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
67+ }
68+ & .repo-desc.mute { color: var(--ink-mute); font-style: italic; }
69+ & .repo-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s4); font-size: var(--fs-s); color: var(--ink-mute); }
70+ & .repo-meta time { color: var(--ink-dim); }
71+ & .repo-branch { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); color: var(--ink-dim); }
38 }72 }
39 .list.tree {73 .list.tree {
40 & .c-kind { width: 36px; color: var(--ink-mute); padding-right: 0; text-align: center; }74 & .c-kind { width: 36px; color: var(--ink-mute); padding-right: 0; text-align: center; }
M internal/adapters/web/view.go +10 -8
@@ -83,25 +83,27 @@ type HomeRow struct {
8383 Pushed string
8484 PushedAt string // RFC 3339, "" when never pushed
8585 Branch string
86- Tag string
8786 Cursor bool
87+ // Private marks a private repository; only set for a signed-in visitor.
88+ Private bool
8889 // Fresh marks a row that a push just updated (live updates only).
8990 Fresh bool
9091 }
9192
92-// SortCol is a column header of the home list; it links to its sort order.
93-type SortCol struct {
93+// SortLink is one way to order the home list; it links to that order.
94+type SortLink struct {
9495 Label string
9596 Href string
96- Dir string // "ascending" | "descending" | "none"
97- Mark string
98- Class string
97+ Mark string // "▲" or "▼" on the current order, "" otherwise
98+ // State says the current direction and what the link does, for screen
99+ // readers (the mark is hidden from them); "" off the current order.
100+ State string
99101 }
100102
101103 // HomePage is the repository list.
102104 type HomePage struct {
103- Cols []SortCol
104- Rows []HomeRow
105+ Sorts []SortLink
106+ Rows []HomeRow
105107 }
106108
107109 // Fact is one key/value row of the facts block.
@@ -83,25 +83,27 @@ type HomeRow struct {
83 Pushed string83 Pushed string
84 PushedAt string // RFC 3339, "" when never pushed84 PushedAt string // RFC 3339, "" when never pushed
85 Branch string85 Branch string
86- Tag string
87 Cursor bool86 Cursor bool
87+ // Private marks a private repository; only set for a signed-in visitor.
88+ Private bool
88 // Fresh marks a row that a push just updated (live updates only).89 // Fresh marks a row that a push just updated (live updates only).
89 Fresh bool90 Fresh bool
90 }91 }
91 92
92-// SortCol is a column header of the home list; it links to its sort order.93+// SortLink is one way to order the home list; it links to that order.
93-type SortCol struct {94+type SortLink struct {
94 Label string95 Label string
95 Href string96 Href string
96- Dir string // "ascending" | "descending" | "none"97+ Mark string // "▲" or "▼" on the current order, "" otherwise
97- Mark string98+ // State says the current direction and what the link does, for screen
98- Class string99+ // readers (the mark is hidden from them); "" off the current order.
100+ State string
99 }101 }
100 102
101 // HomePage is the repository list.103 // HomePage is the repository list.
102 type HomePage struct {104 type HomePage struct {
103- Cols []SortCol105+ Sorts []SortLink
104- Rows []HomeRow106+ Rows []HomeRow
105 }107 }
106 108
107 // Fact is one key/value row of the facts block.109 // Fact is one key/value row of the facts block.
M internal/app/testdata/web/home.html +39 -46
@@ -49,58 +49,51 @@
4949 </header>
5050 <div class="work">
5151 <main id="main" tabindex="-1">
52+<h1 class="sr">Repositories</h1>
53+<div class="home">
54+<section class="home-repos" aria-label="Repositories">
55+<div class="home-bar">
5256 <div class="filter">
5357 <label for="home-filter" class="sr">filter</label>
54-<input id="home-filter" type="search" data-filter="#repo-rows > tr" placeholder="Filter repositories…" autocomplete="off" hx-on:input="ermite.filter(this)">
58+<input id="home-filter" type="search" data-filter="#repo-rows > li" placeholder="Filter repositories…" autocomplete="off" hx-on:input="ermite.filter(this)">
5559 </div>
56-<table class="list home" aria-label="Repositories">
57-<thead>
58-<tr>
59-<th class="cur">
60-</th>
61-<th class="c-name" aria-sort="none">
62-<a href="/?sort=name&amp;dir=asc">name </a>
63-</th>
64-<th class="c-desc" aria-sort="none">
65-<a href="/?sort=desc&amp;dir=asc">description </a>
66-</th>
67-<th class="c-when" aria-sort="descending">
68-<a href="/?sort=push&amp;dir=asc">last push <span aria-hidden="true">▼</span>
60+<nav class="sort" aria-label="Sort repositories">
61+<span class="mute">sort</span>
62+<a href="/?sort=push&amp;dir=asc" aria-current="true">last push <span aria-hidden="true">▼</span>
63+<span class="sr">, descending, select for ascending</span>
6964 </a>
70-</th>
71-<th class="c-ref" aria-sort="none">
72-<a href="/?sort=branch&amp;dir=asc">branch </a>
73-</th>
74-</tr>
75-</thead>
76-<tbody id="repo-rows" data-rows>
77-<tr id="repo-demo" data-cursor>
78-<td class="cur" aria-hidden="true">
79-</td>
80-<td class="c-name">
65+<a href="/?sort=name&amp;dir=asc">name </a>
66+</nav>
67+</div>
68+<ul class="repos" id="repo-rows" data-rows>
69+<li id="repo-demo" class="repo" data-cursor>
70+<h2 class="repo-name">
8171 <a href="/demo">demo</a>
82-</td>
83-<td class="c-desc dim">A demo repository</td>
84-<td class="c-when nowrap">
85-<time datetime="STAMP">AGO</time>
86-</td>
87-<td class="c-ref">main</td>
88-</tr>
89-<tr id="repo-quiet">
90-<td class="cur" aria-hidden="true">
91-</td>
92-<td class="c-name">
72+</h2>
73+<p class="repo-desc">A demo repository</p>
74+<p class="repo-meta">
75+<span class="repo-branch">
76+<span class="ico i-branch" aria-hidden="true">
77+</span>main</span>
78+<span>pushed <time datetime="STAMP" title="STAMP">AGO</time>
79+</span>
80+</p>
81+</li>
82+<li id="repo-quiet" class="repo">
83+<h2 class="repo-name">
9384 <a href="/quiet">quiet</a>
94-</td>
95-<td class="c-desc dim">
96-</td>
97-<td class="c-when nowrap">
98-<time>never</time>
99-</td>
100-<td class="c-ref">main</td>
101-</tr>
102-</tbody>
103-</table>
85+</h2>
86+<p class="repo-desc mute">no description</p>
87+<p class="repo-meta">
88+<span class="repo-branch">
89+<span class="ico i-branch" aria-hidden="true">
90+</span>main</span>
91+<span>never pushed</span>
92+</p>
93+</li>
94+</ul>
95+</section>
96+</div>
10497 </main>
10598 </div>
10699 <footer class="foot">
@@ -49,58 +49,51 @@
49 </header>49 </header>
50 <div class="work">50 <div class="work">
51 <main id="main" tabindex="-1">51 <main id="main" tabindex="-1">
52+<h1 class="sr">Repositories</h1>
53+<div class="home">
54+<section class="home-repos" aria-label="Repositories">
55+<div class="home-bar">
52 <div class="filter">56 <div class="filter">
53 <label for="home-filter" class="sr">filter</label>57 <label for="home-filter" class="sr">filter</label>
54-<input id="home-filter" type="search" data-filter="#repo-rows > tr" placeholder="Filter repositories…" autocomplete="off" hx-on:input="ermite.filter(this)">58+<input id="home-filter" type="search" data-filter="#repo-rows > li" placeholder="Filter repositories…" autocomplete="off" hx-on:input="ermite.filter(this)">
55 </div>59 </div>
56-<table class="list home" aria-label="Repositories">60+<nav class="sort" aria-label="Sort repositories">
57-<thead>61+<span class="mute">sort</span>
58-<tr>62+<a href="/?sort=push&amp;dir=asc" aria-current="true">last push <span aria-hidden="true">▼</span>
59-<th class="cur">63+<span class="sr">, descending, select for ascending</span>
60-</th>
61-<th class="c-name" aria-sort="none">
62-<a href="/?sort=name&amp;dir=asc">name </a>
63-</th>
64-<th class="c-desc" aria-sort="none">
65-<a href="/?sort=desc&amp;dir=asc">description </a>
66-</th>
67-<th class="c-when" aria-sort="descending">
68-<a href="/?sort=push&amp;dir=asc">last push <span aria-hidden="true">▼</span>
69 </a>64 </a>
70-</th>65+<a href="/?sort=name&amp;dir=asc">name </a>
71-<th class="c-ref" aria-sort="none">66+</nav>
72-<a href="/?sort=branch&amp;dir=asc">branch </a>67+</div>
73-</th>68+<ul class="repos" id="repo-rows" data-rows>
74-</tr>69+<li id="repo-demo" class="repo" data-cursor>
75-</thead>70+<h2 class="repo-name">
76-<tbody id="repo-rows" data-rows>
77-<tr id="repo-demo" data-cursor>
78-<td class="cur" aria-hidden="true">
79-</td>
80-<td class="c-name">
81 <a href="/demo">demo</a>71 <a href="/demo">demo</a>
82-</td>72+</h2>
83-<td class="c-desc dim">A demo repository</td>73+<p class="repo-desc">A demo repository</p>
84-<td class="c-when nowrap">74+<p class="repo-meta">
85-<time datetime="STAMP">AGO</time>75+<span class="repo-branch">
86-</td>76+<span class="ico i-branch" aria-hidden="true">
87-<td class="c-ref">main</td>77+</span>main</span>
88-</tr>78+<span>pushed <time datetime="STAMP" title="STAMP">AGO</time>
89-<tr id="repo-quiet">79+</span>
90-<td class="cur" aria-hidden="true">80+</p>
91-</td>81+</li>
92-<td class="c-name">82+<li id="repo-quiet" class="repo">
83+<h2 class="repo-name">
93 <a href="/quiet">quiet</a>84 <a href="/quiet">quiet</a>
94-</td>85+</h2>
95-<td class="c-desc dim">86+<p class="repo-desc mute">no description</p>
96-</td>87+<p class="repo-meta">
97-<td class="c-when nowrap">88+<span class="repo-branch">
98-<time>never</time>89+<span class="ico i-branch" aria-hidden="true">
99-</td>90+</span>main</span>
100-<td class="c-ref">main</td>91+<span>never pushed</span>
101-</tr>92+</p>
102-</tbody>93+</li>
103-</table>94+</ul>
95+</section>
96+</div>
104 </main>97 </main>
105 </div>98 </div>
106 <footer class="foot">99 <footer class="foot">
M internal/app/web_events_test.go +4 -4
@@ -122,7 +122,7 @@ func TestWebEventsHome(t *testing.T) {
122122 assert.Less(t, time.Since(start), 2*time.Second)
123123 assert.Equal(t, "home-list", ev.name)
124124 assert.NotEmpty(t, ev.id)
125- assert.Contains(t, ev.data, `<tr id="repo-pub"`)
125+ assert.Contains(t, ev.data, `<li id="repo-pub"`)
126126 assert.Contains(t, ev.data, `new push`)
127127 assert.Contains(t, ev.data, `href="/pub"`)
128128
@@ -131,7 +131,7 @@ func TestWebEventsHome(t *testing.T) {
131131 require.NoError(t, err)
132132 ev = s.next(t, watchWait)
133133 assert.Equal(t, "home-list", ev.name)
134- assert.Contains(t, ev.data, `<tr id="repo-fresh"`)
134+ assert.Contains(t, ev.data, `<li id="repo-fresh"`)
135135 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")
136136 assert.NotContains(t, ev.data, "new push")
137137
@@ -141,13 +141,13 @@ func TestWebEventsHome(t *testing.T) {
141141 require.NoError(t, err)
142142 ev = s.next(t, watchWait)
143143 assert.Equal(t, "home-list", ev.name)
144- assert.Contains(t, ev.data, `<tr id="repo-fresh" data-gone`)
144+ assert.Contains(t, ev.data, `<li id="repo-fresh" data-gone`)
145145 assert.NotContains(t, ev.data, "fresh one")
146146
147147 // And a deleted one.
148148 require.NoError(t, h.forge.Repos.Delete(h.ctx, domain.Root(), h.name("pub")))
149149 ev = s.next(t, watchWait)
150- assert.Contains(t, ev.data, `<tr id="repo-pub" data-gone`)
150+ assert.Contains(t, ev.data, `<li id="repo-pub" data-gone`)
151151 }
152152
153153 // AC5: the stream of a repository carries its last push; the refs page its
@@ -122,7 +122,7 @@ func TestWebEventsHome(t *testing.T) {
122 assert.Less(t, time.Since(start), 2*time.Second)122 assert.Less(t, time.Since(start), 2*time.Second)
123 assert.Equal(t, "home-list", ev.name)123 assert.Equal(t, "home-list", ev.name)
124 assert.NotEmpty(t, ev.id)124 assert.NotEmpty(t, ev.id)
125- assert.Contains(t, ev.data, `<tr id="repo-pub"`)125+ assert.Contains(t, ev.data, `<li id="repo-pub"`)
126 assert.Contains(t, ev.data, `new push`)126 assert.Contains(t, ev.data, `new push`)
127 assert.Contains(t, ev.data, `href="/pub"`)127 assert.Contains(t, ev.data, `href="/pub"`)
128 128
@@ -131,7 +131,7 @@ func TestWebEventsHome(t *testing.T) {
131 require.NoError(t, err)131 require.NoError(t, err)
132 ev = s.next(t, watchWait)132 ev = s.next(t, watchWait)
133 assert.Equal(t, "home-list", ev.name)133 assert.Equal(t, "home-list", ev.name)
134- assert.Contains(t, ev.data, `<tr id="repo-fresh"`)134+ assert.Contains(t, ev.data, `<li id="repo-fresh"`)
135 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")135 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")
136 assert.NotContains(t, ev.data, "new push")136 assert.NotContains(t, ev.data, "new push")
137 137
@@ -141,13 +141,13 @@ func TestWebEventsHome(t *testing.T) {
141 require.NoError(t, err)141 require.NoError(t, err)
142 ev = s.next(t, watchWait)142 ev = s.next(t, watchWait)
143 assert.Equal(t, "home-list", ev.name)143 assert.Equal(t, "home-list", ev.name)
144- assert.Contains(t, ev.data, `<tr id="repo-fresh" data-gone`)144+ assert.Contains(t, ev.data, `<li id="repo-fresh" data-gone`)
145 assert.NotContains(t, ev.data, "fresh one")145 assert.NotContains(t, ev.data, "fresh one")
146 146
147 // And a deleted one.147 // And a deleted one.
148 require.NoError(t, h.forge.Repos.Delete(h.ctx, domain.Root(), h.name("pub")))148 require.NoError(t, h.forge.Repos.Delete(h.ctx, domain.Root(), h.name("pub")))
149 ev = s.next(t, watchWait)149 ev = s.next(t, watchWait)
150- assert.Contains(t, ev.data, `<tr id="repo-pub" data-gone`)150+ assert.Contains(t, ev.data, `<li id="repo-pub" data-gone`)
151 }151 }
152 152
153 // AC5: the stream of a repository carries its last push; the refs page its153 // AC5: the stream of a repository carries its last push; the refs page its
M internal/app/web_session_test.go +3 -0
@@ -118,6 +118,7 @@ func TestWebLoginEndToEnd(t *testing.T) {
118118 assert.Equal(t, "no-cache", home.hdr.Get("Cache-Control"))
119119 assert.Equal(t, []string{"Accept-Encoding"}, home.hdr.Values("Vary"), "no Cookie in the Vary of a public page")
120120 assert.NotContains(t, home.body, `action="/-/logout"`)
121+ assert.NotContains(t, home.body, `>private</span>`, "an anonymous visitor sees no private badge")
121122 assert.Equal(t, 404, b.get("/secret", nil).status)
122123
123124 // The CLI asks for a link and prints it; stderr says when it ends.
@@ -199,6 +200,8 @@ func TestWebLoginEndToEnd(t *testing.T) {
199200 }
200201 home = b.get("/", as(token))
201202 assert.Contains(t, home.body, "secret")
203+ assert.Equal(t, 1, strings.Count(home.body, `<span class="pill">private</span>`), "only the private repository has the badge")
204+ assert.Regexp(t, `(?s)<li id="repo-secret".*?<span class="pill">private</span>.*?</li>\s*<li id="repo-pub"|<li id="repo-pub".*?</li>\s*<li id="repo-secret".*?<span class="pill">private</span>`, home.body)
202205 assert.Contains(t, home.body, `<form class="owner" method="post" action="/-/logout">`)
203206 assert.Contains(t, b.get("/secret/blob/main/notes.txt", as(token)).body, "notes.txt")
204207 raw := b.get("/secret/raw/main/notes.txt", as(token))
@@ -118,6 +118,7 @@ func TestWebLoginEndToEnd(t *testing.T) {
118 assert.Equal(t, "no-cache", home.hdr.Get("Cache-Control"))118 assert.Equal(t, "no-cache", home.hdr.Get("Cache-Control"))
119 assert.Equal(t, []string{"Accept-Encoding"}, home.hdr.Values("Vary"), "no Cookie in the Vary of a public page")119 assert.Equal(t, []string{"Accept-Encoding"}, home.hdr.Values("Vary"), "no Cookie in the Vary of a public page")
120 assert.NotContains(t, home.body, `action="/-/logout"`)120 assert.NotContains(t, home.body, `action="/-/logout"`)
121+ assert.NotContains(t, home.body, `>private</span>`, "an anonymous visitor sees no private badge")
121 assert.Equal(t, 404, b.get("/secret", nil).status)122 assert.Equal(t, 404, b.get("/secret", nil).status)
122 123
123 // The CLI asks for a link and prints it; stderr says when it ends.124 // The CLI asks for a link and prints it; stderr says when it ends.
@@ -199,6 +200,8 @@ func TestWebLoginEndToEnd(t *testing.T) {
199 }200 }
200 home = b.get("/", as(token))201 home = b.get("/", as(token))
201 assert.Contains(t, home.body, "secret")202 assert.Contains(t, home.body, "secret")
203+ assert.Equal(t, 1, strings.Count(home.body, `<span class="pill">private</span>`), "only the private repository has the badge")
204+ assert.Regexp(t, `(?s)<li id="repo-secret".*?<span class="pill">private</span>.*?</li>\s*<li id="repo-pub"|<li id="repo-pub".*?</li>\s*<li id="repo-secret".*?<span class="pill">private</span>`, home.body)
202 assert.Contains(t, home.body, `<form class="owner" method="post" action="/-/logout">`)205 assert.Contains(t, home.body, `<form class="owner" method="post" action="/-/logout">`)
203 assert.Contains(t, b.get("/secret/blob/main/notes.txt", as(token)).body, "notes.txt")206 assert.Contains(t, b.get("/secret/blob/main/notes.txt", as(token)).body, "notes.txt")
204 raw := b.get("/secret/raw/main/notes.txt", as(token))207 raw := b.get("/secret/raw/main/notes.txt", as(token))

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 45

↑↓ moveenter openesc close