detached 0a9e349

#6 feat(web): review page polish and home page ordered by last activity

state
merged · 4ce8534 · squash
branch
feat/mr-header-card → main
opened
bnema ·
updated

Review page and home page polish, plus "last activity" ordering of the repositories.

Merge request page

  • Title, facts and description in one card; the header a shade lighter than the description. Issues share it.
  • Unified / Split / Collapse all in one view menu (the existing details.menu component); u, d and x still work, escape closes menus on every page and the focus goes back to the menu button.
  • Wide mode on every page with w (listed in ?), kept in localStorage; store.js loads without defer and sets it before the first paint.
  • Hunk headers and hidden-line rows are quieter (background and border), text keeps its contrast.
  • A shift + click range is one block (one border, none between its lines, per side in split); the form title names the whole range (a.go:21-149); lines loaded later from a gap inside the range are selected too.

Home page

  • Inbox button next to the logo, home page only, with a mailbox icon (Lucide) and a darker count badge.
  • Default order is the last activity: push (incl. merge), merge request opened / merged / closed / ready, comment on a merge request, issue opened / approved / closed / reopened / commented. Moderation (submissions, rejections) stays owner-only and does not count.
  • The row of a repository with activity moves to the top live (activity order only; CI updates do not move it).

Storage

  • Repo.ActiveAt (storage proto field 8; falls back to the later of pushed and created).
  • RepoMetaStore.Update is replaced by Mutate: a compare-and-swap on the version read, retried up to 8 times. The previous Update checked the version read at write time, so a read-modify-write could overwrite a concurrent change (e.g. a visibility change).

No change to ermite-api.

Checks: go vet ./..., go test ./..., go test -race on usecase and kvstore, JS tests (160), golden pages refreshed, manual checks on the dev server. Reviewed by Go and JS reviewers; findings fixed.

CommitSubjectAuthorDateSignatureStatus
0a9e349test: run the activity store test outside the usecase package and its home row subtests in parallelbriceGPG Verified
9ca7c68fix(repos): mutate metadata atomically and record approved issue activitybriceGPG Verified
32e9fe7fix(web): match loaded lines to a selection by file key, keep the focus on a closing menu, close menus with escape on every page and restore the hunk header contrastbriceGPG Verified
4277264feat(web): order the home page by last activity and move repositories up livebriceGPG Verified
4fc291efeat(web): move the inbox button of the home page next to the logo, with a mailbox icon and a darker count badgebriceGPG Verified
5f6d2c6test(app): refresh the web golden pages for the view menu, the w shortcut and the store scriptbriceGPG Verified
a772c1bfix(web): a selected line range is one block, its title names the whole range and its hidden lines stay selected once loadedbriceGPG Verified
502935erefactor(web): drop the Wide button of the diff bar, the w shortcut covers itbriceGPG Verified
a1f8e85feat(web): make the wide mode a global w shortcut, set before the first paint, and tighten the merge request headerbriceGPG Verified
fcc7703feat(web): one merge request card, a diff view menu, a wide diff mode and quieter hunk rowsbriceGPG Verified

Checks 4

92 files · +2426 -1392
4/4
M gen/ermite/storage/repo.pb.go +22 -11
M internal/adapters/kvstore/issues_test.go +4 -2
M internal/adapters/kvstore/mergerequests_test.go +4 -2
M internal/adapters/kvstore/repos.go +44 -22
A internal/adapters/kvstore/repos_mutate_test.go +87 -0
M internal/adapters/kvstore/store_test.go +8 -4
M internal/adapters/web/components.templ +2 -2
M internal/adapters/web/components_templ.go +119 -113
M internal/adapters/web/diff.templ +22 -4
M internal/adapters/web/diff_templ.go +523 -437
M internal/adapters/web/events.go +2 -0
M internal/adapters/web/format.go +8 -0
M internal/adapters/web/fragments.go +18 -0
M internal/adapters/web/home.go +24 -14
M internal/adapters/web/home_test.go +50 -1
M internal/adapters/web/issues.templ +24 -22
M internal/adapters/web/issues_templ.go +26 -26
M internal/adapters/web/jstest/a11y.test.mjs +25 -0
M internal/adapters/web/keys.templ +1 -0
M internal/adapters/web/keys_templ.go +1 -1
M internal/adapters/web/mr.go +6 -1
M internal/adapters/web/mr.templ +37 -39
M internal/adapters/web/mr_templ.go +107 -123
M internal/adapters/web/overlays.templ +1 -0
M internal/adapters/web/overlays_templ.go +12 -12
M internal/adapters/web/pages.templ +3 -9
@@ -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) {
@@ -9,14 +11,6 @@ templ Home(l Layout, p HomePage) {
911 <div class="home" data-layout="cards">
1012 <section class="home-repos" aria-label="Repositories">
1113 <div class="home-bar">
12- if p.Inbox != nil {
13- <a class="btn inbox-btn" href={ templ.SafeURL(p.Inbox.Href) } title="inbox">
14- Inbox
15- if p.Inbox.Count >= 0 {
16- <span class="count">{ itoa(p.Inbox.Count) }</span>
17- }
18- </a>
19- }
2014 <div class="filter">
2115 <label for="home-filter" class="sr">filter</label>
2216 <input
@@ -57,7 +51,7 @@ templ Home(l Layout, p HomePage) {
5751 </button>
5852 </div>
5953 </div>
60- <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) }>
6155 for _, r := range p.Rows {
6256 @HomeRowView(r)
6357 }
@@ -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) {
@@ -9,14 +11,6 @@ templ Home(l Layout, p HomePage) {
9 <div class="home" data-layout="cards">11 <div class="home" data-layout="cards">
10 <section class="home-repos" aria-label="Repositories">12 <section class="home-repos" aria-label="Repositories">
11 <div class="home-bar">13 <div class="home-bar">
12- if p.Inbox != nil {
13- <a class="btn inbox-btn" href={ templ.SafeURL(p.Inbox.Href) } title="inbox">
14- Inbox
15- if p.Inbox.Count >= 0 {
16- <span class="count">{ itoa(p.Inbox.Count) }</span>
17- }
18- </a>
19- }
20 <div class="filter">14 <div class="filter">
21 <label for="home-filter" class="sr">filter</label>15 <label for="home-filter" class="sr">filter</label>
22 <input16 <input
@@ -57,7 +51,7 @@ templ Home(l Layout, p HomePage) {
57 </button>51 </button>
58 </div>52 </div>
59 </div>53 </div>
60- <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) }>
61 for _, r := range p.Rows {55 for _, r := range p.Rows {
62 @HomeRowView(r)56 @HomeRowView(r)
63 }57 }
M internal/adapters/web/pages_templ.go +205 -223
@@ -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) {
@@ -44,135 +46,115 @@ func Home(l Layout, p HomePage) templ.Component {
4446 }()
4547 }
4648 ctx = templ.InitializeContext(ctx)
47- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"sr\">Repositories</h1><div class=\"home\" data-layout=\"cards\"><section class=\"home-repos\" aria-label=\"Repositories\"><div class=\"home-bar\">")
49+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"sr\">Repositories</h1><div class=\"home\" data-layout=\"cards\"><section class=\"home-repos\" aria-label=\"Repositories\"><div class=\"home-bar\"><div class=\"filter\"><label for=\"home-filter\" class=\"sr\">filter</label> <input id=\"home-filter\" type=\"search\" data-filter=\"#repo-rows > li\" placeholder=\"Filter repositories…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><nav class=\"sort\" aria-label=\"Sort repositories\"><span class=\"mute\">sort</span> ")
4850 if templ_7745c5c3_Err != nil {
4951 return templ_7745c5c3_Err
5052 }
51- if p.Inbox != nil {
52- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a class=\"btn inbox-btn\" href=\"")
53+ for _, s := range p.Sorts {
54+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a href=\"")
5355 if templ_7745c5c3_Err != nil {
5456 return templ_7745c5c3_Err
5557 }
5658 var templ_7745c5c3_Var3 templ.SafeURL
57- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Inbox.Href))
59+ 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: 13, Col: 65}
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 {
6365 return templ_7745c5c3_Err
6466 }
65- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" title=\"inbox\">Inbox ")
66- if templ_7745c5c3_Err != nil {
67- return templ_7745c5c3_Err
68- }
69- if p.Inbox.Count >= 0 {
70- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"count\">")
71- if templ_7745c5c3_Err != nil {
72- return templ_7745c5c3_Err
73- }
74- var templ_7745c5c3_Var4 string
75- templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Inbox.Count))
76- if templ_7745c5c3_Err != nil {
77- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 16, Col: 49}
78- }
79- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
80- if templ_7745c5c3_Err != nil {
81- return templ_7745c5c3_Err
82- }
83- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span>")
84- if templ_7745c5c3_Err != nil {
85- return templ_7745c5c3_Err
86- }
87- }
88- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</a>")
89- if templ_7745c5c3_Err != nil {
90- return templ_7745c5c3_Err
91- }
92- }
93- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"filter\"><label for=\"home-filter\" class=\"sr\">filter</label> <input id=\"home-filter\" type=\"search\" data-filter=\"#repo-rows > li\" placeholder=\"Filter repositories…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><nav class=\"sort\" aria-label=\"Sort repositories\"><span class=\"mute\">sort</span> ")
94- if templ_7745c5c3_Err != nil {
95- return templ_7745c5c3_Err
96- }
97- for _, s := range p.Sorts {
98- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a href=\"")
99- if templ_7745c5c3_Err != nil {
100- return templ_7745c5c3_Err
101- }
102- var templ_7745c5c3_Var5 templ.SafeURL
103- templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href))
104- if templ_7745c5c3_Err != nil {
105- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 35, Col: 36}
106- }
107- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
108- if templ_7745c5c3_Err != nil {
109- return templ_7745c5c3_Err
110- }
111- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"")
67+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"")
11268 if templ_7745c5c3_Err != nil {
11369 return templ_7745c5c3_Err
11470 }
11571 if s.Mark != "" {
116- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " aria-current=\"true\"")
72+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " aria-current=\"true\"")
11773 if templ_7745c5c3_Err != nil {
11874 return templ_7745c5c3_Err
11975 }
12076 }
121- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, ">")
77+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, ">")
12278 if templ_7745c5c3_Err != nil {
12379 return templ_7745c5c3_Err
12480 }
125- var templ_7745c5c3_Var6 string
126- templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)
81+ var templ_7745c5c3_Var4 string
82+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)
12783 if templ_7745c5c3_Err != nil {
128- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 40, Col: 17}
84+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 34, Col: 17}
12985 }
130- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
86+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
13187 if templ_7745c5c3_Err != nil {
13288 return templ_7745c5c3_Err
13389 }
134- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " ")
90+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " ")
13591 if templ_7745c5c3_Err != nil {
13692 return templ_7745c5c3_Err
13793 }
13894 if s.Mark != "" {
139- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<span aria-hidden=\"true\">")
95+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span aria-hidden=\"true\">")
14096 if templ_7745c5c3_Err != nil {
14197 return templ_7745c5c3_Err
14298 }
143- var templ_7745c5c3_Var7 string
144- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(s.Mark)
99+ var templ_7745c5c3_Var5 string
100+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(s.Mark)
145101 if templ_7745c5c3_Err != nil {
146- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 42, Col: 42}
102+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 36, Col: 42}
147103 }
148- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
104+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
149105 if templ_7745c5c3_Err != nil {
150106 return templ_7745c5c3_Err
151107 }
152- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</span> <span class=\"sr\">")
108+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span> <span class=\"sr\">")
153109 if templ_7745c5c3_Err != nil {
154110 return templ_7745c5c3_Err
155111 }
156- var templ_7745c5c3_Var8 string
157- templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(", " + s.State)
112+ var templ_7745c5c3_Var6 string
113+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(", " + s.State)
158114 if templ_7745c5c3_Err != nil {
159- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 43, Col: 42}
115+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 37, Col: 42}
160116 }
161- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
117+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
162118 if templ_7745c5c3_Err != nil {
163119 return templ_7745c5c3_Err
164120 }
165- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span>")
121+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</span>")
166122 if templ_7745c5c3_Err != nil {
167123 return templ_7745c5c3_Err
168124 }
169125 }
170- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</a>")
126+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</a>")
171127 if templ_7745c5c3_Err != nil {
172128 return templ_7745c5c3_Err
173129 }
174130 }
175- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</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, "\">")
176158 if templ_7745c5c3_Err != nil {
177159 return templ_7745c5c3_Err
178160 }
@@ -182,17 +164,17 @@ func Home(l Layout, p HomePage) templ.Component {
182164 return templ_7745c5c3_Err
183165 }
184166 }
185- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</ul>")
167+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</ul>")
186168 if templ_7745c5c3_Err != nil {
187169 return templ_7745c5c3_Err
188170 }
189171 if len(p.Rows) == 0 {
190- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<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>")
191173 if templ_7745c5c3_Err != nil {
192174 return templ_7745c5c3_Err
193175 }
194176 }
195- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</section></div>")
177+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</section></div>")
196178 if templ_7745c5c3_Err != nil {
197179 return templ_7745c5c3_Err
198180 }
@@ -281,51 +263,51 @@ func latestTagFact(t LatestTag) templ.Component {
281263 templ_7745c5c3_Var11 = templ.NopComponent
282264 }
283265 ctx = templ.ClearChildren(ctx)
284- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<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\">")
285267 if templ_7745c5c3_Err != nil {
286268 return templ_7745c5c3_Err
287269 }
288270 if t.Name != "" {
289- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<a class=\"tag-name\" href=\"")
271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<a class=\"tag-name\" href=\"")
290272 if templ_7745c5c3_Err != nil {
291273 return templ_7745c5c3_Err
292274 }
293275 var templ_7745c5c3_Var12 templ.SafeURL
294276 templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.Href))
295277 if templ_7745c5c3_Err != nil {
296- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 51}
278+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 51}
297279 }
298280 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
299281 if templ_7745c5c3_Err != nil {
300282 return templ_7745c5c3_Err
301283 }
302- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" title=\"")
284+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" title=\"")
303285 if templ_7745c5c3_Err != nil {
304286 return templ_7745c5c3_Err
305287 }
306288 var templ_7745c5c3_Var13 string
307289 templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Subject)
308290 if templ_7745c5c3_Err != nil {
309- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 71}
291+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 71}
310292 }
311293 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
312294 if templ_7745c5c3_Err != nil {
313295 return templ_7745c5c3_Err
314296 }
315- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
316298 if templ_7745c5c3_Err != nil {
317299 return templ_7745c5c3_Err
318300 }
319301 var templ_7745c5c3_Var14 string
320302 templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)
321303 if templ_7745c5c3_Err != nil {
322- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 82}
304+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 82}
323305 }
324306 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
325307 if templ_7745c5c3_Err != nil {
326308 return templ_7745c5c3_Err
327309 }
328- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</a> ")
310+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</a> ")
329311 if templ_7745c5c3_Err != nil {
330312 return templ_7745c5c3_Err
331313 }
@@ -333,101 +315,101 @@ func latestTagFact(t LatestTag) templ.Component {
333315 var templ_7745c5c3_Var15 string
334316 templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
335317 if templ_7745c5c3_Err != nil {
336- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 12}
318+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 12}
337319 }
338320 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
339321 if templ_7745c5c3_Err != nil {
340322 return templ_7745c5c3_Err
341323 }
342- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<time datetime=\"")
324+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<time datetime=\"")
343325 if templ_7745c5c3_Err != nil {
344326 return templ_7745c5c3_Err
345327 }
346328 var templ_7745c5c3_Var16 string
347329 templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
348330 if templ_7745c5c3_Err != nil {
349- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 39}
331+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 39}
350332 }
351333 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
352334 if templ_7745c5c3_Err != nil {
353335 return templ_7745c5c3_Err
354336 }
355- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" title=\"")
337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" title=\"")
356338 if templ_7745c5c3_Err != nil {
357339 return templ_7745c5c3_Err
358340 }
359341 var templ_7745c5c3_Var17 string
360342 templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
361343 if templ_7745c5c3_Err != nil {
362- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 58}
344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 58}
363345 }
364346 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
365347 if templ_7745c5c3_Err != nil {
366348 return templ_7745c5c3_Err
367349 }
368- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\">")
350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
369351 if templ_7745c5c3_Err != nil {
370352 return templ_7745c5c3_Err
371353 }
372354 var templ_7745c5c3_Var18 string
373355 templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(t.When)
374356 if templ_7745c5c3_Err != nil {
375- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 69}
357+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 69}
376358 }
377359 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
378360 if templ_7745c5c3_Err != nil {
379361 return templ_7745c5c3_Err
380362 }
381- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</time>")
363+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</time>")
382364 if templ_7745c5c3_Err != nil {
383365 return templ_7745c5c3_Err
384366 }
385367 }
386- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ")
368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
387369 if templ_7745c5c3_Err != nil {
388370 return templ_7745c5c3_Err
389371 }
390372 var templ_7745c5c3_Var19 string
391373 templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
392374 if templ_7745c5c3_Err != nil {
393- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 94, Col: 11}
375+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 11}
394376 }
395377 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
396378 if templ_7745c5c3_Err != nil {
397379 return templ_7745c5c3_Err
398380 }
399- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ")
381+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ")
400382 if templ_7745c5c3_Err != nil {
401383 return templ_7745c5c3_Err
402384 }
403385 }
404- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<a class=\"all-tags\" href=\"")
386+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<a class=\"all-tags\" href=\"")
405387 if templ_7745c5c3_Err != nil {
406388 return templ_7745c5c3_Err
407389 }
408390 var templ_7745c5c3_Var20 templ.SafeURL
409391 templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.RefsHref))
410392 if templ_7745c5c3_Err != nil {
411- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 96, Col: 54}
393+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 54}
412394 }
413395 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
414396 if templ_7745c5c3_Err != nil {
415397 return templ_7745c5c3_Err
416398 }
417- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\">")
399+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
418400 if templ_7745c5c3_Err != nil {
419401 return templ_7745c5c3_Err
420402 }
421403 var templ_7745c5c3_Var21 string
422404 templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(plural(t.Count, "tag", "tags"))
423405 if templ_7745c5c3_Err != nil {
424- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 96, Col: 89}
406+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 89}
425407 }
426408 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
427409 if templ_7745c5c3_Err != nil {
428410 return templ_7745c5c3_Err
429411 }
430- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</a></dd>")
412+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</a></dd>")
431413 if templ_7745c5c3_Err != nil {
432414 return templ_7745c5c3_Err
433415 }
@@ -456,7 +438,7 @@ func summaryRail(p SummaryPage) templ.Component {
456438 templ_7745c5c3_Var22 = templ.NopComponent
457439 }
458440 ctx = templ.ClearChildren(ctx)
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<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>")
460442 if templ_7745c5c3_Err != nil {
461443 return templ_7745c5c3_Err
462444 }
@@ -464,25 +446,25 @@ func summaryRail(p SummaryPage) templ.Component {
464446 if templ_7745c5c3_Err != nil {
465447 return templ_7745c5c3_Err
466448 }
467- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</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\">")
468450 if templ_7745c5c3_Err != nil {
469451 return templ_7745c5c3_Err
470452 }
471453 for _, f := range p.Facts {
472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<dt>")
454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<dt>")
473455 if templ_7745c5c3_Err != nil {
474456 return templ_7745c5c3_Err
475457 }
476458 var templ_7745c5c3_Var23 string
477459 templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(f.Key)
478460 if templ_7745c5c3_Err != nil {
479- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 107, Col: 15}
461+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 101, Col: 15}
480462 }
481463 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
482464 if templ_7745c5c3_Err != nil {
483465 return templ_7745c5c3_Err
484466 }
485- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</dt>")
467+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</dt>")
486468 if templ_7745c5c3_Err != nil {
487469 return templ_7745c5c3_Err
488470 }
@@ -497,25 +479,25 @@ func summaryRail(p SummaryPage) templ.Component {
497479 return templ_7745c5c3_Err
498480 }
499481 }
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</dl></section>")
482+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</dl></section>")
501483 if templ_7745c5c3_Err != nil {
502484 return templ_7745c5c3_Err
503485 }
504486 if len(p.Recent) > 0 {
505- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<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=\"")
506488 if templ_7745c5c3_Err != nil {
507489 return templ_7745c5c3_Err
508490 }
509491 var templ_7745c5c3_Var24 templ.SafeURL
510492 templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
511493 if templ_7745c5c3_Err != nil {
512- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 116, Col: 105}
494+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 110, Col: 105}
513495 }
514496 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
515497 if templ_7745c5c3_Err != nil {
516498 return templ_7745c5c3_Err
517499 }
518- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\">View all</a></h2>")
500+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">View all</a></h2>")
519501 if templ_7745c5c3_Err != nil {
520502 return templ_7745c5c3_Err
521503 }
@@ -523,7 +505,7 @@ func summaryRail(p SummaryPage) templ.Component {
523505 if templ_7745c5c3_Err != nil {
524506 return templ_7745c5c3_Err
525507 }
526- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</section>")
508+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</section>")
527509 if templ_7745c5c3_Err != nil {
528510 return templ_7745c5c3_Err
529511 }
@@ -535,7 +517,7 @@ func summaryRail(p SummaryPage) templ.Component {
535517 }
536518 }
537519 if len(p.Outline) > 0 && p.Readme != nil {
538- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<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>")
539521 if templ_7745c5c3_Err != nil {
540522 return templ_7745c5c3_Err
541523 }
@@ -543,7 +525,7 @@ func summaryRail(p SummaryPage) templ.Component {
543525 if templ_7745c5c3_Err != nil {
544526 return templ_7745c5c3_Err
545527 }
546- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</section>")
528+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</section>")
547529 if templ_7745c5c3_Err != nil {
548530 return templ_7745c5c3_Err
549531 }
@@ -586,7 +568,7 @@ func Refs(l Layout, p RefsPage) templ.Component {
586568 }()
587569 }
588570 ctx = templ.InitializeContext(ctx)
589- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<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>")
590572 if templ_7745c5c3_Err != nil {
591573 return templ_7745c5c3_Err
592574 }
@@ -594,25 +576,25 @@ func Refs(l Layout, p RefsPage) templ.Component {
594576 if templ_7745c5c3_Err != nil {
595577 return templ_7745c5c3_Err
596578 }
597- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</table>")
579+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</table>")
598580 if templ_7745c5c3_Err != nil {
599581 return templ_7745c5c3_Err
600582 }
601583 if p.Note != "" {
602- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<p class=\"note\">")
584+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<p class=\"note\">")
603585 if templ_7745c5c3_Err != nil {
604586 return templ_7745c5c3_Err
605587 }
606588 var templ_7745c5c3_Var27 string
607589 templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
608590 if templ_7745c5c3_Err != nil {
609- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 149, Col: 27}
591+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 143, Col: 27}
610592 }
611593 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
612594 if templ_7745c5c3_Err != nil {
613595 return templ_7745c5c3_Err
614596 }
615- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</p>")
597+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</p>")
616598 if templ_7745c5c3_Err != nil {
617599 return templ_7745c5c3_Err
618600 }
@@ -648,97 +630,97 @@ func refRow(kind string, r RefRow) templ.Component {
648630 templ_7745c5c3_Var28 = templ.NopComponent
649631 }
650632 ctx = templ.ClearChildren(ctx)
651- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<tr")
633+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<tr")
652634 if templ_7745c5c3_Err != nil {
653635 return templ_7745c5c3_Err
654636 }
655637 if r.Cursor {
656- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " data-cursor")
638+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " data-cursor")
657639 if templ_7745c5c3_Err != nil {
658640 return templ_7745c5c3_Err
659641 }
660642 }
661- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "><td class=\"c-kind mute\">")
643+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "><td class=\"c-kind mute\">")
662644 if templ_7745c5c3_Err != nil {
663645 return templ_7745c5c3_Err
664646 }
665647 var templ_7745c5c3_Var29 string
666648 templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
667649 if templ_7745c5c3_Err != nil {
668- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 156, Col: 32}
650+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 32}
669651 }
670652 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
671653 if templ_7745c5c3_Err != nil {
672654 return templ_7745c5c3_Err
673655 }
674- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-name\"><a href=\"")
656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</td><td class=\"c-name\"><a href=\"")
675657 if templ_7745c5c3_Err != nil {
676658 return templ_7745c5c3_Err
677659 }
678660 var templ_7745c5c3_Var30 templ.SafeURL
679661 templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
680662 if templ_7745c5c3_Err != nil {
681- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 158, Col: 34}
663+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 34}
682664 }
683665 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
684666 if templ_7745c5c3_Err != nil {
685667 return templ_7745c5c3_Err
686668 }
687- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\">")
669+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\">")
688670 if templ_7745c5c3_Err != nil {
689671 return templ_7745c5c3_Err
690672 }
691673 var templ_7745c5c3_Var31 string
692674 templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
693675 if templ_7745c5c3_Err != nil {
694- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 158, Col: 45}
676+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 45}
695677 }
696678 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
697679 if templ_7745c5c3_Err != nil {
698680 return templ_7745c5c3_Err
699681 }
700- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</a> ")
682+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</a> ")
701683 if templ_7745c5c3_Err != nil {
702684 return templ_7745c5c3_Err
703685 }
704686 if r.Default {
705- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<span class=\"pill\">default</span>")
687+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"pill\">default</span>")
706688 if templ_7745c5c3_Err != nil {
707689 return templ_7745c5c3_Err
708690 }
709691 }
710- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</td><td class=\"c-sha\">")
692+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-sha\">")
711693 if templ_7745c5c3_Err != nil {
712694 return templ_7745c5c3_Err
713695 }
714696 if r.CHref != "" {
715- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<a href=\"")
697+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a href=\"")
716698 if templ_7745c5c3_Err != nil {
717699 return templ_7745c5c3_Err
718700 }
719701 var templ_7745c5c3_Var32 templ.SafeURL
720702 templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
721703 if templ_7745c5c3_Err != nil {
722- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 36}
704+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 36}
723705 }
724706 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
725707 if templ_7745c5c3_Err != nil {
726708 return templ_7745c5c3_Err
727709 }
728- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\">")
710+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\">")
729711 if templ_7745c5c3_Err != nil {
730712 return templ_7745c5c3_Err
731713 }
732714 var templ_7745c5c3_Var33 string
733715 templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
734716 if templ_7745c5c3_Err != nil {
735- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 48}
717+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 48}
736718 }
737719 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
738720 if templ_7745c5c3_Err != nil {
739721 return templ_7745c5c3_Err
740722 }
741- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</a>")
723+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</a>")
742724 if templ_7745c5c3_Err != nil {
743725 return templ_7745c5c3_Err
744726 }
@@ -746,7 +728,7 @@ func refRow(kind string, r RefRow) templ.Component {
746728 var templ_7745c5c3_Var34 string
747729 templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
748730 if templ_7745c5c3_Err != nil {
749- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 167, Col: 13}
731+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 13}
750732 }
751733 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
752734 if templ_7745c5c3_Err != nil {
@@ -757,69 +739,69 @@ func refRow(kind string, r RefRow) templ.Component {
757739 if templ_7745c5c3_Err != nil {
758740 return templ_7745c5c3_Err
759741 }
760- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</td><td class=\"c-sub\">")
742+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</td><td class=\"c-sub\">")
761743 if templ_7745c5c3_Err != nil {
762744 return templ_7745c5c3_Err
763745 }
764746 var templ_7745c5c3_Var35 string
765747 templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
766748 if templ_7745c5c3_Err != nil {
767- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 171, Col: 31}
749+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 31}
768750 }
769751 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
770752 if templ_7745c5c3_Err != nil {
771753 return templ_7745c5c3_Err
772754 }
773- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</td><td class=\"c-when dim\">")
755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</td><td class=\"c-when dim\">")
774756 if templ_7745c5c3_Err != nil {
775757 return templ_7745c5c3_Err
776758 }
777759 if r.WhenAt != "" {
778- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<time datetime=\"")
760+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<time datetime=\"")
779761 if templ_7745c5c3_Err != nil {
780762 return templ_7745c5c3_Err
781763 }
782764 var templ_7745c5c3_Var36 string
783765 templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
784766 if templ_7745c5c3_Err != nil {
785- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 174, Col: 29}
767+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 29}
786768 }
787769 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
788770 if templ_7745c5c3_Err != nil {
789771 return templ_7745c5c3_Err
790772 }
791- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" title=\"")
773+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" title=\"")
792774 if templ_7745c5c3_Err != nil {
793775 return templ_7745c5c3_Err
794776 }
795777 var templ_7745c5c3_Var37 string
796778 templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
797779 if templ_7745c5c3_Err != nil {
798- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 174, Col: 48}
780+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 48}
799781 }
800782 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var37)
801783 if templ_7745c5c3_Err != nil {
802784 return templ_7745c5c3_Err
803785 }
804- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\">")
786+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\">")
805787 if templ_7745c5c3_Err != nil {
806788 return templ_7745c5c3_Err
807789 }
808790 var templ_7745c5c3_Var38 string
809791 templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
810792 if templ_7745c5c3_Err != nil {
811- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 174, Col: 59}
793+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 59}
812794 }
813795 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
814796 if templ_7745c5c3_Err != nil {
815797 return templ_7745c5c3_Err
816798 }
817- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</time>")
799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</time>")
818800 if templ_7745c5c3_Err != nil {
819801 return templ_7745c5c3_Err
820802 }
821803 }
822- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</td></tr>")
804+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</td></tr>")
823805 if templ_7745c5c3_Err != nil {
824806 return templ_7745c5c3_Err
825807 }
@@ -848,7 +830,7 @@ func refsRail(p RefsPage) templ.Component {
848830 templ_7745c5c3_Var39 = templ.NopComponent
849831 }
850832 ctx = templ.ClearChildren(ctx)
851- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "<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>")
852834 if templ_7745c5c3_Err != nil {
853835 return templ_7745c5c3_Err
854836 }
@@ -856,7 +838,7 @@ func refsRail(p RefsPage) templ.Component {
856838 if templ_7745c5c3_Err != nil {
857839 return templ_7745c5c3_Err
858840 }
859- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "</section>")
841+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</section>")
860842 if templ_7745c5c3_Err != nil {
861843 return templ_7745c5c3_Err
862844 }
@@ -898,51 +880,51 @@ func State(l Layout, p StatePage) templ.Component {
898880 }()
899881 }
900882 ctx = templ.InitializeContext(ctx)
901- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<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\">")
902884 if templ_7745c5c3_Err != nil {
903885 return templ_7745c5c3_Err
904886 }
905887 var templ_7745c5c3_Var42 string
906888 templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
907889 if templ_7745c5c3_Err != nil {
908- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 191, Col: 30}
890+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 185, Col: 30}
909891 }
910892 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
911893 if templ_7745c5c3_Err != nil {
912894 return templ_7745c5c3_Err
913895 }
914- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</p><h1 id=\"state-title\">")
896+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</p><h1 id=\"state-title\">")
915897 if templ_7745c5c3_Err != nil {
916898 return templ_7745c5c3_Err
917899 }
918900 var templ_7745c5c3_Var43 string
919901 templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
920902 if templ_7745c5c3_Err != nil {
921- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 192, Col: 33}
903+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 186, Col: 33}
922904 }
923905 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
924906 if templ_7745c5c3_Err != nil {
925907 return templ_7745c5c3_Err
926908 }
927- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</h1>")
909+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</h1>")
928910 if templ_7745c5c3_Err != nil {
929911 return templ_7745c5c3_Err
930912 }
931913 for _, line := range p.Lines {
932- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<p class=\"dim\">")
914+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<p class=\"dim\">")
933915 if templ_7745c5c3_Err != nil {
934916 return templ_7745c5c3_Err
935917 }
936918 var templ_7745c5c3_Var44 string
937919 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(line)
938920 if templ_7745c5c3_Err != nil {
939- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 25}
921+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 188, Col: 25}
940922 }
941923 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
942924 if templ_7745c5c3_Err != nil {
943925 return templ_7745c5c3_Err
944926 }
945- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</p>")
927+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</p>")
946928 if templ_7745c5c3_Err != nil {
947929 return templ_7745c5c3_Err
948930 }
@@ -953,43 +935,43 @@ func State(l Layout, p StatePage) templ.Component {
953935 return templ_7745c5c3_Err
954936 }
955937 }
956- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<p class=\"state-back\">")
938+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"state-back\">")
957939 if templ_7745c5c3_Err != nil {
958940 return templ_7745c5c3_Err
959941 }
960942 for _, b := range p.Back {
961- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<a href=\"")
943+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<a href=\"")
962944 if templ_7745c5c3_Err != nil {
963945 return templ_7745c5c3_Err
964946 }
965947 var templ_7745c5c3_Var45 templ.SafeURL
966948 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
967949 if templ_7745c5c3_Err != nil {
968- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 201, Col: 36}
950+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 36}
969951 }
970952 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
971953 if templ_7745c5c3_Err != nil {
972954 return templ_7745c5c3_Err
973955 }
974- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "\">")
956+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\">")
975957 if templ_7745c5c3_Err != nil {
976958 return templ_7745c5c3_Err
977959 }
978960 var templ_7745c5c3_Var46 string
979961 templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
980962 if templ_7745c5c3_Err != nil {
981- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 201, Col: 48}
963+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 48}
982964 }
983965 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
984966 if templ_7745c5c3_Err != nil {
985967 return templ_7745c5c3_Err
986968 }
987- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</a>")
969+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</a>")
988970 if templ_7745c5c3_Err != nil {
989971 return templ_7745c5c3_Err
990972 }
991973 }
992- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</p></section>")
974+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</p></section>")
993975 if templ_7745c5c3_Err != nil {
994976 return templ_7745c5c3_Err
995977 }
@@ -1041,69 +1023,69 @@ func Login(l Layout, p LoginPage) templ.Component {
10411023 }()
10421024 }
10431025 ctx = templ.InitializeContext(ctx)
1044- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<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\">")
10451027 if templ_7745c5c3_Err != nil {
10461028 return templ_7745c5c3_Err
10471029 }
10481030 var templ_7745c5c3_Var49 string
10491031 templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
10501032 if templ_7745c5c3_Err != nil {
1051- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 216, Col: 33}
1033+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 210, Col: 33}
10521034 }
10531035 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
10541036 if templ_7745c5c3_Err != nil {
10551037 return templ_7745c5c3_Err
10561038 }
1057- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "</h1>")
1039+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</h1>")
10581040 if templ_7745c5c3_Err != nil {
10591041 return templ_7745c5c3_Err
10601042 }
10611043 for _, line := range p.Lines {
1062- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<p class=\"dim\">")
1044+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<p class=\"dim\">")
10631045 if templ_7745c5c3_Err != nil {
10641046 return templ_7745c5c3_Err
10651047 }
10661048 var templ_7745c5c3_Var50 string
10671049 templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(line)
10681050 if templ_7745c5c3_Err != nil {
1069- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 218, Col: 25}
1051+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 212, Col: 25}
10701052 }
10711053 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
10721054 if templ_7745c5c3_Err != nil {
10731055 return templ_7745c5c3_Err
10741056 }
1075- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</p>")
1057+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</p>")
10761058 if templ_7745c5c3_Err != nil {
10771059 return templ_7745c5c3_Err
10781060 }
10791061 }
1080- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<form class=\"state-act\" method=\"post\" action=\"")
1062+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<form class=\"state-act\" method=\"post\" action=\"")
10811063 if templ_7745c5c3_Err != nil {
10821064 return templ_7745c5c3_Err
10831065 }
10841066 var templ_7745c5c3_Var51 templ.SafeURL
10851067 templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
10861068 if templ_7745c5c3_Err != nil {
1087- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 220, Col: 73}
1069+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 214, Col: 73}
10881070 }
10891071 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
10901072 if templ_7745c5c3_Err != nil {
10911073 return templ_7745c5c3_Err
10921074 }
1093- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" 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\">")
10941076 if templ_7745c5c3_Err != nil {
10951077 return templ_7745c5c3_Err
10961078 }
10971079 var templ_7745c5c3_Var52 string
10981080 templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
10991081 if templ_7745c5c3_Err != nil {
1100- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 221, Col: 56}
1082+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 215, Col: 56}
11011083 }
11021084 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
11031085 if templ_7745c5c3_Err != nil {
11041086 return templ_7745c5c3_Err
11051087 }
1106- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "</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>")
11071089 if templ_7745c5c3_Err != nil {
11081090 return templ_7745c5c3_Err
11091091 }
@@ -1152,82 +1134,82 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component {
11521134 }()
11531135 }
11541136 ctx = templ.InitializeContext(ctx)
1155- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<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\">")
11561138 if templ_7745c5c3_Err != nil {
11571139 return templ_7745c5c3_Err
11581140 }
11591141 var templ_7745c5c3_Var55 string
11601142 templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
11611143 if templ_7745c5c3_Err != nil {
1162- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 33}
1144+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 33}
11631145 }
11641146 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
11651147 if templ_7745c5c3_Err != nil {
11661148 return templ_7745c5c3_Err
11671149 }
1168- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</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=\"")
11691151 if templ_7745c5c3_Err != nil {
11701152 return templ_7745c5c3_Err
11711153 }
11721154 var templ_7745c5c3_Var56 string
11731155 templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
11741156 if templ_7745c5c3_Err != nil {
1175- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 87}
1157+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 87}
11761158 }
11771159 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)
11781160 if templ_7745c5c3_Err != nil {
11791161 return templ_7745c5c3_Err
11801162 }
1181- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" data-finish=\"")
1163+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" data-finish=\"")
11821164 if templ_7745c5c3_Err != nil {
11831165 return templ_7745c5c3_Err
11841166 }
11851167 var templ_7745c5c3_Var57 string
11861168 templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
11871169 if templ_7745c5c3_Err != nil {
1188- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 112}
1170+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 112}
11891171 }
11901172 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
11911173 if templ_7745c5c3_Err != nil {
11921174 return templ_7745c5c3_Err
11931175 }
1194- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "\">")
1176+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\">")
11951177 if templ_7745c5c3_Err != nil {
11961178 return templ_7745c5c3_Err
11971179 }
11981180 var templ_7745c5c3_Var58 string
11991181 templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
12001182 if templ_7745c5c3_Err != nil {
1201- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 125}
1183+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 125}
12021184 }
12031185 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
12041186 if templ_7745c5c3_Err != nil {
12051187 return templ_7745c5c3_Err
12061188 }
1207- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</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>")
12081190 if templ_7745c5c3_Err != nil {
12091191 return templ_7745c5c3_Err
12101192 }
12111193 for _, line := range p.Lines {
1212- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<p class=\"dim\">")
1194+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<p class=\"dim\">")
12131195 if templ_7745c5c3_Err != nil {
12141196 return templ_7745c5c3_Err
12151197 }
12161198 var templ_7745c5c3_Var59 string
12171199 templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(line)
12181200 if templ_7745c5c3_Err != nil {
1219- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 239, Col: 25}
1201+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 25}
12201202 }
12211203 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
12221204 if templ_7745c5c3_Err != nil {
12231205 return templ_7745c5c3_Err
12241206 }
1225- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</p>")
1207+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</p>")
12261208 if templ_7745c5c3_Err != nil {
12271209 return templ_7745c5c3_Err
12281210 }
12291211 }
1230- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<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>")
12311213 if templ_7745c5c3_Err != nil {
12321214 return templ_7745c5c3_Err
12331215 }
@@ -1275,225 +1257,225 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component {
12751257 }()
12761258 }
12771259 ctx = templ.InitializeContext(ctx)
1278- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "<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\">")
12791261 if templ_7745c5c3_Err != nil {
12801262 return templ_7745c5c3_Err
12811263 }
12821264 var templ_7745c5c3_Var62 string
12831265 templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows)))
12841266 if templ_7745c5c3_Err != nil {
1285- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 249, Col: 81}
1267+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 243, Col: 81}
12861268 }
12871269 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
12881270 if templ_7745c5c3_Err != nil {
12891271 return templ_7745c5c3_Err
12901272 }
1291- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</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>")
12921274 if templ_7745c5c3_Err != nil {
12931275 return templ_7745c5c3_Err
12941276 }
12951277 if len(p.Rows) > 0 {
1296- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<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>")
12971279 if templ_7745c5c3_Err != nil {
12981280 return templ_7745c5c3_Err
12991281 }
13001282 for _, r := range p.Rows {
1301- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<tr><td class=\"c-name\">")
1283+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<tr><td class=\"c-name\">")
13021284 if templ_7745c5c3_Err != nil {
13031285 return templ_7745c5c3_Err
13041286 }
13051287 var templ_7745c5c3_Var63 string
13061288 templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
13071289 if templ_7745c5c3_Err != nil {
1308- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 265, Col: 34}
1290+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 259, Col: 34}
13091291 }
13101292 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
13111293 if templ_7745c5c3_Err != nil {
13121294 return templ_7745c5c3_Err
13131295 }
1314- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</td><td class=\"c-when nowrap\"><time")
1296+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</td><td class=\"c-when nowrap\"><time")
13151297 if templ_7745c5c3_Err != nil {
13161298 return templ_7745c5c3_Err
13171299 }
13181300 if r.CreatedAt != "" {
1319- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, " datetime=\"")
1301+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, " datetime=\"")
13201302 if templ_7745c5c3_Err != nil {
13211303 return templ_7745c5c3_Err
13221304 }
13231305 var templ_7745c5c3_Var64 string
13241306 templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt)
13251307 if templ_7745c5c3_Err != nil {
1326- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 269, Col: 32}
1308+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 263, Col: 32}
13271309 }
13281310 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
13291311 if templ_7745c5c3_Err != nil {
13301312 return templ_7745c5c3_Err
13311313 }
1332- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\"")
1314+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\"")
13331315 if templ_7745c5c3_Err != nil {
13341316 return templ_7745c5c3_Err
13351317 }
13361318 }
1337- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, ">")
1319+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, ">")
13381320 if templ_7745c5c3_Err != nil {
13391321 return templ_7745c5c3_Err
13401322 }
13411323 var templ_7745c5c3_Var65 string
13421324 templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created)
13431325 if templ_7745c5c3_Err != nil {
1344- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 271, Col: 20}
1326+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 265, Col: 20}
13451327 }
13461328 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
13471329 if templ_7745c5c3_Err != nil {
13481330 return templ_7745c5c3_Err
13491331 }
1350- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</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")
13511333 if templ_7745c5c3_Err != nil {
13521334 return templ_7745c5c3_Err
13531335 }
13541336 if r.LastUsedAt != "" {
1355- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, " datetime=\"")
1337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, " datetime=\"")
13561338 if templ_7745c5c3_Err != nil {
13571339 return templ_7745c5c3_Err
13581340 }
13591341 var templ_7745c5c3_Var66 string
13601342 templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt)
13611343 if templ_7745c5c3_Err != nil {
1362- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 276, Col: 33}
1344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 270, Col: 33}
13631345 }
13641346 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
13651347 if templ_7745c5c3_Err != nil {
13661348 return templ_7745c5c3_Err
13671349 }
1368- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "\"")
1350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\"")
13691351 if templ_7745c5c3_Err != nil {
13701352 return templ_7745c5c3_Err
13711353 }
13721354 }
1373- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, ">")
1355+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, ">")
13741356 if templ_7745c5c3_Err != nil {
13751357 return templ_7745c5c3_Err
13761358 }
13771359 var templ_7745c5c3_Var67 string
13781360 templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed)
13791361 if templ_7745c5c3_Err != nil {
1380- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 278, Col: 21}
1362+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 272, Col: 21}
13811363 }
13821364 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
13831365 if templ_7745c5c3_Err != nil {
13841366 return templ_7745c5c3_Err
13851367 }
1386- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</time></td><td class=\"c-sync\">")
1368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</time></td><td class=\"c-sync\">")
13871369 if templ_7745c5c3_Err != nil {
13881370 return templ_7745c5c3_Err
13891371 }
13901372 if r.Synced {
1391- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "yes")
1373+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "yes")
13921374 if templ_7745c5c3_Err != nil {
13931375 return templ_7745c5c3_Err
13941376 }
13951377 } else {
1396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "no")
1378+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "no")
13971379 if templ_7745c5c3_Err != nil {
13981380 return templ_7745c5c3_Err
13991381 }
14001382 }
1401- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "</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=\"")
14021384 if templ_7745c5c3_Err != nil {
14031385 return templ_7745c5c3_Err
14041386 }
14051387 var templ_7745c5c3_Var68 templ.SafeURL
14061388 templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove))
14071389 if templ_7745c5c3_Err != nil {
1408- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 288, Col: 60}
1390+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 282, Col: 60}
14091391 }
14101392 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
14111393 if templ_7745c5c3_Err != nil {
14121394 return templ_7745c5c3_Err
14131395 }
1414- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "\"><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=\"")
14151397 if templ_7745c5c3_Err != nil {
14161398 return templ_7745c5c3_Err
14171399 }
14181400 var templ_7745c5c3_Var69 string
14191401 templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name)
14201402 if templ_7745c5c3_Err != nil {
1421- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 289, Col: 89}
1403+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 283, Col: 89}
14221404 }
14231405 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
14241406 if templ_7745c5c3_Err != nil {
14251407 return templ_7745c5c3_Err
14261408 }
1427- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\">Remove</button></form></td></tr>")
1409+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">Remove</button></form></td></tr>")
14281410 if templ_7745c5c3_Err != nil {
14291411 return templ_7745c5c3_Err
14301412 }
14311413 }
1432- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</tbody></table>")
1414+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</tbody></table>")
14331415 if templ_7745c5c3_Err != nil {
14341416 return templ_7745c5c3_Err
14351417 }
14361418 } else {
1437- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<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>")
14381420 if templ_7745c5c3_Err != nil {
14391421 return templ_7745c5c3_Err
14401422 }
14411423 }
1442- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, " ")
1424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, " ")
14431425 if templ_7745c5c3_Err != nil {
14441426 return templ_7745c5c3_Err
14451427 }
14461428 if p.Full {
1447- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "<p class=\"dim\">")
1429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<p class=\"dim\">")
14481430 if templ_7745c5c3_Err != nil {
14491431 return templ_7745c5c3_Err
14501432 }
14511433 var templ_7745c5c3_Var70 string
14521434 templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max))
14531435 if templ_7745c5c3_Err != nil {
1454- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 300, Col: 31}
1436+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 294, Col: 31}
14551437 }
14561438 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
14571439 if templ_7745c5c3_Err != nil {
14581440 return templ_7745c5c3_Err
14591441 }
1460- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, " 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>")
14611443 if templ_7745c5c3_Err != nil {
14621444 return templ_7745c5c3_Err
14631445 }
14641446 } else {
1465- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "<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=\"")
14661448 if templ_7745c5c3_Err != nil {
14671449 return templ_7745c5c3_Err
14681450 }
14691451 var templ_7745c5c3_Var71 string
14701452 templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
14711453 if templ_7745c5c3_Err != nil {
1472- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 305, Col: 90}
1454+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 90}
14731455 }
14741456 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)
14751457 if templ_7745c5c3_Err != nil {
14761458 return templ_7745c5c3_Err
14771459 }
1478- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "\" data-finish=\"")
1460+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" data-finish=\"")
14791461 if templ_7745c5c3_Err != nil {
14801462 return templ_7745c5c3_Err
14811463 }
14821464 var templ_7745c5c3_Var72 string
14831465 templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
14841466 if templ_7745c5c3_Err != nil {
1485- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 305, Col: 115}
1467+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 115}
14861468 }
14871469 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
14881470 if templ_7745c5c3_Err != nil {
14891471 return templ_7745c5c3_Err
14901472 }
1491- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "\">Add a passkey</button></form>")
1473+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\">Add a passkey</button></form>")
14921474 if templ_7745c5c3_Err != nil {
14931475 return templ_7745c5c3_Err
14941476 }
14951477 }
1496- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, " <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>")
14971479 if templ_7745c5c3_Err != nil {
14981480 return templ_7745c5c3_Err
14991481 }
@@ -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) {
@@ -44,135 +46,115 @@ func Home(l Layout, p HomePage) templ.Component {
44 }()46 }()
45 }47 }
46 ctx = templ.InitializeContext(ctx)48 ctx = templ.InitializeContext(ctx)
47- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"sr\">Repositories</h1><div class=\"home\" data-layout=\"cards\"><section class=\"home-repos\" aria-label=\"Repositories\"><div class=\"home-bar\">")49+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"sr\">Repositories</h1><div class=\"home\" data-layout=\"cards\"><section class=\"home-repos\" aria-label=\"Repositories\"><div class=\"home-bar\"><div class=\"filter\"><label for=\"home-filter\" class=\"sr\">filter</label> <input id=\"home-filter\" type=\"search\" data-filter=\"#repo-rows > li\" placeholder=\"Filter repositories…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><nav class=\"sort\" aria-label=\"Sort repositories\"><span class=\"mute\">sort</span> ")
48 if templ_7745c5c3_Err != nil {50 if templ_7745c5c3_Err != nil {
49 return templ_7745c5c3_Err51 return templ_7745c5c3_Err
50 }52 }
51- if p.Inbox != nil {53+ for _, s := range p.Sorts {
52- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a class=\"btn inbox-btn\" href=\"")54+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a href=\"")
53 if templ_7745c5c3_Err != nil {55 if templ_7745c5c3_Err != nil {
54 return templ_7745c5c3_Err56 return templ_7745c5c3_Err
55 }57 }
56 var templ_7745c5c3_Var3 templ.SafeURL58 var templ_7745c5c3_Var3 templ.SafeURL
57- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Inbox.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: 13, Col: 65}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 {
63 return templ_7745c5c3_Err65 return templ_7745c5c3_Err
64 }66 }
65- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" title=\"inbox\">Inbox ")67+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"")
66- if templ_7745c5c3_Err != nil {
67- return templ_7745c5c3_Err
68- }
69- if p.Inbox.Count >= 0 {
70- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"count\">")
71- if templ_7745c5c3_Err != nil {
72- return templ_7745c5c3_Err
73- }
74- var templ_7745c5c3_Var4 string
75- templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Inbox.Count))
76- if templ_7745c5c3_Err != nil {
77- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 16, Col: 49}
78- }
79- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
80- if templ_7745c5c3_Err != nil {
81- return templ_7745c5c3_Err
82- }
83- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span>")
84- if templ_7745c5c3_Err != nil {
85- return templ_7745c5c3_Err
86- }
87- }
88- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</a>")
89- if templ_7745c5c3_Err != nil {
90- return templ_7745c5c3_Err
91- }
92- }
93- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"filter\"><label for=\"home-filter\" class=\"sr\">filter</label> <input id=\"home-filter\" type=\"search\" data-filter=\"#repo-rows > li\" placeholder=\"Filter repositories…\" autocomplete=\"off\" hx-on:input=\"ermite.filter(this)\"></div><nav class=\"sort\" aria-label=\"Sort repositories\"><span class=\"mute\">sort</span> ")
94- if templ_7745c5c3_Err != nil {
95- return templ_7745c5c3_Err
96- }
97- for _, s := range p.Sorts {
98- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a href=\"")
99- if templ_7745c5c3_Err != nil {
100- return templ_7745c5c3_Err
101- }
102- var templ_7745c5c3_Var5 templ.SafeURL
103- templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(s.Href))
104- if templ_7745c5c3_Err != nil {
105- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 35, Col: 36}
106- }
107- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
108- if templ_7745c5c3_Err != nil {
109- return templ_7745c5c3_Err
110- }
111- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"")
112 if templ_7745c5c3_Err != nil {68 if templ_7745c5c3_Err != nil {
113 return templ_7745c5c3_Err69 return templ_7745c5c3_Err
114 }70 }
115 if s.Mark != "" {71 if s.Mark != "" {
116- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " aria-current=\"true\"")72+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " aria-current=\"true\"")
117 if templ_7745c5c3_Err != nil {73 if templ_7745c5c3_Err != nil {
118 return templ_7745c5c3_Err74 return templ_7745c5c3_Err
119 }75 }
120 }76 }
121- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, ">")77+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, ">")
122 if templ_7745c5c3_Err != nil {78 if templ_7745c5c3_Err != nil {
123 return templ_7745c5c3_Err79 return templ_7745c5c3_Err
124 }80 }
125- var templ_7745c5c3_Var6 string81+ var templ_7745c5c3_Var4 string
126- templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)82+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)
127 if templ_7745c5c3_Err != nil {83 if templ_7745c5c3_Err != nil {
128- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 40, Col: 17}84+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 34, Col: 17}
129 }85 }
130- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))86+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
131 if templ_7745c5c3_Err != nil {87 if templ_7745c5c3_Err != nil {
132 return templ_7745c5c3_Err88 return templ_7745c5c3_Err
133 }89 }
134- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " ")90+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " ")
135 if templ_7745c5c3_Err != nil {91 if templ_7745c5c3_Err != nil {
136 return templ_7745c5c3_Err92 return templ_7745c5c3_Err
137 }93 }
138 if s.Mark != "" {94 if s.Mark != "" {
139- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<span aria-hidden=\"true\">")95+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span aria-hidden=\"true\">")
140 if templ_7745c5c3_Err != nil {96 if templ_7745c5c3_Err != nil {
141 return templ_7745c5c3_Err97 return templ_7745c5c3_Err
142 }98 }
143- var templ_7745c5c3_Var7 string99+ var templ_7745c5c3_Var5 string
144- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(s.Mark)100+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(s.Mark)
145 if templ_7745c5c3_Err != nil {101 if templ_7745c5c3_Err != nil {
146- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 42, Col: 42}102+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 36, Col: 42}
147 }103 }
148- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))104+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
149 if templ_7745c5c3_Err != nil {105 if templ_7745c5c3_Err != nil {
150 return templ_7745c5c3_Err106 return templ_7745c5c3_Err
151 }107 }
152- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</span> <span class=\"sr\">")108+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span> <span class=\"sr\">")
153 if templ_7745c5c3_Err != nil {109 if templ_7745c5c3_Err != nil {
154 return templ_7745c5c3_Err110 return templ_7745c5c3_Err
155 }111 }
156- var templ_7745c5c3_Var8 string112+ var templ_7745c5c3_Var6 string
157- templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(", " + s.State)113+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(", " + s.State)
158 if templ_7745c5c3_Err != nil {114 if templ_7745c5c3_Err != nil {
159- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 43, Col: 42}115+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 37, Col: 42}
160 }116 }
161- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))117+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
162 if templ_7745c5c3_Err != nil {118 if templ_7745c5c3_Err != nil {
163 return templ_7745c5c3_Err119 return templ_7745c5c3_Err
164 }120 }
165- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span>")121+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</span>")
166 if templ_7745c5c3_Err != nil {122 if templ_7745c5c3_Err != nil {
167 return templ_7745c5c3_Err123 return templ_7745c5c3_Err
168 }124 }
169 }125 }
170- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</a>")126+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</a>")
171 if templ_7745c5c3_Err != nil {127 if templ_7745c5c3_Err != nil {
172 return templ_7745c5c3_Err128 return templ_7745c5c3_Err
173 }129 }
174 }130 }
175- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</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, "\">")
176 if templ_7745c5c3_Err != nil {158 if templ_7745c5c3_Err != nil {
177 return templ_7745c5c3_Err159 return templ_7745c5c3_Err
178 }160 }
@@ -182,17 +164,17 @@ func Home(l Layout, p HomePage) templ.Component {
182 return templ_7745c5c3_Err164 return templ_7745c5c3_Err
183 }165 }
184 }166 }
185- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</ul>")167+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</ul>")
186 if templ_7745c5c3_Err != nil {168 if templ_7745c5c3_Err != nil {
187 return templ_7745c5c3_Err169 return templ_7745c5c3_Err
188 }170 }
189 if len(p.Rows) == 0 {171 if len(p.Rows) == 0 {
190- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<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>")
191 if templ_7745c5c3_Err != nil {173 if templ_7745c5c3_Err != nil {
192 return templ_7745c5c3_Err174 return templ_7745c5c3_Err
193 }175 }
194 }176 }
195- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</section></div>")177+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</section></div>")
196 if templ_7745c5c3_Err != nil {178 if templ_7745c5c3_Err != nil {
197 return templ_7745c5c3_Err179 return templ_7745c5c3_Err
198 }180 }
@@ -281,51 +263,51 @@ func latestTagFact(t LatestTag) templ.Component {
281 templ_7745c5c3_Var11 = templ.NopComponent263 templ_7745c5c3_Var11 = templ.NopComponent
282 }264 }
283 ctx = templ.ClearChildren(ctx)265 ctx = templ.ClearChildren(ctx)
284- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<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\">")
285 if templ_7745c5c3_Err != nil {267 if templ_7745c5c3_Err != nil {
286 return templ_7745c5c3_Err268 return templ_7745c5c3_Err
287 }269 }
288 if t.Name != "" {270 if t.Name != "" {
289- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<a class=\"tag-name\" href=\"")271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<a class=\"tag-name\" href=\"")
290 if templ_7745c5c3_Err != nil {272 if templ_7745c5c3_Err != nil {
291 return templ_7745c5c3_Err273 return templ_7745c5c3_Err
292 }274 }
293 var templ_7745c5c3_Var12 templ.SafeURL275 var templ_7745c5c3_Var12 templ.SafeURL
294 templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.Href))276 templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.Href))
295 if templ_7745c5c3_Err != nil {277 if templ_7745c5c3_Err != nil {
296- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 51}278+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 51}
297 }279 }
298 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))280 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
299 if templ_7745c5c3_Err != nil {281 if templ_7745c5c3_Err != nil {
300 return templ_7745c5c3_Err282 return templ_7745c5c3_Err
301 }283 }
302- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" title=\"")284+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" title=\"")
303 if templ_7745c5c3_Err != nil {285 if templ_7745c5c3_Err != nil {
304 return templ_7745c5c3_Err286 return templ_7745c5c3_Err
305 }287 }
306 var templ_7745c5c3_Var13 string288 var templ_7745c5c3_Var13 string
307 templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Subject)289 templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Subject)
308 if templ_7745c5c3_Err != nil {290 if templ_7745c5c3_Err != nil {
309- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 71}291+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 71}
310 }292 }
311 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)293 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
312 if templ_7745c5c3_Err != nil {294 if templ_7745c5c3_Err != nil {
313 return templ_7745c5c3_Err295 return templ_7745c5c3_Err
314 }296 }
315- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
316 if templ_7745c5c3_Err != nil {298 if templ_7745c5c3_Err != nil {
317 return templ_7745c5c3_Err299 return templ_7745c5c3_Err
318 }300 }
319 var templ_7745c5c3_Var14 string301 var templ_7745c5c3_Var14 string
320 templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)302 templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)
321 if templ_7745c5c3_Err != nil {303 if templ_7745c5c3_Err != nil {
322- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 82}304+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 84, Col: 82}
323 }305 }
324 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))306 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
325 if templ_7745c5c3_Err != nil {307 if templ_7745c5c3_Err != nil {
326 return templ_7745c5c3_Err308 return templ_7745c5c3_Err
327 }309 }
328- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</a> ")310+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</a> ")
329 if templ_7745c5c3_Err != nil {311 if templ_7745c5c3_Err != nil {
330 return templ_7745c5c3_Err312 return templ_7745c5c3_Err
331 }313 }
@@ -333,101 +315,101 @@ func latestTagFact(t LatestTag) templ.Component {
333 var templ_7745c5c3_Var15 string315 var templ_7745c5c3_Var15 string
334 templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")316 templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
335 if templ_7745c5c3_Err != nil {317 if templ_7745c5c3_Err != nil {
336- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 12}318+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 12}
337 }319 }
338 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))320 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
339 if templ_7745c5c3_Err != nil {321 if templ_7745c5c3_Err != nil {
340 return templ_7745c5c3_Err322 return templ_7745c5c3_Err
341 }323 }
342- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<time datetime=\"")324+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<time datetime=\"")
343 if templ_7745c5c3_Err != nil {325 if templ_7745c5c3_Err != nil {
344 return templ_7745c5c3_Err326 return templ_7745c5c3_Err
345 }327 }
346 var templ_7745c5c3_Var16 string328 var templ_7745c5c3_Var16 string
347 templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)329 templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
348 if templ_7745c5c3_Err != nil {330 if templ_7745c5c3_Err != nil {
349- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 39}331+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 39}
350 }332 }
351 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)333 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
352 if templ_7745c5c3_Err != nil {334 if templ_7745c5c3_Err != nil {
353 return templ_7745c5c3_Err335 return templ_7745c5c3_Err
354 }336 }
355- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" title=\"")337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" title=\"")
356 if templ_7745c5c3_Err != nil {338 if templ_7745c5c3_Err != nil {
357 return templ_7745c5c3_Err339 return templ_7745c5c3_Err
358 }340 }
359 var templ_7745c5c3_Var17 string341 var templ_7745c5c3_Var17 string
360 templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)342 templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.WhenAt)
361 if templ_7745c5c3_Err != nil {343 if templ_7745c5c3_Err != nil {
362- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 58}344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 58}
363 }345 }
364 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)346 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
365 if templ_7745c5c3_Err != nil {347 if templ_7745c5c3_Err != nil {
366 return templ_7745c5c3_Err348 return templ_7745c5c3_Err
367 }349 }
368- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\">")350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
369 if templ_7745c5c3_Err != nil {351 if templ_7745c5c3_Err != nil {
370 return templ_7745c5c3_Err352 return templ_7745c5c3_Err
371 }353 }
372 var templ_7745c5c3_Var18 string354 var templ_7745c5c3_Var18 string
373 templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(t.When)355 templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(t.When)
374 if templ_7745c5c3_Err != nil {356 if templ_7745c5c3_Err != nil {
375- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 92, Col: 69}357+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 86, Col: 69}
376 }358 }
377 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))359 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
378 if templ_7745c5c3_Err != nil {360 if templ_7745c5c3_Err != nil {
379 return templ_7745c5c3_Err361 return templ_7745c5c3_Err
380 }362 }
381- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</time>")363+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</time>")
382 if templ_7745c5c3_Err != nil {364 if templ_7745c5c3_Err != nil {
383 return templ_7745c5c3_Err365 return templ_7745c5c3_Err
384 }366 }
385 }367 }
386- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ")368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
387 if templ_7745c5c3_Err != nil {369 if templ_7745c5c3_Err != nil {
388 return templ_7745c5c3_Err370 return templ_7745c5c3_Err
389 }371 }
390 var templ_7745c5c3_Var19 string372 var templ_7745c5c3_Var19 string
391 templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")373 templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
392 if templ_7745c5c3_Err != nil {374 if templ_7745c5c3_Err != nil {
393- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 94, Col: 11}375+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 88, Col: 11}
394 }376 }
395 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))377 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
396 if templ_7745c5c3_Err != nil {378 if templ_7745c5c3_Err != nil {
397 return templ_7745c5c3_Err379 return templ_7745c5c3_Err
398 }380 }
399- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ")381+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ")
400 if templ_7745c5c3_Err != nil {382 if templ_7745c5c3_Err != nil {
401 return templ_7745c5c3_Err383 return templ_7745c5c3_Err
402 }384 }
403 }385 }
404- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<a class=\"all-tags\" href=\"")386+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<a class=\"all-tags\" href=\"")
405 if templ_7745c5c3_Err != nil {387 if templ_7745c5c3_Err != nil {
406 return templ_7745c5c3_Err388 return templ_7745c5c3_Err
407 }389 }
408 var templ_7745c5c3_Var20 templ.SafeURL390 var templ_7745c5c3_Var20 templ.SafeURL
409 templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.RefsHref))391 templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(t.RefsHref))
410 if templ_7745c5c3_Err != nil {392 if templ_7745c5c3_Err != nil {
411- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 96, Col: 54}393+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 54}
412 }394 }
413 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))395 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
414 if templ_7745c5c3_Err != nil {396 if templ_7745c5c3_Err != nil {
415 return templ_7745c5c3_Err397 return templ_7745c5c3_Err
416 }398 }
417- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\">")399+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\">")
418 if templ_7745c5c3_Err != nil {400 if templ_7745c5c3_Err != nil {
419 return templ_7745c5c3_Err401 return templ_7745c5c3_Err
420 }402 }
421 var templ_7745c5c3_Var21 string403 var templ_7745c5c3_Var21 string
422 templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(plural(t.Count, "tag", "tags"))404 templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(plural(t.Count, "tag", "tags"))
423 if templ_7745c5c3_Err != nil {405 if templ_7745c5c3_Err != nil {
424- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 96, Col: 89}406+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 90, Col: 89}
425 }407 }
426 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))408 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
427 if templ_7745c5c3_Err != nil {409 if templ_7745c5c3_Err != nil {
428 return templ_7745c5c3_Err410 return templ_7745c5c3_Err
429 }411 }
430- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</a></dd>")412+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</a></dd>")
431 if templ_7745c5c3_Err != nil {413 if templ_7745c5c3_Err != nil {
432 return templ_7745c5c3_Err414 return templ_7745c5c3_Err
433 }415 }
@@ -456,7 +438,7 @@ func summaryRail(p SummaryPage) templ.Component {
456 templ_7745c5c3_Var22 = templ.NopComponent438 templ_7745c5c3_Var22 = templ.NopComponent
457 }439 }
458 ctx = templ.ClearChildren(ctx)440 ctx = templ.ClearChildren(ctx)
459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<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>")
460 if templ_7745c5c3_Err != nil {442 if templ_7745c5c3_Err != nil {
461 return templ_7745c5c3_Err443 return templ_7745c5c3_Err
462 }444 }
@@ -464,25 +446,25 @@ func summaryRail(p SummaryPage) templ.Component {
464 if templ_7745c5c3_Err != nil {446 if templ_7745c5c3_Err != nil {
465 return templ_7745c5c3_Err447 return templ_7745c5c3_Err
466 }448 }
467- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</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\">")
468 if templ_7745c5c3_Err != nil {450 if templ_7745c5c3_Err != nil {
469 return templ_7745c5c3_Err451 return templ_7745c5c3_Err
470 }452 }
471 for _, f := range p.Facts {453 for _, f := range p.Facts {
472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<dt>")454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<dt>")
473 if templ_7745c5c3_Err != nil {455 if templ_7745c5c3_Err != nil {
474 return templ_7745c5c3_Err456 return templ_7745c5c3_Err
475 }457 }
476 var templ_7745c5c3_Var23 string458 var templ_7745c5c3_Var23 string
477 templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(f.Key)459 templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(f.Key)
478 if templ_7745c5c3_Err != nil {460 if templ_7745c5c3_Err != nil {
479- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 107, Col: 15}461+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 101, Col: 15}
480 }462 }
481 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))463 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
482 if templ_7745c5c3_Err != nil {464 if templ_7745c5c3_Err != nil {
483 return templ_7745c5c3_Err465 return templ_7745c5c3_Err
484 }466 }
485- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</dt>")467+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</dt>")
486 if templ_7745c5c3_Err != nil {468 if templ_7745c5c3_Err != nil {
487 return templ_7745c5c3_Err469 return templ_7745c5c3_Err
488 }470 }
@@ -497,25 +479,25 @@ func summaryRail(p SummaryPage) templ.Component {
497 return templ_7745c5c3_Err479 return templ_7745c5c3_Err
498 }480 }
499 }481 }
500- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</dl></section>")482+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</dl></section>")
501 if templ_7745c5c3_Err != nil {483 if templ_7745c5c3_Err != nil {
502 return templ_7745c5c3_Err484 return templ_7745c5c3_Err
503 }485 }
504 if len(p.Recent) > 0 {486 if len(p.Recent) > 0 {
505- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<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=\"")
506 if templ_7745c5c3_Err != nil {488 if templ_7745c5c3_Err != nil {
507 return templ_7745c5c3_Err489 return templ_7745c5c3_Err
508 }490 }
509 var templ_7745c5c3_Var24 templ.SafeURL491 var templ_7745c5c3_Var24 templ.SafeURL
510 templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))492 templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.AllHref))
511 if templ_7745c5c3_Err != nil {493 if templ_7745c5c3_Err != nil {
512- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 116, Col: 105}494+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 110, Col: 105}
513 }495 }
514 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))496 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
515 if templ_7745c5c3_Err != nil {497 if templ_7745c5c3_Err != nil {
516 return templ_7745c5c3_Err498 return templ_7745c5c3_Err
517 }499 }
518- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\">View all</a></h2>")500+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">View all</a></h2>")
519 if templ_7745c5c3_Err != nil {501 if templ_7745c5c3_Err != nil {
520 return templ_7745c5c3_Err502 return templ_7745c5c3_Err
521 }503 }
@@ -523,7 +505,7 @@ func summaryRail(p SummaryPage) templ.Component {
523 if templ_7745c5c3_Err != nil {505 if templ_7745c5c3_Err != nil {
524 return templ_7745c5c3_Err506 return templ_7745c5c3_Err
525 }507 }
526- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</section>")508+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</section>")
527 if templ_7745c5c3_Err != nil {509 if templ_7745c5c3_Err != nil {
528 return templ_7745c5c3_Err510 return templ_7745c5c3_Err
529 }511 }
@@ -535,7 +517,7 @@ func summaryRail(p SummaryPage) templ.Component {
535 }517 }
536 }518 }
537 if len(p.Outline) > 0 && p.Readme != nil {519 if len(p.Outline) > 0 && p.Readme != nil {
538- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<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>")
539 if templ_7745c5c3_Err != nil {521 if templ_7745c5c3_Err != nil {
540 return templ_7745c5c3_Err522 return templ_7745c5c3_Err
541 }523 }
@@ -543,7 +525,7 @@ func summaryRail(p SummaryPage) templ.Component {
543 if templ_7745c5c3_Err != nil {525 if templ_7745c5c3_Err != nil {
544 return templ_7745c5c3_Err526 return templ_7745c5c3_Err
545 }527 }
546- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</section>")528+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</section>")
547 if templ_7745c5c3_Err != nil {529 if templ_7745c5c3_Err != nil {
548 return templ_7745c5c3_Err530 return templ_7745c5c3_Err
549 }531 }
@@ -586,7 +568,7 @@ func Refs(l Layout, p RefsPage) templ.Component {
586 }()568 }()
587 }569 }
588 ctx = templ.InitializeContext(ctx)570 ctx = templ.InitializeContext(ctx)
589- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<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>")
590 if templ_7745c5c3_Err != nil {572 if templ_7745c5c3_Err != nil {
591 return templ_7745c5c3_Err573 return templ_7745c5c3_Err
592 }574 }
@@ -594,25 +576,25 @@ func Refs(l Layout, p RefsPage) templ.Component {
594 if templ_7745c5c3_Err != nil {576 if templ_7745c5c3_Err != nil {
595 return templ_7745c5c3_Err577 return templ_7745c5c3_Err
596 }578 }
597- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</table>")579+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</table>")
598 if templ_7745c5c3_Err != nil {580 if templ_7745c5c3_Err != nil {
599 return templ_7745c5c3_Err581 return templ_7745c5c3_Err
600 }582 }
601 if p.Note != "" {583 if p.Note != "" {
602- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<p class=\"note\">")584+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<p class=\"note\">")
603 if templ_7745c5c3_Err != nil {585 if templ_7745c5c3_Err != nil {
604 return templ_7745c5c3_Err586 return templ_7745c5c3_Err
605 }587 }
606 var templ_7745c5c3_Var27 string588 var templ_7745c5c3_Var27 string
607 templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)589 templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
608 if templ_7745c5c3_Err != nil {590 if templ_7745c5c3_Err != nil {
609- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 149, Col: 27}591+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 143, Col: 27}
610 }592 }
611 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))593 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
612 if templ_7745c5c3_Err != nil {594 if templ_7745c5c3_Err != nil {
613 return templ_7745c5c3_Err595 return templ_7745c5c3_Err
614 }596 }
615- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</p>")597+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</p>")
616 if templ_7745c5c3_Err != nil {598 if templ_7745c5c3_Err != nil {
617 return templ_7745c5c3_Err599 return templ_7745c5c3_Err
618 }600 }
@@ -648,97 +630,97 @@ func refRow(kind string, r RefRow) templ.Component {
648 templ_7745c5c3_Var28 = templ.NopComponent630 templ_7745c5c3_Var28 = templ.NopComponent
649 }631 }
650 ctx = templ.ClearChildren(ctx)632 ctx = templ.ClearChildren(ctx)
651- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<tr")633+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<tr")
652 if templ_7745c5c3_Err != nil {634 if templ_7745c5c3_Err != nil {
653 return templ_7745c5c3_Err635 return templ_7745c5c3_Err
654 }636 }
655 if r.Cursor {637 if r.Cursor {
656- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " data-cursor")638+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " data-cursor")
657 if templ_7745c5c3_Err != nil {639 if templ_7745c5c3_Err != nil {
658 return templ_7745c5c3_Err640 return templ_7745c5c3_Err
659 }641 }
660 }642 }
661- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "><td class=\"c-kind mute\">")643+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "><td class=\"c-kind mute\">")
662 if templ_7745c5c3_Err != nil {644 if templ_7745c5c3_Err != nil {
663 return templ_7745c5c3_Err645 return templ_7745c5c3_Err
664 }646 }
665 var templ_7745c5c3_Var29 string647 var templ_7745c5c3_Var29 string
666 templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(kind)648 templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(kind)
667 if templ_7745c5c3_Err != nil {649 if templ_7745c5c3_Err != nil {
668- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 156, Col: 32}650+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 150, Col: 32}
669 }651 }
670 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))652 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
671 if templ_7745c5c3_Err != nil {653 if templ_7745c5c3_Err != nil {
672 return templ_7745c5c3_Err654 return templ_7745c5c3_Err
673 }655 }
674- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-name\"><a href=\"")656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</td><td class=\"c-name\"><a href=\"")
675 if templ_7745c5c3_Err != nil {657 if templ_7745c5c3_Err != nil {
676 return templ_7745c5c3_Err658 return templ_7745c5c3_Err
677 }659 }
678 var templ_7745c5c3_Var30 templ.SafeURL660 var templ_7745c5c3_Var30 templ.SafeURL
679 templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))661 templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
680 if templ_7745c5c3_Err != nil {662 if templ_7745c5c3_Err != nil {
681- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 158, Col: 34}663+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 34}
682 }664 }
683 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))665 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
684 if templ_7745c5c3_Err != nil {666 if templ_7745c5c3_Err != nil {
685 return templ_7745c5c3_Err667 return templ_7745c5c3_Err
686 }668 }
687- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\">")669+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\">")
688 if templ_7745c5c3_Err != nil {670 if templ_7745c5c3_Err != nil {
689 return templ_7745c5c3_Err671 return templ_7745c5c3_Err
690 }672 }
691 var templ_7745c5c3_Var31 string673 var templ_7745c5c3_Var31 string
692 templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)674 templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
693 if templ_7745c5c3_Err != nil {675 if templ_7745c5c3_Err != nil {
694- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 158, Col: 45}676+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 152, Col: 45}
695 }677 }
696 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))678 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
697 if templ_7745c5c3_Err != nil {679 if templ_7745c5c3_Err != nil {
698 return templ_7745c5c3_Err680 return templ_7745c5c3_Err
699 }681 }
700- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</a> ")682+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</a> ")
701 if templ_7745c5c3_Err != nil {683 if templ_7745c5c3_Err != nil {
702 return templ_7745c5c3_Err684 return templ_7745c5c3_Err
703 }685 }
704 if r.Default {686 if r.Default {
705- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<span class=\"pill\">default</span>")687+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"pill\">default</span>")
706 if templ_7745c5c3_Err != nil {688 if templ_7745c5c3_Err != nil {
707 return templ_7745c5c3_Err689 return templ_7745c5c3_Err
708 }690 }
709 }691 }
710- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</td><td class=\"c-sha\">")692+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</td><td class=\"c-sha\">")
711 if templ_7745c5c3_Err != nil {693 if templ_7745c5c3_Err != nil {
712 return templ_7745c5c3_Err694 return templ_7745c5c3_Err
713 }695 }
714 if r.CHref != "" {696 if r.CHref != "" {
715- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "<a href=\"")697+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<a href=\"")
716 if templ_7745c5c3_Err != nil {698 if templ_7745c5c3_Err != nil {
717 return templ_7745c5c3_Err699 return templ_7745c5c3_Err
718 }700 }
719 var templ_7745c5c3_Var32 templ.SafeURL701 var templ_7745c5c3_Var32 templ.SafeURL
720 templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))702 templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref))
721 if templ_7745c5c3_Err != nil {703 if templ_7745c5c3_Err != nil {
722- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 36}704+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 36}
723 }705 }
724 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))706 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
725 if templ_7745c5c3_Err != nil {707 if templ_7745c5c3_Err != nil {
726 return templ_7745c5c3_Err708 return templ_7745c5c3_Err
727 }709 }
728- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "\">")710+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\">")
729 if templ_7745c5c3_Err != nil {711 if templ_7745c5c3_Err != nil {
730 return templ_7745c5c3_Err712 return templ_7745c5c3_Err
731 }713 }
732 var templ_7745c5c3_Var33 string714 var templ_7745c5c3_Var33 string
733 templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)715 templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
734 if templ_7745c5c3_Err != nil {716 if templ_7745c5c3_Err != nil {
735- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 48}717+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 48}
736 }718 }
737 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))719 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
738 if templ_7745c5c3_Err != nil {720 if templ_7745c5c3_Err != nil {
739 return templ_7745c5c3_Err721 return templ_7745c5c3_Err
740 }722 }
741- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</a>")723+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</a>")
742 if templ_7745c5c3_Err != nil {724 if templ_7745c5c3_Err != nil {
743 return templ_7745c5c3_Err725 return templ_7745c5c3_Err
744 }726 }
@@ -746,7 +728,7 @@ func refRow(kind string, r RefRow) templ.Component {
746 var templ_7745c5c3_Var34 string728 var templ_7745c5c3_Var34 string
747 templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)729 templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short)
748 if templ_7745c5c3_Err != nil {730 if templ_7745c5c3_Err != nil {
749- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 167, Col: 13}731+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 161, Col: 13}
750 }732 }
751 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))733 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
752 if templ_7745c5c3_Err != nil {734 if templ_7745c5c3_Err != nil {
@@ -757,69 +739,69 @@ func refRow(kind string, r RefRow) templ.Component {
757 if templ_7745c5c3_Err != nil {739 if templ_7745c5c3_Err != nil {
758 return templ_7745c5c3_Err740 return templ_7745c5c3_Err
759 }741 }
760- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</td><td class=\"c-sub\">")742+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</td><td class=\"c-sub\">")
761 if templ_7745c5c3_Err != nil {743 if templ_7745c5c3_Err != nil {
762 return templ_7745c5c3_Err744 return templ_7745c5c3_Err
763 }745 }
764 var templ_7745c5c3_Var35 string746 var templ_7745c5c3_Var35 string
765 templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)747 templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject)
766 if templ_7745c5c3_Err != nil {748 if templ_7745c5c3_Err != nil {
767- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 171, Col: 31}749+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 165, Col: 31}
768 }750 }
769 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))751 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
770 if templ_7745c5c3_Err != nil {752 if templ_7745c5c3_Err != nil {
771 return templ_7745c5c3_Err753 return templ_7745c5c3_Err
772 }754 }
773- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</td><td class=\"c-when dim\">")755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</td><td class=\"c-when dim\">")
774 if templ_7745c5c3_Err != nil {756 if templ_7745c5c3_Err != nil {
775 return templ_7745c5c3_Err757 return templ_7745c5c3_Err
776 }758 }
777 if r.WhenAt != "" {759 if r.WhenAt != "" {
778- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<time datetime=\"")760+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<time datetime=\"")
779 if templ_7745c5c3_Err != nil {761 if templ_7745c5c3_Err != nil {
780 return templ_7745c5c3_Err762 return templ_7745c5c3_Err
781 }763 }
782 var templ_7745c5c3_Var36 string764 var templ_7745c5c3_Var36 string
783 templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)765 templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
784 if templ_7745c5c3_Err != nil {766 if templ_7745c5c3_Err != nil {
785- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 174, Col: 29}767+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 29}
786 }768 }
787 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)769 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
788 if templ_7745c5c3_Err != nil {770 if templ_7745c5c3_Err != nil {
789 return templ_7745c5c3_Err771 return templ_7745c5c3_Err
790 }772 }
791- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" title=\"")773+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" title=\"")
792 if templ_7745c5c3_Err != nil {774 if templ_7745c5c3_Err != nil {
793 return templ_7745c5c3_Err775 return templ_7745c5c3_Err
794 }776 }
795 var templ_7745c5c3_Var37 string777 var templ_7745c5c3_Var37 string
796 templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)778 templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt)
797 if templ_7745c5c3_Err != nil {779 if templ_7745c5c3_Err != nil {
798- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 174, Col: 48}780+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 48}
799 }781 }
800 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var37)782 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var37)
801 if templ_7745c5c3_Err != nil {783 if templ_7745c5c3_Err != nil {
802 return templ_7745c5c3_Err784 return templ_7745c5c3_Err
803 }785 }
804- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "\">")786+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "\">")
805 if templ_7745c5c3_Err != nil {787 if templ_7745c5c3_Err != nil {
806 return templ_7745c5c3_Err788 return templ_7745c5c3_Err
807 }789 }
808 var templ_7745c5c3_Var38 string790 var templ_7745c5c3_Var38 string
809 templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)791 templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(r.When)
810 if templ_7745c5c3_Err != nil {792 if templ_7745c5c3_Err != nil {
811- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 174, Col: 59}793+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 168, Col: 59}
812 }794 }
813 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))795 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
814 if templ_7745c5c3_Err != nil {796 if templ_7745c5c3_Err != nil {
815 return templ_7745c5c3_Err797 return templ_7745c5c3_Err
816 }798 }
817- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</time>")799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</time>")
818 if templ_7745c5c3_Err != nil {800 if templ_7745c5c3_Err != nil {
819 return templ_7745c5c3_Err801 return templ_7745c5c3_Err
820 }802 }
821 }803 }
822- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</td></tr>")804+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</td></tr>")
823 if templ_7745c5c3_Err != nil {805 if templ_7745c5c3_Err != nil {
824 return templ_7745c5c3_Err806 return templ_7745c5c3_Err
825 }807 }
@@ -848,7 +830,7 @@ func refsRail(p RefsPage) templ.Component {
848 templ_7745c5c3_Var39 = templ.NopComponent830 templ_7745c5c3_Var39 = templ.NopComponent
849 }831 }
850 ctx = templ.ClearChildren(ctx)832 ctx = templ.ClearChildren(ctx)
851- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "<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>")
852 if templ_7745c5c3_Err != nil {834 if templ_7745c5c3_Err != nil {
853 return templ_7745c5c3_Err835 return templ_7745c5c3_Err
854 }836 }
@@ -856,7 +838,7 @@ func refsRail(p RefsPage) templ.Component {
856 if templ_7745c5c3_Err != nil {838 if templ_7745c5c3_Err != nil {
857 return templ_7745c5c3_Err839 return templ_7745c5c3_Err
858 }840 }
859- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "</section>")841+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</section>")
860 if templ_7745c5c3_Err != nil {842 if templ_7745c5c3_Err != nil {
861 return templ_7745c5c3_Err843 return templ_7745c5c3_Err
862 }844 }
@@ -898,51 +880,51 @@ func State(l Layout, p StatePage) templ.Component {
898 }()880 }()
899 }881 }
900 ctx = templ.InitializeContext(ctx)882 ctx = templ.InitializeContext(ctx)
901- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<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\">")
902 if templ_7745c5c3_Err != nil {884 if templ_7745c5c3_Err != nil {
903 return templ_7745c5c3_Err885 return templ_7745c5c3_Err
904 }886 }
905 var templ_7745c5c3_Var42 string887 var templ_7745c5c3_Var42 string
906 templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)888 templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code)
907 if templ_7745c5c3_Err != nil {889 if templ_7745c5c3_Err != nil {
908- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 191, Col: 30}890+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 185, Col: 30}
909 }891 }
910 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))892 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
911 if templ_7745c5c3_Err != nil {893 if templ_7745c5c3_Err != nil {
912 return templ_7745c5c3_Err894 return templ_7745c5c3_Err
913 }895 }
914- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "</p><h1 id=\"state-title\">")896+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</p><h1 id=\"state-title\">")
915 if templ_7745c5c3_Err != nil {897 if templ_7745c5c3_Err != nil {
916 return templ_7745c5c3_Err898 return templ_7745c5c3_Err
917 }899 }
918 var templ_7745c5c3_Var43 string900 var templ_7745c5c3_Var43 string
919 templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)901 templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
920 if templ_7745c5c3_Err != nil {902 if templ_7745c5c3_Err != nil {
921- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 192, Col: 33}903+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 186, Col: 33}
922 }904 }
923 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))905 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
924 if templ_7745c5c3_Err != nil {906 if templ_7745c5c3_Err != nil {
925 return templ_7745c5c3_Err907 return templ_7745c5c3_Err
926 }908 }
927- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</h1>")909+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</h1>")
928 if templ_7745c5c3_Err != nil {910 if templ_7745c5c3_Err != nil {
929 return templ_7745c5c3_Err911 return templ_7745c5c3_Err
930 }912 }
931 for _, line := range p.Lines {913 for _, line := range p.Lines {
932- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<p class=\"dim\">")914+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<p class=\"dim\">")
933 if templ_7745c5c3_Err != nil {915 if templ_7745c5c3_Err != nil {
934 return templ_7745c5c3_Err916 return templ_7745c5c3_Err
935 }917 }
936 var templ_7745c5c3_Var44 string918 var templ_7745c5c3_Var44 string
937 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(line)919 templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(line)
938 if templ_7745c5c3_Err != nil {920 if templ_7745c5c3_Err != nil {
939- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 25}921+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 188, Col: 25}
940 }922 }
941 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))923 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
942 if templ_7745c5c3_Err != nil {924 if templ_7745c5c3_Err != nil {
943 return templ_7745c5c3_Err925 return templ_7745c5c3_Err
944 }926 }
945- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</p>")927+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</p>")
946 if templ_7745c5c3_Err != nil {928 if templ_7745c5c3_Err != nil {
947 return templ_7745c5c3_Err929 return templ_7745c5c3_Err
948 }930 }
@@ -953,43 +935,43 @@ func State(l Layout, p StatePage) templ.Component {
953 return templ_7745c5c3_Err935 return templ_7745c5c3_Err
954 }936 }
955 }937 }
956- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<p class=\"state-back\">")938+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"state-back\">")
957 if templ_7745c5c3_Err != nil {939 if templ_7745c5c3_Err != nil {
958 return templ_7745c5c3_Err940 return templ_7745c5c3_Err
959 }941 }
960 for _, b := range p.Back {942 for _, b := range p.Back {
961- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<a href=\"")943+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<a href=\"")
962 if templ_7745c5c3_Err != nil {944 if templ_7745c5c3_Err != nil {
963 return templ_7745c5c3_Err945 return templ_7745c5c3_Err
964 }946 }
965 var templ_7745c5c3_Var45 templ.SafeURL947 var templ_7745c5c3_Var45 templ.SafeURL
966 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))948 templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href))
967 if templ_7745c5c3_Err != nil {949 if templ_7745c5c3_Err != nil {
968- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 201, Col: 36}950+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 36}
969 }951 }
970 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))952 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
971 if templ_7745c5c3_Err != nil {953 if templ_7745c5c3_Err != nil {
972 return templ_7745c5c3_Err954 return templ_7745c5c3_Err
973 }955 }
974- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "\">")956+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\">")
975 if templ_7745c5c3_Err != nil {957 if templ_7745c5c3_Err != nil {
976 return templ_7745c5c3_Err958 return templ_7745c5c3_Err
977 }959 }
978 var templ_7745c5c3_Var46 string960 var templ_7745c5c3_Var46 string
979 templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)961 templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label)
980 if templ_7745c5c3_Err != nil {962 if templ_7745c5c3_Err != nil {
981- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 201, Col: 48}963+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 48}
982 }964 }
983 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))965 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
984 if templ_7745c5c3_Err != nil {966 if templ_7745c5c3_Err != nil {
985 return templ_7745c5c3_Err967 return templ_7745c5c3_Err
986 }968 }
987- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</a>")969+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</a>")
988 if templ_7745c5c3_Err != nil {970 if templ_7745c5c3_Err != nil {
989 return templ_7745c5c3_Err971 return templ_7745c5c3_Err
990 }972 }
991 }973 }
992- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</p></section>")974+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</p></section>")
993 if templ_7745c5c3_Err != nil {975 if templ_7745c5c3_Err != nil {
994 return templ_7745c5c3_Err976 return templ_7745c5c3_Err
995 }977 }
@@ -1041,69 +1023,69 @@ func Login(l Layout, p LoginPage) templ.Component {
1041 }()1023 }()
1042 }1024 }
1043 ctx = templ.InitializeContext(ctx)1025 ctx = templ.InitializeContext(ctx)
1044- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<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\">")
1045 if templ_7745c5c3_Err != nil {1027 if templ_7745c5c3_Err != nil {
1046 return templ_7745c5c3_Err1028 return templ_7745c5c3_Err
1047 }1029 }
1048 var templ_7745c5c3_Var49 string1030 var templ_7745c5c3_Var49 string
1049 templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)1031 templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
1050 if templ_7745c5c3_Err != nil {1032 if templ_7745c5c3_Err != nil {
1051- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 216, Col: 33}1033+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 210, Col: 33}
1052 }1034 }
1053 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))1035 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
1054 if templ_7745c5c3_Err != nil {1036 if templ_7745c5c3_Err != nil {
1055 return templ_7745c5c3_Err1037 return templ_7745c5c3_Err
1056 }1038 }
1057- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "</h1>")1039+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</h1>")
1058 if templ_7745c5c3_Err != nil {1040 if templ_7745c5c3_Err != nil {
1059 return templ_7745c5c3_Err1041 return templ_7745c5c3_Err
1060 }1042 }
1061 for _, line := range p.Lines {1043 for _, line := range p.Lines {
1062- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<p class=\"dim\">")1044+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<p class=\"dim\">")
1063 if templ_7745c5c3_Err != nil {1045 if templ_7745c5c3_Err != nil {
1064 return templ_7745c5c3_Err1046 return templ_7745c5c3_Err
1065 }1047 }
1066 var templ_7745c5c3_Var50 string1048 var templ_7745c5c3_Var50 string
1067 templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(line)1049 templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(line)
1068 if templ_7745c5c3_Err != nil {1050 if templ_7745c5c3_Err != nil {
1069- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 218, Col: 25}1051+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 212, Col: 25}
1070 }1052 }
1071 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))1053 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
1072 if templ_7745c5c3_Err != nil {1054 if templ_7745c5c3_Err != nil {
1073 return templ_7745c5c3_Err1055 return templ_7745c5c3_Err
1074 }1056 }
1075- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</p>")1057+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</p>")
1076 if templ_7745c5c3_Err != nil {1058 if templ_7745c5c3_Err != nil {
1077 return templ_7745c5c3_Err1059 return templ_7745c5c3_Err
1078 }1060 }
1079 }1061 }
1080- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<form class=\"state-act\" method=\"post\" action=\"")1062+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<form class=\"state-act\" method=\"post\" action=\"")
1081 if templ_7745c5c3_Err != nil {1063 if templ_7745c5c3_Err != nil {
1082 return templ_7745c5c3_Err1064 return templ_7745c5c3_Err
1083 }1065 }
1084 var templ_7745c5c3_Var51 templ.SafeURL1066 var templ_7745c5c3_Var51 templ.SafeURL
1085 templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))1067 templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action))
1086 if templ_7745c5c3_Err != nil {1068 if templ_7745c5c3_Err != nil {
1087- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 220, Col: 73}1069+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 214, Col: 73}
1088 }1070 }
1089 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))1071 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
1090 if templ_7745c5c3_Err != nil {1072 if templ_7745c5c3_Err != nil {
1091 return templ_7745c5c3_Err1073 return templ_7745c5c3_Err
1092 }1074 }
1093- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" 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\">")
1094 if templ_7745c5c3_Err != nil {1076 if templ_7745c5c3_Err != nil {
1095 return templ_7745c5c3_Err1077 return templ_7745c5c3_Err
1096 }1078 }
1097 var templ_7745c5c3_Var52 string1079 var templ_7745c5c3_Var52 string
1098 templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)1080 templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
1099 if templ_7745c5c3_Err != nil {1081 if templ_7745c5c3_Err != nil {
1100- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 221, Col: 56}1082+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 215, Col: 56}
1101 }1083 }
1102 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))1084 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
1103 if templ_7745c5c3_Err != nil {1085 if templ_7745c5c3_Err != nil {
1104 return templ_7745c5c3_Err1086 return templ_7745c5c3_Err
1105 }1087 }
1106- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "</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>")
1107 if templ_7745c5c3_Err != nil {1089 if templ_7745c5c3_Err != nil {
1108 return templ_7745c5c3_Err1090 return templ_7745c5c3_Err
1109 }1091 }
@@ -1152,82 +1134,82 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component {
1152 }()1134 }()
1153 }1135 }
1154 ctx = templ.InitializeContext(ctx)1136 ctx = templ.InitializeContext(ctx)
1155- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<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\">")
1156 if templ_7745c5c3_Err != nil {1138 if templ_7745c5c3_Err != nil {
1157 return templ_7745c5c3_Err1139 return templ_7745c5c3_Err
1158 }1140 }
1159 var templ_7745c5c3_Var55 string1141 var templ_7745c5c3_Var55 string
1160 templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)1142 templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title)
1161 if templ_7745c5c3_Err != nil {1143 if templ_7745c5c3_Err != nil {
1162- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 33}1144+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 227, Col: 33}
1163 }1145 }
1164 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))1146 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55))
1165 if templ_7745c5c3_Err != nil {1147 if templ_7745c5c3_Err != nil {
1166 return templ_7745c5c3_Err1148 return templ_7745c5c3_Err
1167 }1149 }
1168- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</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=\"")
1169 if templ_7745c5c3_Err != nil {1151 if templ_7745c5c3_Err != nil {
1170 return templ_7745c5c3_Err1152 return templ_7745c5c3_Err
1171 }1153 }
1172 var templ_7745c5c3_Var56 string1154 var templ_7745c5c3_Var56 string
1173 templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)1155 templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
1174 if templ_7745c5c3_Err != nil {1156 if templ_7745c5c3_Err != nil {
1175- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 87}1157+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 87}
1176 }1158 }
1177 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)1159 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var56)
1178 if templ_7745c5c3_Err != nil {1160 if templ_7745c5c3_Err != nil {
1179 return templ_7745c5c3_Err1161 return templ_7745c5c3_Err
1180 }1162 }
1181- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\" data-finish=\"")1163+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\" data-finish=\"")
1182 if templ_7745c5c3_Err != nil {1164 if templ_7745c5c3_Err != nil {
1183 return templ_7745c5c3_Err1165 return templ_7745c5c3_Err
1184 }1166 }
1185 var templ_7745c5c3_Var57 string1167 var templ_7745c5c3_Var57 string
1186 templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)1168 templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
1187 if templ_7745c5c3_Err != nil {1169 if templ_7745c5c3_Err != nil {
1188- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 112}1170+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 112}
1189 }1171 }
1190 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)1172 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var57)
1191 if templ_7745c5c3_Err != nil {1173 if templ_7745c5c3_Err != nil {
1192 return templ_7745c5c3_Err1174 return templ_7745c5c3_Err
1193 }1175 }
1194- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "\">")1176+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "\">")
1195 if templ_7745c5c3_Err != nil {1177 if templ_7745c5c3_Err != nil {
1196 return templ_7745c5c3_Err1178 return templ_7745c5c3_Err
1197 }1179 }
1198 var templ_7745c5c3_Var58 string1180 var templ_7745c5c3_Var58 string
1199 templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)1181 templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button)
1200 if templ_7745c5c3_Err != nil {1182 if templ_7745c5c3_Err != nil {
1201- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 125}1183+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 125}
1202 }1184 }
1203 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))1185 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
1204 if templ_7745c5c3_Err != nil {1186 if templ_7745c5c3_Err != nil {
1205 return templ_7745c5c3_Err1187 return templ_7745c5c3_Err
1206 }1188 }
1207- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</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>")
1208 if templ_7745c5c3_Err != nil {1190 if templ_7745c5c3_Err != nil {
1209 return templ_7745c5c3_Err1191 return templ_7745c5c3_Err
1210 }1192 }
1211 for _, line := range p.Lines {1193 for _, line := range p.Lines {
1212- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<p class=\"dim\">")1194+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<p class=\"dim\">")
1213 if templ_7745c5c3_Err != nil {1195 if templ_7745c5c3_Err != nil {
1214 return templ_7745c5c3_Err1196 return templ_7745c5c3_Err
1215 }1197 }
1216 var templ_7745c5c3_Var59 string1198 var templ_7745c5c3_Var59 string
1217 templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(line)1199 templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(line)
1218 if templ_7745c5c3_Err != nil {1200 if templ_7745c5c3_Err != nil {
1219- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 239, Col: 25}1201+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 25}
1220 }1202 }
1221 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))1203 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
1222 if templ_7745c5c3_Err != nil {1204 if templ_7745c5c3_Err != nil {
1223 return templ_7745c5c3_Err1205 return templ_7745c5c3_Err
1224 }1206 }
1225- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</p>")1207+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</p>")
1226 if templ_7745c5c3_Err != nil {1208 if templ_7745c5c3_Err != nil {
1227 return templ_7745c5c3_Err1209 return templ_7745c5c3_Err
1228 }1210 }
1229 }1211 }
1230- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<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>")
1231 if templ_7745c5c3_Err != nil {1213 if templ_7745c5c3_Err != nil {
1232 return templ_7745c5c3_Err1214 return templ_7745c5c3_Err
1233 }1215 }
@@ -1275,225 +1257,225 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component {
1275 }()1257 }()
1276 }1258 }
1277 ctx = templ.InitializeContext(ctx)1259 ctx = templ.InitializeContext(ctx)
1278- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "<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\">")
1279 if templ_7745c5c3_Err != nil {1261 if templ_7745c5c3_Err != nil {
1280 return templ_7745c5c3_Err1262 return templ_7745c5c3_Err
1281 }1263 }
1282 var templ_7745c5c3_Var62 string1264 var templ_7745c5c3_Var62 string
1283 templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows)))1265 templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows)))
1284 if templ_7745c5c3_Err != nil {1266 if templ_7745c5c3_Err != nil {
1285- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 249, Col: 81}1267+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 243, Col: 81}
1286 }1268 }
1287 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))1269 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
1288 if templ_7745c5c3_Err != nil {1270 if templ_7745c5c3_Err != nil {
1289 return templ_7745c5c3_Err1271 return templ_7745c5c3_Err
1290 }1272 }
1291- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</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>")
1292 if templ_7745c5c3_Err != nil {1274 if templ_7745c5c3_Err != nil {
1293 return templ_7745c5c3_Err1275 return templ_7745c5c3_Err
1294 }1276 }
1295 if len(p.Rows) > 0 {1277 if len(p.Rows) > 0 {
1296- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<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>")
1297 if templ_7745c5c3_Err != nil {1279 if templ_7745c5c3_Err != nil {
1298 return templ_7745c5c3_Err1280 return templ_7745c5c3_Err
1299 }1281 }
1300 for _, r := range p.Rows {1282 for _, r := range p.Rows {
1301- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<tr><td class=\"c-name\">")1283+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<tr><td class=\"c-name\">")
1302 if templ_7745c5c3_Err != nil {1284 if templ_7745c5c3_Err != nil {
1303 return templ_7745c5c3_Err1285 return templ_7745c5c3_Err
1304 }1286 }
1305 var templ_7745c5c3_Var63 string1287 var templ_7745c5c3_Var63 string
1306 templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)1288 templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
1307 if templ_7745c5c3_Err != nil {1289 if templ_7745c5c3_Err != nil {
1308- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 265, Col: 34}1290+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 259, Col: 34}
1309 }1291 }
1310 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))1292 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
1311 if templ_7745c5c3_Err != nil {1293 if templ_7745c5c3_Err != nil {
1312 return templ_7745c5c3_Err1294 return templ_7745c5c3_Err
1313 }1295 }
1314- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</td><td class=\"c-when nowrap\"><time")1296+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</td><td class=\"c-when nowrap\"><time")
1315 if templ_7745c5c3_Err != nil {1297 if templ_7745c5c3_Err != nil {
1316 return templ_7745c5c3_Err1298 return templ_7745c5c3_Err
1317 }1299 }
1318 if r.CreatedAt != "" {1300 if r.CreatedAt != "" {
1319- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, " datetime=\"")1301+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, " datetime=\"")
1320 if templ_7745c5c3_Err != nil {1302 if templ_7745c5c3_Err != nil {
1321 return templ_7745c5c3_Err1303 return templ_7745c5c3_Err
1322 }1304 }
1323 var templ_7745c5c3_Var64 string1305 var templ_7745c5c3_Var64 string
1324 templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt)1306 templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt)
1325 if templ_7745c5c3_Err != nil {1307 if templ_7745c5c3_Err != nil {
1326- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 269, Col: 32}1308+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 263, Col: 32}
1327 }1309 }
1328 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)1310 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
1329 if templ_7745c5c3_Err != nil {1311 if templ_7745c5c3_Err != nil {
1330 return templ_7745c5c3_Err1312 return templ_7745c5c3_Err
1331 }1313 }
1332- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\"")1314+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "\"")
1333 if templ_7745c5c3_Err != nil {1315 if templ_7745c5c3_Err != nil {
1334 return templ_7745c5c3_Err1316 return templ_7745c5c3_Err
1335 }1317 }
1336 }1318 }
1337- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, ">")1319+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, ">")
1338 if templ_7745c5c3_Err != nil {1320 if templ_7745c5c3_Err != nil {
1339 return templ_7745c5c3_Err1321 return templ_7745c5c3_Err
1340 }1322 }
1341 var templ_7745c5c3_Var65 string1323 var templ_7745c5c3_Var65 string
1342 templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created)1324 templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created)
1343 if templ_7745c5c3_Err != nil {1325 if templ_7745c5c3_Err != nil {
1344- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 271, Col: 20}1326+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 265, Col: 20}
1345 }1327 }
1346 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))1328 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
1347 if templ_7745c5c3_Err != nil {1329 if templ_7745c5c3_Err != nil {
1348 return templ_7745c5c3_Err1330 return templ_7745c5c3_Err
1349 }1331 }
1350- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</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")
1351 if templ_7745c5c3_Err != nil {1333 if templ_7745c5c3_Err != nil {
1352 return templ_7745c5c3_Err1334 return templ_7745c5c3_Err
1353 }1335 }
1354 if r.LastUsedAt != "" {1336 if r.LastUsedAt != "" {
1355- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, " datetime=\"")1337+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, " datetime=\"")
1356 if templ_7745c5c3_Err != nil {1338 if templ_7745c5c3_Err != nil {
1357 return templ_7745c5c3_Err1339 return templ_7745c5c3_Err
1358 }1340 }
1359 var templ_7745c5c3_Var66 string1341 var templ_7745c5c3_Var66 string
1360 templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt)1342 templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt)
1361 if templ_7745c5c3_Err != nil {1343 if templ_7745c5c3_Err != nil {
1362- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 276, Col: 33}1344+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 270, Col: 33}
1363 }1345 }
1364 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)1346 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66)
1365 if templ_7745c5c3_Err != nil {1347 if templ_7745c5c3_Err != nil {
1366 return templ_7745c5c3_Err1348 return templ_7745c5c3_Err
1367 }1349 }
1368- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "\"")1350+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "\"")
1369 if templ_7745c5c3_Err != nil {1351 if templ_7745c5c3_Err != nil {
1370 return templ_7745c5c3_Err1352 return templ_7745c5c3_Err
1371 }1353 }
1372 }1354 }
1373- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, ">")1355+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, ">")
1374 if templ_7745c5c3_Err != nil {1356 if templ_7745c5c3_Err != nil {
1375 return templ_7745c5c3_Err1357 return templ_7745c5c3_Err
1376 }1358 }
1377 var templ_7745c5c3_Var67 string1359 var templ_7745c5c3_Var67 string
1378 templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed)1360 templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed)
1379 if templ_7745c5c3_Err != nil {1361 if templ_7745c5c3_Err != nil {
1380- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 278, Col: 21}1362+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 272, Col: 21}
1381 }1363 }
1382 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))1364 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
1383 if templ_7745c5c3_Err != nil {1365 if templ_7745c5c3_Err != nil {
1384 return templ_7745c5c3_Err1366 return templ_7745c5c3_Err
1385 }1367 }
1386- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</time></td><td class=\"c-sync\">")1368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</time></td><td class=\"c-sync\">")
1387 if templ_7745c5c3_Err != nil {1369 if templ_7745c5c3_Err != nil {
1388 return templ_7745c5c3_Err1370 return templ_7745c5c3_Err
1389 }1371 }
1390 if r.Synced {1372 if r.Synced {
1391- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "yes")1373+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "yes")
1392 if templ_7745c5c3_Err != nil {1374 if templ_7745c5c3_Err != nil {
1393 return templ_7745c5c3_Err1375 return templ_7745c5c3_Err
1394 }1376 }
1395 } else {1377 } else {
1396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "no")1378+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "no")
1397 if templ_7745c5c3_Err != nil {1379 if templ_7745c5c3_Err != nil {
1398 return templ_7745c5c3_Err1380 return templ_7745c5c3_Err
1399 }1381 }
1400 }1382 }
1401- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "</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=\"")
1402 if templ_7745c5c3_Err != nil {1384 if templ_7745c5c3_Err != nil {
1403 return templ_7745c5c3_Err1385 return templ_7745c5c3_Err
1404 }1386 }
1405 var templ_7745c5c3_Var68 templ.SafeURL1387 var templ_7745c5c3_Var68 templ.SafeURL
1406 templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove))1388 templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove))
1407 if templ_7745c5c3_Err != nil {1389 if templ_7745c5c3_Err != nil {
1408- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 288, Col: 60}1390+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 282, Col: 60}
1409 }1391 }
1410 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))1392 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
1411 if templ_7745c5c3_Err != nil {1393 if templ_7745c5c3_Err != nil {
1412 return templ_7745c5c3_Err1394 return templ_7745c5c3_Err
1413 }1395 }
1414- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "\"><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=\"")
1415 if templ_7745c5c3_Err != nil {1397 if templ_7745c5c3_Err != nil {
1416 return templ_7745c5c3_Err1398 return templ_7745c5c3_Err
1417 }1399 }
1418 var templ_7745c5c3_Var69 string1400 var templ_7745c5c3_Var69 string
1419 templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name)1401 templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name)
1420 if templ_7745c5c3_Err != nil {1402 if templ_7745c5c3_Err != nil {
1421- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 289, Col: 89}1403+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 283, Col: 89}
1422 }1404 }
1423 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)1405 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69)
1424 if templ_7745c5c3_Err != nil {1406 if templ_7745c5c3_Err != nil {
1425 return templ_7745c5c3_Err1407 return templ_7745c5c3_Err
1426 }1408 }
1427- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\">Remove</button></form></td></tr>")1409+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "\">Remove</button></form></td></tr>")
1428 if templ_7745c5c3_Err != nil {1410 if templ_7745c5c3_Err != nil {
1429 return templ_7745c5c3_Err1411 return templ_7745c5c3_Err
1430 }1412 }
1431 }1413 }
1432- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</tbody></table>")1414+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</tbody></table>")
1433 if templ_7745c5c3_Err != nil {1415 if templ_7745c5c3_Err != nil {
1434 return templ_7745c5c3_Err1416 return templ_7745c5c3_Err
1435 }1417 }
1436 } else {1418 } else {
1437- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<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>")
1438 if templ_7745c5c3_Err != nil {1420 if templ_7745c5c3_Err != nil {
1439 return templ_7745c5c3_Err1421 return templ_7745c5c3_Err
1440 }1422 }
1441 }1423 }
1442- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, " ")1424+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, " ")
1443 if templ_7745c5c3_Err != nil {1425 if templ_7745c5c3_Err != nil {
1444 return templ_7745c5c3_Err1426 return templ_7745c5c3_Err
1445 }1427 }
1446 if p.Full {1428 if p.Full {
1447- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "<p class=\"dim\">")1429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "<p class=\"dim\">")
1448 if templ_7745c5c3_Err != nil {1430 if templ_7745c5c3_Err != nil {
1449 return templ_7745c5c3_Err1431 return templ_7745c5c3_Err
1450 }1432 }
1451 var templ_7745c5c3_Var70 string1433 var templ_7745c5c3_Var70 string
1452 templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max))1434 templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max))
1453 if templ_7745c5c3_Err != nil {1435 if templ_7745c5c3_Err != nil {
1454- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 300, Col: 31}1436+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 294, Col: 31}
1455 }1437 }
1456 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))1438 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
1457 if templ_7745c5c3_Err != nil {1439 if templ_7745c5c3_Err != nil {
1458 return templ_7745c5c3_Err1440 return templ_7745c5c3_Err
1459 }1441 }
1460- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, " 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>")
1461 if templ_7745c5c3_Err != nil {1443 if templ_7745c5c3_Err != nil {
1462 return templ_7745c5c3_Err1444 return templ_7745c5c3_Err
1463 }1445 }
1464 } else {1446 } else {
1465- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "<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=\"")
1466 if templ_7745c5c3_Err != nil {1448 if templ_7745c5c3_Err != nil {
1467 return templ_7745c5c3_Err1449 return templ_7745c5c3_Err
1468 }1450 }
1469 var templ_7745c5c3_Var71 string1451 var templ_7745c5c3_Var71 string
1470 templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)1452 templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin)
1471 if templ_7745c5c3_Err != nil {1453 if templ_7745c5c3_Err != nil {
1472- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 305, Col: 90}1454+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 90}
1473 }1455 }
1474 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)1456 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)
1475 if templ_7745c5c3_Err != nil {1457 if templ_7745c5c3_Err != nil {
1476 return templ_7745c5c3_Err1458 return templ_7745c5c3_Err
1477 }1459 }
1478- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "\" data-finish=\"")1460+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" data-finish=\"")
1479 if templ_7745c5c3_Err != nil {1461 if templ_7745c5c3_Err != nil {
1480 return templ_7745c5c3_Err1462 return templ_7745c5c3_Err
1481 }1463 }
1482 var templ_7745c5c3_Var72 string1464 var templ_7745c5c3_Var72 string
1483 templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)1465 templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish)
1484 if templ_7745c5c3_Err != nil {1466 if templ_7745c5c3_Err != nil {
1485- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 305, Col: 115}1467+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 299, Col: 115}
1486 }1468 }
1487 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)1469 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
1488 if templ_7745c5c3_Err != nil {1470 if templ_7745c5c3_Err != nil {
1489 return templ_7745c5c3_Err1471 return templ_7745c5c3_Err
1490 }1472 }
1491- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "\">Add a passkey</button></form>")1473+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\">Add a passkey</button></form>")
1492 if templ_7745c5c3_Err != nil {1474 if templ_7745c5c3_Err != nil {
1493 return templ_7745c5c3_Err1475 return templ_7745c5c3_Err
1494 }1476 }
1495 }1477 }
1496- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, " <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>")
1497 if templ_7745c5c3_Err != nil {1479 if templ_7745c5c3_Err != nil {
1498 return templ_7745c5c3_Err1480 return templ_7745c5c3_Err
1499 }1481 }
M internal/adapters/web/shell.templ +14 -1
@@ -25,7 +25,7 @@ templ Shell(l Layout, rail templ.Component) {
2525 <script src={ l.Assets.HTMX } defer></script>
2626 <script src={ l.Assets.Live } defer></script>
2727 <script src={ l.Assets.SSE } defer></script>
28- <script src={ l.Assets.Store } defer></script>
28+ <script src={ l.Assets.Store }></script>
2929 <script src={ l.Assets.App } defer></script>
3030 <script src={ l.Assets.Files } defer></script>
3131 <script src={ l.Assets.Passkey } defer></script>
@@ -80,6 +80,19 @@ templ Header(l Layout) {
8080 <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)">
8181 <span class="brand-mark" aria-hidden="true"></span>
8282 </a>
83+ if l.Inbox != nil {
84+ <a
85+ class="btn inbox-btn"
86+ href={ templ.SafeURL(l.Inbox.Href) }
87+ title="inbox"
88+ >
89+ <span class="ico i-mailbox" aria-hidden="true"></span>
90+ Inbox
91+ if l.Inbox.Count >= 0 {
92+ <span class="count">{ itoa(l.Inbox.Count) }</span>
93+ }
94+ </a>
95+ }
8396 if len(l.Crumbs) > 0 {
8497 <nav class="crumbs" aria-label="Path">
8598 <ol>
@@ -25,7 +25,7 @@ templ Shell(l Layout, rail templ.Component) {
25 <script src={ l.Assets.HTMX } defer></script>25 <script src={ l.Assets.HTMX } defer></script>
26 <script src={ l.Assets.Live } defer></script>26 <script src={ l.Assets.Live } defer></script>
27 <script src={ l.Assets.SSE } defer></script>27 <script src={ l.Assets.SSE } defer></script>
28- <script src={ l.Assets.Store } defer></script>28+ <script src={ l.Assets.Store }></script>
29 <script src={ l.Assets.App } defer></script>29 <script src={ l.Assets.App } defer></script>
30 <script src={ l.Assets.Files } defer></script>30 <script src={ l.Assets.Files } defer></script>
31 <script src={ l.Assets.Passkey } defer></script>31 <script src={ l.Assets.Passkey } defer></script>
@@ -80,6 +80,19 @@ templ Header(l Layout) {
80 <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)">80 <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)">
81 <span class="brand-mark" aria-hidden="true"></span>81 <span class="brand-mark" aria-hidden="true"></span>
82 </a>82 </a>
83+ if l.Inbox != nil {
84+ <a
85+ class="btn inbox-btn"
86+ href={ templ.SafeURL(l.Inbox.Href) }
87+ title="inbox"
88+ >
89+ <span class="ico i-mailbox" aria-hidden="true"></span>
90+ Inbox
91+ if l.Inbox.Count >= 0 {
92+ <span class="count">{ itoa(l.Inbox.Count) }</span>
93+ }
94+ </a>
95+ }
83 if len(l.Crumbs) > 0 {96 if len(l.Crumbs) > 0 {
84 <nav class="crumbs" aria-label="Path">97 <nav class="crumbs" aria-label="Path">
85 <ol>98 <ol>
M internal/adapters/web/shell_templ.go +158 -116 generated file: collapsed
M internal/adapters/web/static/css/20-shell.css +9 -0
@@ -29,6 +29,7 @@
2929 .i-cards { --i: url(icons/cards.svg); }
3030 .i-split { --i: url(icons/split.svg); }
3131 .i-compact { --i: url(icons/compact.svg); }
32+ .i-mailbox { --i: url(icons/mailbox.svg); }
3233
3334 .pill {
3435 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -77,6 +78,11 @@
7778 }
7879 /* the hermit by his fire: a grey picture in its own rounded square */
7980 .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; }
81+ /* the inbox, right of the logo on the home page: its number in a darker badge */
82+ .inbox-btn {
83+ flex: none;
84+ & .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); }
85+ }
8086 .crumbs {
8187 min-width: 0; flex: 1 1 auto; overflow: hidden;
8288 & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); }
@@ -133,6 +139,9 @@
133139 align-content: start;
134140 }
135141 .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); }
142+ /* the wide mode (w, ermiteWide): every page takes the whole width of the
143+ screen; the rail goes to the right edge, the content takes the rest */
144+ html.wide :is(.work, .hdr-top, .tabs, .foot) { max-width: none; }
136145 .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }
137146 .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); }
138147 main > * + * { margin-top: var(--s4); }
@@ -29,6 +29,7 @@
29 .i-cards { --i: url(icons/cards.svg); }29 .i-cards { --i: url(icons/cards.svg); }
30 .i-split { --i: url(icons/split.svg); }30 .i-split { --i: url(icons/split.svg); }
31 .i-compact { --i: url(icons/compact.svg); }31 .i-compact { --i: url(icons/compact.svg); }
32+ .i-mailbox { --i: url(icons/mailbox.svg); }
32 33
33 .pill {34 .pill {
34 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);35 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -77,6 +78,11 @@
77 }78 }
78 /* the hermit by his fire: a grey picture in its own rounded square */79 /* the hermit by his fire: a grey picture in its own rounded square */
79 .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; }80 .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; }
81+ /* the inbox, right of the logo on the home page: its number in a darker badge */
82+ .inbox-btn {
83+ flex: none;
84+ & .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); }
85+ }
80 .crumbs {86 .crumbs {
81 min-width: 0; flex: 1 1 auto; overflow: hidden;87 min-width: 0; flex: 1 1 auto; overflow: hidden;
82 & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); }88 & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); }
@@ -133,6 +139,9 @@
133 align-content: start;139 align-content: start;
134 }140 }
135 .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); }141 .work.has-rail { grid-template-columns: minmax(0, 1fr) var(--rail); }
142+ /* the wide mode (w, ermiteWide): every page takes the whole width of the
143+ screen; the rail goes to the right edge, the content takes the rest */
144+ html.wide :is(.work, .hdr-top, .tabs, .foot) { max-width: none; }
136 .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }145 .rail { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }
137 .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); }146 .work-lede { grid-column: 1 / -1; margin-bottom: calc(-1 * var(--s2)); }
138 main > * + * { margin-top: var(--s4); }147 main > * + * { margin-top: var(--s4); }
M internal/adapters/web/static/css/50-code.css +7 -4
@@ -79,13 +79,16 @@
7979 & .ln { width: calc(var(--lnw) + 2ch); min-width: calc(var(--lnw) + 2ch); padding: 0 1ch; text-align: right; color: var(--ink-mute); user-select: none; }
8080 & .m { width: 2ch; text-align: center; user-select: none; color: var(--ink-dim); }
8181 & .tx { white-space: pre; }
82- & .hunk td { color: var(--ink-mute); background: var(--paper-alt); padding: 0 1ch; }
82+ /* the hunk headers and the hidden lines stay quiet: a faint band between
83+ the code, so that the eye keeps reading */
84+ & .hunk td, & .gap td { background: color-mix(in srgb, var(--paper-alt) 35%, var(--paper)); }
85+ & .hunk td { color: var(--ink-mute); padding: 0 1ch; }
8386 /* a run of hidden lines: the whole row loads them */
84- & .gap td { padding: 0; background: var(--paper-alt); }
87+ & .gap td { padding: 0; }
8588 & .gap button {
8689 display: block; width: 100%; padding: 2px 1ch; text-align: left; cursor: pointer;
87- font: inherit; color: var(--ink-dim); background: none; border: 0;
88- border-block: 1px dashed var(--border);
90+ font: inherit; color: var(--ink-mute); background: none; border: 0;
91+ border-block: 1px dashed color-mix(in srgb, var(--border) 60%, transparent);
8992 }
9093 & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); }
9194 & .gap button:disabled { cursor: progress; opacity: .6; }
@@ -79,13 +79,16 @@
79 & .ln { width: calc(var(--lnw) + 2ch); min-width: calc(var(--lnw) + 2ch); padding: 0 1ch; text-align: right; color: var(--ink-mute); user-select: none; }79 & .ln { width: calc(var(--lnw) + 2ch); min-width: calc(var(--lnw) + 2ch); padding: 0 1ch; text-align: right; color: var(--ink-mute); user-select: none; }
80 & .m { width: 2ch; text-align: center; user-select: none; color: var(--ink-dim); }80 & .m { width: 2ch; text-align: center; user-select: none; color: var(--ink-dim); }
81 & .tx { white-space: pre; }81 & .tx { white-space: pre; }
82- & .hunk td { color: var(--ink-mute); background: var(--paper-alt); padding: 0 1ch; }82+ /* the hunk headers and the hidden lines stay quiet: a faint band between
83+ the code, so that the eye keeps reading */
84+ & .hunk td, & .gap td { background: color-mix(in srgb, var(--paper-alt) 35%, var(--paper)); }
85+ & .hunk td { color: var(--ink-mute); padding: 0 1ch; }
83 /* a run of hidden lines: the whole row loads them */86 /* a run of hidden lines: the whole row loads them */
84- & .gap td { padding: 0; background: var(--paper-alt); }87+ & .gap td { padding: 0; }
85 & .gap button {88 & .gap button {
86 display: block; width: 100%; padding: 2px 1ch; text-align: left; cursor: pointer;89 display: block; width: 100%; padding: 2px 1ch; text-align: left; cursor: pointer;
87- font: inherit; color: var(--ink-dim); background: none; border: 0;90+ font: inherit; color: var(--ink-mute); background: none; border: 0;
88- border-block: 1px dashed var(--border);91+ border-block: 1px dashed color-mix(in srgb, var(--border) 60%, transparent);
89 }92 }
90 & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); }93 & .gap button:hover, & .gap button:focus-visible { color: var(--ink-hi); background: var(--bg-strong); }
91 & .gap button:disabled { cursor: progress; opacity: .6; }94 & .gap button:disabled { cursor: progress; opacity: .6; }
M internal/adapters/web/static/css/55-diff.css +25 -0
@@ -9,4 +9,29 @@
99
1010 @layer components {
1111 .sel { outline: 1px solid var(--ink-dim); outline-offset: -1px; }
12+
13+ /* a range of selected diff lines (shift + click) is one block: one border
14+ around the run, none between its lines. Each edge is an inset shadow of
15+ the cells; the top one only on the first line of the run, the bottom one
16+ only on the last. In split, each side is a block of its own. */
17+ .dt {
18+ --sel-edge: var(--ink-dim);
19+ & .sel { outline: none; }
20+ & tr.sel > td, & td.sel {
21+ box-shadow: var(--sel-l, 0 0 transparent), var(--sel-r, 0 0 transparent), var(--sel-t, 0 0 transparent), var(--sel-b, 0 0 transparent);
22+ --sel-t: inset 0 1px var(--sel-edge); --sel-b: inset 0 -1px var(--sel-edge);
23+ }
24+ /* unified: the row is selected */
25+ & tr.sel > td:first-child { --sel-l: inset 1px 0 var(--sel-edge); }
26+ & tr.sel > td:last-child { --sel-r: inset -1px 0 var(--sel-edge); }
27+ & tr.sel + tr.sel > td { --sel-t: 0 0 transparent; }
28+ & tr.sel:has(+ tr.sel) > td { --sel-b: 0 0 transparent; }
29+ /* split: the cells of one side (number, mark, text) are selected */
30+ & td.sel.ln { --sel-l: inset 1px 0 var(--sel-edge); }
31+ & td.sel.tx { --sel-r: inset -1px 0 var(--sel-edge); }
32+ & tr:has(> :nth-child(3).sel) + tr > :nth-child(-n + 3).sel,
33+ & tr:has(> :nth-child(6).sel) + tr > :nth-child(n + 4).sel { --sel-t: 0 0 transparent; }
34+ & tr:has(> :nth-child(3).sel):has(+ tr > :nth-child(3).sel) > :nth-child(-n + 3).sel,
35+ & tr:has(> :nth-child(6).sel):has(+ tr > :nth-child(6).sel) > :nth-child(n + 4).sel { --sel-b: 0 0 transparent; }
36+ }
1237 }
@@ -9,4 +9,29 @@
9 9
10 @layer components {10 @layer components {
11 .sel { outline: 1px solid var(--ink-dim); outline-offset: -1px; }11 .sel { outline: 1px solid var(--ink-dim); outline-offset: -1px; }
12+
13+ /* a range of selected diff lines (shift + click) is one block: one border
14+ around the run, none between its lines. Each edge is an inset shadow of
15+ the cells; the top one only on the first line of the run, the bottom one
16+ only on the last. In split, each side is a block of its own. */
17+ .dt {
18+ --sel-edge: var(--ink-dim);
19+ & .sel { outline: none; }
20+ & tr.sel > td, & td.sel {
21+ box-shadow: var(--sel-l, 0 0 transparent), var(--sel-r, 0 0 transparent), var(--sel-t, 0 0 transparent), var(--sel-b, 0 0 transparent);
22+ --sel-t: inset 0 1px var(--sel-edge); --sel-b: inset 0 -1px var(--sel-edge);
23+ }
24+ /* unified: the row is selected */
25+ & tr.sel > td:first-child { --sel-l: inset 1px 0 var(--sel-edge); }
26+ & tr.sel > td:last-child { --sel-r: inset -1px 0 var(--sel-edge); }
27+ & tr.sel + tr.sel > td { --sel-t: 0 0 transparent; }
28+ & tr.sel:has(+ tr.sel) > td { --sel-b: 0 0 transparent; }
29+ /* split: the cells of one side (number, mark, text) are selected */
30+ & td.sel.ln { --sel-l: inset 1px 0 var(--sel-edge); }
31+ & td.sel.tx { --sel-r: inset -1px 0 var(--sel-edge); }
32+ & tr:has(> :nth-child(3).sel) + tr > :nth-child(-n + 3).sel,
33+ & tr:has(> :nth-child(6).sel) + tr > :nth-child(n + 4).sel { --sel-t: 0 0 transparent; }
34+ & tr:has(> :nth-child(3).sel):has(+ tr > :nth-child(3).sel) > :nth-child(-n + 3).sel,
35+ & tr:has(> :nth-child(6).sel):has(+ tr > :nth-child(6).sel) > :nth-child(n + 4).sel { --sel-b: 0 0 transparent; }
36+ }
12 }37 }
M internal/adapters/web/static/css/60-mr.css +15 -1
@@ -1,6 +1,12 @@
11 @layer components {
22 /* merge requests: the review page. Hue only where it carries a verdict. */
33 .mr-h .show-kv { margin-top: 0; }
4+ /* one card: the header (title, facts) a shade lighter, the description below */
5+ .mr-card {
6+ background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
7+ & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; padding-bottom: var(--s3); }
8+ & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; }
9+ }
410
511 .mr-state[data-state="open"]::before { content: "● "; color: var(--teal); }
612 .mr-state[data-state="merged"]::before { content: "✓ "; color: var(--teal); }
@@ -49,6 +55,15 @@
4955 }
5056 & .kv { margin: 0; }
5157 }
58+ /* the view menu of a diff: the layout and the fold, one row each */
59+ .vmenu {
60+ & > summary::after { content: ""; width: 0; height: 0; margin-left: 2px; border: 4px solid transparent; border-top-color: currentColor; transform: translateY(2px); }
61+ & .menu-pop { left: 0; right: auto; width: 220px; gap: var(--s1); padding: var(--s2); }
62+ & .menu-pop .btn { width: 100%; justify-content: flex-start; border-color: transparent; background: none; }
63+ & .menu-pop .btn:hover { background: var(--bg-raised); }
64+ & .menu-pop .btn[aria-current="true"] { background: var(--bg-raised); color: var(--ink-hi); }
65+ & .menu-pop .btn kbd { margin-left: auto; }
66+ }
5267 .mmenu {
5368 & .d-modes { display: grid; gap: var(--s1); border: 0; padding: 0; margin: 0; }
5469 & .d-modes label, & .d-check {
@@ -207,7 +222,6 @@
207222 & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); }
208223 & .since { align-self: flex-start; max-width: 100%; }
209224 }
210- .inbox-btn { gap: var(--s2); }
211225
212226 @media (max-width: 700px) {
213227 .list.mrs .c-who { display: none; }
@@ -1,6 +1,12 @@
1 @layer components {1 @layer components {
2 /* merge requests: the review page. Hue only where it carries a verdict. */2 /* merge requests: the review page. Hue only where it carries a verdict. */
3 .mr-h .show-kv { margin-top: 0; }3 .mr-h .show-kv { margin-top: 0; }
4+ /* one card: the header (title, facts) a shade lighter, the description below */
5+ .mr-card {
6+ background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden;
7+ & > .mr-h { background: var(--bg-raised); border: 0; border-radius: 0; padding-bottom: var(--s3); }
8+ & > .mr-desc.prose { border: 0; border-top: 1px solid var(--border); border-radius: 0; }
9+ }
4 10
5 .mr-state[data-state="open"]::before { content: "● "; color: var(--teal); }11 .mr-state[data-state="open"]::before { content: "● "; color: var(--teal); }
6 .mr-state[data-state="merged"]::before { content: "✓ "; color: var(--teal); }12 .mr-state[data-state="merged"]::before { content: "✓ "; color: var(--teal); }
@@ -49,6 +55,15 @@
49 }55 }
50 & .kv { margin: 0; }56 & .kv { margin: 0; }
51 }57 }
58+ /* the view menu of a diff: the layout and the fold, one row each */
59+ .vmenu {
60+ & > summary::after { content: ""; width: 0; height: 0; margin-left: 2px; border: 4px solid transparent; border-top-color: currentColor; transform: translateY(2px); }
61+ & .menu-pop { left: 0; right: auto; width: 220px; gap: var(--s1); padding: var(--s2); }
62+ & .menu-pop .btn { width: 100%; justify-content: flex-start; border-color: transparent; background: none; }
63+ & .menu-pop .btn:hover { background: var(--bg-raised); }
64+ & .menu-pop .btn[aria-current="true"] { background: var(--bg-raised); color: var(--ink-hi); }
65+ & .menu-pop .btn kbd { margin-left: auto; }
66+ }
52 .mmenu {67 .mmenu {
53 & .d-modes { display: grid; gap: var(--s1); border: 0; padding: 0; margin: 0; }68 & .d-modes { display: grid; gap: var(--s1); border: 0; padding: 0; margin: 0; }
54 & .d-modes label, & .d-check {69 & .d-modes label, & .d-check {
@@ -207,7 +222,6 @@
207 & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); }222 & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); }
208 & .since { align-self: flex-start; max-width: 100%; }223 & .since { align-self: flex-start; max-width: 100%; }
209 }224 }
210- .inbox-btn { gap: var(--s2); }
211 225
212 @media (max-width: 700px) {226 @media (max-width: 700px) {
213 .list.mrs .c-who { display: none; }227 .list.mrs .c-who { display: none; }
A internal/adapters/web/static/icons/mailbox.svg +1 -0
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg>
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg>
M internal/adapters/web/static/js/ermite.js +70 -3
@@ -14,6 +14,7 @@
1414 // 7. The service worker (ermiteInstall).
1515 // 8. Chip lists kept on one line, with a … chip (ermiteFit).
1616 // 9. The layout of the home page, remembered in the browser (ermiteLayout).
17+// 10. The wide mode: every page takes the whole width (ermiteWide).
1718 //
1819 // Preferences are kept by ermiteStore (store.js, loaded first).
1920 (() => {
@@ -400,6 +401,25 @@
400401 // file whose lines are all loaded has no gap left.
401402 window.ermiteGaps = (() => {
402403 const pending = new Map();
404+ // selectLoaded marks the loaded rows that fall in the range of lines the
405+ // URL selects (?on=new:21-149:a.go, a shift + click), as the server marks
406+ // the rows it sent: the range stays one block once its hidden lines show.
407+ // The loaded rows are context lines, the same on both sides.
408+ function selectLoaded(file, rows) {
409+ const m = /^(new|old):(?:(\d+)-)?(\d+):(.+)$/.exec(new URLSearchParams(location.search).get('on') || '');
410+ // data-file is the path comments name (the new path of a rename), as ?on=
411+ if (!m || file.dataset.file !== m[4]) return;
412+ const end = Number(m[3]);
413+ const start = m[2] ? Number(m[2]) : end;
414+ for (const r of rows) {
415+ const cells = [...r.querySelectorAll('td')];
416+ // unified: old, new, mark, text; split: old side, then new side
417+ const num = Number((cells.length === 4 ? cells[m[1] === 'old' ? 0 : 1] : cells[m[1] === 'old' ? 0 : 3])?.textContent.trim());
418+ if (!(num >= start && num <= end)) continue;
419+ if (cells.length === 4) r.classList.add('sel');
420+ else for (const c of cells) c.classList.add('sel');
421+ }
422+ }
403423 // load fills the gap the button is in, in both tables of its file.
404424 function load(btn) {
405425 const src = btn.dataset.src;
@@ -424,6 +444,7 @@
424444 if (!from || !at) continue;
425445 const rows = [...from.children].map((r) => r.cloneNode(true));
426446 n = Math.max(n, rows.filter((r) => !r.classList.contains('gap')).length);
447+ selectLoaded(file, rows.filter((r) => !r.classList.contains('gap')));
427448 at.replaceWith(...rows);
428449 }
429450 // The focus was on the button, which is gone: on the gap that is
@@ -553,9 +574,44 @@
553574 return { get: pref.get, set, apply, values };
554575 })();
555576
577+ // The wide mode (w, on every page): the work area takes the whole width of
578+ // the screen, the rail at the right edge and the content in the rest.
579+ // The choice is kept by ermiteStore ("wide": '1' when wide) and shown
580+ // as html.wide, which the styles follow. Another tab's change applies here
581+ // at once.
582+ window.ermiteWide = (() => {
583+ const pref = window.ermiteStore.pref('wide', { values: ['1', ''], fallback: '' });
584+ function apply() {
585+ const wide = pref.get() === '1';
586+ document.documentElement.classList.toggle?.('wide', wide);
587+ }
588+ function toggle() {
589+ pref.set(pref.get() === '1' ? '' : '1');
590+ apply();
591+ }
592+ pref.watch(apply);
593+ apply();
594+ return { toggle, apply };
595+ })();
596+
597+ // The menus of the bars (<details class="menu">: the diff view, the merge
598+ // menu): closing one hands the focus back to its button when it was inside,
599+ // so that a keyboard user keeps their place.
600+ function closeMenu(d) {
601+ const had = d.contains(document.activeElement);
602+ d.open = false;
603+ if (had) $('summary', d)?.focus();
604+ }
605+ // closeMenus closes every open menu (escape); true when one was open.
606+ function closeMenus() {
607+ const open = $$('details.menu[open]');
608+ for (const d of open) closeMenu(d);
609+ return open.length > 0;
610+ }
611+
556612 // 1. Keys.
557613 const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink',
558- s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc',
614+ s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', w: 'wide', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc',
559615 // the merge request page (mr.js): the merge menu, mark read
560616 M: 'merge', R: 'read' };
561617 // "g t" is the key "t": the tabs have one event each.
@@ -571,7 +627,7 @@
571627 const mr = window.ermiteMR;
572628 if (e.key === 'Escape') {
573629 pending = 0;
574- if (mr?.closeMenus() || mr?.leaveFiles()) return;
630+ if (closeMenus() || mr?.leaveFiles()) return;
575631 if (typing) e.target.blur();
576632 return ermite.fire('esc');
577633 }
@@ -611,6 +667,13 @@
611667 if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
612668 e.preventDefault();
613669 ermite.fire(el.dataset.fire);
670+ // A choice in a menu closes it; the focus goes back to its button.
671+ const menu = el.closest('details.menu');
672+ if (menu) closeMenu(menu);
673+ });
674+ // A click outside an open menu closes it.
675+ addEventListener('click', (e) => {
676+ for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;
614677 });
615678 for (const a of $$('a[data-fire="refs"]')) {
616679 a.setAttribute('aria-haspopup', 'dialog');
@@ -773,7 +836,8 @@
773836 // internal/adapters/web/events.go). A fragment is a top-level element with an
774837 // id: it replaces the element of the page that has the same id. A fragment
775838 // marked data-gone removes it; a row of a repository the home page does not
776-// list yet is added at the top of the list. A slot (data-slot: the place of a
839+// list yet is added at the top of the list. Activity updates also move existing
840+// home rows to the top when sorted by newest activity; CI updates stay put. A slot (data-slot: the place of a
777841 // banner, a persistent status region) keeps its element and only gets the
778842 // children of the fragment, so that screen readers announce the change. A gap,
779843 // which says the page may have missed events or that what it shows changed,
@@ -872,6 +936,9 @@ window.ermiteLive = (() => {
872936 }
873937 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');
874938 old.replaceWith(el);
939+ const rows = document.getElementById('repo-rows');
940+ if (rows && el.parentElement === rows && rows.dataset.sort === 'activity' &&
941+ rows.dataset.desc === 'true' && el.hasAttribute('data-activity')) rows.prepend(el);
875942 window.htmx?.process(el);
876943 fitIn(el);
877944 if (had) refocus(el, had);
@@ -14,6 +14,7 @@
14 // 7. The service worker (ermiteInstall).14 // 7. The service worker (ermiteInstall).
15 // 8. Chip lists kept on one line, with a … chip (ermiteFit).15 // 8. Chip lists kept on one line, with a … chip (ermiteFit).
16 // 9. The layout of the home page, remembered in the browser (ermiteLayout).16 // 9. The layout of the home page, remembered in the browser (ermiteLayout).
17+// 10. The wide mode: every page takes the whole width (ermiteWide).
17 //18 //
18 // Preferences are kept by ermiteStore (store.js, loaded first).19 // Preferences are kept by ermiteStore (store.js, loaded first).
19 (() => {20 (() => {
@@ -400,6 +401,25 @@
400 // file whose lines are all loaded has no gap left.401 // file whose lines are all loaded has no gap left.
401 window.ermiteGaps = (() => {402 window.ermiteGaps = (() => {
402 const pending = new Map();403 const pending = new Map();
404+ // selectLoaded marks the loaded rows that fall in the range of lines the
405+ // URL selects (?on=new:21-149:a.go, a shift + click), as the server marks
406+ // the rows it sent: the range stays one block once its hidden lines show.
407+ // The loaded rows are context lines, the same on both sides.
408+ function selectLoaded(file, rows) {
409+ const m = /^(new|old):(?:(\d+)-)?(\d+):(.+)$/.exec(new URLSearchParams(location.search).get('on') || '');
410+ // data-file is the path comments name (the new path of a rename), as ?on=
411+ if (!m || file.dataset.file !== m[4]) return;
412+ const end = Number(m[3]);
413+ const start = m[2] ? Number(m[2]) : end;
414+ for (const r of rows) {
415+ const cells = [...r.querySelectorAll('td')];
416+ // unified: old, new, mark, text; split: old side, then new side
417+ const num = Number((cells.length === 4 ? cells[m[1] === 'old' ? 0 : 1] : cells[m[1] === 'old' ? 0 : 3])?.textContent.trim());
418+ if (!(num >= start && num <= end)) continue;
419+ if (cells.length === 4) r.classList.add('sel');
420+ else for (const c of cells) c.classList.add('sel');
421+ }
422+ }
403 // load fills the gap the button is in, in both tables of its file.423 // load fills the gap the button is in, in both tables of its file.
404 function load(btn) {424 function load(btn) {
405 const src = btn.dataset.src;425 const src = btn.dataset.src;
@@ -424,6 +444,7 @@
424 if (!from || !at) continue;444 if (!from || !at) continue;
425 const rows = [...from.children].map((r) => r.cloneNode(true));445 const rows = [...from.children].map((r) => r.cloneNode(true));
426 n = Math.max(n, rows.filter((r) => !r.classList.contains('gap')).length);446 n = Math.max(n, rows.filter((r) => !r.classList.contains('gap')).length);
447+ selectLoaded(file, rows.filter((r) => !r.classList.contains('gap')));
427 at.replaceWith(...rows);448 at.replaceWith(...rows);
428 }449 }
429 // The focus was on the button, which is gone: on the gap that is450 // The focus was on the button, which is gone: on the gap that is
@@ -553,9 +574,44 @@
553 return { get: pref.get, set, apply, values };574 return { get: pref.get, set, apply, values };
554 })();575 })();
555 576
577+ // The wide mode (w, on every page): the work area takes the whole width of
578+ // the screen, the rail at the right edge and the content in the rest.
579+ // The choice is kept by ermiteStore ("wide": '1' when wide) and shown
580+ // as html.wide, which the styles follow. Another tab's change applies here
581+ // at once.
582+ window.ermiteWide = (() => {
583+ const pref = window.ermiteStore.pref('wide', { values: ['1', ''], fallback: '' });
584+ function apply() {
585+ const wide = pref.get() === '1';
586+ document.documentElement.classList.toggle?.('wide', wide);
587+ }
588+ function toggle() {
589+ pref.set(pref.get() === '1' ? '' : '1');
590+ apply();
591+ }
592+ pref.watch(apply);
593+ apply();
594+ return { toggle, apply };
595+ })();
596+
597+ // The menus of the bars (<details class="menu">: the diff view, the merge
598+ // menu): closing one hands the focus back to its button when it was inside,
599+ // so that a keyboard user keeps their place.
600+ function closeMenu(d) {
601+ const had = d.contains(document.activeElement);
602+ d.open = false;
603+ if (had) $('summary', d)?.focus();
604+ }
605+ // closeMenus closes every open menu (escape); true when one was open.
606+ function closeMenus() {
607+ const open = $$('details.menu[open]');
608+ for (const d of open) closeMenu(d);
609+ return open.length > 0;
610+ }
611+
556 // 1. Keys.612 // 1. Keys.
557 const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink',613 const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink',
558- s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc',614+ s: 's', t: 't', c: 'c', r: 'r', m: 'm', i: 'i', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', w: 'wide', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc',
559 // the merge request page (mr.js): the merge menu, mark read615 // the merge request page (mr.js): the merge menu, mark read
560 M: 'merge', R: 'read' };616 M: 'merge', R: 'read' };
561 // "g t" is the key "t": the tabs have one event each.617 // "g t" is the key "t": the tabs have one event each.
@@ -571,7 +627,7 @@
571 const mr = window.ermiteMR;627 const mr = window.ermiteMR;
572 if (e.key === 'Escape') {628 if (e.key === 'Escape') {
573 pending = 0;629 pending = 0;
574- if (mr?.closeMenus() || mr?.leaveFiles()) return;630+ if (closeMenus() || mr?.leaveFiles()) return;
575 if (typing) e.target.blur();631 if (typing) e.target.blur();
576 return ermite.fire('esc');632 return ermite.fire('esc');
577 }633 }
@@ -611,6 +667,13 @@
611 if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;667 if (!el || e.button || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
612 e.preventDefault();668 e.preventDefault();
613 ermite.fire(el.dataset.fire);669 ermite.fire(el.dataset.fire);
670+ // A choice in a menu closes it; the focus goes back to its button.
671+ const menu = el.closest('details.menu');
672+ if (menu) closeMenu(menu);
673+ });
674+ // A click outside an open menu closes it.
675+ addEventListener('click', (e) => {
676+ for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;
614 });677 });
615 for (const a of $$('a[data-fire="refs"]')) {678 for (const a of $$('a[data-fire="refs"]')) {
616 a.setAttribute('aria-haspopup', 'dialog');679 a.setAttribute('aria-haspopup', 'dialog');
@@ -773,7 +836,8 @@
773 // internal/adapters/web/events.go). A fragment is a top-level element with an836 // internal/adapters/web/events.go). A fragment is a top-level element with an
774 // id: it replaces the element of the page that has the same id. A fragment837 // id: it replaces the element of the page that has the same id. A fragment
775 // marked data-gone removes it; a row of a repository the home page does not838 // marked data-gone removes it; a row of a repository the home page does not
776-// list yet is added at the top of the list. A slot (data-slot: the place of a839+// list yet is added at the top of the list. Activity updates also move existing
840+// home rows to the top when sorted by newest activity; CI updates stay put. A slot (data-slot: the place of a
777 // banner, a persistent status region) keeps its element and only gets the841 // banner, a persistent status region) keeps its element and only gets the
778 // children of the fragment, so that screen readers announce the change. A gap,842 // children of the fragment, so that screen readers announce the change. A gap,
779 // which says the page may have missed events or that what it shows changed,843 // which says the page may have missed events or that what it shows changed,
@@ -872,6 +936,9 @@ window.ermiteLive = (() => {
872 }936 }
873 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');937 if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', '');
874 old.replaceWith(el);938 old.replaceWith(el);
939+ const rows = document.getElementById('repo-rows');
940+ if (rows && el.parentElement === rows && rows.dataset.sort === 'activity' &&
941+ rows.dataset.desc === 'true' && el.hasAttribute('data-activity')) rows.prepend(el);
875 window.htmx?.process(el);942 window.htmx?.process(el);
876 fitIn(el);943 fitIn(el);
877 if (had) refocus(el, had);944 if (had) refocus(el, had);
M internal/adapters/web/static/js/mr.js +1 -11
@@ -196,12 +196,6 @@ window.ermiteMR = (() => {
196196 $('[data-default]', m)?.focus();
197197 return true;
198198 }
199- function closeMenus() {
200- const open = $$('details.menu[open]');
201- for (const d of open) d.open = false;
202- if (open.length) $('summary', open[0])?.focus();
203- return open.length > 0;
204- }
205199 // menuKey handles a key while the merge menu is open; true when it used it.
206200 function menuKey(e) {
207201 const m = menu();
@@ -311,13 +305,9 @@ window.ermiteMR = (() => {
311305 try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ }
312306 if (next) { e.preventDefault(); location.assign(next); }
313307 });
314- // A click outside an open menu closes it.
315- addEventListener('click', (e) => {
316- for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;
317- });
318308 }
319309
320- return { core, start, load, reveal, openMerge, closeMenus, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };
310+ return { core, start, load, reveal, openMerge, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };
321311 })();
322312
323313 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => window.ermiteMR.start());
@@ -196,12 +196,6 @@ window.ermiteMR = (() => {
196 $('[data-default]', m)?.focus();196 $('[data-default]', m)?.focus();
197 return true;197 return true;
198 }198 }
199- function closeMenus() {
200- const open = $$('details.menu[open]');
201- for (const d of open) d.open = false;
202- if (open.length) $('summary', open[0])?.focus();
203- return open.length > 0;
204- }
205 // menuKey handles a key while the merge menu is open; true when it used it.199 // menuKey handles a key while the merge menu is open; true when it used it.
206 function menuKey(e) {200 function menuKey(e) {
207 const m = menu();201 const m = menu();
@@ -311,13 +305,9 @@ window.ermiteMR = (() => {
311 try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ }305 try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ }
312 if (next) { e.preventDefault(); location.assign(next); }306 if (next) { e.preventDefault(); location.assign(next); }
313 });307 });
314- // A click outside an open menu closes it.
315- addEventListener('click', (e) => {
316- for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;
317- });
318 }308 }
319 309
320- return { core, start, load, reveal, openMerge, closeMenus, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };310+ return { core, start, load, reveal, openMerge, menuKey, markRead, focusFiles, stepFiles, leaveFiles, foldFiles, openFile };
321 })();311 })();
322 312
323 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => window.ermiteMR.start());313 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => window.ermiteMR.start());
M internal/adapters/web/static/js/store.js +5 -0
@@ -74,3 +74,8 @@ window.ermiteStore = (() => {
7474
7575 return { get, set, remove, watch, pref };
7676 })();
77+
78+// The wide mode (ermiteWide in ermite.js) is set here, before the first paint:
79+// shell.templ loads this file without defer, so a wide page never draws narrow
80+// first.
81+if (window.ermiteStore.get('wide') === '1') document?.documentElement?.classList?.add('wide');
@@ -74,3 +74,8 @@ window.ermiteStore = (() => {
74 74
75 return { get, set, remove, watch, pref };75 return { get, set, remove, watch, pref };
76 })();76 })();
77+
78+// The wide mode (ermiteWide in ermite.js) is set here, before the first paint:
79+// shell.templ loads this file without defer, so a wide page never draws narrow
80+// first.
81+if (window.ermiteStore.get('wide') === '1') document?.documentElement?.classList?.add('wide');
M internal/adapters/web/view.go +6 -4
@@ -71,6 +71,9 @@ type Layout struct {
7171 MR bool
7272 // IssueForm loads the script of the public issue form (issue-form.js).
7373 IssueForm bool
74+ // Inbox is the button to the owner's inbox, next to the logo; set on the
75+ // home page only, for the signed-in owner.
76+ Inbox *InboxButton
7477 }
7578
7679 // Link is a plain label + href pair.
@@ -93,7 +96,7 @@ type HomeRow struct {
9396 Stats *HomeStats
9497 // Private marks a private repository; only set for a signed-in visitor.
9598 Private bool
96- // Fresh marks a row that a push just updated (live updates only).
99+ // Fresh marks a row that activity just updated (live updates only).
97100 Fresh bool
98101 // Commits are the newest of the default branch. NoCommits says there is
99102 // none (never pushed, or an empty branch); a failed read says nothing.
@@ -135,11 +138,10 @@ type SortLink struct {
135138
136139 // HomePage is the repository list.
137140 type HomePage struct {
141+ Sort string
142+ Desc bool
138143 Sorts []SortLink
139144 Rows []HomeRow
140- // Inbox is the button to the owner's inbox; nil unless the owner is signed
141- // in.
142- Inbox *InboxButton
143145 }
144146
145147 // InboxButton is the link to the inbox with the number of open items; Count is
@@ -71,6 +71,9 @@ type Layout struct {
71 MR bool71 MR bool
72 // IssueForm loads the script of the public issue form (issue-form.js).72 // IssueForm loads the script of the public issue form (issue-form.js).
73 IssueForm bool73 IssueForm bool
74+ // Inbox is the button to the owner's inbox, next to the logo; set on the
75+ // home page only, for the signed-in owner.
76+ Inbox *InboxButton
74 }77 }
75 78
76 // Link is a plain label + href pair.79 // Link is a plain label + href pair.
@@ -93,7 +96,7 @@ type HomeRow struct {
93 Stats *HomeStats96 Stats *HomeStats
94 // 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.
95 Private bool98 Private bool
96- // Fresh marks a row that a push just updated (live updates only).99+ // Fresh marks a row that activity just updated (live updates only).
97 Fresh bool100 Fresh bool
98 // Commits are the newest of the default branch. NoCommits says there is101 // Commits are the newest of the default branch. NoCommits says there is
99 // 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.
@@ -135,11 +138,10 @@ type SortLink struct {
135 138
136 // HomePage is the repository list.139 // HomePage is the repository list.
137 type HomePage struct {140 type HomePage struct {
141+ Sort string
142+ Desc bool
138 Sorts []SortLink143 Sorts []SortLink
139 Rows []HomeRow144 Rows []HomeRow
140- // Inbox is the button to the owner's inbox; nil unless the owner is signed
141- // in.
142- Inbox *InboxButton
143 }145 }
144 146
145 // InboxButton is the link to the inbox with the number of open items; Count is147 // InboxButton is the link to the inbox with the number of open items; Count is
M internal/app/testdata/web/blob-binary.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -239,6 +239,12 @@
239239 </div>
240240 <div>
241241 <dt>
242+<kbd>w</kbd>
243+</dt>
244+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
245+</div>
246+<div>
247+<dt>
242248 <kbd>y</kbd>
243249 </dt>
244250 <dd>copy a permalink pinned to the commit</dd>
@@ -416,6 +422,7 @@
416422 k-split from:body -> ermite.layout('split');
417423 k-unified from:body -> ermite.layout('unified');
418424 k-collapse from:body -> ermite.collapse();
425+ k-wide from:body -> window.ermiteWide.toggle();
419426 k-expand from:body -> ermite.expandFile();
420427 k-expandAll from:body -> ermite.expandAll();
421428 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -239,6 +239,12 @@
239 </div>239 </div>
240 <div>240 <div>
241 <dt>241 <dt>
242+<kbd>w</kbd>
243+</dt>
244+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
245+</div>
246+<div>
247+<dt>
242 <kbd>y</kbd>248 <kbd>y</kbd>
243 </dt>249 </dt>
244 <dd>copy a permalink pinned to the commit</dd>250 <dd>copy a permalink pinned to the commit</dd>
@@ -416,6 +422,7 @@
416 k-split from:body -> ermite.layout('split');422 k-split from:body -> ermite.layout('split');
417 k-unified from:body -> ermite.layout('unified');423 k-unified from:body -> ermite.layout('unified');
418 k-collapse from:body -> ermite.collapse();424 k-collapse from:body -> ermite.collapse();
425+ k-wide from:body -> window.ermiteWide.toggle();
419 k-expand from:body -> ermite.expandFile();426 k-expand from:body -> ermite.expandFile();
420 k-expandAll from:body -> ermite.expandAll();427 k-expandAll from:body -> ermite.expandAll();
421 k-permalink from:body -> ermite.permalink();428 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-code.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -309,6 +309,12 @@
309309 </div>
310310 <div>
311311 <dt>
312+<kbd>w</kbd>
313+</dt>
314+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
315+</div>
316+<div>
317+<dt>
312318 <kbd>y</kbd>
313319 </dt>
314320 <dd>copy a permalink pinned to the commit</dd>
@@ -486,6 +492,7 @@
486492 k-split from:body -> ermite.layout('split');
487493 k-unified from:body -> ermite.layout('unified');
488494 k-collapse from:body -> ermite.collapse();
495+ k-wide from:body -> window.ermiteWide.toggle();
489496 k-expand from:body -> ermite.expandFile();
490497 k-expandAll from:body -> ermite.expandAll();
491498 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -309,6 +309,12 @@
309 </div>309 </div>
310 <div>310 <div>
311 <dt>311 <dt>
312+<kbd>w</kbd>
313+</dt>
314+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
315+</div>
316+<div>
317+<dt>
312 <kbd>y</kbd>318 <kbd>y</kbd>
313 </dt>319 </dt>
314 <dd>copy a permalink pinned to the commit</dd>320 <dd>copy a permalink pinned to the commit</dd>
@@ -486,6 +492,7 @@
486 k-split from:body -> ermite.layout('split');492 k-split from:body -> ermite.layout('split');
487 k-unified from:body -> ermite.layout('unified');493 k-unified from:body -> ermite.layout('unified');
488 k-collapse from:body -> ermite.collapse();494 k-collapse from:body -> ermite.collapse();
495+ k-wide from:body -> window.ermiteWide.toggle();
489 k-expand from:body -> ermite.expandFile();496 k-expand from:body -> ermite.expandFile();
490 k-expandAll from:body -> ermite.expandAll();497 k-expandAll from:body -> ermite.expandAll();
491 k-permalink from:body -> ermite.permalink();498 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-empty.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -239,6 +239,12 @@
239239 </div>
240240 <div>
241241 <dt>
242+<kbd>w</kbd>
243+</dt>
244+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
245+</div>
246+<div>
247+<dt>
242248 <kbd>y</kbd>
243249 </dt>
244250 <dd>copy a permalink pinned to the commit</dd>
@@ -416,6 +422,7 @@
416422 k-split from:body -> ermite.layout('split');
417423 k-unified from:body -> ermite.layout('unified');
418424 k-collapse from:body -> ermite.collapse();
425+ k-wide from:body -> window.ermiteWide.toggle();
419426 k-expand from:body -> ermite.expandFile();
420427 k-expandAll from:body -> ermite.expandAll();
421428 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -239,6 +239,12 @@
239 </div>239 </div>
240 <div>240 <div>
241 <dt>241 <dt>
242+<kbd>w</kbd>
243+</dt>
244+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
245+</div>
246+<div>
247+<dt>
242 <kbd>y</kbd>248 <kbd>y</kbd>
243 </dt>249 </dt>
244 <dd>copy a permalink pinned to the commit</dd>250 <dd>copy a permalink pinned to the commit</dd>
@@ -416,6 +422,7 @@
416 k-split from:body -> ermite.layout('split');422 k-split from:body -> ermite.layout('split');
417 k-unified from:body -> ermite.layout('unified');423 k-unified from:body -> ermite.layout('unified');
418 k-collapse from:body -> ermite.collapse();424 k-collapse from:body -> ermite.collapse();
425+ k-wide from:body -> window.ermiteWide.toggle();
419 k-expand from:body -> ermite.expandFile();426 k-expand from:body -> ermite.expandFile();
420 k-expandAll from:body -> ermite.expandAll();427 k-expandAll from:body -> ermite.expandAll();
421 k-permalink from:body -> ermite.permalink();428 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-image.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -241,6 +241,12 @@
241241 </div>
242242 <div>
243243 <dt>
244+<kbd>w</kbd>
245+</dt>
246+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
247+</div>
248+<div>
249+<dt>
244250 <kbd>y</kbd>
245251 </dt>
246252 <dd>copy a permalink pinned to the commit</dd>
@@ -418,6 +424,7 @@
418424 k-split from:body -> ermite.layout('split');
419425 k-unified from:body -> ermite.layout('unified');
420426 k-collapse from:body -> ermite.collapse();
427+ k-wide from:body -> window.ermiteWide.toggle();
421428 k-expand from:body -> ermite.expandFile();
422429 k-expandAll from:body -> ermite.expandAll();
423430 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -241,6 +241,12 @@
241 </div>241 </div>
242 <div>242 <div>
243 <dt>243 <dt>
244+<kbd>w</kbd>
245+</dt>
246+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
247+</div>
248+<div>
249+<dt>
244 <kbd>y</kbd>250 <kbd>y</kbd>
245 </dt>251 </dt>
246 <dd>copy a permalink pinned to the commit</dd>252 <dd>copy a permalink pinned to the commit</dd>
@@ -418,6 +424,7 @@
418 k-split from:body -> ermite.layout('split');424 k-split from:body -> ermite.layout('split');
419 k-unified from:body -> ermite.layout('unified');425 k-unified from:body -> ermite.layout('unified');
420 k-collapse from:body -> ermite.collapse();426 k-collapse from:body -> ermite.collapse();
427+ k-wide from:body -> window.ermiteWide.toggle();
421 k-expand from:body -> ermite.expandFile();428 k-expand from:body -> ermite.expandFile();
422 k-expandAll from:body -> ermite.expandAll();429 k-expandAll from:body -> ermite.expandAll();
423 k-permalink from:body -> ermite.permalink();430 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-markdown.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -306,6 +306,12 @@
306306 </div>
307307 <div>
308308 <dt>
309+<kbd>w</kbd>
310+</dt>
311+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
312+</div>
313+<div>
314+<dt>
309315 <kbd>y</kbd>
310316 </dt>
311317 <dd>copy a permalink pinned to the commit</dd>
@@ -483,6 +489,7 @@
483489 k-split from:body -> ermite.layout('split');
484490 k-unified from:body -> ermite.layout('unified');
485491 k-collapse from:body -> ermite.collapse();
492+ k-wide from:body -> window.ermiteWide.toggle();
486493 k-expand from:body -> ermite.expandFile();
487494 k-expandAll from:body -> ermite.expandAll();
488495 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -306,6 +306,12 @@
306 </div>306 </div>
307 <div>307 <div>
308 <dt>308 <dt>
309+<kbd>w</kbd>
310+</dt>
311+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
312+</div>
313+<div>
314+<dt>
309 <kbd>y</kbd>315 <kbd>y</kbd>
310 </dt>316 </dt>
311 <dd>copy a permalink pinned to the commit</dd>317 <dd>copy a permalink pinned to the commit</dd>
@@ -483,6 +489,7 @@
483 k-split from:body -> ermite.layout('split');489 k-split from:body -> ermite.layout('split');
484 k-unified from:body -> ermite.layout('unified');490 k-unified from:body -> ermite.layout('unified');
485 k-collapse from:body -> ermite.collapse();491 k-collapse from:body -> ermite.collapse();
492+ k-wide from:body -> window.ermiteWide.toggle();
486 k-expand from:body -> ermite.expandFile();493 k-expand from:body -> ermite.expandFile();
487 k-expandAll from:body -> ermite.expandAll();494 k-expandAll from:body -> ermite.expandAll();
488 k-permalink from:body -> ermite.permalink();495 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-svg.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -264,6 +264,12 @@
264264 </div>
265265 <div>
266266 <dt>
267+<kbd>w</kbd>
268+</dt>
269+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
270+</div>
271+<div>
272+<dt>
267273 <kbd>y</kbd>
268274 </dt>
269275 <dd>copy a permalink pinned to the commit</dd>
@@ -441,6 +447,7 @@
441447 k-split from:body -> ermite.layout('split');
442448 k-unified from:body -> ermite.layout('unified');
443449 k-collapse from:body -> ermite.collapse();
450+ k-wide from:body -> window.ermiteWide.toggle();
444451 k-expand from:body -> ermite.expandFile();
445452 k-expandAll from:body -> ermite.expandAll();
446453 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -264,6 +264,12 @@
264 </div>264 </div>
265 <div>265 <div>
266 <dt>266 <dt>
267+<kbd>w</kbd>
268+</dt>
269+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
270+</div>
271+<div>
272+<dt>
267 <kbd>y</kbd>273 <kbd>y</kbd>
268 </dt>274 </dt>
269 <dd>copy a permalink pinned to the commit</dd>275 <dd>copy a permalink pinned to the commit</dd>
@@ -441,6 +447,7 @@
441 k-split from:body -> ermite.layout('split');447 k-split from:body -> ermite.layout('split');
442 k-unified from:body -> ermite.layout('unified');448 k-unified from:body -> ermite.layout('unified');
443 k-collapse from:body -> ermite.collapse();449 k-collapse from:body -> ermite.collapse();
450+ k-wide from:body -> window.ermiteWide.toggle();
444 k-expand from:body -> ermite.expandFile();451 k-expand from:body -> ermite.expandFile();
445 k-expandAll from:body -> ermite.expandAll();452 k-expandAll from:body -> ermite.expandAll();
446 k-permalink from:body -> ermite.permalink();453 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-text.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -259,6 +259,12 @@
259259 </div>
260260 <div>
261261 <dt>
262+<kbd>w</kbd>
263+</dt>
264+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
265+</div>
266+<div>
267+<dt>
262268 <kbd>y</kbd>
263269 </dt>
264270 <dd>copy a permalink pinned to the commit</dd>
@@ -436,6 +442,7 @@
436442 k-split from:body -> ermite.layout('split');
437443 k-unified from:body -> ermite.layout('unified');
438444 k-collapse from:body -> ermite.collapse();
445+ k-wide from:body -> window.ermiteWide.toggle();
439446 k-expand from:body -> ermite.expandFile();
440447 k-expandAll from:body -> ermite.expandAll();
441448 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -259,6 +259,12 @@
259 </div>259 </div>
260 <div>260 <div>
261 <dt>261 <dt>
262+<kbd>w</kbd>
263+</dt>
264+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
265+</div>
266+<div>
267+<dt>
262 <kbd>y</kbd>268 <kbd>y</kbd>
263 </dt>269 </dt>
264 <dd>copy a permalink pinned to the commit</dd>270 <dd>copy a permalink pinned to the commit</dd>
@@ -436,6 +442,7 @@
436 k-split from:body -> ermite.layout('split');442 k-split from:body -> ermite.layout('split');
437 k-unified from:body -> ermite.layout('unified');443 k-unified from:body -> ermite.layout('unified');
438 k-collapse from:body -> ermite.collapse();444 k-collapse from:body -> ermite.collapse();
445+ k-wide from:body -> window.ermiteWide.toggle();
439 k-expand from:body -> ermite.expandFile();446 k-expand from:body -> ermite.expandFile();
440 k-expandAll from:body -> ermite.expandAll();447 k-expandAll from:body -> ermite.expandAll();
441 k-permalink from:body -> ermite.permalink();448 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-too-large.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -239,6 +239,12 @@
239239 </div>
240240 <div>
241241 <dt>
242+<kbd>w</kbd>
243+</dt>
244+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
245+</div>
246+<div>
247+<dt>
242248 <kbd>y</kbd>
243249 </dt>
244250 <dd>copy a permalink pinned to the commit</dd>
@@ -416,6 +422,7 @@
416422 k-split from:body -> ermite.layout('split');
417423 k-unified from:body -> ermite.layout('unified');
418424 k-collapse from:body -> ermite.collapse();
425+ k-wide from:body -> window.ermiteWide.toggle();
419426 k-expand from:body -> ermite.expandFile();
420427 k-expandAll from:body -> ermite.expandAll();
421428 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -239,6 +239,12 @@
239 </div>239 </div>
240 <div>240 <div>
241 <dt>241 <dt>
242+<kbd>w</kbd>
243+</dt>
244+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
245+</div>
246+<div>
247+<dt>
242 <kbd>y</kbd>248 <kbd>y</kbd>
243 </dt>249 </dt>
244 <dd>copy a permalink pinned to the commit</dd>250 <dd>copy a permalink pinned to the commit</dd>
@@ -416,6 +422,7 @@
416 k-split from:body -> ermite.layout('split');422 k-split from:body -> ermite.layout('split');
417 k-unified from:body -> ermite.layout('unified');423 k-unified from:body -> ermite.layout('unified');
418 k-collapse from:body -> ermite.collapse();424 k-collapse from:body -> ermite.collapse();
425+ k-wide from:body -> window.ermiteWide.toggle();
419 k-expand from:body -> ermite.expandFile();426 k-expand from:body -> ermite.expandFile();
420 k-expandAll from:body -> ermite.expandAll();427 k-expandAll from:body -> ermite.expandAll();
421 k-permalink from:body -> ermite.permalink();428 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/blob-trojan.html +8 -1
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -306,6 +306,12 @@
306306 </div>
307307 <div>
308308 <dt>
309+<kbd>w</kbd>
310+</dt>
311+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
312+</div>
313+<div>
314+<dt>
309315 <kbd>y</kbd>
310316 </dt>
311317 <dd>copy a permalink pinned to the commit</dd>
@@ -483,6 +489,7 @@
483489 k-split from:body -> ermite.layout('split');
484490 k-unified from:body -> ermite.layout('unified');
485491 k-collapse from:body -> ermite.collapse();
492+ k-wide from:body -> window.ermiteWide.toggle();
486493 k-expand from:body -> ermite.expandFile();
487494 k-expandAll from:body -> ermite.expandAll();
488495 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -306,6 +306,12 @@
306 </div>306 </div>
307 <div>307 <div>
308 <dt>308 <dt>
309+<kbd>w</kbd>
310+</dt>
311+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
312+</div>
313+<div>
314+<dt>
309 <kbd>y</kbd>315 <kbd>y</kbd>
310 </dt>316 </dt>
311 <dd>copy a permalink pinned to the commit</dd>317 <dd>copy a permalink pinned to the commit</dd>
@@ -483,6 +489,7 @@
483 k-split from:body -> ermite.layout('split');489 k-split from:body -> ermite.layout('split');
484 k-unified from:body -> ermite.layout('unified');490 k-unified from:body -> ermite.layout('unified');
485 k-collapse from:body -> ermite.collapse();491 k-collapse from:body -> ermite.collapse();
492+ k-wide from:body -> window.ermiteWide.toggle();
486 k-expand from:body -> ermite.expandFile();493 k-expand from:body -> ermite.expandFile();
487 k-expandAll from:body -> ermite.expandAll();494 k-expandAll from:body -> ermite.expandAll();
488 k-permalink from:body -> ermite.permalink();495 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-big.html +16 -2
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -125,6 +125,11 @@
125125 </span>
126126 <span class="grow">
127127 </span>
128+<details class="menu vmenu">
129+<summary class="btn" aria-label="Diff view">
130+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
131+</summary>
132+<div class="menu-pop" role="group" aria-label="Diff view">
128133 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
129134 </a>
130135 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -134,9 +139,11 @@
134139 <kbd aria-hidden="true">x</kbd>
135140 </button>
136141 </div>
142+</details>
143+</div>
137144 <p class="notice">this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.</p>
138145 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
139-<details class="dfile lnw-2" id="f1" data-file>
146+<details class="dfile lnw-2" id="f1" data-file="big.txt">
140147 <summary>
141148 <span class="st">A</span>
142149 <span class="p">big.txt</span>
@@ -281,6 +288,12 @@
281288 </div>
282289 <div>
283290 <dt>
291+<kbd>w</kbd>
292+</dt>
293+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
294+</div>
295+<div>
296+<dt>
284297 <kbd>y</kbd>
285298 </dt>
286299 <dd>copy a permalink pinned to the commit</dd>
@@ -458,6 +471,7 @@
458471 k-split from:body -> ermite.layout('split');
459472 k-unified from:body -> ermite.layout('unified');
460473 k-collapse from:body -> ermite.collapse();
474+ k-wide from:body -> window.ermiteWide.toggle();
461475 k-expand from:body -> ermite.expandFile();
462476 k-expandAll from:body -> ermite.expandAll();
463477 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -125,6 +125,11 @@
125 </span>125 </span>
126 <span class="grow">126 <span class="grow">
127 </span>127 </span>
128+<details class="menu vmenu">
129+<summary class="btn" aria-label="Diff view">
130+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
131+</summary>
132+<div class="menu-pop" role="group" aria-label="Diff view">
128 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>133 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
129 </a>134 </a>
130 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>135 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -134,9 +139,11 @@
134 <kbd aria-hidden="true">x</kbd>139 <kbd aria-hidden="true">x</kbd>
135 </button>140 </button>
136 </div>141 </div>
142+</details>
143+</div>
137 <p class="notice">this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.</p>144 <p class="notice">this diff is incomplete: 1 file is too large to show. the totals above count everything; clone the repository to read the rest.</p>
138 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">145 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
139-<details class="dfile lnw-2" id="f1" data-file>146+<details class="dfile lnw-2" id="f1" data-file="big.txt">
140 <summary>147 <summary>
141 <span class="st">A</span>148 <span class="st">A</span>
142 <span class="p">big.txt</span>149 <span class="p">big.txt</span>
@@ -281,6 +288,12 @@
281 </div>288 </div>
282 <div>289 <div>
283 <dt>290 <dt>
291+<kbd>w</kbd>
292+</dt>
293+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
294+</div>
295+<div>
296+<dt>
284 <kbd>y</kbd>297 <kbd>y</kbd>
285 </dt>298 </dt>
286 <dd>copy a permalink pinned to the commit</dd>299 <dd>copy a permalink pinned to the commit</dd>
@@ -458,6 +471,7 @@
458 k-split from:body -> ermite.layout('split');471 k-split from:body -> ermite.layout('split');
459 k-unified from:body -> ermite.layout('unified');472 k-unified from:body -> ermite.layout('unified');
460 k-collapse from:body -> ermite.collapse();473 k-collapse from:body -> ermite.collapse();
474+ k-wide from:body -> window.ermiteWide.toggle();
461 k-expand from:body -> ermite.expandFile();475 k-expand from:body -> ermite.expandFile();
462 k-expandAll from:body -> ermite.expandAll();476 k-expandAll from:body -> ermite.expandAll();
463 k-permalink from:body -> ermite.permalink();477 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-binary.html +16 -2
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -121,6 +121,11 @@
121121 </span>
122122 <span class="grow">
123123 </span>
124+<details class="menu vmenu">
125+<summary class="btn" aria-label="Diff view">
126+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
127+</summary>
128+<div class="menu-pop" role="group" aria-label="Diff view">
124129 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
125130 </a>
126131 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -130,8 +135,10 @@
130135 <kbd aria-hidden="true">x</kbd>
131136 </button>
132137 </div>
138+</details>
139+</div>
133140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
134-<details class="dfile lnw-2" id="f1" data-file>
141+<details class="dfile lnw-2" id="f1" data-file="logo.bin">
135142 <summary>
136143 <span class="st">M</span>
137144 <span class="p">logo.bin</span>
@@ -273,6 +280,12 @@
273280 </div>
274281 <div>
275282 <dt>
283+<kbd>w</kbd>
284+</dt>
285+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
286+</div>
287+<div>
288+<dt>
276289 <kbd>y</kbd>
277290 </dt>
278291 <dd>copy a permalink pinned to the commit</dd>
@@ -450,6 +463,7 @@
450463 k-split from:body -> ermite.layout('split');
451464 k-unified from:body -> ermite.layout('unified');
452465 k-collapse from:body -> ermite.collapse();
466+ k-wide from:body -> window.ermiteWide.toggle();
453467 k-expand from:body -> ermite.expandFile();
454468 k-expandAll from:body -> ermite.expandAll();
455469 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -121,6 +121,11 @@
121 </span>121 </span>
122 <span class="grow">122 <span class="grow">
123 </span>123 </span>
124+<details class="menu vmenu">
125+<summary class="btn" aria-label="Diff view">
126+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
127+</summary>
128+<div class="menu-pop" role="group" aria-label="Diff view">
124 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>129 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
125 </a>130 </a>
126 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>131 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -130,8 +135,10 @@
130 <kbd aria-hidden="true">x</kbd>135 <kbd aria-hidden="true">x</kbd>
131 </button>136 </button>
132 </div>137 </div>
138+</details>
139+</div>
133 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">140 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
134-<details class="dfile lnw-2" id="f1" data-file>141+<details class="dfile lnw-2" id="f1" data-file="logo.bin">
135 <summary>142 <summary>
136 <span class="st">M</span>143 <span class="st">M</span>
137 <span class="p">logo.bin</span>144 <span class="p">logo.bin</span>
@@ -273,6 +280,12 @@
273 </div>280 </div>
274 <div>281 <div>
275 <dt>282 <dt>
283+<kbd>w</kbd>
284+</dt>
285+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
286+</div>
287+<div>
288+<dt>
276 <kbd>y</kbd>289 <kbd>y</kbd>
277 </dt>290 </dt>
278 <dd>copy a permalink pinned to the commit</dd>291 <dd>copy a permalink pinned to the commit</dd>
@@ -450,6 +463,7 @@
450 k-split from:body -> ermite.layout('split');463 k-split from:body -> ermite.layout('split');
451 k-unified from:body -> ermite.layout('unified');464 k-unified from:body -> ermite.layout('unified');
452 k-collapse from:body -> ermite.collapse();465 k-collapse from:body -> ermite.collapse();
466+ k-wide from:body -> window.ermiteWide.toggle();
453 k-expand from:body -> ermite.expandFile();467 k-expand from:body -> ermite.expandFile();
454 k-expandAll from:body -> ermite.expandAll();468 k-expandAll from:body -> ermite.expandAll();
455 k-permalink from:body -> ermite.permalink();469 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-merge.html +16 -2
@@ -20,7 +20,7 @@
2020 </script>
2121 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
2222 </script>
23-<script src="/-/static/js/store.HASH.js" defer>
23+<script src="/-/static/js/store.HASH.js">
2424 </script>
2525 <script src="/-/static/js/ermite.HASH.js" defer>
2626 </script>
@@ -120,6 +120,11 @@
120120 </span>
121121 <span class="grow">
122122 </span>
123+<details class="menu vmenu">
124+<summary class="btn" aria-label="Diff view">
125+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
126+</summary>
127+<div class="menu-pop" role="group" aria-label="Diff view">
123128 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
124129 </a>
125130 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -129,8 +134,10 @@
129134 <kbd aria-hidden="true">x</kbd>
130135 </button>
131136 </div>
137+</details>
138+</div>
132139 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
133-<details class="dfile lnw-2" id="f1" data-file open>
140+<details class="dfile lnw-2" id="f1" data-file="side.txt" open>
134141 <summary>
135142 <span class="st">A</span>
136143 <span class="p">side.txt</span>
@@ -315,6 +322,12 @@
315322 </div>
316323 <div>
317324 <dt>
325+<kbd>w</kbd>
326+</dt>
327+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
328+</div>
329+<div>
330+<dt>
318331 <kbd>y</kbd>
319332 </dt>
320333 <dd>copy a permalink pinned to the commit</dd>
@@ -492,6 +505,7 @@
492505 k-split from:body -> ermite.layout('split');
493506 k-unified from:body -> ermite.layout('unified');
494507 k-collapse from:body -> ermite.collapse();
508+ k-wide from:body -> window.ermiteWide.toggle();
495509 k-expand from:body -> ermite.expandFile();
496510 k-expandAll from:body -> ermite.expandAll();
497511 k-permalink from:body -> ermite.permalink();
@@ -20,7 +20,7 @@
20 </script>20 </script>
21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>21 <script src="/-/static/js/hx-sse.min.HASH.js" defer>
22 </script>22 </script>
23-<script src="/-/static/js/store.HASH.js" defer>23+<script src="/-/static/js/store.HASH.js">
24 </script>24 </script>
25 <script src="/-/static/js/ermite.HASH.js" defer>25 <script src="/-/static/js/ermite.HASH.js" defer>
26 </script>26 </script>
@@ -120,6 +120,11 @@
120 </span>120 </span>
121 <span class="grow">121 <span class="grow">
122 </span>122 </span>
123+<details class="menu vmenu">
124+<summary class="btn" aria-label="Diff view">
125+<span hx-live="this.textContent = q('#diffs').attr['data-diff'] === 'split' ? 'Split' : 'Unified'">Unified</span>
126+</summary>
127+<div class="menu-pop" role="group" aria-label="Diff view">
123 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>128 <a href="/diffs/commit/SHA40?view=unified" class="btn vbtn" data-view="unified" aria-keyshortcuts="u" aria-current="true" data-fire="unified" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Unified<kbd aria-hidden="true">u</kbd>
124 </a>129 </a>
125 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>130 <a href="/diffs/commit/SHA40?view=split" class="btn vbtn" data-view="split" aria-keyshortcuts="d" data-fire="split" :aria-current="q('#diffs').attr['data-diff'] === this.dataset.view ? 'true' : false">Split<kbd aria-hidden="true">d</kbd>
@@ -129,8 +134,10 @@
129 <kbd aria-hidden="true">x</kbd>134 <kbd aria-hidden="true">x</kbd>
130 </button>135 </button>
131 </div>136 </div>
137+</details>
138+</div>
132 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">139 <div id="diffs" data-diff="unified" hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff">
133-<details class="dfile lnw-2" id="f1" data-file open>140+<details class="dfile lnw-2" id="f1" data-file="side.txt" open>
134 <summary>141 <summary>
135 <span class="st">A</span>142 <span class="st">A</span>
136 <span class="p">side.txt</span>143 <span class="p">side.txt</span>
@@ -315,6 +322,12 @@
315 </div>322 </div>
316 <div>323 <div>
317 <dt>324 <dt>
325+<kbd>w</kbd>
326+</dt>
327+<dd>wide mode: use the whole width of the screen, or not (remembered)</dd>
328+</div>
329+<div>
330+<dt>
318 <kbd>y</kbd>331 <kbd>y</kbd>
319 </dt>332 </dt>
320 <dd>copy a permalink pinned to the commit</dd>333 <dd>copy a permalink pinned to the commit</dd>
@@ -492,6 +505,7 @@
492 k-split from:body -> ermite.layout('split');505 k-split from:body -> ermite.layout('split');
493 k-unified from:body -> ermite.layout('unified');506 k-unified from:body -> ermite.layout('unified');
494 k-collapse from:body -> ermite.collapse();507 k-collapse from:body -> ermite.collapse();
508+ k-wide from:body -> window.ermiteWide.toggle();
495 k-expand from:body -> ermite.expandFile();509 k-expand from:body -> ermite.expandFile();
496 k-expandAll from:body -> ermite.expandAll();510 k-expandAll from:body -> ermite.expandAll();
497 k-permalink from:body -> ermite.permalink();511 k-permalink from:body -> ermite.permalink();
M internal/app/testdata/web/commit-modify-split.html +17 -3
M internal/app/testdata/web/commit-modify.html +17 -3
M internal/app/testdata/web/commit-rename.html +17 -3
M internal/app/testdata/web/commit-root.html +20 -6
M internal/app/testdata/web/commit-trojan.html +16 -2
M internal/app/testdata/web/commits-page2.html +8 -1
M internal/app/testdata/web/commits-path.html +8 -1
M internal/app/testdata/web/commits-root.html +8 -1
M internal/app/testdata/web/commits-small.html +8 -1
M internal/app/testdata/web/empty.html +8 -1
M internal/app/testdata/web/home.html +40 -31
M internal/app/testdata/web/not-found-page.html +8 -1
M internal/app/testdata/web/not-found.html +8 -1
M internal/app/testdata/web/refs.html +8 -1
M internal/app/testdata/web/summary.html +8 -1
M internal/app/testdata/web/tree-nested.html +8 -1
M internal/app/testdata/web/tree-root.html +8 -1
M internal/app/testdata/web/tree-slash-ref.html +8 -1
M internal/app/testdata/web/tree-subdir.html +8 -1
M internal/app/web_events_test.go +2 -2
M internal/app/web_mr_test.go +2 -0
M internal/domain/repo.go +1 -0
M internal/ports/dto.go +1 -1
M internal/ports/mocks/RepoMetaStore.go +36 -21
M internal/ports/ports.go +7 -4
A internal/usecase/activity.go +43 -0
A internal/usecase/activity_store_test.go +46 -0
A internal/usecase/activity_test.go +47 -0
M internal/usecase/export_test.go +10 -0
M internal/usecase/issue_moderation.go +1 -0
M internal/usecase/issue_moderation_test.go +1 -0
M internal/usecase/issues.go +3 -0
M internal/usecase/issues_integration_test.go +67 -0
M internal/usecase/issues_test.go +2 -0
M internal/usecase/mergerequests.go +5 -0
M internal/usecase/mergerequests_integration_test.go +11 -0
M internal/usecase/pushes.go +21 -29
M internal/usecase/pushes_test.go +11 -16
A internal/usecase/repo_mutation_test.go +34 -0
M internal/usecase/repos_write.go +6 -14
M internal/usecase/repos_write_test.go +20 -21
M proto/ermite/storage/repo.proto +2 -0

Comments0 open / 0

no comments yet.

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
l
merge request: the list of files (j k move, enter opens, esc back)
M
merge request: the merge menu; there 1–3 pick the mode, D deletes the branch, enter merges
R
merge request: mark the head read
esc
close a dialog, leave a field

Branches 2

Tags 44

↑↓ moveenter openesc close