detached 3bc6931

fix(ci): bound the summaries a page reads and stop reading the run on live job updates

commit
3bc6931d1790eb7bae68fe26f6f4676f9c6d259a
author
brice · · 2026-10-13 12:58 UTC
parent
19fda0c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
17 files · +734 -411
M internal/adapters/cisummaries/cisummaries.go +55 -2
@@ -13,6 +13,7 @@ import (
1313 "context"
1414 "errors"
1515 "fmt"
16+ "io"
1617 "io/fs"
1718 "os"
1819 "path/filepath"
@@ -89,7 +90,7 @@ func (s *Store) Put(_ context.Context, ref ports.CILogRef, step int32, markdown
8990 }
9091
9192 // List returns the summaries of the steps that have one, by step number.
92-func (s *Store) List(_ context.Context, ref ports.CILogRef, limit int) ([]domain.CIStepSummary, bool, error) {
93+func (s *Store) List(_ context.Context, ref ports.CILogRef, limit int, maxBytes int64) ([]domain.CIStepSummary, bool, error) {
9394 if !validRef(ref) {
9495 return nil, false, nil
9596 }
@@ -118,19 +119,71 @@ func (s *Store) List(_ context.Context, ref ports.CILogRef, limit int) ([]domain
118119 steps = steps[:limit]
119120 }
120121 out := make([]domain.CIStepSummary, 0, len(steps))
122+ var read int64
123+ exhausted := false
121124 for _, n := range steps {
122- b, err := os.ReadFile(s.path(ref, n))
125+ if exhausted {
126+ out = append(out, domain.CIStepSummary{Step: n, Omitted: true})
127+ continue
128+ }
129+ if maxBytes > 0 {
130+ // Size the file first: a summary that does not fit is not read.
131+ info, err := os.Stat(s.path(ref, n))
132+ if errors.Is(err, fs.ErrNotExist) {
133+ continue // removed meanwhile
134+ }
135+ if err != nil {
136+ return nil, false, fmt.Errorf("stat summary: %w", err)
137+ }
138+ if read+info.Size() > maxBytes {
139+ exhausted = true
140+ out = append(out, domain.CIStepSummary{Step: n, Omitted: true})
141+ continue
142+ }
143+ }
144+ b, err := readCapped(s.path(ref, n), maxBytes, read)
123145 if errors.Is(err, fs.ErrNotExist) {
124146 continue // removed meanwhile
125147 }
148+ if errors.Is(err, errOverBudget) {
149+ exhausted = true // replaced by a larger file since the stat
150+ out = append(out, domain.CIStepSummary{Step: n, Omitted: true})
151+ continue
152+ }
126153 if err != nil {
127154 return nil, false, fmt.Errorf("read summary: %w", err)
128155 }
156+ read += int64(len(b))
129157 out = append(out, domain.CIStepSummary{Step: n, Markdown: string(b)})
130158 }
131159 return out, more, nil
132160 }
133161
162+var errOverBudget = errors.New("over budget")
163+
164+// readCapped reads a file, at most what is left of maxBytes after read bytes
165+// (no cap when maxBytes is 0): a file that is longer than that, because it grew
166+// since it was sized, is errOverBudget and its content is dropped.
167+func readCapped(path string, maxBytes, read int64) ([]byte, error) {
168+ f, err := os.Open(path)
169+ if err != nil {
170+ return nil, err
171+ }
172+ defer func() { _ = f.Close() }()
173+ if maxBytes <= 0 {
174+ return io.ReadAll(f)
175+ }
176+ left := maxBytes - read
177+ b, err := io.ReadAll(io.LimitReader(f, left+1))
178+ if err != nil {
179+ return nil, err
180+ }
181+ if int64(len(b)) > left {
182+ return nil, errOverBudget
183+ }
184+ return b, nil
185+}
186+
134187 // RemoveJob deletes the summaries of one execution of a job.
135188 func (s *Store) RemoveJob(_ context.Context, ref ports.CILogRef) error {
136189 if !validRef(ref) {
@@ -13,6 +13,7 @@ import (
13 "context"13 "context"
14 "errors"14 "errors"
15 "fmt"15 "fmt"
16+ "io"
16 "io/fs"17 "io/fs"
17 "os"18 "os"
18 "path/filepath"19 "path/filepath"
@@ -89,7 +90,7 @@ func (s *Store) Put(_ context.Context, ref ports.CILogRef, step int32, markdown
89 }90 }
90 91
91 // List returns the summaries of the steps that have one, by step number.92 // List returns the summaries of the steps that have one, by step number.
92-func (s *Store) List(_ context.Context, ref ports.CILogRef, limit int) ([]domain.CIStepSummary, bool, error) {93+func (s *Store) List(_ context.Context, ref ports.CILogRef, limit int, maxBytes int64) ([]domain.CIStepSummary, bool, error) {
93 if !validRef(ref) {94 if !validRef(ref) {
94 return nil, false, nil95 return nil, false, nil
95 }96 }
@@ -118,19 +119,71 @@ func (s *Store) List(_ context.Context, ref ports.CILogRef, limit int) ([]domain
118 steps = steps[:limit]119 steps = steps[:limit]
119 }120 }
120 out := make([]domain.CIStepSummary, 0, len(steps))121 out := make([]domain.CIStepSummary, 0, len(steps))
122+ var read int64
123+ exhausted := false
121 for _, n := range steps {124 for _, n := range steps {
122- b, err := os.ReadFile(s.path(ref, n))125+ if exhausted {
126+ out = append(out, domain.CIStepSummary{Step: n, Omitted: true})
127+ continue
128+ }
129+ if maxBytes > 0 {
130+ // Size the file first: a summary that does not fit is not read.
131+ info, err := os.Stat(s.path(ref, n))
132+ if errors.Is(err, fs.ErrNotExist) {
133+ continue // removed meanwhile
134+ }
135+ if err != nil {
136+ return nil, false, fmt.Errorf("stat summary: %w", err)
137+ }
138+ if read+info.Size() > maxBytes {
139+ exhausted = true
140+ out = append(out, domain.CIStepSummary{Step: n, Omitted: true})
141+ continue
142+ }
143+ }
144+ b, err := readCapped(s.path(ref, n), maxBytes, read)
123 if errors.Is(err, fs.ErrNotExist) {145 if errors.Is(err, fs.ErrNotExist) {
124 continue // removed meanwhile146 continue // removed meanwhile
125 }147 }
148+ if errors.Is(err, errOverBudget) {
149+ exhausted = true // replaced by a larger file since the stat
150+ out = append(out, domain.CIStepSummary{Step: n, Omitted: true})
151+ continue
152+ }
126 if err != nil {153 if err != nil {
127 return nil, false, fmt.Errorf("read summary: %w", err)154 return nil, false, fmt.Errorf("read summary: %w", err)
128 }155 }
156+ read += int64(len(b))
129 out = append(out, domain.CIStepSummary{Step: n, Markdown: string(b)})157 out = append(out, domain.CIStepSummary{Step: n, Markdown: string(b)})
130 }158 }
131 return out, more, nil159 return out, more, nil
132 }160 }
133 161
162+var errOverBudget = errors.New("over budget")
163+
164+// readCapped reads a file, at most what is left of maxBytes after read bytes
165+// (no cap when maxBytes is 0): a file that is longer than that, because it grew
166+// since it was sized, is errOverBudget and its content is dropped.
167+func readCapped(path string, maxBytes, read int64) ([]byte, error) {
168+ f, err := os.Open(path)
169+ if err != nil {
170+ return nil, err
171+ }
172+ defer func() { _ = f.Close() }()
173+ if maxBytes <= 0 {
174+ return io.ReadAll(f)
175+ }
176+ left := maxBytes - read
177+ b, err := io.ReadAll(io.LimitReader(f, left+1))
178+ if err != nil {
179+ return nil, err
180+ }
181+ if int64(len(b)) > left {
182+ return nil, errOverBudget
183+ }
184+ return b, nil
185+}
186+
134 // RemoveJob deletes the summaries of one execution of a job.187 // RemoveJob deletes the summaries of one execution of a job.
135 func (s *Store) RemoveJob(_ context.Context, ref ports.CILogRef) error {188 func (s *Store) RemoveJob(_ context.Context, ref ports.CILogRef) error {
136 if !validRef(ref) {189 if !validRef(ref) {
M internal/adapters/cisummaries/cisummaries_test.go +68 -14
@@ -23,7 +23,7 @@ func TestPutListReplaceAndRemove(t *testing.T) {
2323 root := t.TempDir()
2424 s := cisummaries.New(root)
2525
26- got, more, err := s.List(ctx, ref, 20)
26+ got, more, err := s.List(ctx, ref, 20, 0)
2727 require.NoError(t, err)
2828 assert.Empty(t, got)
2929 assert.False(t, more)
@@ -35,23 +35,23 @@ func TestPutListReplaceAndRemove(t *testing.T) {
3535 require.NoError(t, err)
3636 assert.Equal(t, "two", string(b))
3737
38- got, _, err = s.List(ctx, ref, 20)
38+ got, _, err = s.List(ctx, ref, 20, 0)
3939 require.NoError(t, err)
4040 assert.Equal(t, []domain.CIStepSummary{{Step: 2, Markdown: "two"}, {Step: 10, Markdown: "# ten"}}, got, "by step number, not by name")
4141
4242 // Replace, and an empty markdown removes.
4343 require.NoError(t, s.Put(ctx, ref, 2, "again"))
44- got, _, _ = s.List(ctx, ref, 20)
44+ got, _, _ = s.List(ctx, ref, 20, 0)
4545 assert.Equal(t, "again", got[0].Markdown)
4646 require.NoError(t, s.Put(ctx, ref, 2, ""))
4747 require.NoError(t, s.Put(ctx, ref, 3, ""), "removing what is not there is fine")
48- got, _, _ = s.List(ctx, ref, 20)
48+ got, _, _ = s.List(ctx, ref, 20, 0)
4949 require.Len(t, got, 1)
5050 assert.EqualValues(t, 10, got[0].Step)
5151
5252 // Another attempt and another job have their own.
5353 other := ports.CILogRef{RunID: 7, JobID: "7-0-0", Attempt: 2}
54- got, _, _ = s.List(ctx, other, 20)
54+ got, _, _ = s.List(ctx, other, 20, 0)
5555 assert.Empty(t, got)
5656
5757 // A write leaves no temporary file behind.
@@ -68,12 +68,12 @@ func TestListStopsAtTheLimit(t *testing.T) {
6868 for i := int32(1); i <= 25; i++ {
6969 require.NoError(t, s.Put(ctx, ref, i, "s"))
7070 }
71- got, more, err := s.List(ctx, ref, 20)
71+ got, more, err := s.List(ctx, ref, 20, 0)
7272 require.NoError(t, err)
7373 assert.Len(t, got, 20)
7474 assert.True(t, more)
7575 assert.EqualValues(t, 20, got[19].Step, "the first 20 steps that have one")
76- got, more, _ = s.List(ctx, ref, 25)
76+ got, more, _ = s.List(ctx, ref, 25, 0)
7777 assert.Len(t, got, 25)
7878 assert.False(t, more)
7979 }
@@ -88,7 +88,7 @@ func TestListIgnoresStrayFiles(t *testing.T) {
8888 for _, n := range []string{".tmp-123", "x.md", "01.md", "0.md", "-1.md", "notes.txt"} {
8989 require.NoError(t, os.WriteFile(filepath.Join(dir, n), []byte("stray"), 0o600))
9090 }
91- got, _, err := s.List(ctx, ref, 20)
91+ got, _, err := s.List(ctx, ref, 20, 0)
9292 require.NoError(t, err)
9393 require.Len(t, got, 1)
9494 assert.Equal(t, "one", got[0].Markdown)
@@ -110,15 +110,15 @@ func TestRemoveJobRemoveRunsAndRunIDs(t *testing.T) {
110110 assert.ElementsMatch(t, []int64{7, 8}, ids)
111111
112112 require.NoError(t, s.RemoveJob(ctx, ref))
113- got, _, _ := s.List(ctx, ref, 20)
113+ got, _, _ := s.List(ctx, ref, 20, 0)
114114 assert.Empty(t, got)
115- got, _, _ = s.List(ctx, second, 20)
115+ got, _, _ = s.List(ctx, second, 20, 0)
116116 assert.Len(t, got, 1, "the other job of the run stays")
117117
118118 require.NoError(t, s.RemoveRuns(ctx, []int64{7, 0, -3, 99}))
119- got, _, _ = s.List(ctx, second, 20)
119+ got, _, _ = s.List(ctx, second, 20, 0)
120120 assert.Empty(t, got)
121- got, _, _ = s.List(ctx, third, 20)
121+ got, _, _ = s.List(ctx, third, 20, 0)
122122 assert.Len(t, got, 1)
123123 ids, _ = s.RunIDs(ctx)
124124 assert.Equal(t, []int64{8}, ids)
@@ -136,7 +136,7 @@ func TestRefsCannotEscape(t *testing.T) {
136136 {RunID: 7, JobID: "7-0-0", Attempt: 0},
137137 } {
138138 assert.ErrorIs(t, s.Put(ctx, bad, 1, "x"), domain.ErrNotFound)
139- got, _, err := s.List(ctx, bad, 20)
139+ got, _, err := s.List(ctx, bad, 20, 0)
140140 require.NoError(t, err)
141141 assert.Empty(t, got)
142142 }
@@ -151,7 +151,61 @@ func TestASummaryOfTheMaximumSizeRoundTrips(t *testing.T) {
151151 s := cisummaries.New(t.TempDir())
152152 big := strings.Repeat("x", domain.CIMaxSummaryBytes)
153153 require.NoError(t, s.Put(ctx, ref, 1, big))
154- got, _, err := s.List(ctx, ref, 20)
154+ got, _, err := s.List(ctx, ref, 20, 0)
155155 require.NoError(t, err)
156156 assert.Len(t, got[0].Markdown, domain.CIMaxSummaryBytes)
157157 }
158+
159+// With a byte budget the files are sized before they are read: a summary that
160+// does not fit, and every one after it, comes back Omitted and is never opened.
161+func TestListReadsNoMoreThanTheBudget(t *testing.T) {
162+ ctx := t.Context()
163+ root := t.TempDir()
164+ s := cisummaries.New(root)
165+ require.NoError(t, s.Put(ctx, ref, 1, strings.Repeat("a", 100)))
166+ require.NoError(t, s.Put(ctx, ref, 2, strings.Repeat("b", 100)))
167+ require.NoError(t, s.Put(ctx, ref, 3, strings.Repeat("c", 10)))
168+ require.NoError(t, s.Put(ctx, ref, 4, "d"))
169+ dir := filepath.Join(root, "7", "7-0-0", "1")
170+ // A file that cannot be opened proves it was sized, not read.
171+ if os.Geteuid() != 0 {
172+ for _, n := range []string{"2.md", "3.md", "4.md"} {
173+ require.NoError(t, os.Chmod(filepath.Join(dir, n), 0))
174+ }
175+ }
176+
177+ got, more, err := s.List(ctx, ref, 20, 150)
178+ require.NoError(t, err)
179+ assert.False(t, more)
180+ require.Len(t, got, 4)
181+ assert.Equal(t, strings.Repeat("a", 100), got[0].Markdown)
182+ assert.False(t, got[0].Omitted)
183+ for _, o := range got[1:] {
184+ assert.True(t, o.Omitted, "step %d", o.Step)
185+ assert.Empty(t, o.Markdown)
186+ }
187+ assert.EqualValues(t, []int32{1, 2, 3, 4}, []int32{got[0].Step, got[1].Step, got[2].Step, got[3].Step}, "the steps are still listed")
188+}
189+
190+func TestListBudgetBoundaries(t *testing.T) {
191+ ctx := t.Context()
192+ s := cisummaries.New(t.TempDir())
193+ require.NoError(t, s.Put(ctx, ref, 1, "12345"))
194+ require.NoError(t, s.Put(ctx, ref, 2, "678"))
195+
196+ got, _, _ := s.List(ctx, ref, 20, 8)
197+ assert.False(t, got[0].Omitted || got[1].Omitted, "exactly the budget fits")
198+ got, _, _ = s.List(ctx, ref, 20, 7)
199+ assert.False(t, got[0].Omitted)
200+ assert.True(t, got[1].Omitted, "one byte over")
201+ got, _, _ = s.List(ctx, ref, 20, 4)
202+ assert.True(t, got[0].Omitted && got[1].Omitted, "the first does not fit")
203+ got, _, _ = s.List(ctx, ref, 20, 0)
204+ assert.False(t, got[0].Omitted || got[1].Omitted, "no budget, no bound")
205+
206+ // A smaller file after one that does not fit is still omitted: the order
207+ // of steps is kept and nothing is read past the first omission.
208+ require.NoError(t, s.Put(ctx, ref, 3, "x"))
209+ got, _, _ = s.List(ctx, ref, 20, 7)
210+ assert.True(t, got[2].Omitted)
211+}
@@ -23,7 +23,7 @@ func TestPutListReplaceAndRemove(t *testing.T) {
23 root := t.TempDir()23 root := t.TempDir()
24 s := cisummaries.New(root)24 s := cisummaries.New(root)
25 25
26- got, more, err := s.List(ctx, ref, 20)26+ got, more, err := s.List(ctx, ref, 20, 0)
27 require.NoError(t, err)27 require.NoError(t, err)
28 assert.Empty(t, got)28 assert.Empty(t, got)
29 assert.False(t, more)29 assert.False(t, more)
@@ -35,23 +35,23 @@ func TestPutListReplaceAndRemove(t *testing.T) {
35 require.NoError(t, err)35 require.NoError(t, err)
36 assert.Equal(t, "two", string(b))36 assert.Equal(t, "two", string(b))
37 37
38- got, _, err = s.List(ctx, ref, 20)38+ got, _, err = s.List(ctx, ref, 20, 0)
39 require.NoError(t, err)39 require.NoError(t, err)
40 assert.Equal(t, []domain.CIStepSummary{{Step: 2, Markdown: "two"}, {Step: 10, Markdown: "# ten"}}, got, "by step number, not by name")40 assert.Equal(t, []domain.CIStepSummary{{Step: 2, Markdown: "two"}, {Step: 10, Markdown: "# ten"}}, got, "by step number, not by name")
41 41
42 // Replace, and an empty markdown removes.42 // Replace, and an empty markdown removes.
43 require.NoError(t, s.Put(ctx, ref, 2, "again"))43 require.NoError(t, s.Put(ctx, ref, 2, "again"))
44- got, _, _ = s.List(ctx, ref, 20)44+ got, _, _ = s.List(ctx, ref, 20, 0)
45 assert.Equal(t, "again", got[0].Markdown)45 assert.Equal(t, "again", got[0].Markdown)
46 require.NoError(t, s.Put(ctx, ref, 2, ""))46 require.NoError(t, s.Put(ctx, ref, 2, ""))
47 require.NoError(t, s.Put(ctx, ref, 3, ""), "removing what is not there is fine")47 require.NoError(t, s.Put(ctx, ref, 3, ""), "removing what is not there is fine")
48- got, _, _ = s.List(ctx, ref, 20)48+ got, _, _ = s.List(ctx, ref, 20, 0)
49 require.Len(t, got, 1)49 require.Len(t, got, 1)
50 assert.EqualValues(t, 10, got[0].Step)50 assert.EqualValues(t, 10, got[0].Step)
51 51
52 // Another attempt and another job have their own.52 // Another attempt and another job have their own.
53 other := ports.CILogRef{RunID: 7, JobID: "7-0-0", Attempt: 2}53 other := ports.CILogRef{RunID: 7, JobID: "7-0-0", Attempt: 2}
54- got, _, _ = s.List(ctx, other, 20)54+ got, _, _ = s.List(ctx, other, 20, 0)
55 assert.Empty(t, got)55 assert.Empty(t, got)
56 56
57 // A write leaves no temporary file behind.57 // A write leaves no temporary file behind.
@@ -68,12 +68,12 @@ func TestListStopsAtTheLimit(t *testing.T) {
68 for i := int32(1); i <= 25; i++ {68 for i := int32(1); i <= 25; i++ {
69 require.NoError(t, s.Put(ctx, ref, i, "s"))69 require.NoError(t, s.Put(ctx, ref, i, "s"))
70 }70 }
71- got, more, err := s.List(ctx, ref, 20)71+ got, more, err := s.List(ctx, ref, 20, 0)
72 require.NoError(t, err)72 require.NoError(t, err)
73 assert.Len(t, got, 20)73 assert.Len(t, got, 20)
74 assert.True(t, more)74 assert.True(t, more)
75 assert.EqualValues(t, 20, got[19].Step, "the first 20 steps that have one")75 assert.EqualValues(t, 20, got[19].Step, "the first 20 steps that have one")
76- got, more, _ = s.List(ctx, ref, 25)76+ got, more, _ = s.List(ctx, ref, 25, 0)
77 assert.Len(t, got, 25)77 assert.Len(t, got, 25)
78 assert.False(t, more)78 assert.False(t, more)
79 }79 }
@@ -88,7 +88,7 @@ func TestListIgnoresStrayFiles(t *testing.T) {
88 for _, n := range []string{".tmp-123", "x.md", "01.md", "0.md", "-1.md", "notes.txt"} {88 for _, n := range []string{".tmp-123", "x.md", "01.md", "0.md", "-1.md", "notes.txt"} {
89 require.NoError(t, os.WriteFile(filepath.Join(dir, n), []byte("stray"), 0o600))89 require.NoError(t, os.WriteFile(filepath.Join(dir, n), []byte("stray"), 0o600))
90 }90 }
91- got, _, err := s.List(ctx, ref, 20)91+ got, _, err := s.List(ctx, ref, 20, 0)
92 require.NoError(t, err)92 require.NoError(t, err)
93 require.Len(t, got, 1)93 require.Len(t, got, 1)
94 assert.Equal(t, "one", got[0].Markdown)94 assert.Equal(t, "one", got[0].Markdown)
@@ -110,15 +110,15 @@ func TestRemoveJobRemoveRunsAndRunIDs(t *testing.T) {
110 assert.ElementsMatch(t, []int64{7, 8}, ids)110 assert.ElementsMatch(t, []int64{7, 8}, ids)
111 111
112 require.NoError(t, s.RemoveJob(ctx, ref))112 require.NoError(t, s.RemoveJob(ctx, ref))
113- got, _, _ := s.List(ctx, ref, 20)113+ got, _, _ := s.List(ctx, ref, 20, 0)
114 assert.Empty(t, got)114 assert.Empty(t, got)
115- got, _, _ = s.List(ctx, second, 20)115+ got, _, _ = s.List(ctx, second, 20, 0)
116 assert.Len(t, got, 1, "the other job of the run stays")116 assert.Len(t, got, 1, "the other job of the run stays")
117 117
118 require.NoError(t, s.RemoveRuns(ctx, []int64{7, 0, -3, 99}))118 require.NoError(t, s.RemoveRuns(ctx, []int64{7, 0, -3, 99}))
119- got, _, _ = s.List(ctx, second, 20)119+ got, _, _ = s.List(ctx, second, 20, 0)
120 assert.Empty(t, got)120 assert.Empty(t, got)
121- got, _, _ = s.List(ctx, third, 20)121+ got, _, _ = s.List(ctx, third, 20, 0)
122 assert.Len(t, got, 1)122 assert.Len(t, got, 1)
123 ids, _ = s.RunIDs(ctx)123 ids, _ = s.RunIDs(ctx)
124 assert.Equal(t, []int64{8}, ids)124 assert.Equal(t, []int64{8}, ids)
@@ -136,7 +136,7 @@ func TestRefsCannotEscape(t *testing.T) {
136 {RunID: 7, JobID: "7-0-0", Attempt: 0},136 {RunID: 7, JobID: "7-0-0", Attempt: 0},
137 } {137 } {
138 assert.ErrorIs(t, s.Put(ctx, bad, 1, "x"), domain.ErrNotFound)138 assert.ErrorIs(t, s.Put(ctx, bad, 1, "x"), domain.ErrNotFound)
139- got, _, err := s.List(ctx, bad, 20)139+ got, _, err := s.List(ctx, bad, 20, 0)
140 require.NoError(t, err)140 require.NoError(t, err)
141 assert.Empty(t, got)141 assert.Empty(t, got)
142 }142 }
@@ -151,7 +151,61 @@ func TestASummaryOfTheMaximumSizeRoundTrips(t *testing.T) {
151 s := cisummaries.New(t.TempDir())151 s := cisummaries.New(t.TempDir())
152 big := strings.Repeat("x", domain.CIMaxSummaryBytes)152 big := strings.Repeat("x", domain.CIMaxSummaryBytes)
153 require.NoError(t, s.Put(ctx, ref, 1, big))153 require.NoError(t, s.Put(ctx, ref, 1, big))
154- got, _, err := s.List(ctx, ref, 20)154+ got, _, err := s.List(ctx, ref, 20, 0)
155 require.NoError(t, err)155 require.NoError(t, err)
156 assert.Len(t, got[0].Markdown, domain.CIMaxSummaryBytes)156 assert.Len(t, got[0].Markdown, domain.CIMaxSummaryBytes)
157 }157 }
158+
159+// With a byte budget the files are sized before they are read: a summary that
160+// does not fit, and every one after it, comes back Omitted and is never opened.
161+func TestListReadsNoMoreThanTheBudget(t *testing.T) {
162+ ctx := t.Context()
163+ root := t.TempDir()
164+ s := cisummaries.New(root)
165+ require.NoError(t, s.Put(ctx, ref, 1, strings.Repeat("a", 100)))
166+ require.NoError(t, s.Put(ctx, ref, 2, strings.Repeat("b", 100)))
167+ require.NoError(t, s.Put(ctx, ref, 3, strings.Repeat("c", 10)))
168+ require.NoError(t, s.Put(ctx, ref, 4, "d"))
169+ dir := filepath.Join(root, "7", "7-0-0", "1")
170+ // A file that cannot be opened proves it was sized, not read.
171+ if os.Geteuid() != 0 {
172+ for _, n := range []string{"2.md", "3.md", "4.md"} {
173+ require.NoError(t, os.Chmod(filepath.Join(dir, n), 0))
174+ }
175+ }
176+
177+ got, more, err := s.List(ctx, ref, 20, 150)
178+ require.NoError(t, err)
179+ assert.False(t, more)
180+ require.Len(t, got, 4)
181+ assert.Equal(t, strings.Repeat("a", 100), got[0].Markdown)
182+ assert.False(t, got[0].Omitted)
183+ for _, o := range got[1:] {
184+ assert.True(t, o.Omitted, "step %d", o.Step)
185+ assert.Empty(t, o.Markdown)
186+ }
187+ assert.EqualValues(t, []int32{1, 2, 3, 4}, []int32{got[0].Step, got[1].Step, got[2].Step, got[3].Step}, "the steps are still listed")
188+}
189+
190+func TestListBudgetBoundaries(t *testing.T) {
191+ ctx := t.Context()
192+ s := cisummaries.New(t.TempDir())
193+ require.NoError(t, s.Put(ctx, ref, 1, "12345"))
194+ require.NoError(t, s.Put(ctx, ref, 2, "678"))
195+
196+ got, _, _ := s.List(ctx, ref, 20, 8)
197+ assert.False(t, got[0].Omitted || got[1].Omitted, "exactly the budget fits")
198+ got, _, _ = s.List(ctx, ref, 20, 7)
199+ assert.False(t, got[0].Omitted)
200+ assert.True(t, got[1].Omitted, "one byte over")
201+ got, _, _ = s.List(ctx, ref, 20, 4)
202+ assert.True(t, got[0].Omitted && got[1].Omitted, "the first does not fit")
203+ got, _, _ = s.List(ctx, ref, 20, 0)
204+ assert.False(t, got[0].Omitted || got[1].Omitted, "no budget, no bound")
205+
206+ // A smaller file after one that does not fit is still omitted: the order
207+ // of steps is kept and nothing is read past the first omission.
208+ require.NoError(t, s.Put(ctx, ref, 3, "x"))
209+ got, _, _ = s.List(ctx, ref, 20, 7)
210+ assert.True(t, got[2].Omitted)
211+}
M internal/adapters/rpc/ci.go +1 -1
@@ -367,7 +367,7 @@ func (s *runService) GetStepSummaries(ctx context.Context, req *connect.Request[
367367 if err != nil {
368368 return nil, ciError(ctx, err)
369369 }
370- res, err := s.runs.GetStepSummaries(ctx, caller(ctx), name, req.Msg.GetJobId(), req.Msg.GetAttempt())
370+ res, err := s.runs.GetStepSummaries(ctx, caller(ctx), name, req.Msg.GetJobId(), req.Msg.GetAttempt(), 0)
371371 if err != nil {
372372 return nil, ciError(ctx, err)
373373 }
@@ -367,7 +367,7 @@ func (s *runService) GetStepSummaries(ctx context.Context, req *connect.Request[
367 if err != nil {367 if err != nil {
368 return nil, ciError(ctx, err)368 return nil, ciError(ctx, err)
369 }369 }
370- res, err := s.runs.GetStepSummaries(ctx, caller(ctx), name, req.Msg.GetJobId(), req.Msg.GetAttempt())370+ res, err := s.runs.GetStepSummaries(ctx, caller(ctx), name, req.Msg.GetJobId(), req.Msg.GetAttempt(), 0)
371 if err != nil {371 if err != nil {
372 return nil, ciError(ctx, err)372 return nil, ciError(ctx, err)
373 }373 }
M internal/adapters/rpc/ci_test.go +1 -1
@@ -175,7 +175,7 @@ func TestRunServiceAnnotationsAndSummaries(t *testing.T) {
175175 Annotations: []ports.JobAnnotation{{JobID: "7-0-0", Annotation: domain.CIAnnotation{Level: domain.AnnotationWarning, Message: "m", File: "a.go", StartLine: 4, Step: 3}}},
176176 Truncated: true,
177177 }, nil).Once()
178- runs.EXPECT().GetStepSummaries(mock.Anything, domain.Owner(), repo, "7-0-0", int32(0)).Return(ports.SummaryList{
178+ runs.EXPECT().GetStepSummaries(mock.Anything, domain.Owner(), repo, "7-0-0", int32(0), int64(0)).Return(ports.SummaryList{
179179 Summaries: []domain.CIStepSummary{{Step: 2, Markdown: "# s"}}, Truncated: true,
180180 }, nil).Once()
181181 runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(8), int32(0), "").Return(ports.AnnotationList{}, domain.ErrNotFound).Once()
@@ -175,7 +175,7 @@ func TestRunServiceAnnotationsAndSummaries(t *testing.T) {
175 Annotations: []ports.JobAnnotation{{JobID: "7-0-0", Annotation: domain.CIAnnotation{Level: domain.AnnotationWarning, Message: "m", File: "a.go", StartLine: 4, Step: 3}}},175 Annotations: []ports.JobAnnotation{{JobID: "7-0-0", Annotation: domain.CIAnnotation{Level: domain.AnnotationWarning, Message: "m", File: "a.go", StartLine: 4, Step: 3}}},
176 Truncated: true,176 Truncated: true,
177 }, nil).Once()177 }, nil).Once()
178- runs.EXPECT().GetStepSummaries(mock.Anything, domain.Owner(), repo, "7-0-0", int32(0)).Return(ports.SummaryList{178+ runs.EXPECT().GetStepSummaries(mock.Anything, domain.Owner(), repo, "7-0-0", int32(0), int64(0)).Return(ports.SummaryList{
179 Summaries: []domain.CIStepSummary{{Step: 2, Markdown: "# s"}}, Truncated: true,179 Summaries: []domain.CIStepSummary{{Step: 2, Markdown: "# s"}}, Truncated: true,
180 }, nil).Once()180 }, nil).Once()
181 runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(8), int32(0), "").Return(ports.AnnotationList{}, domain.ErrNotFound).Once()181 runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(8), int32(0), "").Return(ports.AnnotationList{}, domain.ErrNotFound).Once()
M internal/adapters/web/actions.templ +5 -2
@@ -62,7 +62,7 @@ templ RunDetail(l Layout, p RunPage) {
6262 <div id="run-body">
6363 @RunBody(p)
6464 </div>
65- @RunSummaries(p.Summaries)
65+ @RunSummaries(p.Summaries, p.SummariesMore)
6666 }
6767 }
6868
@@ -181,7 +181,7 @@ templ JobAnnotations(anns []AnnotationView, more bool) {
181181 // RunSummaries is the job summaries of a run, one block per step that wrote
182182 // one. The Markdown is rendered by the README renderer: raw HTML dropped, URLs
183183 // filtered, images shown as their alt text.
184-templ RunSummaries(sums []JobSummaries) {
184+templ RunSummaries(sums []JobSummaries, more bool) {
185185 for _, j := range sums {
186186 <section class="summaries" aria-label={ "Summary of " + j.Job }>
187187 <h3>
@@ -207,6 +207,9 @@ templ RunSummaries(sums []JobSummaries) {
207207 }
208208 </section>
209209 }
210+ if more {
211+ <p class="mute">more summaries are not shown: the summaries of the page are over { humanSize(maxSummaryPageBytes) }.</p>
212+ }
210213 }
211214
212215 templ runRail(p RunPage) {
@@ -62,7 +62,7 @@ templ RunDetail(l Layout, p RunPage) {
62 <div id="run-body">62 <div id="run-body">
63 @RunBody(p)63 @RunBody(p)
64 </div>64 </div>
65- @RunSummaries(p.Summaries)65+ @RunSummaries(p.Summaries, p.SummariesMore)
66 }66 }
67 }67 }
68 68
@@ -181,7 +181,7 @@ templ JobAnnotations(anns []AnnotationView, more bool) {
181 // RunSummaries is the job summaries of a run, one block per step that wrote181 // RunSummaries is the job summaries of a run, one block per step that wrote
182 // one. The Markdown is rendered by the README renderer: raw HTML dropped, URLs182 // one. The Markdown is rendered by the README renderer: raw HTML dropped, URLs
183 // filtered, images shown as their alt text.183 // filtered, images shown as their alt text.
184-templ RunSummaries(sums []JobSummaries) {184+templ RunSummaries(sums []JobSummaries, more bool) {
185 for _, j := range sums {185 for _, j := range sums {
186 <section class="summaries" aria-label={ "Summary of " + j.Job }>186 <section class="summaries" aria-label={ "Summary of " + j.Job }>
187 <h3>187 <h3>
@@ -207,6 +207,9 @@ templ RunSummaries(sums []JobSummaries) {
207 }207 }
208 </section>208 </section>
209 }209 }
210+ if more {
211+ <p class="mute">more summaries are not shown: the summaries of the page are over { humanSize(maxSummaryPageBytes) }.</p>
212+ }
210 }213 }
211 214
212 templ runRail(p RunPage) {215 templ runRail(p RunPage) {
M internal/adapters/web/actions_pages.go +4 -2
@@ -179,7 +179,9 @@ type RunPage struct {
179179 AnnotationsMore bool
180180 // Summaries are the job summaries, rendered; only the full page has them.
181181 Summaries []JobSummaries
182- Active bool
182+ // SummariesMore says that some summaries are not shown (the page budget).
183+ SummariesMore bool
184+ Active bool
183185 // detail is what the page was built from.
184186 detail ports.RunDetail
185187 // Forms are the owner's actions; nil/empty when the visitor cannot.
@@ -294,7 +296,7 @@ func (s *Server) run(ctx context.Context, r *http.Request) (templ.Component, int
294296 }
295297 // The summaries are part of the page, not of the live body: the page
296298 // fetches itself again when the run completes (runBody), and they show then.
297- if p.Summaries, err = s.jobSummaries(ctx, d, p.detail.Run, p.detail.Jobs, p.Attempt == p.Latest); err != nil {
299+ if p.Summaries, p.SummariesMore, err = s.jobSummaries(ctx, d, p.detail.Run, p.detail.Jobs, p.Attempt == p.Latest, maxSummaryPageBytes); err != nil {
298300 return nil, 0, err
299301 }
300302 l := s.repoLayout(ctx, d, "actions")
@@ -179,7 +179,9 @@ type RunPage struct {
179 AnnotationsMore bool179 AnnotationsMore bool
180 // Summaries are the job summaries, rendered; only the full page has them.180 // Summaries are the job summaries, rendered; only the full page has them.
181 Summaries []JobSummaries181 Summaries []JobSummaries
182- Active bool182+ // SummariesMore says that some summaries are not shown (the page budget).
183+ SummariesMore bool
184+ Active bool
183 // detail is what the page was built from.185 // detail is what the page was built from.
184 detail ports.RunDetail186 detail ports.RunDetail
185 // Forms are the owner's actions; nil/empty when the visitor cannot.187 // Forms are the owner's actions; nil/empty when the visitor cannot.
@@ -294,7 +296,7 @@ func (s *Server) run(ctx context.Context, r *http.Request) (templ.Component, int
294 }296 }
295 // The summaries are part of the page, not of the live body: the page297 // The summaries are part of the page, not of the live body: the page
296 // fetches itself again when the run completes (runBody), and they show then.298 // fetches itself again when the run completes (runBody), and they show then.
297- if p.Summaries, err = s.jobSummaries(ctx, d, p.detail.Run, p.detail.Jobs, p.Attempt == p.Latest); err != nil {299+ if p.Summaries, p.SummariesMore, err = s.jobSummaries(ctx, d, p.detail.Run, p.detail.Jobs, p.Attempt == p.Latest, maxSummaryPageBytes); err != nil {
298 return nil, 0, err300 return nil, 0, err
299 }301 }
300 l := s.repoLayout(ctx, d, "actions")302 l := s.repoLayout(ctx, d, "actions")
M internal/adapters/web/actions_results.go +34 -10
@@ -21,7 +21,7 @@ import (
2121
2222 // maxSummaryPageBytes bounds the Markdown rendered in one page, all summaries
2323 // together; the ones past it are not shown, with a note.
24-const maxSummaryPageBytes = 4 << 20
24+const maxSummaryPageBytes int64 = 4 << 20
2525
2626 // AnnotationView is an annotation as shown.
2727 type AnnotationView struct {
@@ -156,17 +156,21 @@ type JobSummaries struct {
156156 }
157157
158158 // jobSummaries reads and renders the summaries of the jobs of a run, in the
159-// order of the jobs, within maxSummaryPageBytes of Markdown.
160-func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun, jobs []ports.JobView, linkJobs bool) ([]JobSummaries, error) {
161- budget := maxSummaryPageBytes
162- var out []JobSummaries
159+// order of the jobs, within budget bytes of Markdown. The budget goes down as
160+// summaries are read and is handed to the use case, which sizes the files
161+// before it reads them: a page view reads at most budget bytes. Once it is used
162+// up no further job is asked, and more is true: some summaries are not shown.
163+func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun, jobs []ports.JobView, linkJobs bool, budget int64) (out []JobSummaries, more bool, err error) {
163164 for _, j := range jobs {
164165 if j.Phase != domain.CIPhaseRunning && !j.Ended() {
165166 continue // no step has run
166167 }
167- res, err := s.cfg.Runs.GetStepSummaries(ctx, principal(ctx), d.repo.Name, j.ID, j.Attempt)
168+ if budget <= 0 {
169+ return out, true, nil
170+ }
171+ res, err := s.cfg.Runs.GetStepSummaries(ctx, principal(ctx), d.repo.Name, j.ID, j.Attempt, budget)
168172 if err != nil {
169- return nil, err
173+ return nil, false, err
170174 }
171175 if len(res.Summaries) == 0 {
172176 continue
@@ -179,12 +183,14 @@ func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun
179183 name = st.name
180184 }
181185 v := SummaryView{Step: name}
182- if len(sm.Markdown) > budget {
186+ if sm.Omitted {
183187 v.Note = "this summary is not shown: the summaries of the page are over " + humanSize(maxSummaryPageBytes) + "."
188+ more = true
189+ budget = 0 // everything after an omitted summary is omitted too
184190 js.Steps = append(js.Steps, v)
185191 continue
186192 }
187- budget -= len(sm.Markdown)
193+ budget -= int64(len(sm.Markdown))
188194 prefix := headingIDPrefix + "s" + j.ID + "-" + strconv.Itoa(int(sm.Step)) + "-"
189195 h, _, err := s.md.renderWithIDs([]byte(sm.Markdown), d.name(), run.Sha, "", prefix)
190196 if err != nil {
@@ -196,7 +202,25 @@ func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun
196202 }
197203 out = append(out, js)
198204 }
199- return out, nil
205+ return out, more, nil
206+}
207+
208+// jobRunSha is the commit of the run a job belongs to, for the live fragments of
209+// its page. A job id that is not one, or a run that cannot be read, is not found.
210+func (s *Server) jobRunSha(ctx context.Context, repo, jobID string) (string, error) {
211+ name, err := domain.ParseRepoName(repo)
212+ if err != nil {
213+ return "", domain.ErrNotFound
214+ }
215+ runID, _, _, ok := domain.ParseJobID(jobID)
216+ if !ok {
217+ return "", domain.ErrNotFound
218+ }
219+ d, err := s.cfg.Runs.GetRun(ctx, principal(ctx), name, runID, 0)
220+ if err != nil {
221+ return "", err
222+ }
223+ return d.Run.Sha, nil
200224 }
201225
202226 // runAnnotations reads the annotations of a run attempt and builds their views.
@@ -21,7 +21,7 @@ import (
21 21
22 // maxSummaryPageBytes bounds the Markdown rendered in one page, all summaries22 // maxSummaryPageBytes bounds the Markdown rendered in one page, all summaries
23 // together; the ones past it are not shown, with a note.23 // together; the ones past it are not shown, with a note.
24-const maxSummaryPageBytes = 4 << 2024+const maxSummaryPageBytes int64 = 4 << 20
25 25
26 // AnnotationView is an annotation as shown.26 // AnnotationView is an annotation as shown.
27 type AnnotationView struct {27 type AnnotationView struct {
@@ -156,17 +156,21 @@ type JobSummaries struct {
156 }156 }
157 157
158 // jobSummaries reads and renders the summaries of the jobs of a run, in the158 // jobSummaries reads and renders the summaries of the jobs of a run, in the
159-// order of the jobs, within maxSummaryPageBytes of Markdown.159+// order of the jobs, within budget bytes of Markdown. The budget goes down as
160-func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun, jobs []ports.JobView, linkJobs bool) ([]JobSummaries, error) {160+// summaries are read and is handed to the use case, which sizes the files
161- budget := maxSummaryPageBytes161+// before it reads them: a page view reads at most budget bytes. Once it is used
162- var out []JobSummaries162+// up no further job is asked, and more is true: some summaries are not shown.
163+func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun, jobs []ports.JobView, linkJobs bool, budget int64) (out []JobSummaries, more bool, err error) {
163 for _, j := range jobs {164 for _, j := range jobs {
164 if j.Phase != domain.CIPhaseRunning && !j.Ended() {165 if j.Phase != domain.CIPhaseRunning && !j.Ended() {
165 continue // no step has run166 continue // no step has run
166 }167 }
167- res, err := s.cfg.Runs.GetStepSummaries(ctx, principal(ctx), d.repo.Name, j.ID, j.Attempt)168+ if budget <= 0 {
169+ return out, true, nil
170+ }
171+ res, err := s.cfg.Runs.GetStepSummaries(ctx, principal(ctx), d.repo.Name, j.ID, j.Attempt, budget)
168 if err != nil {172 if err != nil {
169- return nil, err173+ return nil, false, err
170 }174 }
171 if len(res.Summaries) == 0 {175 if len(res.Summaries) == 0 {
172 continue176 continue
@@ -179,12 +183,14 @@ func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun
179 name = st.name183 name = st.name
180 }184 }
181 v := SummaryView{Step: name}185 v := SummaryView{Step: name}
182- if len(sm.Markdown) > budget {186+ if sm.Omitted {
183 v.Note = "this summary is not shown: the summaries of the page are over " + humanSize(maxSummaryPageBytes) + "."187 v.Note = "this summary is not shown: the summaries of the page are over " + humanSize(maxSummaryPageBytes) + "."
188+ more = true
189+ budget = 0 // everything after an omitted summary is omitted too
184 js.Steps = append(js.Steps, v)190 js.Steps = append(js.Steps, v)
185 continue191 continue
186 }192 }
187- budget -= len(sm.Markdown)193+ budget -= int64(len(sm.Markdown))
188 prefix := headingIDPrefix + "s" + j.ID + "-" + strconv.Itoa(int(sm.Step)) + "-"194 prefix := headingIDPrefix + "s" + j.ID + "-" + strconv.Itoa(int(sm.Step)) + "-"
189 h, _, err := s.md.renderWithIDs([]byte(sm.Markdown), d.name(), run.Sha, "", prefix)195 h, _, err := s.md.renderWithIDs([]byte(sm.Markdown), d.name(), run.Sha, "", prefix)
190 if err != nil {196 if err != nil {
@@ -196,7 +202,25 @@ func (s *Server) jobSummaries(ctx context.Context, d *repoData, run domain.CIRun
196 }202 }
197 out = append(out, js)203 out = append(out, js)
198 }204 }
199- return out, nil205+ return out, more, nil
206+}
207+
208+// jobRunSha is the commit of the run a job belongs to, for the live fragments of
209+// its page. A job id that is not one, or a run that cannot be read, is not found.
210+func (s *Server) jobRunSha(ctx context.Context, repo, jobID string) (string, error) {
211+ name, err := domain.ParseRepoName(repo)
212+ if err != nil {
213+ return "", domain.ErrNotFound
214+ }
215+ runID, _, _, ok := domain.ParseJobID(jobID)
216+ if !ok {
217+ return "", domain.ErrNotFound
218+ }
219+ d, err := s.cfg.Runs.GetRun(ctx, principal(ctx), name, runID, 0)
220+ if err != nil {
221+ return "", err
222+ }
223+ return d.Run.Sha, nil
200 }224 }
201 225
202 // runAnnotations reads the annotations of a run attempt and builds their views.226 // runAnnotations reads the annotations of a run attempt and builds their views.
M internal/adapters/web/actions_results_test.go +108 -1
@@ -48,7 +48,7 @@ func TestSummariesAreRenderedSafely(t *testing.T) {
4848 Jobs: []ports.JobView{job}, LatestAttempt: 1,
4949 }, nil)
5050 runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(1), int32(1), "").Return(ports.AnnotationList{}, nil)
51- runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, jobID, int32(1)).Return(ports.SummaryList{
51+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, jobID, int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
5252 Summaries: []domain.CIStepSummary{{Step: 2, Markdown: md}, {Step: 7, Markdown: "second"}}, Truncated: true,
5353 }, nil)
5454 browse := mocks.NewBrowseService(t)
@@ -128,3 +128,110 @@ func TestRepoPath(t *testing.T) {
128128 assert.False(t, repoPath(bad), "%q", bad)
129129 }
130130 }
131+
132+func endedJob(t *testing.T, repo domain.RepoName, id string) ports.JobView {
133+ t.Helper()
134+ return ports.JobView{CIJob: domain.CIJob{
135+ ID: id, RunID: 1, Attempt: 1, RunAttempt: 1, Repo: repo, Key: "k", Name: "job " + id,
136+ Phase: domain.CIPhaseEnded, Conclusion: domain.CISuccess,
137+ }}
138+}
139+
140+func runPageServer(t *testing.T, repo domain.RepoName, jobs []ports.JobView, runs *mocks.RunService) *httptest.ResponseRecorder {
141+ t.Helper()
142+ runs.EXPECT().GetRun(mock.Anything, mock.Anything, repo, int64(1), int32(0)).Return(ports.RunDetail{
143+ Run: domain.CIRun{Repo: repo, ID: 1, Attempt: 1, Number: 1, WorkflowName: "ci", Sha: strings.Repeat("a", 40), Status: domain.CICompleted, Conclusion: domain.CISuccess},
144+ Jobs: jobs, LatestAttempt: 1,
145+ }, nil)
146+ runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(1), int32(1), "").Return(ports.AnnotationList{}, nil)
147+ browse := mocks.NewBrowseService(t)
148+ browse.EXPECT().ListRefs(mock.Anything, mock.Anything, repo, mock.Anything).Return(ports.RefPage{}, nil).Maybe()
149+ srv := newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, repo.String())), Browse: browse, Runs: runs})
150+ rec := httptest.NewRecorder()
151+ srv.ServeHTTP(rec, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/proj/actions/runs/1", nil))
152+ require.Equal(t, http.StatusOK, rec.Code)
153+ return rec
154+}
155+
156+// GO-001: once the render budget is used up no other job is asked for its
157+// summaries, and the page says that some are not shown.
158+func TestSummariesStopAtTheBudget(t *testing.T) {
159+ t.Parallel()
160+ repo := repoName(t, "proj")
161+ jobs := []ports.JobView{endedJob(t, repo, "1-0-0"), endedJob(t, repo, "1-1-0"), endedJob(t, repo, "1-2-0")}
162+ runs := mocks.NewRunService(t)
163+ // The first job takes the whole budget; the other two are never asked
164+ // (the mock fails the test on a call it does not expect).
165+ full := strings.Repeat("x", int(maxSummaryPageBytes))
166+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-0-0", int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
167+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: full}},
168+ }, nil).Once()
169+ body := runPageServer(t, repo, jobs, runs).Body.String()
170+ assert.Contains(t, body, "more summaries are not shown")
171+ assert.Contains(t, body, `aria-label="Summary of job 1-0-0"`)
172+ assert.NotContains(t, body, `aria-label="Summary of job 1-1-0"`, "no summary of the other jobs")
173+}
174+
175+// The budget handed to the next job is what is left; a summary the store left
176+// unread is shown as such, and so are the ones after it.
177+func TestSummariesPassWhatIsLeftOfTheBudget(t *testing.T) {
178+ t.Parallel()
179+ repo := repoName(t, "proj")
180+ jobs := []ports.JobView{endedJob(t, repo, "1-0-0"), endedJob(t, repo, "1-1-0"), endedJob(t, repo, "1-2-0")}
181+ runs := mocks.NewRunService(t)
182+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-0-0", int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
183+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: "hello"}},
184+ }, nil).Once()
185+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-1-0", int32(1), maxSummaryPageBytes-5).Return(ports.SummaryList{
186+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: "world"}, {Step: 2, Omitted: true}, {Step: 3, Omitted: true}},
187+ }, nil).Once()
188+ body := runPageServer(t, repo, jobs, runs).Body.String()
189+ assert.Contains(t, body, "hello")
190+ assert.Contains(t, body, "world")
191+ assert.Equal(t, 2, strings.Count(body, "this summary is not shown"))
192+ assert.Contains(t, body, "more summaries are not shown")
193+ assert.NotContains(t, body, `aria-label="Summary of job 1-2-0"`, "the third job is not asked once an omission happened")
194+}
195+
196+func TestNoNoteWhenEverythingFits(t *testing.T) {
197+ t.Parallel()
198+ repo := repoName(t, "proj")
199+ runs := mocks.NewRunService(t)
200+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-0-0", int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
201+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: "small"}},
202+ }, nil).Once()
203+ body := runPageServer(t, repo, []ports.JobView{endedJob(t, repo, "1-0-0")}, runs).Body.String()
204+ assert.Contains(t, body, "small")
205+ assert.NotContains(t, body, "not shown")
206+}
207+
208+// GO-002: a live job update reads the job and its annotations, never the run;
209+// the commit comes from the stream, and a stream without it has the page fetch
210+// itself again.
211+func TestJobStateDoesNotReadTheRun(t *testing.T) {
212+ t.Parallel()
213+ const jobID = "1-0-0"
214+ repo := repoName(t, "proj")
215+ job := endedJob(t, repo, jobID)
216+ job.Steps = []domain.CIStep{{Number: 1, Name: "s", State: domain.CIStepCompleted, Conclusion: domain.CISuccess, HasLines: true}}
217+ job.Lines = 2
218+ sha := strings.Repeat("c", 40)
219+
220+ runs := mocks.NewRunService(t) // GetRun is not expected
221+ runs.EXPECT().GetJob(mock.Anything, mock.Anything, repo, jobID, int32(0)).Return(job, nil)
222+ runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(1), int32(0), jobID).Return(ports.AnnotationList{
223+ Annotations: []ports.JobAnnotation{{JobID: jobID, Annotation: domain.CIAnnotation{Level: domain.AnnotationWarning, Message: "m", File: "a.go", StartLine: 2, Step: 1}}},
224+ }, nil)
225+ browse := mocks.NewBrowseService(t)
226+ browse.EXPECT().ListRefs(mock.Anything, mock.Anything, repo, mock.Anything).Return(ports.RefPage{}, nil).Maybe()
227+ srv := newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, "proj")), Browse: browse, Runs: runs})
228+
229+ frags, err := (&liveStream{s: srv, repo: "proj", view: "job", job: jobID, sha: sha}).jobState(context.Background())
230+ require.NoError(t, err)
231+ require.Len(t, frags, 3)
232+ assert.Contains(t, frags[2].html, `href="/proj/blob/`+sha+`/a.go#L2"`, "the annotation links to the commit the stream was set up with")
233+
234+ frags, err = (&liveStream{s: srv, repo: "proj", view: "job", job: jobID}).jobState(context.Background())
235+ require.NoError(t, err)
236+ assert.Equal(t, gap, frags)
237+}
@@ -48,7 +48,7 @@ func TestSummariesAreRenderedSafely(t *testing.T) {
48 Jobs: []ports.JobView{job}, LatestAttempt: 1,48 Jobs: []ports.JobView{job}, LatestAttempt: 1,
49 }, nil)49 }, nil)
50 runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(1), int32(1), "").Return(ports.AnnotationList{}, nil)50 runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(1), int32(1), "").Return(ports.AnnotationList{}, nil)
51- runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, jobID, int32(1)).Return(ports.SummaryList{51+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, jobID, int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
52 Summaries: []domain.CIStepSummary{{Step: 2, Markdown: md}, {Step: 7, Markdown: "second"}}, Truncated: true,52 Summaries: []domain.CIStepSummary{{Step: 2, Markdown: md}, {Step: 7, Markdown: "second"}}, Truncated: true,
53 }, nil)53 }, nil)
54 browse := mocks.NewBrowseService(t)54 browse := mocks.NewBrowseService(t)
@@ -128,3 +128,110 @@ func TestRepoPath(t *testing.T) {
128 assert.False(t, repoPath(bad), "%q", bad)128 assert.False(t, repoPath(bad), "%q", bad)
129 }129 }
130 }130 }
131+
132+func endedJob(t *testing.T, repo domain.RepoName, id string) ports.JobView {
133+ t.Helper()
134+ return ports.JobView{CIJob: domain.CIJob{
135+ ID: id, RunID: 1, Attempt: 1, RunAttempt: 1, Repo: repo, Key: "k", Name: "job " + id,
136+ Phase: domain.CIPhaseEnded, Conclusion: domain.CISuccess,
137+ }}
138+}
139+
140+func runPageServer(t *testing.T, repo domain.RepoName, jobs []ports.JobView, runs *mocks.RunService) *httptest.ResponseRecorder {
141+ t.Helper()
142+ runs.EXPECT().GetRun(mock.Anything, mock.Anything, repo, int64(1), int32(0)).Return(ports.RunDetail{
143+ Run: domain.CIRun{Repo: repo, ID: 1, Attempt: 1, Number: 1, WorkflowName: "ci", Sha: strings.Repeat("a", 40), Status: domain.CICompleted, Conclusion: domain.CISuccess},
144+ Jobs: jobs, LatestAttempt: 1,
145+ }, nil)
146+ runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(1), int32(1), "").Return(ports.AnnotationList{}, nil)
147+ browse := mocks.NewBrowseService(t)
148+ browse.EXPECT().ListRefs(mock.Anything, mock.Anything, repo, mock.Anything).Return(ports.RefPage{}, nil).Maybe()
149+ srv := newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, repo.String())), Browse: browse, Runs: runs})
150+ rec := httptest.NewRecorder()
151+ srv.ServeHTTP(rec, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/proj/actions/runs/1", nil))
152+ require.Equal(t, http.StatusOK, rec.Code)
153+ return rec
154+}
155+
156+// GO-001: once the render budget is used up no other job is asked for its
157+// summaries, and the page says that some are not shown.
158+func TestSummariesStopAtTheBudget(t *testing.T) {
159+ t.Parallel()
160+ repo := repoName(t, "proj")
161+ jobs := []ports.JobView{endedJob(t, repo, "1-0-0"), endedJob(t, repo, "1-1-0"), endedJob(t, repo, "1-2-0")}
162+ runs := mocks.NewRunService(t)
163+ // The first job takes the whole budget; the other two are never asked
164+ // (the mock fails the test on a call it does not expect).
165+ full := strings.Repeat("x", int(maxSummaryPageBytes))
166+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-0-0", int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
167+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: full}},
168+ }, nil).Once()
169+ body := runPageServer(t, repo, jobs, runs).Body.String()
170+ assert.Contains(t, body, "more summaries are not shown")
171+ assert.Contains(t, body, `aria-label="Summary of job 1-0-0"`)
172+ assert.NotContains(t, body, `aria-label="Summary of job 1-1-0"`, "no summary of the other jobs")
173+}
174+
175+// The budget handed to the next job is what is left; a summary the store left
176+// unread is shown as such, and so are the ones after it.
177+func TestSummariesPassWhatIsLeftOfTheBudget(t *testing.T) {
178+ t.Parallel()
179+ repo := repoName(t, "proj")
180+ jobs := []ports.JobView{endedJob(t, repo, "1-0-0"), endedJob(t, repo, "1-1-0"), endedJob(t, repo, "1-2-0")}
181+ runs := mocks.NewRunService(t)
182+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-0-0", int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
183+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: "hello"}},
184+ }, nil).Once()
185+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-1-0", int32(1), maxSummaryPageBytes-5).Return(ports.SummaryList{
186+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: "world"}, {Step: 2, Omitted: true}, {Step: 3, Omitted: true}},
187+ }, nil).Once()
188+ body := runPageServer(t, repo, jobs, runs).Body.String()
189+ assert.Contains(t, body, "hello")
190+ assert.Contains(t, body, "world")
191+ assert.Equal(t, 2, strings.Count(body, "this summary is not shown"))
192+ assert.Contains(t, body, "more summaries are not shown")
193+ assert.NotContains(t, body, `aria-label="Summary of job 1-2-0"`, "the third job is not asked once an omission happened")
194+}
195+
196+func TestNoNoteWhenEverythingFits(t *testing.T) {
197+ t.Parallel()
198+ repo := repoName(t, "proj")
199+ runs := mocks.NewRunService(t)
200+ runs.EXPECT().GetStepSummaries(mock.Anything, mock.Anything, repo, "1-0-0", int32(1), maxSummaryPageBytes).Return(ports.SummaryList{
201+ Summaries: []domain.CIStepSummary{{Step: 1, Markdown: "small"}},
202+ }, nil).Once()
203+ body := runPageServer(t, repo, []ports.JobView{endedJob(t, repo, "1-0-0")}, runs).Body.String()
204+ assert.Contains(t, body, "small")
205+ assert.NotContains(t, body, "not shown")
206+}
207+
208+// GO-002: a live job update reads the job and its annotations, never the run;
209+// the commit comes from the stream, and a stream without it has the page fetch
210+// itself again.
211+func TestJobStateDoesNotReadTheRun(t *testing.T) {
212+ t.Parallel()
213+ const jobID = "1-0-0"
214+ repo := repoName(t, "proj")
215+ job := endedJob(t, repo, jobID)
216+ job.Steps = []domain.CIStep{{Number: 1, Name: "s", State: domain.CIStepCompleted, Conclusion: domain.CISuccess, HasLines: true}}
217+ job.Lines = 2
218+ sha := strings.Repeat("c", 40)
219+
220+ runs := mocks.NewRunService(t) // GetRun is not expected
221+ runs.EXPECT().GetJob(mock.Anything, mock.Anything, repo, jobID, int32(0)).Return(job, nil)
222+ runs.EXPECT().ListAnnotations(mock.Anything, mock.Anything, repo, int64(1), int32(0), jobID).Return(ports.AnnotationList{
223+ Annotations: []ports.JobAnnotation{{JobID: jobID, Annotation: domain.CIAnnotation{Level: domain.AnnotationWarning, Message: "m", File: "a.go", StartLine: 2, Step: 1}}},
224+ }, nil)
225+ browse := mocks.NewBrowseService(t)
226+ browse.EXPECT().ListRefs(mock.Anything, mock.Anything, repo, mock.Anything).Return(ports.RefPage{}, nil).Maybe()
227+ srv := newTestServer(t, Config{Repos: reposServing(t, publicRepo(t, "proj")), Browse: browse, Runs: runs})
228+
229+ frags, err := (&liveStream{s: srv, repo: "proj", view: "job", job: jobID, sha: sha}).jobState(context.Background())
230+ require.NoError(t, err)
231+ require.Len(t, frags, 3)
232+ assert.Contains(t, frags[2].html, `href="/proj/blob/`+sha+`/a.go#L2"`, "the annotation links to the commit the stream was set up with")
233+
234+ frags, err = (&liveStream{s: srv, repo: "proj", view: "job", job: jobID}).jobState(context.Background())
235+ require.NoError(t, err)
236+ assert.Equal(t, gap, frags)
237+}
M internal/adapters/web/actions_templ.go +355 -336
@@ -495,7 +495,7 @@ func RunDetail(l Layout, p RunPage) templ.Component {
495495 if templ_7745c5c3_Err != nil {
496496 return templ_7745c5c3_Err
497497 }
498- templ_7745c5c3_Err = RunSummaries(p.Summaries).Render(ctx, templ_7745c5c3_Buffer)
498+ templ_7745c5c3_Err = RunSummaries(p.Summaries, p.SummariesMore).Render(ctx, templ_7745c5c3_Buffer)
499499 if templ_7745c5c3_Err != nil {
500500 return templ_7745c5c3_Err
501501 }
@@ -1271,7 +1271,7 @@ func JobAnnotations(anns []AnnotationView, more bool) templ.Component {
12711271 // RunSummaries is the job summaries of a run, one block per step that wrote
12721272 // one. The Markdown is rendered by the README renderer: raw HTML dropped, URLs
12731273 // filtered, images shown as their alt text.
1274-func RunSummaries(sums []JobSummaries) templ.Component {
1274+func RunSummaries(sums []JobSummaries, more bool) templ.Component {
12751275 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
12761276 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
12771277 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -1431,6 +1431,25 @@ func RunSummaries(sums []JobSummaries) templ.Component {
14311431 return templ_7745c5c3_Err
14321432 }
14331433 }
1434+ if more {
1435+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<p class=\"mute\">more summaries are not shown: the summaries of the page are over ")
1436+ if templ_7745c5c3_Err != nil {
1437+ return templ_7745c5c3_Err
1438+ }
1439+ var templ_7745c5c3_Var76 string
1440+ templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(humanSize(maxSummaryPageBytes))
1441+ if templ_7745c5c3_Err != nil {
1442+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 211, Col: 115}
1443+ }
1444+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76))
1445+ if templ_7745c5c3_Err != nil {
1446+ return templ_7745c5c3_Err
1447+ }
1448+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, ".</p>")
1449+ if templ_7745c5c3_Err != nil {
1450+ return templ_7745c5c3_Err
1451+ }
1452+ }
14341453 return nil
14351454 })
14361455 }
@@ -1451,256 +1470,256 @@ func runRail(p RunPage) templ.Component {
14511470 }()
14521471 }
14531472 ctx = templ.InitializeContext(ctx)
1454- templ_7745c5c3_Var76 := templ.GetChildren(ctx)
1455- if templ_7745c5c3_Var76 == nil {
1456- templ_7745c5c3_Var76 = templ.NopComponent
1473+ templ_7745c5c3_Var77 := templ.GetChildren(ctx)
1474+ if templ_7745c5c3_Var77 == nil {
1475+ templ_7745c5c3_Var77 = templ.NopComponent
14571476 }
14581477 ctx = templ.ClearChildren(ctx)
1459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<section aria-labelledby=\"r-run\"><h2 id=\"r-run\">Run</h2><dl class=\"kv\"><dt>result</dt><dd>")
1478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "<section aria-labelledby=\"r-run\"><h2 id=\"r-run\">Run</h2><dl class=\"kv\"><dt>result</dt><dd>")
14601479 if templ_7745c5c3_Err != nil {
14611480 return templ_7745c5c3_Err
14621481 }
1463- var templ_7745c5c3_Var77 string
1464- templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(p.State)
1482+ var templ_7745c5c3_Var78 string
1483+ templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(p.State)
14651484 if templ_7745c5c3_Err != nil {
1466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 217, Col: 16}
1467- }
1468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77))
1469- if templ_7745c5c3_Err != nil {
1470- return templ_7745c5c3_Err
1471- }
1472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</dd><dt>commit</dt><dd><a href=\"")
1473- if templ_7745c5c3_Err != nil {
1474- return templ_7745c5c3_Err
1475- }
1476- var templ_7745c5c3_Var78 templ.SafeURL
1477- templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CHref))
1478- if templ_7745c5c3_Err != nil {
1479- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 219, Col: 39}
1485+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 220, Col: 16}
14801486 }
14811487 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78))
14821488 if templ_7745c5c3_Err != nil {
14831489 return templ_7745c5c3_Err
14841490 }
1485- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "\">")
1491+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</dd><dt>commit</dt><dd><a href=\"")
14861492 if templ_7745c5c3_Err != nil {
14871493 return templ_7745c5c3_Err
14881494 }
1489- var templ_7745c5c3_Var79 string
1490- templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinStringErrs(p.Short)
1495+ var templ_7745c5c3_Var79 templ.SafeURL
1496+ templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CHref))
14911497 if templ_7745c5c3_Err != nil {
1492- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 219, Col: 51}
1498+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 222, Col: 39}
14931499 }
14941500 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var79))
14951501 if templ_7745c5c3_Err != nil {
14961502 return templ_7745c5c3_Err
14971503 }
1498- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</a></dd><dt>ref</dt><dd><a href=\"")
1504+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "\">")
14991505 if templ_7745c5c3_Err != nil {
15001506 return templ_7745c5c3_Err
15011507 }
1502- var templ_7745c5c3_Var80 templ.SafeURL
1503- templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RefHref))
1508+ var templ_7745c5c3_Var80 string
1509+ templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinStringErrs(p.Short)
15041510 if templ_7745c5c3_Err != nil {
1505- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 221, Col: 41}
1511+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 222, Col: 51}
15061512 }
15071513 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var80))
15081514 if templ_7745c5c3_Err != nil {
15091515 return templ_7745c5c3_Err
15101516 }
1511- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "\">")
1517+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</a></dd><dt>ref</dt><dd><a href=\"")
15121518 if templ_7745c5c3_Err != nil {
15131519 return templ_7745c5c3_Err
15141520 }
1515- var templ_7745c5c3_Var81 string
1516- templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinStringErrs(p.Ref)
1521+ var templ_7745c5c3_Var81 templ.SafeURL
1522+ templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RefHref))
15171523 if templ_7745c5c3_Err != nil {
1518- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 221, Col: 51}
1524+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 224, Col: 41}
15191525 }
15201526 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81))
15211527 if templ_7745c5c3_Err != nil {
15221528 return templ_7745c5c3_Err
15231529 }
1524- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</a></dd><dt>actor</dt><dd>")
1530+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "\">")
15251531 if templ_7745c5c3_Err != nil {
15261532 return templ_7745c5c3_Err
15271533 }
15281534 var templ_7745c5c3_Var82 string
1529- templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(p.Actor)
1535+ templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(p.Ref)
15301536 if templ_7745c5c3_Err != nil {
1531- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 223, Col: 16}
1537+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 224, Col: 51}
15321538 }
15331539 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82))
15341540 if templ_7745c5c3_Err != nil {
15351541 return templ_7745c5c3_Err
15361542 }
1537- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "</dd><dt>attempt</dt><dd>")
1543+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "</a></dd><dt>actor</dt><dd>")
15381544 if templ_7745c5c3_Err != nil {
15391545 return templ_7745c5c3_Err
15401546 }
15411547 var templ_7745c5c3_Var83 string
1542- templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Attempt)))
1548+ templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(p.Actor)
15431549 if templ_7745c5c3_Err != nil {
1544- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 225, Col: 29}
1550+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 226, Col: 16}
15451551 }
15461552 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))
15471553 if templ_7745c5c3_Err != nil {
15481554 return templ_7745c5c3_Err
15491555 }
1550- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, " of ")
1556+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "</dd><dt>attempt</dt><dd>")
15511557 if templ_7745c5c3_Err != nil {
15521558 return templ_7745c5c3_Err
15531559 }
15541560 var templ_7745c5c3_Var84 string
1555- templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Latest)))
1561+ templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Attempt)))
15561562 if templ_7745c5c3_Err != nil {
1557- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 225, Col: 56}
1563+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 228, Col: 29}
15581564 }
15591565 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84))
15601566 if templ_7745c5c3_Err != nil {
15611567 return templ_7745c5c3_Err
15621568 }
1563- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "</dd></dl>")
1569+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, " of ")
1570+ if templ_7745c5c3_Err != nil {
1571+ return templ_7745c5c3_Err
1572+ }
1573+ var templ_7745c5c3_Var85 string
1574+ templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Latest)))
1575+ if templ_7745c5c3_Err != nil {
1576+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 228, Col: 56}
1577+ }
1578+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))
1579+ if templ_7745c5c3_Err != nil {
1580+ return templ_7745c5c3_Err
1581+ }
1582+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "</dd></dl>")
15641583 if templ_7745c5c3_Err != nil {
15651584 return templ_7745c5c3_Err
15661585 }
15671586 if len(p.Attempts) > 0 {
1568- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<ul class=\"attempts\">")
1587+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<ul class=\"attempts\">")
15691588 if templ_7745c5c3_Err != nil {
15701589 return templ_7745c5c3_Err
15711590 }
15721591 for _, a := range p.Attempts {
1573- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "<li>")
1592+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<li>")
15741593 if templ_7745c5c3_Err != nil {
15751594 return templ_7745c5c3_Err
15761595 }
15771596 if a.Current {
1578- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<span aria-current=\"page\">")
1597+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "<span aria-current=\"page\">")
15791598 if templ_7745c5c3_Err != nil {
15801599 return templ_7745c5c3_Err
15811600 }
1582- var templ_7745c5c3_Var85 string
1583- templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)
1601+ var templ_7745c5c3_Var86 string
1602+ templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)
15841603 if templ_7745c5c3_Err != nil {
1585- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 232, Col: 42}
1604+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 235, Col: 42}
15861605 }
1587- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))
1606+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))
15881607 if templ_7745c5c3_Err != nil {
15891608 return templ_7745c5c3_Err
15901609 }
1591- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "</span>")
1610+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "</span>")
15921611 if templ_7745c5c3_Err != nil {
15931612 return templ_7745c5c3_Err
15941613 }
15951614 } else {
1596- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "<a href=\"")
1615+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "<a href=\"")
15971616 if templ_7745c5c3_Err != nil {
15981617 return templ_7745c5c3_Err
15991618 }
1600- var templ_7745c5c3_Var86 templ.SafeURL
1601- templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(a.Href))
1619+ var templ_7745c5c3_Var87 templ.SafeURL
1620+ templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(a.Href))
16021621 if templ_7745c5c3_Err != nil {
1603- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 234, Col: 38}
1622+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 237, Col: 38}
16041623 }
1605- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))
1624+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87))
16061625 if templ_7745c5c3_Err != nil {
16071626 return templ_7745c5c3_Err
16081627 }
1609- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "\">")
1628+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "\">")
16101629 if templ_7745c5c3_Err != nil {
16111630 return templ_7745c5c3_Err
16121631 }
1613- var templ_7745c5c3_Var87 string
1614- templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)
1632+ var templ_7745c5c3_Var88 string
1633+ templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)
16151634 if templ_7745c5c3_Err != nil {
1616- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 234, Col: 50}
1635+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 237, Col: 50}
16171636 }
1618- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87))
1637+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))
16191638 if templ_7745c5c3_Err != nil {
16201639 return templ_7745c5c3_Err
16211640 }
1622- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</a>")
1641+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "</a>")
16231642 if templ_7745c5c3_Err != nil {
16241643 return templ_7745c5c3_Err
16251644 }
16261645 }
1627- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "</li>")
1646+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</li>")
16281647 if templ_7745c5c3_Err != nil {
16291648 return templ_7745c5c3_Err
16301649 }
16311650 }
1632- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "</ul>")
1651+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "</ul>")
16331652 if templ_7745c5c3_Err != nil {
16341653 return templ_7745c5c3_Err
16351654 }
16361655 }
1637- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</section>")
1656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "</section>")
16381657 if templ_7745c5c3_Err != nil {
16391658 return templ_7745c5c3_Err
16401659 }
16411660 if p.CancelAction != "" || p.RerunAction != "" {
1642- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<section aria-labelledby=\"r-act\" class=\"decide\"><h2 id=\"r-act\">Actions</h2><div class=\"d-btns\">")
1661+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "<section aria-labelledby=\"r-act\" class=\"decide\"><h2 id=\"r-act\">Actions</h2><div class=\"d-btns\">")
16431662 if templ_7745c5c3_Err != nil {
16441663 return templ_7745c5c3_Err
16451664 }
16461665 if p.CancelAction != "" {
1647- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "<form method=\"post\" action=\"")
1666+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "<form method=\"post\" action=\"")
16481667 if templ_7745c5c3_Err != nil {
16491668 return templ_7745c5c3_Err
16501669 }
1651- var templ_7745c5c3_Var88 templ.SafeURL
1652- templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CancelAction))
1670+ var templ_7745c5c3_Var89 templ.SafeURL
1671+ templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CancelAction))
16531672 if templ_7745c5c3_Err != nil {
1654- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 246, Col: 63}
1673+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 249, Col: 63}
16551674 }
1656- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))
1675+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89))
16571676 if templ_7745c5c3_Err != nil {
16581677 return templ_7745c5c3_Err
16591678 }
1660- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "\"><button type=\"submit\" class=\"btn\">Cancel run</button></form>")
1679+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "\"><button type=\"submit\" class=\"btn\">Cancel run</button></form>")
16611680 if templ_7745c5c3_Err != nil {
16621681 return templ_7745c5c3_Err
16631682 }
16641683 }
16651684 if p.RerunAction != "" {
1666- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "<form method=\"post\" action=\"")
1685+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<form method=\"post\" action=\"")
16671686 if templ_7745c5c3_Err != nil {
16681687 return templ_7745c5c3_Err
16691688 }
1670- var templ_7745c5c3_Var89 templ.SafeURL
1671- templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunAction))
1689+ var templ_7745c5c3_Var90 templ.SafeURL
1690+ templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunAction))
16721691 if templ_7745c5c3_Err != nil {
1673- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 249, Col: 62}
1692+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 252, Col: 62}
16741693 }
1675- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89))
1694+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90))
16761695 if templ_7745c5c3_Err != nil {
16771696 return templ_7745c5c3_Err
16781697 }
1679- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "\"><button type=\"submit\" class=\"btn primary\">Re-run all jobs</button></form>")
1698+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "\"><button type=\"submit\" class=\"btn primary\">Re-run all jobs</button></form>")
16801699 if templ_7745c5c3_Err != nil {
16811700 return templ_7745c5c3_Err
16821701 }
16831702 }
16841703 if p.CanRerunFailed {
1685- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<form method=\"post\" action=\"")
1704+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "<form method=\"post\" action=\"")
16861705 if templ_7745c5c3_Err != nil {
16871706 return templ_7745c5c3_Err
16881707 }
1689- var templ_7745c5c3_Var90 templ.SafeURL
1690- templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunFailedAction))
1708+ var templ_7745c5c3_Var91 templ.SafeURL
1709+ templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunFailedAction))
16911710 if templ_7745c5c3_Err != nil {
1692- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 252, Col: 68}
1711+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 255, Col: 68}
16931712 }
1694- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90))
1713+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var91))
16951714 if templ_7745c5c3_Err != nil {
16961715 return templ_7745c5c3_Err
16971716 }
1698- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "\"><button type=\"submit\" class=\"btn\">Re-run failed jobs</button></form>")
1717+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "\"><button type=\"submit\" class=\"btn\">Re-run failed jobs</button></form>")
16991718 if templ_7745c5c3_Err != nil {
17001719 return templ_7745c5c3_Err
17011720 }
17021721 }
1703- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "</div></section>")
1722+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "</div></section>")
17041723 if templ_7745c5c3_Err != nil {
17051724 return templ_7745c5c3_Err
17061725 }
@@ -1727,25 +1746,25 @@ func JobHead(j JobRow, total int64) templ.Component {
17271746 }()
17281747 }
17291748 ctx = templ.InitializeContext(ctx)
1730- templ_7745c5c3_Var91 := templ.GetChildren(ctx)
1731- if templ_7745c5c3_Var91 == nil {
1732- templ_7745c5c3_Var91 = templ.NopComponent
1749+ templ_7745c5c3_Var92 := templ.GetChildren(ctx)
1750+ if templ_7745c5c3_Var92 == nil {
1751+ templ_7745c5c3_Var92 = templ.NopComponent
17331752 }
17341753 ctx = templ.ClearChildren(ctx)
1735- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "<div id=\"job-head\" data-total=\"")
1754+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<div id=\"job-head\" data-total=\"")
17361755 if templ_7745c5c3_Err != nil {
17371756 return templ_7745c5c3_Err
17381757 }
1739- var templ_7745c5c3_Var92 string
1740- templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(total))
1758+ var templ_7745c5c3_Var93 string
1759+ templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(total))
17411760 if templ_7745c5c3_Err != nil {
1742- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 262, Col: 46}
1761+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 265, Col: 46}
17431762 }
1744- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var92)
1763+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var93)
17451764 if templ_7745c5c3_Err != nil {
17461765 return templ_7745c5c3_Err
17471766 }
1748- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "\"><h2 class=\"run-h\">")
1767+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "\"><h2 class=\"run-h\">")
17491768 if templ_7745c5c3_Err != nil {
17501769 return templ_7745c5c3_Err
17511770 }
@@ -1753,74 +1772,74 @@ func JobHead(j JobRow, total int64) templ.Component {
17531772 if templ_7745c5c3_Err != nil {
17541773 return templ_7745c5c3_Err
17551774 }
1756- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<span>")
1775+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "<span>")
17571776 if templ_7745c5c3_Err != nil {
17581777 return templ_7745c5c3_Err
17591778 }
1760- var templ_7745c5c3_Var93 string
1761- templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(j.Name)
1779+ var templ_7745c5c3_Var94 string
1780+ templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(j.Name)
17621781 if templ_7745c5c3_Err != nil {
1763- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 61}
1782+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 61}
17641783 }
1765- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93))
1784+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94))
17661785 if templ_7745c5c3_Err != nil {
17671786 return templ_7745c5c3_Err
17681787 }
1769- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</span> <span class=\"dim\">")
1788+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "</span> <span class=\"dim\">")
17701789 if templ_7745c5c3_Err != nil {
17711790 return templ_7745c5c3_Err
17721791 }
1773- var templ_7745c5c3_Var94 string
1774- templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(j.State)
1792+ var templ_7745c5c3_Var95 string
1793+ templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(j.State)
17751794 if templ_7745c5c3_Err != nil {
1776- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 98}
1795+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 98}
17771796 }
1778- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94))
1797+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))
17791798 if templ_7745c5c3_Err != nil {
17801799 return templ_7745c5c3_Err
17811800 }
1782- var templ_7745c5c3_Var95 string
1783- templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
1801+ var templ_7745c5c3_Var96 string
1802+ templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
17841803 if templ_7745c5c3_Err != nil {
1785- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 105}
1804+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 105}
17861805 }
1787- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))
1806+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))
17881807 if templ_7745c5c3_Err != nil {
17891808 return templ_7745c5c3_Err
17901809 }
1791- var templ_7745c5c3_Var96 string
1792- templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(j.Duration)
1810+ var templ_7745c5c3_Var97 string
1811+ templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(j.Duration)
17931812 if templ_7745c5c3_Err != nil {
1794- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 119}
1813+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 119}
17951814 }
1796- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))
1815+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97))
17971816 if templ_7745c5c3_Err != nil {
17981817 return templ_7745c5c3_Err
17991818 }
1800- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "</span></h2>")
1819+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</span></h2>")
18011820 if templ_7745c5c3_Err != nil {
18021821 return templ_7745c5c3_Err
18031822 }
18041823 if j.Error != "" {
1805- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "<p class=\"job-err\">")
1824+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "<p class=\"job-err\">")
18061825 if templ_7745c5c3_Err != nil {
18071826 return templ_7745c5c3_Err
18081827 }
1809- var templ_7745c5c3_Var97 string
1810- templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(j.Error)
1828+ var templ_7745c5c3_Var98 string
1829+ templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(j.Error)
18111830 if templ_7745c5c3_Err != nil {
1812- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 265, Col: 31}
1831+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 268, Col: 31}
18131832 }
1814- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97))
1833+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98))
18151834 if templ_7745c5c3_Err != nil {
18161835 return templ_7745c5c3_Err
18171836 }
1818- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</p>")
1837+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "</p>")
18191838 if templ_7745c5c3_Err != nil {
18201839 return templ_7745c5c3_Err
18211840 }
18221841 }
1823- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "</div>")
1842+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "</div>")
18241843 if templ_7745c5c3_Err != nil {
18251844 return templ_7745c5c3_Err
18261845 }
@@ -1845,17 +1864,17 @@ func JobSteps(steps []StepView) templ.Component {
18451864 }()
18461865 }
18471866 ctx = templ.InitializeContext(ctx)
1848- templ_7745c5c3_Var98 := templ.GetChildren(ctx)
1849- if templ_7745c5c3_Var98 == nil {
1850- templ_7745c5c3_Var98 = templ.NopComponent
1867+ templ_7745c5c3_Var99 := templ.GetChildren(ctx)
1868+ if templ_7745c5c3_Var99 == nil {
1869+ templ_7745c5c3_Var99 = templ.NopComponent
18511870 }
18521871 ctx = templ.ClearChildren(ctx)
1853- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "<ol class=\"steps\" id=\"job-steps\">")
1872+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "<ol class=\"steps\" id=\"job-steps\">")
18541873 if templ_7745c5c3_Err != nil {
18551874 return templ_7745c5c3_Err
18561875 }
18571876 for _, s := range steps {
1858- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "<li>")
1877+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "<li>")
18591878 if templ_7745c5c3_Err != nil {
18601879 return templ_7745c5c3_Err
18611880 }
@@ -1864,74 +1883,74 @@ func JobSteps(steps []StepView) templ.Component {
18641883 return templ_7745c5c3_Err
18651884 }
18661885 if s.LogEnd > s.LogStart {
1867- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "<a href=\"")
1886+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "<a href=\"")
18681887 if templ_7745c5c3_Err != nil {
18691888 return templ_7745c5c3_Err
18701889 }
1871- var templ_7745c5c3_Var99 templ.SafeURL
1872- templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#L" + itoa64(s.LogStart)))
1890+ var templ_7745c5c3_Var100 templ.SafeURL
1891+ templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#L" + itoa64(s.LogStart)))
18731892 if templ_7745c5c3_Err != nil {
1874- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 277, Col: 55}
1893+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 280, Col: 55}
18751894 }
1876- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99))
1895+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))
18771896 if templ_7745c5c3_Err != nil {
18781897 return templ_7745c5c3_Err
18791898 }
1880- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "\">")
1899+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "\">")
18811900 if templ_7745c5c3_Err != nil {
18821901 return templ_7745c5c3_Err
18831902 }
1884- var templ_7745c5c3_Var100 string
1885- templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)
1903+ var templ_7745c5c3_Var101 string
1904+ templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)
18861905 if templ_7745c5c3_Err != nil {
1887- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 277, Col: 66}
1906+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 280, Col: 66}
18881907 }
1889- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))
1908+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101))
18901909 if templ_7745c5c3_Err != nil {
18911910 return templ_7745c5c3_Err
18921911 }
1893- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "</a> ")
1912+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "</a> ")
18941913 if templ_7745c5c3_Err != nil {
18951914 return templ_7745c5c3_Err
18961915 }
18971916 } else {
1898- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "<span>")
1917+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<span>")
18991918 if templ_7745c5c3_Err != nil {
19001919 return templ_7745c5c3_Err
19011920 }
1902- var templ_7745c5c3_Var101 string
1903- templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)
1921+ var templ_7745c5c3_Var102 string
1922+ templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)
19041923 if templ_7745c5c3_Err != nil {
1905- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 279, Col: 19}
1924+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 282, Col: 19}
19061925 }
1907- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101))
1926+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
19081927 if templ_7745c5c3_Err != nil {
19091928 return templ_7745c5c3_Err
19101929 }
1911- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "</span> ")
1930+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "</span> ")
19121931 if templ_7745c5c3_Err != nil {
19131932 return templ_7745c5c3_Err
19141933 }
19151934 }
1916- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<span class=\"dim\">")
1935+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "<span class=\"dim\">")
19171936 if templ_7745c5c3_Err != nil {
19181937 return templ_7745c5c3_Err
19191938 }
1920- var templ_7745c5c3_Var102 string
1921- templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(s.Duration)
1939+ var templ_7745c5c3_Var103 string
1940+ templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(s.Duration)
19221941 if templ_7745c5c3_Err != nil {
1923- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 281, Col: 34}
1942+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 284, Col: 34}
19241943 }
1925- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
1944+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))
19261945 if templ_7745c5c3_Err != nil {
19271946 return templ_7745c5c3_Err
19281947 }
1929- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "</span></li>")
1948+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "</span></li>")
19301949 if templ_7745c5c3_Err != nil {
19311950 return templ_7745c5c3_Err
19321951 }
19331952 }
1934- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "</ol>")
1953+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "</ol>")
19351954 if templ_7745c5c3_Err != nil {
19361955 return templ_7745c5c3_Err
19371956 }
@@ -1955,12 +1974,12 @@ func Job(l Layout, p JobPage) templ.Component {
19551974 }()
19561975 }
19571976 ctx = templ.InitializeContext(ctx)
1958- templ_7745c5c3_Var103 := templ.GetChildren(ctx)
1959- if templ_7745c5c3_Var103 == nil {
1960- templ_7745c5c3_Var103 = templ.NopComponent
1977+ templ_7745c5c3_Var104 := templ.GetChildren(ctx)
1978+ if templ_7745c5c3_Var104 == nil {
1979+ templ_7745c5c3_Var104 = templ.NopComponent
19611980 }
19621981 ctx = templ.ClearChildren(ctx)
1963- templ_7745c5c3_Var104 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1982+ templ_7745c5c3_Var105 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
19641983 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
19651984 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
19661985 if !templ_7745c5c3_IsBuffer {
@@ -1976,7 +1995,7 @@ func Job(l Layout, p JobPage) templ.Component {
19761995 if templ_7745c5c3_Err != nil {
19771996 return templ_7745c5c3_Err
19781997 }
1979- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, " ")
1998+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, " ")
19801999 if templ_7745c5c3_Err != nil {
19812000 return templ_7745c5c3_Err
19822001 }
@@ -1984,7 +2003,7 @@ func Job(l Layout, p JobPage) templ.Component {
19842003 if templ_7745c5c3_Err != nil {
19852004 return templ_7745c5c3_Err
19862005 }
1987- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, " ")
2006+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, " ")
19882007 if templ_7745c5c3_Err != nil {
19892008 return templ_7745c5c3_Err
19902009 }
@@ -1992,111 +2011,111 @@ func Job(l Layout, p JobPage) templ.Component {
19922011 if templ_7745c5c3_Err != nil {
19932012 return templ_7745c5c3_Err
19942013 }
1995- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, " <div id=\"job-log\" class=\"joblog\" data-total=\"")
2014+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, " <div id=\"job-log\" class=\"joblog\" data-total=\"")
19962015 if templ_7745c5c3_Err != nil {
19972016 return templ_7745c5c3_Err
19982017 }
1999- var templ_7745c5c3_Var105 string
2000- templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.Total))
2018+ var templ_7745c5c3_Var106 string
2019+ templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.Total))
20012020 if templ_7745c5c3_Err != nil {
2002- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 292, Col: 63}
2021+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 295, Col: 63}
20032022 }
2004- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var105)
2023+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var106)
20052024 if templ_7745c5c3_Err != nil {
20062025 return templ_7745c5c3_Err
20072026 }
2008- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "\"")
2027+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "\"")
20092028 if templ_7745c5c3_Err != nil {
20102029 return templ_7745c5c3_Err
20112030 }
20122031 if p.Follow {
2013- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, " data-follow")
2032+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, " data-follow")
20142033 if templ_7745c5c3_Err != nil {
20152034 return templ_7745c5c3_Err
20162035 }
20172036 }
2018- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, " data-stream=\"")
2037+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, " data-stream=\"")
20192038 if templ_7745c5c3_Err != nil {
20202039 return templ_7745c5c3_Err
20212040 }
2022- var templ_7745c5c3_Var106 string
2023- templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.StreamHref)
2041+ var templ_7745c5c3_Var107 string
2042+ templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.StreamHref)
20242043 if templ_7745c5c3_Err != nil {
2025- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 292, Col: 118}
2044+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 295, Col: 118}
20262045 }
2027- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var106)
2046+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var107)
20282047 if templ_7745c5c3_Err != nil {
20292048 return templ_7745c5c3_Err
20302049 }
2031- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "\" data-next=\"")
2050+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "\" data-next=\"")
20322051 if templ_7745c5c3_Err != nil {
20332052 return templ_7745c5c3_Err
20342053 }
2035- var templ_7745c5c3_Var107 string
2036- templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + int64(len(p.Lines))))
2054+ var templ_7745c5c3_Var108 string
2055+ templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + int64(len(p.Lines))))
20372056 if templ_7745c5c3_Err != nil {
2038- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 292, Col: 169}
2057+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 295, Col: 169}
20392058 }
2040- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var107)
2059+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var108)
20412060 if templ_7745c5c3_Err != nil {
20422061 return templ_7745c5c3_Err
20432062 }
2044- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "\">")
2063+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "\">")
20452064 if templ_7745c5c3_Err != nil {
20462065 return templ_7745c5c3_Err
20472066 }
20482067 if p.Note != "" {
2049- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "<p class=\"dim\">")
2068+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<p class=\"dim\">")
20502069 if templ_7745c5c3_Err != nil {
20512070 return templ_7745c5c3_Err
20522071 }
2053- var templ_7745c5c3_Var108 string
2054- templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
2072+ var templ_7745c5c3_Var109 string
2073+ templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
20552074 if templ_7745c5c3_Err != nil {
2056- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 294, Col: 27}
2075+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 297, Col: 27}
20572076 }
2058- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var108))
2077+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))
20592078 if templ_7745c5c3_Err != nil {
20602079 return templ_7745c5c3_Err
20612080 }
2062- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</p>")
2081+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "</p>")
20632082 if templ_7745c5c3_Err != nil {
20642083 return templ_7745c5c3_Err
20652084 }
20662085 }
20672086 if p.Prev != "" {
2068- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<p class=\"more\"><a href=\"")
2087+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "<p class=\"more\"><a href=\"")
20692088 if templ_7745c5c3_Err != nil {
20702089 return templ_7745c5c3_Err
20712090 }
2072- var templ_7745c5c3_Var109 templ.SafeURL
2073- templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Prev))
2091+ var templ_7745c5c3_Var110 templ.SafeURL
2092+ templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Prev))
20742093 if templ_7745c5c3_Err != nil {
2075- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 297, Col: 51}
2094+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 300, Col: 51}
20762095 }
2077- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))
2096+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var110))
20782097 if templ_7745c5c3_Err != nil {
20792098 return templ_7745c5c3_Err
20802099 }
2081- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "\" rel=\"prev\">earlier lines</a></p>")
2100+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "\" rel=\"prev\">earlier lines</a></p>")
20822101 if templ_7745c5c3_Err != nil {
20832102 return templ_7745c5c3_Err
20842103 }
20852104 }
2086- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "<ol class=\"log\" start=\"")
2105+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "<ol class=\"log\" start=\"")
20872106 if templ_7745c5c3_Err != nil {
20882107 return templ_7745c5c3_Err
20892108 }
2090- var templ_7745c5c3_Var110 string
2091- templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + 1))
2109+ var templ_7745c5c3_Var111 string
2110+ templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + 1))
20922111 if templ_7745c5c3_Err != nil {
2093- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 299, Col: 45}
2112+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 302, Col: 45}
20942113 }
2095- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var110)
2114+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var111)
20962115 if templ_7745c5c3_Err != nil {
20972116 return templ_7745c5c3_Err
20982117 }
2099- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "\">")
2118+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "\">")
21002119 if templ_7745c5c3_Err != nil {
21012120 return templ_7745c5c3_Err
21022121 }
@@ -2106,36 +2125,36 @@ func Job(l Layout, p JobPage) templ.Component {
21062125 return templ_7745c5c3_Err
21072126 }
21082127 }
2109- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "</ol>")
2128+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "</ol>")
21102129 if templ_7745c5c3_Err != nil {
21112130 return templ_7745c5c3_Err
21122131 }
21132132 if p.Next != "" {
2114- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "<p class=\"more\"><a href=\"")
2133+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "<p class=\"more\"><a href=\"")
21152134 if templ_7745c5c3_Err != nil {
21162135 return templ_7745c5c3_Err
21172136 }
2118- var templ_7745c5c3_Var111 templ.SafeURL
2119- templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Next))
2137+ var templ_7745c5c3_Var112 templ.SafeURL
2138+ templ_7745c5c3_Var112, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Next))
21202139 if templ_7745c5c3_Err != nil {
2121- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 305, Col: 51}
2140+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 308, Col: 51}
21222141 }
2123- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var111))
2142+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var112))
21242143 if templ_7745c5c3_Err != nil {
21252144 return templ_7745c5c3_Err
21262145 }
2127- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "\" rel=\"next\">more lines</a></p>")
2146+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "\" rel=\"next\">more lines</a></p>")
21282147 if templ_7745c5c3_Err != nil {
21292148 return templ_7745c5c3_Err
21302149 }
21312150 }
2132- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "</div>")
2151+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "</div>")
21332152 if templ_7745c5c3_Err != nil {
21342153 return templ_7745c5c3_Err
21352154 }
21362155 return nil
21372156 })
2138- templ_7745c5c3_Err = Shell(l, jobRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var104), templ_7745c5c3_Buffer)
2157+ templ_7745c5c3_Err = Shell(l, jobRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var105), templ_7745c5c3_Buffer)
21392158 if templ_7745c5c3_Err != nil {
21402159 return templ_7745c5c3_Err
21412160 }
@@ -2161,92 +2180,92 @@ func LogLine(ln LogLineView) templ.Component {
21612180 }()
21622181 }
21632182 ctx = templ.InitializeContext(ctx)
2164- templ_7745c5c3_Var112 := templ.GetChildren(ctx)
2165- if templ_7745c5c3_Var112 == nil {
2166- templ_7745c5c3_Var112 = templ.NopComponent
2183+ templ_7745c5c3_Var113 := templ.GetChildren(ctx)
2184+ if templ_7745c5c3_Var113 == nil {
2185+ templ_7745c5c3_Var113 = templ.NopComponent
21672186 }
21682187 ctx = templ.ClearChildren(ctx)
2169- var templ_7745c5c3_Var113 = []any{logClass(ln)}
2170- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var113...)
2188+ var templ_7745c5c3_Var114 = []any{logClass(ln)}
2189+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var114...)
21712190 if templ_7745c5c3_Err != nil {
21722191 return templ_7745c5c3_Err
21732192 }
2174- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "<li id=\"")
2193+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "<li id=\"")
21752194 if templ_7745c5c3_Err != nil {
21762195 return templ_7745c5c3_Err
21772196 }
2178- var templ_7745c5c3_Var114 string
2179- templ_7745c5c3_Var114, templ_7745c5c3_Err = templ.ResolveAttributeValue("L" + itoa64(ln.N))
2197+ var templ_7745c5c3_Var115 string
2198+ templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.ResolveAttributeValue("L" + itoa64(ln.N))
21802199 if templ_7745c5c3_Err != nil {
2181- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 28}
2200+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 28}
21822201 }
2183- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var114)
2202+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var115)
21842203 if templ_7745c5c3_Err != nil {
21852204 return templ_7745c5c3_Err
21862205 }
2187- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "\" class=\"")
2206+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "\" class=\"")
21882207 if templ_7745c5c3_Err != nil {
21892208 return templ_7745c5c3_Err
21902209 }
2191- var templ_7745c5c3_Var115 string
2192- templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var113).String())
2210+ var templ_7745c5c3_Var116 string
2211+ templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var114).String())
21932212 if templ_7745c5c3_Err != nil {
21942213 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 1, Col: 0}
21952214 }
2196- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var115)
2215+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var116)
21972216 if templ_7745c5c3_Err != nil {
21982217 return templ_7745c5c3_Err
21992218 }
2200- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "\" data-step=\"")
2219+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "\" data-step=\"")
22012220 if templ_7745c5c3_Err != nil {
22022221 return templ_7745c5c3_Err
22032222 }
2204- var templ_7745c5c3_Var116 string
2205- templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(int(ln.Step)))
2223+ var templ_7745c5c3_Var117 string
2224+ templ_7745c5c3_Var117, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(int(ln.Step)))
22062225 if templ_7745c5c3_Err != nil {
2207- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 84}
2226+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 84}
22082227 }
2209- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var116)
2228+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var117)
22102229 if templ_7745c5c3_Err != nil {
22112230 return templ_7745c5c3_Err
22122231 }
2213- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "\"")
2232+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "\"")
22142233 if templ_7745c5c3_Err != nil {
22152234 return templ_7745c5c3_Err
22162235 }
22172236 if ln.Continued {
2218- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, " data-cont")
2237+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, " data-cont")
22192238 if templ_7745c5c3_Err != nil {
22202239 return templ_7745c5c3_Err
22212240 }
22222241 }
2223- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "><time>")
2242+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "><time>")
22242243 if templ_7745c5c3_Err != nil {
22252244 return templ_7745c5c3_Err
22262245 }
2227- var templ_7745c5c3_Var117 string
2228- templ_7745c5c3_Var117, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Time)
2246+ var templ_7745c5c3_Var118 string
2247+ templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Time)
22292248 if templ_7745c5c3_Err != nil {
2230- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 130}
2249+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 130}
22312250 }
2232- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var117))
2251+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
22332252 if templ_7745c5c3_Err != nil {
22342253 return templ_7745c5c3_Err
22352254 }
2236- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "</time><span class=\"t\">")
2255+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "</time><span class=\"t\">")
22372256 if templ_7745c5c3_Err != nil {
22382257 return templ_7745c5c3_Err
22392258 }
2240- var templ_7745c5c3_Var118 string
2241- templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Text)
2259+ var templ_7745c5c3_Var119 string
2260+ templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Text)
22422261 if templ_7745c5c3_Err != nil {
2243- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 164}
2262+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 164}
22442263 }
2245- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
2264+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var119))
22462265 if templ_7745c5c3_Err != nil {
22472266 return templ_7745c5c3_Err
22482267 }
2249- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "</span></li>")
2268+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "</span></li>")
22502269 if templ_7745c5c3_Err != nil {
22512270 return templ_7745c5c3_Err
22522271 }
@@ -2270,12 +2289,12 @@ func jobRail(p JobPage) templ.Component {
22702289 }()
22712290 }
22722291 ctx = templ.InitializeContext(ctx)
2273- templ_7745c5c3_Var119 := templ.GetChildren(ctx)
2274- if templ_7745c5c3_Var119 == nil {
2275- templ_7745c5c3_Var119 = templ.NopComponent
2292+ templ_7745c5c3_Var120 := templ.GetChildren(ctx)
2293+ if templ_7745c5c3_Var120 == nil {
2294+ templ_7745c5c3_Var120 = templ.NopComponent
22762295 }
22772296 ctx = templ.ClearChildren(ctx)
2278- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "<section aria-labelledby=\"r-job\"><h2 id=\"r-job\">Steps</h2>")
2297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "<section aria-labelledby=\"r-job\"><h2 id=\"r-job\">Steps</h2>")
22792298 if templ_7745c5c3_Err != nil {
22802299 return templ_7745c5c3_Err
22812300 }
@@ -2283,101 +2302,101 @@ func jobRail(p JobPage) templ.Component {
22832302 if templ_7745c5c3_Err != nil {
22842303 return templ_7745c5c3_Err
22852304 }
2286- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "</section><section aria-labelledby=\"r-jobf\"><h2 id=\"r-jobf\">Job</h2><dl class=\"kv\"><dt>run</dt><dd><a href=\"")
2305+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "</section><section aria-labelledby=\"r-jobf\"><h2 id=\"r-jobf\">Job</h2><dl class=\"kv\"><dt>run</dt><dd><a href=\"")
22872306 if templ_7745c5c3_Err != nil {
22882307 return templ_7745c5c3_Err
22892308 }
2290- var templ_7745c5c3_Var120 templ.SafeURL
2291- templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RunHref))
2309+ var templ_7745c5c3_Var121 templ.SafeURL
2310+ templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RunHref))
22922311 if templ_7745c5c3_Err != nil {
2293- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 326, Col: 41}
2312+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 329, Col: 41}
22942313 }
2295- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var120))
2314+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))
22962315 if templ_7745c5c3_Err != nil {
22972316 return templ_7745c5c3_Err
22982317 }
2299- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "\">")
2318+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "\">")
23002319 if templ_7745c5c3_Err != nil {
23012320 return templ_7745c5c3_Err
23022321 }
2303- var templ_7745c5c3_Var121 string
2304- templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinStringErrs(p.RunName)
2322+ var templ_7745c5c3_Var122 string
2323+ templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinStringErrs(p.RunName)
23052324 if templ_7745c5c3_Err != nil {
2306- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 326, Col: 55}
2325+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 329, Col: 55}
23072326 }
2308- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))
2327+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))
23092328 if templ_7745c5c3_Err != nil {
23102329 return templ_7745c5c3_Err
23112330 }
2312- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "</a></dd>")
2331+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "</a></dd>")
23132332 if templ_7745c5c3_Err != nil {
23142333 return templ_7745c5c3_Err
23152334 }
23162335 if p.Runner != "" {
2317- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "<dt>runner</dt><dd>")
2336+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "<dt>runner</dt><dd>")
23182337 if templ_7745c5c3_Err != nil {
23192338 return templ_7745c5c3_Err
23202339 }
2321- var templ_7745c5c3_Var122 string
2322- templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinStringErrs(p.Runner)
2340+ var templ_7745c5c3_Var123 string
2341+ templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Runner)
23232342 if templ_7745c5c3_Err != nil {
2324- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 329, Col: 18}
2343+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 332, Col: 18}
23252344 }
2326- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))
2345+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))
23272346 if templ_7745c5c3_Err != nil {
23282347 return templ_7745c5c3_Err
23292348 }
2330- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "</dd>")
2349+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "</dd>")
23312350 if templ_7745c5c3_Err != nil {
23322351 return templ_7745c5c3_Err
23332352 }
23342353 }
23352354 if p.Isolation != "" {
2336- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "<dt>isolation</dt><dd>")
2355+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<dt>isolation</dt><dd>")
23372356 if templ_7745c5c3_Err != nil {
23382357 return templ_7745c5c3_Err
23392358 }
2340- var templ_7745c5c3_Var123 string
2341- templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Isolation)
2359+ var templ_7745c5c3_Var124 string
2360+ templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(p.Isolation)
23422361 if templ_7745c5c3_Err != nil {
2343- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 333, Col: 21}
2362+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 336, Col: 21}
23442363 }
2345- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))
2364+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))
23462365 if templ_7745c5c3_Err != nil {
23472366 return templ_7745c5c3_Err
23482367 }
2349- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "</dd>")
2368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "</dd>")
23502369 if templ_7745c5c3_Err != nil {
23512370 return templ_7745c5c3_Err
23522371 }
23532372 }
2354- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<dt>lines</dt><dd>")
2373+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "<dt>lines</dt><dd>")
23552374 if templ_7745c5c3_Err != nil {
23562375 return templ_7745c5c3_Err
23572376 }
2358- var templ_7745c5c3_Var124 string
2359- templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(itoa64(p.Total))
2377+ var templ_7745c5c3_Var125 string
2378+ templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.JoinStringErrs(itoa64(p.Total))
23602379 if templ_7745c5c3_Err != nil {
2361- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 336, Col: 24}
2380+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 339, Col: 24}
23622381 }
2363- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))
2382+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var125))
23642383 if templ_7745c5c3_Err != nil {
23652384 return templ_7745c5c3_Err
23662385 }
2367- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "</dd></dl><p><a href=\"")
2386+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "</dd></dl><p><a href=\"")
23682387 if templ_7745c5c3_Err != nil {
23692388 return templ_7745c5c3_Err
23702389 }
2371- var templ_7745c5c3_Var125 templ.SafeURL
2372- templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RawHref))
2390+ var templ_7745c5c3_Var126 templ.SafeURL
2391+ templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RawHref))
23732392 if templ_7745c5c3_Err != nil {
2374- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 338, Col: 39}
2393+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 341, Col: 39}
23752394 }
2376- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var125))
2395+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126))
23772396 if templ_7745c5c3_Err != nil {
23782397 return templ_7745c5c3_Err
23792398 }
2380- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "\">raw log</a></p></section>")
2399+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "\">raw log</a></p></section>")
23812400 if templ_7745c5c3_Err != nil {
23822401 return templ_7745c5c3_Err
23832402 }
@@ -2403,39 +2422,39 @@ func CommitChecks(c ChecksSummary) templ.Component {
24032422 }()
24042423 }
24052424 ctx = templ.InitializeContext(ctx)
2406- templ_7745c5c3_Var126 := templ.GetChildren(ctx)
2407- if templ_7745c5c3_Var126 == nil {
2408- templ_7745c5c3_Var126 = templ.NopComponent
2425+ templ_7745c5c3_Var127 := templ.GetChildren(ctx)
2426+ if templ_7745c5c3_Var127 == nil {
2427+ templ_7745c5c3_Var127 = templ.NopComponent
24092428 }
24102429 ctx = templ.ClearChildren(ctx)
24112430 if c.Total > 0 {
2412- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "<a class=\"ck-sum\" href=\"")
2431+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "<a class=\"ck-sum\" href=\"")
24132432 if templ_7745c5c3_Err != nil {
24142433 return templ_7745c5c3_Err
24152434 }
2416- var templ_7745c5c3_Var127 templ.SafeURL
2417- templ_7745c5c3_Var127, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))
2435+ var templ_7745c5c3_Var128 templ.SafeURL
2436+ templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))
24182437 if templ_7745c5c3_Err != nil {
2419- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 346, Col: 48}
2438+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 349, Col: 48}
24202439 }
2421- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var127))
2440+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128))
24222441 if templ_7745c5c3_Err != nil {
24232442 return templ_7745c5c3_Err
24242443 }
2425- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "\" title=\"")
2444+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "\" title=\"")
24262445 if templ_7745c5c3_Err != nil {
24272446 return templ_7745c5c3_Err
24282447 }
2429- var templ_7745c5c3_Var128 string
2430- templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Title)
2448+ var templ_7745c5c3_Var129 string
2449+ templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Title)
24312450 if templ_7745c5c3_Err != nil {
2432- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 346, Col: 66}
2451+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 349, Col: 66}
24332452 }
2434- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var128)
2453+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var129)
24352454 if templ_7745c5c3_Err != nil {
24362455 return templ_7745c5c3_Err
24372456 }
2438- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "\">")
2457+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "\">")
24392458 if templ_7745c5c3_Err != nil {
24402459 return templ_7745c5c3_Err
24412460 }
@@ -2443,20 +2462,20 @@ func CommitChecks(c ChecksSummary) templ.Component {
24432462 if templ_7745c5c3_Err != nil {
24442463 return templ_7745c5c3_Err
24452464 }
2446- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "<span class=\"mute\">")
2465+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "<span class=\"mute\">")
24472466 if templ_7745c5c3_Err != nil {
24482467 return templ_7745c5c3_Err
24492468 }
2450- var templ_7745c5c3_Var129 string
2451- templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label)
2469+ var templ_7745c5c3_Var130 string
2470+ templ_7745c5c3_Var130, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label)
24522471 if templ_7745c5c3_Err != nil {
2453- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 348, Col: 31}
2472+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 351, Col: 31}
24542473 }
2455- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var129))
2474+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var130))
24562475 if templ_7745c5c3_Err != nil {
24572476 return templ_7745c5c3_Err
24582477 }
2459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "</span></a>")
2478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "</span></a>")
24602479 if templ_7745c5c3_Err != nil {
24612480 return templ_7745c5c3_Err
24622481 }
@@ -2482,40 +2501,40 @@ func MRChecks(c ChecksSection) templ.Component {
24822501 }()
24832502 }
24842503 ctx = templ.InitializeContext(ctx)
2485- templ_7745c5c3_Var130 := templ.GetChildren(ctx)
2486- if templ_7745c5c3_Var130 == nil {
2487- templ_7745c5c3_Var130 = templ.NopComponent
2504+ templ_7745c5c3_Var131 := templ.GetChildren(ctx)
2505+ if templ_7745c5c3_Var131 == nil {
2506+ templ_7745c5c3_Var131 = templ.NopComponent
24882507 }
24892508 ctx = templ.ClearChildren(ctx)
2490- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "<section aria-labelledby=\"r-checks\" id=\"mr-checks\"><h2 id=\"r-checks\">Checks <span class=\"count\">")
2509+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "<section aria-labelledby=\"r-checks\" id=\"mr-checks\"><h2 id=\"r-checks\">Checks <span class=\"count\">")
24912510 if templ_7745c5c3_Err != nil {
24922511 return templ_7745c5c3_Err
24932512 }
2494- var templ_7745c5c3_Var131 string
2495- templ_7745c5c3_Var131, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(c.Rows)))
2513+ var templ_7745c5c3_Var132 string
2514+ templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(c.Rows)))
24962515 if templ_7745c5c3_Err != nil {
2497- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 356, Col: 66}
2516+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 359, Col: 66}
24982517 }
2499- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var131))
2518+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))
25002519 if templ_7745c5c3_Err != nil {
25012520 return templ_7745c5c3_Err
25022521 }
2503- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "</span></h2>")
2522+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "</span></h2>")
25042523 if templ_7745c5c3_Err != nil {
25052524 return templ_7745c5c3_Err
25062525 }
25072526 if len(c.Rows) == 0 {
2508- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "<p class=\"dim\">no checks</p>")
2527+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "<p class=\"dim\">no checks</p>")
25092528 if templ_7745c5c3_Err != nil {
25102529 return templ_7745c5c3_Err
25112530 }
25122531 } else {
2513- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "<ul class=\"checks\">")
2532+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "<ul class=\"checks\">")
25142533 if templ_7745c5c3_Err != nil {
25152534 return templ_7745c5c3_Err
25162535 }
25172536 for _, r := range c.Rows {
2518- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "<li>")
2537+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "<li>")
25192538 if templ_7745c5c3_Err != nil {
25202539 return templ_7745c5c3_Err
25212540 }
@@ -2524,108 +2543,108 @@ func MRChecks(c ChecksSection) templ.Component {
25242543 return templ_7745c5c3_Err
25252544 }
25262545 if r.Href != "" {
2527- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "<a href=\"")
2546+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "<a href=\"")
25282547 if templ_7745c5c3_Err != nil {
25292548 return templ_7745c5c3_Err
25302549 }
2531- var templ_7745c5c3_Var132 templ.SafeURL
2532- templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
2550+ var templ_7745c5c3_Var133 templ.SafeURL
2551+ templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
25332552 if templ_7745c5c3_Err != nil {
2534- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 365, Col: 38}
2553+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 368, Col: 38}
25352554 }
2536- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))
2555+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))
25372556 if templ_7745c5c3_Err != nil {
25382557 return templ_7745c5c3_Err
25392558 }
2540- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "\">")
2559+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "\">")
25412560 if templ_7745c5c3_Err != nil {
25422561 return templ_7745c5c3_Err
25432562 }
2544- var templ_7745c5c3_Var133 string
2545- templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
2563+ var templ_7745c5c3_Var134 string
2564+ templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
25462565 if templ_7745c5c3_Err != nil {
2547- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 365, Col: 49}
2566+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 368, Col: 49}
25482567 }
2549- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))
2568+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))
25502569 if templ_7745c5c3_Err != nil {
25512570 return templ_7745c5c3_Err
25522571 }
2553- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "</a> ")
2572+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "</a> ")
25542573 if templ_7745c5c3_Err != nil {
25552574 return templ_7745c5c3_Err
25562575 }
25572576 } else {
2558- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "<span>")
2577+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<span>")
25592578 if templ_7745c5c3_Err != nil {
25602579 return templ_7745c5c3_Err
25612580 }
2562- var templ_7745c5c3_Var134 string
2563- templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
2581+ var templ_7745c5c3_Var135 string
2582+ templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
25642583 if templ_7745c5c3_Err != nil {
2565- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 367, Col: 21}
2584+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 370, Col: 21}
25662585 }
2567- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))
2586+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135))
25682587 if templ_7745c5c3_Err != nil {
25692588 return templ_7745c5c3_Err
25702589 }
2571- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "</span> ")
2590+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "</span> ")
25722591 if templ_7745c5c3_Err != nil {
25732592 return templ_7745c5c3_Err
25742593 }
25752594 }
2576- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<span class=\"dim\">")
2595+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "<span class=\"dim\">")
25772596 if templ_7745c5c3_Err != nil {
25782597 return templ_7745c5c3_Err
25792598 }
2580- var templ_7745c5c3_Var135 string
2581- templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinStringErrs(r.State)
2599+ var templ_7745c5c3_Var136 string
2600+ templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(r.State)
25822601 if templ_7745c5c3_Err != nil {
2583- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 369, Col: 33}
2602+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 372, Col: 33}
25842603 }
2585- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135))
2604+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136))
25862605 if templ_7745c5c3_Err != nil {
25872606 return templ_7745c5c3_Err
25882607 }
2589- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "</span> ")
2608+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "</span> ")
25902609 if templ_7745c5c3_Err != nil {
25912610 return templ_7745c5c3_Err
25922611 }
25932612 if r.Waiting {
2594- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "<span class=\"mute\">waiting for a runner</span>")
2613+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "<span class=\"mute\">waiting for a runner</span>")
25952614 if templ_7745c5c3_Err != nil {
25962615 return templ_7745c5c3_Err
25972616 }
25982617 }
2599- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "</li>")
2618+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "</li>")
26002619 if templ_7745c5c3_Err != nil {
26012620 return templ_7745c5c3_Err
26022621 }
26032622 }
2604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "</ul>")
2623+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "</ul>")
26052624 if templ_7745c5c3_Err != nil {
26062625 return templ_7745c5c3_Err
26072626 }
26082627 }
26092628 if c.Note != "" {
2610- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "<p class=\"mute\">")
2629+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "<p class=\"mute\">")
26112630 if templ_7745c5c3_Err != nil {
26122631 return templ_7745c5c3_Err
26132632 }
2614- var templ_7745c5c3_Var136 string
2615- templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(c.Note)
2633+ var templ_7745c5c3_Var137 string
2634+ templ_7745c5c3_Var137, templ_7745c5c3_Err = templ.JoinStringErrs(c.Note)
26162635 if templ_7745c5c3_Err != nil {
2617- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 378, Col: 27}
2636+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 381, Col: 27}
26182637 }
2619- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136))
2638+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var137))
26202639 if templ_7745c5c3_Err != nil {
26212640 return templ_7745c5c3_Err
26222641 }
2623- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "</p>")
2642+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 224, "</p>")
26242643 if templ_7745c5c3_Err != nil {
26252644 return templ_7745c5c3_Err
26262645 }
26272646 }
2628- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "</section>")
2647+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 225, "</section>")
26292648 if templ_7745c5c3_Err != nil {
26302649 return templ_7745c5c3_Err
26312650 }
@@ -495,7 +495,7 @@ func RunDetail(l Layout, p RunPage) templ.Component {
495 if templ_7745c5c3_Err != nil {495 if templ_7745c5c3_Err != nil {
496 return templ_7745c5c3_Err496 return templ_7745c5c3_Err
497 }497 }
498- templ_7745c5c3_Err = RunSummaries(p.Summaries).Render(ctx, templ_7745c5c3_Buffer)498+ templ_7745c5c3_Err = RunSummaries(p.Summaries, p.SummariesMore).Render(ctx, templ_7745c5c3_Buffer)
499 if templ_7745c5c3_Err != nil {499 if templ_7745c5c3_Err != nil {
500 return templ_7745c5c3_Err500 return templ_7745c5c3_Err
501 }501 }
@@ -1271,7 +1271,7 @@ func JobAnnotations(anns []AnnotationView, more bool) templ.Component {
1271 // RunSummaries is the job summaries of a run, one block per step that wrote1271 // RunSummaries is the job summaries of a run, one block per step that wrote
1272 // one. The Markdown is rendered by the README renderer: raw HTML dropped, URLs1272 // one. The Markdown is rendered by the README renderer: raw HTML dropped, URLs
1273 // filtered, images shown as their alt text.1273 // filtered, images shown as their alt text.
1274-func RunSummaries(sums []JobSummaries) templ.Component {1274+func RunSummaries(sums []JobSummaries, more bool) templ.Component {
1275 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {1275 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1276 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context1276 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
1277 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {1277 if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -1431,6 +1431,25 @@ func RunSummaries(sums []JobSummaries) templ.Component {
1431 return templ_7745c5c3_Err1431 return templ_7745c5c3_Err
1432 }1432 }
1433 }1433 }
1434+ if more {
1435+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<p class=\"mute\">more summaries are not shown: the summaries of the page are over ")
1436+ if templ_7745c5c3_Err != nil {
1437+ return templ_7745c5c3_Err
1438+ }
1439+ var templ_7745c5c3_Var76 string
1440+ templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(humanSize(maxSummaryPageBytes))
1441+ if templ_7745c5c3_Err != nil {
1442+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 211, Col: 115}
1443+ }
1444+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76))
1445+ if templ_7745c5c3_Err != nil {
1446+ return templ_7745c5c3_Err
1447+ }
1448+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, ".</p>")
1449+ if templ_7745c5c3_Err != nil {
1450+ return templ_7745c5c3_Err
1451+ }
1452+ }
1434 return nil1453 return nil
1435 })1454 })
1436 }1455 }
@@ -1451,256 +1470,256 @@ func runRail(p RunPage) templ.Component {
1451 }()1470 }()
1452 }1471 }
1453 ctx = templ.InitializeContext(ctx)1472 ctx = templ.InitializeContext(ctx)
1454- templ_7745c5c3_Var76 := templ.GetChildren(ctx)1473+ templ_7745c5c3_Var77 := templ.GetChildren(ctx)
1455- if templ_7745c5c3_Var76 == nil {1474+ if templ_7745c5c3_Var77 == nil {
1456- templ_7745c5c3_Var76 = templ.NopComponent1475+ templ_7745c5c3_Var77 = templ.NopComponent
1457 }1476 }
1458 ctx = templ.ClearChildren(ctx)1477 ctx = templ.ClearChildren(ctx)
1459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<section aria-labelledby=\"r-run\"><h2 id=\"r-run\">Run</h2><dl class=\"kv\"><dt>result</dt><dd>")1478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "<section aria-labelledby=\"r-run\"><h2 id=\"r-run\">Run</h2><dl class=\"kv\"><dt>result</dt><dd>")
1460 if templ_7745c5c3_Err != nil {1479 if templ_7745c5c3_Err != nil {
1461 return templ_7745c5c3_Err1480 return templ_7745c5c3_Err
1462 }1481 }
1463- var templ_7745c5c3_Var77 string1482+ var templ_7745c5c3_Var78 string
1464- templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(p.State)1483+ templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(p.State)
1465 if templ_7745c5c3_Err != nil {1484 if templ_7745c5c3_Err != nil {
1466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 217, Col: 16}1485+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 220, Col: 16}
1467- }
1468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77))
1469- if templ_7745c5c3_Err != nil {
1470- return templ_7745c5c3_Err
1471- }
1472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</dd><dt>commit</dt><dd><a href=\"")
1473- if templ_7745c5c3_Err != nil {
1474- return templ_7745c5c3_Err
1475- }
1476- var templ_7745c5c3_Var78 templ.SafeURL
1477- templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CHref))
1478- if templ_7745c5c3_Err != nil {
1479- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 219, Col: 39}
1480 }1486 }
1481 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78))1487 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78))
1482 if templ_7745c5c3_Err != nil {1488 if templ_7745c5c3_Err != nil {
1483 return templ_7745c5c3_Err1489 return templ_7745c5c3_Err
1484 }1490 }
1485- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "\">")1491+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</dd><dt>commit</dt><dd><a href=\"")
1486 if templ_7745c5c3_Err != nil {1492 if templ_7745c5c3_Err != nil {
1487 return templ_7745c5c3_Err1493 return templ_7745c5c3_Err
1488 }1494 }
1489- var templ_7745c5c3_Var79 string1495+ var templ_7745c5c3_Var79 templ.SafeURL
1490- templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinStringErrs(p.Short)1496+ templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CHref))
1491 if templ_7745c5c3_Err != nil {1497 if templ_7745c5c3_Err != nil {
1492- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 219, Col: 51}1498+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 222, Col: 39}
1493 }1499 }
1494 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var79))1500 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var79))
1495 if templ_7745c5c3_Err != nil {1501 if templ_7745c5c3_Err != nil {
1496 return templ_7745c5c3_Err1502 return templ_7745c5c3_Err
1497 }1503 }
1498- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</a></dd><dt>ref</dt><dd><a href=\"")1504+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "\">")
1499 if templ_7745c5c3_Err != nil {1505 if templ_7745c5c3_Err != nil {
1500 return templ_7745c5c3_Err1506 return templ_7745c5c3_Err
1501 }1507 }
1502- var templ_7745c5c3_Var80 templ.SafeURL1508+ var templ_7745c5c3_Var80 string
1503- templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RefHref))1509+ templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinStringErrs(p.Short)
1504 if templ_7745c5c3_Err != nil {1510 if templ_7745c5c3_Err != nil {
1505- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 221, Col: 41}1511+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 222, Col: 51}
1506 }1512 }
1507 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var80))1513 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var80))
1508 if templ_7745c5c3_Err != nil {1514 if templ_7745c5c3_Err != nil {
1509 return templ_7745c5c3_Err1515 return templ_7745c5c3_Err
1510 }1516 }
1511- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "\">")1517+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</a></dd><dt>ref</dt><dd><a href=\"")
1512 if templ_7745c5c3_Err != nil {1518 if templ_7745c5c3_Err != nil {
1513 return templ_7745c5c3_Err1519 return templ_7745c5c3_Err
1514 }1520 }
1515- var templ_7745c5c3_Var81 string1521+ var templ_7745c5c3_Var81 templ.SafeURL
1516- templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinStringErrs(p.Ref)1522+ templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RefHref))
1517 if templ_7745c5c3_Err != nil {1523 if templ_7745c5c3_Err != nil {
1518- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 221, Col: 51}1524+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 224, Col: 41}
1519 }1525 }
1520 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81))1526 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81))
1521 if templ_7745c5c3_Err != nil {1527 if templ_7745c5c3_Err != nil {
1522 return templ_7745c5c3_Err1528 return templ_7745c5c3_Err
1523 }1529 }
1524- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</a></dd><dt>actor</dt><dd>")1530+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "\">")
1525 if templ_7745c5c3_Err != nil {1531 if templ_7745c5c3_Err != nil {
1526 return templ_7745c5c3_Err1532 return templ_7745c5c3_Err
1527 }1533 }
1528 var templ_7745c5c3_Var82 string1534 var templ_7745c5c3_Var82 string
1529- templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(p.Actor)1535+ templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinStringErrs(p.Ref)
1530 if templ_7745c5c3_Err != nil {1536 if templ_7745c5c3_Err != nil {
1531- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 223, Col: 16}1537+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 224, Col: 51}
1532 }1538 }
1533 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82))1539 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82))
1534 if templ_7745c5c3_Err != nil {1540 if templ_7745c5c3_Err != nil {
1535 return templ_7745c5c3_Err1541 return templ_7745c5c3_Err
1536 }1542 }
1537- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "</dd><dt>attempt</dt><dd>")1543+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "</a></dd><dt>actor</dt><dd>")
1538 if templ_7745c5c3_Err != nil {1544 if templ_7745c5c3_Err != nil {
1539 return templ_7745c5c3_Err1545 return templ_7745c5c3_Err
1540 }1546 }
1541 var templ_7745c5c3_Var83 string1547 var templ_7745c5c3_Var83 string
1542- templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Attempt)))1548+ templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(p.Actor)
1543 if templ_7745c5c3_Err != nil {1549 if templ_7745c5c3_Err != nil {
1544- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 225, Col: 29}1550+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 226, Col: 16}
1545 }1551 }
1546 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))1552 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))
1547 if templ_7745c5c3_Err != nil {1553 if templ_7745c5c3_Err != nil {
1548 return templ_7745c5c3_Err1554 return templ_7745c5c3_Err
1549 }1555 }
1550- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, " of ")1556+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "</dd><dt>attempt</dt><dd>")
1551 if templ_7745c5c3_Err != nil {1557 if templ_7745c5c3_Err != nil {
1552 return templ_7745c5c3_Err1558 return templ_7745c5c3_Err
1553 }1559 }
1554 var templ_7745c5c3_Var84 string1560 var templ_7745c5c3_Var84 string
1555- templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Latest)))1561+ templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Attempt)))
1556 if templ_7745c5c3_Err != nil {1562 if templ_7745c5c3_Err != nil {
1557- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 225, Col: 56}1563+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 228, Col: 29}
1558 }1564 }
1559 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84))1565 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84))
1560 if templ_7745c5c3_Err != nil {1566 if templ_7745c5c3_Err != nil {
1561 return templ_7745c5c3_Err1567 return templ_7745c5c3_Err
1562 }1568 }
1563- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "</dd></dl>")1569+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, " of ")
1570+ if templ_7745c5c3_Err != nil {
1571+ return templ_7745c5c3_Err
1572+ }
1573+ var templ_7745c5c3_Var85 string
1574+ templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(int(p.Latest)))
1575+ if templ_7745c5c3_Err != nil {
1576+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 228, Col: 56}
1577+ }
1578+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))
1579+ if templ_7745c5c3_Err != nil {
1580+ return templ_7745c5c3_Err
1581+ }
1582+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "</dd></dl>")
1564 if templ_7745c5c3_Err != nil {1583 if templ_7745c5c3_Err != nil {
1565 return templ_7745c5c3_Err1584 return templ_7745c5c3_Err
1566 }1585 }
1567 if len(p.Attempts) > 0 {1586 if len(p.Attempts) > 0 {
1568- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<ul class=\"attempts\">")1587+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<ul class=\"attempts\">")
1569 if templ_7745c5c3_Err != nil {1588 if templ_7745c5c3_Err != nil {
1570 return templ_7745c5c3_Err1589 return templ_7745c5c3_Err
1571 }1590 }
1572 for _, a := range p.Attempts {1591 for _, a := range p.Attempts {
1573- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "<li>")1592+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<li>")
1574 if templ_7745c5c3_Err != nil {1593 if templ_7745c5c3_Err != nil {
1575 return templ_7745c5c3_Err1594 return templ_7745c5c3_Err
1576 }1595 }
1577 if a.Current {1596 if a.Current {
1578- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<span aria-current=\"page\">")1597+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "<span aria-current=\"page\">")
1579 if templ_7745c5c3_Err != nil {1598 if templ_7745c5c3_Err != nil {
1580 return templ_7745c5c3_Err1599 return templ_7745c5c3_Err
1581 }1600 }
1582- var templ_7745c5c3_Var85 string1601+ var templ_7745c5c3_Var86 string
1583- templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)1602+ templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)
1584 if templ_7745c5c3_Err != nil {1603 if templ_7745c5c3_Err != nil {
1585- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 232, Col: 42}1604+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 235, Col: 42}
1586 }1605 }
1587- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))1606+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))
1588 if templ_7745c5c3_Err != nil {1607 if templ_7745c5c3_Err != nil {
1589 return templ_7745c5c3_Err1608 return templ_7745c5c3_Err
1590 }1609 }
1591- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "</span>")1610+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "</span>")
1592 if templ_7745c5c3_Err != nil {1611 if templ_7745c5c3_Err != nil {
1593 return templ_7745c5c3_Err1612 return templ_7745c5c3_Err
1594 }1613 }
1595 } else {1614 } else {
1596- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "<a href=\"")1615+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "<a href=\"")
1597 if templ_7745c5c3_Err != nil {1616 if templ_7745c5c3_Err != nil {
1598 return templ_7745c5c3_Err1617 return templ_7745c5c3_Err
1599 }1618 }
1600- var templ_7745c5c3_Var86 templ.SafeURL1619+ var templ_7745c5c3_Var87 templ.SafeURL
1601- templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(a.Href))1620+ templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(a.Href))
1602 if templ_7745c5c3_Err != nil {1621 if templ_7745c5c3_Err != nil {
1603- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 234, Col: 38}1622+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 237, Col: 38}
1604 }1623 }
1605- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))1624+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87))
1606 if templ_7745c5c3_Err != nil {1625 if templ_7745c5c3_Err != nil {
1607 return templ_7745c5c3_Err1626 return templ_7745c5c3_Err
1608 }1627 }
1609- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "\">")1628+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "\">")
1610 if templ_7745c5c3_Err != nil {1629 if templ_7745c5c3_Err != nil {
1611 return templ_7745c5c3_Err1630 return templ_7745c5c3_Err
1612 }1631 }
1613- var templ_7745c5c3_Var87 string1632+ var templ_7745c5c3_Var88 string
1614- templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)1633+ templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(a.Label)
1615 if templ_7745c5c3_Err != nil {1634 if templ_7745c5c3_Err != nil {
1616- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 234, Col: 50}1635+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 237, Col: 50}
1617 }1636 }
1618- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87))1637+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))
1619 if templ_7745c5c3_Err != nil {1638 if templ_7745c5c3_Err != nil {
1620 return templ_7745c5c3_Err1639 return templ_7745c5c3_Err
1621 }1640 }
1622- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</a>")1641+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "</a>")
1623 if templ_7745c5c3_Err != nil {1642 if templ_7745c5c3_Err != nil {
1624 return templ_7745c5c3_Err1643 return templ_7745c5c3_Err
1625 }1644 }
1626 }1645 }
1627- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "</li>")1646+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</li>")
1628 if templ_7745c5c3_Err != nil {1647 if templ_7745c5c3_Err != nil {
1629 return templ_7745c5c3_Err1648 return templ_7745c5c3_Err
1630 }1649 }
1631 }1650 }
1632- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "</ul>")1651+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "</ul>")
1633 if templ_7745c5c3_Err != nil {1652 if templ_7745c5c3_Err != nil {
1634 return templ_7745c5c3_Err1653 return templ_7745c5c3_Err
1635 }1654 }
1636 }1655 }
1637- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</section>")1656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "</section>")
1638 if templ_7745c5c3_Err != nil {1657 if templ_7745c5c3_Err != nil {
1639 return templ_7745c5c3_Err1658 return templ_7745c5c3_Err
1640 }1659 }
1641 if p.CancelAction != "" || p.RerunAction != "" {1660 if p.CancelAction != "" || p.RerunAction != "" {
1642- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<section aria-labelledby=\"r-act\" class=\"decide\"><h2 id=\"r-act\">Actions</h2><div class=\"d-btns\">")1661+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "<section aria-labelledby=\"r-act\" class=\"decide\"><h2 id=\"r-act\">Actions</h2><div class=\"d-btns\">")
1643 if templ_7745c5c3_Err != nil {1662 if templ_7745c5c3_Err != nil {
1644 return templ_7745c5c3_Err1663 return templ_7745c5c3_Err
1645 }1664 }
1646 if p.CancelAction != "" {1665 if p.CancelAction != "" {
1647- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "<form method=\"post\" action=\"")1666+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "<form method=\"post\" action=\"")
1648 if templ_7745c5c3_Err != nil {1667 if templ_7745c5c3_Err != nil {
1649 return templ_7745c5c3_Err1668 return templ_7745c5c3_Err
1650 }1669 }
1651- var templ_7745c5c3_Var88 templ.SafeURL1670+ var templ_7745c5c3_Var89 templ.SafeURL
1652- templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CancelAction))1671+ templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.CancelAction))
1653 if templ_7745c5c3_Err != nil {1672 if templ_7745c5c3_Err != nil {
1654- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 246, Col: 63}1673+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 249, Col: 63}
1655 }1674 }
1656- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))1675+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89))
1657 if templ_7745c5c3_Err != nil {1676 if templ_7745c5c3_Err != nil {
1658 return templ_7745c5c3_Err1677 return templ_7745c5c3_Err
1659 }1678 }
1660- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "\"><button type=\"submit\" class=\"btn\">Cancel run</button></form>")1679+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "\"><button type=\"submit\" class=\"btn\">Cancel run</button></form>")
1661 if templ_7745c5c3_Err != nil {1680 if templ_7745c5c3_Err != nil {
1662 return templ_7745c5c3_Err1681 return templ_7745c5c3_Err
1663 }1682 }
1664 }1683 }
1665 if p.RerunAction != "" {1684 if p.RerunAction != "" {
1666- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "<form method=\"post\" action=\"")1685+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<form method=\"post\" action=\"")
1667 if templ_7745c5c3_Err != nil {1686 if templ_7745c5c3_Err != nil {
1668 return templ_7745c5c3_Err1687 return templ_7745c5c3_Err
1669 }1688 }
1670- var templ_7745c5c3_Var89 templ.SafeURL1689+ var templ_7745c5c3_Var90 templ.SafeURL
1671- templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunAction))1690+ templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunAction))
1672 if templ_7745c5c3_Err != nil {1691 if templ_7745c5c3_Err != nil {
1673- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 249, Col: 62}1692+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 252, Col: 62}
1674 }1693 }
1675- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89))1694+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90))
1676 if templ_7745c5c3_Err != nil {1695 if templ_7745c5c3_Err != nil {
1677 return templ_7745c5c3_Err1696 return templ_7745c5c3_Err
1678 }1697 }
1679- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "\"><button type=\"submit\" class=\"btn primary\">Re-run all jobs</button></form>")1698+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "\"><button type=\"submit\" class=\"btn primary\">Re-run all jobs</button></form>")
1680 if templ_7745c5c3_Err != nil {1699 if templ_7745c5c3_Err != nil {
1681 return templ_7745c5c3_Err1700 return templ_7745c5c3_Err
1682 }1701 }
1683 }1702 }
1684 if p.CanRerunFailed {1703 if p.CanRerunFailed {
1685- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<form method=\"post\" action=\"")1704+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "<form method=\"post\" action=\"")
1686 if templ_7745c5c3_Err != nil {1705 if templ_7745c5c3_Err != nil {
1687 return templ_7745c5c3_Err1706 return templ_7745c5c3_Err
1688 }1707 }
1689- var templ_7745c5c3_Var90 templ.SafeURL1708+ var templ_7745c5c3_Var91 templ.SafeURL
1690- templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunFailedAction))1709+ templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RerunFailedAction))
1691 if templ_7745c5c3_Err != nil {1710 if templ_7745c5c3_Err != nil {
1692- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 252, Col: 68}1711+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 255, Col: 68}
1693 }1712 }
1694- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90))1713+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var91))
1695 if templ_7745c5c3_Err != nil {1714 if templ_7745c5c3_Err != nil {
1696 return templ_7745c5c3_Err1715 return templ_7745c5c3_Err
1697 }1716 }
1698- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "\"><button type=\"submit\" class=\"btn\">Re-run failed jobs</button></form>")1717+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "\"><button type=\"submit\" class=\"btn\">Re-run failed jobs</button></form>")
1699 if templ_7745c5c3_Err != nil {1718 if templ_7745c5c3_Err != nil {
1700 return templ_7745c5c3_Err1719 return templ_7745c5c3_Err
1701 }1720 }
1702 }1721 }
1703- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "</div></section>")1722+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "</div></section>")
1704 if templ_7745c5c3_Err != nil {1723 if templ_7745c5c3_Err != nil {
1705 return templ_7745c5c3_Err1724 return templ_7745c5c3_Err
1706 }1725 }
@@ -1727,25 +1746,25 @@ func JobHead(j JobRow, total int64) templ.Component {
1727 }()1746 }()
1728 }1747 }
1729 ctx = templ.InitializeContext(ctx)1748 ctx = templ.InitializeContext(ctx)
1730- templ_7745c5c3_Var91 := templ.GetChildren(ctx)1749+ templ_7745c5c3_Var92 := templ.GetChildren(ctx)
1731- if templ_7745c5c3_Var91 == nil {1750+ if templ_7745c5c3_Var92 == nil {
1732- templ_7745c5c3_Var91 = templ.NopComponent1751+ templ_7745c5c3_Var92 = templ.NopComponent
1733 }1752 }
1734 ctx = templ.ClearChildren(ctx)1753 ctx = templ.ClearChildren(ctx)
1735- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "<div id=\"job-head\" data-total=\"")1754+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<div id=\"job-head\" data-total=\"")
1736 if templ_7745c5c3_Err != nil {1755 if templ_7745c5c3_Err != nil {
1737 return templ_7745c5c3_Err1756 return templ_7745c5c3_Err
1738 }1757 }
1739- var templ_7745c5c3_Var92 string1758+ var templ_7745c5c3_Var93 string
1740- templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(total))1759+ templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(total))
1741 if templ_7745c5c3_Err != nil {1760 if templ_7745c5c3_Err != nil {
1742- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 262, Col: 46}1761+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 265, Col: 46}
1743 }1762 }
1744- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var92)1763+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var93)
1745 if templ_7745c5c3_Err != nil {1764 if templ_7745c5c3_Err != nil {
1746 return templ_7745c5c3_Err1765 return templ_7745c5c3_Err
1747 }1766 }
1748- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "\"><h2 class=\"run-h\">")1767+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "\"><h2 class=\"run-h\">")
1749 if templ_7745c5c3_Err != nil {1768 if templ_7745c5c3_Err != nil {
1750 return templ_7745c5c3_Err1769 return templ_7745c5c3_Err
1751 }1770 }
@@ -1753,74 +1772,74 @@ func JobHead(j JobRow, total int64) templ.Component {
1753 if templ_7745c5c3_Err != nil {1772 if templ_7745c5c3_Err != nil {
1754 return templ_7745c5c3_Err1773 return templ_7745c5c3_Err
1755 }1774 }
1756- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<span>")1775+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "<span>")
1757 if templ_7745c5c3_Err != nil {1776 if templ_7745c5c3_Err != nil {
1758 return templ_7745c5c3_Err1777 return templ_7745c5c3_Err
1759 }1778 }
1760- var templ_7745c5c3_Var93 string1779+ var templ_7745c5c3_Var94 string
1761- templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(j.Name)1780+ templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(j.Name)
1762 if templ_7745c5c3_Err != nil {1781 if templ_7745c5c3_Err != nil {
1763- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 61}1782+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 61}
1764 }1783 }
1765- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93))1784+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94))
1766 if templ_7745c5c3_Err != nil {1785 if templ_7745c5c3_Err != nil {
1767 return templ_7745c5c3_Err1786 return templ_7745c5c3_Err
1768 }1787 }
1769- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</span> <span class=\"dim\">")1788+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "</span> <span class=\"dim\">")
1770 if templ_7745c5c3_Err != nil {1789 if templ_7745c5c3_Err != nil {
1771 return templ_7745c5c3_Err1790 return templ_7745c5c3_Err
1772 }1791 }
1773- var templ_7745c5c3_Var94 string1792+ var templ_7745c5c3_Var95 string
1774- templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(j.State)1793+ templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(j.State)
1775 if templ_7745c5c3_Err != nil {1794 if templ_7745c5c3_Err != nil {
1776- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 98}1795+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 98}
1777 }1796 }
1778- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94))1797+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))
1779 if templ_7745c5c3_Err != nil {1798 if templ_7745c5c3_Err != nil {
1780 return templ_7745c5c3_Err1799 return templ_7745c5c3_Err
1781 }1800 }
1782- var templ_7745c5c3_Var95 string1801+ var templ_7745c5c3_Var96 string
1783- templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(" ")1802+ templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
1784 if templ_7745c5c3_Err != nil {1803 if templ_7745c5c3_Err != nil {
1785- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 105}1804+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 105}
1786 }1805 }
1787- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))1806+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))
1788 if templ_7745c5c3_Err != nil {1807 if templ_7745c5c3_Err != nil {
1789 return templ_7745c5c3_Err1808 return templ_7745c5c3_Err
1790 }1809 }
1791- var templ_7745c5c3_Var96 string1810+ var templ_7745c5c3_Var97 string
1792- templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(j.Duration)1811+ templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(j.Duration)
1793 if templ_7745c5c3_Err != nil {1812 if templ_7745c5c3_Err != nil {
1794- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 263, Col: 119}1813+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 266, Col: 119}
1795 }1814 }
1796- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))1815+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97))
1797 if templ_7745c5c3_Err != nil {1816 if templ_7745c5c3_Err != nil {
1798 return templ_7745c5c3_Err1817 return templ_7745c5c3_Err
1799 }1818 }
1800- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "</span></h2>")1819+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</span></h2>")
1801 if templ_7745c5c3_Err != nil {1820 if templ_7745c5c3_Err != nil {
1802 return templ_7745c5c3_Err1821 return templ_7745c5c3_Err
1803 }1822 }
1804 if j.Error != "" {1823 if j.Error != "" {
1805- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "<p class=\"job-err\">")1824+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "<p class=\"job-err\">")
1806 if templ_7745c5c3_Err != nil {1825 if templ_7745c5c3_Err != nil {
1807 return templ_7745c5c3_Err1826 return templ_7745c5c3_Err
1808 }1827 }
1809- var templ_7745c5c3_Var97 string1828+ var templ_7745c5c3_Var98 string
1810- templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(j.Error)1829+ templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(j.Error)
1811 if templ_7745c5c3_Err != nil {1830 if templ_7745c5c3_Err != nil {
1812- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 265, Col: 31}1831+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 268, Col: 31}
1813 }1832 }
1814- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97))1833+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98))
1815 if templ_7745c5c3_Err != nil {1834 if templ_7745c5c3_Err != nil {
1816 return templ_7745c5c3_Err1835 return templ_7745c5c3_Err
1817 }1836 }
1818- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</p>")1837+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "</p>")
1819 if templ_7745c5c3_Err != nil {1838 if templ_7745c5c3_Err != nil {
1820 return templ_7745c5c3_Err1839 return templ_7745c5c3_Err
1821 }1840 }
1822 }1841 }
1823- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "</div>")1842+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "</div>")
1824 if templ_7745c5c3_Err != nil {1843 if templ_7745c5c3_Err != nil {
1825 return templ_7745c5c3_Err1844 return templ_7745c5c3_Err
1826 }1845 }
@@ -1845,17 +1864,17 @@ func JobSteps(steps []StepView) templ.Component {
1845 }()1864 }()
1846 }1865 }
1847 ctx = templ.InitializeContext(ctx)1866 ctx = templ.InitializeContext(ctx)
1848- templ_7745c5c3_Var98 := templ.GetChildren(ctx)1867+ templ_7745c5c3_Var99 := templ.GetChildren(ctx)
1849- if templ_7745c5c3_Var98 == nil {1868+ if templ_7745c5c3_Var99 == nil {
1850- templ_7745c5c3_Var98 = templ.NopComponent1869+ templ_7745c5c3_Var99 = templ.NopComponent
1851 }1870 }
1852 ctx = templ.ClearChildren(ctx)1871 ctx = templ.ClearChildren(ctx)
1853- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "<ol class=\"steps\" id=\"job-steps\">")1872+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "<ol class=\"steps\" id=\"job-steps\">")
1854 if templ_7745c5c3_Err != nil {1873 if templ_7745c5c3_Err != nil {
1855 return templ_7745c5c3_Err1874 return templ_7745c5c3_Err
1856 }1875 }
1857 for _, s := range steps {1876 for _, s := range steps {
1858- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "<li>")1877+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "<li>")
1859 if templ_7745c5c3_Err != nil {1878 if templ_7745c5c3_Err != nil {
1860 return templ_7745c5c3_Err1879 return templ_7745c5c3_Err
1861 }1880 }
@@ -1864,74 +1883,74 @@ func JobSteps(steps []StepView) templ.Component {
1864 return templ_7745c5c3_Err1883 return templ_7745c5c3_Err
1865 }1884 }
1866 if s.LogEnd > s.LogStart {1885 if s.LogEnd > s.LogStart {
1867- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "<a href=\"")1886+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "<a href=\"")
1868 if templ_7745c5c3_Err != nil {1887 if templ_7745c5c3_Err != nil {
1869 return templ_7745c5c3_Err1888 return templ_7745c5c3_Err
1870 }1889 }
1871- var templ_7745c5c3_Var99 templ.SafeURL1890+ var templ_7745c5c3_Var100 templ.SafeURL
1872- templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#L" + itoa64(s.LogStart)))1891+ templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("#L" + itoa64(s.LogStart)))
1873 if templ_7745c5c3_Err != nil {1892 if templ_7745c5c3_Err != nil {
1874- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 277, Col: 55}1893+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 280, Col: 55}
1875 }1894 }
1876- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99))1895+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))
1877 if templ_7745c5c3_Err != nil {1896 if templ_7745c5c3_Err != nil {
1878 return templ_7745c5c3_Err1897 return templ_7745c5c3_Err
1879 }1898 }
1880- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "\">")1899+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "\">")
1881 if templ_7745c5c3_Err != nil {1900 if templ_7745c5c3_Err != nil {
1882 return templ_7745c5c3_Err1901 return templ_7745c5c3_Err
1883 }1902 }
1884- var templ_7745c5c3_Var100 string1903+ var templ_7745c5c3_Var101 string
1885- templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)1904+ templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)
1886 if templ_7745c5c3_Err != nil {1905 if templ_7745c5c3_Err != nil {
1887- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 277, Col: 66}1906+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 280, Col: 66}
1888 }1907 }
1889- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))1908+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101))
1890 if templ_7745c5c3_Err != nil {1909 if templ_7745c5c3_Err != nil {
1891 return templ_7745c5c3_Err1910 return templ_7745c5c3_Err
1892 }1911 }
1893- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "</a> ")1912+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "</a> ")
1894 if templ_7745c5c3_Err != nil {1913 if templ_7745c5c3_Err != nil {
1895 return templ_7745c5c3_Err1914 return templ_7745c5c3_Err
1896 }1915 }
1897 } else {1916 } else {
1898- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "<span>")1917+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<span>")
1899 if templ_7745c5c3_Err != nil {1918 if templ_7745c5c3_Err != nil {
1900 return templ_7745c5c3_Err1919 return templ_7745c5c3_Err
1901 }1920 }
1902- var templ_7745c5c3_Var101 string1921+ var templ_7745c5c3_Var102 string
1903- templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)1922+ templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(s.Name)
1904 if templ_7745c5c3_Err != nil {1923 if templ_7745c5c3_Err != nil {
1905- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 279, Col: 19}1924+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 282, Col: 19}
1906 }1925 }
1907- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101))1926+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
1908 if templ_7745c5c3_Err != nil {1927 if templ_7745c5c3_Err != nil {
1909 return templ_7745c5c3_Err1928 return templ_7745c5c3_Err
1910 }1929 }
1911- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "</span> ")1930+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "</span> ")
1912 if templ_7745c5c3_Err != nil {1931 if templ_7745c5c3_Err != nil {
1913 return templ_7745c5c3_Err1932 return templ_7745c5c3_Err
1914 }1933 }
1915 }1934 }
1916- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<span class=\"dim\">")1935+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "<span class=\"dim\">")
1917 if templ_7745c5c3_Err != nil {1936 if templ_7745c5c3_Err != nil {
1918 return templ_7745c5c3_Err1937 return templ_7745c5c3_Err
1919 }1938 }
1920- var templ_7745c5c3_Var102 string1939+ var templ_7745c5c3_Var103 string
1921- templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(s.Duration)1940+ templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(s.Duration)
1922 if templ_7745c5c3_Err != nil {1941 if templ_7745c5c3_Err != nil {
1923- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 281, Col: 34}1942+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 284, Col: 34}
1924 }1943 }
1925- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))1944+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))
1926 if templ_7745c5c3_Err != nil {1945 if templ_7745c5c3_Err != nil {
1927 return templ_7745c5c3_Err1946 return templ_7745c5c3_Err
1928 }1947 }
1929- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "</span></li>")1948+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "</span></li>")
1930 if templ_7745c5c3_Err != nil {1949 if templ_7745c5c3_Err != nil {
1931 return templ_7745c5c3_Err1950 return templ_7745c5c3_Err
1932 }1951 }
1933 }1952 }
1934- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "</ol>")1953+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "</ol>")
1935 if templ_7745c5c3_Err != nil {1954 if templ_7745c5c3_Err != nil {
1936 return templ_7745c5c3_Err1955 return templ_7745c5c3_Err
1937 }1956 }
@@ -1955,12 +1974,12 @@ func Job(l Layout, p JobPage) templ.Component {
1955 }()1974 }()
1956 }1975 }
1957 ctx = templ.InitializeContext(ctx)1976 ctx = templ.InitializeContext(ctx)
1958- templ_7745c5c3_Var103 := templ.GetChildren(ctx)1977+ templ_7745c5c3_Var104 := templ.GetChildren(ctx)
1959- if templ_7745c5c3_Var103 == nil {1978+ if templ_7745c5c3_Var104 == nil {
1960- templ_7745c5c3_Var103 = templ.NopComponent1979+ templ_7745c5c3_Var104 = templ.NopComponent
1961 }1980 }
1962 ctx = templ.ClearChildren(ctx)1981 ctx = templ.ClearChildren(ctx)
1963- templ_7745c5c3_Var104 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {1982+ templ_7745c5c3_Var105 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1964 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context1983 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
1965 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)1984 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
1966 if !templ_7745c5c3_IsBuffer {1985 if !templ_7745c5c3_IsBuffer {
@@ -1976,7 +1995,7 @@ func Job(l Layout, p JobPage) templ.Component {
1976 if templ_7745c5c3_Err != nil {1995 if templ_7745c5c3_Err != nil {
1977 return templ_7745c5c3_Err1996 return templ_7745c5c3_Err
1978 }1997 }
1979- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, " ")1998+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, " ")
1980 if templ_7745c5c3_Err != nil {1999 if templ_7745c5c3_Err != nil {
1981 return templ_7745c5c3_Err2000 return templ_7745c5c3_Err
1982 }2001 }
@@ -1984,7 +2003,7 @@ func Job(l Layout, p JobPage) templ.Component {
1984 if templ_7745c5c3_Err != nil {2003 if templ_7745c5c3_Err != nil {
1985 return templ_7745c5c3_Err2004 return templ_7745c5c3_Err
1986 }2005 }
1987- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, " ")2006+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, " ")
1988 if templ_7745c5c3_Err != nil {2007 if templ_7745c5c3_Err != nil {
1989 return templ_7745c5c3_Err2008 return templ_7745c5c3_Err
1990 }2009 }
@@ -1992,111 +2011,111 @@ func Job(l Layout, p JobPage) templ.Component {
1992 if templ_7745c5c3_Err != nil {2011 if templ_7745c5c3_Err != nil {
1993 return templ_7745c5c3_Err2012 return templ_7745c5c3_Err
1994 }2013 }
1995- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, " <div id=\"job-log\" class=\"joblog\" data-total=\"")2014+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, " <div id=\"job-log\" class=\"joblog\" data-total=\"")
1996 if templ_7745c5c3_Err != nil {2015 if templ_7745c5c3_Err != nil {
1997 return templ_7745c5c3_Err2016 return templ_7745c5c3_Err
1998 }2017 }
1999- var templ_7745c5c3_Var105 string2018+ var templ_7745c5c3_Var106 string
2000- templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.Total))2019+ templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.Total))
2001 if templ_7745c5c3_Err != nil {2020 if templ_7745c5c3_Err != nil {
2002- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 292, Col: 63}2021+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 295, Col: 63}
2003 }2022 }
2004- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var105)2023+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var106)
2005 if templ_7745c5c3_Err != nil {2024 if templ_7745c5c3_Err != nil {
2006 return templ_7745c5c3_Err2025 return templ_7745c5c3_Err
2007 }2026 }
2008- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "\"")2027+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "\"")
2009 if templ_7745c5c3_Err != nil {2028 if templ_7745c5c3_Err != nil {
2010 return templ_7745c5c3_Err2029 return templ_7745c5c3_Err
2011 }2030 }
2012 if p.Follow {2031 if p.Follow {
2013- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, " data-follow")2032+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, " data-follow")
2014 if templ_7745c5c3_Err != nil {2033 if templ_7745c5c3_Err != nil {
2015 return templ_7745c5c3_Err2034 return templ_7745c5c3_Err
2016 }2035 }
2017 }2036 }
2018- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, " data-stream=\"")2037+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, " data-stream=\"")
2019 if templ_7745c5c3_Err != nil {2038 if templ_7745c5c3_Err != nil {
2020 return templ_7745c5c3_Err2039 return templ_7745c5c3_Err
2021 }2040 }
2022- var templ_7745c5c3_Var106 string2041+ var templ_7745c5c3_Var107 string
2023- templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.StreamHref)2042+ templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.StreamHref)
2024 if templ_7745c5c3_Err != nil {2043 if templ_7745c5c3_Err != nil {
2025- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 292, Col: 118}2044+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 295, Col: 118}
2026 }2045 }
2027- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var106)2046+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var107)
2028 if templ_7745c5c3_Err != nil {2047 if templ_7745c5c3_Err != nil {
2029 return templ_7745c5c3_Err2048 return templ_7745c5c3_Err
2030 }2049 }
2031- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "\" data-next=\"")2050+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "\" data-next=\"")
2032 if templ_7745c5c3_Err != nil {2051 if templ_7745c5c3_Err != nil {
2033 return templ_7745c5c3_Err2052 return templ_7745c5c3_Err
2034 }2053 }
2035- var templ_7745c5c3_Var107 string2054+ var templ_7745c5c3_Var108 string
2036- templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + int64(len(p.Lines))))2055+ templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + int64(len(p.Lines))))
2037 if templ_7745c5c3_Err != nil {2056 if templ_7745c5c3_Err != nil {
2038- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 292, Col: 169}2057+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 295, Col: 169}
2039 }2058 }
2040- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var107)2059+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var108)
2041 if templ_7745c5c3_Err != nil {2060 if templ_7745c5c3_Err != nil {
2042 return templ_7745c5c3_Err2061 return templ_7745c5c3_Err
2043 }2062 }
2044- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "\">")2063+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "\">")
2045 if templ_7745c5c3_Err != nil {2064 if templ_7745c5c3_Err != nil {
2046 return templ_7745c5c3_Err2065 return templ_7745c5c3_Err
2047 }2066 }
2048 if p.Note != "" {2067 if p.Note != "" {
2049- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "<p class=\"dim\">")2068+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<p class=\"dim\">")
2050 if templ_7745c5c3_Err != nil {2069 if templ_7745c5c3_Err != nil {
2051 return templ_7745c5c3_Err2070 return templ_7745c5c3_Err
2052 }2071 }
2053- var templ_7745c5c3_Var108 string2072+ var templ_7745c5c3_Var109 string
2054- templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)2073+ templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
2055 if templ_7745c5c3_Err != nil {2074 if templ_7745c5c3_Err != nil {
2056- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 294, Col: 27}2075+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 297, Col: 27}
2057 }2076 }
2058- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var108))2077+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))
2059 if templ_7745c5c3_Err != nil {2078 if templ_7745c5c3_Err != nil {
2060 return templ_7745c5c3_Err2079 return templ_7745c5c3_Err
2061 }2080 }
2062- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</p>")2081+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "</p>")
2063 if templ_7745c5c3_Err != nil {2082 if templ_7745c5c3_Err != nil {
2064 return templ_7745c5c3_Err2083 return templ_7745c5c3_Err
2065 }2084 }
2066 }2085 }
2067 if p.Prev != "" {2086 if p.Prev != "" {
2068- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<p class=\"more\"><a href=\"")2087+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "<p class=\"more\"><a href=\"")
2069 if templ_7745c5c3_Err != nil {2088 if templ_7745c5c3_Err != nil {
2070 return templ_7745c5c3_Err2089 return templ_7745c5c3_Err
2071 }2090 }
2072- var templ_7745c5c3_Var109 templ.SafeURL2091+ var templ_7745c5c3_Var110 templ.SafeURL
2073- templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Prev))2092+ templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Prev))
2074 if templ_7745c5c3_Err != nil {2093 if templ_7745c5c3_Err != nil {
2075- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 297, Col: 51}2094+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 300, Col: 51}
2076 }2095 }
2077- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))2096+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var110))
2078 if templ_7745c5c3_Err != nil {2097 if templ_7745c5c3_Err != nil {
2079 return templ_7745c5c3_Err2098 return templ_7745c5c3_Err
2080 }2099 }
2081- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "\" rel=\"prev\">earlier lines</a></p>")2100+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "\" rel=\"prev\">earlier lines</a></p>")
2082 if templ_7745c5c3_Err != nil {2101 if templ_7745c5c3_Err != nil {
2083 return templ_7745c5c3_Err2102 return templ_7745c5c3_Err
2084 }2103 }
2085 }2104 }
2086- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "<ol class=\"log\" start=\"")2105+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "<ol class=\"log\" start=\"")
2087 if templ_7745c5c3_Err != nil {2106 if templ_7745c5c3_Err != nil {
2088 return templ_7745c5c3_Err2107 return templ_7745c5c3_Err
2089 }2108 }
2090- var templ_7745c5c3_Var110 string2109+ var templ_7745c5c3_Var111 string
2091- templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + 1))2110+ templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa64(p.From + 1))
2092 if templ_7745c5c3_Err != nil {2111 if templ_7745c5c3_Err != nil {
2093- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 299, Col: 45}2112+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 302, Col: 45}
2094 }2113 }
2095- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var110)2114+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var111)
2096 if templ_7745c5c3_Err != nil {2115 if templ_7745c5c3_Err != nil {
2097 return templ_7745c5c3_Err2116 return templ_7745c5c3_Err
2098 }2117 }
2099- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "\">")2118+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "\">")
2100 if templ_7745c5c3_Err != nil {2119 if templ_7745c5c3_Err != nil {
2101 return templ_7745c5c3_Err2120 return templ_7745c5c3_Err
2102 }2121 }
@@ -2106,36 +2125,36 @@ func Job(l Layout, p JobPage) templ.Component {
2106 return templ_7745c5c3_Err2125 return templ_7745c5c3_Err
2107 }2126 }
2108 }2127 }
2109- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "</ol>")2128+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "</ol>")
2110 if templ_7745c5c3_Err != nil {2129 if templ_7745c5c3_Err != nil {
2111 return templ_7745c5c3_Err2130 return templ_7745c5c3_Err
2112 }2131 }
2113 if p.Next != "" {2132 if p.Next != "" {
2114- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "<p class=\"more\"><a href=\"")2133+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "<p class=\"more\"><a href=\"")
2115 if templ_7745c5c3_Err != nil {2134 if templ_7745c5c3_Err != nil {
2116 return templ_7745c5c3_Err2135 return templ_7745c5c3_Err
2117 }2136 }
2118- var templ_7745c5c3_Var111 templ.SafeURL2137+ var templ_7745c5c3_Var112 templ.SafeURL
2119- templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Next))2138+ templ_7745c5c3_Var112, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Next))
2120 if templ_7745c5c3_Err != nil {2139 if templ_7745c5c3_Err != nil {
2121- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 305, Col: 51}2140+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 308, Col: 51}
2122 }2141 }
2123- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var111))2142+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var112))
2124 if templ_7745c5c3_Err != nil {2143 if templ_7745c5c3_Err != nil {
2125 return templ_7745c5c3_Err2144 return templ_7745c5c3_Err
2126 }2145 }
2127- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "\" rel=\"next\">more lines</a></p>")2146+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "\" rel=\"next\">more lines</a></p>")
2128 if templ_7745c5c3_Err != nil {2147 if templ_7745c5c3_Err != nil {
2129 return templ_7745c5c3_Err2148 return templ_7745c5c3_Err
2130 }2149 }
2131 }2150 }
2132- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "</div>")2151+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "</div>")
2133 if templ_7745c5c3_Err != nil {2152 if templ_7745c5c3_Err != nil {
2134 return templ_7745c5c3_Err2153 return templ_7745c5c3_Err
2135 }2154 }
2136 return nil2155 return nil
2137 })2156 })
2138- templ_7745c5c3_Err = Shell(l, jobRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var104), templ_7745c5c3_Buffer)2157+ templ_7745c5c3_Err = Shell(l, jobRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var105), templ_7745c5c3_Buffer)
2139 if templ_7745c5c3_Err != nil {2158 if templ_7745c5c3_Err != nil {
2140 return templ_7745c5c3_Err2159 return templ_7745c5c3_Err
2141 }2160 }
@@ -2161,92 +2180,92 @@ func LogLine(ln LogLineView) templ.Component {
2161 }()2180 }()
2162 }2181 }
2163 ctx = templ.InitializeContext(ctx)2182 ctx = templ.InitializeContext(ctx)
2164- templ_7745c5c3_Var112 := templ.GetChildren(ctx)2183+ templ_7745c5c3_Var113 := templ.GetChildren(ctx)
2165- if templ_7745c5c3_Var112 == nil {2184+ if templ_7745c5c3_Var113 == nil {
2166- templ_7745c5c3_Var112 = templ.NopComponent2185+ templ_7745c5c3_Var113 = templ.NopComponent
2167 }2186 }
2168 ctx = templ.ClearChildren(ctx)2187 ctx = templ.ClearChildren(ctx)
2169- var templ_7745c5c3_Var113 = []any{logClass(ln)}2188+ var templ_7745c5c3_Var114 = []any{logClass(ln)}
2170- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var113...)2189+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var114...)
2171 if templ_7745c5c3_Err != nil {2190 if templ_7745c5c3_Err != nil {
2172 return templ_7745c5c3_Err2191 return templ_7745c5c3_Err
2173 }2192 }
2174- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "<li id=\"")2193+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "<li id=\"")
2175 if templ_7745c5c3_Err != nil {2194 if templ_7745c5c3_Err != nil {
2176 return templ_7745c5c3_Err2195 return templ_7745c5c3_Err
2177 }2196 }
2178- var templ_7745c5c3_Var114 string2197+ var templ_7745c5c3_Var115 string
2179- templ_7745c5c3_Var114, templ_7745c5c3_Err = templ.ResolveAttributeValue("L" + itoa64(ln.N))2198+ templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.ResolveAttributeValue("L" + itoa64(ln.N))
2180 if templ_7745c5c3_Err != nil {2199 if templ_7745c5c3_Err != nil {
2181- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 28}2200+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 28}
2182 }2201 }
2183- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var114)2202+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var115)
2184 if templ_7745c5c3_Err != nil {2203 if templ_7745c5c3_Err != nil {
2185 return templ_7745c5c3_Err2204 return templ_7745c5c3_Err
2186 }2205 }
2187- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "\" class=\"")2206+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "\" class=\"")
2188 if templ_7745c5c3_Err != nil {2207 if templ_7745c5c3_Err != nil {
2189 return templ_7745c5c3_Err2208 return templ_7745c5c3_Err
2190 }2209 }
2191- var templ_7745c5c3_Var115 string2210+ var templ_7745c5c3_Var116 string
2192- templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var113).String())2211+ templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var114).String())
2193 if templ_7745c5c3_Err != nil {2212 if templ_7745c5c3_Err != nil {
2194 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 1, Col: 0}2213 return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 1, Col: 0}
2195 }2214 }
2196- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var115)2215+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var116)
2197 if templ_7745c5c3_Err != nil {2216 if templ_7745c5c3_Err != nil {
2198 return templ_7745c5c3_Err2217 return templ_7745c5c3_Err
2199 }2218 }
2200- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "\" data-step=\"")2219+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "\" data-step=\"")
2201 if templ_7745c5c3_Err != nil {2220 if templ_7745c5c3_Err != nil {
2202 return templ_7745c5c3_Err2221 return templ_7745c5c3_Err
2203 }2222 }
2204- var templ_7745c5c3_Var116 string2223+ var templ_7745c5c3_Var117 string
2205- templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(int(ln.Step)))2224+ templ_7745c5c3_Var117, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(int(ln.Step)))
2206 if templ_7745c5c3_Err != nil {2225 if templ_7745c5c3_Err != nil {
2207- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 84}2226+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 84}
2208 }2227 }
2209- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var116)2228+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var117)
2210 if templ_7745c5c3_Err != nil {2229 if templ_7745c5c3_Err != nil {
2211 return templ_7745c5c3_Err2230 return templ_7745c5c3_Err
2212 }2231 }
2213- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "\"")2232+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "\"")
2214 if templ_7745c5c3_Err != nil {2233 if templ_7745c5c3_Err != nil {
2215 return templ_7745c5c3_Err2234 return templ_7745c5c3_Err
2216 }2235 }
2217 if ln.Continued {2236 if ln.Continued {
2218- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, " data-cont")2237+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, " data-cont")
2219 if templ_7745c5c3_Err != nil {2238 if templ_7745c5c3_Err != nil {
2220 return templ_7745c5c3_Err2239 return templ_7745c5c3_Err
2221 }2240 }
2222 }2241 }
2223- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "><time>")2242+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "><time>")
2224 if templ_7745c5c3_Err != nil {2243 if templ_7745c5c3_Err != nil {
2225 return templ_7745c5c3_Err2244 return templ_7745c5c3_Err
2226 }2245 }
2227- var templ_7745c5c3_Var117 string2246+ var templ_7745c5c3_Var118 string
2228- templ_7745c5c3_Var117, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Time)2247+ templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Time)
2229 if templ_7745c5c3_Err != nil {2248 if templ_7745c5c3_Err != nil {
2230- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 130}2249+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 130}
2231 }2250 }
2232- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var117))2251+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
2233 if templ_7745c5c3_Err != nil {2252 if templ_7745c5c3_Err != nil {
2234 return templ_7745c5c3_Err2253 return templ_7745c5c3_Err
2235 }2254 }
2236- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "</time><span class=\"t\">")2255+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "</time><span class=\"t\">")
2237 if templ_7745c5c3_Err != nil {2256 if templ_7745c5c3_Err != nil {
2238 return templ_7745c5c3_Err2257 return templ_7745c5c3_Err
2239 }2258 }
2240- var templ_7745c5c3_Var118 string2259+ var templ_7745c5c3_Var119 string
2241- templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Text)2260+ templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.JoinStringErrs(ln.Text)
2242 if templ_7745c5c3_Err != nil {2261 if templ_7745c5c3_Err != nil {
2243- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 314, Col: 164}2262+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 317, Col: 164}
2244 }2263 }
2245- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))2264+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var119))
2246 if templ_7745c5c3_Err != nil {2265 if templ_7745c5c3_Err != nil {
2247 return templ_7745c5c3_Err2266 return templ_7745c5c3_Err
2248 }2267 }
2249- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "</span></li>")2268+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "</span></li>")
2250 if templ_7745c5c3_Err != nil {2269 if templ_7745c5c3_Err != nil {
2251 return templ_7745c5c3_Err2270 return templ_7745c5c3_Err
2252 }2271 }
@@ -2270,12 +2289,12 @@ func jobRail(p JobPage) templ.Component {
2270 }()2289 }()
2271 }2290 }
2272 ctx = templ.InitializeContext(ctx)2291 ctx = templ.InitializeContext(ctx)
2273- templ_7745c5c3_Var119 := templ.GetChildren(ctx)2292+ templ_7745c5c3_Var120 := templ.GetChildren(ctx)
2274- if templ_7745c5c3_Var119 == nil {2293+ if templ_7745c5c3_Var120 == nil {
2275- templ_7745c5c3_Var119 = templ.NopComponent2294+ templ_7745c5c3_Var120 = templ.NopComponent
2276 }2295 }
2277 ctx = templ.ClearChildren(ctx)2296 ctx = templ.ClearChildren(ctx)
2278- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "<section aria-labelledby=\"r-job\"><h2 id=\"r-job\">Steps</h2>")2297+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "<section aria-labelledby=\"r-job\"><h2 id=\"r-job\">Steps</h2>")
2279 if templ_7745c5c3_Err != nil {2298 if templ_7745c5c3_Err != nil {
2280 return templ_7745c5c3_Err2299 return templ_7745c5c3_Err
2281 }2300 }
@@ -2283,101 +2302,101 @@ func jobRail(p JobPage) templ.Component {
2283 if templ_7745c5c3_Err != nil {2302 if templ_7745c5c3_Err != nil {
2284 return templ_7745c5c3_Err2303 return templ_7745c5c3_Err
2285 }2304 }
2286- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "</section><section aria-labelledby=\"r-jobf\"><h2 id=\"r-jobf\">Job</h2><dl class=\"kv\"><dt>run</dt><dd><a href=\"")2305+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "</section><section aria-labelledby=\"r-jobf\"><h2 id=\"r-jobf\">Job</h2><dl class=\"kv\"><dt>run</dt><dd><a href=\"")
2287 if templ_7745c5c3_Err != nil {2306 if templ_7745c5c3_Err != nil {
2288 return templ_7745c5c3_Err2307 return templ_7745c5c3_Err
2289 }2308 }
2290- var templ_7745c5c3_Var120 templ.SafeURL2309+ var templ_7745c5c3_Var121 templ.SafeURL
2291- templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RunHref))2310+ templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RunHref))
2292 if templ_7745c5c3_Err != nil {2311 if templ_7745c5c3_Err != nil {
2293- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 326, Col: 41}2312+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 329, Col: 41}
2294 }2313 }
2295- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var120))2314+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))
2296 if templ_7745c5c3_Err != nil {2315 if templ_7745c5c3_Err != nil {
2297 return templ_7745c5c3_Err2316 return templ_7745c5c3_Err
2298 }2317 }
2299- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "\">")2318+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "\">")
2300 if templ_7745c5c3_Err != nil {2319 if templ_7745c5c3_Err != nil {
2301 return templ_7745c5c3_Err2320 return templ_7745c5c3_Err
2302 }2321 }
2303- var templ_7745c5c3_Var121 string2322+ var templ_7745c5c3_Var122 string
2304- templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinStringErrs(p.RunName)2323+ templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinStringErrs(p.RunName)
2305 if templ_7745c5c3_Err != nil {2324 if templ_7745c5c3_Err != nil {
2306- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 326, Col: 55}2325+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 329, Col: 55}
2307 }2326 }
2308- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))2327+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))
2309 if templ_7745c5c3_Err != nil {2328 if templ_7745c5c3_Err != nil {
2310 return templ_7745c5c3_Err2329 return templ_7745c5c3_Err
2311 }2330 }
2312- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "</a></dd>")2331+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "</a></dd>")
2313 if templ_7745c5c3_Err != nil {2332 if templ_7745c5c3_Err != nil {
2314 return templ_7745c5c3_Err2333 return templ_7745c5c3_Err
2315 }2334 }
2316 if p.Runner != "" {2335 if p.Runner != "" {
2317- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "<dt>runner</dt><dd>")2336+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "<dt>runner</dt><dd>")
2318 if templ_7745c5c3_Err != nil {2337 if templ_7745c5c3_Err != nil {
2319 return templ_7745c5c3_Err2338 return templ_7745c5c3_Err
2320 }2339 }
2321- var templ_7745c5c3_Var122 string2340+ var templ_7745c5c3_Var123 string
2322- templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinStringErrs(p.Runner)2341+ templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Runner)
2323 if templ_7745c5c3_Err != nil {2342 if templ_7745c5c3_Err != nil {
2324- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 329, Col: 18}2343+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 332, Col: 18}
2325 }2344 }
2326- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))2345+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))
2327 if templ_7745c5c3_Err != nil {2346 if templ_7745c5c3_Err != nil {
2328 return templ_7745c5c3_Err2347 return templ_7745c5c3_Err
2329 }2348 }
2330- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "</dd>")2349+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "</dd>")
2331 if templ_7745c5c3_Err != nil {2350 if templ_7745c5c3_Err != nil {
2332 return templ_7745c5c3_Err2351 return templ_7745c5c3_Err
2333 }2352 }
2334 }2353 }
2335 if p.Isolation != "" {2354 if p.Isolation != "" {
2336- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "<dt>isolation</dt><dd>")2355+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<dt>isolation</dt><dd>")
2337 if templ_7745c5c3_Err != nil {2356 if templ_7745c5c3_Err != nil {
2338 return templ_7745c5c3_Err2357 return templ_7745c5c3_Err
2339 }2358 }
2340- var templ_7745c5c3_Var123 string2359+ var templ_7745c5c3_Var124 string
2341- templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Isolation)2360+ templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(p.Isolation)
2342 if templ_7745c5c3_Err != nil {2361 if templ_7745c5c3_Err != nil {
2343- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 333, Col: 21}2362+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 336, Col: 21}
2344 }2363 }
2345- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))2364+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))
2346 if templ_7745c5c3_Err != nil {2365 if templ_7745c5c3_Err != nil {
2347 return templ_7745c5c3_Err2366 return templ_7745c5c3_Err
2348 }2367 }
2349- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "</dd>")2368+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "</dd>")
2350 if templ_7745c5c3_Err != nil {2369 if templ_7745c5c3_Err != nil {
2351 return templ_7745c5c3_Err2370 return templ_7745c5c3_Err
2352 }2371 }
2353 }2372 }
2354- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<dt>lines</dt><dd>")2373+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "<dt>lines</dt><dd>")
2355 if templ_7745c5c3_Err != nil {2374 if templ_7745c5c3_Err != nil {
2356 return templ_7745c5c3_Err2375 return templ_7745c5c3_Err
2357 }2376 }
2358- var templ_7745c5c3_Var124 string2377+ var templ_7745c5c3_Var125 string
2359- templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(itoa64(p.Total))2378+ templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.JoinStringErrs(itoa64(p.Total))
2360 if templ_7745c5c3_Err != nil {2379 if templ_7745c5c3_Err != nil {
2361- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 336, Col: 24}2380+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 339, Col: 24}
2362 }2381 }
2363- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))2382+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var125))
2364 if templ_7745c5c3_Err != nil {2383 if templ_7745c5c3_Err != nil {
2365 return templ_7745c5c3_Err2384 return templ_7745c5c3_Err
2366 }2385 }
2367- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "</dd></dl><p><a href=\"")2386+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "</dd></dl><p><a href=\"")
2368 if templ_7745c5c3_Err != nil {2387 if templ_7745c5c3_Err != nil {
2369 return templ_7745c5c3_Err2388 return templ_7745c5c3_Err
2370 }2389 }
2371- var templ_7745c5c3_Var125 templ.SafeURL2390+ var templ_7745c5c3_Var126 templ.SafeURL
2372- templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RawHref))2391+ templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.RawHref))
2373 if templ_7745c5c3_Err != nil {2392 if templ_7745c5c3_Err != nil {
2374- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 338, Col: 39}2393+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 341, Col: 39}
2375 }2394 }
2376- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var125))2395+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126))
2377 if templ_7745c5c3_Err != nil {2396 if templ_7745c5c3_Err != nil {
2378 return templ_7745c5c3_Err2397 return templ_7745c5c3_Err
2379 }2398 }
2380- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "\">raw log</a></p></section>")2399+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "\">raw log</a></p></section>")
2381 if templ_7745c5c3_Err != nil {2400 if templ_7745c5c3_Err != nil {
2382 return templ_7745c5c3_Err2401 return templ_7745c5c3_Err
2383 }2402 }
@@ -2403,39 +2422,39 @@ func CommitChecks(c ChecksSummary) templ.Component {
2403 }()2422 }()
2404 }2423 }
2405 ctx = templ.InitializeContext(ctx)2424 ctx = templ.InitializeContext(ctx)
2406- templ_7745c5c3_Var126 := templ.GetChildren(ctx)2425+ templ_7745c5c3_Var127 := templ.GetChildren(ctx)
2407- if templ_7745c5c3_Var126 == nil {2426+ if templ_7745c5c3_Var127 == nil {
2408- templ_7745c5c3_Var126 = templ.NopComponent2427+ templ_7745c5c3_Var127 = templ.NopComponent
2409 }2428 }
2410 ctx = templ.ClearChildren(ctx)2429 ctx = templ.ClearChildren(ctx)
2411 if c.Total > 0 {2430 if c.Total > 0 {
2412- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "<a class=\"ck-sum\" href=\"")2431+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "<a class=\"ck-sum\" href=\"")
2413 if templ_7745c5c3_Err != nil {2432 if templ_7745c5c3_Err != nil {
2414 return templ_7745c5c3_Err2433 return templ_7745c5c3_Err
2415 }2434 }
2416- var templ_7745c5c3_Var127 templ.SafeURL2435+ var templ_7745c5c3_Var128 templ.SafeURL
2417- templ_7745c5c3_Var127, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))2436+ templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))
2418 if templ_7745c5c3_Err != nil {2437 if templ_7745c5c3_Err != nil {
2419- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 346, Col: 48}2438+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 349, Col: 48}
2420 }2439 }
2421- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var127))2440+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128))
2422 if templ_7745c5c3_Err != nil {2441 if templ_7745c5c3_Err != nil {
2423 return templ_7745c5c3_Err2442 return templ_7745c5c3_Err
2424 }2443 }
2425- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "\" title=\"")2444+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "\" title=\"")
2426 if templ_7745c5c3_Err != nil {2445 if templ_7745c5c3_Err != nil {
2427 return templ_7745c5c3_Err2446 return templ_7745c5c3_Err
2428 }2447 }
2429- var templ_7745c5c3_Var128 string2448+ var templ_7745c5c3_Var129 string
2430- templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Title)2449+ templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Title)
2431 if templ_7745c5c3_Err != nil {2450 if templ_7745c5c3_Err != nil {
2432- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 346, Col: 66}2451+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 349, Col: 66}
2433 }2452 }
2434- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var128)2453+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var129)
2435 if templ_7745c5c3_Err != nil {2454 if templ_7745c5c3_Err != nil {
2436 return templ_7745c5c3_Err2455 return templ_7745c5c3_Err
2437 }2456 }
2438- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "\">")2457+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "\">")
2439 if templ_7745c5c3_Err != nil {2458 if templ_7745c5c3_Err != nil {
2440 return templ_7745c5c3_Err2459 return templ_7745c5c3_Err
2441 }2460 }
@@ -2443,20 +2462,20 @@ func CommitChecks(c ChecksSummary) templ.Component {
2443 if templ_7745c5c3_Err != nil {2462 if templ_7745c5c3_Err != nil {
2444 return templ_7745c5c3_Err2463 return templ_7745c5c3_Err
2445 }2464 }
2446- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "<span class=\"mute\">")2465+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "<span class=\"mute\">")
2447 if templ_7745c5c3_Err != nil {2466 if templ_7745c5c3_Err != nil {
2448 return templ_7745c5c3_Err2467 return templ_7745c5c3_Err
2449 }2468 }
2450- var templ_7745c5c3_Var129 string2469+ var templ_7745c5c3_Var130 string
2451- templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label)2470+ templ_7745c5c3_Var130, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label)
2452 if templ_7745c5c3_Err != nil {2471 if templ_7745c5c3_Err != nil {
2453- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 348, Col: 31}2472+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 351, Col: 31}
2454 }2473 }
2455- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var129))2474+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var130))
2456 if templ_7745c5c3_Err != nil {2475 if templ_7745c5c3_Err != nil {
2457 return templ_7745c5c3_Err2476 return templ_7745c5c3_Err
2458 }2477 }
2459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "</span></a>")2478+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "</span></a>")
2460 if templ_7745c5c3_Err != nil {2479 if templ_7745c5c3_Err != nil {
2461 return templ_7745c5c3_Err2480 return templ_7745c5c3_Err
2462 }2481 }
@@ -2482,40 +2501,40 @@ func MRChecks(c ChecksSection) templ.Component {
2482 }()2501 }()
2483 }2502 }
2484 ctx = templ.InitializeContext(ctx)2503 ctx = templ.InitializeContext(ctx)
2485- templ_7745c5c3_Var130 := templ.GetChildren(ctx)2504+ templ_7745c5c3_Var131 := templ.GetChildren(ctx)
2486- if templ_7745c5c3_Var130 == nil {2505+ if templ_7745c5c3_Var131 == nil {
2487- templ_7745c5c3_Var130 = templ.NopComponent2506+ templ_7745c5c3_Var131 = templ.NopComponent
2488 }2507 }
2489 ctx = templ.ClearChildren(ctx)2508 ctx = templ.ClearChildren(ctx)
2490- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "<section aria-labelledby=\"r-checks\" id=\"mr-checks\"><h2 id=\"r-checks\">Checks <span class=\"count\">")2509+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "<section aria-labelledby=\"r-checks\" id=\"mr-checks\"><h2 id=\"r-checks\">Checks <span class=\"count\">")
2491 if templ_7745c5c3_Err != nil {2510 if templ_7745c5c3_Err != nil {
2492 return templ_7745c5c3_Err2511 return templ_7745c5c3_Err
2493 }2512 }
2494- var templ_7745c5c3_Var131 string2513+ var templ_7745c5c3_Var132 string
2495- templ_7745c5c3_Var131, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(c.Rows)))2514+ templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(c.Rows)))
2496 if templ_7745c5c3_Err != nil {2515 if templ_7745c5c3_Err != nil {
2497- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 356, Col: 66}2516+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 359, Col: 66}
2498 }2517 }
2499- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var131))2518+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))
2500 if templ_7745c5c3_Err != nil {2519 if templ_7745c5c3_Err != nil {
2501 return templ_7745c5c3_Err2520 return templ_7745c5c3_Err
2502 }2521 }
2503- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "</span></h2>")2522+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "</span></h2>")
2504 if templ_7745c5c3_Err != nil {2523 if templ_7745c5c3_Err != nil {
2505 return templ_7745c5c3_Err2524 return templ_7745c5c3_Err
2506 }2525 }
2507 if len(c.Rows) == 0 {2526 if len(c.Rows) == 0 {
2508- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "<p class=\"dim\">no checks</p>")2527+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "<p class=\"dim\">no checks</p>")
2509 if templ_7745c5c3_Err != nil {2528 if templ_7745c5c3_Err != nil {
2510 return templ_7745c5c3_Err2529 return templ_7745c5c3_Err
2511 }2530 }
2512 } else {2531 } else {
2513- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "<ul class=\"checks\">")2532+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "<ul class=\"checks\">")
2514 if templ_7745c5c3_Err != nil {2533 if templ_7745c5c3_Err != nil {
2515 return templ_7745c5c3_Err2534 return templ_7745c5c3_Err
2516 }2535 }
2517 for _, r := range c.Rows {2536 for _, r := range c.Rows {
2518- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "<li>")2537+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "<li>")
2519 if templ_7745c5c3_Err != nil {2538 if templ_7745c5c3_Err != nil {
2520 return templ_7745c5c3_Err2539 return templ_7745c5c3_Err
2521 }2540 }
@@ -2524,108 +2543,108 @@ func MRChecks(c ChecksSection) templ.Component {
2524 return templ_7745c5c3_Err2543 return templ_7745c5c3_Err
2525 }2544 }
2526 if r.Href != "" {2545 if r.Href != "" {
2527- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "<a href=\"")2546+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "<a href=\"")
2528 if templ_7745c5c3_Err != nil {2547 if templ_7745c5c3_Err != nil {
2529 return templ_7745c5c3_Err2548 return templ_7745c5c3_Err
2530 }2549 }
2531- var templ_7745c5c3_Var132 templ.SafeURL2550+ var templ_7745c5c3_Var133 templ.SafeURL
2532- templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))2551+ templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(r.Href))
2533 if templ_7745c5c3_Err != nil {2552 if templ_7745c5c3_Err != nil {
2534- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 365, Col: 38}2553+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 368, Col: 38}
2535 }2554 }
2536- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))2555+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))
2537 if templ_7745c5c3_Err != nil {2556 if templ_7745c5c3_Err != nil {
2538 return templ_7745c5c3_Err2557 return templ_7745c5c3_Err
2539 }2558 }
2540- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "\">")2559+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "\">")
2541 if templ_7745c5c3_Err != nil {2560 if templ_7745c5c3_Err != nil {
2542 return templ_7745c5c3_Err2561 return templ_7745c5c3_Err
2543 }2562 }
2544- var templ_7745c5c3_Var133 string2563+ var templ_7745c5c3_Var134 string
2545- templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)2564+ templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
2546 if templ_7745c5c3_Err != nil {2565 if templ_7745c5c3_Err != nil {
2547- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 365, Col: 49}2566+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 368, Col: 49}
2548 }2567 }
2549- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))2568+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))
2550 if templ_7745c5c3_Err != nil {2569 if templ_7745c5c3_Err != nil {
2551 return templ_7745c5c3_Err2570 return templ_7745c5c3_Err
2552 }2571 }
2553- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "</a> ")2572+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "</a> ")
2554 if templ_7745c5c3_Err != nil {2573 if templ_7745c5c3_Err != nil {
2555 return templ_7745c5c3_Err2574 return templ_7745c5c3_Err
2556 }2575 }
2557 } else {2576 } else {
2558- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "<span>")2577+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<span>")
2559 if templ_7745c5c3_Err != nil {2578 if templ_7745c5c3_Err != nil {
2560 return templ_7745c5c3_Err2579 return templ_7745c5c3_Err
2561 }2580 }
2562- var templ_7745c5c3_Var134 string2581+ var templ_7745c5c3_Var135 string
2563- templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)2582+ templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
2564 if templ_7745c5c3_Err != nil {2583 if templ_7745c5c3_Err != nil {
2565- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 367, Col: 21}2584+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 370, Col: 21}
2566 }2585 }
2567- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))2586+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135))
2568 if templ_7745c5c3_Err != nil {2587 if templ_7745c5c3_Err != nil {
2569 return templ_7745c5c3_Err2588 return templ_7745c5c3_Err
2570 }2589 }
2571- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "</span> ")2590+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "</span> ")
2572 if templ_7745c5c3_Err != nil {2591 if templ_7745c5c3_Err != nil {
2573 return templ_7745c5c3_Err2592 return templ_7745c5c3_Err
2574 }2593 }
2575 }2594 }
2576- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<span class=\"dim\">")2595+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "<span class=\"dim\">")
2577 if templ_7745c5c3_Err != nil {2596 if templ_7745c5c3_Err != nil {
2578 return templ_7745c5c3_Err2597 return templ_7745c5c3_Err
2579 }2598 }
2580- var templ_7745c5c3_Var135 string2599+ var templ_7745c5c3_Var136 string
2581- templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinStringErrs(r.State)2600+ templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(r.State)
2582 if templ_7745c5c3_Err != nil {2601 if templ_7745c5c3_Err != nil {
2583- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 369, Col: 33}2602+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 372, Col: 33}
2584 }2603 }
2585- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135))2604+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136))
2586 if templ_7745c5c3_Err != nil {2605 if templ_7745c5c3_Err != nil {
2587 return templ_7745c5c3_Err2606 return templ_7745c5c3_Err
2588 }2607 }
2589- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "</span> ")2608+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "</span> ")
2590 if templ_7745c5c3_Err != nil {2609 if templ_7745c5c3_Err != nil {
2591 return templ_7745c5c3_Err2610 return templ_7745c5c3_Err
2592 }2611 }
2593 if r.Waiting {2612 if r.Waiting {
2594- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "<span class=\"mute\">waiting for a runner</span>")2613+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "<span class=\"mute\">waiting for a runner</span>")
2595 if templ_7745c5c3_Err != nil {2614 if templ_7745c5c3_Err != nil {
2596 return templ_7745c5c3_Err2615 return templ_7745c5c3_Err
2597 }2616 }
2598 }2617 }
2599- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "</li>")2618+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "</li>")
2600 if templ_7745c5c3_Err != nil {2619 if templ_7745c5c3_Err != nil {
2601 return templ_7745c5c3_Err2620 return templ_7745c5c3_Err
2602 }2621 }
2603 }2622 }
2604- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "</ul>")2623+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "</ul>")
2605 if templ_7745c5c3_Err != nil {2624 if templ_7745c5c3_Err != nil {
2606 return templ_7745c5c3_Err2625 return templ_7745c5c3_Err
2607 }2626 }
2608 }2627 }
2609 if c.Note != "" {2628 if c.Note != "" {
2610- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "<p class=\"mute\">")2629+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "<p class=\"mute\">")
2611 if templ_7745c5c3_Err != nil {2630 if templ_7745c5c3_Err != nil {
2612 return templ_7745c5c3_Err2631 return templ_7745c5c3_Err
2613 }2632 }
2614- var templ_7745c5c3_Var136 string2633+ var templ_7745c5c3_Var137 string
2615- templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(c.Note)2634+ templ_7745c5c3_Var137, templ_7745c5c3_Err = templ.JoinStringErrs(c.Note)
2616 if templ_7745c5c3_Err != nil {2635 if templ_7745c5c3_Err != nil {
2617- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 378, Col: 27}2636+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/actions.templ`, Line: 381, Col: 27}
2618 }2637 }
2619- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136))2638+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var137))
2620 if templ_7745c5c3_Err != nil {2639 if templ_7745c5c3_Err != nil {
2621 return templ_7745c5c3_Err2640 return templ_7745c5c3_Err
2622 }2641 }
2623- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "</p>")2642+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 224, "</p>")
2624 if templ_7745c5c3_Err != nil {2643 if templ_7745c5c3_Err != nil {
2625 return templ_7745c5c3_Err2644 return templ_7745c5c3_Err
2626 }2645 }
2627 }2646 }
2628- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "</section>")2647+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 225, "</section>")
2629 if templ_7745c5c3_Err != nil {2648 if templ_7745c5c3_Err != nil {
2630 return templ_7745c5c3_Err2649 return templ_7745c5c3_Err
2631 }2650 }
M internal/adapters/web/events.go +17 -1
@@ -120,6 +120,22 @@ func (s *Server) events(w http.ResponseWriter, r *http.Request) {
120120 }
121121 }
122122
123+ // The commit of the run of a job page is read once here: the annotations of
124+ // the live job fragments link to files at it, and it never changes, so a
125+ // job update does not read the run again. A run that is not there or not
126+ // readable refuses the stream like a missing repository.
127+ var sha string
128+ if repo != "" && q.Get("view") == "job" && s.cfg.Runs != nil {
129+ cctx, ccancel := context.WithTimeout(ctx, requestTimeout)
130+ var err error
131+ sha, err = s.jobRunSha(cctx, repo, q.Get("job"))
132+ ccancel()
133+ if err != nil {
134+ s.denyEvents(ctx, w, err)
135+ return
136+ }
137+ }
138+
123139 // The watch runs in a goroutine and hands each event to this one through
124140 // an unbuffered channel: a visitor that reads slowly holds the watch in
125141 // its send, falls behind the buffer of the use case, and gets a gap.
@@ -202,7 +218,7 @@ func (s *Server) events(w http.ResponseWriter, r *http.Request) {
202218 return
203219 }
204220
205- st := &liveStream{s: s, repo: repo, view: q.Get("view"), ref: q.Get("ref"), head: q.Get("head"), n: q.Get("n"), job: q.Get("job")}
221+ st := &liveStream{s: s, repo: repo, view: q.Get("view"), ref: q.Get("ref"), head: q.Get("head"), n: q.Get("n"), job: q.Get("job"), sha: sha}
206222 handle := func(m recvd) bool {
207223 if m.err != nil {
208224 if ctx.Err() == nil && !errors.Is(m.err, context.Canceled) {
@@ -120,6 +120,22 @@ func (s *Server) events(w http.ResponseWriter, r *http.Request) {
120 }120 }
121 }121 }
122 122
123+ // The commit of the run of a job page is read once here: the annotations of
124+ // the live job fragments link to files at it, and it never changes, so a
125+ // job update does not read the run again. A run that is not there or not
126+ // readable refuses the stream like a missing repository.
127+ var sha string
128+ if repo != "" && q.Get("view") == "job" && s.cfg.Runs != nil {
129+ cctx, ccancel := context.WithTimeout(ctx, requestTimeout)
130+ var err error
131+ sha, err = s.jobRunSha(cctx, repo, q.Get("job"))
132+ ccancel()
133+ if err != nil {
134+ s.denyEvents(ctx, w, err)
135+ return
136+ }
137+ }
138+
123 // The watch runs in a goroutine and hands each event to this one through139 // The watch runs in a goroutine and hands each event to this one through
124 // an unbuffered channel: a visitor that reads slowly holds the watch in140 // an unbuffered channel: a visitor that reads slowly holds the watch in
125 // its send, falls behind the buffer of the use case, and gets a gap.141 // its send, falls behind the buffer of the use case, and gets a gap.
@@ -202,7 +218,7 @@ func (s *Server) events(w http.ResponseWriter, r *http.Request) {
202 return218 return
203 }219 }
204 220
205- st := &liveStream{s: s, repo: repo, view: q.Get("view"), ref: q.Get("ref"), head: q.Get("head"), n: q.Get("n"), job: q.Get("job")}221+ st := &liveStream{s: s, repo: repo, view: q.Get("view"), ref: q.Get("ref"), head: q.Get("head"), n: q.Get("n"), job: q.Get("job"), sha: sha}
206 handle := func(m recvd) bool {222 handle := func(m recvd) bool {
207 if m.err != nil {223 if m.err != nil {
208 if ctx.Err() == nil && !errors.Is(m.err, context.Canceled) {224 if ctx.Err() == nil && !errors.Is(m.err, context.Canceled) {
M internal/adapters/web/fragments.go +7 -5
@@ -26,6 +26,9 @@ type liveStream struct {
2626 head string // commits: the commit listed first
2727 n string // mr: the number of the merge request shown; run: the run id
2828 job string // job: the id of the job shown
29+ // sha is the commit of the run of the job shown, read once when the stream
30+ // is set up: the annotations link to files at it, and it never changes.
31+ sha string
2932 }
3033
3134 // renderFragment renders a component to the data of an event.
@@ -478,6 +481,9 @@ func (l *liveStream) runBody(ctx context.Context) ([]fragment, error) {
478481 // jobState tells the job page that its state or its steps changed: the page
479482 // reloads the part of the log after the last line it holds (ermite.js).
480483 func (l *liveStream) jobState(ctx context.Context) ([]fragment, error) {
484+ if l.sha == "" {
485+ return gap, nil // the stream was set up without the commit of the run
486+ }
481487 d, err := l.s.loadRepo(ctx, l.repo)
482488 if err != nil {
483489 return nil, err
@@ -496,11 +502,7 @@ func (l *liveStream) jobState(ctx context.Context) ([]fragment, error) {
496502 if err != nil {
497503 return nil, err
498504 }
499- run, err := l.s.cfg.Runs.GetRun(ctx, principal(ctx), d.repo.Name, view.RunID, 0)
500- if err != nil {
501- return nil, err
502- }
503- anns, more, err := l.s.jobAnnotations(ctx, d, view.RunID, run.Run.Sha, view, 0)
505+ anns, more, err := l.s.jobAnnotations(ctx, d, view.RunID, l.sha, view, 0)
504506 if err != nil {
505507 return nil, err
506508 }
@@ -26,6 +26,9 @@ type liveStream struct {
26 head string // commits: the commit listed first26 head string // commits: the commit listed first
27 n string // mr: the number of the merge request shown; run: the run id27 n string // mr: the number of the merge request shown; run: the run id
28 job string // job: the id of the job shown28 job string // job: the id of the job shown
29+ // sha is the commit of the run of the job shown, read once when the stream
30+ // is set up: the annotations link to files at it, and it never changes.
31+ sha string
29 }32 }
30 33
31 // renderFragment renders a component to the data of an event.34 // renderFragment renders a component to the data of an event.
@@ -478,6 +481,9 @@ func (l *liveStream) runBody(ctx context.Context) ([]fragment, error) {
478 // jobState tells the job page that its state or its steps changed: the page481 // jobState tells the job page that its state or its steps changed: the page
479 // reloads the part of the log after the last line it holds (ermite.js).482 // reloads the part of the log after the last line it holds (ermite.js).
480 func (l *liveStream) jobState(ctx context.Context) ([]fragment, error) {483 func (l *liveStream) jobState(ctx context.Context) ([]fragment, error) {
484+ if l.sha == "" {
485+ return gap, nil // the stream was set up without the commit of the run
486+ }
481 d, err := l.s.loadRepo(ctx, l.repo)487 d, err := l.s.loadRepo(ctx, l.repo)
482 if err != nil {488 if err != nil {
483 return nil, err489 return nil, err
@@ -496,11 +502,7 @@ func (l *liveStream) jobState(ctx context.Context) ([]fragment, error) {
496 if err != nil {502 if err != nil {
497 return nil, err503 return nil, err
498 }504 }
499- run, err := l.s.cfg.Runs.GetRun(ctx, principal(ctx), d.repo.Name, view.RunID, 0)505+ anns, more, err := l.s.jobAnnotations(ctx, d, view.RunID, l.sha, view, 0)
500- if err != nil {
501- return nil, err
502- }
503- anns, more, err := l.s.jobAnnotations(ctx, d, view.RunID, run.Run.Sha, view, 0)
504 if err != nil {506 if err != nil {
505 return nil, err507 return nil, err
506 }508 }
M internal/domain/ci_annotation.go +3 -0
@@ -57,6 +57,9 @@ type CIAnnotation struct {
5757 type CIStepSummary struct {
5858 Step int32
5959 Markdown string
60+ // Omitted is set when the summary exists but was not read, because the
61+ // caller's byte budget was used up; Markdown is then empty.
62+ Omitted bool
6063 }
6164
6265 // CheckAnnotations validates the annotations of a request: the limits of the
@@ -57,6 +57,9 @@ type CIAnnotation struct {
57 type CIStepSummary struct {57 type CIStepSummary struct {
58 Step int3258 Step int32
59 Markdown string59 Markdown string
60+ // Omitted is set when the summary exists but was not read, because the
61+ // caller's byte budget was used up; Markdown is then empty.
62+ Omitted bool
60 }63 }
61 64
62 // CheckAnnotations validates the annotations of a request: the limits of the65 // CheckAnnotations validates the annotations of a request: the limits of the
M internal/ports/ci.go +10 -4
@@ -218,8 +218,12 @@ type CISummaryStore interface {
218218 // appears whole or not at all. An empty markdown removes the summary.
219219 Put(ctx context.Context, ref CILogRef, step int32, markdown string) error
220220 // List returns the summaries of the steps that have one, by step number,
221- // at most limit; more is true when there are others.
222- List(ctx context.Context, ref CILogRef, limit int) (summaries []domain.CIStepSummary, more bool, err error)
221+ // at most limit; more is true when there are others. With maxBytes above
222+ // 0 the files are sized (stat) before they are read, and a summary that
223+ // would take the total of the Markdown read past maxBytes is returned
224+ // Omitted, unread, as are all the ones after it: a call never reads more
225+ // than maxBytes.
226+ List(ctx context.Context, ref CILogRef, limit int, maxBytes int64) (summaries []domain.CIStepSummary, more bool, err error)
223227 // RemoveJob deletes the summaries of one execution of a job.
224228 RemoveJob(ctx context.Context, ref CILogRef) error
225229 // RemoveRuns deletes the summaries of the runs.
@@ -531,8 +535,10 @@ type RunService interface {
531535 // attempt in which it executed.
532536 ListAnnotations(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, attempt int32, jobID string) (AnnotationList, error)
533537 // GetStepSummaries returns the summaries of a job at an attempt (0 for the
534- // latest), by step number.
535- GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (SummaryList, error)
538+ // latest), by step number. maxBytes above 0 bounds the Markdown read: the
539+ // summaries past it come back Omitted, without their Markdown (the API
540+ // passes 0, no bound).
541+ GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (SummaryList, error)
536542 ListRunners(ctx context.Context, p domain.Principal, size int, token string) (RunnerPage, error)
537543 RemoveRunner(ctx context.Context, p domain.Principal, id string) error
538544 }
@@ -218,8 +218,12 @@ type CISummaryStore interface {
218 // appears whole or not at all. An empty markdown removes the summary.218 // appears whole or not at all. An empty markdown removes the summary.
219 Put(ctx context.Context, ref CILogRef, step int32, markdown string) error219 Put(ctx context.Context, ref CILogRef, step int32, markdown string) error
220 // List returns the summaries of the steps that have one, by step number,220 // List returns the summaries of the steps that have one, by step number,
221- // at most limit; more is true when there are others.221+ // at most limit; more is true when there are others. With maxBytes above
222- List(ctx context.Context, ref CILogRef, limit int) (summaries []domain.CIStepSummary, more bool, err error)222+ // 0 the files are sized (stat) before they are read, and a summary that
223+ // would take the total of the Markdown read past maxBytes is returned
224+ // Omitted, unread, as are all the ones after it: a call never reads more
225+ // than maxBytes.
226+ List(ctx context.Context, ref CILogRef, limit int, maxBytes int64) (summaries []domain.CIStepSummary, more bool, err error)
223 // RemoveJob deletes the summaries of one execution of a job.227 // RemoveJob deletes the summaries of one execution of a job.
224 RemoveJob(ctx context.Context, ref CILogRef) error228 RemoveJob(ctx context.Context, ref CILogRef) error
225 // RemoveRuns deletes the summaries of the runs.229 // RemoveRuns deletes the summaries of the runs.
@@ -531,8 +535,10 @@ type RunService interface {
531 // attempt in which it executed.535 // attempt in which it executed.
532 ListAnnotations(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, attempt int32, jobID string) (AnnotationList, error)536 ListAnnotations(ctx context.Context, p domain.Principal, repo domain.RepoName, runID int64, attempt int32, jobID string) (AnnotationList, error)
533 // GetStepSummaries returns the summaries of a job at an attempt (0 for the537 // GetStepSummaries returns the summaries of a job at an attempt (0 for the
534- // latest), by step number.538+ // latest), by step number. maxBytes above 0 bounds the Markdown read: the
535- GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (SummaryList, error)539+ // summaries past it come back Omitted, without their Markdown (the API
540+ // passes 0, no bound).
541+ GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (SummaryList, error)
536 ListRunners(ctx context.Context, p domain.Principal, size int, token string) (RunnerPage, error)542 ListRunners(ctx context.Context, p domain.Principal, size int, token string) (RunnerPage, error)
537 RemoveRunner(ctx context.Context, p domain.Principal, id string) error543 RemoveRunner(ctx context.Context, p domain.Principal, id string) error
538 }544 }
M internal/ports/mocks/CISummaryStore.go +20 -14
@@ -49,8 +49,8 @@ func (_m *CISummaryStore) EXPECT() *CISummaryStore_Expecter {
4949 }
5050
5151 // List provides a mock function for the type CISummaryStore
52-func (_mock *CISummaryStore) List(ctx context.Context, ref ports.CILogRef, limit int) ([]domain.CIStepSummary, bool, error) {
53- ret := _mock.Called(ctx, ref, limit)
52+func (_mock *CISummaryStore) List(ctx context.Context, ref ports.CILogRef, limit int, maxBytes int64) ([]domain.CIStepSummary, bool, error) {
53+ ret := _mock.Called(ctx, ref, limit, maxBytes)
5454
5555 if len(ret) == 0 {
5656 panic("no return value specified for List")
@@ -59,23 +59,23 @@ func (_mock *CISummaryStore) List(ctx context.Context, ref ports.CILogRef, limit
5959 var r0 []domain.CIStepSummary
6060 var r1 bool
6161 var r2 error
62- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int) ([]domain.CIStepSummary, bool, error)); ok {
63- return returnFunc(ctx, ref, limit)
62+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int, int64) ([]domain.CIStepSummary, bool, error)); ok {
63+ return returnFunc(ctx, ref, limit, maxBytes)
6464 }
65- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int) []domain.CIStepSummary); ok {
66- r0 = returnFunc(ctx, ref, limit)
65+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int, int64) []domain.CIStepSummary); ok {
66+ r0 = returnFunc(ctx, ref, limit, maxBytes)
6767 } else {
6868 if ret.Get(0) != nil {
6969 r0 = ret.Get(0).([]domain.CIStepSummary)
7070 }
7171 }
72- if returnFunc, ok := ret.Get(1).(func(context.Context, ports.CILogRef, int) bool); ok {
73- r1 = returnFunc(ctx, ref, limit)
72+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.CILogRef, int, int64) bool); ok {
73+ r1 = returnFunc(ctx, ref, limit, maxBytes)
7474 } else {
7575 r1 = ret.Get(1).(bool)
7676 }
77- if returnFunc, ok := ret.Get(2).(func(context.Context, ports.CILogRef, int) error); ok {
78- r2 = returnFunc(ctx, ref, limit)
77+ if returnFunc, ok := ret.Get(2).(func(context.Context, ports.CILogRef, int, int64) error); ok {
78+ r2 = returnFunc(ctx, ref, limit, maxBytes)
7979 } else {
8080 r2 = ret.Error(2)
8181 }
@@ -91,11 +91,12 @@ type CISummaryStore_List_Call struct {
9191 // - ctx context.Context
9292 // - ref ports.CILogRef
9393 // - limit int
94-func (_e *CISummaryStore_Expecter) List(ctx any, ref any, limit any) *CISummaryStore_List_Call {
95- return &CISummaryStore_List_Call{Call: _e.mock.On("List", ctx, ref, limit)}
94+// - maxBytes int64
95+func (_e *CISummaryStore_Expecter) List(ctx any, ref any, limit any, maxBytes any) *CISummaryStore_List_Call {
96+ return &CISummaryStore_List_Call{Call: _e.mock.On("List", ctx, ref, limit, maxBytes)}
9697 }
9798
98-func (_c *CISummaryStore_List_Call) Run(run func(ctx context.Context, ref ports.CILogRef, limit int)) *CISummaryStore_List_Call {
99+func (_c *CISummaryStore_List_Call) Run(run func(ctx context.Context, ref ports.CILogRef, limit int, maxBytes int64)) *CISummaryStore_List_Call {
99100 _c.Call.Run(func(args mock.Arguments) {
100101 var arg0 context.Context
101102 if args[0] != nil {
@@ -109,10 +110,15 @@ func (_c *CISummaryStore_List_Call) Run(run func(ctx context.Context, ref ports.
109110 if args[2] != nil {
110111 arg2 = args[2].(int)
111112 }
113+ var arg3 int64
114+ if args[3] != nil {
115+ arg3 = args[3].(int64)
116+ }
112117 run(
113118 arg0,
114119 arg1,
115120 arg2,
121+ arg3,
116122 )
117123 })
118124 return _c
@@ -123,7 +129,7 @@ func (_c *CISummaryStore_List_Call) Return(summaries []domain.CIStepSummary, mor
123129 return _c
124130 }
125131
126-func (_c *CISummaryStore_List_Call) RunAndReturn(run func(ctx context.Context, ref ports.CILogRef, limit int) ([]domain.CIStepSummary, bool, error)) *CISummaryStore_List_Call {
132+func (_c *CISummaryStore_List_Call) RunAndReturn(run func(ctx context.Context, ref ports.CILogRef, limit int, maxBytes int64) ([]domain.CIStepSummary, bool, error)) *CISummaryStore_List_Call {
127133 _c.Call.Return(run)
128134 return _c
129135 }
@@ -49,8 +49,8 @@ func (_m *CISummaryStore) EXPECT() *CISummaryStore_Expecter {
49 }49 }
50 50
51 // List provides a mock function for the type CISummaryStore51 // List provides a mock function for the type CISummaryStore
52-func (_mock *CISummaryStore) List(ctx context.Context, ref ports.CILogRef, limit int) ([]domain.CIStepSummary, bool, error) {52+func (_mock *CISummaryStore) List(ctx context.Context, ref ports.CILogRef, limit int, maxBytes int64) ([]domain.CIStepSummary, bool, error) {
53- ret := _mock.Called(ctx, ref, limit)53+ ret := _mock.Called(ctx, ref, limit, maxBytes)
54 54
55 if len(ret) == 0 {55 if len(ret) == 0 {
56 panic("no return value specified for List")56 panic("no return value specified for List")
@@ -59,23 +59,23 @@ func (_mock *CISummaryStore) List(ctx context.Context, ref ports.CILogRef, limit
59 var r0 []domain.CIStepSummary59 var r0 []domain.CIStepSummary
60 var r1 bool60 var r1 bool
61 var r2 error61 var r2 error
62- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int) ([]domain.CIStepSummary, bool, error)); ok {62+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int, int64) ([]domain.CIStepSummary, bool, error)); ok {
63- return returnFunc(ctx, ref, limit)63+ return returnFunc(ctx, ref, limit, maxBytes)
64 }64 }
65- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int) []domain.CIStepSummary); ok {65+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CILogRef, int, int64) []domain.CIStepSummary); ok {
66- r0 = returnFunc(ctx, ref, limit)66+ r0 = returnFunc(ctx, ref, limit, maxBytes)
67 } else {67 } else {
68 if ret.Get(0) != nil {68 if ret.Get(0) != nil {
69 r0 = ret.Get(0).([]domain.CIStepSummary)69 r0 = ret.Get(0).([]domain.CIStepSummary)
70 }70 }
71 }71 }
72- if returnFunc, ok := ret.Get(1).(func(context.Context, ports.CILogRef, int) bool); ok {72+ if returnFunc, ok := ret.Get(1).(func(context.Context, ports.CILogRef, int, int64) bool); ok {
73- r1 = returnFunc(ctx, ref, limit)73+ r1 = returnFunc(ctx, ref, limit, maxBytes)
74 } else {74 } else {
75 r1 = ret.Get(1).(bool)75 r1 = ret.Get(1).(bool)
76 }76 }
77- if returnFunc, ok := ret.Get(2).(func(context.Context, ports.CILogRef, int) error); ok {77+ if returnFunc, ok := ret.Get(2).(func(context.Context, ports.CILogRef, int, int64) error); ok {
78- r2 = returnFunc(ctx, ref, limit)78+ r2 = returnFunc(ctx, ref, limit, maxBytes)
79 } else {79 } else {
80 r2 = ret.Error(2)80 r2 = ret.Error(2)
81 }81 }
@@ -91,11 +91,12 @@ type CISummaryStore_List_Call struct {
91 // - ctx context.Context91 // - ctx context.Context
92 // - ref ports.CILogRef92 // - ref ports.CILogRef
93 // - limit int93 // - limit int
94-func (_e *CISummaryStore_Expecter) List(ctx any, ref any, limit any) *CISummaryStore_List_Call {94+// - maxBytes int64
95- return &CISummaryStore_List_Call{Call: _e.mock.On("List", ctx, ref, limit)}95+func (_e *CISummaryStore_Expecter) List(ctx any, ref any, limit any, maxBytes any) *CISummaryStore_List_Call {
96+ return &CISummaryStore_List_Call{Call: _e.mock.On("List", ctx, ref, limit, maxBytes)}
96 }97 }
97 98
98-func (_c *CISummaryStore_List_Call) Run(run func(ctx context.Context, ref ports.CILogRef, limit int)) *CISummaryStore_List_Call {99+func (_c *CISummaryStore_List_Call) Run(run func(ctx context.Context, ref ports.CILogRef, limit int, maxBytes int64)) *CISummaryStore_List_Call {
99 _c.Call.Run(func(args mock.Arguments) {100 _c.Call.Run(func(args mock.Arguments) {
100 var arg0 context.Context101 var arg0 context.Context
101 if args[0] != nil {102 if args[0] != nil {
@@ -109,10 +110,15 @@ func (_c *CISummaryStore_List_Call) Run(run func(ctx context.Context, ref ports.
109 if args[2] != nil {110 if args[2] != nil {
110 arg2 = args[2].(int)111 arg2 = args[2].(int)
111 }112 }
113+ var arg3 int64
114+ if args[3] != nil {
115+ arg3 = args[3].(int64)
116+ }
112 run(117 run(
113 arg0,118 arg0,
114 arg1,119 arg1,
115 arg2,120 arg2,
121+ arg3,
116 )122 )
117 })123 })
118 return _c124 return _c
@@ -123,7 +129,7 @@ func (_c *CISummaryStore_List_Call) Return(summaries []domain.CIStepSummary, mor
123 return _c129 return _c
124 }130 }
125 131
126-func (_c *CISummaryStore_List_Call) RunAndReturn(run func(ctx context.Context, ref ports.CILogRef, limit int) ([]domain.CIStepSummary, bool, error)) *CISummaryStore_List_Call {132+func (_c *CISummaryStore_List_Call) RunAndReturn(run func(ctx context.Context, ref ports.CILogRef, limit int, maxBytes int64) ([]domain.CIStepSummary, bool, error)) *CISummaryStore_List_Call {
127 _c.Call.Return(run)133 _c.Call.Return(run)
128 return _c134 return _c
129 }135 }
M internal/ports/mocks/RunService.go +18 -12
@@ -370,8 +370,8 @@ func (_c *RunService_GetRun_Call) RunAndReturn(run func(ctx context.Context, p d
370370 }
371371
372372 // GetStepSummaries provides a mock function for the type RunService
373-func (_mock *RunService) GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (ports.SummaryList, error) {
374- ret := _mock.Called(ctx, p, repo, jobID, attempt)
373+func (_mock *RunService) GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (ports.SummaryList, error) {
374+ ret := _mock.Called(ctx, p, repo, jobID, attempt, maxBytes)
375375
376376 if len(ret) == 0 {
377377 panic("no return value specified for GetStepSummaries")
@@ -379,16 +379,16 @@ func (_mock *RunService) GetStepSummaries(ctx context.Context, p domain.Principa
379379
380380 var r0 ports.SummaryList
381381 var r1 error
382- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32) (ports.SummaryList, error)); ok {
383- return returnFunc(ctx, p, repo, jobID, attempt)
382+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32, int64) (ports.SummaryList, error)); ok {
383+ return returnFunc(ctx, p, repo, jobID, attempt, maxBytes)
384384 }
385- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32) ports.SummaryList); ok {
386- r0 = returnFunc(ctx, p, repo, jobID, attempt)
385+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32, int64) ports.SummaryList); ok {
386+ r0 = returnFunc(ctx, p, repo, jobID, attempt, maxBytes)
387387 } else {
388388 r0 = ret.Get(0).(ports.SummaryList)
389389 }
390- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, string, int32) error); ok {
391- r1 = returnFunc(ctx, p, repo, jobID, attempt)
390+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, string, int32, int64) error); ok {
391+ r1 = returnFunc(ctx, p, repo, jobID, attempt, maxBytes)
392392 } else {
393393 r1 = ret.Error(1)
394394 }
@@ -406,11 +406,12 @@ type RunService_GetStepSummaries_Call struct {
406406 // - repo domain.RepoName
407407 // - jobID string
408408 // - attempt int32
409-func (_e *RunService_Expecter) GetStepSummaries(ctx any, p any, repo any, jobID any, attempt any) *RunService_GetStepSummaries_Call {
410- return &RunService_GetStepSummaries_Call{Call: _e.mock.On("GetStepSummaries", ctx, p, repo, jobID, attempt)}
409+// - maxBytes int64
410+func (_e *RunService_Expecter) GetStepSummaries(ctx any, p any, repo any, jobID any, attempt any, maxBytes any) *RunService_GetStepSummaries_Call {
411+ return &RunService_GetStepSummaries_Call{Call: _e.mock.On("GetStepSummaries", ctx, p, repo, jobID, attempt, maxBytes)}
411412 }
412413
413-func (_c *RunService_GetStepSummaries_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32)) *RunService_GetStepSummaries_Call {
414+func (_c *RunService_GetStepSummaries_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64)) *RunService_GetStepSummaries_Call {
414415 _c.Call.Run(func(args mock.Arguments) {
415416 var arg0 context.Context
416417 if args[0] != nil {
@@ -432,12 +433,17 @@ func (_c *RunService_GetStepSummaries_Call) Run(run func(ctx context.Context, p
432433 if args[4] != nil {
433434 arg4 = args[4].(int32)
434435 }
436+ var arg5 int64
437+ if args[5] != nil {
438+ arg5 = args[5].(int64)
439+ }
435440 run(
436441 arg0,
437442 arg1,
438443 arg2,
439444 arg3,
440445 arg4,
446+ arg5,
441447 )
442448 })
443449 return _c
@@ -448,7 +454,7 @@ func (_c *RunService_GetStepSummaries_Call) Return(summaryList ports.SummaryList
448454 return _c
449455 }
450456
451-func (_c *RunService_GetStepSummaries_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (ports.SummaryList, error)) *RunService_GetStepSummaries_Call {
457+func (_c *RunService_GetStepSummaries_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (ports.SummaryList, error)) *RunService_GetStepSummaries_Call {
452458 _c.Call.Return(run)
453459 return _c
454460 }
@@ -370,8 +370,8 @@ func (_c *RunService_GetRun_Call) RunAndReturn(run func(ctx context.Context, p d
370 }370 }
371 371
372 // GetStepSummaries provides a mock function for the type RunService372 // GetStepSummaries provides a mock function for the type RunService
373-func (_mock *RunService) GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (ports.SummaryList, error) {373+func (_mock *RunService) GetStepSummaries(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (ports.SummaryList, error) {
374- ret := _mock.Called(ctx, p, repo, jobID, attempt)374+ ret := _mock.Called(ctx, p, repo, jobID, attempt, maxBytes)
375 375
376 if len(ret) == 0 {376 if len(ret) == 0 {
377 panic("no return value specified for GetStepSummaries")377 panic("no return value specified for GetStepSummaries")
@@ -379,16 +379,16 @@ func (_mock *RunService) GetStepSummaries(ctx context.Context, p domain.Principa
379 379
380 var r0 ports.SummaryList380 var r0 ports.SummaryList
381 var r1 error381 var r1 error
382- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32) (ports.SummaryList, error)); ok {382+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32, int64) (ports.SummaryList, error)); ok {
383- return returnFunc(ctx, p, repo, jobID, attempt)383+ return returnFunc(ctx, p, repo, jobID, attempt, maxBytes)
384 }384 }
385- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32) ports.SummaryList); ok {385+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, string, int32, int64) ports.SummaryList); ok {
386- r0 = returnFunc(ctx, p, repo, jobID, attempt)386+ r0 = returnFunc(ctx, p, repo, jobID, attempt, maxBytes)
387 } else {387 } else {
388 r0 = ret.Get(0).(ports.SummaryList)388 r0 = ret.Get(0).(ports.SummaryList)
389 }389 }
390- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, string, int32) error); ok {390+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, string, int32, int64) error); ok {
391- r1 = returnFunc(ctx, p, repo, jobID, attempt)391+ r1 = returnFunc(ctx, p, repo, jobID, attempt, maxBytes)
392 } else {392 } else {
393 r1 = ret.Error(1)393 r1 = ret.Error(1)
394 }394 }
@@ -406,11 +406,12 @@ type RunService_GetStepSummaries_Call struct {
406 // - repo domain.RepoName406 // - repo domain.RepoName
407 // - jobID string407 // - jobID string
408 // - attempt int32408 // - attempt int32
409-func (_e *RunService_Expecter) GetStepSummaries(ctx any, p any, repo any, jobID any, attempt any) *RunService_GetStepSummaries_Call {409+// - maxBytes int64
410- return &RunService_GetStepSummaries_Call{Call: _e.mock.On("GetStepSummaries", ctx, p, repo, jobID, attempt)}410+func (_e *RunService_Expecter) GetStepSummaries(ctx any, p any, repo any, jobID any, attempt any, maxBytes any) *RunService_GetStepSummaries_Call {
411+ return &RunService_GetStepSummaries_Call{Call: _e.mock.On("GetStepSummaries", ctx, p, repo, jobID, attempt, maxBytes)}
411 }412 }
412 413
413-func (_c *RunService_GetStepSummaries_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32)) *RunService_GetStepSummaries_Call {414+func (_c *RunService_GetStepSummaries_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64)) *RunService_GetStepSummaries_Call {
414 _c.Call.Run(func(args mock.Arguments) {415 _c.Call.Run(func(args mock.Arguments) {
415 var arg0 context.Context416 var arg0 context.Context
416 if args[0] != nil {417 if args[0] != nil {
@@ -432,12 +433,17 @@ func (_c *RunService_GetStepSummaries_Call) Run(run func(ctx context.Context, p
432 if args[4] != nil {433 if args[4] != nil {
433 arg4 = args[4].(int32)434 arg4 = args[4].(int32)
434 }435 }
436+ var arg5 int64
437+ if args[5] != nil {
438+ arg5 = args[5].(int64)
439+ }
435 run(440 run(
436 arg0,441 arg0,
437 arg1,442 arg1,
438 arg2,443 arg2,
439 arg3,444 arg3,
440 arg4,445 arg4,
446+ arg5,
441 )447 )
442 })448 })
443 return _c449 return _c
@@ -448,7 +454,7 @@ func (_c *RunService_GetStepSummaries_Call) Return(summaryList ports.SummaryList
448 return _c454 return _c
449 }455 }
450 456
451-func (_c *RunService_GetStepSummaries_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32) (ports.SummaryList, error)) *RunService_GetStepSummaries_Call {457+func (_c *RunService_GetStepSummaries_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, jobID string, attempt int32, maxBytes int64) (ports.SummaryList, error)) *RunService_GetStepSummaries_Call {
452 _c.Call.Return(run)458 _c.Call.Return(run)
453 return _c459 return _c
454 }460 }
M internal/usecase/ci_results.go +4 -3
@@ -111,8 +111,9 @@ func (c *CI) ListAnnotations(ctx context.Context, p domain.Principal, name domai
111111 }
112112
113113 // GetStepSummaries returns the summaries of a job at an attempt (0 for the
114-// latest): the first 20 steps that have one, by step number.
115-func (c *CI) GetStepSummaries(ctx context.Context, p domain.Principal, name domain.RepoName, jobID string, attempt int32) (ports.SummaryList, error) {
114+// latest): the first 20 steps that have one, by step number. With maxBytes
115+// above 0, no more than that many bytes of Markdown are read.
116+func (c *CI) GetStepSummaries(ctx context.Context, p domain.Principal, name domain.RepoName, jobID string, attempt int32, maxBytes int64) (ports.SummaryList, error) {
116117 if _, err := c.readableRepo(ctx, p, name); err != nil {
117118 return ports.SummaryList{}, err
118119 }
@@ -120,7 +121,7 @@ func (c *CI) GetStepSummaries(ctx context.Context, p domain.Principal, name doma
120121 if err != nil {
121122 return ports.SummaryList{}, err
122123 }
123- list, more, err := c.cfg.Summaries.List(ctx, logRef(j), domain.CIMaxListedSummaries)
124+ list, more, err := c.cfg.Summaries.List(ctx, logRef(j), domain.CIMaxListedSummaries, max(maxBytes, 0))
124125 if err != nil {
125126 return ports.SummaryList{}, err
126127 }
@@ -111,8 +111,9 @@ func (c *CI) ListAnnotations(ctx context.Context, p domain.Principal, name domai
111 }111 }
112 112
113 // GetStepSummaries returns the summaries of a job at an attempt (0 for the113 // GetStepSummaries returns the summaries of a job at an attempt (0 for the
114-// latest): the first 20 steps that have one, by step number.114+// latest): the first 20 steps that have one, by step number. With maxBytes
115-func (c *CI) GetStepSummaries(ctx context.Context, p domain.Principal, name domain.RepoName, jobID string, attempt int32) (ports.SummaryList, error) {115+// above 0, no more than that many bytes of Markdown are read.
116+func (c *CI) GetStepSummaries(ctx context.Context, p domain.Principal, name domain.RepoName, jobID string, attempt int32, maxBytes int64) (ports.SummaryList, error) {
116 if _, err := c.readableRepo(ctx, p, name); err != nil {117 if _, err := c.readableRepo(ctx, p, name); err != nil {
117 return ports.SummaryList{}, err118 return ports.SummaryList{}, err
118 }119 }
@@ -120,7 +121,7 @@ func (c *CI) GetStepSummaries(ctx context.Context, p domain.Principal, name doma
120 if err != nil {121 if err != nil {
121 return ports.SummaryList{}, err122 return ports.SummaryList{}, err
122 }123 }
123- list, more, err := c.cfg.Summaries.List(ctx, logRef(j), domain.CIMaxListedSummaries)124+ list, more, err := c.cfg.Summaries.List(ctx, logRef(j), domain.CIMaxListedSummaries, max(maxBytes, 0))
124 if err != nil {125 if err != nil {
125 return ports.SummaryList{}, err126 return ports.SummaryList{}, err
126 }127 }
M internal/usecase/ci_results_unit_test.go +24 -3
@@ -177,7 +177,7 @@ func TestGetStepSummariesAndListAnnotations(t *testing.T) {
177177 u.store.EXPECT().GetJob(mock.Anything, int64(7), int32(2), jobIDOne).Return(j, nil)
178178 u.store.EXPECT().ListJobs(mock.Anything, int64(7), int32(2)).Return([]domain.CIJob{j}, nil)
179179 u.store.EXPECT().JobAnnotations(mock.Anything, int64(7), int32(1), jobIDOne).Return([]domain.CIAnnotation{warn(1, "from attempt 1")}, nil)
180- u.sums.EXPECT().List(mock.Anything, ports.CILogRef{RunID: 7, JobID: jobIDOne, Attempt: 1}, domain.CIMaxListedSummaries).
180+ u.sums.EXPECT().List(mock.Anything, ports.CILogRef{RunID: 7, JobID: jobIDOne, Attempt: 1}, domain.CIMaxListedSummaries, int64(0)).
181181 Return([]domain.CIStepSummary{{Step: 3, Markdown: "s"}}, true, nil)
182182 ctx := context.Background()
183183
@@ -187,7 +187,7 @@ func TestGetStepSummariesAndListAnnotations(t *testing.T) {
187187 assert.Equal(t, jobIDOne, anns.Annotations[0].JobID)
188188 assert.False(t, anns.Truncated)
189189
190- sums, err := u.ci.GetStepSummaries(ctx, domain.Anonymous(), repo, jobIDOne, 0)
190+ sums, err := u.ci.GetStepSummaries(ctx, domain.Anonymous(), repo, jobIDOne, 0, 0)
191191 require.NoError(t, err)
192192 assert.True(t, sums.Truncated)
193193 assert.Equal(t, []domain.CIStepSummary{{Step: 3, Markdown: "s"}}, sums.Summaries)
@@ -226,6 +226,27 @@ func TestResultsReadsFollowTheAccessOfTheRepository(t *testing.T) {
226226 u.meta.EXPECT().Get(mock.Anything, repo).Return(domain.Repo{Name: repo, Visibility: domain.VisibilityPrivate}, nil)
227227 _, err := u.ci.ListAnnotations(context.Background(), domain.Anonymous(), repo, 7, 0, "")
228228 assert.ErrorIs(t, err, domain.ErrNotFound)
229- _, err = u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0)
229+ _, err = u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0, 0)
230230 assert.ErrorIs(t, err, domain.ErrNotFound)
231231 }
232+
233+// The byte budget of a reader reaches the store, and a negative one means none.
234+func TestGetStepSummariesPassesTheBudget(t *testing.T) {
235+ t.Parallel()
236+ u := newCIUnit(t, nil)
237+ repo := name(t, "proj")
238+ j := ciJob(t, domain.CIPhaseEnded, jobSecret)
239+ u.meta.EXPECT().Get(mock.Anything, repo).Return(domain.Repo{Name: repo, Visibility: domain.VisibilityPublic}, nil)
240+ u.store.EXPECT().GetRun(mock.Anything, repo, int64(7)).Return(domain.CIRun{Repo: repo, ID: 7, Attempt: 1}, nil)
241+ u.store.EXPECT().GetJob(mock.Anything, int64(7), int32(1), jobIDOne).Return(j, nil)
242+ ref := ports.CILogRef{RunID: 7, JobID: jobIDOne, Attempt: 1}
243+ u.sums.EXPECT().List(mock.Anything, ref, domain.CIMaxListedSummaries, int64(4096)).
244+ Return([]domain.CIStepSummary{{Step: 1, Markdown: "a"}, {Step: 2, Omitted: true}}, false, nil).Once()
245+ u.sums.EXPECT().List(mock.Anything, ref, domain.CIMaxListedSummaries, int64(0)).Return(nil, false, nil).Once()
246+
247+ got, err := u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0, 4096)
248+ require.NoError(t, err)
249+ assert.True(t, got.Summaries[1].Omitted)
250+ _, err = u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0, -5)
251+ require.NoError(t, err)
252+}
@@ -177,7 +177,7 @@ func TestGetStepSummariesAndListAnnotations(t *testing.T) {
177 u.store.EXPECT().GetJob(mock.Anything, int64(7), int32(2), jobIDOne).Return(j, nil)177 u.store.EXPECT().GetJob(mock.Anything, int64(7), int32(2), jobIDOne).Return(j, nil)
178 u.store.EXPECT().ListJobs(mock.Anything, int64(7), int32(2)).Return([]domain.CIJob{j}, nil)178 u.store.EXPECT().ListJobs(mock.Anything, int64(7), int32(2)).Return([]domain.CIJob{j}, nil)
179 u.store.EXPECT().JobAnnotations(mock.Anything, int64(7), int32(1), jobIDOne).Return([]domain.CIAnnotation{warn(1, "from attempt 1")}, nil)179 u.store.EXPECT().JobAnnotations(mock.Anything, int64(7), int32(1), jobIDOne).Return([]domain.CIAnnotation{warn(1, "from attempt 1")}, nil)
180- u.sums.EXPECT().List(mock.Anything, ports.CILogRef{RunID: 7, JobID: jobIDOne, Attempt: 1}, domain.CIMaxListedSummaries).180+ u.sums.EXPECT().List(mock.Anything, ports.CILogRef{RunID: 7, JobID: jobIDOne, Attempt: 1}, domain.CIMaxListedSummaries, int64(0)).
181 Return([]domain.CIStepSummary{{Step: 3, Markdown: "s"}}, true, nil)181 Return([]domain.CIStepSummary{{Step: 3, Markdown: "s"}}, true, nil)
182 ctx := context.Background()182 ctx := context.Background()
183 183
@@ -187,7 +187,7 @@ func TestGetStepSummariesAndListAnnotations(t *testing.T) {
187 assert.Equal(t, jobIDOne, anns.Annotations[0].JobID)187 assert.Equal(t, jobIDOne, anns.Annotations[0].JobID)
188 assert.False(t, anns.Truncated)188 assert.False(t, anns.Truncated)
189 189
190- sums, err := u.ci.GetStepSummaries(ctx, domain.Anonymous(), repo, jobIDOne, 0)190+ sums, err := u.ci.GetStepSummaries(ctx, domain.Anonymous(), repo, jobIDOne, 0, 0)
191 require.NoError(t, err)191 require.NoError(t, err)
192 assert.True(t, sums.Truncated)192 assert.True(t, sums.Truncated)
193 assert.Equal(t, []domain.CIStepSummary{{Step: 3, Markdown: "s"}}, sums.Summaries)193 assert.Equal(t, []domain.CIStepSummary{{Step: 3, Markdown: "s"}}, sums.Summaries)
@@ -226,6 +226,27 @@ func TestResultsReadsFollowTheAccessOfTheRepository(t *testing.T) {
226 u.meta.EXPECT().Get(mock.Anything, repo).Return(domain.Repo{Name: repo, Visibility: domain.VisibilityPrivate}, nil)226 u.meta.EXPECT().Get(mock.Anything, repo).Return(domain.Repo{Name: repo, Visibility: domain.VisibilityPrivate}, nil)
227 _, err := u.ci.ListAnnotations(context.Background(), domain.Anonymous(), repo, 7, 0, "")227 _, err := u.ci.ListAnnotations(context.Background(), domain.Anonymous(), repo, 7, 0, "")
228 assert.ErrorIs(t, err, domain.ErrNotFound)228 assert.ErrorIs(t, err, domain.ErrNotFound)
229- _, err = u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0)229+ _, err = u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0, 0)
230 assert.ErrorIs(t, err, domain.ErrNotFound)230 assert.ErrorIs(t, err, domain.ErrNotFound)
231 }231 }
232+
233+// The byte budget of a reader reaches the store, and a negative one means none.
234+func TestGetStepSummariesPassesTheBudget(t *testing.T) {
235+ t.Parallel()
236+ u := newCIUnit(t, nil)
237+ repo := name(t, "proj")
238+ j := ciJob(t, domain.CIPhaseEnded, jobSecret)
239+ u.meta.EXPECT().Get(mock.Anything, repo).Return(domain.Repo{Name: repo, Visibility: domain.VisibilityPublic}, nil)
240+ u.store.EXPECT().GetRun(mock.Anything, repo, int64(7)).Return(domain.CIRun{Repo: repo, ID: 7, Attempt: 1}, nil)
241+ u.store.EXPECT().GetJob(mock.Anything, int64(7), int32(1), jobIDOne).Return(j, nil)
242+ ref := ports.CILogRef{RunID: 7, JobID: jobIDOne, Attempt: 1}
243+ u.sums.EXPECT().List(mock.Anything, ref, domain.CIMaxListedSummaries, int64(4096)).
244+ Return([]domain.CIStepSummary{{Step: 1, Markdown: "a"}, {Step: 2, Omitted: true}}, false, nil).Once()
245+ u.sums.EXPECT().List(mock.Anything, ref, domain.CIMaxListedSummaries, int64(0)).Return(nil, false, nil).Once()
246+
247+ got, err := u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0, 4096)
248+ require.NoError(t, err)
249+ assert.True(t, got.Summaries[1].Omitted)
250+ _, err = u.ci.GetStepSummaries(context.Background(), domain.Anonymous(), repo, jobIDOne, 0, -5)
251+ require.NoError(t, err)
252+}

Keyboard shortcuts

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

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

Branches 2

Tags 42

↑↓ moveenter openesc close