detached 4277264

feat(web): order the home page by last activity and move repositories up live

commit
427726483415e5fdfc0b1c4aae86aea88a80a1fa
author
brice · · 2026-10-19 15:48 UTC
parent
4fc291e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
28 files · +816 -525
M gen/ermite/storage/repo.pb.go +22 -11 generated file: collapsed
M internal/adapters/kvstore/repos.go +2 -0
@@ -208,6 +208,7 @@ func encodeRepo(r domain.Repo) ([]byte, error) {
208208 Visibility: vis,
209209 CreatedAt: encodeTime(r.CreatedAt),
210210 PushedAt: encodeTime(r.PushedAt),
211+ ActiveAt: encodeTime(r.ActiveAt),
211212 Settings: pbSettings,
212213 }
213214 return mustMarshal(pb), nil
@@ -229,6 +230,7 @@ func decodeRepo(val []byte) (domain.Repo, error) {
229230 Visibility: decodeVisibility(pb.GetVisibility()),
230231 CreatedAt: decodeTime(pb.GetCreatedAt()),
231232 PushedAt: decodeTime(pb.GetPushedAt()),
233+ ActiveAt: decodeTime(pb.GetActiveAt()),
232234 Settings: decodeSettings(pb.GetSettings()),
233235 }, nil
234236 }
@@ -208,6 +208,7 @@ func encodeRepo(r domain.Repo) ([]byte, error) {
208 Visibility: vis,208 Visibility: vis,
209 CreatedAt: encodeTime(r.CreatedAt),209 CreatedAt: encodeTime(r.CreatedAt),
210 PushedAt: encodeTime(r.PushedAt),210 PushedAt: encodeTime(r.PushedAt),
211+ ActiveAt: encodeTime(r.ActiveAt),
211 Settings: pbSettings,212 Settings: pbSettings,
212 }213 }
213 return mustMarshal(pb), nil214 return mustMarshal(pb), nil
@@ -229,6 +230,7 @@ func decodeRepo(val []byte) (domain.Repo, error) {
229 Visibility: decodeVisibility(pb.GetVisibility()),230 Visibility: decodeVisibility(pb.GetVisibility()),
230 CreatedAt: decodeTime(pb.GetCreatedAt()),231 CreatedAt: decodeTime(pb.GetCreatedAt()),
231 PushedAt: decodeTime(pb.GetPushedAt()),232 PushedAt: decodeTime(pb.GetPushedAt()),
233+ ActiveAt: decodeTime(pb.GetActiveAt()),
232 Settings: decodeSettings(pb.GetSettings()),234 Settings: decodeSettings(pb.GetSettings()),
233 }, nil235 }, nil
234 }236 }
M internal/adapters/kvstore/store_test.go +2 -1
@@ -43,7 +43,7 @@ func TestCreateGetListUpdateDelete(t *testing.T) {
4343 s := openStore(t, t.TempDir())
4444
4545 b := domain.Repo{Name: name(t, "b"), Description: "second", Topics: []string{"cli", "go"}, Visibility: domain.VisibilityPublic, CreatedAt: created, Settings: domain.DefaultRepoSettings()}
46- a := domain.Repo{Name: name(t, "a"), Visibility: domain.VisibilityPrivate, CreatedAt: created, PushedAt: created.Add(time.Hour), Settings: domain.DefaultRepoSettings()}
46+ a := domain.Repo{Name: name(t, "a"), Visibility: domain.VisibilityPrivate, CreatedAt: created, PushedAt: created.Add(time.Hour), ActiveAt: created.Add(2 * time.Hour), Settings: domain.DefaultRepoSettings()}
4747 require.NoError(t, s.Create(ctx, b))
4848 require.NoError(t, s.Create(ctx, a))
4949
@@ -54,6 +54,7 @@ func TestCreateGetListUpdateDelete(t *testing.T) {
5454 require.NoError(t, err)
5555 assert.Equal(t, a, got)
5656 assert.True(t, got.PushedAt.Equal(a.PushedAt))
57+ assert.True(t, got.ActiveAt.Equal(a.ActiveAt))
5758
5859 list, err := s.List(ctx)
5960 require.NoError(t, err)
@@ -43,7 +43,7 @@ func TestCreateGetListUpdateDelete(t *testing.T) {
43 s := openStore(t, t.TempDir())43 s := openStore(t, t.TempDir())
44 44
45 b := domain.Repo{Name: name(t, "b"), Description: "second", Topics: []string{"cli", "go"}, Visibility: domain.VisibilityPublic, CreatedAt: created, Settings: domain.DefaultRepoSettings()}45 b := domain.Repo{Name: name(t, "b"), Description: "second", Topics: []string{"cli", "go"}, Visibility: domain.VisibilityPublic, CreatedAt: created, Settings: domain.DefaultRepoSettings()}
46- a := domain.Repo{Name: name(t, "a"), Visibility: domain.VisibilityPrivate, CreatedAt: created, PushedAt: created.Add(time.Hour), Settings: domain.DefaultRepoSettings()}46+ a := domain.Repo{Name: name(t, "a"), Visibility: domain.VisibilityPrivate, CreatedAt: created, PushedAt: created.Add(time.Hour), ActiveAt: created.Add(2 * time.Hour), Settings: domain.DefaultRepoSettings()}
47 require.NoError(t, s.Create(ctx, b))47 require.NoError(t, s.Create(ctx, b))
48 require.NoError(t, s.Create(ctx, a))48 require.NoError(t, s.Create(ctx, a))
49 49
@@ -54,6 +54,7 @@ func TestCreateGetListUpdateDelete(t *testing.T) {
54 require.NoError(t, err)54 require.NoError(t, err)
55 assert.Equal(t, a, got)55 assert.Equal(t, a, got)
56 assert.True(t, got.PushedAt.Equal(a.PushedAt))56 assert.True(t, got.PushedAt.Equal(a.PushedAt))
57+ assert.True(t, got.ActiveAt.Equal(a.ActiveAt))
57 58
58 list, err := s.List(ctx)59 list, err := s.List(ctx)
59 require.NoError(t, err)60 require.NoError(t, err)
M internal/adapters/web/components.templ +2 -2
@@ -37,7 +37,7 @@ templ CloneBlock(c Clone) {
3737 // right its figures and CI state over its newest commits. Its id is the
3838 // target of SSE swaps; the name link comes first, `enter` follows it.
3939 templ HomeRowView(r HomeRow) {
40- <li id={ r.ID } class="repo" data-cursor?={ r.Cursor }>
40+ <li id={ r.ID } class="repo" data-cursor?={ r.Cursor } data-activity?={ r.Fresh }>
4141 <div class="repo-main">
4242 <h2 class="repo-name">
4343 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
@@ -45,7 +45,7 @@ templ HomeRowView(r HomeRow) {
4545 <span class="pill">private</span>
4646 }
4747 if r.Fresh {
48- <span class="pill new">new push</span>
48+ <span class="pill new">new activity</span>
4949 }
5050 </h2>
5151 if r.Desc != "" {
@@ -37,7 +37,7 @@ templ CloneBlock(c Clone) {
37 // right its figures and CI state over its newest commits. Its id is the37 // right its figures and CI state over its newest commits. Its id is the
38 // target of SSE swaps; the name link comes first, `enter` follows it.38 // target of SSE swaps; the name link comes first, `enter` follows it.
39 templ HomeRowView(r HomeRow) {39 templ HomeRowView(r HomeRow) {
40- <li id={ r.ID } class="repo" data-cursor?={ r.Cursor }>40+ <li id={ r.ID } class="repo" data-cursor?={ r.Cursor } data-activity?={ r.Fresh }>
41 <div class="repo-main">41 <div class="repo-main">
42 <h2 class="repo-name">42 <h2 class="repo-name">
43 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>43 <a href={ templ.SafeURL(r.Href) }>{ r.Name }</a>
@@ -45,7 +45,7 @@ templ HomeRowView(r HomeRow) {
45 <span class="pill">private</span>45 <span class="pill">private</span>
46 }46 }
47 if r.Fresh {47 if r.Fresh {
48- <span class="pill new">new push</span>48+ <span class="pill new">new activity</span>
49 }49 }
50 </h2>50 </h2>
51 if r.Desc != "" {51 if r.Desc != "" {
M internal/adapters/web/components_templ.go +119 -113 generated file: collapsed
M internal/adapters/web/events.go +2 -0
@@ -50,6 +50,8 @@ import (
5050 //
5151 // home-list data: <li id="repo-<name>">…</li>, or <li id="repo-<name>" data-gone></li>
5252 // for a repository that is deleted or no longer visible
53+// Activity rows carry data-activity: push, MR lifecycle/comment,
54+// or issue lifecycle/comment (never pending moderation or CI).
5355 // repo-last-push data: <dd id="last-push-<name>">…</dd>
5456 // refs-list data: <tbody id="ref-rows">…</tbody>, and again with
5557 // <dl id="refs-kv">…</dl>, the facts of the rail
@@ -50,6 +50,8 @@ import (
50 //50 //
51 // home-list data: <li id="repo-<name>">…</li>, or <li id="repo-<name>" data-gone></li>51 // home-list data: <li id="repo-<name>">…</li>, or <li id="repo-<name>" data-gone></li>
52 // for a repository that is deleted or no longer visible52 // for a repository that is deleted or no longer visible
53+// Activity rows carry data-activity: push, MR lifecycle/comment,
54+// or issue lifecycle/comment (never pending moderation or CI).
53 // repo-last-push data: <dd id="last-push-<name>">…</dd>55 // repo-last-push data: <dd id="last-push-<name>">…</dd>
54 // refs-list data: <tbody id="ref-rows">…</tbody>, and again with56 // refs-list data: <tbody id="ref-rows">…</tbody>, and again with
55 // <dl id="refs-kv">…</dl>, the facts of the rail57 // <dl id="refs-kv">…</dl>, the facts of the rail
M internal/adapters/web/fragments.go +18 -0
@@ -112,6 +112,24 @@ func (l *liveStream) home(ctx context.Context, we ports.WatchEvent) ([]fragment,
112112 name = ev.Repo.String()
113113 case domain.PushEvent:
114114 name, fresh = ev.Repo.String(), true
115+ case domain.MergeRequestOpened:
116+ name, fresh = ev.MergeRequest.Repo.String(), true
117+ case domain.MergeRequestMerged:
118+ name, fresh = ev.MergeRequest.Repo.String(), true
119+ case domain.MergeRequestClosed:
120+ name, fresh = ev.MergeRequest.Repo.String(), true
121+ case domain.MergeRequestReady:
122+ name, fresh = ev.MergeRequest.Repo.String(), true
123+ case domain.CommentAdded:
124+ name, fresh = ev.Repo.String(), true
125+ case domain.IssueOpened:
126+ name, fresh = ev.Issue.Repo.String(), true
127+ case domain.IssueClosed:
128+ name, fresh = ev.Issue.Repo.String(), true
129+ case domain.IssueReopened:
130+ name, fresh = ev.Issue.Repo.String(), true
131+ case domain.IssueCommented:
132+ name, fresh = ev.Repo.String(), true
115133 case domain.RunUpdated:
116134 name = ev.Run.Repo.String() // the CI state of the row
117135 case domain.JobUpdated:
@@ -112,6 +112,24 @@ func (l *liveStream) home(ctx context.Context, we ports.WatchEvent) ([]fragment,
112 name = ev.Repo.String()112 name = ev.Repo.String()
113 case domain.PushEvent:113 case domain.PushEvent:
114 name, fresh = ev.Repo.String(), true114 name, fresh = ev.Repo.String(), true
115+ case domain.MergeRequestOpened:
116+ name, fresh = ev.MergeRequest.Repo.String(), true
117+ case domain.MergeRequestMerged:
118+ name, fresh = ev.MergeRequest.Repo.String(), true
119+ case domain.MergeRequestClosed:
120+ name, fresh = ev.MergeRequest.Repo.String(), true
121+ case domain.MergeRequestReady:
122+ name, fresh = ev.MergeRequest.Repo.String(), true
123+ case domain.CommentAdded:
124+ name, fresh = ev.Repo.String(), true
125+ case domain.IssueOpened:
126+ name, fresh = ev.Issue.Repo.String(), true
127+ case domain.IssueClosed:
128+ name, fresh = ev.Issue.Repo.String(), true
129+ case domain.IssueReopened:
130+ name, fresh = ev.Issue.Repo.String(), true
131+ case domain.IssueCommented:
132+ name, fresh = ev.Repo.String(), true
115 case domain.RunUpdated:133 case domain.RunUpdated:
116 name = ev.Run.Repo.String() // the CI state of the row134 name = ev.Run.Repo.String() // the CI state of the row
117 case domain.JobUpdated:135 case domain.JobUpdated:
M internal/adapters/web/home.go +22 -11
@@ -56,12 +56,12 @@ func (s *Server) listRepos(ctx context.Context) ([]ports.RepoView, error) {
5656 // homeSorts are the orders the home list offers, in order. The query also
5757 // accepts "desc" and "branch", which the page does not link to.
5858 var homeSorts = []struct{ key, label string }{
59- {"push", "last push"},
59+ {"activity", "last activity"},
6060 {"name", "name"},
6161 }
6262
6363 // homeKeys are the keys of ?sort= that sortRepos knows.
64-var homeKeys = []string{"push", "name", "desc", "branch"}
64+var homeKeys = []string{"activity", "name", "desc", "branch"}
6565
6666 // sortRepos orders repos by the column key; ties keep the order by name.
6767 func sortRepos(repos []ports.RepoView, key string, desc bool) {
@@ -71,8 +71,8 @@ func sortRepos(repos []ports.RepoView, key string, desc bool) {
7171 return strings.ToLower(a.Description) < strings.ToLower(b.Description)
7272 case "branch":
7373 return a.DefaultBranch < b.DefaultBranch
74- case "push":
75- return a.PushedAt.Before(b.PushedAt)
74+ case "activity":
75+ return repoActiveAt(a).Before(repoActiveAt(b))
7676 }
7777 return a.Name.String() < b.Name.String()
7878 }
@@ -84,17 +84,17 @@ func sortRepos(repos []ports.RepoView, key string, desc bool) {
8484 })
8585 }
8686
87-// home lists the repositories: /?sort=<col>&dir=asc|desc, newest push first
87+// home lists the repositories: /?sort=<col>&dir=asc|desc, newest activity first
8888 // by default.
8989 func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, int, error) {
9090 repos, err := s.listRepos(ctx)
9191 if err != nil {
9292 return nil, 0, err
9393 }
94- key, desc := "push", true
94+ key, desc := "activity", true
9595 for _, k := range homeKeys {
9696 if r.URL.Query().Get("sort") == k {
97- key, desc = k, k == "push"
97+ key, desc = k, k == "activity"
9898 }
9999 }
100100 if d := r.URL.Query().Get("dir"); d == "asc" || d == "desc" {
@@ -103,11 +103,11 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
103103 sortRepos(repos, key, desc)
104104
105105 now := s.cfg.Now()
106- p := HomePage{}
106+ p := HomePage{Sort: key, Desc: desc}
107107 for _, c := range homeSorts {
108108 sl := SortLink{Label: c.label}
109109 next := "asc"
110- if c.key == "push" {
110+ if c.key == "activity" {
111111 next = "desc"
112112 }
113113 if c.key == key {
@@ -267,8 +267,8 @@ func homeRow(r ports.RepoView, now time.Time, cursor, signed bool) HomeRow {
267267 Desc: r.Description, Topics: r.Topics, Cursor: cursor,
268268 Private: signed && r.Visibility == domain.VisibilityPrivate,
269269 }
270- if !r.PushedAt.IsZero() {
271- row.Active, row.ActiveAt = ago(now, r.PushedAt), rfc3339(r.PushedAt)
270+ if at := repoActiveAt(r); !at.IsZero() {
271+ row.Active, row.ActiveAt = ago(now, at), rfc3339(at)
272272 }
273273 return row
274274 }
@@ -284,3 +284,14 @@ func repoCmds(repos []ports.RepoView, except string) []CmdItem {
284284 }
285285 return out
286286 }
287+
288+// repoActiveAt falls back for repositories without recorded activity.
289+func repoActiveAt(r ports.RepoView) time.Time {
290+ if !r.ActiveAt.IsZero() {
291+ return r.ActiveAt
292+ }
293+ if r.PushedAt.After(r.CreatedAt) {
294+ return r.PushedAt
295+ }
296+ return r.CreatedAt
297+}
@@ -56,12 +56,12 @@ func (s *Server) listRepos(ctx context.Context) ([]ports.RepoView, error) {
56 // homeSorts are the orders the home list offers, in order. The query also56 // homeSorts are the orders the home list offers, in order. The query also
57 // accepts "desc" and "branch", which the page does not link to.57 // accepts "desc" and "branch", which the page does not link to.
58 var homeSorts = []struct{ key, label string }{58 var homeSorts = []struct{ key, label string }{
59- {"push", "last push"},59+ {"activity", "last activity"},
60 {"name", "name"},60 {"name", "name"},
61 }61 }
62 62
63 // homeKeys are the keys of ?sort= that sortRepos knows.63 // homeKeys are the keys of ?sort= that sortRepos knows.
64-var homeKeys = []string{"push", "name", "desc", "branch"}64+var homeKeys = []string{"activity", "name", "desc", "branch"}
65 65
66 // sortRepos orders repos by the column key; ties keep the order by name.66 // sortRepos orders repos by the column key; ties keep the order by name.
67 func sortRepos(repos []ports.RepoView, key string, desc bool) {67 func sortRepos(repos []ports.RepoView, key string, desc bool) {
@@ -71,8 +71,8 @@ func sortRepos(repos []ports.RepoView, key string, desc bool) {
71 return strings.ToLower(a.Description) < strings.ToLower(b.Description)71 return strings.ToLower(a.Description) < strings.ToLower(b.Description)
72 case "branch":72 case "branch":
73 return a.DefaultBranch < b.DefaultBranch73 return a.DefaultBranch < b.DefaultBranch
74- case "push":74+ case "activity":
75- return a.PushedAt.Before(b.PushedAt)75+ return repoActiveAt(a).Before(repoActiveAt(b))
76 }76 }
77 return a.Name.String() < b.Name.String()77 return a.Name.String() < b.Name.String()
78 }78 }
@@ -84,17 +84,17 @@ func sortRepos(repos []ports.RepoView, key string, desc bool) {
84 })84 })
85 }85 }
86 86
87-// home lists the repositories: /?sort=<col>&dir=asc|desc, newest push first87+// home lists the repositories: /?sort=<col>&dir=asc|desc, newest activity first
88 // by default.88 // by default.
89 func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, int, error) {89 func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, int, error) {
90 repos, err := s.listRepos(ctx)90 repos, err := s.listRepos(ctx)
91 if err != nil {91 if err != nil {
92 return nil, 0, err92 return nil, 0, err
93 }93 }
94- key, desc := "push", true94+ key, desc := "activity", true
95 for _, k := range homeKeys {95 for _, k := range homeKeys {
96 if r.URL.Query().Get("sort") == k {96 if r.URL.Query().Get("sort") == k {
97- key, desc = k, k == "push"97+ key, desc = k, k == "activity"
98 }98 }
99 }99 }
100 if d := r.URL.Query().Get("dir"); d == "asc" || d == "desc" {100 if d := r.URL.Query().Get("dir"); d == "asc" || d == "desc" {
@@ -103,11 +103,11 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
103 sortRepos(repos, key, desc)103 sortRepos(repos, key, desc)
104 104
105 now := s.cfg.Now()105 now := s.cfg.Now()
106- p := HomePage{}106+ p := HomePage{Sort: key, Desc: desc}
107 for _, c := range homeSorts {107 for _, c := range homeSorts {
108 sl := SortLink{Label: c.label}108 sl := SortLink{Label: c.label}
109 next := "asc"109 next := "asc"
110- if c.key == "push" {110+ if c.key == "activity" {
111 next = "desc"111 next = "desc"
112 }112 }
113 if c.key == key {113 if c.key == key {
@@ -267,8 +267,8 @@ func homeRow(r ports.RepoView, now time.Time, cursor, signed bool) HomeRow {
267 Desc: r.Description, Topics: r.Topics, Cursor: cursor,267 Desc: r.Description, Topics: r.Topics, Cursor: cursor,
268 Private: signed && r.Visibility == domain.VisibilityPrivate,268 Private: signed && r.Visibility == domain.VisibilityPrivate,
269 }269 }
270- if !r.PushedAt.IsZero() {270+ if at := repoActiveAt(r); !at.IsZero() {
271- row.Active, row.ActiveAt = ago(now, r.PushedAt), rfc3339(r.PushedAt)271+ row.Active, row.ActiveAt = ago(now, at), rfc3339(at)
272 }272 }
273 return row273 return row
274 }274 }
@@ -284,3 +284,14 @@ func repoCmds(repos []ports.RepoView, except string) []CmdItem {
284 }284 }
285 return out285 return out
286 }286 }
287+
288+// repoActiveAt falls back for repositories without recorded activity.
289+func repoActiveAt(r ports.RepoView) time.Time {
290+ if !r.ActiveAt.IsZero() {
291+ return r.ActiveAt
292+ }
293+ if r.PushedAt.After(r.CreatedAt) {
294+ return r.PushedAt
295+ }
296+ return r.CreatedAt
297+}
M internal/adapters/web/home_test.go +49 -1
@@ -252,7 +252,7 @@ func TestRunUpdatedRefreshesTheHomeRow(t *testing.T) {
252252 assert.Equal(t, "home-list", got[0].event)
253253 assert.Contains(t, got[0].html, `id="repo-proj"`)
254254 assert.Contains(t, got[0].html, "passing")
255- assert.NotContains(t, got[0].html, "new push", "a run is not a push")
255+ assert.NotContains(t, got[0].html, "new activity", "a run is not a push")
256256
257257 // A job that ended refreshes the row too; one in progress does not.
258258 got, err = (&liveStream{s: srv}).render(t.Context(), ports.WatchEvent{Event: domain.JobUpdated{Job: domain.CIJob{Repo: repo, Phase: domain.CIPhaseRunning}}})
@@ -344,3 +344,51 @@ func TestHomeOffersTheLayouts(t *testing.T) {
344344 require.NotEqual(t, -1, store)
345345 assert.Less(t, store, app, "the store loads before the app script")
346346 }
347+
348+func TestHomeActivityOrderAndFallback(t *testing.T) {
349+ t.Parallel()
350+ now := time.Now().UTC()
351+ a, b, c := publicRepo(t, "a"), publicRepo(t, "b"), publicRepo(t, "c")
352+ a.PushedAt = now.Add(-time.Hour)
353+ a.ActiveAt = now
354+ b.PushedAt = now.Add(-time.Minute)
355+ c.CreatedAt = now.Add(-30 * time.Minute)
356+ repos := []ports.RepoView{b, c, a}
357+ sortRepos(repos, "activity", true)
358+ assert.Equal(t, []ports.RepoView{a, b, c}, repos)
359+ sortRepos(repos, "activity", false)
360+ assert.Equal(t, []ports.RepoView{c, b, a}, repos)
361+ assert.Equal(t, rfc3339(now), homeRow(a, now, false, false).ActiveAt)
362+ c.PushedAt = c.CreatedAt.Add(-time.Hour)
363+ assert.Equal(t, c.CreatedAt, repoActiveAt(c), "fallback is the later of creation and push")
364+}
365+
366+func TestActivityEventsRefreshTheHomeRow(t *testing.T) {
367+ t.Parallel()
368+ repo := publicRepo(t, "proj")
369+ srv := newTestServer(t, Config{Repos: reposServing(t, repo), Browse: homeBrowse(t)})
370+ mr := domain.MergeRequest{Repo: repo.Name}
371+ issue := domain.Issue{Repo: repo.Name}
372+ for _, ev := range []domain.Event{
373+ domain.PushEvent{Repo: repo.Name},
374+ domain.MergeRequestOpened{MergeRequest: mr}, domain.MergeRequestMerged{MergeRequest: mr},
375+ domain.MergeRequestClosed{MergeRequest: mr}, domain.MergeRequestReady{MergeRequest: mr},
376+ domain.CommentAdded{Repo: repo.Name}, domain.IssueOpened{Issue: issue},
377+ domain.IssueClosed{Issue: issue}, domain.IssueReopened{Issue: issue}, domain.IssueCommented{Repo: repo.Name},
378+ } {
379+ t.Run(ev.EventKind(), func(t *testing.T) {
380+ got, err := (&liveStream{s: srv}).home(t.Context(), ports.WatchEvent{Event: ev})
381+ require.NoError(t, err)
382+ require.Len(t, got, 1)
383+ assert.Equal(t, "home-list", got[0].event)
384+ assert.Contains(t, got[0].html, `id="repo-proj"`)
385+ assert.Contains(t, got[0].html, `data-activity`)
386+ assert.Contains(t, got[0].html, "new activity")
387+ })
388+ }
389+ for _, ev := range []domain.Event{domain.IssueSubmitted{Repo: repo.Name}, domain.IssuePendingRemoved{Repo: repo.Name}} {
390+ got, err := (&liveStream{s: srv}).home(t.Context(), ports.WatchEvent{Event: ev})
391+ require.NoError(t, err)
392+ assert.Empty(t, got)
393+ }
394+}
@@ -252,7 +252,7 @@ func TestRunUpdatedRefreshesTheHomeRow(t *testing.T) {
252 assert.Equal(t, "home-list", got[0].event)252 assert.Equal(t, "home-list", got[0].event)
253 assert.Contains(t, got[0].html, `id="repo-proj"`)253 assert.Contains(t, got[0].html, `id="repo-proj"`)
254 assert.Contains(t, got[0].html, "passing")254 assert.Contains(t, got[0].html, "passing")
255- assert.NotContains(t, got[0].html, "new push", "a run is not a push")255+ assert.NotContains(t, got[0].html, "new activity", "a run is not a push")
256 256
257 // A job that ended refreshes the row too; one in progress does not.257 // A job that ended refreshes the row too; one in progress does not.
258 got, err = (&liveStream{s: srv}).render(t.Context(), ports.WatchEvent{Event: domain.JobUpdated{Job: domain.CIJob{Repo: repo, Phase: domain.CIPhaseRunning}}})258 got, err = (&liveStream{s: srv}).render(t.Context(), ports.WatchEvent{Event: domain.JobUpdated{Job: domain.CIJob{Repo: repo, Phase: domain.CIPhaseRunning}}})
@@ -344,3 +344,51 @@ func TestHomeOffersTheLayouts(t *testing.T) {
344 require.NotEqual(t, -1, store)344 require.NotEqual(t, -1, store)
345 assert.Less(t, store, app, "the store loads before the app script")345 assert.Less(t, store, app, "the store loads before the app script")
346 }346 }
347+
348+func TestHomeActivityOrderAndFallback(t *testing.T) {
349+ t.Parallel()
350+ now := time.Now().UTC()
351+ a, b, c := publicRepo(t, "a"), publicRepo(t, "b"), publicRepo(t, "c")
352+ a.PushedAt = now.Add(-time.Hour)
353+ a.ActiveAt = now
354+ b.PushedAt = now.Add(-time.Minute)
355+ c.CreatedAt = now.Add(-30 * time.Minute)
356+ repos := []ports.RepoView{b, c, a}
357+ sortRepos(repos, "activity", true)
358+ assert.Equal(t, []ports.RepoView{a, b, c}, repos)
359+ sortRepos(repos, "activity", false)
360+ assert.Equal(t, []ports.RepoView{c, b, a}, repos)
361+ assert.Equal(t, rfc3339(now), homeRow(a, now, false, false).ActiveAt)
362+ c.PushedAt = c.CreatedAt.Add(-time.Hour)
363+ assert.Equal(t, c.CreatedAt, repoActiveAt(c), "fallback is the later of creation and push")
364+}
365+
366+func TestActivityEventsRefreshTheHomeRow(t *testing.T) {
367+ t.Parallel()
368+ repo := publicRepo(t, "proj")
369+ srv := newTestServer(t, Config{Repos: reposServing(t, repo), Browse: homeBrowse(t)})
370+ mr := domain.MergeRequest{Repo: repo.Name}
371+ issue := domain.Issue{Repo: repo.Name}
372+ for _, ev := range []domain.Event{
373+ domain.PushEvent{Repo: repo.Name},
374+ domain.MergeRequestOpened{MergeRequest: mr}, domain.MergeRequestMerged{MergeRequest: mr},
375+ domain.MergeRequestClosed{MergeRequest: mr}, domain.MergeRequestReady{MergeRequest: mr},
376+ domain.CommentAdded{Repo: repo.Name}, domain.IssueOpened{Issue: issue},
377+ domain.IssueClosed{Issue: issue}, domain.IssueReopened{Issue: issue}, domain.IssueCommented{Repo: repo.Name},
378+ } {
379+ t.Run(ev.EventKind(), func(t *testing.T) {
380+ got, err := (&liveStream{s: srv}).home(t.Context(), ports.WatchEvent{Event: ev})
381+ require.NoError(t, err)
382+ require.Len(t, got, 1)
383+ assert.Equal(t, "home-list", got[0].event)
384+ assert.Contains(t, got[0].html, `id="repo-proj"`)
385+ assert.Contains(t, got[0].html, `data-activity`)
386+ assert.Contains(t, got[0].html, "new activity")
387+ })
388+ }
389+ for _, ev := range []domain.Event{domain.IssueSubmitted{Repo: repo.Name}, domain.IssuePendingRemoved{Repo: repo.Name}} {
390+ got, err := (&liveStream{s: srv}).home(t.Context(), ports.WatchEvent{Event: ev})
391+ require.NoError(t, err)
392+ assert.Empty(t, got)
393+ }
394+}
M internal/adapters/web/jstest/a11y.test.mjs +25 -0
@@ -8,6 +8,7 @@ import { test } from 'node:test';
88 import { dom, El, el, flushFrames, installDom, settle } from './dom.mjs';
99
1010 // The few things ermite.js uses that dom.mjs does not have.
11+El.prototype.prepend = function (...nodes) { this.nodes.unshift(...this.adopt(nodes)); };
1112 El.prototype.replaceWith = function (n) {
1213 const p = this.parent;
1314 const i = p.nodes.indexOf(this);
@@ -333,3 +334,27 @@ test('the preference is read from storage when the page loads', async () => {
333334 assert.deepEqual(dom.body.events, []);
334335 store.set('keys-off', '');
335336 });
337+
338+
339+test('activity moves a home row up only in newest activity order, preserving focus and cursor', () => {
340+ for (const [sort, desc, activity, moves] of [
341+ ['activity', 'true', true, true],
342+ ['activity', 'false', true, false],
343+ ['name', 'true', true, false],
344+ ['name', 'false', true, false],
345+ ['activity', 'true', false, false], // CI or metadata, not activity
346+ ]) {
347+ const r = rows();
348+ r.list.dataset.sort = sort;
349+ r.list.dataset.desc = desc;
350+ const old = r.list.children[1];
351+ old.setAttribute('data-cursor', '');
352+ active = old.children[0];
353+ const fresh = r.mk('repo-b', '/b');
354+ if (activity) fresh.setAttribute('data-activity', '');
355+ swapWith(`activity-${sort}-${desc}-${activity}`, fresh);
356+ assert.equal(r.list.children.indexOf(fresh), moves ? 0 : 1);
357+ assert.equal(active, fresh.children[0]);
358+ assert.ok(fresh.hasAttribute('data-cursor'));
359+ }
360+});
@@ -8,6 +8,7 @@ import { test } from 'node:test';
8 import { dom, El, el, flushFrames, installDom, settle } from './dom.mjs';8 import { dom, El, el, flushFrames, installDom, settle } from './dom.mjs';
9 9
10 // The few things ermite.js uses that dom.mjs does not have.10 // The few things ermite.js uses that dom.mjs does not have.
11+El.prototype.prepend = function (...nodes) { this.nodes.unshift(...this.adopt(nodes)); };
11 El.prototype.replaceWith = function (n) {12 El.prototype.replaceWith = function (n) {
12 const p = this.parent;13 const p = this.parent;
13 const i = p.nodes.indexOf(this);14 const i = p.nodes.indexOf(this);
@@ -333,3 +334,27 @@ test('the preference is read from storage when the page loads', async () => {
333 assert.deepEqual(dom.body.events, []);334 assert.deepEqual(dom.body.events, []);
334 store.set('keys-off', '');335 store.set('keys-off', '');
335 });336 });
337+
338+
339+test('activity moves a home row up only in newest activity order, preserving focus and cursor', () => {
340+ for (const [sort, desc, activity, moves] of [
341+ ['activity', 'true', true, true],
342+ ['activity', 'false', true, false],
343+ ['name', 'true', true, false],
344+ ['name', 'false', true, false],
345+ ['activity', 'true', false, false], // CI or metadata, not activity
346+ ]) {
347+ const r = rows();
348+ r.list.dataset.sort = sort;
349+ r.list.dataset.desc = desc;
350+ const old = r.list.children[1];
351+ old.setAttribute('data-cursor', '');
352+ active = old.children[0];
353+ const fresh = r.mk('repo-b', '/b');
354+ if (activity) fresh.setAttribute('data-activity', '');
355+ swapWith(`activity-${sort}-${desc}-${activity}`, fresh);
356+ assert.equal(r.list.children.indexOf(fresh), moves ? 0 : 1);
357+ assert.equal(active, fresh.children[0]);
358+ assert.ok(fresh.hasAttribute('data-cursor'));
359+ }
360+});
M internal/adapters/web/pages.templ +3 -1
@@ -2,6 +2,8 @@
22
33 package web
44
5+import "fmt"
6+
57 // Home is the repository list.
68 templ Home(l Layout, p HomePage) {
79 @Shell(l, nil) {
@@ -49,7 +51,7 @@ templ Home(l Layout, p HomePage) {
4951 </button>
5052 </div>
5153 </div>
52- <ul class="repos" id="repo-rows" data-rows>
54+ <ul class="repos" id="repo-rows" data-rows data-sort={ p.Sort } data-desc={ fmt.Sprint(p.Desc) }>
5355 for _, r := range p.Rows {
5456 @HomeRowView(r)
5557 }
@@ -2,6 +2,8 @@
2 2
3 package web3 package web
4 4
5+import "fmt"
6+
5 // Home is the repository list.7 // Home is the repository list.
6 templ Home(l Layout, p HomePage) {8 templ Home(l Layout, p HomePage) {
7 @Shell(l, nil) {9 @Shell(l, nil) {
@@ -49,7 +51,7 @@ templ Home(l Layout, p HomePage) {
49 </button>51 </button>
50 </div>52 </div>
51 </div>53 </div>
52- <ul class="repos" id="repo-rows" data-rows>54+ <ul class="repos" id="repo-rows" data-rows data-sort={ p.Sort } data-desc={ fmt.Sprint(p.Desc) }>
53 for _, r := range p.Rows {55 for _, r := range p.Rows {
54 @HomeRowView(r)56 @HomeRowView(r)
55 }57 }
M internal/adapters/web/pages_templ.go +370 -342
@@ -10,6 +10,8 @@ package web
1010 import "github.com/a-h/templ"
1111 import templruntime "github.com/a-h/templ/runtime"
1212
13+import "fmt"
14+
1315 // Home is the repository list.
1416 func Home(l Layout, p HomePage) templ.Component {
1517 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
@@ -56,7 +58,7 @@ func Home(l Layout, p HomePage) templ.Component {
5658 var templ_7745c5c3_Var3 templ.SafeURL
5759 templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href))
5860 if templ_7745c5c3_Err != nil {
59- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 27, Col: 36}
61+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 29, Col: 36}
6062 }
6163 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
6264 if templ_7745c5c3_Err != nil {
@@ -79,7 +81,7 @@ func Home(l Layout, p HomePage) templ.Component {
7981 var templ_7745c5c3_Var4 string
8082 templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)
8183 if templ_7745c5c3_Err != nil {
82- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 32, Col: 17}
84+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 34, Col: 17}
8385 }
8486 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
8587 if templ_7745c5c3_Err != nil {
@@ -97,7 +99,7 @@ func Home(l Layout, p HomePage) templ.Component {
9799 var templ_7745c5c3_Var5 string
98100 templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(s.Mark)
99101 if templ_7745c5c3_Err != nil {
100- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 34, Col: 42}
102+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 36, Col: 42}
101103 }
102104 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
103105 if templ_7745c5c3_Err != nil {
@@ -110,7 +112,7 @@ func Home(l Layout, p HomePage) templ.Component {
110112 var templ_7745c5c3_Var6 string
111113 templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(", " + s.State)
112114 if templ_7745c5c3_Err != nil {
113- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 35, Col: 42}
115+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 37, Col: 42}
114116 }
115117 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
116118 if templ_7745c5c3_Err != nil {
@@ -126,7 +128,33 @@ func Home(l Layout, p HomePage) templ.Component {
126128 return templ_7745c5c3_Err
127129 }
128130 }
129- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</nav><div class=\"layout-switch\" role=\"group\" aria-label=\"Layout\" data-js><button type=\"button\" class=\"btn icon\" data-set-layout=\"cards\" aria-pressed=\"true\" title=\"Cards\" aria-label=\"Cards\"><span class=\"ico i-cards\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"split\" aria-pressed=\"false\" title=\"Two columns\" aria-label=\"Two columns\"><span class=\"ico i-split\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"compact\" aria-pressed=\"false\" title=\"Compact list\" aria-label=\"Compact list\"><span class=\"ico i-compact\" aria-hidden=\"true\"></span></button></div></div><ul class=\"repos\" id=\"repo-rows\" data-rows>")
131+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</nav><div class=\"layout-switch\" role=\"group\" aria-label=\"Layout\" data-js><button type=\"button\" class=\"btn icon\" data-set-layout=\"cards\" aria-pressed=\"true\" title=\"Cards\" aria-label=\"Cards\"><span class=\"ico i-cards\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"split\" aria-pressed=\"false\" title=\"Two columns\" aria-label=\"Two columns\"><span class=\"ico i-split\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"compact\" aria-pressed=\"false\" title=\"Compact list\" aria-label=\"Compact list\"><span class=\"ico i-compact\" aria-hidden=\"true\"></span></button></div></div><ul class=\"repos\" id=\"repo-rows\" data-rows data-sort=\"")
132+ if templ_7745c5c3_Err != nil {
133+ return templ_7745c5c3_Err
134+ }
135+ var templ_7745c5c3_Var7 string
136+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Sort)
137+ if templ_7745c5c3_Err != nil {
138+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 54, Col: 65}
139+ }
140+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
141+ if templ_7745c5c3_Err != nil {
142+ return templ_7745c5c3_Err
143+ }
144+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" data-desc=\"")
145+ if templ_7745c5c3_Err != nil {
146+ return templ_7745c5c3_Err
147+ }
148+ var templ_7745c5c3_Var8 string
149+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprint(p.Desc))
150+ if templ_7745c5c3_Err != nil {
151+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 54, Col: 98}
152+ }
153+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
154+ if templ_7745c5c3_Err != nil {
155+ return templ_7745c5c3_Err
156+ }
157+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">")
130158 if templ_7745c5c3_Err != nil {
131159 return templ_7745c5c3_Err
132160 }
@@ -136,17 +164,17 @@ func Home(l Layout, p HomePage) templ.Component {
136164 return templ_7745c5c3_Err
137165 }
138166 }
139- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</ul>")
167+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</ul>")
140168 if templ_7745c5c3_Err != nil {
141169 return templ_7745c5c3_Err
142170 }
143171 if len(p.Rows) == 0 {
144- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<p class=\"lede dim\">no repositories yet.</p>")
172+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p class=\"lede dim\">no repositories yet.</p>")
145173 if templ_7745c5c3_Err != nil {
146174 return templ_7745c5c3_Err
147175 }
148176 }
149- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</section></div>")
177+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</section></div>")
150178 if templ_7745c5c3_Err != nil {
151179 return templ_7745c5c3_Err
152180 }
@@ -178,12 +206,12 @@ func Summary(l Layout, p SummaryPage) templ.Component {
178206 }()
179207 }
180208 ctx = templ.InitializeContext(ctx)
181- templ_7745c5c3_Var7 := templ.GetChildren(ctx)
182- if templ_7745c5c3_Var7 == nil {
183- templ_7745c5c3_Var7 = templ.NopComponent
209+ templ_7745c5c3_Var9 := templ.GetChildren(ctx)
210+ if templ_7745c5c3_Var9 == nil {
211+ templ_7745c5c3_Var9 = templ.NopComponent
184212 }
185213 ctx = templ.ClearChildren(ctx)
186- templ_7745c5c3_Var8 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
214+ templ_7745c5c3_Var10 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
187215 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
188216 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
189217 if !templ_7745c5c3_IsBuffer {
@@ -203,7 +231,7 @@ func Summary(l Layout, p SummaryPage) templ.Component {
203231 }
204232 return nil
205233 })
206- templ_7745c5c3_Err = Shell(l, summaryRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)
234+ templ_7745c5c3_Err = Shell(l, summaryRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer)
207235 if templ_7745c5c3_Err != nil {
208236 return templ_7745c5c3_Err
209237 }
@@ -230,158 +258,158 @@ func latestTagFact(t LatestTag) templ.Component {
230258 }()
231259 }
232260 ctx = templ.InitializeContext(ctx)
233- templ_7745c5c3_Var9 := templ.GetChildren(ctx)
234- if templ_7745c5c3_Var9 == nil {
235- templ_7745c5c3_Var9 = templ.NopComponent
261+ templ_7745c5c3_Var11 := templ.GetChildren(ctx)
262+ if templ_7745c5c3_Var11 == nil {
263+ templ_7745c5c3_Var11 = templ.NopComponent
236264 }
237265 ctx = templ.ClearChildren(ctx)
238- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<dt>latest tag</dt><dd class=\"latest-tag\">")
266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<dt>latest tag</dt><dd class=\"latest-tag\">")
239267 if templ_7745c5c3_Err != nil {
240268 return templ_7745c5c3_Err
241269 }
242270 if t.Name != "" {
243- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<a class=\"tag-name\" href=\"")
271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<a class=\"tag-name\" href=\"")
244272 if templ_7745c5c3_Err != nil {
245273 return templ_7745c5c3_Err
246274 }
247- var templ_7745c5c3_Var10 templ.SafeURL
248- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.Href))
275+ var templ_7745c5c3_Var12 templ.SafeURL
276+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.Href))
249277 if templ_7745c5c3_Err != nil {
250- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 82, Col: 51}
278+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 51}
251279 }
252- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
280+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
253281 if templ_7745c5c3_Err != nil {
254282 return templ_7745c5c3_Err
255283 }
256- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" title=\"")
284+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" title=\"")
257285 if templ_7745c5c3_Err != nil {
258286 return templ_7745c5c3_Err
259287 }
260- var templ_7745c5c3_Var11 string
261- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Subject)
288+ var templ_7745c5c3_Var13 string
289+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Subject)
262290 if templ_7745c5c3_Err != nil {
263- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 82, Col: 71}
291+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 71}
264292 }
265- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
293+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
266294 if templ_7745c5c3_Err != nil {
267295 return templ_7745c5c3_Err
268296 }
269- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">")
297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
270298 if templ_7745c5c3_Err != nil {
271299 return templ_7745c5c3_Err
272300 }
273- var templ_7745c5c3_Var12 string
274- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)
301+ var templ_7745c5c3_Var14 string
302+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)
275303 if templ_7745c5c3_Err != nil {
276- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 82, Col: 82}
304+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 82}
277305 }
278- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
306+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
279307 if templ_7745c5c3_Err != nil {
280308 return templ_7745c5c3_Err
281309 }
282- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</a> ")
310+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</a> ")
283311 if templ_7745c5c3_Err != nil {
284312 return templ_7745c5c3_Err
285313 }
286314 if t.WhenAt != "" {
287- var templ_7745c5c3_Var13 string
288- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
315+ var templ_7745c5c3_Var15 string
316+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
289317 if templ_7745c5c3_Err != nil {
290- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 12}
318+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 12}
291319 }
292- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
320+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
293321 if templ_7745c5c3_Err != nil {
294322 return templ_7745c5c3_Err
295323 }
296- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<time datetime=\"")
324+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<time datetime=\"")
297325 if templ_7745c5c3_Err != nil {
298326 return templ_7745c5c3_Err
299327 }
300- var templ_7745c5c3_Var14 string
301- templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
328+ var templ_7745c5c3_Var16 string
329+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
302330 if templ_7745c5c3_Err != nil {
303- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 39}
331+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 39}
304332 }
305- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
333+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
306334 if templ_7745c5c3_Err != nil {
307335 return templ_7745c5c3_Err
308336 }
309- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" title=\"")
337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" title=\"")
310338 if templ_7745c5c3_Err != nil {
311339 return templ_7745c5c3_Err
312340 }
313- var templ_7745c5c3_Var15 string
314- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
341+ var templ_7745c5c3_Var17 string
342+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
315343 if templ_7745c5c3_Err != nil {
316- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 58}
344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 58}
317345 }
318- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
346+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
319347 if templ_7745c5c3_Err != nil {
320348 return templ_7745c5c3_Err
321349 }
322- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">")
350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
323351 if templ_7745c5c3_Err != nil {
324352 return templ_7745c5c3_Err
325353 }
326- var templ_7745c5c3_Var16 string
327- templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(t.When)
354+ var templ_7745c5c3_Var18 string
355+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(t.When)
328356 if templ_7745c5c3_Err != nil {
329- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 69}
357+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 69}
330358 }
331- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
359+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
332360 if templ_7745c5c3_Err != nil {
333361 return templ_7745c5c3_Err
334362 }
335- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</time>")
363+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</time>")
336364 if templ_7745c5c3_Err != nil {
337365 return templ_7745c5c3_Err
338366 }
339367 }
340- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " ")
368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
341369 if templ_7745c5c3_Err != nil {
342370 return templ_7745c5c3_Err
343371 }
344- var templ_7745c5c3_Var17 string
345- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
372+ var templ_7745c5c3_Var19 string
373+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
346374 if templ_7745c5c3_Err != nil {
347- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 11}
375+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 11}
348376 }
349- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
377+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
350378 if templ_7745c5c3_Err != nil {
351379 return templ_7745c5c3_Err
352380 }
353- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ")
381+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ")
354382 if templ_7745c5c3_Err != nil {
355383 return templ_7745c5c3_Err
356384 }
357385 }
358- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<a class=\"all-tags\" href=\"")
386+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<a class=\"all-tags\" href=\"")
359387 if templ_7745c5c3_Err != nil {
360388 return templ_7745c5c3_Err
361389 }
362- var templ_7745c5c3_Var18 templ.SafeURL
363- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.RefsHref))
390+ var templ_7745c5c3_Var20 templ.SafeURL
391+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.RefsHref))
364392 if templ_7745c5c3_Err != nil {
365- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 54}
393+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 54}
366394 }
367- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
395+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
368396 if templ_7745c5c3_Err != nil {
369397 return templ_7745c5c3_Err
370398 }
371- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\">")
399+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
372400 if templ_7745c5c3_Err != nil {
373401 return templ_7745c5c3_Err
374402 }
375- var templ_7745c5c3_Var19 string
376- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(plural(t.Count, "tag", "tags"))
403+ var templ_7745c5c3_Var21 string
404+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(plural(t.Count, "tag", "tags"))
377405 if templ_7745c5c3_Err != nil {
378- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 89}
406+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 89}
379407 }
380- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
408+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
381409 if templ_7745c5c3_Err != nil {
382410 return templ_7745c5c3_Err
383411 }
384- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</a></dd>")
412+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</a></dd>")
385413 if templ_7745c5c3_Err != nil {
386414 return templ_7745c5c3_Err
387415 }
@@ -405,12 +433,12 @@ func summaryRail(p SummaryPage) templ.Component {
405433 }()
406434 }
407435 ctx = templ.InitializeContext(ctx)
408- templ_7745c5c3_Var20 := templ.GetChildren(ctx)
409- if templ_7745c5c3_Var20 == nil {
410- templ_7745c5c3_Var20 = templ.NopComponent
436+ templ_7745c5c3_Var22 := templ.GetChildren(ctx)
437+ if templ_7745c5c3_Var22 == nil {
438+ templ_7745c5c3_Var22 = templ.NopComponent
411439 }
412440 ctx = templ.ClearChildren(ctx)
413- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<section aria-labelledby=\"r-clone\"><h2 id=\"r-clone\">Clone</h2>")
441+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<section aria-labelledby=\"r-clone\"><h2 id=\"r-clone\">Clone</h2>")
414442 if templ_7745c5c3_Err != nil {
415443 return templ_7745c5c3_Err
416444 }
@@ -418,25 +446,25 @@ func summaryRail(p SummaryPage) templ.Component {
418446 if templ_7745c5c3_Err != nil {
419447 return templ_7745c5c3_Err
420448 }
421- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</section><section aria-labelledby=\"r-facts\"><h2 id=\"r-facts\">About</h2><dl class=\"kv\">")
449+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</section><section aria-labelledby=\"r-facts\"><h2 id=\"r-facts\">About</h2><dl class=\"kv\">")
422450 if templ_7745c5c3_Err != nil {
423451 return templ_7745c5c3_Err
424452 }
425453 for _, f := range p.Facts {
426- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<dt>")
454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<dt>")
427455 if templ_7745c5c3_Err != nil {
428456 return templ_7745c5c3_Err
429457 }
430- var templ_7745c5c3_Var21 string
431- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(f.Key)
458+ var templ_7745c5c3_Var23 string
459+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(f.Key)
432460 if templ_7745c5c3_Err != nil {
433- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 99, Col: 15}
461+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 101, Col: 15}
434462 }
435- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
463+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
436464 if templ_7745c5c3_Err != nil {
437465 return templ_7745c5c3_Err
438466 }
439- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</dt>")
467+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</dt>")
440468 if templ_7745c5c3_Err != nil {
441469 return templ_7745c5c3_Err
442470 }
@@ -451,25 +479,25 @@ func summaryRail(p SummaryPage) templ.Component {
451479 return templ_7745c5c3_Err
452480 }
453481 }
454- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</dl></section>")
482+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</dl></section>")
455483 if templ_7745c5c3_Err != nil {
456484 return templ_7745c5c3_Err
457485 }
458486 if len(p.Recent) > 0 {
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")
487+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")
460488 if templ_7745c5c3_Err != nil {
461489 return templ_7745c5c3_Err
462490 }
463- var templ_7745c5c3_Var22 templ.SafeURL
464- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
491+ var templ_7745c5c3_Var24 templ.SafeURL
492+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
465493 if templ_7745c5c3_Err != nil {
466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 108, Col: 105}
494+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 110, Col: 105}
467495 }
468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
496+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
469497 if templ_7745c5c3_Err != nil {
470498 return templ_7745c5c3_Err
471499 }
472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\">View all</a></h2>")
500+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">View all</a></h2>")
473501 if templ_7745c5c3_Err != nil {
474502 return templ_7745c5c3_Err
475503 }
@@ -477,7 +505,7 @@ func summaryRail(p SummaryPage) templ.Component {
477505 if templ_7745c5c3_Err != nil {
478506 return templ_7745c5c3_Err
479507 }
480- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</section>")
508+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</section>")
481509 if templ_7745c5c3_Err != nil {
482510 return templ_7745c5c3_Err
483511 }
@@ -489,7 +517,7 @@ func summaryRail(p SummaryPage) templ.Component {
489517 }
490518 }
491519 if len(p.Outline) > 0 && p.Readme != nil {
492- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")
520+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")
493521 if templ_7745c5c3_Err != nil {
494522 return templ_7745c5c3_Err
495523 }
@@ -497,7 +525,7 @@ func summaryRail(p SummaryPage) templ.Component {
497525 if templ_7745c5c3_Err != nil {
498526 return templ_7745c5c3_Err
499527 }
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</section>")
528+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</section>")
501529 if templ_7745c5c3_Err != nil {
502530 return templ_7745c5c3_Err
503531 }
@@ -523,12 +551,12 @@ func Refs(l Layout, p RefsPage) templ.Component {
523551 }()
524552 }
525553 ctx = templ.InitializeContext(ctx)
526- templ_7745c5c3_Var23 := templ.GetChildren(ctx)
527- if templ_7745c5c3_Var23 == nil {
528- templ_7745c5c3_Var23 = templ.NopComponent
554+ templ_7745c5c3_Var25 := templ.GetChildren(ctx)
555+ if templ_7745c5c3_Var25 == nil {
556+ templ_7745c5c3_Var25 = templ.NopComponent
529557 }
530558 ctx = templ.ClearChildren(ctx)
531- templ_7745c5c3_Var24 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
559+ templ_7745c5c3_Var26 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
532560 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
533561 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
534562 if !templ_7745c5c3_IsBuffer {
@@ -540,7 +568,7 @@ func Refs(l Layout, p RefsPage) templ.Component {
540568 }()
541569 }
542570 ctx = templ.InitializeContext(ctx)
543- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter=\"#ref-rows > tr\" placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")
571+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter=\"#ref-rows > tr\" placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")
544572 if templ_7745c5c3_Err != nil {
545573 return templ_7745c5c3_Err
546574 }
@@ -548,32 +576,32 @@ func Refs(l Layout, p RefsPage) templ.Component {
548576 if templ_7745c5c3_Err != nil {
549577 return templ_7745c5c3_Err
550578 }
551- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</table>")
579+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</table>")
552580 if templ_7745c5c3_Err != nil {
553581 return templ_7745c5c3_Err
554582 }
555583 if p.Note != "" {
556- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<p class=\"note\">")
584+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<p class=\"note\">")
557585 if templ_7745c5c3_Err != nil {
558586 return templ_7745c5c3_Err
559587 }
560- var templ_7745c5c3_Var25 string
561- templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
588+ var templ_7745c5c3_Var27 string
589+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
562590 if templ_7745c5c3_Err != nil {
563- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 141, Col: 27}
591+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 143, Col: 27}
564592 }
565- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
593+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
566594 if templ_7745c5c3_Err != nil {
567595 return templ_7745c5c3_Err
568596 }
569- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</p>")
597+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</p>")
570598 if templ_7745c5c3_Err != nil {
571599 return templ_7745c5c3_Err
572600 }
573601 }
574602 return nil
575603 })
576- templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var24), templ_7745c5c3_Buffer)
604+ templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var26), templ_7745c5c3_Buffer)
577605 if templ_7745c5c3_Err != nil {
578606 return templ_7745c5c3_Err
579607 }
@@ -597,112 +625,112 @@ func refRow(kind string, r RefRow) templ.Component {
597625 }()
598626 }
599627 ctx = templ.InitializeContext(ctx)
600- templ_7745c5c3_Var26 := templ.GetChildren(ctx)
601- if templ_7745c5c3_Var26 == nil {
602- templ_7745c5c3_Var26 = templ.NopComponent
628+ templ_7745c5c3_Var28 := templ.GetChildren(ctx)
629+ if templ_7745c5c3_Var28 == nil {
630+ templ_7745c5c3_Var28 = templ.NopComponent
603631 }
604632 ctx = templ.ClearChildren(ctx)
605- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<tr")
633+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<tr")
606634 if templ_7745c5c3_Err != nil {
607635 return templ_7745c5c3_Err
608636 }
609637 if r.Cursor {
610- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " data-cursor")
638+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " data-cursor")
611639 if templ_7745c5c3_Err != nil {
612640 return templ_7745c5c3_Err
613641 }
614642 }
615- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "><td class=\"c-kind mute\">")
643+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "><td class=\"c-kind mute\">")
616644 if templ_7745c5c3_Err != nil {
617645 return templ_7745c5c3_Err
618646 }
619- var templ_7745c5c3_Var27 string
620- templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
647+ var templ_7745c5c3_Var29 string
648+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
621649 if templ_7745c5c3_Err != nil {
622- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 148, Col: 32}
650+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 32}
623651 }
624- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
652+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
625653 if templ_7745c5c3_Err != nil {
626654 return templ_7745c5c3_Err
627655 }
628- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</td><td class=\"c-name\"><a href=\"")
656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</td><td class=\"c-name\"><a href=\"")
629657 if templ_7745c5c3_Err != nil {
630658 return templ_7745c5c3_Err
631659 }
632- var templ_7745c5c3_Var28 templ.SafeURL
633- templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
660+ var templ_7745c5c3_Var30 templ.SafeURL
661+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
634662 if templ_7745c5c3_Err != nil {
635- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 34}
663+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 34}
636664 }
637- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
665+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
638666 if templ_7745c5c3_Err != nil {
639667 return templ_7745c5c3_Err
640668 }
641- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\">")
669+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\">")
642670 if templ_7745c5c3_Err != nil {
643671 return templ_7745c5c3_Err
644672 }
645- var templ_7745c5c3_Var29 string
646- templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
673+ var templ_7745c5c3_Var31 string
674+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
647675 if templ_7745c5c3_Err != nil {
648- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 45}
676+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 45}
649677 }
650- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
678+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
651679 if templ_7745c5c3_Err != nil {
652680 return templ_7745c5c3_Err
653681 }
654- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</a> ")
682+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</a> ")
655683 if templ_7745c5c3_Err != nil {
656684 return templ_7745c5c3_Err
657685 }
658686 if r.Default {
659- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<span class=\"pill\">default</span>")
687+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"pill\">default</span>")
660688 if templ_7745c5c3_Err != nil {
661689 return templ_7745c5c3_Err
662690 }
663691 }
664- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</td><td class=\"c-sha\">")
692+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-sha\">")
665693 if templ_7745c5c3_Err != nil {
666694 return templ_7745c5c3_Err
667695 }
668696 if r.CHref != "" {
669- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<a href=\"")
697+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a href=\"")
670698 if templ_7745c5c3_Err != nil {
671699 return templ_7745c5c3_Err
672700 }
673- var templ_7745c5c3_Var30 templ.SafeURL
674- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
701+ var templ_7745c5c3_Var32 templ.SafeURL
702+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
675703 if templ_7745c5c3_Err != nil {
676- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 157, Col: 36}
704+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 36}
677705 }
678- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
706+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
679707 if templ_7745c5c3_Err != nil {
680708 return templ_7745c5c3_Err
681709 }
682- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\">")
710+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\">")
683711 if templ_7745c5c3_Err != nil {
684712 return templ_7745c5c3_Err
685713 }
686- var templ_7745c5c3_Var31 string
687- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
714+ var templ_7745c5c3_Var33 string
715+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
688716 if templ_7745c5c3_Err != nil {
689- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 157, Col: 48}
717+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 48}
690718 }
691- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
719+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
692720 if templ_7745c5c3_Err != nil {
693721 return templ_7745c5c3_Err
694722 }
695- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</a>")
723+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</a>")
696724 if templ_7745c5c3_Err != nil {
697725 return templ_7745c5c3_Err
698726 }
699727 } else {
700- var templ_7745c5c3_Var32 string
701- templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
728+ var templ_7745c5c3_Var34 string
729+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
702730 if templ_7745c5c3_Err != nil {
703- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 13}
731+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 13}
704732 }
705- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
733+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
706734 if templ_7745c5c3_Err != nil {
707735 return templ_7745c5c3_Err
708736 }
@@ -711,69 +739,69 @@ func refRow(kind string, r RefRow) templ.Component {
711739 if templ_7745c5c3_Err != nil {
712740 return templ_7745c5c3_Err
713741 }
714- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</td><td class=\"c-sub\">")
742+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</td><td class=\"c-sub\">")
715743 if templ_7745c5c3_Err != nil {
716744 return templ_7745c5c3_Err
717745 }
718- var templ_7745c5c3_Var33 string
719- templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
746+ var templ_7745c5c3_Var35 string
747+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
720748 if templ_7745c5c3_Err != nil {
721- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 31}
749+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 31}
722750 }
723- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
751+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
724752 if templ_7745c5c3_Err != nil {
725753 return templ_7745c5c3_Err
726754 }
727- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</td><td class=\"c-when dim\">")
755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</td><td class=\"c-when dim\">")
728756 if templ_7745c5c3_Err != nil {
729757 return templ_7745c5c3_Err
730758 }
731759 if r.WhenAt != "" {
732- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<time datetime=\"")
760+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<time datetime=\"")
733761 if templ_7745c5c3_Err != nil {
734762 return templ_7745c5c3_Err
735763 }
736- var templ_7745c5c3_Var34 string
737- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
764+ var templ_7745c5c3_Var36 string
765+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
738766 if templ_7745c5c3_Err != nil {
739- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 29}
767+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 29}
740768 }
741- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
769+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
742770 if templ_7745c5c3_Err != nil {
743771 return templ_7745c5c3_Err
744772 }
745- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\" title=\"")
773+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" title=\"")
746774 if templ_7745c5c3_Err != nil {
747775 return templ_7745c5c3_Err
748776 }
749- var templ_7745c5c3_Var35 string
750- templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
777+ var templ_7745c5c3_Var37 string
778+ templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
751779 if templ_7745c5c3_Err != nil {
752- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 48}
780+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 48}
753781 }
754- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
782+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var37)
755783 if templ_7745c5c3_Err != nil {
756784 return templ_7745c5c3_Err
757785 }
758- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\">")
786+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\">")
759787 if templ_7745c5c3_Err != nil {
760788 return templ_7745c5c3_Err
761789 }
762- var templ_7745c5c3_Var36 string
763- templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
790+ var templ_7745c5c3_Var38 string
791+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
764792 if templ_7745c5c3_Err != nil {
765- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 59}
793+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 59}
766794 }
767- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
795+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
768796 if templ_7745c5c3_Err != nil {
769797 return templ_7745c5c3_Err
770798 }
771- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</time>")
799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</time>")
772800 if templ_7745c5c3_Err != nil {
773801 return templ_7745c5c3_Err
774802 }
775803 }
776- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</td></tr>")
804+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</td></tr>")
777805 if templ_7745c5c3_Err != nil {
778806 return templ_7745c5c3_Err
779807 }
@@ -797,12 +825,12 @@ func refsRail(p RefsPage) templ.Component {
797825 }()
798826 }
799827 ctx = templ.InitializeContext(ctx)
800- templ_7745c5c3_Var37 := templ.GetChildren(ctx)
801- if templ_7745c5c3_Var37 == nil {
802- templ_7745c5c3_Var37 = templ.NopComponent
828+ templ_7745c5c3_Var39 := templ.GetChildren(ctx)
829+ if templ_7745c5c3_Var39 == nil {
830+ templ_7745c5c3_Var39 = templ.NopComponent
803831 }
804832 ctx = templ.ClearChildren(ctx)
805- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")
833+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")
806834 if templ_7745c5c3_Err != nil {
807835 return templ_7745c5c3_Err
808836 }
@@ -810,7 +838,7 @@ func refsRail(p RefsPage) templ.Component {
810838 if templ_7745c5c3_Err != nil {
811839 return templ_7745c5c3_Err
812840 }
813- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</section>")
841+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</section>")
814842 if templ_7745c5c3_Err != nil {
815843 return templ_7745c5c3_Err
816844 }
@@ -835,12 +863,12 @@ func State(l Layout, p StatePage) templ.Component {
835863 }()
836864 }
837865 ctx = templ.InitializeContext(ctx)
838- templ_7745c5c3_Var38 := templ.GetChildren(ctx)
839- if templ_7745c5c3_Var38 == nil {
840- templ_7745c5c3_Var38 = templ.NopComponent
866+ templ_7745c5c3_Var40 := templ.GetChildren(ctx)
867+ if templ_7745c5c3_Var40 == nil {
868+ templ_7745c5c3_Var40 = templ.NopComponent
841869 }
842870 ctx = templ.ClearChildren(ctx)
843- templ_7745c5c3_Var39 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
871+ templ_7745c5c3_Var41 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
844872 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
845873 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
846874 if !templ_7745c5c3_IsBuffer {
@@ -852,51 +880,51 @@ func State(l Layout, p StatePage) templ.Component {
852880 }()
853881 }
854882 ctx = templ.InitializeContext(ctx)
855- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")
883+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")
856884 if templ_7745c5c3_Err != nil {
857885 return templ_7745c5c3_Err
858886 }
859- var templ_7745c5c3_Var40 string
860- templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
887+ var templ_7745c5c3_Var42 string
888+ templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
861889 if templ_7745c5c3_Err != nil {
862- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 183, Col: 30}
890+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 185, Col: 30}
863891 }
864- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
892+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
865893 if templ_7745c5c3_Err != nil {
866894 return templ_7745c5c3_Err
867895 }
868- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</p><h1 id=\"state-title\">")
896+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</p><h1 id=\"state-title\">")
869897 if templ_7745c5c3_Err != nil {
870898 return templ_7745c5c3_Err
871899 }
872- var templ_7745c5c3_Var41 string
873- templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
900+ var templ_7745c5c3_Var43 string
901+ templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
874902 if templ_7745c5c3_Err != nil {
875- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 184, Col: 33}
903+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 186, Col: 33}
876904 }
877- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
905+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
878906 if templ_7745c5c3_Err != nil {
879907 return templ_7745c5c3_Err
880908 }
881- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</h1>")
909+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</h1>")
882910 if templ_7745c5c3_Err != nil {
883911 return templ_7745c5c3_Err
884912 }
885913 for _, line := range p.Lines {
886- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<p class=\"dim\">")
914+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<p class=\"dim\">")
887915 if templ_7745c5c3_Err != nil {
888916 return templ_7745c5c3_Err
889917 }
890- var templ_7745c5c3_Var42 string
891- templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(line)
918+ var templ_7745c5c3_Var44 string
919+ templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(line)
892920 if templ_7745c5c3_Err != nil {
893- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 186, Col: 25}
921+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 188, Col: 25}
894922 }
895- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
923+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
896924 if templ_7745c5c3_Err != nil {
897925 return templ_7745c5c3_Err
898926 }
899- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</p>")
927+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</p>")
900928 if templ_7745c5c3_Err != nil {
901929 return templ_7745c5c3_Err
902930 }
@@ -907,49 +935,49 @@ func State(l Layout, p StatePage) templ.Component {
907935 return templ_7745c5c3_Err
908936 }
909937 }
910- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<p class=\"state-back\">")
938+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"state-back\">")
911939 if templ_7745c5c3_Err != nil {
912940 return templ_7745c5c3_Err
913941 }
914942 for _, b := range p.Back {
915- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<a href=\"")
943+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<a href=\"")
916944 if templ_7745c5c3_Err != nil {
917945 return templ_7745c5c3_Err
918946 }
919- var templ_7745c5c3_Var43 templ.SafeURL
920- templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
947+ var templ_7745c5c3_Var45 templ.SafeURL
948+ templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
921949 if templ_7745c5c3_Err != nil {
922- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 193, Col: 36}
950+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 36}
923951 }
924- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
952+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
925953 if templ_7745c5c3_Err != nil {
926954 return templ_7745c5c3_Err
927955 }
928- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\">")
956+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\">")
929957 if templ_7745c5c3_Err != nil {
930958 return templ_7745c5c3_Err
931959 }
932- var templ_7745c5c3_Var44 string
933- templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
960+ var templ_7745c5c3_Var46 string
961+ templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
934962 if templ_7745c5c3_Err != nil {
935- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 193, Col: 48}
963+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 48}
936964 }
937- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
965+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
938966 if templ_7745c5c3_Err != nil {
939967 return templ_7745c5c3_Err
940968 }
941- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</a>")
969+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</a>")
942970 if templ_7745c5c3_Err != nil {
943971 return templ_7745c5c3_Err
944972 }
945973 }
946- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</p></section>")
974+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</p></section>")
947975 if templ_7745c5c3_Err != nil {
948976 return templ_7745c5c3_Err
949977 }
950978 return nil
951979 })
952- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var39), templ_7745c5c3_Buffer)
980+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var41), templ_7745c5c3_Buffer)
953981 if templ_7745c5c3_Err != nil {
954982 return templ_7745c5c3_Err
955983 }
@@ -978,12 +1006,12 @@ func Login(l Layout, p LoginPage) templ.Component {
9781006 }()
9791007 }
9801008 ctx = templ.InitializeContext(ctx)
981- templ_7745c5c3_Var45 := templ.GetChildren(ctx)
982- if templ_7745c5c3_Var45 == nil {
983- templ_7745c5c3_Var45 = templ.NopComponent
1009+ templ_7745c5c3_Var47 := templ.GetChildren(ctx)
1010+ if templ_7745c5c3_Var47 == nil {
1011+ templ_7745c5c3_Var47 = templ.NopComponent
9841012 }
9851013 ctx = templ.ClearChildren(ctx)
986- templ_7745c5c3_Var46 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1014+ templ_7745c5c3_Var48 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
9871015 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
9881016 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
9891017 if !templ_7745c5c3_IsBuffer {
@@ -995,75 +1023,75 @@ func Login(l Layout, p LoginPage) templ.Component {
9951023 }()
9961024 }
9971025 ctx = templ.InitializeContext(ctx)
998- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
1026+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
9991027 if templ_7745c5c3_Err != nil {
10001028 return templ_7745c5c3_Err
10011029 }
1002- var templ_7745c5c3_Var47 string
1003- templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
1030+ var templ_7745c5c3_Var49 string
1031+ templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
10041032 if templ_7745c5c3_Err != nil {
1005- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 208, Col: 33}
1033+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 210, Col: 33}
10061034 }
1007- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
1035+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
10081036 if templ_7745c5c3_Err != nil {
10091037 return templ_7745c5c3_Err
10101038 }
1011- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</h1>")
1039+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</h1>")
10121040 if templ_7745c5c3_Err != nil {
10131041 return templ_7745c5c3_Err
10141042 }
10151043 for _, line := range p.Lines {
1016- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<p class=\"dim\">")
1044+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<p class=\"dim\">")
10171045 if templ_7745c5c3_Err != nil {
10181046 return templ_7745c5c3_Err
10191047 }
1020- var templ_7745c5c3_Var48 string
1021- templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(line)
1048+ var templ_7745c5c3_Var50 string
1049+ templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(line)
10221050 if templ_7745c5c3_Err != nil {
1023- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 210, Col: 25}
1051+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 212, Col: 25}
10241052 }
1025- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
1053+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
10261054 if templ_7745c5c3_Err != nil {
10271055 return templ_7745c5c3_Err
10281056 }
1029- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</p>")
1057+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</p>")
10301058 if templ_7745c5c3_Err != nil {
10311059 return templ_7745c5c3_Err
10321060 }
10331061 }
1034- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<form class=\"state-act\" method=\"post\" action=\"")
1062+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<form class=\"state-act\" method=\"post\" action=\"")
10351063 if templ_7745c5c3_Err != nil {
10361064 return templ_7745c5c3_Err
10371065 }
1038- var templ_7745c5c3_Var49 templ.SafeURL
1039- templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
1066+ var templ_7745c5c3_Var51 templ.SafeURL
1067+ templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
10401068 if templ_7745c5c3_Err != nil {
1041- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 212, Col: 73}
1069+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 214, Col: 73}
10421070 }
1043- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
1071+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
10441072 if templ_7745c5c3_Err != nil {
10451073 return templ_7745c5c3_Err
10461074 }
1047- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\" data-autosubmit><button type=\"submit\" class=\"btn primary\">")
1075+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" data-autosubmit><button type=\"submit\" class=\"btn primary\">")
10481076 if templ_7745c5c3_Err != nil {
10491077 return templ_7745c5c3_Err
10501078 }
1051- var templ_7745c5c3_Var50 string
1052- templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
1079+ var templ_7745c5c3_Var52 string
1080+ templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
10531081 if templ_7745c5c3_Err != nil {
1054- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 213, Col: 56}
1082+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 215, Col: 56}
10551083 }
1056- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
1084+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
10571085 if templ_7745c5c3_Err != nil {
10581086 return templ_7745c5c3_Err
10591087 }
1060- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
1088+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
10611089 if templ_7745c5c3_Err != nil {
10621090 return templ_7745c5c3_Err
10631091 }
10641092 return nil
10651093 })
1066- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var46), templ_7745c5c3_Buffer)
1094+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var48), templ_7745c5c3_Buffer)
10671095 if templ_7745c5c3_Err != nil {
10681096 return templ_7745c5c3_Err
10691097 }
@@ -1089,12 +1117,12 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component {
10891117 }()
10901118 }
10911119 ctx = templ.InitializeContext(ctx)
1092- templ_7745c5c3_Var51 := templ.GetChildren(ctx)
1093- if templ_7745c5c3_Var51 == nil {
1094- templ_7745c5c3_Var51 = templ.NopComponent
1120+ templ_7745c5c3_Var53 := templ.GetChildren(ctx)
1121+ if templ_7745c5c3_Var53 == nil {
1122+ templ_7745c5c3_Var53 = templ.NopComponent
10951123 }
10961124 ctx = templ.ClearChildren(ctx)
1097- templ_7745c5c3_Var52 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1125+ templ_7745c5c3_Var54 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
10981126 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
10991127 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
11001128 if !templ_7745c5c3_IsBuffer {
@@ -1106,88 +1134,88 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component {
11061134 }()
11071135 }
11081136 ctx = templ.InitializeContext(ctx)
1109- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
1137+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
11101138 if templ_7745c5c3_Err != nil {
11111139 return templ_7745c5c3_Err
11121140 }
1113- var templ_7745c5c3_Var53 string
1114- templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
1141+ var templ_7745c5c3_Var55 string
1142+ templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
11151143 if templ_7745c5c3_Err != nil {
1116- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 225, Col: 33}
1144+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 33}
11171145 }
1118- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
1146+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
11191147 if templ_7745c5c3_Err != nil {
11201148 return templ_7745c5c3_Err
11211149 }
1122- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</h1><form class=\"state-act\" data-js data-passkey-form><button type=\"submit\" class=\"btn primary\" data-passkey=\"login\" data-begin=\"")
1150+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "</h1><form class=\"state-act\" data-js data-passkey-form><button type=\"submit\" class=\"btn primary\" data-passkey=\"login\" data-begin=\"")
11231151 if templ_7745c5c3_Err != nil {
11241152 return templ_7745c5c3_Err
11251153 }
1126- var templ_7745c5c3_Var54 string
1127- templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
1154+ var templ_7745c5c3_Var56 string
1155+ templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
11281156 if templ_7745c5c3_Err != nil {
1129- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 87}
1157+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 87}
11301158 }
1131- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54)
1159+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)
11321160 if templ_7745c5c3_Err != nil {
11331161 return templ_7745c5c3_Err
11341162 }
1135- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\" data-finish=\"")
1163+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" data-finish=\"")
11361164 if templ_7745c5c3_Err != nil {
11371165 return templ_7745c5c3_Err
11381166 }
1139- var templ_7745c5c3_Var55 string
1140- templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
1167+ var templ_7745c5c3_Var57 string
1168+ templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
11411169 if templ_7745c5c3_Err != nil {
1142- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 112}
1170+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 112}
11431171 }
1144- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var55)
1172+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
11451173 if templ_7745c5c3_Err != nil {
11461174 return templ_7745c5c3_Err
11471175 }
1148- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\">")
1176+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\">")
11491177 if templ_7745c5c3_Err != nil {
11501178 return templ_7745c5c3_Err
11511179 }
1152- var templ_7745c5c3_Var56 string
1153- templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
1180+ var templ_7745c5c3_Var58 string
1181+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
11541182 if templ_7745c5c3_Err != nil {
1155- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 125}
1183+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 125}
11561184 }
1157- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
1185+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
11581186 if templ_7745c5c3_Err != nil {
11591187 return templ_7745c5c3_Err
11601188 }
1161- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</button></form><p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")
1189+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</button></form><p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")
11621190 if templ_7745c5c3_Err != nil {
11631191 return templ_7745c5c3_Err
11641192 }
11651193 for _, line := range p.Lines {
1166- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<p class=\"dim\">")
1194+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<p class=\"dim\">")
11671195 if templ_7745c5c3_Err != nil {
11681196 return templ_7745c5c3_Err
11691197 }
1170- var templ_7745c5c3_Var57 string
1171- templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(line)
1198+ var templ_7745c5c3_Var59 string
1199+ templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(line)
11721200 if templ_7745c5c3_Err != nil {
1173- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 231, Col: 25}
1201+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 25}
11741202 }
1175- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
1203+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
11761204 if templ_7745c5c3_Err != nil {
11771205 return templ_7745c5c3_Err
11781206 }
1179- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</p>")
1207+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</p>")
11801208 if templ_7745c5c3_Err != nil {
11811209 return templ_7745c5c3_Err
11821210 }
11831211 }
1184- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
1212+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
11851213 if templ_7745c5c3_Err != nil {
11861214 return templ_7745c5c3_Err
11871215 }
11881216 return nil
11891217 })
1190- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var52), templ_7745c5c3_Buffer)
1218+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var54), templ_7745c5c3_Buffer)
11911219 if templ_7745c5c3_Err != nil {
11921220 return templ_7745c5c3_Err
11931221 }
@@ -1212,12 +1240,12 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component {
12121240 }()
12131241 }
12141242 ctx = templ.InitializeContext(ctx)
1215- templ_7745c5c3_Var58 := templ.GetChildren(ctx)
1216- if templ_7745c5c3_Var58 == nil {
1217- templ_7745c5c3_Var58 = templ.NopComponent
1243+ templ_7745c5c3_Var60 := templ.GetChildren(ctx)
1244+ if templ_7745c5c3_Var60 == nil {
1245+ templ_7745c5c3_Var60 = templ.NopComponent
12181246 }
12191247 ctx = templ.ClearChildren(ctx)
1220- templ_7745c5c3_Var59 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1248+ templ_7745c5c3_Var61 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
12211249 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
12221250 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
12231251 if !templ_7745c5c3_IsBuffer {
@@ -1229,231 +1257,231 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component {
12291257 }()
12301258 }
12311259 ctx = templ.InitializeContext(ctx)
1232- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<h2 class=\"rule-h\"><span>Passkeys</span><span class=\"count\">")
1260+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<h2 class=\"rule-h\"><span>Passkeys</span><span class=\"count\">")
12331261 if templ_7745c5c3_Err != nil {
12341262 return templ_7745c5c3_Err
12351263 }
1236- var templ_7745c5c3_Var60 string
1237- templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows)))
1264+ var templ_7745c5c3_Var62 string
1265+ templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows)))
12381266 if templ_7745c5c3_Err != nil {
1239- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 241, Col: 81}
1267+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 243, Col: 81}
12401268 }
1241- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
1269+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
12421270 if templ_7745c5c3_Err != nil {
12431271 return templ_7745c5c3_Err
12441272 }
1245- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</span></h2><p class=\"dim\">A passkey signs you in from this device or your phone, without a terminal. Removing one signs every browser out.</p>")
1273+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</span></h2><p class=\"dim\">A passkey signs you in from this device or your phone, without a terminal. Removing one signs every browser out.</p>")
12461274 if templ_7745c5c3_Err != nil {
12471275 return templ_7745c5c3_Err
12481276 }
12491277 if len(p.Rows) > 0 {
1250- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<table class=\"list passkeys\" aria-label=\"Passkeys\"><thead><tr><th class=\"c-name\">Name</th><th class=\"c-when\">Created</th><th class=\"c-when\">Last used</th><th class=\"c-sync\">Synced</th><th class=\"c-act\"><span class=\"sr\">Remove</span></th></tr></thead> <tbody>")
1278+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<table class=\"list passkeys\" aria-label=\"Passkeys\"><thead><tr><th class=\"c-name\">Name</th><th class=\"c-when\">Created</th><th class=\"c-when\">Last used</th><th class=\"c-sync\">Synced</th><th class=\"c-act\"><span class=\"sr\">Remove</span></th></tr></thead> <tbody>")
12511279 if templ_7745c5c3_Err != nil {
12521280 return templ_7745c5c3_Err
12531281 }
12541282 for _, r := range p.Rows {
1255- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<tr><td class=\"c-name\">")
1283+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<tr><td class=\"c-name\">")
12561284 if templ_7745c5c3_Err != nil {
12571285 return templ_7745c5c3_Err
12581286 }
1259- var templ_7745c5c3_Var61 string
1260- templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
1287+ var templ_7745c5c3_Var63 string
1288+ templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
12611289 if templ_7745c5c3_Err != nil {
1262- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 257, Col: 34}
1290+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 259, Col: 34}
12631291 }
1264- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
1292+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
12651293 if templ_7745c5c3_Err != nil {
12661294 return templ_7745c5c3_Err
12671295 }
1268- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</td><td class=\"c-when nowrap\"><time")
1296+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</td><td class=\"c-when nowrap\"><time")
12691297 if templ_7745c5c3_Err != nil {
12701298 return templ_7745c5c3_Err
12711299 }
12721300 if r.CreatedAt != "" {
1273- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, " datetime=\"")
1301+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, " datetime=\"")
12741302 if templ_7745c5c3_Err != nil {
12751303 return templ_7745c5c3_Err
12761304 }
1277- var templ_7745c5c3_Var62 string
1278- templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt)
1305+ var templ_7745c5c3_Var64 string
1306+ templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt)
12791307 if templ_7745c5c3_Err != nil {
1280- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 261, Col: 32}
1308+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 263, Col: 32}
12811309 }
1282- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var62)
1310+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
12831311 if templ_7745c5c3_Err != nil {
12841312 return templ_7745c5c3_Err
12851313 }
1286- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\"")
1314+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\"")
12871315 if templ_7745c5c3_Err != nil {
12881316 return templ_7745c5c3_Err
12891317 }
12901318 }
1291- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, ">")
1319+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, ">")
12921320 if templ_7745c5c3_Err != nil {
12931321 return templ_7745c5c3_Err
12941322 }
1295- var templ_7745c5c3_Var63 string
1296- templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created)
1323+ var templ_7745c5c3_Var65 string
1324+ templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created)
12971325 if templ_7745c5c3_Err != nil {
1298- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 263, Col: 20}
1326+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 265, Col: 20}
12991327 }
1300- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
1328+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
13011329 if templ_7745c5c3_Err != nil {
13021330 return templ_7745c5c3_Err
13031331 }
1304- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</time></td><td class=\"c-when nowrap\"><time")
1332+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</time></td><td class=\"c-when nowrap\"><time")
13051333 if templ_7745c5c3_Err != nil {
13061334 return templ_7745c5c3_Err
13071335 }
13081336 if r.LastUsedAt != "" {
1309- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, " datetime=\"")
1337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, " datetime=\"")
13101338 if templ_7745c5c3_Err != nil {
13111339 return templ_7745c5c3_Err
13121340 }
1313- var templ_7745c5c3_Var64 string
1314- templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt)
1341+ var templ_7745c5c3_Var66 string
1342+ templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt)
13151343 if templ_7745c5c3_Err != nil {
1316- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 268, Col: 33}
1344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 270, Col: 33}
13171345 }
1318- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
1346+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
13191347 if templ_7745c5c3_Err != nil {
13201348 return templ_7745c5c3_Err
13211349 }
1322- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\"")
1350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\"")
13231351 if templ_7745c5c3_Err != nil {
13241352 return templ_7745c5c3_Err
13251353 }
13261354 }
1327- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, ">")
1355+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, ">")
13281356 if templ_7745c5c3_Err != nil {
13291357 return templ_7745c5c3_Err
13301358 }
1331- var templ_7745c5c3_Var65 string
1332- templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed)
1359+ var templ_7745c5c3_Var67 string
1360+ templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed)
13331361 if templ_7745c5c3_Err != nil {
1334- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 270, Col: 21}
1362+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 272, Col: 21}
13351363 }
1336- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
1364+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
13371365 if templ_7745c5c3_Err != nil {
13381366 return templ_7745c5c3_Err
13391367 }
1340- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</time></td><td class=\"c-sync\">")
1368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</time></td><td class=\"c-sync\">")
13411369 if templ_7745c5c3_Err != nil {
13421370 return templ_7745c5c3_Err
13431371 }
13441372 if r.Synced {
1345- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "yes")
1373+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "yes")
13461374 if templ_7745c5c3_Err != nil {
13471375 return templ_7745c5c3_Err
13481376 }
13491377 } else {
1350- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "no")
1378+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "no")
13511379 if templ_7745c5c3_Err != nil {
13521380 return templ_7745c5c3_Err
13531381 }
13541382 }
1355- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</td><td class=\"c-act\"><form method=\"post\" action=\"")
1383+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</td><td class=\"c-act\"><form method=\"post\" action=\"")
13561384 if templ_7745c5c3_Err != nil {
13571385 return templ_7745c5c3_Err
13581386 }
1359- var templ_7745c5c3_Var66 templ.SafeURL
1360- templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove))
1387+ var templ_7745c5c3_Var68 templ.SafeURL
1388+ templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove))
13611389 if templ_7745c5c3_Err != nil {
1362- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 280, Col: 60}
1390+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 282, Col: 60}
13631391 }
1364- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
1392+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
13651393 if templ_7745c5c3_Err != nil {
13661394 return templ_7745c5c3_Err
13671395 }
1368- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "\"><button type=\"submit\" class=\"btn danger\" aria-label=\"")
1396+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "\"><button type=\"submit\" class=\"btn danger\" aria-label=\"")
13691397 if templ_7745c5c3_Err != nil {
13701398 return templ_7745c5c3_Err
13711399 }
1372- var templ_7745c5c3_Var67 string
1373- templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name)
1400+ var templ_7745c5c3_Var69 string
1401+ templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name)
13741402 if templ_7745c5c3_Err != nil {
1375- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 281, Col: 89}
1403+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 283, Col: 89}
13761404 }
1377- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var67)
1405+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
13781406 if templ_7745c5c3_Err != nil {
13791407 return templ_7745c5c3_Err
13801408 }
1381- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "\">Remove</button></form></td></tr>")
1409+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">Remove</button></form></td></tr>")
13821410 if templ_7745c5c3_Err != nil {
13831411 return templ_7745c5c3_Err
13841412 }
13851413 }
1386- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</tbody></table>")
1414+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</tbody></table>")
13871415 if templ_7745c5c3_Err != nil {
13881416 return templ_7745c5c3_Err
13891417 }
13901418 } else {
1391- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "<p class=\"lede dim\">no passkeys yet.</p>")
1419+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<p class=\"lede dim\">no passkeys yet.</p>")
13921420 if templ_7745c5c3_Err != nil {
13931421 return templ_7745c5c3_Err
13941422 }
13951423 }
1396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, " ")
1424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, " ")
13971425 if templ_7745c5c3_Err != nil {
13981426 return templ_7745c5c3_Err
13991427 }
14001428 if p.Full {
1401- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<p class=\"dim\">")
1429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<p class=\"dim\">")
14021430 if templ_7745c5c3_Err != nil {
14031431 return templ_7745c5c3_Err
14041432 }
1405- var templ_7745c5c3_Var68 string
1406- templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max))
1433+ var templ_7745c5c3_Var70 string
1434+ templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max))
14071435 if templ_7745c5c3_Err != nil {
1408- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 292, Col: 31}
1436+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 294, Col: 31}
14091437 }
1410- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
1438+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
14111439 if templ_7745c5c3_Err != nil {
14121440 return templ_7745c5c3_Err
14131441 }
1414- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, " passkeys are registered, the most there can be. Remove one to add another.</p>")
1442+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, " passkeys are registered, the most there can be. Remove one to add another.</p>")
14151443 if templ_7745c5c3_Err != nil {
14161444 return templ_7745c5c3_Err
14171445 }
14181446 } else {
1419- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<form class=\"passkey-add\" data-js data-passkey-form><label for=\"passkey-name\" class=\"sr\">Name of the new passkey</label> <input id=\"passkey-name\" type=\"text\" placeholder=\"Name, for example Pixel 9\" autocomplete=\"off\" data-passkey-name> <button type=\"submit\" class=\"btn primary\" data-passkey=\"register\" data-begin=\"")
1447+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<form class=\"passkey-add\" data-js data-passkey-form><label for=\"passkey-name\" class=\"sr\">Name of the new passkey</label> <input id=\"passkey-name\" type=\"text\" placeholder=\"Name, for example Pixel 9\" autocomplete=\"off\" data-passkey-name> <button type=\"submit\" class=\"btn primary\" data-passkey=\"register\" data-begin=\"")
14201448 if templ_7745c5c3_Err != nil {
14211449 return templ_7745c5c3_Err
14221450 }
1423- var templ_7745c5c3_Var69 string
1424- templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
1451+ var templ_7745c5c3_Var71 string
1452+ templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
14251453 if templ_7745c5c3_Err != nil {
1426- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 297, Col: 90}
1454+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 90}
14271455 }
1428- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
1456+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)
14291457 if templ_7745c5c3_Err != nil {
14301458 return templ_7745c5c3_Err
14311459 }
1432- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "\" data-finish=\"")
1460+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" data-finish=\"")
14331461 if templ_7745c5c3_Err != nil {
14341462 return templ_7745c5c3_Err
14351463 }
1436- var templ_7745c5c3_Var70 string
1437- templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
1464+ var templ_7745c5c3_Var72 string
1465+ templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
14381466 if templ_7745c5c3_Err != nil {
1439- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 297, Col: 115}
1467+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 115}
14401468 }
1441- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var70)
1469+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
14421470 if templ_7745c5c3_Err != nil {
14431471 return templ_7745c5c3_Err
14441472 }
1445- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "\">Add a passkey</button></form>")
1473+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\">Add a passkey</button></form>")
14461474 if templ_7745c5c3_Err != nil {
14471475 return templ_7745c5c3_Err
14481476 }
14491477 }
1450- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, " <p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")
1478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, " <p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")
14511479 if templ_7745c5c3_Err != nil {
14521480 return templ_7745c5c3_Err
14531481 }
14541482 return nil
14551483 })
1456- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var59), templ_7745c5c3_Buffer)
1484+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var61), templ_7745c5c3_Buffer)
14571485 if templ_7745c5c3_Err != nil {
14581486 return templ_7745c5c3_Err
14591487 }
@@ -10,6 +10,8 @@ package web
10 import "github.com/a-h/templ"10 import "github.com/a-h/templ"
11 import templruntime "github.com/a-h/templ/runtime"11 import templruntime "github.com/a-h/templ/runtime"
12 12
13+import "fmt"
14+
13 // Home is the repository list.15 // Home is the repository list.
14 func Home(l Layout, p HomePage) templ.Component {16 func Home(l Layout, p HomePage) templ.Component {
15 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {17 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
@@ -56,7 +58,7 @@ func Home(l Layout, p HomePage) templ.Component {
56 var templ_7745c5c3_Var3 templ.SafeURL58 var templ_7745c5c3_Var3 templ.SafeURL
57 templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href))59 templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href))
58 if templ_7745c5c3_Err != nil {60 if templ_7745c5c3_Err != nil {
59- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 27, Col: 36}61+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 29, Col: 36}
60 }62 }
61 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))63 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
62 if templ_7745c5c3_Err != nil {64 if templ_7745c5c3_Err != nil {
@@ -79,7 +81,7 @@ func Home(l Layout, p HomePage) templ.Component {
79 var templ_7745c5c3_Var4 string81 var templ_7745c5c3_Var4 string
80 templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)82 templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)
81 if templ_7745c5c3_Err != nil {83 if templ_7745c5c3_Err != nil {
82- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 32, Col: 17}84+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 34, Col: 17}
83 }85 }
84 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))86 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
85 if templ_7745c5c3_Err != nil {87 if templ_7745c5c3_Err != nil {
@@ -97,7 +99,7 @@ func Home(l Layout, p HomePage) templ.Component {
97 var templ_7745c5c3_Var5 string99 var templ_7745c5c3_Var5 string
98 templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(s.Mark)100 templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(s.Mark)
99 if templ_7745c5c3_Err != nil {101 if templ_7745c5c3_Err != nil {
100- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 34, Col: 42}102+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 36, Col: 42}
101 }103 }
102 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))104 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
103 if templ_7745c5c3_Err != nil {105 if templ_7745c5c3_Err != nil {
@@ -110,7 +112,7 @@ func Home(l Layout, p HomePage) templ.Component {
110 var templ_7745c5c3_Var6 string112 var templ_7745c5c3_Var6 string
111 templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(", " + s.State)113 templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(", " + s.State)
112 if templ_7745c5c3_Err != nil {114 if templ_7745c5c3_Err != nil {
113- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 35, Col: 42}115+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 37, Col: 42}
114 }116 }
115 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))117 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
116 if templ_7745c5c3_Err != nil {118 if templ_7745c5c3_Err != nil {
@@ -126,7 +128,33 @@ func Home(l Layout, p HomePage) templ.Component {
126 return templ_7745c5c3_Err128 return templ_7745c5c3_Err
127 }129 }
128 }130 }
129- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</nav><div class=\"layout-switch\" role=\"group\" aria-label=\"Layout\" data-js><button type=\"button\" class=\"btn icon\" data-set-layout=\"cards\" aria-pressed=\"true\" title=\"Cards\" aria-label=\"Cards\"><span class=\"ico i-cards\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"split\" aria-pressed=\"false\" title=\"Two columns\" aria-label=\"Two columns\"><span class=\"ico i-split\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"compact\" aria-pressed=\"false\" title=\"Compact list\" aria-label=\"Compact list\"><span class=\"ico i-compact\" aria-hidden=\"true\"></span></button></div></div><ul class=\"repos\" id=\"repo-rows\" data-rows>")131+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</nav><div class=\"layout-switch\" role=\"group\" aria-label=\"Layout\" data-js><button type=\"button\" class=\"btn icon\" data-set-layout=\"cards\" aria-pressed=\"true\" title=\"Cards\" aria-label=\"Cards\"><span class=\"ico i-cards\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"split\" aria-pressed=\"false\" title=\"Two columns\" aria-label=\"Two columns\"><span class=\"ico i-split\" aria-hidden=\"true\"></span></button> <button type=\"button\" class=\"btn icon\" data-set-layout=\"compact\" aria-pressed=\"false\" title=\"Compact list\" aria-label=\"Compact list\"><span class=\"ico i-compact\" aria-hidden=\"true\"></span></button></div></div><ul class=\"repos\" id=\"repo-rows\" data-rows data-sort=\"")
132+ if templ_7745c5c3_Err != nil {
133+ return templ_7745c5c3_Err
134+ }
135+ var templ_7745c5c3_Var7 string
136+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Sort)
137+ if templ_7745c5c3_Err != nil {
138+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 54, Col: 65}
139+ }
140+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
141+ if templ_7745c5c3_Err != nil {
142+ return templ_7745c5c3_Err
143+ }
144+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" data-desc=\"")
145+ if templ_7745c5c3_Err != nil {
146+ return templ_7745c5c3_Err
147+ }
148+ var templ_7745c5c3_Var8 string
149+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprint(p.Desc))
150+ if templ_7745c5c3_Err != nil {
151+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 54, Col: 98}
152+ }
153+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
154+ if templ_7745c5c3_Err != nil {
155+ return templ_7745c5c3_Err
156+ }
157+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">")
130 if templ_7745c5c3_Err != nil {158 if templ_7745c5c3_Err != nil {
131 return templ_7745c5c3_Err159 return templ_7745c5c3_Err
132 }160 }
@@ -136,17 +164,17 @@ func Home(l Layout, p HomePage) templ.Component {
136 return templ_7745c5c3_Err164 return templ_7745c5c3_Err
137 }165 }
138 }166 }
139- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</ul>")167+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</ul>")
140 if templ_7745c5c3_Err != nil {168 if templ_7745c5c3_Err != nil {
141 return templ_7745c5c3_Err169 return templ_7745c5c3_Err
142 }170 }
143 if len(p.Rows) == 0 {171 if len(p.Rows) == 0 {
144- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<p class=\"lede dim\">no repositories yet.</p>")172+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p class=\"lede dim\">no repositories yet.</p>")
145 if templ_7745c5c3_Err != nil {173 if templ_7745c5c3_Err != nil {
146 return templ_7745c5c3_Err174 return templ_7745c5c3_Err
147 }175 }
148 }176 }
149- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</section></div>")177+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</section></div>")
150 if templ_7745c5c3_Err != nil {178 if templ_7745c5c3_Err != nil {
151 return templ_7745c5c3_Err179 return templ_7745c5c3_Err
152 }180 }
@@ -178,12 +206,12 @@ func Summary(l Layout, p SummaryPage) templ.Component {
178 }()206 }()
179 }207 }
180 ctx = templ.InitializeContext(ctx)208 ctx = templ.InitializeContext(ctx)
181- templ_7745c5c3_Var7 := templ.GetChildren(ctx)209+ templ_7745c5c3_Var9 := templ.GetChildren(ctx)
182- if templ_7745c5c3_Var7 == nil {210+ if templ_7745c5c3_Var9 == nil {
183- templ_7745c5c3_Var7 = templ.NopComponent211+ templ_7745c5c3_Var9 = templ.NopComponent
184 }212 }
185 ctx = templ.ClearChildren(ctx)213 ctx = templ.ClearChildren(ctx)
186- templ_7745c5c3_Var8 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {214+ templ_7745c5c3_Var10 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
187 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context215 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
188 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)216 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
189 if !templ_7745c5c3_IsBuffer {217 if !templ_7745c5c3_IsBuffer {
@@ -203,7 +231,7 @@ func Summary(l Layout, p SummaryPage) templ.Component {
203 }231 }
204 return nil232 return nil
205 })233 })
206- templ_7745c5c3_Err = Shell(l, summaryRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)234+ templ_7745c5c3_Err = Shell(l, summaryRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer)
207 if templ_7745c5c3_Err != nil {235 if templ_7745c5c3_Err != nil {
208 return templ_7745c5c3_Err236 return templ_7745c5c3_Err
209 }237 }
@@ -230,158 +258,158 @@ func latestTagFact(t LatestTag) templ.Component {
230 }()258 }()
231 }259 }
232 ctx = templ.InitializeContext(ctx)260 ctx = templ.InitializeContext(ctx)
233- templ_7745c5c3_Var9 := templ.GetChildren(ctx)261+ templ_7745c5c3_Var11 := templ.GetChildren(ctx)
234- if templ_7745c5c3_Var9 == nil {262+ if templ_7745c5c3_Var11 == nil {
235- templ_7745c5c3_Var9 = templ.NopComponent263+ templ_7745c5c3_Var11 = templ.NopComponent
236 }264 }
237 ctx = templ.ClearChildren(ctx)265 ctx = templ.ClearChildren(ctx)
238- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<dt>latest tag</dt><dd class=\"latest-tag\">")266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<dt>latest tag</dt><dd class=\"latest-tag\">")
239 if templ_7745c5c3_Err != nil {267 if templ_7745c5c3_Err != nil {
240 return templ_7745c5c3_Err268 return templ_7745c5c3_Err
241 }269 }
242 if t.Name != "" {270 if t.Name != "" {
243- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<a class=\"tag-name\" href=\"")271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<a class=\"tag-name\" href=\"")
244 if templ_7745c5c3_Err != nil {272 if templ_7745c5c3_Err != nil {
245 return templ_7745c5c3_Err273 return templ_7745c5c3_Err
246 }274 }
247- var templ_7745c5c3_Var10 templ.SafeURL275+ var templ_7745c5c3_Var12 templ.SafeURL
248- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.Href))276+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.Href))
249 if templ_7745c5c3_Err != nil {277 if templ_7745c5c3_Err != nil {
250- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 82, Col: 51}278+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 51}
251 }279 }
252- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))280+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
253 if templ_7745c5c3_Err != nil {281 if templ_7745c5c3_Err != nil {
254 return templ_7745c5c3_Err282 return templ_7745c5c3_Err
255 }283 }
256- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" title=\"")284+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" title=\"")
257 if templ_7745c5c3_Err != nil {285 if templ_7745c5c3_Err != nil {
258 return templ_7745c5c3_Err286 return templ_7745c5c3_Err
259 }287 }
260- var templ_7745c5c3_Var11 string288+ var templ_7745c5c3_Var13 string
261- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Subject)289+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Subject)
262 if templ_7745c5c3_Err != nil {290 if templ_7745c5c3_Err != nil {
263- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 82, Col: 71}291+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 71}
264 }292 }
265- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)293+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
266 if templ_7745c5c3_Err != nil {294 if templ_7745c5c3_Err != nil {
267 return templ_7745c5c3_Err295 return templ_7745c5c3_Err
268 }296 }
269- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">")297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
270 if templ_7745c5c3_Err != nil {298 if templ_7745c5c3_Err != nil {
271 return templ_7745c5c3_Err299 return templ_7745c5c3_Err
272 }300 }
273- var templ_7745c5c3_Var12 string301+ var templ_7745c5c3_Var14 string
274- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)302+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)
275 if templ_7745c5c3_Err != nil {303 if templ_7745c5c3_Err != nil {
276- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 82, Col: 82}304+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 82}
277 }305 }
278- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))306+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
279 if templ_7745c5c3_Err != nil {307 if templ_7745c5c3_Err != nil {
280 return templ_7745c5c3_Err308 return templ_7745c5c3_Err
281 }309 }
282- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</a> ")310+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</a> ")
283 if templ_7745c5c3_Err != nil {311 if templ_7745c5c3_Err != nil {
284 return templ_7745c5c3_Err312 return templ_7745c5c3_Err
285 }313 }
286 if t.WhenAt != "" {314 if t.WhenAt != "" {
287- var templ_7745c5c3_Var13 string315+ var templ_7745c5c3_Var15 string
288- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")316+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
289 if templ_7745c5c3_Err != nil {317 if templ_7745c5c3_Err != nil {
290- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 12}318+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 12}
291 }319 }
292- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))320+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
293 if templ_7745c5c3_Err != nil {321 if templ_7745c5c3_Err != nil {
294 return templ_7745c5c3_Err322 return templ_7745c5c3_Err
295 }323 }
296- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<time datetime=\"")324+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<time datetime=\"")
297 if templ_7745c5c3_Err != nil {325 if templ_7745c5c3_Err != nil {
298 return templ_7745c5c3_Err326 return templ_7745c5c3_Err
299 }327 }
300- var templ_7745c5c3_Var14 string328+ var templ_7745c5c3_Var16 string
301- templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)329+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
302 if templ_7745c5c3_Err != nil {330 if templ_7745c5c3_Err != nil {
303- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 39}331+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 39}
304 }332 }
305- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)333+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
306 if templ_7745c5c3_Err != nil {334 if templ_7745c5c3_Err != nil {
307 return templ_7745c5c3_Err335 return templ_7745c5c3_Err
308 }336 }
309- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" title=\"")337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" title=\"")
310 if templ_7745c5c3_Err != nil {338 if templ_7745c5c3_Err != nil {
311 return templ_7745c5c3_Err339 return templ_7745c5c3_Err
312 }340 }
313- var templ_7745c5c3_Var15 string341+ var templ_7745c5c3_Var17 string
314- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)342+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
315 if templ_7745c5c3_Err != nil {343 if templ_7745c5c3_Err != nil {
316- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 58}344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 58}
317 }345 }
318- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)346+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
319 if templ_7745c5c3_Err != nil {347 if templ_7745c5c3_Err != nil {
320 return templ_7745c5c3_Err348 return templ_7745c5c3_Err
321 }349 }
322- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">")350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
323 if templ_7745c5c3_Err != nil {351 if templ_7745c5c3_Err != nil {
324 return templ_7745c5c3_Err352 return templ_7745c5c3_Err
325 }353 }
326- var templ_7745c5c3_Var16 string354+ var templ_7745c5c3_Var18 string
327- templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(t.When)355+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(t.When)
328 if templ_7745c5c3_Err != nil {356 if templ_7745c5c3_Err != nil {
329- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 69}357+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 69}
330 }358 }
331- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))359+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
332 if templ_7745c5c3_Err != nil {360 if templ_7745c5c3_Err != nil {
333 return templ_7745c5c3_Err361 return templ_7745c5c3_Err
334 }362 }
335- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</time>")363+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</time>")
336 if templ_7745c5c3_Err != nil {364 if templ_7745c5c3_Err != nil {
337 return templ_7745c5c3_Err365 return templ_7745c5c3_Err
338 }366 }
339 }367 }
340- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " ")368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
341 if templ_7745c5c3_Err != nil {369 if templ_7745c5c3_Err != nil {
342 return templ_7745c5c3_Err370 return templ_7745c5c3_Err
343 }371 }
344- var templ_7745c5c3_Var17 string372+ var templ_7745c5c3_Var19 string
345- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")373+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
346 if templ_7745c5c3_Err != nil {374 if templ_7745c5c3_Err != nil {
347- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 11}375+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 11}
348 }376 }
349- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))377+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
350 if templ_7745c5c3_Err != nil {378 if templ_7745c5c3_Err != nil {
351 return templ_7745c5c3_Err379 return templ_7745c5c3_Err
352 }380 }
353- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ")381+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ")
354 if templ_7745c5c3_Err != nil {382 if templ_7745c5c3_Err != nil {
355 return templ_7745c5c3_Err383 return templ_7745c5c3_Err
356 }384 }
357 }385 }
358- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<a class=\"all-tags\" href=\"")386+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<a class=\"all-tags\" href=\"")
359 if templ_7745c5c3_Err != nil {387 if templ_7745c5c3_Err != nil {
360 return templ_7745c5c3_Err388 return templ_7745c5c3_Err
361 }389 }
362- var templ_7745c5c3_Var18 templ.SafeURL390+ var templ_7745c5c3_Var20 templ.SafeURL
363- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.RefsHref))391+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.RefsHref))
364 if templ_7745c5c3_Err != nil {392 if templ_7745c5c3_Err != nil {
365- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 54}393+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 54}
366 }394 }
367- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))395+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
368 if templ_7745c5c3_Err != nil {396 if templ_7745c5c3_Err != nil {
369 return templ_7745c5c3_Err397 return templ_7745c5c3_Err
370 }398 }
371- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\">")399+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
372 if templ_7745c5c3_Err != nil {400 if templ_7745c5c3_Err != nil {
373 return templ_7745c5c3_Err401 return templ_7745c5c3_Err
374 }402 }
375- var templ_7745c5c3_Var19 string403+ var templ_7745c5c3_Var21 string
376- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(plural(t.Count, "tag", "tags"))404+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(plural(t.Count, "tag", "tags"))
377 if templ_7745c5c3_Err != nil {405 if templ_7745c5c3_Err != nil {
378- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 89}406+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 89}
379 }407 }
380- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))408+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
381 if templ_7745c5c3_Err != nil {409 if templ_7745c5c3_Err != nil {
382 return templ_7745c5c3_Err410 return templ_7745c5c3_Err
383 }411 }
384- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</a></dd>")412+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</a></dd>")
385 if templ_7745c5c3_Err != nil {413 if templ_7745c5c3_Err != nil {
386 return templ_7745c5c3_Err414 return templ_7745c5c3_Err
387 }415 }
@@ -405,12 +433,12 @@ func summaryRail(p SummaryPage) templ.Component {
405 }()433 }()
406 }434 }
407 ctx = templ.InitializeContext(ctx)435 ctx = templ.InitializeContext(ctx)
408- templ_7745c5c3_Var20 := templ.GetChildren(ctx)436+ templ_7745c5c3_Var22 := templ.GetChildren(ctx)
409- if templ_7745c5c3_Var20 == nil {437+ if templ_7745c5c3_Var22 == nil {
410- templ_7745c5c3_Var20 = templ.NopComponent438+ templ_7745c5c3_Var22 = templ.NopComponent
411 }439 }
412 ctx = templ.ClearChildren(ctx)440 ctx = templ.ClearChildren(ctx)
413- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<section aria-labelledby=\"r-clone\"><h2 id=\"r-clone\">Clone</h2>")441+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<section aria-labelledby=\"r-clone\"><h2 id=\"r-clone\">Clone</h2>")
414 if templ_7745c5c3_Err != nil {442 if templ_7745c5c3_Err != nil {
415 return templ_7745c5c3_Err443 return templ_7745c5c3_Err
416 }444 }
@@ -418,25 +446,25 @@ func summaryRail(p SummaryPage) templ.Component {
418 if templ_7745c5c3_Err != nil {446 if templ_7745c5c3_Err != nil {
419 return templ_7745c5c3_Err447 return templ_7745c5c3_Err
420 }448 }
421- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</section><section aria-labelledby=\"r-facts\"><h2 id=\"r-facts\">About</h2><dl class=\"kv\">")449+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</section><section aria-labelledby=\"r-facts\"><h2 id=\"r-facts\">About</h2><dl class=\"kv\">")
422 if templ_7745c5c3_Err != nil {450 if templ_7745c5c3_Err != nil {
423 return templ_7745c5c3_Err451 return templ_7745c5c3_Err
424 }452 }
425 for _, f := range p.Facts {453 for _, f := range p.Facts {
426- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<dt>")454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<dt>")
427 if templ_7745c5c3_Err != nil {455 if templ_7745c5c3_Err != nil {
428 return templ_7745c5c3_Err456 return templ_7745c5c3_Err
429 }457 }
430- var templ_7745c5c3_Var21 string458+ var templ_7745c5c3_Var23 string
431- templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(f.Key)459+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(f.Key)
432 if templ_7745c5c3_Err != nil {460 if templ_7745c5c3_Err != nil {
433- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 99, Col: 15}461+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 101, Col: 15}
434 }462 }
435- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))463+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
436 if templ_7745c5c3_Err != nil {464 if templ_7745c5c3_Err != nil {
437 return templ_7745c5c3_Err465 return templ_7745c5c3_Err
438 }466 }
439- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</dt>")467+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</dt>")
440 if templ_7745c5c3_Err != nil {468 if templ_7745c5c3_Err != nil {
441 return templ_7745c5c3_Err469 return templ_7745c5c3_Err
442 }470 }
@@ -451,25 +479,25 @@ func summaryRail(p SummaryPage) templ.Component {
451 return templ_7745c5c3_Err479 return templ_7745c5c3_Err
452 }480 }
453 }481 }
454- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</dl></section>")482+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</dl></section>")
455 if templ_7745c5c3_Err != nil {483 if templ_7745c5c3_Err != nil {
456 return templ_7745c5c3_Err484 return templ_7745c5c3_Err
457 }485 }
458 if len(p.Recent) > 0 {486 if len(p.Recent) > 0 {
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")487+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<section aria-labelledby=\"r-recent\"><h2 id=\"r-recent\">Recent commits <a href=\"")
460 if templ_7745c5c3_Err != nil {488 if templ_7745c5c3_Err != nil {
461 return templ_7745c5c3_Err489 return templ_7745c5c3_Err
462 }490 }
463- var templ_7745c5c3_Var22 templ.SafeURL491+ var templ_7745c5c3_Var24 templ.SafeURL
464- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))492+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
465 if templ_7745c5c3_Err != nil {493 if templ_7745c5c3_Err != nil {
466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 108, Col: 105}494+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 110, Col: 105}
467 }495 }
468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))496+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
469 if templ_7745c5c3_Err != nil {497 if templ_7745c5c3_Err != nil {
470 return templ_7745c5c3_Err498 return templ_7745c5c3_Err
471 }499 }
472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\">View all</a></h2>")500+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">View all</a></h2>")
473 if templ_7745c5c3_Err != nil {501 if templ_7745c5c3_Err != nil {
474 return templ_7745c5c3_Err502 return templ_7745c5c3_Err
475 }503 }
@@ -477,7 +505,7 @@ func summaryRail(p SummaryPage) templ.Component {
477 if templ_7745c5c3_Err != nil {505 if templ_7745c5c3_Err != nil {
478 return templ_7745c5c3_Err506 return templ_7745c5c3_Err
479 }507 }
480- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</section>")508+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</section>")
481 if templ_7745c5c3_Err != nil {509 if templ_7745c5c3_Err != nil {
482 return templ_7745c5c3_Err510 return templ_7745c5c3_Err
483 }511 }
@@ -489,7 +517,7 @@ func summaryRail(p SummaryPage) templ.Component {
489 }517 }
490 }518 }
491 if len(p.Outline) > 0 && p.Readme != nil {519 if len(p.Outline) > 0 && p.Readme != nil {
492- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")520+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>")
493 if templ_7745c5c3_Err != nil {521 if templ_7745c5c3_Err != nil {
494 return templ_7745c5c3_Err522 return templ_7745c5c3_Err
495 }523 }
@@ -497,7 +525,7 @@ func summaryRail(p SummaryPage) templ.Component {
497 if templ_7745c5c3_Err != nil {525 if templ_7745c5c3_Err != nil {
498 return templ_7745c5c3_Err526 return templ_7745c5c3_Err
499 }527 }
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</section>")528+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</section>")
501 if templ_7745c5c3_Err != nil {529 if templ_7745c5c3_Err != nil {
502 return templ_7745c5c3_Err530 return templ_7745c5c3_Err
503 }531 }
@@ -523,12 +551,12 @@ func Refs(l Layout, p RefsPage) templ.Component {
523 }()551 }()
524 }552 }
525 ctx = templ.InitializeContext(ctx)553 ctx = templ.InitializeContext(ctx)
526- templ_7745c5c3_Var23 := templ.GetChildren(ctx)554+ templ_7745c5c3_Var25 := templ.GetChildren(ctx)
527- if templ_7745c5c3_Var23 == nil {555+ if templ_7745c5c3_Var25 == nil {
528- templ_7745c5c3_Var23 = templ.NopComponent556+ templ_7745c5c3_Var25 = templ.NopComponent
529 }557 }
530 ctx = templ.ClearChildren(ctx)558 ctx = templ.ClearChildren(ctx)
531- templ_7745c5c3_Var24 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {559+ templ_7745c5c3_Var26 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
532 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context560 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
533 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)561 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
534 if !templ_7745c5c3_IsBuffer {562 if !templ_7745c5c3_IsBuffer {
@@ -540,7 +568,7 @@ func Refs(l Layout, p RefsPage) templ.Component {
540 }()568 }()
541 }569 }
542 ctx = templ.InitializeContext(ctx)570 ctx = templ.InitializeContext(ctx)
543- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter=\"#ref-rows > tr\" placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")571+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<div class=\"filter\"><label for=\"ref-filter\" class=\"sr\">filter</label> <input id=\"ref-filter\" type=\"search\" data-filter=\"#ref-rows > tr\" placeholder=\"Filter branches and tags…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><table class=\"list refs\" aria-label=\"Branches and tags\"><thead><tr><th class=\"c-kind\">kind</th><th class=\"c-name\">name</th><th class=\"c-sha\">commit</th><th class=\"c-sub\">subject</th><th class=\"c-when\">date</th></tr></thead>")
544 if templ_7745c5c3_Err != nil {572 if templ_7745c5c3_Err != nil {
545 return templ_7745c5c3_Err573 return templ_7745c5c3_Err
546 }574 }
@@ -548,32 +576,32 @@ func Refs(l Layout, p RefsPage) templ.Component {
548 if templ_7745c5c3_Err != nil {576 if templ_7745c5c3_Err != nil {
549 return templ_7745c5c3_Err577 return templ_7745c5c3_Err
550 }578 }
551- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</table>")579+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</table>")
552 if templ_7745c5c3_Err != nil {580 if templ_7745c5c3_Err != nil {
553 return templ_7745c5c3_Err581 return templ_7745c5c3_Err
554 }582 }
555 if p.Note != "" {583 if p.Note != "" {
556- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<p class=\"note\">")584+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<p class=\"note\">")
557 if templ_7745c5c3_Err != nil {585 if templ_7745c5c3_Err != nil {
558 return templ_7745c5c3_Err586 return templ_7745c5c3_Err
559 }587 }
560- var templ_7745c5c3_Var25 string588+ var templ_7745c5c3_Var27 string
561- templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)589+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
562 if templ_7745c5c3_Err != nil {590 if templ_7745c5c3_Err != nil {
563- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 141, Col: 27}591+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 143, Col: 27}
564 }592 }
565- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))593+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
566 if templ_7745c5c3_Err != nil {594 if templ_7745c5c3_Err != nil {
567 return templ_7745c5c3_Err595 return templ_7745c5c3_Err
568 }596 }
569- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</p>")597+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</p>")
570 if templ_7745c5c3_Err != nil {598 if templ_7745c5c3_Err != nil {
571 return templ_7745c5c3_Err599 return templ_7745c5c3_Err
572 }600 }
573 }601 }
574 return nil602 return nil
575 })603 })
576- templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var24), templ_7745c5c3_Buffer)604+ templ_7745c5c3_Err = Shell(l, refsRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var26), templ_7745c5c3_Buffer)
577 if templ_7745c5c3_Err != nil {605 if templ_7745c5c3_Err != nil {
578 return templ_7745c5c3_Err606 return templ_7745c5c3_Err
579 }607 }
@@ -597,112 +625,112 @@ func refRow(kind string, r RefRow) templ.Component {
597 }()625 }()
598 }626 }
599 ctx = templ.InitializeContext(ctx)627 ctx = templ.InitializeContext(ctx)
600- templ_7745c5c3_Var26 := templ.GetChildren(ctx)628+ templ_7745c5c3_Var28 := templ.GetChildren(ctx)
601- if templ_7745c5c3_Var26 == nil {629+ if templ_7745c5c3_Var28 == nil {
602- templ_7745c5c3_Var26 = templ.NopComponent630+ templ_7745c5c3_Var28 = templ.NopComponent
603 }631 }
604 ctx = templ.ClearChildren(ctx)632 ctx = templ.ClearChildren(ctx)
605- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<tr")633+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<tr")
606 if templ_7745c5c3_Err != nil {634 if templ_7745c5c3_Err != nil {
607 return templ_7745c5c3_Err635 return templ_7745c5c3_Err
608 }636 }
609 if r.Cursor {637 if r.Cursor {
610- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " data-cursor")638+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " data-cursor")
611 if templ_7745c5c3_Err != nil {639 if templ_7745c5c3_Err != nil {
612 return templ_7745c5c3_Err640 return templ_7745c5c3_Err
613 }641 }
614 }642 }
615- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "><td class=\"c-kind mute\">")643+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "><td class=\"c-kind mute\">")
616 if templ_7745c5c3_Err != nil {644 if templ_7745c5c3_Err != nil {
617 return templ_7745c5c3_Err645 return templ_7745c5c3_Err
618 }646 }
619- var templ_7745c5c3_Var27 string647+ var templ_7745c5c3_Var29 string
620- templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(kind)648+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
621 if templ_7745c5c3_Err != nil {649 if templ_7745c5c3_Err != nil {
622- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 148, Col: 32}650+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 32}
623 }651 }
624- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))652+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
625 if templ_7745c5c3_Err != nil {653 if templ_7745c5c3_Err != nil {
626 return templ_7745c5c3_Err654 return templ_7745c5c3_Err
627 }655 }
628- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</td><td class=\"c-name\"><a href=\"")656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</td><td class=\"c-name\"><a href=\"")
629 if templ_7745c5c3_Err != nil {657 if templ_7745c5c3_Err != nil {
630 return templ_7745c5c3_Err658 return templ_7745c5c3_Err
631 }659 }
632- var templ_7745c5c3_Var28 templ.SafeURL660+ var templ_7745c5c3_Var30 templ.SafeURL
633- templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))661+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
634 if templ_7745c5c3_Err != nil {662 if templ_7745c5c3_Err != nil {
635- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 34}663+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 34}
636 }664 }
637- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))665+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
638 if templ_7745c5c3_Err != nil {666 if templ_7745c5c3_Err != nil {
639 return templ_7745c5c3_Err667 return templ_7745c5c3_Err
640 }668 }
641- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\">")669+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\">")
642 if templ_7745c5c3_Err != nil {670 if templ_7745c5c3_Err != nil {
643 return templ_7745c5c3_Err671 return templ_7745c5c3_Err
644 }672 }
645- var templ_7745c5c3_Var29 string673+ var templ_7745c5c3_Var31 string
646- templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)674+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
647 if templ_7745c5c3_Err != nil {675 if templ_7745c5c3_Err != nil {
648- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 45}676+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 45}
649 }677 }
650- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))678+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
651 if templ_7745c5c3_Err != nil {679 if templ_7745c5c3_Err != nil {
652 return templ_7745c5c3_Err680 return templ_7745c5c3_Err
653 }681 }
654- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</a> ")682+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</a> ")
655 if templ_7745c5c3_Err != nil {683 if templ_7745c5c3_Err != nil {
656 return templ_7745c5c3_Err684 return templ_7745c5c3_Err
657 }685 }
658 if r.Default {686 if r.Default {
659- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<span class=\"pill\">default</span>")687+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"pill\">default</span>")
660 if templ_7745c5c3_Err != nil {688 if templ_7745c5c3_Err != nil {
661 return templ_7745c5c3_Err689 return templ_7745c5c3_Err
662 }690 }
663 }691 }
664- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</td><td class=\"c-sha\">")692+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-sha\">")
665 if templ_7745c5c3_Err != nil {693 if templ_7745c5c3_Err != nil {
666 return templ_7745c5c3_Err694 return templ_7745c5c3_Err
667 }695 }
668 if r.CHref != "" {696 if r.CHref != "" {
669- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<a href=\"")697+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a href=\"")
670 if templ_7745c5c3_Err != nil {698 if templ_7745c5c3_Err != nil {
671 return templ_7745c5c3_Err699 return templ_7745c5c3_Err
672 }700 }
673- var templ_7745c5c3_Var30 templ.SafeURL701+ var templ_7745c5c3_Var32 templ.SafeURL
674- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))702+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
675 if templ_7745c5c3_Err != nil {703 if templ_7745c5c3_Err != nil {
676- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 157, Col: 36}704+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 36}
677 }705 }
678- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))706+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
679 if templ_7745c5c3_Err != nil {707 if templ_7745c5c3_Err != nil {
680 return templ_7745c5c3_Err708 return templ_7745c5c3_Err
681 }709 }
682- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "\">")710+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\">")
683 if templ_7745c5c3_Err != nil {711 if templ_7745c5c3_Err != nil {
684 return templ_7745c5c3_Err712 return templ_7745c5c3_Err
685 }713 }
686- var templ_7745c5c3_Var31 string714+ var templ_7745c5c3_Var33 string
687- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)715+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
688 if templ_7745c5c3_Err != nil {716 if templ_7745c5c3_Err != nil {
689- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 157, Col: 48}717+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 48}
690 }718 }
691- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))719+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
692 if templ_7745c5c3_Err != nil {720 if templ_7745c5c3_Err != nil {
693 return templ_7745c5c3_Err721 return templ_7745c5c3_Err
694 }722 }
695- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</a>")723+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</a>")
696 if templ_7745c5c3_Err != nil {724 if templ_7745c5c3_Err != nil {
697 return templ_7745c5c3_Err725 return templ_7745c5c3_Err
698 }726 }
699 } else {727 } else {
700- var templ_7745c5c3_Var32 string728+ var templ_7745c5c3_Var34 string
701- templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)729+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
702 if templ_7745c5c3_Err != nil {730 if templ_7745c5c3_Err != nil {
703- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 13}731+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 13}
704 }732 }
705- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))733+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
706 if templ_7745c5c3_Err != nil {734 if templ_7745c5c3_Err != nil {
707 return templ_7745c5c3_Err735 return templ_7745c5c3_Err
708 }736 }
@@ -711,69 +739,69 @@ func refRow(kind string, r RefRow) templ.Component {
711 if templ_7745c5c3_Err != nil {739 if templ_7745c5c3_Err != nil {
712 return templ_7745c5c3_Err740 return templ_7745c5c3_Err
713 }741 }
714- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</td><td class=\"c-sub\">")742+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</td><td class=\"c-sub\">")
715 if templ_7745c5c3_Err != nil {743 if templ_7745c5c3_Err != nil {
716 return templ_7745c5c3_Err744 return templ_7745c5c3_Err
717 }745 }
718- var templ_7745c5c3_Var33 string746+ var templ_7745c5c3_Var35 string
719- templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)747+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
720 if templ_7745c5c3_Err != nil {748 if templ_7745c5c3_Err != nil {
721- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 31}749+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 31}
722 }750 }
723- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))751+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
724 if templ_7745c5c3_Err != nil {752 if templ_7745c5c3_Err != nil {
725 return templ_7745c5c3_Err753 return templ_7745c5c3_Err
726 }754 }
727- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</td><td class=\"c-when dim\">")755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</td><td class=\"c-when dim\">")
728 if templ_7745c5c3_Err != nil {756 if templ_7745c5c3_Err != nil {
729 return templ_7745c5c3_Err757 return templ_7745c5c3_Err
730 }758 }
731 if r.WhenAt != "" {759 if r.WhenAt != "" {
732- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<time datetime=\"")760+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<time datetime=\"")
733 if templ_7745c5c3_Err != nil {761 if templ_7745c5c3_Err != nil {
734 return templ_7745c5c3_Err762 return templ_7745c5c3_Err
735 }763 }
736- var templ_7745c5c3_Var34 string764+ var templ_7745c5c3_Var36 string
737- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)765+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
738 if templ_7745c5c3_Err != nil {766 if templ_7745c5c3_Err != nil {
739- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 29}767+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 29}
740 }768 }
741- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)769+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
742 if templ_7745c5c3_Err != nil {770 if templ_7745c5c3_Err != nil {
743 return templ_7745c5c3_Err771 return templ_7745c5c3_Err
744 }772 }
745- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\" title=\"")773+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" title=\"")
746 if templ_7745c5c3_Err != nil {774 if templ_7745c5c3_Err != nil {
747 return templ_7745c5c3_Err775 return templ_7745c5c3_Err
748 }776 }
749- var templ_7745c5c3_Var35 string777+ var templ_7745c5c3_Var37 string
750- templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)778+ templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
751 if templ_7745c5c3_Err != nil {779 if templ_7745c5c3_Err != nil {
752- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 48}780+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 48}
753 }781 }
754- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)782+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var37)
755 if templ_7745c5c3_Err != nil {783 if templ_7745c5c3_Err != nil {
756 return templ_7745c5c3_Err784 return templ_7745c5c3_Err
757 }785 }
758- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\">")786+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\">")
759 if templ_7745c5c3_Err != nil {787 if templ_7745c5c3_Err != nil {
760 return templ_7745c5c3_Err788 return templ_7745c5c3_Err
761 }789 }
762- var templ_7745c5c3_Var36 string790+ var templ_7745c5c3_Var38 string
763- templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)791+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
764 if templ_7745c5c3_Err != nil {792 if templ_7745c5c3_Err != nil {
765- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 59}793+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 59}
766 }794 }
767- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))795+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
768 if templ_7745c5c3_Err != nil {796 if templ_7745c5c3_Err != nil {
769 return templ_7745c5c3_Err797 return templ_7745c5c3_Err
770 }798 }
771- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</time>")799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</time>")
772 if templ_7745c5c3_Err != nil {800 if templ_7745c5c3_Err != nil {
773 return templ_7745c5c3_Err801 return templ_7745c5c3_Err
774 }802 }
775 }803 }
776- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</td></tr>")804+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</td></tr>")
777 if templ_7745c5c3_Err != nil {805 if templ_7745c5c3_Err != nil {
778 return templ_7745c5c3_Err806 return templ_7745c5c3_Err
779 }807 }
@@ -797,12 +825,12 @@ func refsRail(p RefsPage) templ.Component {
797 }()825 }()
798 }826 }
799 ctx = templ.InitializeContext(ctx)827 ctx = templ.InitializeContext(ctx)
800- templ_7745c5c3_Var37 := templ.GetChildren(ctx)828+ templ_7745c5c3_Var39 := templ.GetChildren(ctx)
801- if templ_7745c5c3_Var37 == nil {829+ if templ_7745c5c3_Var39 == nil {
802- templ_7745c5c3_Var37 = templ.NopComponent830+ templ_7745c5c3_Var39 = templ.NopComponent
803 }831 }
804 ctx = templ.ClearChildren(ctx)832 ctx = templ.ClearChildren(ctx)
805- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")833+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<section aria-labelledby=\"r-refs\"><h2 id=\"r-refs\">Overview</h2>")
806 if templ_7745c5c3_Err != nil {834 if templ_7745c5c3_Err != nil {
807 return templ_7745c5c3_Err835 return templ_7745c5c3_Err
808 }836 }
@@ -810,7 +838,7 @@ func refsRail(p RefsPage) templ.Component {
810 if templ_7745c5c3_Err != nil {838 if templ_7745c5c3_Err != nil {
811 return templ_7745c5c3_Err839 return templ_7745c5c3_Err
812 }840 }
813- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</section>")841+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</section>")
814 if templ_7745c5c3_Err != nil {842 if templ_7745c5c3_Err != nil {
815 return templ_7745c5c3_Err843 return templ_7745c5c3_Err
816 }844 }
@@ -835,12 +863,12 @@ func State(l Layout, p StatePage) templ.Component {
835 }()863 }()
836 }864 }
837 ctx = templ.InitializeContext(ctx)865 ctx = templ.InitializeContext(ctx)
838- templ_7745c5c3_Var38 := templ.GetChildren(ctx)866+ templ_7745c5c3_Var40 := templ.GetChildren(ctx)
839- if templ_7745c5c3_Var38 == nil {867+ if templ_7745c5c3_Var40 == nil {
840- templ_7745c5c3_Var38 = templ.NopComponent868+ templ_7745c5c3_Var40 = templ.NopComponent
841 }869 }
842 ctx = templ.ClearChildren(ctx)870 ctx = templ.ClearChildren(ctx)
843- templ_7745c5c3_Var39 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {871+ templ_7745c5c3_Var41 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
844 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context872 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
845 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)873 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
846 if !templ_7745c5c3_IsBuffer {874 if !templ_7745c5c3_IsBuffer {
@@ -852,51 +880,51 @@ func State(l Layout, p StatePage) templ.Component {
852 }()880 }()
853 }881 }
854 ctx = templ.InitializeContext(ctx)882 ctx = templ.InitializeContext(ctx)
855- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")883+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<section class=\"state\" aria-labelledby=\"state-title\"><p class=\"eyebrow\">")
856 if templ_7745c5c3_Err != nil {884 if templ_7745c5c3_Err != nil {
857 return templ_7745c5c3_Err885 return templ_7745c5c3_Err
858 }886 }
859- var templ_7745c5c3_Var40 string887+ var templ_7745c5c3_Var42 string
860- templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)888+ templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
861 if templ_7745c5c3_Err != nil {889 if templ_7745c5c3_Err != nil {
862- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 183, Col: 30}890+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 185, Col: 30}
863 }891 }
864- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))892+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
865 if templ_7745c5c3_Err != nil {893 if templ_7745c5c3_Err != nil {
866 return templ_7745c5c3_Err894 return templ_7745c5c3_Err
867 }895 }
868- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</p><h1 id=\"state-title\">")896+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</p><h1 id=\"state-title\">")
869 if templ_7745c5c3_Err != nil {897 if templ_7745c5c3_Err != nil {
870 return templ_7745c5c3_Err898 return templ_7745c5c3_Err
871 }899 }
872- var templ_7745c5c3_Var41 string900+ var templ_7745c5c3_Var43 string
873- templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)901+ templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
874 if templ_7745c5c3_Err != nil {902 if templ_7745c5c3_Err != nil {
875- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 184, Col: 33}903+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 186, Col: 33}
876 }904 }
877- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))905+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
878 if templ_7745c5c3_Err != nil {906 if templ_7745c5c3_Err != nil {
879 return templ_7745c5c3_Err907 return templ_7745c5c3_Err
880 }908 }
881- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</h1>")909+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</h1>")
882 if templ_7745c5c3_Err != nil {910 if templ_7745c5c3_Err != nil {
883 return templ_7745c5c3_Err911 return templ_7745c5c3_Err
884 }912 }
885 for _, line := range p.Lines {913 for _, line := range p.Lines {
886- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<p class=\"dim\">")914+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<p class=\"dim\">")
887 if templ_7745c5c3_Err != nil {915 if templ_7745c5c3_Err != nil {
888 return templ_7745c5c3_Err916 return templ_7745c5c3_Err
889 }917 }
890- var templ_7745c5c3_Var42 string918+ var templ_7745c5c3_Var44 string
891- templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(line)919+ templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(line)
892 if templ_7745c5c3_Err != nil {920 if templ_7745c5c3_Err != nil {
893- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 186, Col: 25}921+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 188, Col: 25}
894 }922 }
895- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))923+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
896 if templ_7745c5c3_Err != nil {924 if templ_7745c5c3_Err != nil {
897 return templ_7745c5c3_Err925 return templ_7745c5c3_Err
898 }926 }
899- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</p>")927+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</p>")
900 if templ_7745c5c3_Err != nil {928 if templ_7745c5c3_Err != nil {
901 return templ_7745c5c3_Err929 return templ_7745c5c3_Err
902 }930 }
@@ -907,49 +935,49 @@ func State(l Layout, p StatePage) templ.Component {
907 return templ_7745c5c3_Err935 return templ_7745c5c3_Err
908 }936 }
909 }937 }
910- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<p class=\"state-back\">")938+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"state-back\">")
911 if templ_7745c5c3_Err != nil {939 if templ_7745c5c3_Err != nil {
912 return templ_7745c5c3_Err940 return templ_7745c5c3_Err
913 }941 }
914 for _, b := range p.Back {942 for _, b := range p.Back {
915- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<a href=\"")943+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<a href=\"")
916 if templ_7745c5c3_Err != nil {944 if templ_7745c5c3_Err != nil {
917 return templ_7745c5c3_Err945 return templ_7745c5c3_Err
918 }946 }
919- var templ_7745c5c3_Var43 templ.SafeURL947+ var templ_7745c5c3_Var45 templ.SafeURL
920- templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))948+ templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
921 if templ_7745c5c3_Err != nil {949 if templ_7745c5c3_Err != nil {
922- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 193, Col: 36}950+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 36}
923 }951 }
924- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))952+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
925 if templ_7745c5c3_Err != nil {953 if templ_7745c5c3_Err != nil {
926 return templ_7745c5c3_Err954 return templ_7745c5c3_Err
927 }955 }
928- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\">")956+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\">")
929 if templ_7745c5c3_Err != nil {957 if templ_7745c5c3_Err != nil {
930 return templ_7745c5c3_Err958 return templ_7745c5c3_Err
931 }959 }
932- var templ_7745c5c3_Var44 string960+ var templ_7745c5c3_Var46 string
933- templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)961+ templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
934 if templ_7745c5c3_Err != nil {962 if templ_7745c5c3_Err != nil {
935- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 193, Col: 48}963+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 48}
936 }964 }
937- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))965+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
938 if templ_7745c5c3_Err != nil {966 if templ_7745c5c3_Err != nil {
939 return templ_7745c5c3_Err967 return templ_7745c5c3_Err
940 }968 }
941- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</a>")969+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</a>")
942 if templ_7745c5c3_Err != nil {970 if templ_7745c5c3_Err != nil {
943 return templ_7745c5c3_Err971 return templ_7745c5c3_Err
944 }972 }
945 }973 }
946- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</p></section>")974+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</p></section>")
947 if templ_7745c5c3_Err != nil {975 if templ_7745c5c3_Err != nil {
948 return templ_7745c5c3_Err976 return templ_7745c5c3_Err
949 }977 }
950 return nil978 return nil
951 })979 })
952- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var39), templ_7745c5c3_Buffer)980+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var41), templ_7745c5c3_Buffer)
953 if templ_7745c5c3_Err != nil {981 if templ_7745c5c3_Err != nil {
954 return templ_7745c5c3_Err982 return templ_7745c5c3_Err
955 }983 }
@@ -978,12 +1006,12 @@ func Login(l Layout, p LoginPage) templ.Component {
978 }()1006 }()
979 }1007 }
980 ctx = templ.InitializeContext(ctx)1008 ctx = templ.InitializeContext(ctx)
981- templ_7745c5c3_Var45 := templ.GetChildren(ctx)1009+ templ_7745c5c3_Var47 := templ.GetChildren(ctx)
982- if templ_7745c5c3_Var45 == nil {1010+ if templ_7745c5c3_Var47 == nil {
983- templ_7745c5c3_Var45 = templ.NopComponent1011+ templ_7745c5c3_Var47 = templ.NopComponent
984 }1012 }
985 ctx = templ.ClearChildren(ctx)1013 ctx = templ.ClearChildren(ctx)
986- templ_7745c5c3_Var46 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {1014+ templ_7745c5c3_Var48 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
987 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context1015 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
988 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)1016 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
989 if !templ_7745c5c3_IsBuffer {1017 if !templ_7745c5c3_IsBuffer {
@@ -995,75 +1023,75 @@ func Login(l Layout, p LoginPage) templ.Component {
995 }()1023 }()
996 }1024 }
997 ctx = templ.InitializeContext(ctx)1025 ctx = templ.InitializeContext(ctx)
998- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")1026+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
999 if templ_7745c5c3_Err != nil {1027 if templ_7745c5c3_Err != nil {
1000 return templ_7745c5c3_Err1028 return templ_7745c5c3_Err
1001 }1029 }
1002- var templ_7745c5c3_Var47 string1030+ var templ_7745c5c3_Var49 string
1003- templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)1031+ templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
1004 if templ_7745c5c3_Err != nil {1032 if templ_7745c5c3_Err != nil {
1005- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 208, Col: 33}1033+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 210, Col: 33}
1006 }1034 }
1007- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))1035+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
1008 if templ_7745c5c3_Err != nil {1036 if templ_7745c5c3_Err != nil {
1009 return templ_7745c5c3_Err1037 return templ_7745c5c3_Err
1010 }1038 }
1011- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</h1>")1039+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</h1>")
1012 if templ_7745c5c3_Err != nil {1040 if templ_7745c5c3_Err != nil {
1013 return templ_7745c5c3_Err1041 return templ_7745c5c3_Err
1014 }1042 }
1015 for _, line := range p.Lines {1043 for _, line := range p.Lines {
1016- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<p class=\"dim\">")1044+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<p class=\"dim\">")
1017 if templ_7745c5c3_Err != nil {1045 if templ_7745c5c3_Err != nil {
1018 return templ_7745c5c3_Err1046 return templ_7745c5c3_Err
1019 }1047 }
1020- var templ_7745c5c3_Var48 string1048+ var templ_7745c5c3_Var50 string
1021- templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(line)1049+ templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(line)
1022 if templ_7745c5c3_Err != nil {1050 if templ_7745c5c3_Err != nil {
1023- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 210, Col: 25}1051+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 212, Col: 25}
1024 }1052 }
1025- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))1053+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
1026 if templ_7745c5c3_Err != nil {1054 if templ_7745c5c3_Err != nil {
1027 return templ_7745c5c3_Err1055 return templ_7745c5c3_Err
1028 }1056 }
1029- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</p>")1057+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</p>")
1030 if templ_7745c5c3_Err != nil {1058 if templ_7745c5c3_Err != nil {
1031 return templ_7745c5c3_Err1059 return templ_7745c5c3_Err
1032 }1060 }
1033 }1061 }
1034- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<form class=\"state-act\" method=\"post\" action=\"")1062+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<form class=\"state-act\" method=\"post\" action=\"")
1035 if templ_7745c5c3_Err != nil {1063 if templ_7745c5c3_Err != nil {
1036 return templ_7745c5c3_Err1064 return templ_7745c5c3_Err
1037 }1065 }
1038- var templ_7745c5c3_Var49 templ.SafeURL1066+ var templ_7745c5c3_Var51 templ.SafeURL
1039- templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))1067+ templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
1040 if templ_7745c5c3_Err != nil {1068 if templ_7745c5c3_Err != nil {
1041- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 212, Col: 73}1069+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 214, Col: 73}
1042 }1070 }
1043- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))1071+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
1044 if templ_7745c5c3_Err != nil {1072 if templ_7745c5c3_Err != nil {
1045 return templ_7745c5c3_Err1073 return templ_7745c5c3_Err
1046 }1074 }
1047- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "\" data-autosubmit><button type=\"submit\" class=\"btn primary\">")1075+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "\" data-autosubmit><button type=\"submit\" class=\"btn primary\">")
1048 if templ_7745c5c3_Err != nil {1076 if templ_7745c5c3_Err != nil {
1049 return templ_7745c5c3_Err1077 return templ_7745c5c3_Err
1050 }1078 }
1051- var templ_7745c5c3_Var50 string1079+ var templ_7745c5c3_Var52 string
1052- templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)1080+ templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
1053 if templ_7745c5c3_Err != nil {1081 if templ_7745c5c3_Err != nil {
1054- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 213, Col: 56}1082+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 215, Col: 56}
1055 }1083 }
1056- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))1084+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
1057 if templ_7745c5c3_Err != nil {1085 if templ_7745c5c3_Err != nil {
1058 return templ_7745c5c3_Err1086 return templ_7745c5c3_Err
1059 }1087 }
1060- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")1088+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</button></form><p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
1061 if templ_7745c5c3_Err != nil {1089 if templ_7745c5c3_Err != nil {
1062 return templ_7745c5c3_Err1090 return templ_7745c5c3_Err
1063 }1091 }
1064 return nil1092 return nil
1065 })1093 })
1066- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var46), templ_7745c5c3_Buffer)1094+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var48), templ_7745c5c3_Buffer)
1067 if templ_7745c5c3_Err != nil {1095 if templ_7745c5c3_Err != nil {
1068 return templ_7745c5c3_Err1096 return templ_7745c5c3_Err
1069 }1097 }
@@ -1089,12 +1117,12 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component {
1089 }()1117 }()
1090 }1118 }
1091 ctx = templ.InitializeContext(ctx)1119 ctx = templ.InitializeContext(ctx)
1092- templ_7745c5c3_Var51 := templ.GetChildren(ctx)1120+ templ_7745c5c3_Var53 := templ.GetChildren(ctx)
1093- if templ_7745c5c3_Var51 == nil {1121+ if templ_7745c5c3_Var53 == nil {
1094- templ_7745c5c3_Var51 = templ.NopComponent1122+ templ_7745c5c3_Var53 = templ.NopComponent
1095 }1123 }
1096 ctx = templ.ClearChildren(ctx)1124 ctx = templ.ClearChildren(ctx)
1097- templ_7745c5c3_Var52 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {1125+ templ_7745c5c3_Var54 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1098 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context1126 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
1099 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)1127 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
1100 if !templ_7745c5c3_IsBuffer {1128 if !templ_7745c5c3_IsBuffer {
@@ -1106,88 +1134,88 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component {
1106 }()1134 }()
1107 }1135 }
1108 ctx = templ.InitializeContext(ctx)1136 ctx = templ.InitializeContext(ctx)
1109- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")1137+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<section class=\"state\" aria-labelledby=\"state-title\"><h1 id=\"state-title\">")
1110 if templ_7745c5c3_Err != nil {1138 if templ_7745c5c3_Err != nil {
1111 return templ_7745c5c3_Err1139 return templ_7745c5c3_Err
1112 }1140 }
1113- var templ_7745c5c3_Var53 string1141+ var templ_7745c5c3_Var55 string
1114- templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)1142+ templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
1115 if templ_7745c5c3_Err != nil {1143 if templ_7745c5c3_Err != nil {
1116- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 225, Col: 33}1144+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 33}
1117 }1145 }
1118- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))1146+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
1119 if templ_7745c5c3_Err != nil {1147 if templ_7745c5c3_Err != nil {
1120 return templ_7745c5c3_Err1148 return templ_7745c5c3_Err
1121 }1149 }
1122- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</h1><form class=\"state-act\" data-js data-passkey-form><button type=\"submit\" class=\"btn primary\" data-passkey=\"login\" data-begin=\"")1150+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "</h1><form class=\"state-act\" data-js data-passkey-form><button type=\"submit\" class=\"btn primary\" data-passkey=\"login\" data-begin=\"")
1123 if templ_7745c5c3_Err != nil {1151 if templ_7745c5c3_Err != nil {
1124 return templ_7745c5c3_Err1152 return templ_7745c5c3_Err
1125 }1153 }
1126- var templ_7745c5c3_Var54 string1154+ var templ_7745c5c3_Var56 string
1127- templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)1155+ templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
1128 if templ_7745c5c3_Err != nil {1156 if templ_7745c5c3_Err != nil {
1129- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 87}1157+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 87}
1130 }1158 }
1131- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54)1159+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)
1132 if templ_7745c5c3_Err != nil {1160 if templ_7745c5c3_Err != nil {
1133 return templ_7745c5c3_Err1161 return templ_7745c5c3_Err
1134 }1162 }
1135- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "\" data-finish=\"")1163+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" data-finish=\"")
1136 if templ_7745c5c3_Err != nil {1164 if templ_7745c5c3_Err != nil {
1137 return templ_7745c5c3_Err1165 return templ_7745c5c3_Err
1138 }1166 }
1139- var templ_7745c5c3_Var55 string1167+ var templ_7745c5c3_Var57 string
1140- templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)1168+ templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
1141 if templ_7745c5c3_Err != nil {1169 if templ_7745c5c3_Err != nil {
1142- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 112}1170+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 112}
1143 }1171 }
1144- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var55)1172+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
1145 if templ_7745c5c3_Err != nil {1173 if templ_7745c5c3_Err != nil {
1146 return templ_7745c5c3_Err1174 return templ_7745c5c3_Err
1147 }1175 }
1148- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\">")1176+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\">")
1149 if templ_7745c5c3_Err != nil {1177 if templ_7745c5c3_Err != nil {
1150 return templ_7745c5c3_Err1178 return templ_7745c5c3_Err
1151 }1179 }
1152- var templ_7745c5c3_Var56 string1180+ var templ_7745c5c3_Var58 string
1153- templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)1181+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
1154 if templ_7745c5c3_Err != nil {1182 if templ_7745c5c3_Err != nil {
1155- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 125}1183+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 125}
1156 }1184 }
1157- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))1185+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
1158 if templ_7745c5c3_Err != nil {1186 if templ_7745c5c3_Err != nil {
1159 return templ_7745c5c3_Err1187 return templ_7745c5c3_Err
1160 }1188 }
1161- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</button></form><p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")1189+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</button></form><p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")
1162 if templ_7745c5c3_Err != nil {1190 if templ_7745c5c3_Err != nil {
1163 return templ_7745c5c3_Err1191 return templ_7745c5c3_Err
1164 }1192 }
1165 for _, line := range p.Lines {1193 for _, line := range p.Lines {
1166- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<p class=\"dim\">")1194+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<p class=\"dim\">")
1167 if templ_7745c5c3_Err != nil {1195 if templ_7745c5c3_Err != nil {
1168 return templ_7745c5c3_Err1196 return templ_7745c5c3_Err
1169 }1197 }
1170- var templ_7745c5c3_Var57 string1198+ var templ_7745c5c3_Var59 string
1171- templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(line)1199+ templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(line)
1172 if templ_7745c5c3_Err != nil {1200 if templ_7745c5c3_Err != nil {
1173- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 231, Col: 25}1201+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 25}
1174 }1202 }
1175- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))1203+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
1176 if templ_7745c5c3_Err != nil {1204 if templ_7745c5c3_Err != nil {
1177 return templ_7745c5c3_Err1205 return templ_7745c5c3_Err
1178 }1206 }
1179- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</p>")1207+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</p>")
1180 if templ_7745c5c3_Err != nil {1208 if templ_7745c5c3_Err != nil {
1181 return templ_7745c5c3_Err1209 return templ_7745c5c3_Err
1182 }1210 }
1183 }1211 }
1184- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")1212+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<p class=\"state-back\"><a href=\"/\">Back to home</a></p></section>")
1185 if templ_7745c5c3_Err != nil {1213 if templ_7745c5c3_Err != nil {
1186 return templ_7745c5c3_Err1214 return templ_7745c5c3_Err
1187 }1215 }
1188 return nil1216 return nil
1189 })1217 })
1190- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var52), templ_7745c5c3_Buffer)1218+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var54), templ_7745c5c3_Buffer)
1191 if templ_7745c5c3_Err != nil {1219 if templ_7745c5c3_Err != nil {
1192 return templ_7745c5c3_Err1220 return templ_7745c5c3_Err
1193 }1221 }
@@ -1212,12 +1240,12 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component {
1212 }()1240 }()
1213 }1241 }
1214 ctx = templ.InitializeContext(ctx)1242 ctx = templ.InitializeContext(ctx)
1215- templ_7745c5c3_Var58 := templ.GetChildren(ctx)1243+ templ_7745c5c3_Var60 := templ.GetChildren(ctx)
1216- if templ_7745c5c3_Var58 == nil {1244+ if templ_7745c5c3_Var60 == nil {
1217- templ_7745c5c3_Var58 = templ.NopComponent1245+ templ_7745c5c3_Var60 = templ.NopComponent
1218 }1246 }
1219 ctx = templ.ClearChildren(ctx)1247 ctx = templ.ClearChildren(ctx)
1220- templ_7745c5c3_Var59 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {1248+ templ_7745c5c3_Var61 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1221 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context1249 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
1222 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)1250 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
1223 if !templ_7745c5c3_IsBuffer {1251 if !templ_7745c5c3_IsBuffer {
@@ -1229,231 +1257,231 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component {
1229 }()1257 }()
1230 }1258 }
1231 ctx = templ.InitializeContext(ctx)1259 ctx = templ.InitializeContext(ctx)
1232- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<h2 class=\"rule-h\"><span>Passkeys</span><span class=\"count\">")1260+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<h2 class=\"rule-h\"><span>Passkeys</span><span class=\"count\">")
1233 if templ_7745c5c3_Err != nil {1261 if templ_7745c5c3_Err != nil {
1234 return templ_7745c5c3_Err1262 return templ_7745c5c3_Err
1235 }1263 }
1236- var templ_7745c5c3_Var60 string1264+ var templ_7745c5c3_Var62 string
1237- templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows)))1265+ templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows)))
1238 if templ_7745c5c3_Err != nil {1266 if templ_7745c5c3_Err != nil {
1239- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 241, Col: 81}1267+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 243, Col: 81}
1240 }1268 }
1241- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))1269+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
1242 if templ_7745c5c3_Err != nil {1270 if templ_7745c5c3_Err != nil {
1243 return templ_7745c5c3_Err1271 return templ_7745c5c3_Err
1244 }1272 }
1245- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</span></h2><p class=\"dim\">A passkey signs you in from this device or your phone, without a terminal. Removing one signs every browser out.</p>")1273+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</span></h2><p class=\"dim\">A passkey signs you in from this device or your phone, without a terminal. Removing one signs every browser out.</p>")
1246 if templ_7745c5c3_Err != nil {1274 if templ_7745c5c3_Err != nil {
1247 return templ_7745c5c3_Err1275 return templ_7745c5c3_Err
1248 }1276 }
1249 if len(p.Rows) > 0 {1277 if len(p.Rows) > 0 {
1250- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<table class=\"list passkeys\" aria-label=\"Passkeys\"><thead><tr><th class=\"c-name\">Name</th><th class=\"c-when\">Created</th><th class=\"c-when\">Last used</th><th class=\"c-sync\">Synced</th><th class=\"c-act\"><span class=\"sr\">Remove</span></th></tr></thead> <tbody>")1278+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<table class=\"list passkeys\" aria-label=\"Passkeys\"><thead><tr><th class=\"c-name\">Name</th><th class=\"c-when\">Created</th><th class=\"c-when\">Last used</th><th class=\"c-sync\">Synced</th><th class=\"c-act\"><span class=\"sr\">Remove</span></th></tr></thead> <tbody>")
1251 if templ_7745c5c3_Err != nil {1279 if templ_7745c5c3_Err != nil {
1252 return templ_7745c5c3_Err1280 return templ_7745c5c3_Err
1253 }1281 }
1254 for _, r := range p.Rows {1282 for _, r := range p.Rows {
1255- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<tr><td class=\"c-name\">")1283+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<tr><td class=\"c-name\">")
1256 if templ_7745c5c3_Err != nil {1284 if templ_7745c5c3_Err != nil {
1257 return templ_7745c5c3_Err1285 return templ_7745c5c3_Err
1258 }1286 }
1259- var templ_7745c5c3_Var61 string1287+ var templ_7745c5c3_Var63 string
1260- templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)1288+ templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
1261 if templ_7745c5c3_Err != nil {1289 if templ_7745c5c3_Err != nil {
1262- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 257, Col: 34}1290+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 259, Col: 34}
1263 }1291 }
1264- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))1292+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
1265 if templ_7745c5c3_Err != nil {1293 if templ_7745c5c3_Err != nil {
1266 return templ_7745c5c3_Err1294 return templ_7745c5c3_Err
1267 }1295 }
1268- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</td><td class=\"c-when nowrap\"><time")1296+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</td><td class=\"c-when nowrap\"><time")
1269 if templ_7745c5c3_Err != nil {1297 if templ_7745c5c3_Err != nil {
1270 return templ_7745c5c3_Err1298 return templ_7745c5c3_Err
1271 }1299 }
1272 if r.CreatedAt != "" {1300 if r.CreatedAt != "" {
1273- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, " datetime=\"")1301+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, " datetime=\"")
1274 if templ_7745c5c3_Err != nil {1302 if templ_7745c5c3_Err != nil {
1275 return templ_7745c5c3_Err1303 return templ_7745c5c3_Err
1276 }1304 }
1277- var templ_7745c5c3_Var62 string1305+ var templ_7745c5c3_Var64 string
1278- templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt)1306+ templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt)
1279 if templ_7745c5c3_Err != nil {1307 if templ_7745c5c3_Err != nil {
1280- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 261, Col: 32}1308+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 263, Col: 32}
1281 }1309 }
1282- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var62)1310+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
1283 if templ_7745c5c3_Err != nil {1311 if templ_7745c5c3_Err != nil {
1284 return templ_7745c5c3_Err1312 return templ_7745c5c3_Err
1285 }1313 }
1286- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\"")1314+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\"")
1287 if templ_7745c5c3_Err != nil {1315 if templ_7745c5c3_Err != nil {
1288 return templ_7745c5c3_Err1316 return templ_7745c5c3_Err
1289 }1317 }
1290 }1318 }
1291- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, ">")1319+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, ">")
1292 if templ_7745c5c3_Err != nil {1320 if templ_7745c5c3_Err != nil {
1293 return templ_7745c5c3_Err1321 return templ_7745c5c3_Err
1294 }1322 }
1295- var templ_7745c5c3_Var63 string1323+ var templ_7745c5c3_Var65 string
1296- templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created)1324+ templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created)
1297 if templ_7745c5c3_Err != nil {1325 if templ_7745c5c3_Err != nil {
1298- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 263, Col: 20}1326+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 265, Col: 20}
1299 }1327 }
1300- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))1328+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
1301 if templ_7745c5c3_Err != nil {1329 if templ_7745c5c3_Err != nil {
1302 return templ_7745c5c3_Err1330 return templ_7745c5c3_Err
1303 }1331 }
1304- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</time></td><td class=\"c-when nowrap\"><time")1332+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</time></td><td class=\"c-when nowrap\"><time")
1305 if templ_7745c5c3_Err != nil {1333 if templ_7745c5c3_Err != nil {
1306 return templ_7745c5c3_Err1334 return templ_7745c5c3_Err
1307 }1335 }
1308 if r.LastUsedAt != "" {1336 if r.LastUsedAt != "" {
1309- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, " datetime=\"")1337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, " datetime=\"")
1310 if templ_7745c5c3_Err != nil {1338 if templ_7745c5c3_Err != nil {
1311 return templ_7745c5c3_Err1339 return templ_7745c5c3_Err
1312 }1340 }
1313- var templ_7745c5c3_Var64 string1341+ var templ_7745c5c3_Var66 string
1314- templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt)1342+ templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt)
1315 if templ_7745c5c3_Err != nil {1343 if templ_7745c5c3_Err != nil {
1316- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 268, Col: 33}1344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 270, Col: 33}
1317 }1345 }
1318- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)1346+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
1319 if templ_7745c5c3_Err != nil {1347 if templ_7745c5c3_Err != nil {
1320 return templ_7745c5c3_Err1348 return templ_7745c5c3_Err
1321 }1349 }
1322- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\"")1350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\"")
1323 if templ_7745c5c3_Err != nil {1351 if templ_7745c5c3_Err != nil {
1324 return templ_7745c5c3_Err1352 return templ_7745c5c3_Err
1325 }1353 }
1326 }1354 }
1327- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, ">")1355+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, ">")
1328 if templ_7745c5c3_Err != nil {1356 if templ_7745c5c3_Err != nil {
1329 return templ_7745c5c3_Err1357 return templ_7745c5c3_Err
1330 }1358 }
1331- var templ_7745c5c3_Var65 string1359+ var templ_7745c5c3_Var67 string
1332- templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed)1360+ templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed)
1333 if templ_7745c5c3_Err != nil {1361 if templ_7745c5c3_Err != nil {
1334- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 270, Col: 21}1362+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 272, Col: 21}
1335 }1363 }
1336- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))1364+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
1337 if templ_7745c5c3_Err != nil {1365 if templ_7745c5c3_Err != nil {
1338 return templ_7745c5c3_Err1366 return templ_7745c5c3_Err
1339 }1367 }
1340- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</time></td><td class=\"c-sync\">")1368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</time></td><td class=\"c-sync\">")
1341 if templ_7745c5c3_Err != nil {1369 if templ_7745c5c3_Err != nil {
1342 return templ_7745c5c3_Err1370 return templ_7745c5c3_Err
1343 }1371 }
1344 if r.Synced {1372 if r.Synced {
1345- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "yes")1373+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "yes")
1346 if templ_7745c5c3_Err != nil {1374 if templ_7745c5c3_Err != nil {
1347 return templ_7745c5c3_Err1375 return templ_7745c5c3_Err
1348 }1376 }
1349 } else {1377 } else {
1350- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "no")1378+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "no")
1351 if templ_7745c5c3_Err != nil {1379 if templ_7745c5c3_Err != nil {
1352 return templ_7745c5c3_Err1380 return templ_7745c5c3_Err
1353 }1381 }
1354 }1382 }
1355- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</td><td class=\"c-act\"><form method=\"post\" action=\"")1383+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</td><td class=\"c-act\"><form method=\"post\" action=\"")
1356 if templ_7745c5c3_Err != nil {1384 if templ_7745c5c3_Err != nil {
1357 return templ_7745c5c3_Err1385 return templ_7745c5c3_Err
1358 }1386 }
1359- var templ_7745c5c3_Var66 templ.SafeURL1387+ var templ_7745c5c3_Var68 templ.SafeURL
1360- templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove))1388+ templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove))
1361 if templ_7745c5c3_Err != nil {1389 if templ_7745c5c3_Err != nil {
1362- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 280, Col: 60}1390+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 282, Col: 60}
1363 }1391 }
1364- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))1392+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
1365 if templ_7745c5c3_Err != nil {1393 if templ_7745c5c3_Err != nil {
1366 return templ_7745c5c3_Err1394 return templ_7745c5c3_Err
1367 }1395 }
1368- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "\"><button type=\"submit\" class=\"btn danger\" aria-label=\"")1396+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "\"><button type=\"submit\" class=\"btn danger\" aria-label=\"")
1369 if templ_7745c5c3_Err != nil {1397 if templ_7745c5c3_Err != nil {
1370 return templ_7745c5c3_Err1398 return templ_7745c5c3_Err
1371 }1399 }
1372- var templ_7745c5c3_Var67 string1400+ var templ_7745c5c3_Var69 string
1373- templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name)1401+ templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name)
1374 if templ_7745c5c3_Err != nil {1402 if templ_7745c5c3_Err != nil {
1375- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 281, Col: 89}1403+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 283, Col: 89}
1376 }1404 }
1377- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var67)1405+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
1378 if templ_7745c5c3_Err != nil {1406 if templ_7745c5c3_Err != nil {
1379 return templ_7745c5c3_Err1407 return templ_7745c5c3_Err
1380 }1408 }
1381- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "\">Remove</button></form></td></tr>")1409+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">Remove</button></form></td></tr>")
1382 if templ_7745c5c3_Err != nil {1410 if templ_7745c5c3_Err != nil {
1383 return templ_7745c5c3_Err1411 return templ_7745c5c3_Err
1384 }1412 }
1385 }1413 }
1386- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</tbody></table>")1414+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</tbody></table>")
1387 if templ_7745c5c3_Err != nil {1415 if templ_7745c5c3_Err != nil {
1388 return templ_7745c5c3_Err1416 return templ_7745c5c3_Err
1389 }1417 }
1390 } else {1418 } else {
1391- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "<p class=\"lede dim\">no passkeys yet.</p>")1419+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<p class=\"lede dim\">no passkeys yet.</p>")
1392 if templ_7745c5c3_Err != nil {1420 if templ_7745c5c3_Err != nil {
1393 return templ_7745c5c3_Err1421 return templ_7745c5c3_Err
1394 }1422 }
1395 }1423 }
1396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, " ")1424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, " ")
1397 if templ_7745c5c3_Err != nil {1425 if templ_7745c5c3_Err != nil {
1398 return templ_7745c5c3_Err1426 return templ_7745c5c3_Err
1399 }1427 }
1400 if p.Full {1428 if p.Full {
1401- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<p class=\"dim\">")1429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<p class=\"dim\">")
1402 if templ_7745c5c3_Err != nil {1430 if templ_7745c5c3_Err != nil {
1403 return templ_7745c5c3_Err1431 return templ_7745c5c3_Err
1404 }1432 }
1405- var templ_7745c5c3_Var68 string1433+ var templ_7745c5c3_Var70 string
1406- templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max))1434+ templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max))
1407 if templ_7745c5c3_Err != nil {1435 if templ_7745c5c3_Err != nil {
1408- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 292, Col: 31}1436+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 294, Col: 31}
1409 }1437 }
1410- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))1438+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
1411 if templ_7745c5c3_Err != nil {1439 if templ_7745c5c3_Err != nil {
1412 return templ_7745c5c3_Err1440 return templ_7745c5c3_Err
1413 }1441 }
1414- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, " passkeys are registered, the most there can be. Remove one to add another.</p>")1442+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, " passkeys are registered, the most there can be. Remove one to add another.</p>")
1415 if templ_7745c5c3_Err != nil {1443 if templ_7745c5c3_Err != nil {
1416 return templ_7745c5c3_Err1444 return templ_7745c5c3_Err
1417 }1445 }
1418 } else {1446 } else {
1419- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<form class=\"passkey-add\" data-js data-passkey-form><label for=\"passkey-name\" class=\"sr\">Name of the new passkey</label> <input id=\"passkey-name\" type=\"text\" placeholder=\"Name, for example Pixel 9\" autocomplete=\"off\" data-passkey-name> <button type=\"submit\" class=\"btn primary\" data-passkey=\"register\" data-begin=\"")1447+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<form class=\"passkey-add\" data-js data-passkey-form><label for=\"passkey-name\" class=\"sr\">Name of the new passkey</label> <input id=\"passkey-name\" type=\"text\" placeholder=\"Name, for example Pixel 9\" autocomplete=\"off\" data-passkey-name> <button type=\"submit\" class=\"btn primary\" data-passkey=\"register\" data-begin=\"")
1420 if templ_7745c5c3_Err != nil {1448 if templ_7745c5c3_Err != nil {
1421 return templ_7745c5c3_Err1449 return templ_7745c5c3_Err
1422 }1450 }
1423- var templ_7745c5c3_Var69 string1451+ var templ_7745c5c3_Var71 string
1424- templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)1452+ templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
1425 if templ_7745c5c3_Err != nil {1453 if templ_7745c5c3_Err != nil {
1426- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 297, Col: 90}1454+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 90}
1427 }1455 }
1428- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)1456+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)
1429 if templ_7745c5c3_Err != nil {1457 if templ_7745c5c3_Err != nil {
1430 return templ_7745c5c3_Err1458 return templ_7745c5c3_Err
1431 }1459 }
1432- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "\" data-finish=\"")1460+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" data-finish=\"")
1433 if templ_7745c5c3_Err != nil {1461 if templ_7745c5c3_Err != nil {
1434 return templ_7745c5c3_Err1462 return templ_7745c5c3_Err
1435 }1463 }
1436- var templ_7745c5c3_Var70 string1464+ var templ_7745c5c3_Var72 string
1437- templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)1465+ templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
1438 if templ_7745c5c3_Err != nil {1466 if templ_7745c5c3_Err != nil {
1439- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 297, Col: 115}1467+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 115}
1440 }1468 }
1441- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var70)1469+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
1442 if templ_7745c5c3_Err != nil {1470 if templ_7745c5c3_Err != nil {
1443 return templ_7745c5c3_Err1471 return templ_7745c5c3_Err
1444 }1472 }
1445- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "\">Add a passkey</button></form>")1473+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\">Add a passkey</button></form>")
1446 if templ_7745c5c3_Err != nil {1474 if templ_7745c5c3_Err != nil {
1447 return templ_7745c5c3_Err1475 return templ_7745c5c3_Err
1448 }1476 }
1449 }1477 }
1450- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, " <p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")1478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, " <p class=\"dim\" role=\"status\" aria-live=\"polite\" data-passkey-status></p>")
1451 if templ_7745c5c3_Err != nil {1479 if templ_7745c5c3_Err != nil {
1452 return templ_7745c5c3_Err1480 return templ_7745c5c3_Err
1453 }1481 }
1454 return nil1482 return nil
1455 })1483 })
1456- templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var59), templ_7745c5c3_Buffer)1484+ templ_7745c5c3_Err = Shell(l, nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var61), templ_7745c5c3_Buffer)
1457 if templ_7745c5c3_Err != nil {1485 if templ_7745c5c3_Err != nil {
1458 return templ_7745c5c3_Err1486 return templ_7745c5c3_Err
1459 }1487 }
M internal/adapters/web/static/js/ermite.js +5 -1
@@ -820,7 +820,8 @@
820820 // internal/adapters/web/events.go). A fragment is a top-level element with an
821821 // id: it replaces the element of the page that has the same id. A fragment
822822 // marked data-gone removes it; a row of a repository the home page does not
823-// list yet is added at the top of the list. A slot (data-slot: the place of a
823+// list yet is added at the top of the list. Activity updates also move existing
824+// home rows to the top when sorted by newest activity; CI updates stay put. A slot (data-slot: the place of a
824825 // banner, a persistent status region) keeps its element and only gets the
825826 // children of the fragment, so that screen readers announce the change. A gap,
826827 // which says the page may have missed events or that what it shows changed,
@@ -919,6 +920,9 @@ window.ermiteLive = (() => {
919920 }
920921 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');
921922 old.replaceWith(el);
923+ const rows = document.getElementById('repo-rows');
924+ if (rows && el.parentElement === rows && rows.dataset.sort === 'activity' &&
925+ rows.dataset.desc === 'true' && el.hasAttribute('data-activity')) rows.prepend(el);
922926 window.htmx?.process(el);
923927 fitIn(el);
924928 if (had) refocus(el, had);
@@ -820,7 +820,8 @@
820 // internal/adapters/web/events.go). A fragment is a top-level element with an820 // internal/adapters/web/events.go). A fragment is a top-level element with an
821 // id: it replaces the element of the page that has the same id. A fragment821 // id: it replaces the element of the page that has the same id. A fragment
822 // marked data-gone removes it; a row of a repository the home page does not822 // marked data-gone removes it; a row of a repository the home page does not
823-// list yet is added at the top of the list. A slot (data-slot: the place of a823+// list yet is added at the top of the list. Activity updates also move existing
824+// home rows to the top when sorted by newest activity; CI updates stay put. A slot (data-slot: the place of a
824 // banner, a persistent status region) keeps its element and only gets the825 // banner, a persistent status region) keeps its element and only gets the
825 // children of the fragment, so that screen readers announce the change. A gap,826 // children of the fragment, so that screen readers announce the change. A gap,
826 // which says the page may have missed events or that what it shows changed,827 // which says the page may have missed events or that what it shows changed,
@@ -919,6 +920,9 @@ window.ermiteLive = (() => {
919 }920 }
920 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');921 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');
921 old.replaceWith(el);922 old.replaceWith(el);
923+ const rows = document.getElementById('repo-rows');
924+ if (rows && el.parentElement === rows && rows.dataset.sort === 'activity' &&
925+ rows.dataset.desc === 'true' && el.hasAttribute('data-activity')) rows.prepend(el);
922 window.htmx?.process(el);926 window.htmx?.process(el);
923 fitIn(el);927 fitIn(el);
924 if (had) refocus(el, had);928 if (had) refocus(el, had);
M internal/adapters/web/view.go +3 -1
@@ -96,7 +96,7 @@ type HomeRow struct {
9696 Stats *HomeStats
9797 // Private marks a private repository; only set for a signed-in visitor.
9898 Private bool
99- // Fresh marks a row that a push just updated (live updates only).
99+ // Fresh marks a row that activity just updated (live updates only).
100100 Fresh bool
101101 // Commits are the newest of the default branch. NoCommits says there is
102102 // none (never pushed, or an empty branch); a failed read says nothing.
@@ -138,6 +138,8 @@ type SortLink struct {
138138
139139 // HomePage is the repository list.
140140 type HomePage struct {
141+ Sort string
142+ Desc bool
141143 Sorts []SortLink
142144 Rows []HomeRow
143145 }
@@ -96,7 +96,7 @@ type HomeRow struct {
96 Stats *HomeStats96 Stats *HomeStats
97 // Private marks a private repository; only set for a signed-in visitor.97 // Private marks a private repository; only set for a signed-in visitor.
98 Private bool98 Private bool
99- // Fresh marks a row that a push just updated (live updates only).99+ // Fresh marks a row that activity just updated (live updates only).
100 Fresh bool100 Fresh bool
101 // Commits are the newest of the default branch. NoCommits says there is101 // Commits are the newest of the default branch. NoCommits says there is
102 // none (never pushed, or an empty branch); a failed read says nothing.102 // none (never pushed, or an empty branch); a failed read says nothing.
@@ -138,6 +138,8 @@ type SortLink struct {
138 138
139 // HomePage is the repository list.139 // HomePage is the repository list.
140 type HomePage struct {140 type HomePage struct {
141+ Sort string
142+ Desc bool
141 Sorts []SortLink143 Sorts []SortLink
142 Rows []HomeRow144 Rows []HomeRow
143 }145 }
M internal/app/testdata/web/home.html +32 -30
@@ -61,7 +61,7 @@
6161 </div>
6262 <nav class="sort" aria-label="Sort repositories">
6363 <span class="mute">sort</span>
64-<a href="/?sort=push&amp;dir=asc" aria-current="true">last push <span aria-hidden="true">▼</span>
64+<a href="/?sort=activity&amp;dir=asc" aria-current="true">last activity <span aria-hidden="true">▼</span>
6565 <span class="sr">, descending, select for ascending</span>
6666 </a>
6767 <a href="/?sort=name&amp;dir=asc">name </a>
@@ -81,8 +81,31 @@
8181 </button>
8282 </div>
8383 </div>
84-<ul class="repos" id="repo-rows" data-rows>
85-<li id="repo-demo" class="repo" data-cursor>
84+<ul class="repos" id="repo-rows" data-rows data-sort="activity" data-desc="true">
85+<li id="repo-quiet" class="repo" data-cursor>
86+<div class="repo-main">
87+<h2 class="repo-name">
88+<a href="/quiet">quiet</a>
89+</h2>
90+<p class="repo-desc mute">no description</p>
91+<div class="repo-topics" aria-hidden="true">
92+</div>
93+<div class="repo-pulse" aria-hidden="true">
94+</div>
95+</div>
96+<div class="repo-head">
97+<dl class="repo-stats" aria-label="Figures of quiet">
98+<div class="rs-active">
99+<dt>last activity</dt>
100+<dd>
101+<time datetime="STAMP" title="STAMP">AGO</time>
102+</dd>
103+</div>
104+</dl>
105+</div>
106+<p class="repo-commits mute">no commits yet</p>
107+</li>
108+<li id="repo-demo" class="repo">
86109 <div class="repo-main">
87110 <h2 class="repo-name">
88111 <a href="/demo">demo</a>
@@ -148,27 +171,6 @@
148171 </li>
149172 </ul>
150173 </li>
151-<li id="repo-quiet" class="repo">
152-<div class="repo-main">
153-<h2 class="repo-name">
154-<a href="/quiet">quiet</a>
155-</h2>
156-<p class="repo-desc mute">no description</p>
157-<div class="repo-topics" aria-hidden="true">
158-</div>
159-<div class="repo-pulse" aria-hidden="true">
160-</div>
161-</div>
162-<div class="repo-head">
163-<dl class="repo-stats" aria-label="Figures of quiet">
164-<div class="rs-active">
165-<dt>last activity</dt>
166-<dd class="mute">never</dd>
167-</div>
168-</dl>
169-</div>
170-<p class="repo-commits mute">no commits yet</p>
171-</li>
172174 </ul>
173175 </section>
174176 </div>
@@ -343,15 +345,15 @@
343345 <span class="k">go</span>
344346 </a>
345347 </li>
346-<li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo">
347-<a href="/demo" tabindex="-1">
348-<span class="lbl">demo</span>
348+<li id="cmd-o1" role="option" aria-selected="false" data-k="repo quiet">
349+<a href="/quiet" tabindex="-1">
350+<span class="lbl">quiet</span>
349351 <span class="k">repo</span>
350352 </a>
351353 </li>
352-<li id="cmd-o2" role="option" aria-selected="false" data-k="repo quiet">
353-<a href="/quiet" tabindex="-1">
354-<span class="lbl">quiet</span>
354+<li id="cmd-o2" role="option" aria-selected="false" data-k="repo demo">
355+<a href="/demo" tabindex="-1">
356+<span class="lbl">demo</span>
355357 <span class="k">repo</span>
356358 </a>
357359 </li>
@@ -61,7 +61,7 @@
61 </div>61 </div>
62 <nav class="sort" aria-label="Sort repositories">62 <nav class="sort" aria-label="Sort repositories">
63 <span class="mute">sort</span>63 <span class="mute">sort</span>
64-<a href="/?sort=push&amp;dir=asc" aria-current="true">last push <span aria-hidden="true">▼</span>64+<a href="/?sort=activity&amp;dir=asc" aria-current="true">last activity <span aria-hidden="true">▼</span>
65 <span class="sr">, descending, select for ascending</span>65 <span class="sr">, descending, select for ascending</span>
66 </a>66 </a>
67 <a href="/?sort=name&amp;dir=asc">name </a>67 <a href="/?sort=name&amp;dir=asc">name </a>
@@ -81,8 +81,31 @@
81 </button>81 </button>
82 </div>82 </div>
83 </div>83 </div>
84-<ul class="repos" id="repo-rows" data-rows>84+<ul class="repos" id="repo-rows" data-rows data-sort="activity" data-desc="true">
85-<li id="repo-demo" class="repo" data-cursor>85+<li id="repo-quiet" class="repo" data-cursor>
86+<div class="repo-main">
87+<h2 class="repo-name">
88+<a href="/quiet">quiet</a>
89+</h2>
90+<p class="repo-desc mute">no description</p>
91+<div class="repo-topics" aria-hidden="true">
92+</div>
93+<div class="repo-pulse" aria-hidden="true">
94+</div>
95+</div>
96+<div class="repo-head">
97+<dl class="repo-stats" aria-label="Figures of quiet">
98+<div class="rs-active">
99+<dt>last activity</dt>
100+<dd>
101+<time datetime="STAMP" title="STAMP">AGO</time>
102+</dd>
103+</div>
104+</dl>
105+</div>
106+<p class="repo-commits mute">no commits yet</p>
107+</li>
108+<li id="repo-demo" class="repo">
86 <div class="repo-main">109 <div class="repo-main">
87 <h2 class="repo-name">110 <h2 class="repo-name">
88 <a href="/demo">demo</a>111 <a href="/demo">demo</a>
@@ -148,27 +171,6 @@
148 </li>171 </li>
149 </ul>172 </ul>
150 </li>173 </li>
151-<li id="repo-quiet" class="repo">
152-<div class="repo-main">
153-<h2 class="repo-name">
154-<a href="/quiet">quiet</a>
155-</h2>
156-<p class="repo-desc mute">no description</p>
157-<div class="repo-topics" aria-hidden="true">
158-</div>
159-<div class="repo-pulse" aria-hidden="true">
160-</div>
161-</div>
162-<div class="repo-head">
163-<dl class="repo-stats" aria-label="Figures of quiet">
164-<div class="rs-active">
165-<dt>last activity</dt>
166-<dd class="mute">never</dd>
167-</div>
168-</dl>
169-</div>
170-<p class="repo-commits mute">no commits yet</p>
171-</li>
172 </ul>174 </ul>
173 </section>175 </section>
174 </div>176 </div>
@@ -343,15 +345,15 @@
343 <span class="k">go</span>345 <span class="k">go</span>
344 </a>346 </a>
345 </li>347 </li>
346-<li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo">348+<li id="cmd-o1" role="option" aria-selected="false" data-k="repo quiet">
347-<a href="/demo" tabindex="-1">349+<a href="/quiet" tabindex="-1">
348-<span class="lbl">demo</span>350+<span class="lbl">quiet</span>
349 <span class="k">repo</span>351 <span class="k">repo</span>
350 </a>352 </a>
351 </li>353 </li>
352-<li id="cmd-o2" role="option" aria-selected="false" data-k="repo quiet">354+<li id="cmd-o2" role="option" aria-selected="false" data-k="repo demo">
353-<a href="/quiet" tabindex="-1">355+<a href="/demo" tabindex="-1">
354-<span class="lbl">quiet</span>356+<span class="lbl">demo</span>
355 <span class="k">repo</span>357 <span class="k">repo</span>
356 </a>358 </a>
357 </li>359 </li>
M internal/app/web_events_test.go +2 -2
@@ -123,7 +123,7 @@ func TestWebEventsHome(t *testing.T) {
123123 assert.Equal(t, "home-list", ev.name)
124124 assert.NotEmpty(t, ev.id)
125125 assert.Contains(t, ev.data, `<li id="repo-pub"`)
126- assert.Contains(t, ev.data, `new push`)
126+ assert.Contains(t, ev.data, `new activity`)
127127 assert.Contains(t, ev.data, `href="/pub"`)
128128 assert.Contains(t, ev.data, `<ul class="repo-commits" aria-label="Recent commits of pub">`, "the live row lists the commits too")
129129
@@ -134,7 +134,7 @@ func TestWebEventsHome(t *testing.T) {
134134 assert.Equal(t, "home-list", ev.name)
135135 assert.Contains(t, ev.data, `<li id="repo-fresh"`)
136136 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")
137- assert.NotContains(t, ev.data, "new push")
137+ assert.NotContains(t, ev.data, "new activity")
138138
139139 // A repository that turns private is removed from the anonymous list.
140140 priv := domain.VisibilityPrivate
@@ -123,7 +123,7 @@ func TestWebEventsHome(t *testing.T) {
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, `<li 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 activity`)
127 assert.Contains(t, ev.data, `href="/pub"`)127 assert.Contains(t, ev.data, `href="/pub"`)
128 assert.Contains(t, ev.data, `<ul class="repo-commits" aria-label="Recent commits of pub">`, "the live row lists the commits too")128 assert.Contains(t, ev.data, `<ul class="repo-commits" aria-label="Recent commits of pub">`, "the live row lists the commits too")
129 129
@@ -134,7 +134,7 @@ func TestWebEventsHome(t *testing.T) {
134 assert.Equal(t, "home-list", ev.name)134 assert.Equal(t, "home-list", ev.name)
135 assert.Contains(t, ev.data, `<li id="repo-fresh"`)135 assert.Contains(t, ev.data, `<li id="repo-fresh"`)
136 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")136 assert.Contains(t, ev.data, "a &lt;b&gt;fresh&lt;/b&gt; one", "text is escaped")
137- assert.NotContains(t, ev.data, "new push")137+ assert.NotContains(t, ev.data, "new activity")
138 138
139 // A repository that turns private is removed from the anonymous list.139 // A repository that turns private is removed from the anonymous list.
140 priv := domain.VisibilityPrivate140 priv := domain.VisibilityPrivate
M internal/domain/repo.go +1 -0
@@ -42,6 +42,7 @@ type Repo struct {
4242 Visibility Visibility
4343 CreatedAt time.Time
4444 PushedAt time.Time // zero until the first push
45+ ActiveAt time.Time // latest push, merge request or issue activity
4546 // Settings are the merge settings; the stores always fill them in, with
4647 // the defaults for a repository that never stored any.
4748 Settings RepoSettings
@@ -42,6 +42,7 @@ type Repo struct {
42 Visibility Visibility42 Visibility Visibility
43 CreatedAt time.Time43 CreatedAt time.Time
44 PushedAt time.Time // zero until the first push44 PushedAt time.Time // zero until the first push
45+ ActiveAt time.Time // latest push, merge request or issue activity
45 // Settings are the merge settings; the stores always fill them in, with46 // Settings are the merge settings; the stores always fill them in, with
46 // the defaults for a repository that never stored any.47 // the defaults for a repository that never stored any.
47 Settings RepoSettings48 Settings RepoSettings
M internal/ports/dto.go +1 -1
@@ -21,7 +21,7 @@ type RepoPage struct {
2121
2222 // RepoView is a repository with the Git state that belongs in its summary.
2323 type RepoView struct {
24- domain.Repo
24+ domain.Repo // includes ActiveAt, the latest repository activity
2525 // DefaultBranch is the branch HEAD points to, even when it does not exist
2626 // yet; empty when HEAD is detached or the directory is missing.
2727 DefaultBranch string
@@ -21,7 +21,7 @@ type RepoPage struct {
21 21
22 // RepoView is a repository with the Git state that belongs in its summary.22 // RepoView is a repository with the Git state that belongs in its summary.
23 type RepoView struct {23 type RepoView struct {
24- domain.Repo24+ domain.Repo // includes ActiveAt, the latest repository activity
25 // DefaultBranch is the branch HEAD points to, even when it does not exist25 // DefaultBranch is the branch HEAD points to, even when it does not exist
26 // yet; empty when HEAD is detached or the directory is missing.26 // yet; empty when HEAD is detached or the directory is missing.
27 DefaultBranch string27 DefaultBranch string
A internal/usecase/activity.go +53 -0
@@ -0,0 +1,53 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package usecase
4+
5+import (
6+ "context"
7+ "errors"
8+ "fmt"
9+ "time"
10+
11+ "github.com/bnema/zerowrap"
12+
13+ "bnema.dev/ermite/internal/domain"
14+ "bnema.dev/ermite/internal/ports"
15+)
16+
17+// recordActivity is best-effort: a metadata failure never fails the action.
18+func recordActivity(ctx context.Context, meta ports.RepoMetaStore, name domain.RepoName, at time.Time) {
19+ if _, err := touchActiveAt(ctx, meta, name, at); err != nil {
20+ log := zerowrap.FromCtx(ctx)
21+ log.Error().Err(err).Str("repo", name.String()).Msg("record repository activity")
22+ }
23+}
24+
25+// touchActiveAt preserves later activity and retries concurrent metadata writes.
26+// A repository deleted meanwhile is not an error.
27+func touchActiveAt(ctx context.Context, meta ports.RepoMetaStore, name domain.RepoName, now time.Time) (domain.Repo, error) {
28+ for range pushRetries {
29+ repo, err := meta.Get(ctx, name)
30+ if errors.Is(err, domain.ErrNotFound) {
31+ return domain.Repo{}, nil
32+ }
33+ if err != nil {
34+ return domain.Repo{}, fmt.Errorf("get repository %q: %w", name, err)
35+ }
36+ if !now.After(repo.ActiveAt) {
37+ return repo, nil // a later activity already recorded
38+ }
39+ repo.ActiveAt = now
40+ err = meta.Update(ctx, repo)
41+ if errors.Is(err, domain.ErrConflict) {
42+ continue
43+ }
44+ if errors.Is(err, domain.ErrNotFound) {
45+ return domain.Repo{}, nil
46+ }
47+ if err != nil {
48+ return repo, fmt.Errorf("update repository %q: %w", name, err)
49+ }
50+ return repo, nil
51+ }
52+ return domain.Repo{}, fmt.Errorf("update active_at of %q: %w", name, domain.ErrConflict)
53+}
@@ -0,0 +1,53 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package usecase
4+
5+import (
6+ "context"
7+ "errors"
8+ "fmt"
9+ "time"
10+
11+ "github.com/bnema/zerowrap"
12+
13+ "bnema.dev/ermite/internal/domain"
14+ "bnema.dev/ermite/internal/ports"
15+)
16+
17+// recordActivity is best-effort: a metadata failure never fails the action.
18+func recordActivity(ctx context.Context, meta ports.RepoMetaStore, name domain.RepoName, at time.Time) {
19+ if _, err := touchActiveAt(ctx, meta, name, at); err != nil {
20+ log := zerowrap.FromCtx(ctx)
21+ log.Error().Err(err).Str("repo", name.String()).Msg("record repository activity")
22+ }
23+}
24+
25+// touchActiveAt preserves later activity and retries concurrent metadata writes.
26+// A repository deleted meanwhile is not an error.
27+func touchActiveAt(ctx context.Context, meta ports.RepoMetaStore, name domain.RepoName, now time.Time) (domain.Repo, error) {
28+ for range pushRetries {
29+ repo, err := meta.Get(ctx, name)
30+ if errors.Is(err, domain.ErrNotFound) {
31+ return domain.Repo{}, nil
32+ }
33+ if err != nil {
34+ return domain.Repo{}, fmt.Errorf("get repository %q: %w", name, err)
35+ }
36+ if !now.After(repo.ActiveAt) {
37+ return repo, nil // a later activity already recorded
38+ }
39+ repo.ActiveAt = now
40+ err = meta.Update(ctx, repo)
41+ if errors.Is(err, domain.ErrConflict) {
42+ continue
43+ }
44+ if errors.Is(err, domain.ErrNotFound) {
45+ return domain.Repo{}, nil
46+ }
47+ if err != nil {
48+ return repo, fmt.Errorf("update repository %q: %w", name, err)
49+ }
50+ return repo, nil
51+ }
52+ return domain.Repo{}, fmt.Errorf("update active_at of %q: %w", name, domain.ErrConflict)
53+}
A internal/usecase/activity_test.go +42 -0
@@ -0,0 +1,42 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package usecase
4+
5+import (
6+ "errors"
7+ "testing"
8+ "time"
9+
10+ "github.com/stretchr/testify/mock"
11+ "github.com/stretchr/testify/require"
12+
13+ "bnema.dev/ermite/internal/domain"
14+ "bnema.dev/ermite/internal/ports/mocks"
15+)
16+
17+func TestTouchActiveAt(t *testing.T) {
18+ name, err := domain.ParseRepoName("proj")
19+ require.NoError(t, err)
20+ now := time.Now().UTC()
21+ t.Run("retries and preserves later activity", func(t *testing.T) {
22+ meta := mocks.NewRepoMetaStore(t)
23+ meta.EXPECT().Get(mock.Anything, name).Return(domain.Repo{Name: name}, nil).Once()
24+ meta.EXPECT().Update(mock.Anything, mock.MatchedBy(func(r domain.Repo) bool { return r.ActiveAt.Equal(now) && r.PushedAt.IsZero() })).Return(domain.ErrConflict).Once()
25+ later := domain.Repo{Name: name, ActiveAt: now.Add(time.Hour)}
26+ meta.EXPECT().Get(mock.Anything, name).Return(later, nil).Once()
27+ got, err := touchActiveAt(t.Context(), meta, name, now)
28+ require.NoError(t, err)
29+ require.Equal(t, later, got)
30+ })
31+ t.Run("deleted repository", func(t *testing.T) {
32+ meta := mocks.NewRepoMetaStore(t)
33+ meta.EXPECT().Get(mock.Anything, name).Return(domain.Repo{}, domain.ErrNotFound)
34+ _, err := touchActiveAt(t.Context(), meta, name, now)
35+ require.NoError(t, err)
36+ })
37+ t.Run("best effort failure", func(t *testing.T) {
38+ meta := mocks.NewRepoMetaStore(t)
39+ meta.EXPECT().Get(mock.Anything, name).Return(domain.Repo{}, errors.New("offline"))
40+ require.NotPanics(t, func() { recordActivity(t.Context(), meta, name, now) })
41+ })
42+}
@@ -0,0 +1,42 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package usecase
4+
5+import (
6+ "errors"
7+ "testing"
8+ "time"
9+
10+ "github.com/stretchr/testify/mock"
11+ "github.com/stretchr/testify/require"
12+
13+ "bnema.dev/ermite/internal/domain"
14+ "bnema.dev/ermite/internal/ports/mocks"
15+)
16+
17+func TestTouchActiveAt(t *testing.T) {
18+ name, err := domain.ParseRepoName("proj")
19+ require.NoError(t, err)
20+ now := time.Now().UTC()
21+ t.Run("retries and preserves later activity", func(t *testing.T) {
22+ meta := mocks.NewRepoMetaStore(t)
23+ meta.EXPECT().Get(mock.Anything, name).Return(domain.Repo{Name: name}, nil).Once()
24+ meta.EXPECT().Update(mock.Anything, mock.MatchedBy(func(r domain.Repo) bool { return r.ActiveAt.Equal(now) && r.PushedAt.IsZero() })).Return(domain.ErrConflict).Once()
25+ later := domain.Repo{Name: name, ActiveAt: now.Add(time.Hour)}
26+ meta.EXPECT().Get(mock.Anything, name).Return(later, nil).Once()
27+ got, err := touchActiveAt(t.Context(), meta, name, now)
28+ require.NoError(t, err)
29+ require.Equal(t, later, got)
30+ })
31+ t.Run("deleted repository", func(t *testing.T) {
32+ meta := mocks.NewRepoMetaStore(t)
33+ meta.EXPECT().Get(mock.Anything, name).Return(domain.Repo{}, domain.ErrNotFound)
34+ _, err := touchActiveAt(t.Context(), meta, name, now)
35+ require.NoError(t, err)
36+ })
37+ t.Run("best effort failure", func(t *testing.T) {
38+ meta := mocks.NewRepoMetaStore(t)
39+ meta.EXPECT().Get(mock.Anything, name).Return(domain.Repo{}, errors.New("offline"))
40+ require.NotPanics(t, func() { recordActivity(t.Context(), meta, name, now) })
41+ })
42+}
M internal/usecase/issues.go +3 -0
@@ -124,6 +124,7 @@ func (i *Issues) Create(ctx context.Context, p domain.Principal, name domain.Rep
124124 log := zerowrap.FromCtx(ctx)
125125 log.Info().Str("repo", name.String()).Int64("number", issue.Number).
126126 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue opened")
127+ recordActivity(ctx, i.meta, repo.Name, now)
127128 i.bus.Publish(ctx, domain.IssueOpened{Issue: issue, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
128129 return issue, nil
129130 }
@@ -211,6 +212,7 @@ func (i *Issues) AddComment(ctx context.Context, p domain.Principal, name domain
211212 log := zerowrap.FromCtx(ctx)
212213 log.Info().Str("repo", name.String()).Int64("number", number).Int64("comment", c.ID).
213214 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue comment added")
215+ recordActivity(ctx, i.meta, name, c.CreatedAt)
214216 i.bus.Publish(ctx, domain.IssueCommented{
215217 Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt,
216218 })
@@ -260,6 +262,7 @@ func (i *Issues) transition(ctx context.Context, p domain.Principal, name domain
260262 log := zerowrap.FromCtx(ctx)
261263 log.Info().Str("repo", name.String()).Int64("number", number).
262264 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue " + verb + "d")
265+ recordActivity(ctx, i.meta, repo.Name, issue.UpdatedAt)
263266 if to == domain.IssueStateOpen {
264267 i.bus.Publish(ctx, domain.IssueReopened{Issue: issue, At: issue.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
265268 } else {
@@ -124,6 +124,7 @@ func (i *Issues) Create(ctx context.Context, p domain.Principal, name domain.Rep
124 log := zerowrap.FromCtx(ctx)124 log := zerowrap.FromCtx(ctx)
125 log.Info().Str("repo", name.String()).Int64("number", issue.Number).125 log.Info().Str("repo", name.String()).Int64("number", issue.Number).
126 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue opened")126 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue opened")
127+ recordActivity(ctx, i.meta, repo.Name, now)
127 i.bus.Publish(ctx, domain.IssueOpened{Issue: issue, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})128 i.bus.Publish(ctx, domain.IssueOpened{Issue: issue, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
128 return issue, nil129 return issue, nil
129 }130 }
@@ -211,6 +212,7 @@ func (i *Issues) AddComment(ctx context.Context, p domain.Principal, name domain
211 log := zerowrap.FromCtx(ctx)212 log := zerowrap.FromCtx(ctx)
212 log.Info().Str("repo", name.String()).Int64("number", number).Int64("comment", c.ID).213 log.Info().Str("repo", name.String()).Int64("number", number).Int64("comment", c.ID).
213 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue comment added")214 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue comment added")
215+ recordActivity(ctx, i.meta, name, c.CreatedAt)
214 i.bus.Publish(ctx, domain.IssueCommented{216 i.bus.Publish(ctx, domain.IssueCommented{
215 Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt,217 Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt,
216 })218 })
@@ -260,6 +262,7 @@ func (i *Issues) transition(ctx context.Context, p domain.Principal, name domain
260 log := zerowrap.FromCtx(ctx)262 log := zerowrap.FromCtx(ctx)
261 log.Info().Str("repo", name.String()).Int64("number", number).263 log.Info().Str("repo", name.String()).Int64("number", number).
262 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue " + verb + "d")264 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("issue " + verb + "d")
265+ recordActivity(ctx, i.meta, repo.Name, issue.UpdatedAt)
263 if to == domain.IssueStateOpen {266 if to == domain.IssueStateOpen {
264 i.bus.Publish(ctx, domain.IssueReopened{Issue: issue, At: issue.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})267 i.bus.Publish(ctx, domain.IssueReopened{Issue: issue, At: issue.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
265 } else {268 } else {
M internal/usecase/issues_integration_test.go +4 -0
@@ -46,6 +46,10 @@ func TestIssuesOverTheStore(t *testing.T) {
4646 require.NoError(t, err)
4747 assert.EqualValues(t, 2, issue.Number, "the number follows the merge request")
4848 assert.Equal(t, "bot", issue.Author)
49+ repo, err := store.Get(ctx, n)
50+ require.NoError(t, err)
51+ assert.True(t, repo.ActiveAt.Equal(now))
52+ assert.True(t, repo.PushedAt.IsZero())
4953
5054 kind, err := issues.Kind(ctx, domain.Anonymous(), n, 1)
5155 require.NoError(t, err)
@@ -46,6 +46,10 @@ func TestIssuesOverTheStore(t *testing.T) {
46 require.NoError(t, err)46 require.NoError(t, err)
47 assert.EqualValues(t, 2, issue.Number, "the number follows the merge request")47 assert.EqualValues(t, 2, issue.Number, "the number follows the merge request")
48 assert.Equal(t, "bot", issue.Author)48 assert.Equal(t, "bot", issue.Author)
49+ repo, err := store.Get(ctx, n)
50+ require.NoError(t, err)
51+ assert.True(t, repo.ActiveAt.Equal(now))
52+ assert.True(t, repo.PushedAt.IsZero())
49 53
50 kind, err := issues.Kind(ctx, domain.Anonymous(), n, 1)54 kind, err := issues.Kind(ctx, domain.Anonymous(), n, 1)
51 require.NoError(t, err)55 require.NoError(t, err)
M internal/usecase/issues_test.go +2 -0
@@ -39,6 +39,7 @@ func newIssueUnit(t *testing.T, vis domain.Visibility, mode domain.IssuesMode) *
3939 clock := mocks.NewClock(t)
4040 clock.EXPECT().Now().Return(now).Maybe()
4141 u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil).Maybe()
42+ u.meta.EXPECT().Update(mock.Anything, mock.MatchedBy(func(r domain.Repo) bool { return r.Name == u.repo.Name && r.ActiveAt.Equal(now) && r.PushedAt.IsZero() })).Return(nil).Maybe()
4243 u.issues = usecase.NewIssues(usecase.IssuesConfig{Meta: u.meta, Store: u.store, MergeRequests: u.mrs, Bus: u.bus, Clock: clock})
4344 return u
4445 }
@@ -116,6 +117,7 @@ func TestIssueCreateAndTransitions(t *testing.T) {
116117 got, err := u.issues.Create(ctx, p, u.repo.Name, ports.CreateIssueParams{Title: " Crash ", Body: "why"})
117118 require.NoError(t, err)
118119 assert.EqualValues(t, 7, got.Number)
120+ u.meta.AssertCalled(t, "Update", mock.Anything, mock.MatchedBy(func(r domain.Repo) bool { return r.ActiveAt.Equal(now) && r.PushedAt.IsZero() }))
119121 })
120122
121123 t.Run("invalid text stores nothing", func(t *testing.T) {
@@ -39,6 +39,7 @@ func newIssueUnit(t *testing.T, vis domain.Visibility, mode domain.IssuesMode) *
39 clock := mocks.NewClock(t)39 clock := mocks.NewClock(t)
40 clock.EXPECT().Now().Return(now).Maybe()40 clock.EXPECT().Now().Return(now).Maybe()
41 u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil).Maybe()41 u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil).Maybe()
42+ u.meta.EXPECT().Update(mock.Anything, mock.MatchedBy(func(r domain.Repo) bool { return r.Name == u.repo.Name && r.ActiveAt.Equal(now) && r.PushedAt.IsZero() })).Return(nil).Maybe()
42 u.issues = usecase.NewIssues(usecase.IssuesConfig{Meta: u.meta, Store: u.store, MergeRequests: u.mrs, Bus: u.bus, Clock: clock})43 u.issues = usecase.NewIssues(usecase.IssuesConfig{Meta: u.meta, Store: u.store, MergeRequests: u.mrs, Bus: u.bus, Clock: clock})
43 return u44 return u
44 }45 }
@@ -116,6 +117,7 @@ func TestIssueCreateAndTransitions(t *testing.T) {
116 got, err := u.issues.Create(ctx, p, u.repo.Name, ports.CreateIssueParams{Title: " Crash ", Body: "why"})117 got, err := u.issues.Create(ctx, p, u.repo.Name, ports.CreateIssueParams{Title: " Crash ", Body: "why"})
117 require.NoError(t, err)118 require.NoError(t, err)
118 assert.EqualValues(t, 7, got.Number)119 assert.EqualValues(t, 7, got.Number)
120+ u.meta.AssertCalled(t, "Update", mock.Anything, mock.MatchedBy(func(r domain.Repo) bool { return r.ActiveAt.Equal(now) && r.PushedAt.IsZero() }))
119 })121 })
120 122
121 t.Run("invalid text stores nothing", func(t *testing.T) {123 t.Run("invalid text stores nothing", func(t *testing.T) {
M internal/usecase/mergerequests.go +5 -0
@@ -254,6 +254,7 @@ func (m *MergeRequests) Create(ctx context.Context, p domain.Principal, name dom
254254 if err != nil {
255255 return domain.MergeRequest{}, err
256256 }
257+ recordActivity(ctx, m.meta, repo.Name, now)
257258 m.bus.Publish(ctx, domain.MergeRequestOpened{MergeRequest: mr, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
258259 if m.pulls != nil {
259260 // The commits the opening is for are the ones of now.
@@ -377,6 +378,7 @@ func (m *MergeRequests) MarkReady(ctx context.Context, p domain.Principal, name
377378 if err != nil {
378379 return domain.MergeRequest{}, fmt.Errorf("mark !%d of %q ready: %w", number, name, err)
379380 }
381+ recordActivity(ctx, m.meta, repo.Name, mr.UpdatedAt)
380382 m.bus.Publish(ctx, domain.MergeRequestReady{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
381383 return m.withHead(ctx, mr)
382384 }
@@ -656,6 +658,7 @@ func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name
656658 log := zerowrap.FromCtx(ctx)
657659 log.Info().Str("repo", name.String()).Int64("number", number).Int64("comment", c.ID).
658660 Int64("reply_to", c.ReplyTo).Str("verdict", c.Verdict.String()).Bool("on_line", !general).Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("comment added")
661+ recordActivity(ctx, m.meta, name, c.CreatedAt)
659662 m.bus.Publish(ctx, domain.CommentAdded{
660663 Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt,
661664 })
@@ -796,6 +799,7 @@ func (m *MergeRequests) Close(ctx context.Context, p domain.Principal, name doma
796799 log := zerowrap.FromCtx(ctx)
797800 log.Info().Str("repo", name.String()).Int64("number", number).
798801 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("merge request closed")
802+ recordActivity(ctx, m.meta, repo.Name, mr.UpdatedAt)
799803 m.bus.Publish(ctx, domain.MergeRequestClosed{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
800804 if m.pulls != nil {
801805 m.pulls.OnPullRequest(ctx, name, ports.PullRequestEvent{Number: number, Action: domain.PullClosed, Head: mr.HeadID, Base: mr.BaseID}, p)
@@ -998,6 +1002,7 @@ func (m *MergeRequests) merge(ctx context.Context, p domain.Principal, name doma
9981002 }
9991003 }
10001004 at := mr.UpdatedAt
1005+ recordActivity(ctx, m.meta, repo.Name, at)
10011006 m.bus.Publish(ctx, domain.MergeRequestMerged{MergeRequest: mr, At: at, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
10021007 m.bus.Publish(ctx, domain.PushEvent{
10031008 Repo: name, At: at, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt, Updates: updates,
@@ -254,6 +254,7 @@ func (m *MergeRequests) Create(ctx context.Context, p domain.Principal, name dom
254 if err != nil {254 if err != nil {
255 return domain.MergeRequest{}, err255 return domain.MergeRequest{}, err
256 }256 }
257+ recordActivity(ctx, m.meta, repo.Name, now)
257 m.bus.Publish(ctx, domain.MergeRequestOpened{MergeRequest: mr, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})258 m.bus.Publish(ctx, domain.MergeRequestOpened{MergeRequest: mr, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
258 if m.pulls != nil {259 if m.pulls != nil {
259 // The commits the opening is for are the ones of now.260 // The commits the opening is for are the ones of now.
@@ -377,6 +378,7 @@ func (m *MergeRequests) MarkReady(ctx context.Context, p domain.Principal, name
377 if err != nil {378 if err != nil {
378 return domain.MergeRequest{}, fmt.Errorf("mark !%d of %q ready: %w", number, name, err)379 return domain.MergeRequest{}, fmt.Errorf("mark !%d of %q ready: %w", number, name, err)
379 }380 }
381+ recordActivity(ctx, m.meta, repo.Name, mr.UpdatedAt)
380 m.bus.Publish(ctx, domain.MergeRequestReady{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})382 m.bus.Publish(ctx, domain.MergeRequestReady{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
381 return m.withHead(ctx, mr)383 return m.withHead(ctx, mr)
382 }384 }
@@ -656,6 +658,7 @@ func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name
656 log := zerowrap.FromCtx(ctx)658 log := zerowrap.FromCtx(ctx)
657 log.Info().Str("repo", name.String()).Int64("number", number).Int64("comment", c.ID).659 log.Info().Str("repo", name.String()).Int64("number", number).Int64("comment", c.ID).
658 Int64("reply_to", c.ReplyTo).Str("verdict", c.Verdict.String()).Bool("on_line", !general).Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("comment added")660 Int64("reply_to", c.ReplyTo).Str("verdict", c.Verdict.String()).Bool("on_line", !general).Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("comment added")
661+ recordActivity(ctx, m.meta, name, c.CreatedAt)
659 m.bus.Publish(ctx, domain.CommentAdded{662 m.bus.Publish(ctx, domain.CommentAdded{
660 Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt,663 Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt,
661 })664 })
@@ -796,6 +799,7 @@ func (m *MergeRequests) Close(ctx context.Context, p domain.Principal, name doma
796 log := zerowrap.FromCtx(ctx)799 log := zerowrap.FromCtx(ctx)
797 log.Info().Str("repo", name.String()).Int64("number", number).800 log.Info().Str("repo", name.String()).Int64("number", number).
798 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("merge request closed")801 Str("principal", p.Label()).Str("token_id", p.TokenID()).Msg("merge request closed")
802+ recordActivity(ctx, m.meta, repo.Name, mr.UpdatedAt)
799 m.bus.Publish(ctx, domain.MergeRequestClosed{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})803 m.bus.Publish(ctx, domain.MergeRequestClosed{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
800 if m.pulls != nil {804 if m.pulls != nil {
801 m.pulls.OnPullRequest(ctx, name, ports.PullRequestEvent{Number: number, Action: domain.PullClosed, Head: mr.HeadID, Base: mr.BaseID}, p)805 m.pulls.OnPullRequest(ctx, name, ports.PullRequestEvent{Number: number, Action: domain.PullClosed, Head: mr.HeadID, Base: mr.BaseID}, p)
@@ -998,6 +1002,7 @@ func (m *MergeRequests) merge(ctx context.Context, p domain.Principal, name doma
998 }1002 }
999 }1003 }
1000 at := mr.UpdatedAt1004 at := mr.UpdatedAt
1005+ recordActivity(ctx, m.meta, repo.Name, at)
1001 m.bus.Publish(ctx, domain.MergeRequestMerged{MergeRequest: mr, At: at, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})1006 m.bus.Publish(ctx, domain.MergeRequestMerged{MergeRequest: mr, At: at, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt})
1002 m.bus.Publish(ctx, domain.PushEvent{1007 m.bus.Publish(ctx, domain.PushEvent{
1003 Repo: name, At: at, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt, Updates: updates,1008 Repo: name, At: at, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt, Updates: updates,
M internal/usecase/mergerequests_integration_test.go +11 -0
@@ -1448,3 +1448,14 @@ func TestNoteThreadsRangesAndVerdicts(t *testing.T) {
14481448 _, err = r.mrs.DeleteComment(r.ctx, root, r.repo, mr.Number, changes.ID)
14491449 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
14501450 }
1451+
1452+func TestMergeRequestRecordsRepositoryActivity(t *testing.T) {
1453+ r := newMRRig(t)
1454+ r.seed()
1455+ r.feature("activity")
1456+ _, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "Activity", Source: "activity", Target: "main"})
1457+ require.NoError(t, err)
1458+ repo, err := r.store.Get(r.ctx, r.repo)
1459+ require.NoError(t, err)
1460+ assert.True(t, repo.ActiveAt.Equal(now))
1461+}
@@ -1448,3 +1448,14 @@ func TestNoteThreadsRangesAndVerdicts(t *testing.T) {
1448 _, err = r.mrs.DeleteComment(r.ctx, root, r.repo, mr.Number, changes.ID)1448 _, err = r.mrs.DeleteComment(r.ctx, root, r.repo, mr.Number, changes.ID)
1449 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)1449 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
1450 }1450 }
1451+
1452+func TestMergeRequestRecordsRepositoryActivity(t *testing.T) {
1453+ r := newMRRig(t)
1454+ r.seed()
1455+ r.feature("activity")
1456+ _, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "Activity", Source: "activity", Target: "main"})
1457+ require.NoError(t, err)
1458+ repo, err := r.store.Get(r.ctx, r.repo)
1459+ require.NoError(t, err)
1460+ assert.True(t, repo.ActiveAt.Equal(now))
1461+}
M internal/usecase/pushes.go +10 -4
@@ -209,7 +209,7 @@ func (u *Pushes) checkWorkflows(ctx context.Context, name domain.RepoName, updat
209209 return nil
210210 }
211211
212-// Record is the post-receive step of an accepted push: it sets pushed_at,
212+// Record is the post-receive step of an accepted push: it sets pushed_at and active_at,
213213 // repairs HEAD when it points to a missing branch, tells the reader that the
214214 // repository changed, queues the workflows the push triggers, publishes a
215215 // PushEvent and schedules maintenance. Every
@@ -245,7 +245,8 @@ func (u *Pushes) Record(ctx context.Context, p domain.Principal, name domain.Rep
245245 return errors.Join(errs...)
246246 }
247247
248-// touchPushedAt stores pushed_at, retrying when another write got in between.
248+// touchPushedAt stores pushed_at and active_at, preserving later timestamps
249+// and retrying when another write got in between.
249250 // A repository deleted meanwhile is not an error. It returns the repository
250251 // as read, or the zero value when it is unknown. A push (Pushes.Record) and a
251252 // merge, which moves a branch too, both record through it.
@@ -258,10 +259,15 @@ func touchPushedAt(ctx context.Context, meta ports.RepoMetaStore, name domain.Re
258259 if err != nil {
259260 return domain.Repo{}, fmt.Errorf("get repository %q: %w", name, err)
260261 }
261- if !now.After(repo.PushedAt) {
262+ if !now.After(repo.PushedAt) && !now.After(repo.ActiveAt) {
262263 return repo, nil // a later push already recorded
263264 }
264- repo.PushedAt = now
265+ if now.After(repo.PushedAt) {
266+ repo.PushedAt = now
267+ }
268+ if now.After(repo.ActiveAt) {
269+ repo.ActiveAt = now
270+ }
265271 err = meta.Update(ctx, repo)
266272 if errors.Is(err, domain.ErrConflict) {
267273 continue
@@ -209,7 +209,7 @@ func (u *Pushes) checkWorkflows(ctx context.Context, name domain.RepoName, updat
209 return nil209 return nil
210 }210 }
211 211
212-// Record is the post-receive step of an accepted push: it sets pushed_at,212+// Record is the post-receive step of an accepted push: it sets pushed_at and active_at,
213 // repairs HEAD when it points to a missing branch, tells the reader that the213 // repairs HEAD when it points to a missing branch, tells the reader that the
214 // repository changed, queues the workflows the push triggers, publishes a214 // repository changed, queues the workflows the push triggers, publishes a
215 // PushEvent and schedules maintenance. Every215 // PushEvent and schedules maintenance. Every
@@ -245,7 +245,8 @@ func (u *Pushes) Record(ctx context.Context, p domain.Principal, name domain.Rep
245 return errors.Join(errs...)245 return errors.Join(errs...)
246 }246 }
247 247
248-// touchPushedAt stores pushed_at, retrying when another write got in between.248+// touchPushedAt stores pushed_at and active_at, preserving later timestamps
249+// and retrying when another write got in between.
249 // A repository deleted meanwhile is not an error. It returns the repository250 // A repository deleted meanwhile is not an error. It returns the repository
250 // as read, or the zero value when it is unknown. A push (Pushes.Record) and a251 // as read, or the zero value when it is unknown. A push (Pushes.Record) and a
251 // merge, which moves a branch too, both record through it.252 // merge, which moves a branch too, both record through it.
@@ -258,10 +259,15 @@ func touchPushedAt(ctx context.Context, meta ports.RepoMetaStore, name domain.Re
258 if err != nil {259 if err != nil {
259 return domain.Repo{}, fmt.Errorf("get repository %q: %w", name, err)260 return domain.Repo{}, fmt.Errorf("get repository %q: %w", name, err)
260 }261 }
261- if !now.After(repo.PushedAt) {262+ if !now.After(repo.PushedAt) && !now.After(repo.ActiveAt) {
262 return repo, nil // a later push already recorded263 return repo, nil // a later push already recorded
263 }264 }
264- repo.PushedAt = now265+ if now.After(repo.PushedAt) {
266+ repo.PushedAt = now
267+ }
268+ if now.After(repo.ActiveAt) {
269+ repo.ActiveAt = now
270+ }
265 err = meta.Update(ctx, repo)271 err = meta.Update(ctx, repo)
266 if errors.Is(err, domain.ErrConflict) {272 if errors.Is(err, domain.ErrConflict) {
267 continue273 continue
M internal/usecase/pushes_test.go +4 -4
@@ -306,7 +306,7 @@ func TestPushesRecord(t *testing.T) {
306306 f.sched.EXPECT().Schedule(ctx, n).Return().Once()
307307 repo := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: now.Add(-time.Hour)}
308308 f.meta.EXPECT().Get(ctx, n).Return(repo, nil)
309- f.meta.EXPECT().Update(ctx, domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: repo.CreatedAt, PushedAt: now}).Return(nil)
309+ f.meta.EXPECT().Update(ctx, domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: repo.CreatedAt, PushedAt: now, ActiveAt: now}).Return(nil)
310310 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", true, nil)
311311 f.bus.EXPECT().Publish(ctx, domain.PushEvent{Repo: n, Updates: ups, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}).Return()
312312
@@ -334,7 +334,7 @@ func TestPushesRecord(t *testing.T) {
334334 repo := domain.Repo{Name: n}
335335 f.meta.EXPECT().Get(ctx, n).Return(repo, nil).Once()
336336 f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once()
337- later := domain.Repo{Name: n, PushedAt: now.Add(time.Second)}
337+ later := domain.Repo{Name: n, PushedAt: now.Add(time.Second), ActiveAt: now.Add(time.Second)}
338338 f.meta.EXPECT().Get(ctx, n).Return(later, nil).Once() // a later push recorded meanwhile
339339 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", true, nil)
340340 f.bus.EXPECT().Publish(ctx, mock.Anything).Return()
@@ -414,7 +414,7 @@ func TestPushesDefaultBranchRule(t *testing.T) {
414414 t.Parallel()
415415 f := newPushFixture(t)
416416 f.sched.EXPECT().Schedule(ctx, n).Return().Once()
417- f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now}, nil)
417+ f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now, ActiveAt: now}, nil)
418418 f.git.EXPECT().DefaultBranch(ctx, n).Return(tt.head, tt.exists, nil)
419419 if tt.want != "" {
420420 f.git.EXPECT().SetDefaultBranch(ctx, n, tt.want).Return(nil)
@@ -429,7 +429,7 @@ func TestPushesDefaultBranchRule(t *testing.T) {
429429 f := newPushFixture(t)
430430 f.sched.EXPECT().Schedule(ctx, n).Return().Once()
431431 boom := errors.New("boom")
432- f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now}, nil)
432+ f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now, ActiveAt: now}, nil)
433433 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", false, nil)
434434 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(boom)
435435 f.bus.EXPECT().Publish(ctx, mock.Anything).Return()
@@ -306,7 +306,7 @@ func TestPushesRecord(t *testing.T) {
306 f.sched.EXPECT().Schedule(ctx, n).Return().Once()306 f.sched.EXPECT().Schedule(ctx, n).Return().Once()
307 repo := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: now.Add(-time.Hour)}307 repo := domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: now.Add(-time.Hour)}
308 f.meta.EXPECT().Get(ctx, n).Return(repo, nil)308 f.meta.EXPECT().Get(ctx, n).Return(repo, nil)
309- f.meta.EXPECT().Update(ctx, domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: repo.CreatedAt, PushedAt: now}).Return(nil)309+ f.meta.EXPECT().Update(ctx, domain.Repo{Name: n, Visibility: domain.VisibilityPrivate, CreatedAt: repo.CreatedAt, PushedAt: now, ActiveAt: now}).Return(nil)
310 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", true, nil)310 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", true, nil)
311 f.bus.EXPECT().Publish(ctx, domain.PushEvent{Repo: n, Updates: ups, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}).Return()311 f.bus.EXPECT().Publish(ctx, domain.PushEvent{Repo: n, Updates: ups, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}).Return()
312 312
@@ -334,7 +334,7 @@ func TestPushesRecord(t *testing.T) {
334 repo := domain.Repo{Name: n}334 repo := domain.Repo{Name: n}
335 f.meta.EXPECT().Get(ctx, n).Return(repo, nil).Once()335 f.meta.EXPECT().Get(ctx, n).Return(repo, nil).Once()
336 f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once()336 f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once()
337- later := domain.Repo{Name: n, PushedAt: now.Add(time.Second)}337+ later := domain.Repo{Name: n, PushedAt: now.Add(time.Second), ActiveAt: now.Add(time.Second)}
338 f.meta.EXPECT().Get(ctx, n).Return(later, nil).Once() // a later push recorded meanwhile338 f.meta.EXPECT().Get(ctx, n).Return(later, nil).Once() // a later push recorded meanwhile
339 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", true, nil)339 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", true, nil)
340 f.bus.EXPECT().Publish(ctx, mock.Anything).Return()340 f.bus.EXPECT().Publish(ctx, mock.Anything).Return()
@@ -414,7 +414,7 @@ func TestPushesDefaultBranchRule(t *testing.T) {
414 t.Parallel()414 t.Parallel()
415 f := newPushFixture(t)415 f := newPushFixture(t)
416 f.sched.EXPECT().Schedule(ctx, n).Return().Once()416 f.sched.EXPECT().Schedule(ctx, n).Return().Once()
417- f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now}, nil)417+ f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now, ActiveAt: now}, nil)
418 f.git.EXPECT().DefaultBranch(ctx, n).Return(tt.head, tt.exists, nil)418 f.git.EXPECT().DefaultBranch(ctx, n).Return(tt.head, tt.exists, nil)
419 if tt.want != "" {419 if tt.want != "" {
420 f.git.EXPECT().SetDefaultBranch(ctx, n, tt.want).Return(nil)420 f.git.EXPECT().SetDefaultBranch(ctx, n, tt.want).Return(nil)
@@ -429,7 +429,7 @@ func TestPushesDefaultBranchRule(t *testing.T) {
429 f := newPushFixture(t)429 f := newPushFixture(t)
430 f.sched.EXPECT().Schedule(ctx, n).Return().Once()430 f.sched.EXPECT().Schedule(ctx, n).Return().Once()
431 boom := errors.New("boom")431 boom := errors.New("boom")
432- f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now}, nil)432+ f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{Name: n, PushedAt: now, ActiveAt: now}, nil)
433 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", false, nil)433 f.git.EXPECT().DefaultBranch(ctx, n).Return("main", false, nil)
434 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(boom)434 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(boom)
435 f.bus.EXPECT().Publish(ctx, mock.Anything).Return()435 f.bus.EXPECT().Publish(ctx, mock.Anything).Return()
M proto/ermite/storage/repo.proto +2 -0
@@ -32,6 +32,8 @@ message Repo {
3232 RepoSettings settings = 6;
3333 // Normalized and sorted; empty on repositories stored before topics.
3434 repeated string topics = 7;
35+ // Latest push, merge request or issue activity; unset until recorded.
36+ google.protobuf.Timestamp active_at = 8;
3537 }
3638
3739 // SchemaVersion is stored under the key "meta/schema".
@@ -32,6 +32,8 @@ message Repo {
32 RepoSettings settings = 6;32 RepoSettings settings = 6;
33 // Normalized and sorted; empty on repositories stored before topics.33 // Normalized and sorted; empty on repositories stored before topics.
34 repeated string topics = 7;34 repeated string topics = 7;
35+ // Latest push, merge request or issue activity; unset until recorded.
36+ google.protobuf.Timestamp active_at = 8;
35 }37 }
36 38
37 // SchemaVersion is stored under the key "meta/schema".39 // SchemaVersion is stored under the key "meta/schema".

Keyboard shortcuts

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

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

Branches 2

Tags 44

↑↓ moveenter openesc close