feat(web): recent actions on the summary and visible status marks (#25)
- commit
- 09479031802551efcc45e4ac5c1782372a8df4f3
- author
- brice · · 2026-10-14 15:58 UTC
- committer
- bnema · · 2026-10-14 15:58 UTC
- parent
- dd8597a
- signature
- SSH Unverified
SHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivAThe key is unknown
13 files · +1125 -656
M internal/adapters/kvstore/ci.go +21 -8
| @@ -660,19 +660,32 @@ func (c *CI) ListRuns(ctx context.Context, repo domain.RepoName, before int64, l | |||
| 660 | 660 | if before > 0 { | |
| 661 | 661 | end = runKey(repo, before) | |
| 662 | 662 | } | |
| 663 | - | // Collect then reverse: the snapshot iterates in key order only. | |
| 664 | - | var all []domain.CIRun | |
| 663 | + | // The snapshot iterates in key order only: keep the last limit items in a | |
| 664 | + | // ring and decode only those, so the cost of a page does not grow with the | |
| 665 | + | // decoded history of the repository. | |
| 666 | + | type entry struct { | |
| 667 | + | key []byte | |
| 668 | + | item kv.Item | |
| 669 | + | } | |
| 670 | + | var ring []entry | |
| 671 | + | next := 0 | |
| 665 | 672 | for key, item := range c.db.Snapshot(ctx).Range(ctx, start, end) { | |
| 666 | - | r, err := decodeRun(item) | |
| 673 | + | if limit <= 0 || len(ring) < limit { | |
| 674 | + | ring = append(ring, entry{key, item}) | |
| 675 | + | continue | |
| 676 | + | } | |
| 677 | + | ring[next] = entry{key, item} | |
| 678 | + | next = (next + 1) % limit | |
| 679 | + | } | |
| 680 | + | ordered := append(ring[next:len(ring):len(ring)], ring[:next]...) | |
| 681 | + | all := make([]domain.CIRun, 0, len(ordered)) | |
| 682 | + | for i := len(ordered) - 1; i >= 0; i-- { | |
| 683 | + | r, err := decodeRun(ordered[i].item) | |
| 667 | 684 | if err != nil { | |
| 668 | - | return nil, fmt.Errorf("decode %s: %w", key, err) | |
| 685 | + | return nil, fmt.Errorf("decode %s: %w", ordered[i].key, err) | |
| 669 | 686 | } | |
| 670 | 687 | all = append(all, r) | |
| 671 | 688 | } | |
| 672 | - | slices.Reverse(all) | |
| 673 | - | if limit > 0 && len(all) > limit { | |
| 674 | - | all = all[:limit] | |
| 675 | - | } | |
| 676 | 689 | return all, nil | |
| 677 | 690 | } | |
| 678 | 691 | ||
| @@ -660,19 +660,32 @@ func (c *CI) ListRuns(ctx context.Context, repo domain.RepoName, before int64, l | |||||
| 660 | if before > 0 { | 660 | if before > 0 { | ||
| 661 | end = runKey(repo, before) | 661 | end = runKey(repo, before) | ||
| 662 | } | 662 | } | ||
| 663 | - | // Collect then reverse: the snapshot iterates in key order only. | 663 | + | // The snapshot iterates in key order only: keep the last limit items in a |
| 664 | - | var all []domain.CIRun | 664 | + | // ring and decode only those, so the cost of a page does not grow with the |
| 665 | + | // decoded history of the repository. | |||
| 666 | + | type entry struct { | |||
| 667 | + | key []byte | |||
| 668 | + | item kv.Item | |||
| 669 | + | } | |||
| 670 | + | var ring []entry | |||
| 671 | + | next := 0 | |||
| 665 | for key, item := range c.db.Snapshot(ctx).Range(ctx, start, end) { | 672 | for key, item := range c.db.Snapshot(ctx).Range(ctx, start, end) { | ||
| 666 | - | r, err := decodeRun(item) | 673 | + | if limit <= 0 || len(ring) < limit { |
| 674 | + | ring = append(ring, entry{key, item}) | |||
| 675 | + | continue | |||
| 676 | + | } | |||
| 677 | + | ring[next] = entry{key, item} | |||
| 678 | + | next = (next + 1) % limit | |||
| 679 | + | } | |||
| 680 | + | ordered := append(ring[next:len(ring):len(ring)], ring[:next]...) | |||
| 681 | + | all := make([]domain.CIRun, 0, len(ordered)) | |||
| 682 | + | for i := len(ordered) - 1; i >= 0; i-- { | |||
| 683 | + | r, err := decodeRun(ordered[i].item) | |||
| 667 | if err != nil { | 684 | if err != nil { | ||
| 668 | - | return nil, fmt.Errorf("decode %s: %w", key, err) | 685 | + | return nil, fmt.Errorf("decode %s: %w", ordered[i].key, err) |
| 669 | } | 686 | } | ||
| 670 | all = append(all, r) | 687 | all = append(all, r) | ||
| 671 | } | 688 | } | ||
| 672 | - | slices.Reverse(all) | |||
| 673 | - | if limit > 0 && len(all) > limit { | |||
| 674 | - | all = all[:limit] | |||
| 675 | - | } | |||
| 676 | return all, nil | 689 | return all, nil | ||
| 677 | } | 690 | } | ||
| 678 | 691 | ||||
M internal/adapters/kvstore/ci_test.go +5 -0
| @@ -204,6 +204,11 @@ func TestCIListRunsNewestFirstAndBounded(t *testing.T) { | |||
| 204 | 204 | runs, err = ci.ListRuns(ctx, n, 4, 10) | |
| 205 | 205 | require.NoError(t, err) | |
| 206 | 206 | assert.Equal(t, []int64{3, 2}, []int64{runs[0].ID, runs[1].ID}) | |
| 207 | + | runs, err = ci.ListRuns(ctx, n, 0, 0) | |
| 208 | + | require.NoError(t, err) | |
| 209 | + | require.Len(t, runs, 5) | |
| 210 | + | assert.Equal(t, int64(6), runs[0].ID) | |
| 211 | + | assert.Equal(t, int64(2), runs[4].ID) | |
| 207 | 212 | ids, err := ci.RunIDs(ctx, n) | |
| 208 | 213 | require.NoError(t, err) | |
| 209 | 214 | assert.Len(t, ids, 5) | |
| @@ -204,6 +204,11 @@ func TestCIListRunsNewestFirstAndBounded(t *testing.T) { | |||||
| 204 | runs, err = ci.ListRuns(ctx, n, 4, 10) | 204 | runs, err = ci.ListRuns(ctx, n, 4, 10) | ||
| 205 | require.NoError(t, err) | 205 | require.NoError(t, err) | ||
| 206 | assert.Equal(t, []int64{3, 2}, []int64{runs[0].ID, runs[1].ID}) | 206 | assert.Equal(t, []int64{3, 2}, []int64{runs[0].ID, runs[1].ID}) | ||
| 207 | + | runs, err = ci.ListRuns(ctx, n, 0, 0) | |||
| 208 | + | require.NoError(t, err) | |||
| 209 | + | require.Len(t, runs, 5) | |||
| 210 | + | assert.Equal(t, int64(6), runs[0].ID) | |||
| 211 | + | assert.Equal(t, int64(2), runs[4].ID) | |||
| 207 | ids, err := ci.RunIDs(ctx, n) | 212 | ids, err := ci.RunIDs(ctx, n) | ||
| 208 | require.NoError(t, err) | 213 | require.NoError(t, err) | ||
| 209 | assert.Len(t, ids, 5) | 214 | assert.Len(t, ids, 5) | ||
M internal/adapters/web/actions.templ +21 -0
| @@ -56,6 +56,27 @@ templ RunListRow(r RunRow) { | |||
| 56 | 56 | </tr> | |
| 57 | 57 | } | |
| 58 | 58 | ||
| 59 | + | // RecentActionsBlock is the "Recent actions" section of the summary rail; its | |
| 60 | + | // id is the target of the live updates. Without a run it is hidden. | |
| 61 | + | templ RecentActionsBlock(a RecentActions) { | |
| 62 | + | <section id="recent-actions" aria-labelledby="r-actions" hidden?={ len(a.Runs) == 0 }> | |
| 63 | + | <h2 id="r-actions">Recent actions <a href={ templ.SafeURL(a.AllHref) }>View all</a></h2> | |
| 64 | + | <ul class="rruns"> | |
| 65 | + | for _, r := range a.Runs { | |
| 66 | + | <li> | |
| 67 | + | @CheckIcon(r.Kind, r.State) | |
| 68 | + | <a class="rr-name" href={ templ.SafeURL(r.Href) } title={ r.Workflow + " " + r.Number + " · " + r.State }>{ r.Workflow }</a> | |
| 69 | + | <span class="mute">{ r.Number }</span> | |
| 70 | + | <a class="rr-ref" href={ templ.SafeURL(r.RefHref) }>{ r.Ref }</a> | |
| 71 | + | if r.WhenAt != "" { | |
| 72 | + | <time class="dim" datetime={ r.WhenAt } title={ r.WhenAt }>{ r.When }</time> | |
| 73 | + | } | |
| 74 | + | </li> | |
| 75 | + | } | |
| 76 | + | </ul> | |
| 77 | + | </section> | |
| 78 | + | } | |
| 79 | + | ||
| 59 | 80 | templ RunDetail(l Layout, p RunPage) { | |
| 60 | 81 | @Shell(l, runRail(p)) { | |
| 61 | 82 | @LiveSlot("run-notice") | |
| @@ -56,6 +56,27 @@ templ RunListRow(r RunRow) { | |||||
| 56 | </tr> | 56 | </tr> | ||
| 57 | } | 57 | } | ||
| 58 | 58 | ||||
| 59 | + | // RecentActionsBlock is the "Recent actions" section of the summary rail; its | |||
| 60 | + | // id is the target of the live updates. Without a run it is hidden. | |||
| 61 | + | templ RecentActionsBlock(a RecentActions) { | |||
| 62 | + | <section id="recent-actions" aria-labelledby="r-actions" hidden?={ len(a.Runs) == 0 }> | |||
| 63 | + | <h2 id="r-actions">Recent actions <a href={ templ.SafeURL(a.AllHref) }>View all</a></h2> | |||
| 64 | + | <ul class="rruns"> | |||
| 65 | + | for _, r := range a.Runs { | |||
| 66 | + | <li> | |||
| 67 | + | @CheckIcon(r.Kind, r.State) | |||
| 68 | + | <a class="rr-name" href={ templ.SafeURL(r.Href) } title={ r.Workflow + " " + r.Number + " · " + r.State }>{ r.Workflow }</a> | |||
| 69 | + | <span class="mute">{ r.Number }</span> | |||
| 70 | + | <a class="rr-ref" href={ templ.SafeURL(r.RefHref) }>{ r.Ref }</a> | |||
| 71 | + | if r.WhenAt != "" { | |||
| 72 | + | <time class="dim" datetime={ r.WhenAt } title={ r.WhenAt }>{ r.When }</time> | |||
| 73 | + | } | |||
| 74 | + | </li> | |||
| 75 | + | } | |||
| 76 | + | </ul> | |||
| 77 | + | </section> | |||
| 78 | + | } | |||
| 79 | + | ||||
| 59 | templ RunDetail(l Layout, p RunPage) { | 80 | templ RunDetail(l Layout, p RunPage) { | ||
| 60 | @Shell(l, runRail(p)) { | 81 | @Shell(l, runRail(p)) { | ||
| 61 | @LiveSlot("run-notice") | 82 | @LiveSlot("run-notice") | ||
M internal/adapters/web/actions_templ.go +808 -609 generated file: collapsed
M internal/adapters/web/events.go +2 -1
| @@ -53,7 +53,8 @@ import ( | |||
| 53 | 53 | // commits-head data: <div id="commits-head">N new commits — reload</div> | |
| 54 | 54 | // mr-notice data: <div id="mr-notice">…</div>, on the merge request pages: | |
| 55 | 55 | // something changed, with the link that reloads the page | |
| 56 | - | // ci-run data: <tr id="run-<id>">…</tr> (the list of runs) or | |
| 56 | + | // ci-run data: <tr id="run-<id>">…</tr> (the list of runs), | |
| 57 | + | // <section id="recent-actions">…</section> (the rail of the summary) or | |
| 57 | 58 | // <div id="run-body">…</div> (one run) | |
| 58 | 59 | // ci-job data: <h2 id="job-head">…</h2> and <ol id="job-steps">…</ol>; the log | |
| 59 | 60 | // itself is never sent in this stream: the page follows it on its own | |
| @@ -53,7 +53,8 @@ import ( | |||||
| 53 | // commits-head data: <div id="commits-head">N new commits — reload</div> | 53 | // commits-head data: <div id="commits-head">N new commits — reload</div> | ||
| 54 | // mr-notice data: <div id="mr-notice">…</div>, on the merge request pages: | 54 | // mr-notice data: <div id="mr-notice">…</div>, on the merge request pages: | ||
| 55 | // something changed, with the link that reloads the page | 55 | // something changed, with the link that reloads the page | ||
| 56 | - | // ci-run data: <tr id="run-<id>">…</tr> (the list of runs) or | 56 | + | // ci-run data: <tr id="run-<id>">…</tr> (the list of runs), |
| 57 | + | // <section id="recent-actions">…</section> (the rail of the summary) or | |||
| 57 | // <div id="run-body">…</div> (one run) | 58 | // <div id="run-body">…</div> (one run) | ||
| 58 | // ci-job data: <h2 id="job-head">…</h2> and <ol id="job-steps">…</ol>; the log | 59 | // ci-job data: <h2 id="job-head">…</h2> and <ol id="job-steps">…</ol>; the log | ||
| 59 | // itself is never sent in this stream: the page follows it on its own | 60 | // itself is never sent in this stream: the page follows it on its own | ||
M internal/adapters/web/fragments.go +23 -1
| @@ -407,7 +407,8 @@ func (l *liveStream) notice(ctx context.Context, text, href string) ([]fragment, | |||
| 407 | 407 | } | |
| 408 | 408 | ||
| 409 | 409 | // runEvent updates the pages of CI that a RunUpdated changes: the row of the | |
| 410 | - | // list of runs, or the body of the run page shown. Only a state is sent: the | |
| 410 | + | // list of runs, the recent actions of the summary, or the body of the run page | |
| 411 | + | // shown. Only a state is sent: the | |
| 411 | 412 | // events carry no log and no text of the workflow. | |
| 412 | 413 | func (l *liveStream) runEvent(ctx context.Context, ev domain.RunUpdated) ([]fragment, error) { | |
| 413 | 414 | if l.s.cfg.Runs == nil { | |
| @@ -423,6 +424,8 @@ func (l *liveStream) runEvent(ctx context.Context, ev domain.RunUpdated) ([]frag | |||
| 423 | 424 | f.html = strings.Replace(f.html, "<tr id=", "<tr data-new id=", 1) | |
| 424 | 425 | } | |
| 425 | 426 | return []fragment{f}, nil | |
| 427 | + | case "summary": | |
| 428 | + | return l.recentActions(ctx) | |
| 426 | 429 | case "run": | |
| 427 | 430 | if strconv.FormatInt(ev.Run.ID, 10) != l.n { | |
| 428 | 431 | return nil, nil | |
| @@ -432,6 +435,25 @@ func (l *liveStream) runEvent(ctx context.Context, ev domain.RunUpdated) ([]frag | |||
| 432 | 435 | return nil, nil | |
| 433 | 436 | } | |
| 434 | 437 | ||
| 438 | + | // recentActions is the "Recent actions" block of the summary rail, read again | |
| 439 | + | // as the principal of the stream: the newest runs change with every event, and | |
| 440 | + | // a viewer who cannot read CI any more gets the block hidden and empty. | |
| 441 | + | func (l *liveStream) recentActions(ctx context.Context) ([]fragment, error) { | |
| 442 | + | name, err := domain.ParseRepoName(l.repo) | |
| 443 | + | if err != nil { | |
| 444 | + | return nil, nil | |
| 445 | + | } | |
| 446 | + | a, err := l.s.listRecentActions(ctx, name, l.s.cfg.Now()) | |
| 447 | + | switch { | |
| 448 | + | case errors.Is(err, domain.ErrNotFound): | |
| 449 | + | a = RecentActions{AllHref: actionsBase(l.repo)} | |
| 450 | + | case err != nil: | |
| 451 | + | return nil, err | |
| 452 | + | } | |
| 453 | + | f, err := renderFragment(ctx, "ci-run", RecentActionsBlock(a)) | |
| 454 | + | return []fragment{f}, err | |
| 455 | + | } | |
| 456 | + | ||
| 435 | 457 | // jobEvent updates the body of the run page and the steps of the job page. | |
| 436 | 458 | func (l *liveStream) jobEvent(ctx context.Context, ev domain.JobUpdated) ([]fragment, error) { | |
| 437 | 459 | if l.s.cfg.Runs == nil { | |
| @@ -407,7 +407,8 @@ func (l *liveStream) notice(ctx context.Context, text, href string) ([]fragment, | |||||
| 407 | } | 407 | } | ||
| 408 | 408 | ||||
| 409 | // runEvent updates the pages of CI that a RunUpdated changes: the row of the | 409 | // runEvent updates the pages of CI that a RunUpdated changes: the row of the | ||
| 410 | - | // list of runs, or the body of the run page shown. Only a state is sent: the | 410 | + | // list of runs, the recent actions of the summary, or the body of the run page |
| 411 | + | // shown. Only a state is sent: the | |||
| 411 | // events carry no log and no text of the workflow. | 412 | // events carry no log and no text of the workflow. | ||
| 412 | func (l *liveStream) runEvent(ctx context.Context, ev domain.RunUpdated) ([]fragment, error) { | 413 | func (l *liveStream) runEvent(ctx context.Context, ev domain.RunUpdated) ([]fragment, error) { | ||
| 413 | if l.s.cfg.Runs == nil { | 414 | if l.s.cfg.Runs == nil { | ||
| @@ -423,6 +424,8 @@ func (l *liveStream) runEvent(ctx context.Context, ev domain.RunUpdated) ([]frag | |||||
| 423 | f.html = strings.Replace(f.html, "<tr id=", "<tr data-new id=", 1) | 424 | f.html = strings.Replace(f.html, "<tr id=", "<tr data-new id=", 1) | ||
| 424 | } | 425 | } | ||
| 425 | return []fragment{f}, nil | 426 | return []fragment{f}, nil | ||
| 427 | + | case "summary": | |||
| 428 | + | return l.recentActions(ctx) | |||
| 426 | case "run": | 429 | case "run": | ||
| 427 | if strconv.FormatInt(ev.Run.ID, 10) != l.n { | 430 | if strconv.FormatInt(ev.Run.ID, 10) != l.n { | ||
| 428 | return nil, nil | 431 | return nil, nil | ||
| @@ -432,6 +435,25 @@ func (l *liveStream) runEvent(ctx context.Context, ev domain.RunUpdated) ([]frag | |||||
| 432 | return nil, nil | 435 | return nil, nil | ||
| 433 | } | 436 | } | ||
| 434 | 437 | ||||
| 438 | + | // recentActions is the "Recent actions" block of the summary rail, read again | |||
| 439 | + | // as the principal of the stream: the newest runs change with every event, and | |||
| 440 | + | // a viewer who cannot read CI any more gets the block hidden and empty. | |||
| 441 | + | func (l *liveStream) recentActions(ctx context.Context) ([]fragment, error) { | |||
| 442 | + | name, err := domain.ParseRepoName(l.repo) | |||
| 443 | + | if err != nil { | |||
| 444 | + | return nil, nil | |||
| 445 | + | } | |||
| 446 | + | a, err := l.s.listRecentActions(ctx, name, l.s.cfg.Now()) | |||
| 447 | + | switch { | |||
| 448 | + | case errors.Is(err, domain.ErrNotFound): | |||
| 449 | + | a = RecentActions{AllHref: actionsBase(l.repo)} | |||
| 450 | + | case err != nil: | |||
| 451 | + | return nil, err | |||
| 452 | + | } | |||
| 453 | + | f, err := renderFragment(ctx, "ci-run", RecentActionsBlock(a)) | |||
| 454 | + | return []fragment{f}, err | |||
| 455 | + | } | |||
| 456 | + | ||||
| 435 | // jobEvent updates the body of the run page and the steps of the job page. | 457 | // jobEvent updates the body of the run page and the steps of the job page. | ||
| 436 | func (l *liveStream) jobEvent(ctx context.Context, ev domain.JobUpdated) ([]fragment, error) { | 458 | func (l *liveStream) jobEvent(ctx context.Context, ev domain.JobUpdated) ([]fragment, error) { | ||
| 437 | if l.s.cfg.Runs == nil { | 459 | if l.s.cfg.Runs == nil { | ||
M internal/adapters/web/pages.templ +3 -0
| @@ -80,6 +80,9 @@ templ summaryRail(p SummaryPage) { | |||
| 80 | 80 | <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent) | |
| 81 | 81 | </section> | |
| 82 | 82 | } | |
| 83 | + | if p.Actions != nil { | |
| 84 | + | @RecentActionsBlock(*p.Actions) | |
| 85 | + | } | |
| 83 | 86 | if len(p.Outline) > 0 && p.Readme != nil { | |
| 84 | 87 | <section aria-labelledby="r-outline"><h2 id="r-outline">On this page</h2>@Outline(p.Outline) | |
| 85 | 88 | </section> | |
| @@ -80,6 +80,9 @@ templ summaryRail(p SummaryPage) { | |||||
| 80 | <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent) | 80 | <section aria-labelledby="r-recent"><h2 id="r-recent">Recent commits <a href={ templ.SafeURL(p.AllHref) }>View all</a></h2>@CommitRows(p.Recent) | ||
| 81 | </section> | 81 | </section> | ||
| 82 | } | 82 | } | ||
| 83 | + | if p.Actions != nil { | |||
| 84 | + | @RecentActionsBlock(*p.Actions) | |||
| 85 | + | } | |||
| 83 | if len(p.Outline) > 0 && p.Readme != nil { | 86 | if len(p.Outline) > 0 && p.Readme != nil { | ||
| 84 | <section aria-labelledby="r-outline"><h2 id="r-outline">On this page</h2>@Outline(p.Outline) | 87 | <section aria-labelledby="r-outline"><h2 id="r-outline">On this page</h2>@Outline(p.Outline) | ||
| 85 | </section> | 88 | </section> | ||
M internal/adapters/web/pages_templ.go +42 -36
| @@ -431,6 +431,12 @@ func summaryRail(p SummaryPage) templ.Component { | |||
| 431 | 431 | return templ_7745c5c3_Err | |
| 432 | 432 | } | |
| 433 | 433 | } | |
| 434 | + | if p.Actions != nil { | |
| 435 | + | templ_7745c5c3_Err = RecentActionsBlock(*p.Actions).Render(ctx, templ_7745c5c3_Buffer) | |
| 436 | + | if templ_7745c5c3_Err != nil { | |
| 437 | + | return templ_7745c5c3_Err | |
| 438 | + | } | |
| 439 | + | } | |
| 434 | 440 | if len(p.Outline) > 0 && p.Readme != nil { | |
| 435 | 441 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>") | |
| 436 | 442 | if templ_7745c5c3_Err != nil { | |
| @@ -503,7 +509,7 @@ func Refs(l Layout, p RefsPage) templ.Component { | |||
| 503 | 509 | var templ_7745c5c3_Var24 string | |
| 504 | 510 | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note) | |
| 505 | 511 | if templ_7745c5c3_Err != nil { | |
| 506 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 110, Col: 27} | |
| 512 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 113, Col: 27} | |
| 507 | 513 | } | |
| 508 | 514 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | |
| 509 | 515 | if templ_7745c5c3_Err != nil { | |
| @@ -562,7 +568,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 562 | 568 | var templ_7745c5c3_Var26 string | |
| 563 | 569 | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(kind) | |
| 564 | 570 | if templ_7745c5c3_Err != nil { | |
| 565 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 118, Col: 32} | |
| 571 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 121, Col: 32} | |
| 566 | 572 | } | |
| 567 | 573 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | |
| 568 | 574 | if templ_7745c5c3_Err != nil { | |
| @@ -575,7 +581,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 575 | 581 | var templ_7745c5c3_Var27 templ.SafeURL | |
| 576 | 582 | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href)) | |
| 577 | 583 | if templ_7745c5c3_Err != nil { | |
| 578 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 120, Col: 34} | |
| 584 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 123, Col: 34} | |
| 579 | 585 | } | |
| 580 | 586 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | |
| 581 | 587 | if templ_7745c5c3_Err != nil { | |
| @@ -588,7 +594,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 588 | 594 | var templ_7745c5c3_Var28 string | |
| 589 | 595 | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | |
| 590 | 596 | if templ_7745c5c3_Err != nil { | |
| 591 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 120, Col: 45} | |
| 597 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 123, Col: 45} | |
| 592 | 598 | } | |
| 593 | 599 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | |
| 594 | 600 | if templ_7745c5c3_Err != nil { | |
| @@ -616,7 +622,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 616 | 622 | var templ_7745c5c3_Var29 templ.SafeURL | |
| 617 | 623 | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref)) | |
| 618 | 624 | if templ_7745c5c3_Err != nil { | |
| 619 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 127, Col: 36} | |
| 625 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 130, Col: 36} | |
| 620 | 626 | } | |
| 621 | 627 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) | |
| 622 | 628 | if templ_7745c5c3_Err != nil { | |
| @@ -629,7 +635,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 629 | 635 | var templ_7745c5c3_Var30 string | |
| 630 | 636 | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short) | |
| 631 | 637 | if templ_7745c5c3_Err != nil { | |
| 632 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 127, Col: 48} | |
| 638 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 130, Col: 48} | |
| 633 | 639 | } | |
| 634 | 640 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | |
| 635 | 641 | if templ_7745c5c3_Err != nil { | |
| @@ -643,7 +649,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 643 | 649 | var templ_7745c5c3_Var31 string | |
| 644 | 650 | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short) | |
| 645 | 651 | if templ_7745c5c3_Err != nil { | |
| 646 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 129, Col: 13} | |
| 652 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 132, Col: 13} | |
| 647 | 653 | } | |
| 648 | 654 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | |
| 649 | 655 | if templ_7745c5c3_Err != nil { | |
| @@ -661,7 +667,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 661 | 667 | var templ_7745c5c3_Var32 string | |
| 662 | 668 | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject) | |
| 663 | 669 | if templ_7745c5c3_Err != nil { | |
| 664 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 133, Col: 31} | |
| 670 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 31} | |
| 665 | 671 | } | |
| 666 | 672 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | |
| 667 | 673 | if templ_7745c5c3_Err != nil { | |
| @@ -679,7 +685,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 679 | 685 | var templ_7745c5c3_Var33 string | |
| 680 | 686 | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt) | |
| 681 | 687 | if templ_7745c5c3_Err != nil { | |
| 682 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 29} | |
| 688 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 29} | |
| 683 | 689 | } | |
| 684 | 690 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) | |
| 685 | 691 | if templ_7745c5c3_Err != nil { | |
| @@ -692,7 +698,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 692 | 698 | var templ_7745c5c3_Var34 string | |
| 693 | 699 | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt) | |
| 694 | 700 | if templ_7745c5c3_Err != nil { | |
| 695 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 48} | |
| 701 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 48} | |
| 696 | 702 | } | |
| 697 | 703 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34) | |
| 698 | 704 | if templ_7745c5c3_Err != nil { | |
| @@ -705,7 +711,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||
| 705 | 711 | var templ_7745c5c3_Var35 string | |
| 706 | 712 | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.When) | |
| 707 | 713 | if templ_7745c5c3_Err != nil { | |
| 708 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 59} | |
| 714 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 59} | |
| 709 | 715 | } | |
| 710 | 716 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | |
| 711 | 717 | if templ_7745c5c3_Err != nil { | |
| @@ -802,7 +808,7 @@ func State(l Layout, p StatePage) templ.Component { | |||
| 802 | 808 | var templ_7745c5c3_Var39 string | |
| 803 | 809 | templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code) | |
| 804 | 810 | if templ_7745c5c3_Err != nil { | |
| 805 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 153, Col: 30} | |
| 811 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 156, Col: 30} | |
| 806 | 812 | } | |
| 807 | 813 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) | |
| 808 | 814 | if templ_7745c5c3_Err != nil { | |
| @@ -815,7 +821,7 @@ func State(l Layout, p StatePage) templ.Component { | |||
| 815 | 821 | var templ_7745c5c3_Var40 string | |
| 816 | 822 | templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | |
| 817 | 823 | if templ_7745c5c3_Err != nil { | |
| 818 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 154, Col: 33} | |
| 824 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 157, Col: 33} | |
| 819 | 825 | } | |
| 820 | 826 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) | |
| 821 | 827 | if templ_7745c5c3_Err != nil { | |
| @@ -833,7 +839,7 @@ func State(l Layout, p StatePage) templ.Component { | |||
| 833 | 839 | var templ_7745c5c3_Var41 string | |
| 834 | 840 | templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(line) | |
| 835 | 841 | if templ_7745c5c3_Err != nil { | |
| 836 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 156, Col: 25} | |
| 842 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 25} | |
| 837 | 843 | } | |
| 838 | 844 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) | |
| 839 | 845 | if templ_7745c5c3_Err != nil { | |
| @@ -862,7 +868,7 @@ func State(l Layout, p StatePage) templ.Component { | |||
| 862 | 868 | var templ_7745c5c3_Var42 templ.SafeURL | |
| 863 | 869 | templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href)) | |
| 864 | 870 | if templ_7745c5c3_Err != nil { | |
| 865 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 36} | |
| 871 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 36} | |
| 866 | 872 | } | |
| 867 | 873 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) | |
| 868 | 874 | if templ_7745c5c3_Err != nil { | |
| @@ -875,7 +881,7 @@ func State(l Layout, p StatePage) templ.Component { | |||
| 875 | 881 | var templ_7745c5c3_Var43 string | |
| 876 | 882 | templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label) | |
| 877 | 883 | if templ_7745c5c3_Err != nil { | |
| 878 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 48} | |
| 884 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 48} | |
| 879 | 885 | } | |
| 880 | 886 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) | |
| 881 | 887 | if templ_7745c5c3_Err != nil { | |
| @@ -942,7 +948,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||
| 942 | 948 | var templ_7745c5c3_Var46 string | |
| 943 | 949 | templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | |
| 944 | 950 | if templ_7745c5c3_Err != nil { | |
| 945 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 175, Col: 33} | |
| 951 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 178, Col: 33} | |
| 946 | 952 | } | |
| 947 | 953 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) | |
| 948 | 954 | if templ_7745c5c3_Err != nil { | |
| @@ -960,7 +966,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||
| 960 | 966 | var templ_7745c5c3_Var47 string | |
| 961 | 967 | templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(line) | |
| 962 | 968 | if templ_7745c5c3_Err != nil { | |
| 963 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 177, Col: 25} | |
| 969 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 180, Col: 25} | |
| 964 | 970 | } | |
| 965 | 971 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) | |
| 966 | 972 | if templ_7745c5c3_Err != nil { | |
| @@ -978,7 +984,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||
| 978 | 984 | var templ_7745c5c3_Var48 templ.SafeURL | |
| 979 | 985 | templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action)) | |
| 980 | 986 | if templ_7745c5c3_Err != nil { | |
| 981 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 179, Col: 73} | |
| 987 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 182, Col: 73} | |
| 982 | 988 | } | |
| 983 | 989 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) | |
| 984 | 990 | if templ_7745c5c3_Err != nil { | |
| @@ -991,7 +997,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||
| 991 | 997 | var templ_7745c5c3_Var49 string | |
| 992 | 998 | templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button) | |
| 993 | 999 | if templ_7745c5c3_Err != nil { | |
| 994 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 180, Col: 56} | |
| 1000 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 183, Col: 56} | |
| 995 | 1001 | } | |
| 996 | 1002 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) | |
| 997 | 1003 | if templ_7745c5c3_Err != nil { | |
| @@ -1053,7 +1059,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||
| 1053 | 1059 | var templ_7745c5c3_Var52 string | |
| 1054 | 1060 | templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | |
| 1055 | 1061 | if templ_7745c5c3_Err != nil { | |
| 1056 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 192, Col: 33} | |
| 1062 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 33} | |
| 1057 | 1063 | } | |
| 1058 | 1064 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) | |
| 1059 | 1065 | if templ_7745c5c3_Err != nil { | |
| @@ -1066,7 +1072,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||
| 1066 | 1072 | var templ_7745c5c3_Var53 string | |
| 1067 | 1073 | templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin) | |
| 1068 | 1074 | if templ_7745c5c3_Err != nil { | |
| 1069 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 87} | |
| 1075 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 197, Col: 87} | |
| 1070 | 1076 | } | |
| 1071 | 1077 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var53) | |
| 1072 | 1078 | if templ_7745c5c3_Err != nil { | |
| @@ -1079,7 +1085,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||
| 1079 | 1085 | var templ_7745c5c3_Var54 string | |
| 1080 | 1086 | templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish) | |
| 1081 | 1087 | if templ_7745c5c3_Err != nil { | |
| 1082 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 112} | |
| 1088 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 197, Col: 112} | |
| 1083 | 1089 | } | |
| 1084 | 1090 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54) | |
| 1085 | 1091 | if templ_7745c5c3_Err != nil { | |
| @@ -1092,7 +1098,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||
| 1092 | 1098 | var templ_7745c5c3_Var55 string | |
| 1093 | 1099 | templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button) | |
| 1094 | 1100 | if templ_7745c5c3_Err != nil { | |
| 1095 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 125} | |
| 1101 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 197, Col: 125} | |
| 1096 | 1102 | } | |
| 1097 | 1103 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) | |
| 1098 | 1104 | if templ_7745c5c3_Err != nil { | |
| @@ -1110,7 +1116,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||
| 1110 | 1116 | var templ_7745c5c3_Var56 string | |
| 1111 | 1117 | templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(line) | |
| 1112 | 1118 | if templ_7745c5c3_Err != nil { | |
| 1113 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 198, Col: 25} | |
| 1119 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 201, Col: 25} | |
| 1114 | 1120 | } | |
| 1115 | 1121 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) | |
| 1116 | 1122 | if templ_7745c5c3_Err != nil { | |
| @@ -1176,7 +1182,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1176 | 1182 | var templ_7745c5c3_Var59 string | |
| 1177 | 1183 | templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows))) | |
| 1178 | 1184 | if templ_7745c5c3_Err != nil { | |
| 1179 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 208, Col: 81} | |
| 1185 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 211, Col: 81} | |
| 1180 | 1186 | } | |
| 1181 | 1187 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59)) | |
| 1182 | 1188 | if templ_7745c5c3_Err != nil { | |
| @@ -1199,7 +1205,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1199 | 1205 | var templ_7745c5c3_Var60 string | |
| 1200 | 1206 | templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | |
| 1201 | 1207 | if templ_7745c5c3_Err != nil { | |
| 1202 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 226, Col: 34} | |
| 1208 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 34} | |
| 1203 | 1209 | } | |
| 1204 | 1210 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60)) | |
| 1205 | 1211 | if templ_7745c5c3_Err != nil { | |
| @@ -1217,7 +1223,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1217 | 1223 | var templ_7745c5c3_Var61 string | |
| 1218 | 1224 | templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt) | |
| 1219 | 1225 | if templ_7745c5c3_Err != nil { | |
| 1220 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 230, Col: 32} | |
| 1226 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 32} | |
| 1221 | 1227 | } | |
| 1222 | 1228 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61) | |
| 1223 | 1229 | if templ_7745c5c3_Err != nil { | |
| @@ -1235,7 +1241,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1235 | 1241 | var templ_7745c5c3_Var62 string | |
| 1236 | 1242 | templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created) | |
| 1237 | 1243 | if templ_7745c5c3_Err != nil { | |
| 1238 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 232, Col: 20} | |
| 1244 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 20} | |
| 1239 | 1245 | } | |
| 1240 | 1246 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) | |
| 1241 | 1247 | if templ_7745c5c3_Err != nil { | |
| @@ -1253,7 +1259,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1253 | 1259 | var templ_7745c5c3_Var63 string | |
| 1254 | 1260 | templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt) | |
| 1255 | 1261 | if templ_7745c5c3_Err != nil { | |
| 1256 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 237, Col: 33} | |
| 1262 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 240, Col: 33} | |
| 1257 | 1263 | } | |
| 1258 | 1264 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63) | |
| 1259 | 1265 | if templ_7745c5c3_Err != nil { | |
| @@ -1271,7 +1277,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1271 | 1277 | var templ_7745c5c3_Var64 string | |
| 1272 | 1278 | templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed) | |
| 1273 | 1279 | if templ_7745c5c3_Err != nil { | |
| 1274 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 239, Col: 21} | |
| 1280 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 242, Col: 21} | |
| 1275 | 1281 | } | |
| 1276 | 1282 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64)) | |
| 1277 | 1283 | if templ_7745c5c3_Err != nil { | |
| @@ -1299,7 +1305,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1299 | 1305 | var templ_7745c5c3_Var65 templ.SafeURL | |
| 1300 | 1306 | templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove)) | |
| 1301 | 1307 | if templ_7745c5c3_Err != nil { | |
| 1302 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 249, Col: 60} | |
| 1308 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 252, Col: 60} | |
| 1303 | 1309 | } | |
| 1304 | 1310 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) | |
| 1305 | 1311 | if templ_7745c5c3_Err != nil { | |
| @@ -1312,7 +1318,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1312 | 1318 | var templ_7745c5c3_Var66 string | |
| 1313 | 1319 | templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name) | |
| 1314 | 1320 | if templ_7745c5c3_Err != nil { | |
| 1315 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 250, Col: 89} | |
| 1321 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 253, Col: 89} | |
| 1316 | 1322 | } | |
| 1317 | 1323 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66) | |
| 1318 | 1324 | if templ_7745c5c3_Err != nil { | |
| @@ -1345,7 +1351,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1345 | 1351 | var templ_7745c5c3_Var67 string | |
| 1346 | 1352 | templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max)) | |
| 1347 | 1353 | if templ_7745c5c3_Err != nil { | |
| 1348 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 261, Col: 31} | |
| 1354 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 264, Col: 31} | |
| 1349 | 1355 | } | |
| 1350 | 1356 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) | |
| 1351 | 1357 | if templ_7745c5c3_Err != nil { | |
| @@ -1363,7 +1369,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1363 | 1369 | var templ_7745c5c3_Var68 string | |
| 1364 | 1370 | templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin) | |
| 1365 | 1371 | if templ_7745c5c3_Err != nil { | |
| 1366 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 266, Col: 90} | |
| 1372 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 269, Col: 90} | |
| 1367 | 1373 | } | |
| 1368 | 1374 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var68) | |
| 1369 | 1375 | if templ_7745c5c3_Err != nil { | |
| @@ -1376,7 +1382,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||
| 1376 | 1382 | var templ_7745c5c3_Var69 string | |
| 1377 | 1383 | templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish) | |
| 1378 | 1384 | if templ_7745c5c3_Err != nil { | |
| 1379 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 266, Col: 115} | |
| 1385 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 269, Col: 115} | |
| 1380 | 1386 | } | |
| 1381 | 1387 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69) | |
| 1382 | 1388 | if templ_7745c5c3_Err != nil { | |
| @@ -431,6 +431,12 @@ func summaryRail(p SummaryPage) templ.Component { | |||||
| 431 | return templ_7745c5c3_Err | 431 | return templ_7745c5c3_Err | ||
| 432 | } | 432 | } | ||
| 433 | } | 433 | } | ||
| 434 | + | if p.Actions != nil { | |||
| 435 | + | templ_7745c5c3_Err = RecentActionsBlock(*p.Actions).Render(ctx, templ_7745c5c3_Buffer) | |||
| 436 | + | if templ_7745c5c3_Err != nil { | |||
| 437 | + | return templ_7745c5c3_Err | |||
| 438 | + | } | |||
| 439 | + | } | |||
| 434 | if len(p.Outline) > 0 && p.Readme != nil { | 440 | if len(p.Outline) > 0 && p.Readme != nil { | ||
| 435 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>") | 441 | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<section aria-labelledby=\"r-outline\"><h2 id=\"r-outline\">On this page</h2>") | ||
| 436 | if templ_7745c5c3_Err != nil { | 442 | if templ_7745c5c3_Err != nil { | ||
| @@ -503,7 +509,7 @@ func Refs(l Layout, p RefsPage) templ.Component { | |||||
| 503 | var templ_7745c5c3_Var24 string | 509 | var templ_7745c5c3_Var24 string | ||
| 504 | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note) | 510 | templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note) | ||
| 505 | if templ_7745c5c3_Err != nil { | 511 | if templ_7745c5c3_Err != nil { | ||
| 506 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 110, Col: 27} | 512 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 113, Col: 27} |
| 507 | } | 513 | } | ||
| 508 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | 514 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | ||
| 509 | if templ_7745c5c3_Err != nil { | 515 | if templ_7745c5c3_Err != nil { | ||
| @@ -562,7 +568,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 562 | var templ_7745c5c3_Var26 string | 568 | var templ_7745c5c3_Var26 string | ||
| 563 | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(kind) | 569 | templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(kind) | ||
| 564 | if templ_7745c5c3_Err != nil { | 570 | if templ_7745c5c3_Err != nil { | ||
| 565 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 118, Col: 32} | 571 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 121, Col: 32} |
| 566 | } | 572 | } | ||
| 567 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | 573 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | ||
| 568 | if templ_7745c5c3_Err != nil { | 574 | if templ_7745c5c3_Err != nil { | ||
| @@ -575,7 +581,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 575 | var templ_7745c5c3_Var27 templ.SafeURL | 581 | var templ_7745c5c3_Var27 templ.SafeURL | ||
| 576 | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href)) | 582 | templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href)) | ||
| 577 | if templ_7745c5c3_Err != nil { | 583 | if templ_7745c5c3_Err != nil { | ||
| 578 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 120, Col: 34} | 584 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 123, Col: 34} |
| 579 | } | 585 | } | ||
| 580 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | 586 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) | ||
| 581 | if templ_7745c5c3_Err != nil { | 587 | if templ_7745c5c3_Err != nil { | ||
| @@ -588,7 +594,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 588 | var templ_7745c5c3_Var28 string | 594 | var templ_7745c5c3_Var28 string | ||
| 589 | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | 595 | templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | ||
| 590 | if templ_7745c5c3_Err != nil { | 596 | if templ_7745c5c3_Err != nil { | ||
| 591 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 120, Col: 45} | 597 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 123, Col: 45} |
| 592 | } | 598 | } | ||
| 593 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | 599 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) | ||
| 594 | if templ_7745c5c3_Err != nil { | 600 | if templ_7745c5c3_Err != nil { | ||
| @@ -616,7 +622,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 616 | var templ_7745c5c3_Var29 templ.SafeURL | 622 | var templ_7745c5c3_Var29 templ.SafeURL | ||
| 617 | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref)) | 623 | templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.CHref)) | ||
| 618 | if templ_7745c5c3_Err != nil { | 624 | if templ_7745c5c3_Err != nil { | ||
| 619 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 127, Col: 36} | 625 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 130, Col: 36} |
| 620 | } | 626 | } | ||
| 621 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) | 627 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) | ||
| 622 | if templ_7745c5c3_Err != nil { | 628 | if templ_7745c5c3_Err != nil { | ||
| @@ -629,7 +635,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 629 | var templ_7745c5c3_Var30 string | 635 | var templ_7745c5c3_Var30 string | ||
| 630 | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short) | 636 | templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short) | ||
| 631 | if templ_7745c5c3_Err != nil { | 637 | if templ_7745c5c3_Err != nil { | ||
| 632 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 127, Col: 48} | 638 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 130, Col: 48} |
| 633 | } | 639 | } | ||
| 634 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | 640 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) | ||
| 635 | if templ_7745c5c3_Err != nil { | 641 | if templ_7745c5c3_Err != nil { | ||
| @@ -643,7 +649,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 643 | var templ_7745c5c3_Var31 string | 649 | var templ_7745c5c3_Var31 string | ||
| 644 | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short) | 650 | templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Short) | ||
| 645 | if templ_7745c5c3_Err != nil { | 651 | if templ_7745c5c3_Err != nil { | ||
| 646 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 129, Col: 13} | 652 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 132, Col: 13} |
| 647 | } | 653 | } | ||
| 648 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | 654 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) | ||
| 649 | if templ_7745c5c3_Err != nil { | 655 | if templ_7745c5c3_Err != nil { | ||
| @@ -661,7 +667,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 661 | var templ_7745c5c3_Var32 string | 667 | var templ_7745c5c3_Var32 string | ||
| 662 | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject) | 668 | templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(r.Subject) | ||
| 663 | if templ_7745c5c3_Err != nil { | 669 | if templ_7745c5c3_Err != nil { | ||
| 664 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 133, Col: 31} | 670 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 31} |
| 665 | } | 671 | } | ||
| 666 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | 672 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) | ||
| 667 | if templ_7745c5c3_Err != nil { | 673 | if templ_7745c5c3_Err != nil { | ||
| @@ -679,7 +685,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 679 | var templ_7745c5c3_Var33 string | 685 | var templ_7745c5c3_Var33 string | ||
| 680 | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt) | 686 | templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt) | ||
| 681 | if templ_7745c5c3_Err != nil { | 687 | if templ_7745c5c3_Err != nil { | ||
| 682 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 29} | 688 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 29} |
| 683 | } | 689 | } | ||
| 684 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) | 690 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var33) | ||
| 685 | if templ_7745c5c3_Err != nil { | 691 | if templ_7745c5c3_Err != nil { | ||
| @@ -692,7 +698,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 692 | var templ_7745c5c3_Var34 string | 698 | var templ_7745c5c3_Var34 string | ||
| 693 | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt) | 699 | templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.WhenAt) | ||
| 694 | if templ_7745c5c3_Err != nil { | 700 | if templ_7745c5c3_Err != nil { | ||
| 695 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 48} | 701 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 48} |
| 696 | } | 702 | } | ||
| 697 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34) | 703 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34) | ||
| 698 | if templ_7745c5c3_Err != nil { | 704 | if templ_7745c5c3_Err != nil { | ||
| @@ -705,7 +711,7 @@ func refRow(kind string, r RefRow) templ.Component { | |||||
| 705 | var templ_7745c5c3_Var35 string | 711 | var templ_7745c5c3_Var35 string | ||
| 706 | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.When) | 712 | templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.When) | ||
| 707 | if templ_7745c5c3_Err != nil { | 713 | if templ_7745c5c3_Err != nil { | ||
| 708 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 136, Col: 59} | 714 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 139, Col: 59} |
| 709 | } | 715 | } | ||
| 710 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | 716 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) | ||
| 711 | if templ_7745c5c3_Err != nil { | 717 | if templ_7745c5c3_Err != nil { | ||
| @@ -802,7 +808,7 @@ func State(l Layout, p StatePage) templ.Component { | |||||
| 802 | var templ_7745c5c3_Var39 string | 808 | var templ_7745c5c3_Var39 string | ||
| 803 | templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code) | 809 | templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(p.Code) | ||
| 804 | if templ_7745c5c3_Err != nil { | 810 | if templ_7745c5c3_Err != nil { | ||
| 805 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 153, Col: 30} | 811 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 156, Col: 30} |
| 806 | } | 812 | } | ||
| 807 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) | 813 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) | ||
| 808 | if templ_7745c5c3_Err != nil { | 814 | if templ_7745c5c3_Err != nil { | ||
| @@ -815,7 +821,7 @@ func State(l Layout, p StatePage) templ.Component { | |||||
| 815 | var templ_7745c5c3_Var40 string | 821 | var templ_7745c5c3_Var40 string | ||
| 816 | templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | 822 | templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | ||
| 817 | if templ_7745c5c3_Err != nil { | 823 | if templ_7745c5c3_Err != nil { | ||
| 818 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 154, Col: 33} | 824 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 157, Col: 33} |
| 819 | } | 825 | } | ||
| 820 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) | 826 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) | ||
| 821 | if templ_7745c5c3_Err != nil { | 827 | if templ_7745c5c3_Err != nil { | ||
| @@ -833,7 +839,7 @@ func State(l Layout, p StatePage) templ.Component { | |||||
| 833 | var templ_7745c5c3_Var41 string | 839 | var templ_7745c5c3_Var41 string | ||
| 834 | templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(line) | 840 | templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(line) | ||
| 835 | if templ_7745c5c3_Err != nil { | 841 | if templ_7745c5c3_Err != nil { | ||
| 836 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 156, Col: 25} | 842 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 159, Col: 25} |
| 837 | } | 843 | } | ||
| 838 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) | 844 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) | ||
| 839 | if templ_7745c5c3_Err != nil { | 845 | if templ_7745c5c3_Err != nil { | ||
| @@ -862,7 +868,7 @@ func State(l Layout, p StatePage) templ.Component { | |||||
| 862 | var templ_7745c5c3_Var42 templ.SafeURL | 868 | var templ_7745c5c3_Var42 templ.SafeURL | ||
| 863 | templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href)) | 869 | templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(b.Href)) | ||
| 864 | if templ_7745c5c3_Err != nil { | 870 | if templ_7745c5c3_Err != nil { | ||
| 865 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 36} | 871 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 36} |
| 866 | } | 872 | } | ||
| 867 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) | 873 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) | ||
| 868 | if templ_7745c5c3_Err != nil { | 874 | if templ_7745c5c3_Err != nil { | ||
| @@ -875,7 +881,7 @@ func State(l Layout, p StatePage) templ.Component { | |||||
| 875 | var templ_7745c5c3_Var43 string | 881 | var templ_7745c5c3_Var43 string | ||
| 876 | templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label) | 882 | templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(b.Label) | ||
| 877 | if templ_7745c5c3_Err != nil { | 883 | if templ_7745c5c3_Err != nil { | ||
| 878 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 163, Col: 48} | 884 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 166, Col: 48} |
| 879 | } | 885 | } | ||
| 880 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) | 886 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) | ||
| 881 | if templ_7745c5c3_Err != nil { | 887 | if templ_7745c5c3_Err != nil { | ||
| @@ -942,7 +948,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||||
| 942 | var templ_7745c5c3_Var46 string | 948 | var templ_7745c5c3_Var46 string | ||
| 943 | templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | 949 | templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | ||
| 944 | if templ_7745c5c3_Err != nil { | 950 | if templ_7745c5c3_Err != nil { | ||
| 945 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 175, Col: 33} | 951 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 178, Col: 33} |
| 946 | } | 952 | } | ||
| 947 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) | 953 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) | ||
| 948 | if templ_7745c5c3_Err != nil { | 954 | if templ_7745c5c3_Err != nil { | ||
| @@ -960,7 +966,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||||
| 960 | var templ_7745c5c3_Var47 string | 966 | var templ_7745c5c3_Var47 string | ||
| 961 | templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(line) | 967 | templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(line) | ||
| 962 | if templ_7745c5c3_Err != nil { | 968 | if templ_7745c5c3_Err != nil { | ||
| 963 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 177, Col: 25} | 969 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 180, Col: 25} |
| 964 | } | 970 | } | ||
| 965 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) | 971 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) | ||
| 966 | if templ_7745c5c3_Err != nil { | 972 | if templ_7745c5c3_Err != nil { | ||
| @@ -978,7 +984,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||||
| 978 | var templ_7745c5c3_Var48 templ.SafeURL | 984 | var templ_7745c5c3_Var48 templ.SafeURL | ||
| 979 | templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action)) | 985 | templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Action)) | ||
| 980 | if templ_7745c5c3_Err != nil { | 986 | if templ_7745c5c3_Err != nil { | ||
| 981 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 179, Col: 73} | 987 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 182, Col: 73} |
| 982 | } | 988 | } | ||
| 983 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) | 989 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) | ||
| 984 | if templ_7745c5c3_Err != nil { | 990 | if templ_7745c5c3_Err != nil { | ||
| @@ -991,7 +997,7 @@ func Login(l Layout, p LoginPage) templ.Component { | |||||
| 991 | var templ_7745c5c3_Var49 string | 997 | var templ_7745c5c3_Var49 string | ||
| 992 | templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button) | 998 | templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button) | ||
| 993 | if templ_7745c5c3_Err != nil { | 999 | if templ_7745c5c3_Err != nil { | ||
| 994 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 180, Col: 56} | 1000 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 183, Col: 56} |
| 995 | } | 1001 | } | ||
| 996 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) | 1002 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) | ||
| 997 | if templ_7745c5c3_Err != nil { | 1003 | if templ_7745c5c3_Err != nil { | ||
| @@ -1053,7 +1059,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||||
| 1053 | var templ_7745c5c3_Var52 string | 1059 | var templ_7745c5c3_Var52 string | ||
| 1054 | templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | 1060 | templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(p.Title) | ||
| 1055 | if templ_7745c5c3_Err != nil { | 1061 | if templ_7745c5c3_Err != nil { | ||
| 1056 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 192, Col: 33} | 1062 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 195, Col: 33} |
| 1057 | } | 1063 | } | ||
| 1058 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) | 1064 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52)) | ||
| 1059 | if templ_7745c5c3_Err != nil { | 1065 | if templ_7745c5c3_Err != nil { | ||
| @@ -1066,7 +1072,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||||
| 1066 | var templ_7745c5c3_Var53 string | 1072 | var templ_7745c5c3_Var53 string | ||
| 1067 | templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin) | 1073 | templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin) | ||
| 1068 | if templ_7745c5c3_Err != nil { | 1074 | if templ_7745c5c3_Err != nil { | ||
| 1069 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 87} | 1075 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 197, Col: 87} |
| 1070 | } | 1076 | } | ||
| 1071 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var53) | 1077 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var53) | ||
| 1072 | if templ_7745c5c3_Err != nil { | 1078 | if templ_7745c5c3_Err != nil { | ||
| @@ -1079,7 +1085,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||||
| 1079 | var templ_7745c5c3_Var54 string | 1085 | var templ_7745c5c3_Var54 string | ||
| 1080 | templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish) | 1086 | templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish) | ||
| 1081 | if templ_7745c5c3_Err != nil { | 1087 | if templ_7745c5c3_Err != nil { | ||
| 1082 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 112} | 1088 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 197, Col: 112} |
| 1083 | } | 1089 | } | ||
| 1084 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54) | 1090 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var54) | ||
| 1085 | if templ_7745c5c3_Err != nil { | 1091 | if templ_7745c5c3_Err != nil { | ||
| @@ -1092,7 +1098,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||||
| 1092 | var templ_7745c5c3_Var55 string | 1098 | var templ_7745c5c3_Var55 string | ||
| 1093 | templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button) | 1099 | templ_7745c5c3_Var55, templ_7745c5c3_Err = templ.JoinStringErrs(p.Button) | ||
| 1094 | if templ_7745c5c3_Err != nil { | 1100 | if templ_7745c5c3_Err != nil { | ||
| 1095 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 194, Col: 125} | 1101 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 197, Col: 125} |
| 1096 | } | 1102 | } | ||
| 1097 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) | 1103 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var55)) | ||
| 1098 | if templ_7745c5c3_Err != nil { | 1104 | if templ_7745c5c3_Err != nil { | ||
| @@ -1110,7 +1116,7 @@ func PasskeyLogin(l Layout, p PasskeyLoginPage) templ.Component { | |||||
| 1110 | var templ_7745c5c3_Var56 string | 1116 | var templ_7745c5c3_Var56 string | ||
| 1111 | templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(line) | 1117 | templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinStringErrs(line) | ||
| 1112 | if templ_7745c5c3_Err != nil { | 1118 | if templ_7745c5c3_Err != nil { | ||
| 1113 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 198, Col: 25} | 1119 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 201, Col: 25} |
| 1114 | } | 1120 | } | ||
| 1115 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) | 1121 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56)) | ||
| 1116 | if templ_7745c5c3_Err != nil { | 1122 | if templ_7745c5c3_Err != nil { | ||
| @@ -1176,7 +1182,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1176 | var templ_7745c5c3_Var59 string | 1182 | var templ_7745c5c3_Var59 string | ||
| 1177 | templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows))) | 1183 | templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Rows))) | ||
| 1178 | if templ_7745c5c3_Err != nil { | 1184 | if templ_7745c5c3_Err != nil { | ||
| 1179 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 208, Col: 81} | 1185 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 211, Col: 81} |
| 1180 | } | 1186 | } | ||
| 1181 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59)) | 1187 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59)) | ||
| 1182 | if templ_7745c5c3_Err != nil { | 1188 | if templ_7745c5c3_Err != nil { | ||
| @@ -1199,7 +1205,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1199 | var templ_7745c5c3_Var60 string | 1205 | var templ_7745c5c3_Var60 string | ||
| 1200 | templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | 1206 | templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name) | ||
| 1201 | if templ_7745c5c3_Err != nil { | 1207 | if templ_7745c5c3_Err != nil { | ||
| 1202 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 226, Col: 34} | 1208 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 229, Col: 34} |
| 1203 | } | 1209 | } | ||
| 1204 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60)) | 1210 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60)) | ||
| 1205 | if templ_7745c5c3_Err != nil { | 1211 | if templ_7745c5c3_Err != nil { | ||
| @@ -1217,7 +1223,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1217 | var templ_7745c5c3_Var61 string | 1223 | var templ_7745c5c3_Var61 string | ||
| 1218 | templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt) | 1224 | templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.CreatedAt) | ||
| 1219 | if templ_7745c5c3_Err != nil { | 1225 | if templ_7745c5c3_Err != nil { | ||
| 1220 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 230, Col: 32} | 1226 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 233, Col: 32} |
| 1221 | } | 1227 | } | ||
| 1222 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61) | 1228 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var61) | ||
| 1223 | if templ_7745c5c3_Err != nil { | 1229 | if templ_7745c5c3_Err != nil { | ||
| @@ -1235,7 +1241,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1235 | var templ_7745c5c3_Var62 string | 1241 | var templ_7745c5c3_Var62 string | ||
| 1236 | templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created) | 1242 | templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(r.Created) | ||
| 1237 | if templ_7745c5c3_Err != nil { | 1243 | if templ_7745c5c3_Err != nil { | ||
| 1238 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 232, Col: 20} | 1244 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 235, Col: 20} |
| 1239 | } | 1245 | } | ||
| 1240 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) | 1246 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) | ||
| 1241 | if templ_7745c5c3_Err != nil { | 1247 | if templ_7745c5c3_Err != nil { | ||
| @@ -1253,7 +1259,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1253 | var templ_7745c5c3_Var63 string | 1259 | var templ_7745c5c3_Var63 string | ||
| 1254 | templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt) | 1260 | templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(r.LastUsedAt) | ||
| 1255 | if templ_7745c5c3_Err != nil { | 1261 | if templ_7745c5c3_Err != nil { | ||
| 1256 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 237, Col: 33} | 1262 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 240, Col: 33} |
| 1257 | } | 1263 | } | ||
| 1258 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63) | 1264 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63) | ||
| 1259 | if templ_7745c5c3_Err != nil { | 1265 | if templ_7745c5c3_Err != nil { | ||
| @@ -1271,7 +1277,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1271 | var templ_7745c5c3_Var64 string | 1277 | var templ_7745c5c3_Var64 string | ||
| 1272 | templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed) | 1278 | templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(r.LastUsed) | ||
| 1273 | if templ_7745c5c3_Err != nil { | 1279 | if templ_7745c5c3_Err != nil { | ||
| 1274 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 239, Col: 21} | 1280 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 242, Col: 21} |
| 1275 | } | 1281 | } | ||
| 1276 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64)) | 1282 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64)) | ||
| 1277 | if templ_7745c5c3_Err != nil { | 1283 | if templ_7745c5c3_Err != nil { | ||
| @@ -1299,7 +1305,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1299 | var templ_7745c5c3_Var65 templ.SafeURL | 1305 | var templ_7745c5c3_Var65 templ.SafeURL | ||
| 1300 | templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove)) | 1306 | templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Remove)) | ||
| 1301 | if templ_7745c5c3_Err != nil { | 1307 | if templ_7745c5c3_Err != nil { | ||
| 1302 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 249, Col: 60} | 1308 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 252, Col: 60} |
| 1303 | } | 1309 | } | ||
| 1304 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) | 1310 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) | ||
| 1305 | if templ_7745c5c3_Err != nil { | 1311 | if templ_7745c5c3_Err != nil { | ||
| @@ -1312,7 +1318,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1312 | var templ_7745c5c3_Var66 string | 1318 | var templ_7745c5c3_Var66 string | ||
| 1313 | templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name) | 1319 | templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.ResolveAttributeValue("Remove passkey " + r.Name) | ||
| 1314 | if templ_7745c5c3_Err != nil { | 1320 | if templ_7745c5c3_Err != nil { | ||
| 1315 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 250, Col: 89} | 1321 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 253, Col: 89} |
| 1316 | } | 1322 | } | ||
| 1317 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66) | 1323 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var66) | ||
| 1318 | if templ_7745c5c3_Err != nil { | 1324 | if templ_7745c5c3_Err != nil { | ||
| @@ -1345,7 +1351,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1345 | var templ_7745c5c3_Var67 string | 1351 | var templ_7745c5c3_Var67 string | ||
| 1346 | templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max)) | 1352 | templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Max)) | ||
| 1347 | if templ_7745c5c3_Err != nil { | 1353 | if templ_7745c5c3_Err != nil { | ||
| 1348 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 261, Col: 31} | 1354 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 264, Col: 31} |
| 1349 | } | 1355 | } | ||
| 1350 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) | 1356 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) | ||
| 1351 | if templ_7745c5c3_Err != nil { | 1357 | if templ_7745c5c3_Err != nil { | ||
| @@ -1363,7 +1369,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1363 | var templ_7745c5c3_Var68 string | 1369 | var templ_7745c5c3_Var68 string | ||
| 1364 | templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin) | 1370 | templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Begin) | ||
| 1365 | if templ_7745c5c3_Err != nil { | 1371 | if templ_7745c5c3_Err != nil { | ||
| 1366 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 266, Col: 90} | 1372 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 269, Col: 90} |
| 1367 | } | 1373 | } | ||
| 1368 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var68) | 1374 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var68) | ||
| 1369 | if templ_7745c5c3_Err != nil { | 1375 | if templ_7745c5c3_Err != nil { | ||
| @@ -1376,7 +1382,7 @@ func Passkeys(l Layout, p PasskeysPage) templ.Component { | |||||
| 1376 | var templ_7745c5c3_Var69 string | 1382 | var templ_7745c5c3_Var69 string | ||
| 1377 | templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish) | 1383 | templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Finish) | ||
| 1378 | if templ_7745c5c3_Err != nil { | 1384 | if templ_7745c5c3_Err != nil { | ||
| 1379 | - | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 266, Col: 115} | 1385 | + | return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/pages.templ`, Line: 269, Col: 115} |
| 1380 | } | 1386 | } | ||
| 1381 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69) | 1387 | _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var69) | ||
| 1382 | if templ_7745c5c3_Err != nil { | 1388 | if templ_7745c5c3_Err != nil { | ||
M internal/adapters/web/static/css/65-ci.css +19 -1
| @@ -15,9 +15,27 @@ | |||
| 15 | 15 | @media (prefers-reduced-motion: reduce) { .ck-running { animation: none; border-top-color: var(--teal); } } | |
| 16 | 16 | ||
| 17 | 17 | .ck-sum { display: inline-flex; align-items: center; gap: var(--s1); margin-left: var(--s2); font-size: var(--fs-xs); white-space: nowrap; } | |
| 18 | + | /* The lists are table-layout: fixed, where a percentage is only a sliver: | |
| 19 | + | the check column holds the mark, its count and the padding of the cells; | |
| 20 | + | the refs list has the mark next to the sha. */ | |
| 18 | 21 | .c-ck { width: 1%; white-space: nowrap; } | |
| 22 | + | .list .c-ck { width: 92px; } | |
| 23 | + | .rail .list.commits .c-ck { width: 76px; } | |
| 24 | + | .list.refs .c-sha { width: 150px; } | |
| 19 | 25 | ||
| 20 | - | .runs .c-st { width: 1%; } | |
| 26 | + | /* the table is fixed: the status column is the icon, its margin and the | |
| 27 | + | padding of the cells (a percentage would leave a sliver) */ | |
| 28 | + | .runs .c-st { width: calc(14px + var(--s1) + 2 * var(--s3)); } | |
| 29 | + | /* the recent runs of the summary rail */ | |
| 30 | + | .rruns { | |
| 31 | + | display: grid; padding: 0; list-style: none; font-size: var(--fs-s); | |
| 32 | + | & li { display: flex; align-items: center; gap: var(--s1); min-width: 0; padding: 2px 0; } | |
| 33 | + | & .ck { margin-right: var(--s1); } | |
| 34 | + | & .rr-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-hi); text-decoration: none; } | |
| 35 | + | & .rr-name:hover { text-decoration: underline; } | |
| 36 | + | & .rr-ref { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-left: var(--s1); font-family: var(--mono); color: var(--ink-dim); text-decoration: none; } | |
| 37 | + | & time { flex: none; margin-left: auto; padding-left: var(--s2); white-space: nowrap; } | |
| 38 | + | } | |
| 21 | 39 | .runs .c-dur, .runs .c-who { white-space: nowrap; } | |
| 22 | 40 | .runs tr[data-new] { background: var(--bg-raised); } | |
| 23 | 41 | ||
| @@ -15,9 +15,27 @@ | |||||
| 15 | @media (prefers-reduced-motion: reduce) { .ck-running { animation: none; border-top-color: var(--teal); } } | 15 | @media (prefers-reduced-motion: reduce) { .ck-running { animation: none; border-top-color: var(--teal); } } | ||
| 16 | 16 | ||||
| 17 | .ck-sum { display: inline-flex; align-items: center; gap: var(--s1); margin-left: var(--s2); font-size: var(--fs-xs); white-space: nowrap; } | 17 | .ck-sum { display: inline-flex; align-items: center; gap: var(--s1); margin-left: var(--s2); font-size: var(--fs-xs); white-space: nowrap; } | ||
| 18 | + | /* The lists are table-layout: fixed, where a percentage is only a sliver: | |||
| 19 | + | the check column holds the mark, its count and the padding of the cells; | |||
| 20 | + | the refs list has the mark next to the sha. */ | |||
| 18 | .c-ck { width: 1%; white-space: nowrap; } | 21 | .c-ck { width: 1%; white-space: nowrap; } | ||
| 22 | + | .list .c-ck { width: 92px; } | |||
| 23 | + | .rail .list.commits .c-ck { width: 76px; } | |||
| 24 | + | .list.refs .c-sha { width: 150px; } | |||
| 19 | 25 | ||||
| 20 | - | .runs .c-st { width: 1%; } | 26 | + | /* the table is fixed: the status column is the icon, its margin and the |
| 27 | + | padding of the cells (a percentage would leave a sliver) */ | |||
| 28 | + | .runs .c-st { width: calc(14px + var(--s1) + 2 * var(--s3)); } | |||
| 29 | + | /* the recent runs of the summary rail */ | |||
| 30 | + | .rruns { | |||
| 31 | + | display: grid; padding: 0; list-style: none; font-size: var(--fs-s); | |||
| 32 | + | & li { display: flex; align-items: center; gap: var(--s1); min-width: 0; padding: 2px 0; } | |||
| 33 | + | & .ck { margin-right: var(--s1); } | |||
| 34 | + | & .rr-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-hi); text-decoration: none; } | |||
| 35 | + | & .rr-name:hover { text-decoration: underline; } | |||
| 36 | + | & .rr-ref { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-left: var(--s1); font-family: var(--mono); color: var(--ink-dim); text-decoration: none; } | |||
| 37 | + | & time { flex: none; margin-left: auto; padding-left: var(--s2); white-space: nowrap; } | |||
| 38 | + | } | |||
| 21 | .runs .c-dur, .runs .c-who { white-space: nowrap; } | 39 | .runs .c-dur, .runs .c-who { white-space: nowrap; } | ||
| 22 | .runs tr[data-new] { background: var(--bg-raised); } | 40 | .runs tr[data-new] { background: var(--bg-raised); } | ||
| 23 | 41 | ||||
M internal/adapters/web/summary.go +37 -0
| @@ -4,6 +4,7 @@ package web | |||
| 4 | 4 | ||
| 5 | 5 | import ( | |
| 6 | 6 | "context" | |
| 7 | + | "errors" | |
| 7 | 8 | "fmt" | |
| 8 | 9 | "html" | |
| 9 | 10 | "html/template" | |
| @@ -26,6 +27,9 @@ const maxReadmeBytes = 512 << 10 | |||
| 26 | 27 | // recentCommits is how many commits the rail of the summary lists. | |
| 27 | 28 | const recentCommits = 5 | |
| 28 | 29 | ||
| 30 | + | // recentRuns is how many runs the rail of the summary lists. | |
| 31 | + | const recentRuns = 5 | |
| 32 | + | ||
| 29 | 33 | // readmeNames are the README file names in order of preference (compared | |
| 30 | 34 | // lowercase): Markdown first. | |
| 31 | 35 | var readmeNames = []string{"readme.md", "readme.markdown", "readme", "readme.txt"} | |
| @@ -63,6 +67,7 @@ func (s *Server) summary(ctx context.Context, r *http.Request) (templ.Component, | |||
| 63 | 67 | ||
| 64 | 68 | p.Facts = s.facts(d, now) | |
| 65 | 69 | p.Latest = s.latest(ctx, d) | |
| 70 | + | p.Actions = s.recentActions(ctx, d.repo.Name, now) | |
| 66 | 71 | ||
| 67 | 72 | if rd, err := s.readme(ctx, d, ref); err != nil { | |
| 68 | 73 | return nil, 0, err | |
| @@ -232,3 +237,35 @@ func humanSize(n int64) string { | |||
| 232 | 237 | } | |
| 233 | 238 | return fmt.Sprintf("%.1f MiB", float64(n)/(1<<20)) | |
| 234 | 239 | } | |
| 240 | + | ||
| 241 | + | // recentActions is the newest runs for the rail, as the caller of the request | |
| 242 | + | // sees them (the permission of the actions page). It is nil when the server | |
| 243 | + | // runs no CI or the caller cannot read it; a failure of the read is logged and | |
| 244 | + | // the page goes on without, since the code does not depend on CI. | |
| 245 | + | func (s *Server) recentActions(ctx context.Context, repo domain.RepoName, now time.Time) *RecentActions { | |
| 246 | + | if s.cfg.Runs == nil { | |
| 247 | + | return nil | |
| 248 | + | } | |
| 249 | + | a, err := s.listRecentActions(ctx, repo, now) | |
| 250 | + | if err != nil { | |
| 251 | + | if !errors.Is(err, domain.ErrNotFound) { | |
| 252 | + | log := zerowrap.FromCtx(ctx) | |
| 253 | + | log.Warn().Err(err).Str("repo", repo.String()).Msg("read the recent runs") | |
| 254 | + | } | |
| 255 | + | return nil | |
| 256 | + | } | |
| 257 | + | return &a | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | // listRecentActions reads the newest runs of a repository. | |
| 261 | + | func (s *Server) listRecentActions(ctx context.Context, repo domain.RepoName, now time.Time) (RecentActions, error) { | |
| 262 | + | page, err := s.cfg.Runs.ListRuns(ctx, principal(ctx), repo, ports.ListRunsParams{PageSize: recentRuns}) | |
| 263 | + | if err != nil { | |
| 264 | + | return RecentActions{}, err | |
| 265 | + | } | |
| 266 | + | a := RecentActions{AllHref: actionsBase(repo.String())} | |
| 267 | + | for _, run := range page.Runs { | |
| 268 | + | a.Runs = append(a.Runs, s.runRow(repo.String(), run, now)) | |
| 269 | + | } | |
| 270 | + | return a, nil | |
| 271 | + | } | |
| @@ -4,6 +4,7 @@ package web | |||||
| 4 | 4 | ||||
| 5 | import ( | 5 | import ( | ||
| 6 | "context" | 6 | "context" | ||
| 7 | + | "errors" | |||
| 7 | "fmt" | 8 | "fmt" | ||
| 8 | "html" | 9 | "html" | ||
| 9 | "html/template" | 10 | "html/template" | ||
| @@ -26,6 +27,9 @@ const maxReadmeBytes = 512 << 10 | |||||
| 26 | // recentCommits is how many commits the rail of the summary lists. | 27 | // recentCommits is how many commits the rail of the summary lists. | ||
| 27 | const recentCommits = 5 | 28 | const recentCommits = 5 | ||
| 28 | 29 | ||||
| 30 | + | // recentRuns is how many runs the rail of the summary lists. | |||
| 31 | + | const recentRuns = 5 | |||
| 32 | + | ||||
| 29 | // readmeNames are the README file names in order of preference (compared | 33 | // readmeNames are the README file names in order of preference (compared | ||
| 30 | // lowercase): Markdown first. | 34 | // lowercase): Markdown first. | ||
| 31 | var readmeNames = []string{"readme.md", "readme.markdown", "readme", "readme.txt"} | 35 | var readmeNames = []string{"readme.md", "readme.markdown", "readme", "readme.txt"} | ||
| @@ -63,6 +67,7 @@ func (s *Server) summary(ctx context.Context, r *http.Request) (templ.Component, | |||||
| 63 | 67 | ||||
| 64 | p.Facts = s.facts(d, now) | 68 | p.Facts = s.facts(d, now) | ||
| 65 | p.Latest = s.latest(ctx, d) | 69 | p.Latest = s.latest(ctx, d) | ||
| 70 | + | p.Actions = s.recentActions(ctx, d.repo.Name, now) | |||
| 66 | 71 | ||||
| 67 | if rd, err := s.readme(ctx, d, ref); err != nil { | 72 | if rd, err := s.readme(ctx, d, ref); err != nil { | ||
| 68 | return nil, 0, err | 73 | return nil, 0, err | ||
| @@ -232,3 +237,35 @@ func humanSize(n int64) string { | |||||
| 232 | } | 237 | } | ||
| 233 | return fmt.Sprintf("%.1f MiB", float64(n)/(1<<20)) | 238 | return fmt.Sprintf("%.1f MiB", float64(n)/(1<<20)) | ||
| 234 | } | 239 | } | ||
| 240 | + | ||||
| 241 | + | // recentActions is the newest runs for the rail, as the caller of the request | |||
| 242 | + | // sees them (the permission of the actions page). It is nil when the server | |||
| 243 | + | // runs no CI or the caller cannot read it; a failure of the read is logged and | |||
| 244 | + | // the page goes on without, since the code does not depend on CI. | |||
| 245 | + | func (s *Server) recentActions(ctx context.Context, repo domain.RepoName, now time.Time) *RecentActions { | |||
| 246 | + | if s.cfg.Runs == nil { | |||
| 247 | + | return nil | |||
| 248 | + | } | |||
| 249 | + | a, err := s.listRecentActions(ctx, repo, now) | |||
| 250 | + | if err != nil { | |||
| 251 | + | if !errors.Is(err, domain.ErrNotFound) { | |||
| 252 | + | log := zerowrap.FromCtx(ctx) | |||
| 253 | + | log.Warn().Err(err).Str("repo", repo.String()).Msg("read the recent runs") | |||
| 254 | + | } | |||
| 255 | + | return nil | |||
| 256 | + | } | |||
| 257 | + | return &a | |||
| 258 | + | } | |||
| 259 | + | ||||
| 260 | + | // listRecentActions reads the newest runs of a repository. | |||
| 261 | + | func (s *Server) listRecentActions(ctx context.Context, repo domain.RepoName, now time.Time) (RecentActions, error) { | |||
| 262 | + | page, err := s.cfg.Runs.ListRuns(ctx, principal(ctx), repo, ports.ListRunsParams{PageSize: recentRuns}) | |||
| 263 | + | if err != nil { | |||
| 264 | + | return RecentActions{}, err | |||
| 265 | + | } | |||
| 266 | + | a := RecentActions{AllHref: actionsBase(repo.String())} | |||
| 267 | + | for _, run := range page.Runs { | |||
| 268 | + | a.Runs = append(a.Runs, s.runRow(repo.String(), run, now)) | |||
| 269 | + | } | |||
| 270 | + | return a, nil | |||
| 271 | + | } | |||
A internal/adapters/web/summary_actions_test.go +127 -0
| @@ -0,0 +1,127 @@ | |||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | ||
| 3 | + | package web | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bytes" | |
| 7 | + | "errors" | |
| 8 | + | "strings" | |
| 9 | + | "testing" | |
| 10 | + | "time" | |
| 11 | + | ||
| 12 | + | "github.com/stretchr/testify/assert" | |
| 13 | + | "github.com/stretchr/testify/mock" | |
| 14 | + | "github.com/stretchr/testify/require" | |
| 15 | + | ||
| 16 | + | "bnema.dev/ermite/internal/domain" | |
| 17 | + | "bnema.dev/ermite/internal/ports" | |
| 18 | + | "bnema.dev/ermite/internal/ports/mocks" | |
| 19 | + | ) | |
| 20 | + | ||
| 21 | + | func testRuns(repo domain.RepoName, n int) []domain.CIRun { | |
| 22 | + | var runs []domain.CIRun | |
| 23 | + | for i := n; i >= 1; i-- { | |
| 24 | + | runs = append(runs, domain.CIRun{ | |
| 25 | + | Repo: repo, ID: int64(i), Number: int64(i), Attempt: 1, WorkflowName: "ci", Ref: "refs/heads/main", | |
| 26 | + | Sha: strings.Repeat("a", 40), Status: domain.CICompleted, Conclusion: domain.CISuccess, | |
| 27 | + | CreatedAt: time.Now().Add(-time.Duration(i) * time.Minute), | |
| 28 | + | }) | |
| 29 | + | } | |
| 30 | + | return runs | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | func servingRuns(t *testing.T, repo domain.RepoName, runs []domain.CIRun, err error) (*Server, *mocks.RunService) { | |
| 34 | + | t.Helper() | |
| 35 | + | m := mocks.NewRunService(t) | |
| 36 | + | m.EXPECT().ListRuns(mock.Anything, mock.Anything, repo, ports.ListRunsParams{PageSize: recentRuns}). | |
| 37 | + | Return(ports.RunPage{Runs: runs}, err).Maybe() | |
| 38 | + | return newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, repo.String())), Browse: mocks.NewBrowseService(t), Runs: m}), m | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | func renderRail(t *testing.T, p SummaryPage) string { | |
| 42 | + | t.Helper() | |
| 43 | + | var b bytes.Buffer | |
| 44 | + | require.NoError(t, summaryRail(p).Render(t.Context(), &b)) | |
| 45 | + | return b.String() | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | func TestSummaryRailRecentActions(t *testing.T) { | |
| 49 | + | t.Parallel() | |
| 50 | + | repo := repoName(t, "proj") | |
| 51 | + | srv, _ := servingRuns(t, repo, testRuns(repo, 5), nil) | |
| 52 | + | ||
| 53 | + | p := SummaryPage{Actions: srv.recentActions(t.Context(), repo, time.Now())} | |
| 54 | + | require.NotNil(t, p.Actions) | |
| 55 | + | out := renderRail(t, p) | |
| 56 | + | assert.Contains(t, out, `id="recent-actions"`) | |
| 57 | + | assert.Contains(t, out, "Recent actions") | |
| 58 | + | assert.Contains(t, out, `href="/proj/actions"`, "view all") | |
| 59 | + | assert.Equal(t, 5, strings.Count(out, `class="rr-name"`)) | |
| 60 | + | assert.Contains(t, out, `href="/proj/actions/runs/5"`) | |
| 61 | + | assert.Contains(t, out, `aria-label="success"`, "the status icon") | |
| 62 | + | assert.Contains(t, out, `href="/proj/tree/main"`, "the ref") | |
| 63 | + | assert.Contains(t, out, "#5") | |
| 64 | + | assert.NotContains(t, out, `id="recent-actions" aria-labelledby="r-actions" hidden`) | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | func TestSummaryRailRecentActionsHidden(t *testing.T) { | |
| 68 | + | t.Parallel() | |
| 69 | + | repo := repoName(t, "proj") | |
| 70 | + | ||
| 71 | + | // The viewer cannot read CI: no block at all, not even to receive events. | |
| 72 | + | srv, _ := servingRuns(t, repo, nil, domain.ErrNotFound) | |
| 73 | + | assert.Nil(t, srv.recentActions(t.Context(), repo, time.Now())) | |
| 74 | + | assert.NotContains(t, renderRail(t, SummaryPage{}), "recent-actions") | |
| 75 | + | ||
| 76 | + | // A failure of the read does not break the page. | |
| 77 | + | srv, _ = servingRuns(t, repo, nil, errors.New("boom")) | |
| 78 | + | assert.Nil(t, srv.recentActions(t.Context(), repo, time.Now())) | |
| 79 | + | ||
| 80 | + | // No CI on this server. | |
| 81 | + | noCI := newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, "proj")), Browse: mocks.NewBrowseService(t)}) | |
| 82 | + | assert.Nil(t, noCI.recentActions(t.Context(), repo, time.Now())) | |
| 83 | + | ||
| 84 | + | // No run yet: the block is there for the first run, but hidden. | |
| 85 | + | srv, _ = servingRuns(t, repo, nil, nil) | |
| 86 | + | a := srv.recentActions(t.Context(), repo, time.Now()) | |
| 87 | + | require.NotNil(t, a) | |
| 88 | + | assert.Contains(t, renderRail(t, SummaryPage{Actions: a}), `id="recent-actions" aria-labelledby="r-actions" hidden`) | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func TestRunUpdatedReplacesRecentActionsOnSummary(t *testing.T) { | |
| 92 | + | t.Parallel() | |
| 93 | + | repo := repoName(t, "proj") | |
| 94 | + | runs := testRuns(repo, 3) | |
| 95 | + | runs[0].Status, runs[0].Conclusion = domain.CIInProgress, "" | |
| 96 | + | srv, _ := servingRuns(t, repo, runs, nil) | |
| 97 | + | ||
| 98 | + | ev := ports.WatchEvent{Event: domain.RunUpdated{Run: runs[0]}} | |
| 99 | + | ls := &liveStream{s: srv, repo: "proj", view: "summary"} | |
| 100 | + | got, err := ls.render(t.Context(), ev) | |
| 101 | + | require.NoError(t, err) | |
| 102 | + | require.Len(t, got, 1) | |
| 103 | + | assert.Equal(t, "ci-run", got[0].event) | |
| 104 | + | assert.True(t, strings.HasPrefix(got[0].html, `<section id="recent-actions"`), got[0].html) | |
| 105 | + | assert.Equal(t, 3, strings.Count(got[0].html, `class="rr-name"`)) | |
| 106 | + | assert.Contains(t, got[0].html, `aria-label="running"`) | |
| 107 | + | ||
| 108 | + | // The other pages do not get the block. | |
| 109 | + | for _, view := range []string{"refs", "commits", "mrs"} { | |
| 110 | + | got, err = (&liveStream{s: srv, repo: "proj", view: view}).render(t.Context(), ev) | |
| 111 | + | require.NoError(t, err, view) | |
| 112 | + | assert.Empty(t, got, view) | |
| 113 | + | } | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | func TestRunUpdatedOnSummaryWhenCIIsNotReadable(t *testing.T) { | |
| 117 | + | t.Parallel() | |
| 118 | + | repo := repoName(t, "proj") | |
| 119 | + | srv, _ := servingRuns(t, repo, nil, domain.ErrNotFound) | |
| 120 | + | ||
| 121 | + | ls := &liveStream{s: srv, repo: "proj", view: "summary"} | |
| 122 | + | got, err := ls.render(t.Context(), ports.WatchEvent{Event: domain.RunUpdated{Run: testRuns(repo, 1)[0]}}) | |
| 123 | + | require.NoError(t, err) | |
| 124 | + | require.Len(t, got, 1) | |
| 125 | + | assert.Contains(t, got[0].html, "hidden", "the block empties and hides, no run leaks") | |
| 126 | + | assert.NotContains(t, got[0].html, "rr-name") | |
| 127 | + | } | |
| @@ -0,0 +1,127 @@ | |||||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |||
| 2 | + | ||||
| 3 | + | package web | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "bytes" | |||
| 7 | + | "errors" | |||
| 8 | + | "strings" | |||
| 9 | + | "testing" | |||
| 10 | + | "time" | |||
| 11 | + | ||||
| 12 | + | "github.com/stretchr/testify/assert" | |||
| 13 | + | "github.com/stretchr/testify/mock" | |||
| 14 | + | "github.com/stretchr/testify/require" | |||
| 15 | + | ||||
| 16 | + | "bnema.dev/ermite/internal/domain" | |||
| 17 | + | "bnema.dev/ermite/internal/ports" | |||
| 18 | + | "bnema.dev/ermite/internal/ports/mocks" | |||
| 19 | + | ) | |||
| 20 | + | ||||
| 21 | + | func testRuns(repo domain.RepoName, n int) []domain.CIRun { | |||
| 22 | + | var runs []domain.CIRun | |||
| 23 | + | for i := n; i >= 1; i-- { | |||
| 24 | + | runs = append(runs, domain.CIRun{ | |||
| 25 | + | Repo: repo, ID: int64(i), Number: int64(i), Attempt: 1, WorkflowName: "ci", Ref: "refs/heads/main", | |||
| 26 | + | Sha: strings.Repeat("a", 40), Status: domain.CICompleted, Conclusion: domain.CISuccess, | |||
| 27 | + | CreatedAt: time.Now().Add(-time.Duration(i) * time.Minute), | |||
| 28 | + | }) | |||
| 29 | + | } | |||
| 30 | + | return runs | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | func servingRuns(t *testing.T, repo domain.RepoName, runs []domain.CIRun, err error) (*Server, *mocks.RunService) { | |||
| 34 | + | t.Helper() | |||
| 35 | + | m := mocks.NewRunService(t) | |||
| 36 | + | m.EXPECT().ListRuns(mock.Anything, mock.Anything, repo, ports.ListRunsParams{PageSize: recentRuns}). | |||
| 37 | + | Return(ports.RunPage{Runs: runs}, err).Maybe() | |||
| 38 | + | return newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, repo.String())), Browse: mocks.NewBrowseService(t), Runs: m}), m | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | func renderRail(t *testing.T, p SummaryPage) string { | |||
| 42 | + | t.Helper() | |||
| 43 | + | var b bytes.Buffer | |||
| 44 | + | require.NoError(t, summaryRail(p).Render(t.Context(), &b)) | |||
| 45 | + | return b.String() | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | func TestSummaryRailRecentActions(t *testing.T) { | |||
| 49 | + | t.Parallel() | |||
| 50 | + | repo := repoName(t, "proj") | |||
| 51 | + | srv, _ := servingRuns(t, repo, testRuns(repo, 5), nil) | |||
| 52 | + | ||||
| 53 | + | p := SummaryPage{Actions: srv.recentActions(t.Context(), repo, time.Now())} | |||
| 54 | + | require.NotNil(t, p.Actions) | |||
| 55 | + | out := renderRail(t, p) | |||
| 56 | + | assert.Contains(t, out, `id="recent-actions"`) | |||
| 57 | + | assert.Contains(t, out, "Recent actions") | |||
| 58 | + | assert.Contains(t, out, `href="/proj/actions"`, "view all") | |||
| 59 | + | assert.Equal(t, 5, strings.Count(out, `class="rr-name"`)) | |||
| 60 | + | assert.Contains(t, out, `href="/proj/actions/runs/5"`) | |||
| 61 | + | assert.Contains(t, out, `aria-label="success"`, "the status icon") | |||
| 62 | + | assert.Contains(t, out, `href="/proj/tree/main"`, "the ref") | |||
| 63 | + | assert.Contains(t, out, "#5") | |||
| 64 | + | assert.NotContains(t, out, `id="recent-actions" aria-labelledby="r-actions" hidden`) | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | func TestSummaryRailRecentActionsHidden(t *testing.T) { | |||
| 68 | + | t.Parallel() | |||
| 69 | + | repo := repoName(t, "proj") | |||
| 70 | + | ||||
| 71 | + | // The viewer cannot read CI: no block at all, not even to receive events. | |||
| 72 | + | srv, _ := servingRuns(t, repo, nil, domain.ErrNotFound) | |||
| 73 | + | assert.Nil(t, srv.recentActions(t.Context(), repo, time.Now())) | |||
| 74 | + | assert.NotContains(t, renderRail(t, SummaryPage{}), "recent-actions") | |||
| 75 | + | ||||
| 76 | + | // A failure of the read does not break the page. | |||
| 77 | + | srv, _ = servingRuns(t, repo, nil, errors.New("boom")) | |||
| 78 | + | assert.Nil(t, srv.recentActions(t.Context(), repo, time.Now())) | |||
| 79 | + | ||||
| 80 | + | // No CI on this server. | |||
| 81 | + | noCI := newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, "proj")), Browse: mocks.NewBrowseService(t)}) | |||
| 82 | + | assert.Nil(t, noCI.recentActions(t.Context(), repo, time.Now())) | |||
| 83 | + | ||||
| 84 | + | // No run yet: the block is there for the first run, but hidden. | |||
| 85 | + | srv, _ = servingRuns(t, repo, nil, nil) | |||
| 86 | + | a := srv.recentActions(t.Context(), repo, time.Now()) | |||
| 87 | + | require.NotNil(t, a) | |||
| 88 | + | assert.Contains(t, renderRail(t, SummaryPage{Actions: a}), `id="recent-actions" aria-labelledby="r-actions" hidden`) | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | func TestRunUpdatedReplacesRecentActionsOnSummary(t *testing.T) { | |||
| 92 | + | t.Parallel() | |||
| 93 | + | repo := repoName(t, "proj") | |||
| 94 | + | runs := testRuns(repo, 3) | |||
| 95 | + | runs[0].Status, runs[0].Conclusion = domain.CIInProgress, "" | |||
| 96 | + | srv, _ := servingRuns(t, repo, runs, nil) | |||
| 97 | + | ||||
| 98 | + | ev := ports.WatchEvent{Event: domain.RunUpdated{Run: runs[0]}} | |||
| 99 | + | ls := &liveStream{s: srv, repo: "proj", view: "summary"} | |||
| 100 | + | got, err := ls.render(t.Context(), ev) | |||
| 101 | + | require.NoError(t, err) | |||
| 102 | + | require.Len(t, got, 1) | |||
| 103 | + | assert.Equal(t, "ci-run", got[0].event) | |||
| 104 | + | assert.True(t, strings.HasPrefix(got[0].html, `<section id="recent-actions"`), got[0].html) | |||
| 105 | + | assert.Equal(t, 3, strings.Count(got[0].html, `class="rr-name"`)) | |||
| 106 | + | assert.Contains(t, got[0].html, `aria-label="running"`) | |||
| 107 | + | ||||
| 108 | + | // The other pages do not get the block. | |||
| 109 | + | for _, view := range []string{"refs", "commits", "mrs"} { | |||
| 110 | + | got, err = (&liveStream{s: srv, repo: "proj", view: view}).render(t.Context(), ev) | |||
| 111 | + | require.NoError(t, err, view) | |||
| 112 | + | assert.Empty(t, got, view) | |||
| 113 | + | } | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | func TestRunUpdatedOnSummaryWhenCIIsNotReadable(t *testing.T) { | |||
| 117 | + | t.Parallel() | |||
| 118 | + | repo := repoName(t, "proj") | |||
| 119 | + | srv, _ := servingRuns(t, repo, nil, domain.ErrNotFound) | |||
| 120 | + | ||||
| 121 | + | ls := &liveStream{s: srv, repo: "proj", view: "summary"} | |||
| 122 | + | got, err := ls.render(t.Context(), ports.WatchEvent{Event: domain.RunUpdated{Run: testRuns(repo, 1)[0]}}) | |||
| 123 | + | require.NoError(t, err) | |||
| 124 | + | require.Len(t, got, 1) | |||
| 125 | + | assert.Contains(t, got[0].html, "hidden", "the block empties and hides, no run leaks") | |||
| 126 | + | assert.NotContains(t, got[0].html, "rr-name") | |||
| 127 | + | } | |||
M internal/adapters/web/view.go +11 -0
| @@ -153,6 +153,17 @@ type SummaryPage struct { | |||
| 153 | 153 | AllHref string | |
| 154 | 154 | // Latest is the newest tag, nil when the repository has none. | |
| 155 | 155 | Latest *LatestTag | |
| 156 | + | // Actions is the recent runs of the rail, nil when the viewer cannot read | |
| 157 | + | // CI (or the server runs none). | |
| 158 | + | Actions *RecentActions | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | // RecentActions is the "Recent actions" block of the summary rail: the newest | |
| 162 | + | // runs. With no run the block is in the page but hidden, so that the first | |
| 163 | + | // run reaches it live. | |
| 164 | + | type RecentActions struct { | |
| 165 | + | Runs []RunRow | |
| 166 | + | AllHref string | |
| 156 | 167 | } | |
| 157 | 168 | ||
| 158 | 169 | // LatestTag is the newest tag of a repository, shown in the summary rail. | |
| @@ -153,6 +153,17 @@ type SummaryPage struct { | |||||
| 153 | AllHref string | 153 | AllHref string | ||
| 154 | // Latest is the newest tag, nil when the repository has none. | 154 | // Latest is the newest tag, nil when the repository has none. | ||
| 155 | Latest *LatestTag | 155 | Latest *LatestTag | ||
| 156 | + | // Actions is the recent runs of the rail, nil when the viewer cannot read | |||
| 157 | + | // CI (or the server runs none). | |||
| 158 | + | Actions *RecentActions | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | // RecentActions is the "Recent actions" block of the summary rail: the newest | |||
| 162 | + | // runs. With no run the block is in the page but hidden, so that the first | |||
| 163 | + | // run reaches it live. | |||
| 164 | + | type RecentActions struct { | |||
| 165 | + | Runs []RunRow | |||
| 166 | + | AllHref string | |||
| 156 | } | 167 | } | ||
| 157 | 168 | ||||
| 158 | // LatestTag is the newest tag of a repository, shown in the summary rail. | 169 | // LatestTag is the newest tag of a repository, shown in the summary rail. | ||
M internal/app/testdata/web/summary.html +6 -0
| @@ -200,6 +200,12 @@ | |||
| 200 | 200 | </tbody> | |
| 201 | 201 | </table> | |
| 202 | 202 | </section> | |
| 203 | + | <section id="recent-actions" aria-labelledby="r-actions" hidden> | |
| 204 | + | <h2 id="r-actions">Recent actions <a href="/demo/actions">View all</a> | |
| 205 | + | </h2> | |
| 206 | + | <ul class="rruns"> | |
| 207 | + | </ul> | |
| 208 | + | </section> | |
| 203 | 209 | <section aria-labelledby="r-outline"> | |
| 204 | 210 | <h2 id="r-outline">On this page</h2> | |
| 205 | 211 | <nav class="outline" aria-label="Outline"> | |
| @@ -200,6 +200,12 @@ | |||||
| 200 | </tbody> | 200 | </tbody> | ||
| 201 | </table> | 201 | </table> | ||
| 202 | </section> | 202 | </section> | ||
| 203 | + | <section id="recent-actions" aria-labelledby="r-actions" hidden> | |||
| 204 | + | <h2 id="r-actions">Recent actions <a href="/demo/actions">View all</a> | |||
| 205 | + | </h2> | |||
| 206 | + | <ul class="rruns"> | |||
| 207 | + | </ul> | |||
| 208 | + | </section> | |||
| 203 | <section aria-labelledby="r-outline"> | 209 | <section aria-labelledby="r-outline"> | ||
| 204 | <h2 id="r-outline">On this page</h2> | 210 | <h2 id="r-outline">On this page</h2> | ||
| 205 | <nav class="outline" aria-label="Outline"> | 211 | <nav class="outline" aria-label="Outline"> | ||