detached f8d5320

merge: web for merge request notes

commit
f8d5320afe458590fd31791cd183c3a51c4a33bf
author
brice · · 2026-10-19 05:47 UTC
merge of
945925c + aec37bf · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
18 files · +1311 -527
M internal/adapters/web/diff.go +1 -0
@@ -30,6 +30,7 @@ type Seg struct {
3030 // DiffLine is one line of a hunk, in the unified view and, paired, in the
3131 // split view.
3232 type DiffLine struct {
33+ Selected bool
3334 Kind string // "ctx", "add", "del"
3435 Mark string // " ", "+", "-": every change keeps its marker, not only a colour
3536 Old, New string // line numbers, "" when the side has none
@@ -30,6 +30,7 @@ type Seg struct {
30 // DiffLine is one line of a hunk, in the unified view and, paired, in the30 // DiffLine is one line of a hunk, in the unified view and, paired, in the
31 // split view.31 // split view.
32 type DiffLine struct {32 type DiffLine struct {
33+ Selected bool
33 Kind string // "ctx", "add", "del"34 Kind string // "ctx", "add", "del"
34 Mark string // " ", "+", "-": every change keeps its marker, not only a colour35 Mark string // " ", "+", "-": every change keeps its marker, not only a colour
35 Old, New string // line numbers, "" when the side has none36 Old, New string // line numbers, "" when the side has none
M internal/adapters/web/diff.templ +4 -4
@@ -232,7 +232,7 @@ templ DiffFileView(f DiffFile, p DiffView) {
232232 }
233233 <tr class="hunk"><td colspan="4">{ h.Header }</td></tr>
234234 for _, ln := range h.Lines {
235- <tr class={ ln.Kind }>
235+ <tr class={ ln.Kind, templ.KV("sel", ln.Selected) }>
236236 <td class="ln">@gutter(ln.Old, ln.OldHref)</td>
237237 <td class="ln">@gutter(ln.New, ln.NewHref)</td>
238238 <td class="m">{ ln.Mark }</td>
@@ -369,15 +369,15 @@ templ splitSide(ln *DiffLine, old bool) {
369369 <td class="m none"></td>
370370 <td class="tx none"></td>
371371 } else {
372- <td class={ "ln", ln.Kind }>
372+ <td class={ "ln", ln.Kind, templ.KV("sel", ln.Selected) }>
373373 if old {
374374 @gutter(ln.Old, ln.OldHref)
375375 } else {
376376 @gutter(ln.New, ln.NewHref)
377377 }
378378 </td>
379- <td class={ "m", ln.Kind }>{ ln.Mark }</td>
380- <td class={ "tx", "chroma", ln.Kind }>@codeLine(*ln)</td>
379+ <td class={ "m", ln.Kind, templ.KV("sel", ln.Selected) }>{ ln.Mark }</td>
380+ <td class={ "tx", "chroma", ln.Kind, templ.KV("sel", ln.Selected) }>@codeLine(*ln)</td>
381381 }
382382 }
383383
@@ -232,7 +232,7 @@ templ DiffFileView(f DiffFile, p DiffView) {
232 }232 }
233 <tr class="hunk"><td colspan="4">{ h.Header }</td></tr>233 <tr class="hunk"><td colspan="4">{ h.Header }</td></tr>
234 for _, ln := range h.Lines {234 for _, ln := range h.Lines {
235- <tr class={ ln.Kind }>235+ <tr class={ ln.Kind, templ.KV("sel", ln.Selected) }>
236 <td class="ln">@gutter(ln.Old, ln.OldHref)</td>236 <td class="ln">@gutter(ln.Old, ln.OldHref)</td>
237 <td class="ln">@gutter(ln.New, ln.NewHref)</td>237 <td class="ln">@gutter(ln.New, ln.NewHref)</td>
238 <td class="m">{ ln.Mark }</td>238 <td class="m">{ ln.Mark }</td>
@@ -369,15 +369,15 @@ templ splitSide(ln *DiffLine, old bool) {
369 <td class="m none"></td>369 <td class="m none"></td>
370 <td class="tx none"></td>370 <td class="tx none"></td>
371 } else {371 } else {
372- <td class={ "ln", ln.Kind }>372+ <td class={ "ln", ln.Kind, templ.KV("sel", ln.Selected) }>
373 if old {373 if old {
374 @gutter(ln.Old, ln.OldHref)374 @gutter(ln.Old, ln.OldHref)
375 } else {375 } else {
376 @gutter(ln.New, ln.NewHref)376 @gutter(ln.New, ln.NewHref)
377 }377 }
378 </td>378 </td>
379- <td class={ "m", ln.Kind }>{ ln.Mark }</td>379+ <td class={ "m", ln.Kind, templ.KV("sel", ln.Selected) }>{ ln.Mark }</td>
380- <td class={ "tx", "chroma", ln.Kind }>@codeLine(*ln)</td>380+ <td class={ "tx", "chroma", ln.Kind, templ.KV("sel", ln.Selected) }>@codeLine(*ln)</td>
381 }381 }
382 }382 }
383 383
M internal/adapters/web/diff_templ.go +5 -5 generated file: collapsed
M internal/adapters/web/fragments.go +7 -1
@@ -58,7 +58,7 @@ func liveEvent(we ports.WatchEvent) bool {
5858 }
5959 switch we.Event.(type) {
6060 case domain.RepoCreated, domain.RepoUpdated, domain.RepoDeleted, domain.PushEvent,
61- domain.MergeRequestOpened, domain.MergeRequestMerged, domain.MergeRequestClosed, domain.MergeRequestReady, domain.CommentAdded,
61+ domain.MergeRequestOpened, domain.MergeRequestMerged, domain.MergeRequestClosed, domain.MergeRequestReady, domain.CommentAdded, domain.CommentEdited, domain.CommentDeleted, domain.CommentResolved,
6262 domain.RunUpdated, domain.JobUpdated:
6363 return true
6464 }
@@ -164,6 +164,12 @@ func (l *liveStream) repoPage(ctx context.Context, we ports.WatchEvent) ([]fragm
164164 return l.mrNotice(ctx, ev.MergeRequest.Number, "this merge request was closed — reload")
165165 case domain.MergeRequestReady:
166166 return l.mrNotice(ctx, ev.MergeRequest.Number, "this merge request is ready for review — reload")
167+ case domain.CommentEdited:
168+ return l.mrNotice(ctx, ev.Number, "a note was edited — reload")
169+ case domain.CommentDeleted:
170+ return l.mrNotice(ctx, ev.Number, "a note was deleted — reload")
171+ case domain.CommentResolved:
172+ return l.mrNotice(ctx, ev.Number, "a thread was resolved — reload")
167173 case domain.CommentAdded:
168174 return l.mrNotice(ctx, ev.Number, "a comment was added — reload")
169175 case domain.PushEvent:
@@ -58,7 +58,7 @@ func liveEvent(we ports.WatchEvent) bool {
58 }58 }
59 switch we.Event.(type) {59 switch we.Event.(type) {
60 case domain.RepoCreated, domain.RepoUpdated, domain.RepoDeleted, domain.PushEvent,60 case domain.RepoCreated, domain.RepoUpdated, domain.RepoDeleted, domain.PushEvent,
61- domain.MergeRequestOpened, domain.MergeRequestMerged, domain.MergeRequestClosed, domain.MergeRequestReady, domain.CommentAdded,61+ domain.MergeRequestOpened, domain.MergeRequestMerged, domain.MergeRequestClosed, domain.MergeRequestReady, domain.CommentAdded, domain.CommentEdited, domain.CommentDeleted, domain.CommentResolved,
62 domain.RunUpdated, domain.JobUpdated:62 domain.RunUpdated, domain.JobUpdated:
63 return true63 return true
64 }64 }
@@ -164,6 +164,12 @@ func (l *liveStream) repoPage(ctx context.Context, we ports.WatchEvent) ([]fragm
164 return l.mrNotice(ctx, ev.MergeRequest.Number, "this merge request was closed — reload")164 return l.mrNotice(ctx, ev.MergeRequest.Number, "this merge request was closed — reload")
165 case domain.MergeRequestReady:165 case domain.MergeRequestReady:
166 return l.mrNotice(ctx, ev.MergeRequest.Number, "this merge request is ready for review — reload")166 return l.mrNotice(ctx, ev.MergeRequest.Number, "this merge request is ready for review — reload")
167+ case domain.CommentEdited:
168+ return l.mrNotice(ctx, ev.Number, "a note was edited — reload")
169+ case domain.CommentDeleted:
170+ return l.mrNotice(ctx, ev.Number, "a note was deleted — reload")
171+ case domain.CommentResolved:
172+ return l.mrNotice(ctx, ev.Number, "a thread was resolved — reload")
167 case domain.CommentAdded:173 case domain.CommentAdded:
168 return l.mrNotice(ctx, ev.Number, "a comment was added — reload")174 return l.mrNotice(ctx, ev.Number, "a comment was added — reload")
169 case domain.PushEvent:175 case domain.PushEvent:
M internal/adapters/web/jstest/mr.test.mjs +26 -0
@@ -36,3 +36,29 @@ test('step: stays in the list', () => {
3636 assert.equal(C.step(-1, -1, 3), 2);
3737 assert.equal(C.step(0, 1, 0), -1);
3838 });
39+
40+test('rangeURL requires the same file, side and MR and sorts endpoints', () => {
41+ const base = 'https://forge.test/demo/merge-requests/1';
42+ const a = base+'?on=new:24:dir%2Fa.go#f1';
43+ const b = base+'?from=25&on=new:10:dir%2Fa.go#f1';
44+ const u = new URL(C.rangeURL(a,b,base));
45+ assert.equal(u.searchParams.get('on'),'new:10-24:dir/a.go');
46+ assert.equal(u.searchParams.get('from'),'25');
47+ assert.equal(u.hash,'#f1');
48+ assert.equal(C.rangeURL(a,a,base),null);
49+ assert.equal(C.rangeURL(a,b.replace('new:','old:'),base),null);
50+ assert.equal(C.rangeURL(a,b.replace('a.go','b.go'),base),null);
51+ assert.equal(C.rangeURL(a,b.replace('/1?','/2?'),base),null);
52+});
53+
54+test('quote opens the thread reply and prefills and focuses its textarea', () => {
55+ let focused=false;
56+ const field={value:'',focus(){focused=true;}};
57+ const details={open:false,querySelector(){return field;}};
58+ const thread={querySelector(){return details;}};
59+ const button={dataset:{quote:'> source\n\n^2 '},closest(){return {closest(){return thread;}};}};
60+ assert.equal(C.quote(button),true);
61+ assert.equal(details.open,true);
62+ assert.equal(field.value,'> source\n\n^2 ');
63+ assert.equal(focused,true);
64+});
@@ -36,3 +36,29 @@ test('step: stays in the list', () => {
36 assert.equal(C.step(-1, -1, 3), 2);36 assert.equal(C.step(-1, -1, 3), 2);
37 assert.equal(C.step(0, 1, 0), -1);37 assert.equal(C.step(0, 1, 0), -1);
38 });38 });
39+
40+test('rangeURL requires the same file, side and MR and sorts endpoints', () => {
41+ const base = 'https://forge.test/demo/merge-requests/1';
42+ const a = base+'?on=new:24:dir%2Fa.go#f1';
43+ const b = base+'?from=25&on=new:10:dir%2Fa.go#f1';
44+ const u = new URL(C.rangeURL(a,b,base));
45+ assert.equal(u.searchParams.get('on'),'new:10-24:dir/a.go');
46+ assert.equal(u.searchParams.get('from'),'25');
47+ assert.equal(u.hash,'#f1');
48+ assert.equal(C.rangeURL(a,a,base),null);
49+ assert.equal(C.rangeURL(a,b.replace('new:','old:'),base),null);
50+ assert.equal(C.rangeURL(a,b.replace('a.go','b.go'),base),null);
51+ assert.equal(C.rangeURL(a,b.replace('/1?','/2?'),base),null);
52+});
53+
54+test('quote opens the thread reply and prefills and focuses its textarea', () => {
55+ let focused=false;
56+ const field={value:'',focus(){focused=true;}};
57+ const details={open:false,querySelector(){return field;}};
58+ const thread={querySelector(){return details;}};
59+ const button={dataset:{quote:'> source\n\n^2 '},closest(){return {closest(){return thread;}};}};
60+ assert.equal(C.quote(button),true);
61+ assert.equal(details.open,true);
62+ assert.equal(field.value,'> source\n\n^2 ');
63+ assert.equal(focused,true);
64+});
M internal/adapters/web/markdown.go +79 -0
@@ -7,6 +7,8 @@ import (
77 "html/template"
88 "net/url"
99 "path"
10+ "regexp"
11+ "strconv"
1012 "strings"
1113
1214 "github.com/yuin/goldmark"
@@ -62,10 +64,40 @@ func (m *markdown) render(source []byte, repo, ref, dir string) (template.HTML,
6264 // #fragment links chosen, so that several documents on one page (the summaries
6365 // of a run) do not share ids.
6466 func (m *markdown) renderWithIDs(source []byte, repo, ref, dir, idPrefix string) (template.HTML, []Heading, error) {
67+ return m.renderDocument(source, repo, ref, dir, idPrefix, nil)
68+}
69+
70+// renderNote uses the repository Markdown policy, then adds local note links.
71+func (m *markdown) renderNote(source []byte, repo, ref, idPrefix string, notes func(int64) bool) (template.HTML, error) {
72+ h, _, err := m.renderDocument(source, repo, ref, "", idPrefix, notes)
73+ return h, err
74+}
75+
76+func (m *markdown) renderDocument(source []byte, repo, ref, dir, idPrefix string, notes func(int64) bool) (template.HTML, []Heading, error) {
6577 pc := parser.NewContext(parser.WithIDs(&prefixedIDs{prefix: idPrefix, inner: parser.NewContext().IDs()}))
6678 doc := m.md.Parser().Parse(text.NewReader(source), parser.WithContext(pc))
6779 pol := linkPolicy{repo: repo, ref: ref, dir: dir, source: source, idPrefix: idPrefix}
80+ // Collect eligible text before the policy unwraps rejected links: their
81+ // labels must not accidentally become references either.
82+ var refs []*ast.Text
83+ if notes != nil {
84+ _ = ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
85+ if !entering {
86+ return ast.WalkContinue, nil
87+ }
88+ switch n := n.(type) {
89+ case *ast.CodeSpan, *ast.Link, *ast.AutoLink, *ast.Image, *ast.CodeBlock, *ast.FencedCodeBlock:
90+ return ast.WalkSkipChildren, nil
91+ case *ast.Text:
92+ refs = append(refs, n)
93+ }
94+ return ast.WalkContinue, nil
95+ })
96+ }
6897 pol.apply(doc)
98+ for _, t := range refs {
99+ noteReferences(t, source, notes)
100+ }
69101
70102 var buf bytes.Buffer
71103 if err := m.md.Renderer().Render(&buf, source, doc); err != nil {
@@ -291,3 +323,50 @@ func (altRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) {
291323 return ast.WalkContinue, nil
292324 })
293325 }
326+
327+var noteReferencePattern = regexp.MustCompile(`(^|[^0-9A-Za-z_^])\^([1-9][0-9]{0,5})\b`)
328+
329+// noteReferences changes only text segments; code and link labels never enter
330+// this function. New fragment links deliberately bypass heading-prefix rewriting.
331+func noteReferences(t *ast.Text, source []byte, exists func(int64) bool) {
332+ parent := t.Parent()
333+ if parent == nil {
334+ return
335+ }
336+ value := t.Value(source)
337+ offset := 0
338+ for _, match := range noteReferencePattern.FindAllSubmatchIndex(value, -1) {
339+ id := string(value[match[4]:match[5]])
340+ n, err := strconv.ParseInt(id, 10, 64)
341+ if err != nil || !exists(n) {
342+ continue
343+ }
344+ start := match[4] - 1
345+ // Markdown punctuation may split adjacent text into separate AST nodes.
346+ // Check the original preceding byte as well as the segment-local regex.
347+ if at := t.Segment.Start + start; at > 0 {
348+ prev := source[at-1]
349+ if prev >= '0' && prev <= '9' || prev >= 'A' && prev <= 'Z' || prev >= 'a' && prev <= 'z' || prev == '_' || prev == '^' {
350+ continue
351+ }
352+ }
353+ if start > offset {
354+ parent.InsertBefore(parent, t, ast.NewTextSegment(text.NewSegment(t.Segment.Start+offset, t.Segment.Start+start)))
355+ }
356+ link := ast.NewLink()
357+ link.Destination = []byte("#c" + id)
358+ link.Title = []byte("note " + id)
359+ link.SetAttributeString("class", []byte("ref"))
360+ link.AppendChild(link, ast.NewString(value[start:match[5]]))
361+ parent.InsertBefore(parent, t, link)
362+ offset = match[5]
363+ }
364+ if offset == 0 {
365+ return
366+ }
367+ tail := ast.NewTextSegment(text.NewSegment(t.Segment.Start+offset, t.Segment.Stop))
368+ tail.SetSoftLineBreak(t.SoftLineBreak())
369+ tail.SetHardLineBreak(t.HardLineBreak())
370+ parent.InsertBefore(parent, t, tail)
371+ parent.RemoveChild(parent, t)
372+}
@@ -7,6 +7,8 @@ import (
7 "html/template"7 "html/template"
8 "net/url"8 "net/url"
9 "path"9 "path"
10+ "regexp"
11+ "strconv"
10 "strings"12 "strings"
11 13
12 "github.com/yuin/goldmark"14 "github.com/yuin/goldmark"
@@ -62,10 +64,40 @@ func (m *markdown) render(source []byte, repo, ref, dir string) (template.HTML,
62 // #fragment links chosen, so that several documents on one page (the summaries64 // #fragment links chosen, so that several documents on one page (the summaries
63 // of a run) do not share ids.65 // of a run) do not share ids.
64 func (m *markdown) renderWithIDs(source []byte, repo, ref, dir, idPrefix string) (template.HTML, []Heading, error) {66 func (m *markdown) renderWithIDs(source []byte, repo, ref, dir, idPrefix string) (template.HTML, []Heading, error) {
67+ return m.renderDocument(source, repo, ref, dir, idPrefix, nil)
68+}
69+
70+// renderNote uses the repository Markdown policy, then adds local note links.
71+func (m *markdown) renderNote(source []byte, repo, ref, idPrefix string, notes func(int64) bool) (template.HTML, error) {
72+ h, _, err := m.renderDocument(source, repo, ref, "", idPrefix, notes)
73+ return h, err
74+}
75+
76+func (m *markdown) renderDocument(source []byte, repo, ref, dir, idPrefix string, notes func(int64) bool) (template.HTML, []Heading, error) {
65 pc := parser.NewContext(parser.WithIDs(&prefixedIDs{prefix: idPrefix, inner: parser.NewContext().IDs()}))77 pc := parser.NewContext(parser.WithIDs(&prefixedIDs{prefix: idPrefix, inner: parser.NewContext().IDs()}))
66 doc := m.md.Parser().Parse(text.NewReader(source), parser.WithContext(pc))78 doc := m.md.Parser().Parse(text.NewReader(source), parser.WithContext(pc))
67 pol := linkPolicy{repo: repo, ref: ref, dir: dir, source: source, idPrefix: idPrefix}79 pol := linkPolicy{repo: repo, ref: ref, dir: dir, source: source, idPrefix: idPrefix}
80+ // Collect eligible text before the policy unwraps rejected links: their
81+ // labels must not accidentally become references either.
82+ var refs []*ast.Text
83+ if notes != nil {
84+ _ = ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
85+ if !entering {
86+ return ast.WalkContinue, nil
87+ }
88+ switch n := n.(type) {
89+ case *ast.CodeSpan, *ast.Link, *ast.AutoLink, *ast.Image, *ast.CodeBlock, *ast.FencedCodeBlock:
90+ return ast.WalkSkipChildren, nil
91+ case *ast.Text:
92+ refs = append(refs, n)
93+ }
94+ return ast.WalkContinue, nil
95+ })
96+ }
68 pol.apply(doc)97 pol.apply(doc)
98+ for _, t := range refs {
99+ noteReferences(t, source, notes)
100+ }
69 101
70 var buf bytes.Buffer102 var buf bytes.Buffer
71 if err := m.md.Renderer().Render(&buf, source, doc); err != nil {103 if err := m.md.Renderer().Render(&buf, source, doc); err != nil {
@@ -291,3 +323,50 @@ func (altRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) {
291 return ast.WalkContinue, nil323 return ast.WalkContinue, nil
292 })324 })
293 }325 }
326+
327+var noteReferencePattern = regexp.MustCompile(`(^|[^0-9A-Za-z_^])\^([1-9][0-9]{0,5})\b`)
328+
329+// noteReferences changes only text segments; code and link labels never enter
330+// this function. New fragment links deliberately bypass heading-prefix rewriting.
331+func noteReferences(t *ast.Text, source []byte, exists func(int64) bool) {
332+ parent := t.Parent()
333+ if parent == nil {
334+ return
335+ }
336+ value := t.Value(source)
337+ offset := 0
338+ for _, match := range noteReferencePattern.FindAllSubmatchIndex(value, -1) {
339+ id := string(value[match[4]:match[5]])
340+ n, err := strconv.ParseInt(id, 10, 64)
341+ if err != nil || !exists(n) {
342+ continue
343+ }
344+ start := match[4] - 1
345+ // Markdown punctuation may split adjacent text into separate AST nodes.
346+ // Check the original preceding byte as well as the segment-local regex.
347+ if at := t.Segment.Start + start; at > 0 {
348+ prev := source[at-1]
349+ if prev >= '0' && prev <= '9' || prev >= 'A' && prev <= 'Z' || prev >= 'a' && prev <= 'z' || prev == '_' || prev == '^' {
350+ continue
351+ }
352+ }
353+ if start > offset {
354+ parent.InsertBefore(parent, t, ast.NewTextSegment(text.NewSegment(t.Segment.Start+offset, t.Segment.Start+start)))
355+ }
356+ link := ast.NewLink()
357+ link.Destination = []byte("#c" + id)
358+ link.Title = []byte("note " + id)
359+ link.SetAttributeString("class", []byte("ref"))
360+ link.AppendChild(link, ast.NewString(value[start:match[5]]))
361+ parent.InsertBefore(parent, t, link)
362+ offset = match[5]
363+ }
364+ if offset == 0 {
365+ return
366+ }
367+ tail := ast.NewTextSegment(text.NewSegment(t.Segment.Start+offset, t.Segment.Stop))
368+ tail.SetSoftLineBreak(t.SoftLineBreak())
369+ tail.SetHardLineBreak(t.HardLineBreak())
370+ parent.InsertBefore(parent, t, tail)
371+ parent.RemoveChild(parent, t)
372+}
M internal/adapters/web/markdown_test.go +29 -0
@@ -136,3 +136,32 @@ func TestMarkdownGFM(t *testing.T) {
136136 assert.Contains(t, out, "<del>gone</del>")
137137 assert.Contains(t, out, `type="checkbox"`)
138138 }
139+
140+func TestNoteMarkdown(t *testing.T) {
141+ t.Parallel()
142+ tests := []struct {
143+ source string
144+ links int
145+ }{
146+ {"^2 ^99 a^2 _^2 ^^2 1^2", 1},
147+ {"`^2` [^2](https://example.org) [^2](javascript:alert(1))", 0},
148+ {"```\n^2\n```", 0},
149+ {"^2, ^2\n^2", 3},
150+ }
151+ for _, tt := range tests {
152+ t.Run(tt.source, func(t *testing.T) {
153+ t.Parallel()
154+ h, err := newMarkdown().renderNote([]byte(tt.source), "demo", "main", "c1-", func(n int64) bool { return n == 2 })
155+ require.NoError(t, err)
156+ assert.Equal(t, tt.links, strings.Count(string(h), `href="#c2"`), string(h))
157+ assert.NotContains(t, string(h), `href="#c99"`)
158+ assert.NotContains(t, string(h), "javascript:")
159+ })
160+ }
161+ h, err := newMarkdown().renderNote([]byte("# Heading\n\n<script>alert(1)</script>\n\n**bold** ^2"), "demo", "main", "c1-", func(n int64) bool { return n == 2 })
162+ require.NoError(t, err)
163+ assert.Contains(t, string(h), `id="c1-heading"`)
164+ assert.Contains(t, string(h), "<strong>bold</strong>")
165+ assert.NotContains(t, string(h), "<script")
166+ assert.Contains(t, string(h), `title="note 2"`)
167+}
@@ -136,3 +136,32 @@ func TestMarkdownGFM(t *testing.T) {
136 assert.Contains(t, out, "<del>gone</del>")136 assert.Contains(t, out, "<del>gone</del>")
137 assert.Contains(t, out, `type="checkbox"`)137 assert.Contains(t, out, `type="checkbox"`)
138 }138 }
139+
140+func TestNoteMarkdown(t *testing.T) {
141+ t.Parallel()
142+ tests := []struct {
143+ source string
144+ links int
145+ }{
146+ {"^2 ^99 a^2 _^2 ^^2 1^2", 1},
147+ {"`^2` [^2](https://example.org) [^2](javascript:alert(1))", 0},
148+ {"```\n^2\n```", 0},
149+ {"^2, ^2\n^2", 3},
150+ }
151+ for _, tt := range tests {
152+ t.Run(tt.source, func(t *testing.T) {
153+ t.Parallel()
154+ h, err := newMarkdown().renderNote([]byte(tt.source), "demo", "main", "c1-", func(n int64) bool { return n == 2 })
155+ require.NoError(t, err)
156+ assert.Equal(t, tt.links, strings.Count(string(h), `href="#c2"`), string(h))
157+ assert.NotContains(t, string(h), `href="#c99"`)
158+ assert.NotContains(t, string(h), "javascript:")
159+ })
160+ }
161+ h, err := newMarkdown().renderNote([]byte("# Heading\n\n<script>alert(1)</script>\n\n**bold** ^2"), "demo", "main", "c1-", func(n int64) bool { return n == 2 })
162+ require.NoError(t, err)
163+ assert.Contains(t, string(h), `id="c1-heading"`)
164+ assert.Contains(t, string(h), "<strong>bold</strong>")
165+ assert.NotContains(t, string(h), "<script")
166+ assert.Contains(t, string(h), `title="note 2"`)
167+}
M internal/adapters/web/mr.go +125 -25
@@ -101,13 +101,19 @@ type MRListPage struct {
101101
102102 // CommentView is a comment of a merge request, ready to show.
103103 type CommentView struct {
104- ID string
105- Author string
106- When string
107- WhenAt string
108- Abs string
109- Parts []TextPart
110- Resolved bool
104+ ID string
105+ Author string
106+ When string
107+ WhenAt string
108+ Abs string
109+ Body template.HTML
110+ Replies []CommentView
111+ Edited, Deleted bool
112+ Verdict, Range, Quote string
113+ Edit *EditForm
114+ Delete *DeleteForm
115+ Reply *CommentForm
116+ Resolved bool
111117 // Outdated is true when the head moved since the comment was written, so
112118 // its line may have moved too.
113119 Outdated bool
@@ -119,6 +125,19 @@ type CommentView struct {
119125 Resolve *ResolveForm
120126 }
121127
128+type EditForm struct {
129+ Action, Text string
130+ From int
131+}
132+type DeleteForm struct {
133+ Action string
134+ From int
135+}
136+type VerdictView struct {
137+ Author, Label, Href, Short string
138+ Stale bool
139+}
140+
122141 // ResolveForm is the POST that changes the resolved state of a comment.
123142 type ResolveForm struct {
124143 Action string
@@ -135,9 +154,10 @@ type CommentForm struct {
135154 Action string
136155 Title string
137156 // Path, Line and Side designate the line; empty for a general comment.
138- Path, Line, Side string
139- Cancel string
140- Focus bool
157+ Path, Line, Side string
158+ StartLine, ReplyTo string
159+ Cancel string
160+ Focus bool
141161 // From is the window of the page the form is on (see mrWindow), sent
142162 // back so that the answer returns to it; 0 for the first.
143163 From int
@@ -192,6 +212,7 @@ type MRPage struct {
192212 State string
193213 Open bool
194214 Signed bool
215+ NoteAuthor string
195216 Source, Target string
196217 SourceHref string
197218 TargetHref string
@@ -226,6 +247,7 @@ type MRPage struct {
226247 NComments int
227248 NUnresolved int
228249 GeneralForm *CommentForm
250+ Verdicts []VerdictView
229251
230252 Commits []MRCommit
231253 CommitsNote string
@@ -471,7 +493,7 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ
471493
472494 p := MRPage{
473495 Number: "#" + strconv.FormatInt(n, 10), Title: visibleText(m.Title.String()),
474- State: mrStateName(m), Open: open, Signed: signed,
496+ State: mrStateName(m), Open: open, Signed: signed, NoteAuthor: domain.AuthorName(principal(ctx)),
475497 Source: visibleText(m.Source), Target: visibleText(m.Target),
476498 Author: s.who(m.Author),
477499 Head: head, HeadShort: short(head),
@@ -485,12 +507,15 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ
485507 // Relative links point into the source branch as it is now (its head
486508 // commit); the heading ids carry their own prefix, apart from the
487509 // ids of the diff.
488- h, _, err := s.md.renderWithIDs([]byte(desc), d.name(), head, "", "mr-desc-")
510+ h, err := s.md.renderNote([]byte(desc), d.name(), head, "mr-desc-", noteIDs(res.Comments))
489511 if err != nil {
490512 return nil, 0, err
491513 }
492514 p.Desc = h
493515 }
516+ for _, v := range res.Verdicts {
517+ p.Verdicts = append(p.Verdicts, VerdictView{Author: s.who(v.Author), Label: v.Verdict.String(), Stale: v.Stale, Href: "#c" + strconv.FormatInt(v.CommentID, 10), Short: short(v.CommitID.String())})
518+ }
494519 p.Opened.Name = p.Author
495520 p.Opened.When, p.Opened.WhenAt = whenOf(now, m.CreatedAt)
496521 if !m.CreatedAt.IsZero() {
@@ -684,9 +709,10 @@ func shellWord(s string) string {
684709 // lineRef designates a line of the diff in the query of the comment form:
685710 // "?on=new:12:path/to/file".
686711 type lineRef struct {
687- side string
688- line int
689- path string
712+ side string
713+ line int
714+ start int
715+ path string
690716 }
691717
692718 func parseOn(s string) *lineRef {
@@ -695,11 +721,21 @@ func parseOn(s string) *lineRef {
695721 return nil
696722 }
697723 num, path, ok := strings.Cut(rest, ":")
724+ start := 0
725+ if a, b, rangeOK := strings.Cut(num, "-"); rangeOK {
726+ var e error
727+ start, e = strconv.Atoi(a)
728+ end, e2 := strconv.Atoi(b)
729+ if e != nil || e2 != nil || start < 1 || start >= end {
730+ return nil
731+ }
732+ num = b
733+ }
698734 line, err := strconv.Atoi(num)
699735 if !ok || err != nil || line < 1 || path == "" {
700736 return nil
701737 }
702- return &lineRef{side: side, line: line, path: path}
738+ return &lineRef{side: side, line: line, start: start, path: path}
703739 }
704740
705741 // placeComments puts every comment under its line when the diff shown has it,
@@ -757,21 +793,62 @@ func (s *Server) placeComments(p *MRPage, repo string, n int64, comments []domai
757793 return index[lineKey{path, side, line}]
758794 }
759795
796+ exists := noteIDs(comments)
797+ children := map[int64][]domain.Comment{}
760798 for _, c := range comments {
799+ if c.ReplyTo != 0 {
800+ children[c.ReplyTo] = append(children[c.ReplyTo], c)
801+ }
802+ if !c.Deleted {
803+ p.NComments++
804+ }
805+ if c.ReplyTo == 0 && !c.Deleted && !c.Resolved {
806+ p.NUnresolved++
807+ }
808+ }
809+ var view func(domain.Comment) CommentView
810+ view = func(c domain.Comment) CommentView {
761811 cid := strconv.FormatInt(c.ID, 10)
762- v := CommentView{
763- ID: cid, Author: s.who(c.Author),
764- Parts: plainParts(c.Text.String()), Resolved: c.Resolved,
765- Outdated: !c.CommitID.IsZero() && c.CommitID.String() != head,
812+ body, err := s.md.renderNote([]byte(c.Text.String()), repo, head, "c"+cid+"-", exists)
813+ if err != nil {
814+ body = ""
766815 }
816+ v := CommentView{ID: cid, Author: s.who(c.Author), Body: body, Resolved: c.Resolved, Deleted: c.Deleted, Edited: !c.EditedAt.IsZero(), Verdict: c.Verdict.String(), Outdated: !c.CommitID.IsZero() && c.CommitID.String() != head}
767817 v.When, v.WhenAt = whenOf(now, c.CreatedAt)
768818 if !c.CreatedAt.IsZero() {
769819 v.Abs = absolute(c.CreatedAt)
770820 }
771- p.NComments++
772- if !c.Resolved {
773- p.NUnresolved++
821+ if c.StartLine > 0 {
822+ v.Range = "lines " + itoa(c.StartLine) + "–" + itoa(c.Line)
774823 }
824+ if canWrite && !c.Deleted {
825+ from := windowStart(fileAt[c.Path])
826+ root := cid
827+ if c.ReplyTo != 0 {
828+ root = strconv.FormatInt(c.ReplyTo, 10)
829+ }
830+ v.Quote = "> " + strings.ReplaceAll(c.Text.String(), "\n", "\n> ") + "\n\n^" + cid + " "
831+ if c.ReplyTo == 0 {
832+ v.Reply = &CommentForm{ID: "reply-" + cid, Action: mrHref(repo, n, "comments"), Title: "Reply", ReplyTo: root, From: from}
833+ }
834+ if c.Author == p.NoteAuthor {
835+ v.Edit = &EditForm{Action: mrHref(repo, n, "comments", cid, "edit"), Text: c.Text.String(), From: from}
836+ v.Delete = &DeleteForm{Action: mrHref(repo, n, "comments", cid, "delete"), From: from}
837+ }
838+ }
839+ for _, reply := range children[c.ID] {
840+ if !reply.Deleted {
841+ v.Replies = append(v.Replies, view(reply))
842+ }
843+ }
844+ return v
845+ }
846+ for _, c := range comments {
847+ if c.ReplyTo != 0 || c.Deleted && len(children[c.ID]) == 0 {
848+ continue
849+ }
850+ cid := strconv.FormatInt(c.ID, 10)
851+ v := view(c)
775852 general := c.Path == ""
776853 fi, listed := fileAt[c.Path]
777854 if !general && listed && p.Files[fi].Lazy {
@@ -789,7 +866,7 @@ func (s *Server) placeComments(p *MRPage, repo string, n int64, comments []domai
789866 v.Where = visibleText(c.Path) + ":" + itoa(c.Line) + " (" + side + ")"
790867 }
791868 }
792- if canWrite {
869+ if canWrite && !c.Deleted {
793870 v.Resolve = &ResolveForm{Action: resolveAction(cid), Value: !c.Resolved, Label: "Resolve"}
794871 if !general && listed {
795872 v.Resolve.From = windowStart(fi)
@@ -815,9 +892,18 @@ func (s *Server) placeComments(p *MRPage, repo string, n int64, comments []domai
815892 if on.side == "old" {
816893 side = domain.SideOld
817894 }
895+ start := on.start
896+ if start == 0 {
897+ start = on.line
898+ }
899+ for key, ln := range index {
900+ if key.path == on.path && key.side == side && key.line >= start && key.line <= on.line {
901+ ln.Selected = true
902+ }
903+ }
818904 if ln := find(on.path, side, on.line); ln != nil {
819905 ln.Form = &CommentForm{
820- ID: "cf-line", Action: mrHref(repo, n, "comments"), Path: on.path, Line: itoa(on.line), Side: on.side,
906+ StartLine: optionalLine(on.start), ID: "cf-line", Action: mrHref(repo, n, "comments"), Path: on.path, Line: itoa(on.line), Side: on.side,
821907 Title: "comment on " + visibleText(on.path) + ":" + itoa(on.line) + " (" + on.side + ")",
822908 Cancel: withFrom(self, windowStart(fileAt[on.path])) + "#" + fileIDs[on.path], Focus: true,
823909 From: windowStart(fileAt[on.path]),
@@ -842,3 +928,17 @@ func userText(parts []TextPart) templ.Component {
842928 return err
843929 })
844930 }
931+
932+func noteIDs(comments []domain.Comment) func(int64) bool {
933+ ids := make(map[int64]bool, len(comments))
934+ for _, c := range comments {
935+ ids[c.ID] = true
936+ }
937+ return func(n int64) bool { return ids[n] }
938+}
939+func optionalLine(n int) string {
940+ if n == 0 {
941+ return ""
942+ }
943+ return itoa(n)
944+}
@@ -101,13 +101,19 @@ type MRListPage struct {
101 101
102 // CommentView is a comment of a merge request, ready to show.102 // CommentView is a comment of a merge request, ready to show.
103 type CommentView struct {103 type CommentView struct {
104- ID string104+ ID string
105- Author string105+ Author string
106- When string106+ When string
107- WhenAt string107+ WhenAt string
108- Abs string108+ Abs string
109- Parts []TextPart109+ Body template.HTML
110- Resolved bool110+ Replies []CommentView
111+ Edited, Deleted bool
112+ Verdict, Range, Quote string
113+ Edit *EditForm
114+ Delete *DeleteForm
115+ Reply *CommentForm
116+ Resolved bool
111 // Outdated is true when the head moved since the comment was written, so117 // Outdated is true when the head moved since the comment was written, so
112 // its line may have moved too.118 // its line may have moved too.
113 Outdated bool119 Outdated bool
@@ -119,6 +125,19 @@ type CommentView struct {
119 Resolve *ResolveForm125 Resolve *ResolveForm
120 }126 }
121 127
128+type EditForm struct {
129+ Action, Text string
130+ From int
131+}
132+type DeleteForm struct {
133+ Action string
134+ From int
135+}
136+type VerdictView struct {
137+ Author, Label, Href, Short string
138+ Stale bool
139+}
140+
122 // ResolveForm is the POST that changes the resolved state of a comment.141 // ResolveForm is the POST that changes the resolved state of a comment.
123 type ResolveForm struct {142 type ResolveForm struct {
124 Action string143 Action string
@@ -135,9 +154,10 @@ type CommentForm struct {
135 Action string154 Action string
136 Title string155 Title string
137 // Path, Line and Side designate the line; empty for a general comment.156 // Path, Line and Side designate the line; empty for a general comment.
138- Path, Line, Side string157+ Path, Line, Side string
139- Cancel string158+ StartLine, ReplyTo string
140- Focus bool159+ Cancel string
160+ Focus bool
141 // From is the window of the page the form is on (see mrWindow), sent161 // From is the window of the page the form is on (see mrWindow), sent
142 // back so that the answer returns to it; 0 for the first.162 // back so that the answer returns to it; 0 for the first.
143 From int163 From int
@@ -192,6 +212,7 @@ type MRPage struct {
192 State string212 State string
193 Open bool213 Open bool
194 Signed bool214 Signed bool
215+ NoteAuthor string
195 Source, Target string216 Source, Target string
196 SourceHref string217 SourceHref string
197 TargetHref string218 TargetHref string
@@ -226,6 +247,7 @@ type MRPage struct {
226 NComments int247 NComments int
227 NUnresolved int248 NUnresolved int
228 GeneralForm *CommentForm249 GeneralForm *CommentForm
250+ Verdicts []VerdictView
229 251
230 Commits []MRCommit252 Commits []MRCommit
231 CommitsNote string253 CommitsNote string
@@ -471,7 +493,7 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ
471 493
472 p := MRPage{494 p := MRPage{
473 Number: "#" + strconv.FormatInt(n, 10), Title: visibleText(m.Title.String()),495 Number: "#" + strconv.FormatInt(n, 10), Title: visibleText(m.Title.String()),
474- State: mrStateName(m), Open: open, Signed: signed,496+ State: mrStateName(m), Open: open, Signed: signed, NoteAuthor: domain.AuthorName(principal(ctx)),
475 Source: visibleText(m.Source), Target: visibleText(m.Target),497 Source: visibleText(m.Source), Target: visibleText(m.Target),
476 Author: s.who(m.Author),498 Author: s.who(m.Author),
477 Head: head, HeadShort: short(head),499 Head: head, HeadShort: short(head),
@@ -485,12 +507,15 @@ func (s *Server) mrPage(ctx context.Context, r *http.Request, since bool) (templ
485 // Relative links point into the source branch as it is now (its head507 // Relative links point into the source branch as it is now (its head
486 // commit); the heading ids carry their own prefix, apart from the508 // commit); the heading ids carry their own prefix, apart from the
487 // ids of the diff.509 // ids of the diff.
488- h, _, err := s.md.renderWithIDs([]byte(desc), d.name(), head, "", "mr-desc-")510+ h, err := s.md.renderNote([]byte(desc), d.name(), head, "mr-desc-", noteIDs(res.Comments))
489 if err != nil {511 if err != nil {
490 return nil, 0, err512 return nil, 0, err
491 }513 }
492 p.Desc = h514 p.Desc = h
493 }515 }
516+ for _, v := range res.Verdicts {
517+ p.Verdicts = append(p.Verdicts, VerdictView{Author: s.who(v.Author), Label: v.Verdict.String(), Stale: v.Stale, Href: "#c" + strconv.FormatInt(v.CommentID, 10), Short: short(v.CommitID.String())})
518+ }
494 p.Opened.Name = p.Author519 p.Opened.Name = p.Author
495 p.Opened.When, p.Opened.WhenAt = whenOf(now, m.CreatedAt)520 p.Opened.When, p.Opened.WhenAt = whenOf(now, m.CreatedAt)
496 if !m.CreatedAt.IsZero() {521 if !m.CreatedAt.IsZero() {
@@ -684,9 +709,10 @@ func shellWord(s string) string {
684 // lineRef designates a line of the diff in the query of the comment form:709 // lineRef designates a line of the diff in the query of the comment form:
685 // "?on=new:12:path/to/file".710 // "?on=new:12:path/to/file".
686 type lineRef struct {711 type lineRef struct {
687- side string712+ side string
688- line int713+ line int
689- path string714+ start int
715+ path string
690 }716 }
691 717
692 func parseOn(s string) *lineRef {718 func parseOn(s string) *lineRef {
@@ -695,11 +721,21 @@ func parseOn(s string) *lineRef {
695 return nil721 return nil
696 }722 }
697 num, path, ok := strings.Cut(rest, ":")723 num, path, ok := strings.Cut(rest, ":")
724+ start := 0
725+ if a, b, rangeOK := strings.Cut(num, "-"); rangeOK {
726+ var e error
727+ start, e = strconv.Atoi(a)
728+ end, e2 := strconv.Atoi(b)
729+ if e != nil || e2 != nil || start < 1 || start >= end {
730+ return nil
731+ }
732+ num = b
733+ }
698 line, err := strconv.Atoi(num)734 line, err := strconv.Atoi(num)
699 if !ok || err != nil || line < 1 || path == "" {735 if !ok || err != nil || line < 1 || path == "" {
700 return nil736 return nil
701 }737 }
702- return &lineRef{side: side, line: line, path: path}738+ return &lineRef{side: side, line: line, start: start, path: path}
703 }739 }
704 740
705 // placeComments puts every comment under its line when the diff shown has it,741 // placeComments puts every comment under its line when the diff shown has it,
@@ -757,21 +793,62 @@ func (s *Server) placeComments(p *MRPage, repo string, n int64, comments []domai
757 return index[lineKey{path, side, line}]793 return index[lineKey{path, side, line}]
758 }794 }
759 795
796+ exists := noteIDs(comments)
797+ children := map[int64][]domain.Comment{}
760 for _, c := range comments {798 for _, c := range comments {
799+ if c.ReplyTo != 0 {
800+ children[c.ReplyTo] = append(children[c.ReplyTo], c)
801+ }
802+ if !c.Deleted {
803+ p.NComments++
804+ }
805+ if c.ReplyTo == 0 && !c.Deleted && !c.Resolved {
806+ p.NUnresolved++
807+ }
808+ }
809+ var view func(domain.Comment) CommentView
810+ view = func(c domain.Comment) CommentView {
761 cid := strconv.FormatInt(c.ID, 10)811 cid := strconv.FormatInt(c.ID, 10)
762- v := CommentView{812+ body, err := s.md.renderNote([]byte(c.Text.String()), repo, head, "c"+cid+"-", exists)
763- ID: cid, Author: s.who(c.Author),813+ if err != nil {
764- Parts: plainParts(c.Text.String()), Resolved: c.Resolved,814+ body = ""
765- Outdated: !c.CommitID.IsZero() && c.CommitID.String() != head,
766 }815 }
816+ v := CommentView{ID: cid, Author: s.who(c.Author), Body: body, Resolved: c.Resolved, Deleted: c.Deleted, Edited: !c.EditedAt.IsZero(), Verdict: c.Verdict.String(), Outdated: !c.CommitID.IsZero() && c.CommitID.String() != head}
767 v.When, v.WhenAt = whenOf(now, c.CreatedAt)817 v.When, v.WhenAt = whenOf(now, c.CreatedAt)
768 if !c.CreatedAt.IsZero() {818 if !c.CreatedAt.IsZero() {
769 v.Abs = absolute(c.CreatedAt)819 v.Abs = absolute(c.CreatedAt)
770 }820 }
771- p.NComments++821+ if c.StartLine > 0 {
772- if !c.Resolved {822+ v.Range = "lines " + itoa(c.StartLine) + "–" + itoa(c.Line)
773- p.NUnresolved++
774 }823 }
824+ if canWrite && !c.Deleted {
825+ from := windowStart(fileAt[c.Path])
826+ root := cid
827+ if c.ReplyTo != 0 {
828+ root = strconv.FormatInt(c.ReplyTo, 10)
829+ }
830+ v.Quote = "> " + strings.ReplaceAll(c.Text.String(), "\n", "\n> ") + "\n\n^" + cid + " "
831+ if c.ReplyTo == 0 {
832+ v.Reply = &CommentForm{ID: "reply-" + cid, Action: mrHref(repo, n, "comments"), Title: "Reply", ReplyTo: root, From: from}
833+ }
834+ if c.Author == p.NoteAuthor {
835+ v.Edit = &EditForm{Action: mrHref(repo, n, "comments", cid, "edit"), Text: c.Text.String(), From: from}
836+ v.Delete = &DeleteForm{Action: mrHref(repo, n, "comments", cid, "delete"), From: from}
837+ }
838+ }
839+ for _, reply := range children[c.ID] {
840+ if !reply.Deleted {
841+ v.Replies = append(v.Replies, view(reply))
842+ }
843+ }
844+ return v
845+ }
846+ for _, c := range comments {
847+ if c.ReplyTo != 0 || c.Deleted && len(children[c.ID]) == 0 {
848+ continue
849+ }
850+ cid := strconv.FormatInt(c.ID, 10)
851+ v := view(c)
775 general := c.Path == ""852 general := c.Path == ""
776 fi, listed := fileAt[c.Path]853 fi, listed := fileAt[c.Path]
777 if !general && listed && p.Files[fi].Lazy {854 if !general && listed && p.Files[fi].Lazy {
@@ -789,7 +866,7 @@ func (s *Server) placeComments(p *MRPage, repo string, n int64, comments []domai
789 v.Where = visibleText(c.Path) + ":" + itoa(c.Line) + " (" + side + ")"866 v.Where = visibleText(c.Path) + ":" + itoa(c.Line) + " (" + side + ")"
790 }867 }
791 }868 }
792- if canWrite {869+ if canWrite && !c.Deleted {
793 v.Resolve = &ResolveForm{Action: resolveAction(cid), Value: !c.Resolved, Label: "Resolve"}870 v.Resolve = &ResolveForm{Action: resolveAction(cid), Value: !c.Resolved, Label: "Resolve"}
794 if !general && listed {871 if !general && listed {
795 v.Resolve.From = windowStart(fi)872 v.Resolve.From = windowStart(fi)
@@ -815,9 +892,18 @@ func (s *Server) placeComments(p *MRPage, repo string, n int64, comments []domai
815 if on.side == "old" {892 if on.side == "old" {
816 side = domain.SideOld893 side = domain.SideOld
817 }894 }
895+ start := on.start
896+ if start == 0 {
897+ start = on.line
898+ }
899+ for key, ln := range index {
900+ if key.path == on.path && key.side == side && key.line >= start && key.line <= on.line {
901+ ln.Selected = true
902+ }
903+ }
818 if ln := find(on.path, side, on.line); ln != nil {904 if ln := find(on.path, side, on.line); ln != nil {
819 ln.Form = &CommentForm{905 ln.Form = &CommentForm{
820- ID: "cf-line", Action: mrHref(repo, n, "comments"), Path: on.path, Line: itoa(on.line), Side: on.side,906+ StartLine: optionalLine(on.start), ID: "cf-line", Action: mrHref(repo, n, "comments"), Path: on.path, Line: itoa(on.line), Side: on.side,
821 Title: "comment on " + visibleText(on.path) + ":" + itoa(on.line) + " (" + on.side + ")",907 Title: "comment on " + visibleText(on.path) + ":" + itoa(on.line) + " (" + on.side + ")",
822 Cancel: withFrom(self, windowStart(fileAt[on.path])) + "#" + fileIDs[on.path], Focus: true,908 Cancel: withFrom(self, windowStart(fileAt[on.path])) + "#" + fileIDs[on.path], Focus: true,
823 From: windowStart(fileAt[on.path]),909 From: windowStart(fileAt[on.path]),
@@ -842,3 +928,17 @@ func userText(parts []TextPart) templ.Component {
842 return err928 return err
843 })929 })
844 }930 }
931+
932+func noteIDs(comments []domain.Comment) func(int64) bool {
933+ ids := make(map[int64]bool, len(comments))
934+ for _, c := range comments {
935+ ids[c.ID] = true
936+ }
937+ return func(n int64) bool { return ids[n] }
938+}
939+func optionalLine(n int) string {
940+ if n == 0 {
941+ return ""
942+ }
943+ return itoa(n)
944+}
M internal/adapters/web/mr.templ +58 -6
@@ -229,10 +229,9 @@ templ lineNotes(notes []CommentView, form *CommentForm, suffix string) {
229229 }
230230
231231 // commentView is a note: a ruled block, under its line in the diff or in the
232-// list of comments. The text is plain: escaped, its line breaks kept, its
233-// http(s) addresses linked.
232+// list of comments. Body comes only from the safe Markdown renderer.
234233 templ commentView(c CommentView, suffix string) {
235- <div class="cmt" id={ "c" + c.ID + suffix } data-resolved={ boolText(c.Resolved) }>
234+ <div class="cmt" id={ "c" + c.ID + suffix } data-resolved={ boolText(c.Resolved) } data-thread?={ c.Reply != nil }>
236235 <p class="c-head">
237236 <span class="c-who">{ c.Author }</span>
238237 if c.WhenAt != "" {
@@ -248,7 +247,39 @@ templ commentView(c CommentView, suffix string) {
248247 { " " }<span class="c-where mute">{ c.Where }</span>
249248 }
250249 </p>
251- <p class="c-text">@userText(c.Parts)</p>
250+ if c.Deleted {
251+ <p class="mute">deleted note</p>
252+ } else {
253+ <div class="c-body prose">
254+ @templ.Raw(string(c.Body))
255+ </div>
256+ }
257+ if c.Edited {
258+ <span class="mute">edited</span>
259+ }
260+ if c.Verdict != "" {
261+ <span class="c-state">{ c.Verdict }</span>
262+ }
263+ if c.Range != "" {
264+ <span class="mute">{ c.Range }</span>
265+ }
266+ if c.Quote != "" {
267+ <button type="button" class="btn" data-js data-quote={ c.Quote }>Quote</button>
268+ }
269+ if c.Edit != nil {
270+ <details><summary>Edit</summary><form method="post" action={ templ.SafeURL(c.Edit.Action) }><input type="hidden" name="from" value={ itoa(c.Edit.From) }/><textarea name="text" maxlength="8000">{ c.Edit.Text }</textarea><button class="btn" type="submit">Save</button></form></details>
271+ }
272+ if c.Delete != nil {
273+ <details><summary>Delete</summary><form method="post" action={ templ.SafeURL(c.Delete.Action) }><input type="hidden" name="from" value={ itoa(c.Delete.From) }/><p>Delete this note?</p><button class="btn" type="submit">Confirm delete</button></form></details>
274+ }
275+ for _, reply := range c.Replies {
276+ @commentView(reply, suffix)
277+ }
278+ if c.Reply != nil {
279+ <details data-reply>
280+ <summary>Reply</summary>@commentForm(*c.Reply, suffix)
281+ </details>
282+ }
252283 if c.Resolve != nil {
253284 <form class="c-act" method="post" action={ templ.SafeURL(c.Resolve.Action) }>
254285 <input type="hidden" name="resolved" value={ boolText(c.Resolve.Value) }/>
@@ -277,8 +308,7 @@ templ commentForm(f CommentForm, suffix string) {
277308 name="text"
278309 rows="3"
279310 maxlength="8000"
280- required
281- placeholder="plain text, 8000 characters at most"
311+ placeholder="Markdown, 8000 characters at most"
282312 if f.Focus {
283313 autofocus
284314 }
@@ -287,6 +317,13 @@ templ commentForm(f CommentForm, suffix string) {
287317 <input type="hidden" name="path" value={ f.Path }/>
288318 <input type="hidden" name="line" value={ f.Line }/>
289319 <input type="hidden" name="side" value={ f.Side }/>
320+ <label>from line <input type="number" name="start_line" min="1" value={ f.StartLine }/></label>
321+ }
322+ if f.ReplyTo != "" {
323+ <input type="hidden" name="reply_to" value={ f.ReplyTo }/>
324+ }
325+ if f.Path == "" && f.ReplyTo == "" {
326+ <fieldset><legend>Verdict</legend><label><input type="radio" name="verdict" value="" checked/>Note</label><label><input type="radio" name="verdict" value="approve"/>Approve</label><label><input type="radio" name="verdict" value="changes_requested"/>Request changes</label></fieldset>
290327 }
291328 if f.From > 0 {
292329 <input type="hidden" name="from" value={ itoa(f.From) }/>
@@ -310,6 +347,21 @@ templ mrRail(p MRPage) {
310347 </h2>
311348 @FileList(p.Files)
312349 </section>
350+ if len(p.Verdicts) > 0 {
351+ <section>
352+ <h2>Verdicts</h2>
353+ <ul>
354+ for _, v := range p.Verdicts {
355+ <li>
356+ <a href={ templ.SafeURL(v.Href) }>{ v.Author }: { v.Label }</a>
357+ if v.Stale {
358+ <span class="mute">(stale)</span>
359+ }
360+ </li>
361+ }
362+ </ul>
363+ </section>
364+ }
313365 @MRChecks(p.Checks)
314366 <section aria-labelledby="r-commits">
315367 <h2 id="r-commits">Commits <span class="count">{ itoa(len(p.Commits)) }</span></h2>
@@ -229,10 +229,9 @@ templ lineNotes(notes []CommentView, form *CommentForm, suffix string) {
229 }229 }
230 230
231 // commentView is a note: a ruled block, under its line in the diff or in the231 // commentView is a note: a ruled block, under its line in the diff or in the
232-// list of comments. The text is plain: escaped, its line breaks kept, its232+// list of comments. Body comes only from the safe Markdown renderer.
233-// http(s) addresses linked.
234 templ commentView(c CommentView, suffix string) {233 templ commentView(c CommentView, suffix string) {
235- <div class="cmt" id={ "c" + c.ID + suffix } data-resolved={ boolText(c.Resolved) }>234+ <div class="cmt" id={ "c" + c.ID + suffix } data-resolved={ boolText(c.Resolved) } data-thread?={ c.Reply != nil }>
236 <p class="c-head">235 <p class="c-head">
237 <span class="c-who">{ c.Author }</span>236 <span class="c-who">{ c.Author }</span>
238 if c.WhenAt != "" {237 if c.WhenAt != "" {
@@ -248,7 +247,39 @@ templ commentView(c CommentView, suffix string) {
248 { " " }<span class="c-where mute">{ c.Where }</span>247 { " " }<span class="c-where mute">{ c.Where }</span>
249 }248 }
250 </p>249 </p>
251- <p class="c-text">@userText(c.Parts)</p>250+ if c.Deleted {
251+ <p class="mute">deleted note</p>
252+ } else {
253+ <div class="c-body prose">
254+ @templ.Raw(string(c.Body))
255+ </div>
256+ }
257+ if c.Edited {
258+ <span class="mute">edited</span>
259+ }
260+ if c.Verdict != "" {
261+ <span class="c-state">{ c.Verdict }</span>
262+ }
263+ if c.Range != "" {
264+ <span class="mute">{ c.Range }</span>
265+ }
266+ if c.Quote != "" {
267+ <button type="button" class="btn" data-js data-quote={ c.Quote }>Quote</button>
268+ }
269+ if c.Edit != nil {
270+ <details><summary>Edit</summary><form method="post" action={ templ.SafeURL(c.Edit.Action) }><input type="hidden" name="from" value={ itoa(c.Edit.From) }/><textarea name="text" maxlength="8000">{ c.Edit.Text }</textarea><button class="btn" type="submit">Save</button></form></details>
271+ }
272+ if c.Delete != nil {
273+ <details><summary>Delete</summary><form method="post" action={ templ.SafeURL(c.Delete.Action) }><input type="hidden" name="from" value={ itoa(c.Delete.From) }/><p>Delete this note?</p><button class="btn" type="submit">Confirm delete</button></form></details>
274+ }
275+ for _, reply := range c.Replies {
276+ @commentView(reply, suffix)
277+ }
278+ if c.Reply != nil {
279+ <details data-reply>
280+ <summary>Reply</summary>@commentForm(*c.Reply, suffix)
281+ </details>
282+ }
252 if c.Resolve != nil {283 if c.Resolve != nil {
253 <form class="c-act" method="post" action={ templ.SafeURL(c.Resolve.Action) }>284 <form class="c-act" method="post" action={ templ.SafeURL(c.Resolve.Action) }>
254 <input type="hidden" name="resolved" value={ boolText(c.Resolve.Value) }/>285 <input type="hidden" name="resolved" value={ boolText(c.Resolve.Value) }/>
@@ -277,8 +308,7 @@ templ commentForm(f CommentForm, suffix string) {
277 name="text"308 name="text"
278 rows="3"309 rows="3"
279 maxlength="8000"310 maxlength="8000"
280- required311+ placeholder="Markdown, 8000 characters at most"
281- placeholder="plain text, 8000 characters at most"
282 if f.Focus {312 if f.Focus {
283 autofocus313 autofocus
284 }314 }
@@ -287,6 +317,13 @@ templ commentForm(f CommentForm, suffix string) {
287 <input type="hidden" name="path" value={ f.Path }/>317 <input type="hidden" name="path" value={ f.Path }/>
288 <input type="hidden" name="line" value={ f.Line }/>318 <input type="hidden" name="line" value={ f.Line }/>
289 <input type="hidden" name="side" value={ f.Side }/>319 <input type="hidden" name="side" value={ f.Side }/>
320+ <label>from line <input type="number" name="start_line" min="1" value={ f.StartLine }/></label>
321+ }
322+ if f.ReplyTo != "" {
323+ <input type="hidden" name="reply_to" value={ f.ReplyTo }/>
324+ }
325+ if f.Path == "" && f.ReplyTo == "" {
326+ <fieldset><legend>Verdict</legend><label><input type="radio" name="verdict" value="" checked/>Note</label><label><input type="radio" name="verdict" value="approve"/>Approve</label><label><input type="radio" name="verdict" value="changes_requested"/>Request changes</label></fieldset>
290 }327 }
291 if f.From > 0 {328 if f.From > 0 {
292 <input type="hidden" name="from" value={ itoa(f.From) }/>329 <input type="hidden" name="from" value={ itoa(f.From) }/>
@@ -310,6 +347,21 @@ templ mrRail(p MRPage) {
310 </h2>347 </h2>
311 @FileList(p.Files)348 @FileList(p.Files)
312 </section>349 </section>
350+ if len(p.Verdicts) > 0 {
351+ <section>
352+ <h2>Verdicts</h2>
353+ <ul>
354+ for _, v := range p.Verdicts {
355+ <li>
356+ <a href={ templ.SafeURL(v.Href) }>{ v.Author }: { v.Label }</a>
357+ if v.Stale {
358+ <span class="mute">(stale)</span>
359+ }
360+ </li>
361+ }
362+ </ul>
363+ </section>
364+ }
313 @MRChecks(p.Checks)365 @MRChecks(p.Checks)
314 <section aria-labelledby="r-commits">366 <section aria-labelledby="r-commits">
315 <h2 id="r-commits">Commits <span class="count">{ itoa(len(p.Commits)) }</span></h2>367 <h2 id="r-commits">Commits <span class="count">{ itoa(len(p.Commits)) }</span></h2>
M internal/adapters/web/mr_files.go +5 -3
@@ -131,8 +131,10 @@ func countNotes(files []DiffFile, comments []domain.Comment) {
131131 if c.Path == "" || !ok {
132132 continue
133133 }
134- files[i].Notes++
135- if !c.Resolved {
134+ if c.ReplyTo == 0 && !c.Deleted {
135+ files[i].Notes++
136+ }
137+ if c.ReplyTo == 0 && !c.Deleted && !c.Resolved {
136138 files[i].OpenNotes++
137139 }
138140 ids[i] = append(ids[i], "c"+strconv.FormatInt(c.ID, 10))
@@ -196,7 +198,7 @@ func (s *Server) mrFiles(ctx context.Context, r *http.Request) (templ.Component,
196198 m := res.MergeRequest
197199 signed := SignedIn(ctx)
198200 canWrite := signed && m.State == domain.MRStateOpen && !since
199- p := MRPage{Files: s.hl.files(ctx, res.Diff.Files), LooseInFiles: true}
201+ p := MRPage{Files: s.hl.files(ctx, res.Diff.Files), LooseInFiles: true, NoteAuthor: domain.AuthorName(principal(ctx))}
200202 contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", head.String()))
201203 s.placeComments(&p, d.name(), n, res.Comments, m.HeadID.String(), !since, canWrite, nil)
202204 window := p.Files[from:min(from+mrWindow, len(p.Files))]
@@ -131,8 +131,10 @@ func countNotes(files []DiffFile, comments []domain.Comment) {
131 if c.Path == "" || !ok {131 if c.Path == "" || !ok {
132 continue132 continue
133 }133 }
134- files[i].Notes++134+ if c.ReplyTo == 0 && !c.Deleted {
135- if !c.Resolved {135+ files[i].Notes++
136+ }
137+ if c.ReplyTo == 0 && !c.Deleted && !c.Resolved {
136 files[i].OpenNotes++138 files[i].OpenNotes++
137 }139 }
138 ids[i] = append(ids[i], "c"+strconv.FormatInt(c.ID, 10))140 ids[i] = append(ids[i], "c"+strconv.FormatInt(c.ID, 10))
@@ -196,7 +198,7 @@ func (s *Server) mrFiles(ctx context.Context, r *http.Request) (templ.Component,
196 m := res.MergeRequest198 m := res.MergeRequest
197 signed := SignedIn(ctx)199 signed := SignedIn(ctx)
198 canWrite := signed && m.State == domain.MRStateOpen && !since200 canWrite := signed && m.State == domain.MRStateOpen && !since
199- p := MRPage{Files: s.hl.files(ctx, res.Diff.Files), LooseInFiles: true}201+ p := MRPage{Files: s.hl.files(ctx, res.Diff.Files), LooseInFiles: true, NoteAuthor: domain.AuthorName(principal(ctx))}
200 contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", head.String()))202 contextGaps(p.Files, res.Diff.Files, repoHref(d.name(), "context", head.String()))
201 s.placeComments(&p, d.name(), n, res.Comments, m.HeadID.String(), !since, canWrite, nil)203 s.placeComments(&p, d.name(), n, res.Comments, m.HeadID.String(), !since, canWrite, nil)
202 window := p.Files[from:min(from+mrWindow, len(p.Files))]204 window := p.Files[from:min(from+mrWindow, len(p.Files))]
A internal/adapters/web/mr_notes_test.go +112 -0
@@ -0,0 +1,112 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package web
4+
5+import (
6+ "bnema.dev/ermite/internal/ports/mocks"
7+ "bytes"
8+ "context"
9+ "github.com/stretchr/testify/mock"
10+ "net/http"
11+ "net/http/httptest"
12+ "net/url"
13+ "strings"
14+ "testing"
15+ "time"
16+
17+ "bnema.dev/ermite/internal/domain"
18+ "bnema.dev/ermite/internal/ports"
19+ "github.com/stretchr/testify/assert"
20+ "github.com/stretchr/testify/require"
21+)
22+
23+func TestParseOnRange(t *testing.T) {
24+ t.Parallel()
25+ for _, raw := range []string{"new:0-2:a", "new:2-2:a", "new:3-2:a", "new:1-x:a", "new:1-2:", "other:1-2:a"} {
26+ assert.Nil(t, parseOn(raw), raw)
27+ }
28+ assert.Equal(t, &lineRef{side: "new", start: 10, line: 24, path: "dir:a.go"}, parseOn("new:10-24:dir:a.go"))
29+ assert.Equal(t, &lineRef{side: "old", line: 10, path: "a"}, parseOn("old:10:a"))
30+}
31+
32+func TestNoteThreads(t *testing.T) {
33+ t.Parallel()
34+ text, err := domain.ParsePlainText("**hello** ^2")
35+ require.NoError(t, err)
36+ s := &Server{cfg: Config{Now: time.Now}, md: newMarkdown()}
37+ comments := []domain.Comment{
38+ {ID: 1, Author: "alice", Text: text},
39+ {ID: 2, Author: "bob", ReplyTo: 1, Text: text},
40+ {ID: 3, Author: "alice", Deleted: true},
41+ {ID: 4, Author: "alice", Deleted: true},
42+ {ID: 5, Author: "bob", ReplyTo: 4, Text: text},
43+ {ID: 6, Author: "alice", Text: text, Verdict: domain.VerdictApprove, Resolved: true},
44+ }
45+ p := MRPage{NoteAuthor: "alice"}
46+ s.placeComments(&p, "demo", 1, comments, "", true, true, nil)
47+ require.Len(t, p.General, 3)
48+ assert.Equal(t, 4, p.NComments)
49+ assert.Equal(t, 1, p.NUnresolved)
50+ require.Len(t, p.General[0].Replies, 1)
51+ assert.NotNil(t, p.General[0].Edit)
52+ assert.Nil(t, p.General[0].Replies[0].Edit)
53+ assert.Nil(t, p.General[0].Replies[0].Resolve)
54+ assert.Contains(t, string(p.General[0].Body), "<strong>hello</strong>")
55+ assert.Contains(t, string(p.General[0].Body), `href="#c2"`)
56+ assert.True(t, p.General[1].Deleted)
57+ var b bytes.Buffer
58+ require.NoError(t, commentView(p.General[0], "").Render(context.Background(), &b))
59+ assert.Contains(t, b.String(), `name="reply_to" value="1"`)
60+ assert.Contains(t, b.String(), `data-js data-quote=`)
61+ assert.Contains(t, b.String(), `/comments/1/edit`)
62+ assert.NotContains(t, b.String(), `/comments/2/edit`)
63+}
64+
65+func TestNoteLiveEvents(t *testing.T) {
66+ t.Parallel()
67+ l := liveStream{view: "mr", n: "1"}
68+ events := []domain.Event{domain.CommentEdited{Number: 1}, domain.CommentDeleted{Number: 1}, domain.CommentResolved{Number: 1}}
69+ for _, ev := range events {
70+ we := ports.WatchEvent{Event: ev}
71+ assert.True(t, liveEvent(we))
72+ fs, err := l.repoPage(context.Background(), we)
73+ require.NoError(t, err)
74+ require.Len(t, fs, 1)
75+ assert.Contains(t, fs[0].html, "reload")
76+ }
77+}
78+
79+func TestNotePosts(t *testing.T) {
80+ t.Parallel()
81+ svc := mocks.NewMergeRequestService(t)
82+ repo := repoName(t, "demo")
83+ s := &Server{cfg: Config{MergeRequests: svc}}
84+ ctx := domain.ContextWithPrincipal(context.Background(), domain.Owner())
85+ request := func(values url.Values) *http.Request {
86+ r := httptest.NewRequest("POST", "/demo/merge-requests/1/comments", strings.NewReader(values.Encode())).WithContext(ctx)
87+ r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
88+ r.SetPathValue("repo", "demo")
89+ r.SetPathValue("n", "1")
90+ r.SetPathValue("id", "2")
91+ require.NoError(t, r.ParseForm())
92+ return r
93+ }
94+ svc.EXPECT().AddComment(mock.Anything, mock.Anything, repo, int64(1), mock.MatchedBy(func(p ports.AddCommentParams) bool { return p.ReplyTo == 2 && p.Text == "reply" })).Return(domain.Comment{ID: 7}, nil).Once()
95+ to, _, err := s.mrComment(ctx, request(url.Values{"reply_to": {"2"}, "text": {"reply"}}))
96+ require.NoError(t, err)
97+ assert.Equal(t, "/demo/merge-requests/1#c7", to)
98+ svc.EXPECT().AddComment(mock.Anything, mock.Anything, repo, int64(1), mock.MatchedBy(func(p ports.AddCommentParams) bool {
99+ return p.StartLine == 10 && p.Line == 24 && p.Path == "a.go" && p.Verdict == domain.VerdictApprove
100+ })).Return(domain.Comment{ID: 8}, nil).Once()
101+ to, _, err = s.mrComment(ctx, request(url.Values{"path": {"a.go"}, "line": {"24"}, "start_line": {"10"}, "side": {"new"}, "verdict": {"approve"}}))
102+ require.NoError(t, err)
103+ assert.Equal(t, "/demo/merge-requests/1#c8", to)
104+ svc.EXPECT().EditComment(mock.Anything, mock.Anything, repo, int64(1), int64(2), "new\ntext").Return(domain.Comment{ID: 2}, nil).Once()
105+ to, _, err = s.mrEditComment(ctx, request(url.Values{"text": {"new\r\ntext"}}))
106+ require.NoError(t, err)
107+ assert.Equal(t, "/demo/merge-requests/1#c2", to)
108+ svc.EXPECT().DeleteComment(mock.Anything, mock.Anything, repo, int64(1), int64(2)).Return(domain.Comment{ID: 2}, nil).Once()
109+ to, _, err = s.mrDeleteComment(ctx, request(nil))
110+ require.NoError(t, err)
111+ assert.Equal(t, "/demo/merge-requests/1#c2", to)
112+}
@@ -0,0 +1,112 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package web
4+
5+import (
6+ "bnema.dev/ermite/internal/ports/mocks"
7+ "bytes"
8+ "context"
9+ "github.com/stretchr/testify/mock"
10+ "net/http"
11+ "net/http/httptest"
12+ "net/url"
13+ "strings"
14+ "testing"
15+ "time"
16+
17+ "bnema.dev/ermite/internal/domain"
18+ "bnema.dev/ermite/internal/ports"
19+ "github.com/stretchr/testify/assert"
20+ "github.com/stretchr/testify/require"
21+)
22+
23+func TestParseOnRange(t *testing.T) {
24+ t.Parallel()
25+ for _, raw := range []string{"new:0-2:a", "new:2-2:a", "new:3-2:a", "new:1-x:a", "new:1-2:", "other:1-2:a"} {
26+ assert.Nil(t, parseOn(raw), raw)
27+ }
28+ assert.Equal(t, &lineRef{side: "new", start: 10, line: 24, path: "dir:a.go"}, parseOn("new:10-24:dir:a.go"))
29+ assert.Equal(t, &lineRef{side: "old", line: 10, path: "a"}, parseOn("old:10:a"))
30+}
31+
32+func TestNoteThreads(t *testing.T) {
33+ t.Parallel()
34+ text, err := domain.ParsePlainText("**hello** ^2")
35+ require.NoError(t, err)
36+ s := &Server{cfg: Config{Now: time.Now}, md: newMarkdown()}
37+ comments := []domain.Comment{
38+ {ID: 1, Author: "alice", Text: text},
39+ {ID: 2, Author: "bob", ReplyTo: 1, Text: text},
40+ {ID: 3, Author: "alice", Deleted: true},
41+ {ID: 4, Author: "alice", Deleted: true},
42+ {ID: 5, Author: "bob", ReplyTo: 4, Text: text},
43+ {ID: 6, Author: "alice", Text: text, Verdict: domain.VerdictApprove, Resolved: true},
44+ }
45+ p := MRPage{NoteAuthor: "alice"}
46+ s.placeComments(&p, "demo", 1, comments, "", true, true, nil)
47+ require.Len(t, p.General, 3)
48+ assert.Equal(t, 4, p.NComments)
49+ assert.Equal(t, 1, p.NUnresolved)
50+ require.Len(t, p.General[0].Replies, 1)
51+ assert.NotNil(t, p.General[0].Edit)
52+ assert.Nil(t, p.General[0].Replies[0].Edit)
53+ assert.Nil(t, p.General[0].Replies[0].Resolve)
54+ assert.Contains(t, string(p.General[0].Body), "<strong>hello</strong>")
55+ assert.Contains(t, string(p.General[0].Body), `href="#c2"`)
56+ assert.True(t, p.General[1].Deleted)
57+ var b bytes.Buffer
58+ require.NoError(t, commentView(p.General[0], "").Render(context.Background(), &b))
59+ assert.Contains(t, b.String(), `name="reply_to" value="1"`)
60+ assert.Contains(t, b.String(), `data-js data-quote=`)
61+ assert.Contains(t, b.String(), `/comments/1/edit`)
62+ assert.NotContains(t, b.String(), `/comments/2/edit`)
63+}
64+
65+func TestNoteLiveEvents(t *testing.T) {
66+ t.Parallel()
67+ l := liveStream{view: "mr", n: "1"}
68+ events := []domain.Event{domain.CommentEdited{Number: 1}, domain.CommentDeleted{Number: 1}, domain.CommentResolved{Number: 1}}
69+ for _, ev := range events {
70+ we := ports.WatchEvent{Event: ev}
71+ assert.True(t, liveEvent(we))
72+ fs, err := l.repoPage(context.Background(), we)
73+ require.NoError(t, err)
74+ require.Len(t, fs, 1)
75+ assert.Contains(t, fs[0].html, "reload")
76+ }
77+}
78+
79+func TestNotePosts(t *testing.T) {
80+ t.Parallel()
81+ svc := mocks.NewMergeRequestService(t)
82+ repo := repoName(t, "demo")
83+ s := &Server{cfg: Config{MergeRequests: svc}}
84+ ctx := domain.ContextWithPrincipal(context.Background(), domain.Owner())
85+ request := func(values url.Values) *http.Request {
86+ r := httptest.NewRequest("POST", "/demo/merge-requests/1/comments", strings.NewReader(values.Encode())).WithContext(ctx)
87+ r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
88+ r.SetPathValue("repo", "demo")
89+ r.SetPathValue("n", "1")
90+ r.SetPathValue("id", "2")
91+ require.NoError(t, r.ParseForm())
92+ return r
93+ }
94+ svc.EXPECT().AddComment(mock.Anything, mock.Anything, repo, int64(1), mock.MatchedBy(func(p ports.AddCommentParams) bool { return p.ReplyTo == 2 && p.Text == "reply" })).Return(domain.Comment{ID: 7}, nil).Once()
95+ to, _, err := s.mrComment(ctx, request(url.Values{"reply_to": {"2"}, "text": {"reply"}}))
96+ require.NoError(t, err)
97+ assert.Equal(t, "/demo/merge-requests/1#c7", to)
98+ svc.EXPECT().AddComment(mock.Anything, mock.Anything, repo, int64(1), mock.MatchedBy(func(p ports.AddCommentParams) bool {
99+ return p.StartLine == 10 && p.Line == 24 && p.Path == "a.go" && p.Verdict == domain.VerdictApprove
100+ })).Return(domain.Comment{ID: 8}, nil).Once()
101+ to, _, err = s.mrComment(ctx, request(url.Values{"path": {"a.go"}, "line": {"24"}, "start_line": {"10"}, "side": {"new"}, "verdict": {"approve"}}))
102+ require.NoError(t, err)
103+ assert.Equal(t, "/demo/merge-requests/1#c8", to)
104+ svc.EXPECT().EditComment(mock.Anything, mock.Anything, repo, int64(1), int64(2), "new\ntext").Return(domain.Comment{ID: 2}, nil).Once()
105+ to, _, err = s.mrEditComment(ctx, request(url.Values{"text": {"new\r\ntext"}}))
106+ require.NoError(t, err)
107+ assert.Equal(t, "/demo/merge-requests/1#c2", to)
108+ svc.EXPECT().DeleteComment(mock.Anything, mock.Anything, repo, int64(1), int64(2)).Return(domain.Comment{ID: 2}, nil).Once()
109+ to, _, err = s.mrDeleteComment(ctx, request(nil))
110+ require.NoError(t, err)
111+ assert.Equal(t, "/demo/merge-requests/1#c2", to)
112+}
M internal/adapters/web/mr_post.go +47 -0
@@ -177,6 +177,23 @@ func (s *Server) mrComment(ctx context.Context, r *http.Request) (string, string
177177 return "", back, formError("side must be new or old")
178178 }
179179 }
180+ if raw := r.PostFormValue("reply_to"); raw != "" {
181+ params.ReplyTo = commentID(raw)
182+ if params.ReplyTo == 0 {
183+ return "", back, formError("reply_to is not a note id")
184+ }
185+ }
186+ if raw := r.PostFormValue("start_line"); raw != "" {
187+ v, e := strconv.ParseInt(raw, 10, 32)
188+ if e != nil {
189+ return "", back, formError("start_line is not a number")
190+ }
191+ params.StartLine = int(v)
192+ }
193+ params.Verdict, err = domain.ParseVerdict(r.PostFormValue("verdict"))
194+ if err != nil {
195+ return "", back, err
196+ }
180197 name, err := domain.ParseRepoName(repo)
181198 if err != nil {
182199 return "", back, err
@@ -356,3 +373,33 @@ func formBool(s string) (bool, error) {
356373 func normalizeNewlines(s string) string {
357374 return strings.ReplaceAll(s, "\r\n", "\n")
358375 }
376+
377+// mrEditComment passes source text to the author-checked use case.
378+func (s *Server) mrEditComment(ctx context.Context, r *http.Request) (string, string, error) {
379+ n, err := number(r)
380+ if err != nil {
381+ return "", "", err
382+ }
383+ back := withFrom(mrHref(r.PathValue("repo"), n), formFrom(r))
384+ name, err := domain.ParseRepoName(r.PathValue("repo"))
385+ if err != nil {
386+ return "", back, err
387+ }
388+ c, err := s.cfg.MergeRequests.EditComment(ctx, principal(ctx), name, n, commentID(r.PathValue("id")), normalizeNewlines(r.PostFormValue("text")))
389+ return back + "#c" + strconv.FormatInt(c.ID, 10), back, err
390+}
391+
392+// mrDeleteComment passes the note id to the author-checked use case.
393+func (s *Server) mrDeleteComment(ctx context.Context, r *http.Request) (string, string, error) {
394+ n, err := number(r)
395+ if err != nil {
396+ return "", "", err
397+ }
398+ back := withFrom(mrHref(r.PathValue("repo"), n), formFrom(r))
399+ name, err := domain.ParseRepoName(r.PathValue("repo"))
400+ if err != nil {
401+ return "", back, err
402+ }
403+ c, err := s.cfg.MergeRequests.DeleteComment(ctx, principal(ctx), name, n, commentID(r.PathValue("id")))
404+ return back + "#c" + strconv.FormatInt(c.ID, 10), back, err
405+}
@@ -177,6 +177,23 @@ func (s *Server) mrComment(ctx context.Context, r *http.Request) (string, string
177 return "", back, formError("side must be new or old")177 return "", back, formError("side must be new or old")
178 }178 }
179 }179 }
180+ if raw := r.PostFormValue("reply_to"); raw != "" {
181+ params.ReplyTo = commentID(raw)
182+ if params.ReplyTo == 0 {
183+ return "", back, formError("reply_to is not a note id")
184+ }
185+ }
186+ if raw := r.PostFormValue("start_line"); raw != "" {
187+ v, e := strconv.ParseInt(raw, 10, 32)
188+ if e != nil {
189+ return "", back, formError("start_line is not a number")
190+ }
191+ params.StartLine = int(v)
192+ }
193+ params.Verdict, err = domain.ParseVerdict(r.PostFormValue("verdict"))
194+ if err != nil {
195+ return "", back, err
196+ }
180 name, err := domain.ParseRepoName(repo)197 name, err := domain.ParseRepoName(repo)
181 if err != nil {198 if err != nil {
182 return "", back, err199 return "", back, err
@@ -356,3 +373,33 @@ func formBool(s string) (bool, error) {
356 func normalizeNewlines(s string) string {373 func normalizeNewlines(s string) string {
357 return strings.ReplaceAll(s, "\r\n", "\n")374 return strings.ReplaceAll(s, "\r\n", "\n")
358 }375 }
376+
377+// mrEditComment passes source text to the author-checked use case.
378+func (s *Server) mrEditComment(ctx context.Context, r *http.Request) (string, string, error) {
379+ n, err := number(r)
380+ if err != nil {
381+ return "", "", err
382+ }
383+ back := withFrom(mrHref(r.PathValue("repo"), n), formFrom(r))
384+ name, err := domain.ParseRepoName(r.PathValue("repo"))
385+ if err != nil {
386+ return "", back, err
387+ }
388+ c, err := s.cfg.MergeRequests.EditComment(ctx, principal(ctx), name, n, commentID(r.PathValue("id")), normalizeNewlines(r.PostFormValue("text")))
389+ return back + "#c" + strconv.FormatInt(c.ID, 10), back, err
390+}
391+
392+// mrDeleteComment passes the note id to the author-checked use case.
393+func (s *Server) mrDeleteComment(ctx context.Context, r *http.Request) (string, string, error) {
394+ n, err := number(r)
395+ if err != nil {
396+ return "", "", err
397+ }
398+ back := withFrom(mrHref(r.PathValue("repo"), n), formFrom(r))
399+ name, err := domain.ParseRepoName(r.PathValue("repo"))
400+ if err != nil {
401+ return "", back, err
402+ }
403+ c, err := s.cfg.MergeRequests.DeleteComment(ctx, principal(ctx), name, n, commentID(r.PathValue("id")))
404+ return back + "#c" + strconv.FormatInt(c.ID, 10), back, err
405+}
M internal/adapters/web/mr_templ.go +763 -480
@@ -1215,8 +1215,7 @@ func lineNotes(notes []CommentView, form *CommentForm, suffix string) templ.Comp
12151215 }
12161216
12171217 // commentView is a note: a ruled block, under its line in the diff or in the
1218-// list of comments. The text is plain: escaped, its line breaks kept, its
1219-// http(s) addresses linked.
1218+// list of comments. Body comes only from the safe Markdown renderer.
12201219 func commentView(c CommentView, suffix string) templ.Component {
12211220 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
12221221 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -1245,7 +1244,7 @@ func commentView(c CommentView, suffix string) templ.Component {
12451244 var templ_7745c5c3_Var59 string
12461245 templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue("c" + c.ID + suffix)
12471246 if templ_7745c5c3_Err != nil {
1248- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 235, Col: 42}
1247+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 234, Col: 42}
12491248 }
12501249 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)
12511250 if templ_7745c5c3_Err != nil {
@@ -1258,26 +1257,36 @@ func commentView(c CommentView, suffix string) templ.Component {
12581257 var templ_7745c5c3_Var60 string
12591258 templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolText(c.Resolved))
12601259 if templ_7745c5c3_Err != nil {
1261- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 235, Col: 81}
1260+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 234, Col: 81}
12621261 }
12631262 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60)
12641263 if templ_7745c5c3_Err != nil {
12651264 return templ_7745c5c3_Err
12661265 }
1267- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\"><p class=\"c-head\"><span class=\"c-who\">")
1266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\"")
1267+ if templ_7745c5c3_Err != nil {
1268+ return templ_7745c5c3_Err
1269+ }
1270+ if c.Reply != nil {
1271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, " data-thread")
1272+ if templ_7745c5c3_Err != nil {
1273+ return templ_7745c5c3_Err
1274+ }
1275+ }
1276+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "><p class=\"c-head\"><span class=\"c-who\">")
12681277 if templ_7745c5c3_Err != nil {
12691278 return templ_7745c5c3_Err
12701279 }
12711280 var templ_7745c5c3_Var61 string
12721281 templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
12731282 if templ_7745c5c3_Err != nil {
1274- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 237, Col: 33}
1283+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 236, Col: 33}
12751284 }
12761285 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
12771286 if templ_7745c5c3_Err != nil {
12781287 return templ_7745c5c3_Err
12791288 }
1280- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</span> ")
1289+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</span> ")
12811290 if templ_7745c5c3_Err != nil {
12821291 return templ_7745c5c3_Err
12831292 }
@@ -1285,52 +1294,52 @@ func commentView(c CommentView, suffix string) templ.Component {
12851294 var templ_7745c5c3_Var62 string
12861295 templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
12871296 if templ_7745c5c3_Err != nil {
1288- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 9}
1297+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 9}
12891298 }
12901299 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
12911300 if templ_7745c5c3_Err != nil {
12921301 return templ_7745c5c3_Err
12931302 }
1294- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<time class=\"mute\" datetime=\"")
1303+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "<time class=\"mute\" datetime=\"")
12951304 if templ_7745c5c3_Err != nil {
12961305 return templ_7745c5c3_Err
12971306 }
12981307 var templ_7745c5c3_Var63 string
12991308 templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
13001309 if templ_7745c5c3_Err != nil {
1301- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 49}
1310+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 49}
13021311 }
13031312 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63)
13041313 if templ_7745c5c3_Err != nil {
13051314 return templ_7745c5c3_Err
13061315 }
1307- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" title=\"")
1316+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "\" title=\"")
13081317 if templ_7745c5c3_Err != nil {
13091318 return templ_7745c5c3_Err
13101319 }
13111320 var templ_7745c5c3_Var64 string
13121321 templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Abs)
13131322 if templ_7745c5c3_Err != nil {
1314- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 65}
1323+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 65}
13151324 }
13161325 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
13171326 if templ_7745c5c3_Err != nil {
13181327 return templ_7745c5c3_Err
13191328 }
1320- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\">")
1329+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\">")
13211330 if templ_7745c5c3_Err != nil {
13221331 return templ_7745c5c3_Err
13231332 }
13241333 var templ_7745c5c3_Var65 string
13251334 templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
13261335 if templ_7745c5c3_Err != nil {
1327- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 76}
1336+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 76}
13281337 }
13291338 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
13301339 if templ_7745c5c3_Err != nil {
13311340 return templ_7745c5c3_Err
13321341 }
1333- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "</time> ")
1342+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</time> ")
13341343 if templ_7745c5c3_Err != nil {
13351344 return templ_7745c5c3_Err
13361345 }
@@ -1339,13 +1348,13 @@ func commentView(c CommentView, suffix string) templ.Component {
13391348 var templ_7745c5c3_Var66 string
13401349 templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
13411350 if templ_7745c5c3_Err != nil {
1342- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 242, Col: 9}
1351+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 241, Col: 9}
13431352 }
13441353 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
13451354 if templ_7745c5c3_Err != nil {
13461355 return templ_7745c5c3_Err
13471356 }
1348- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "<span class=\"c-state\">resolved</span> ")
1357+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<span class=\"c-state\">resolved</span> ")
13491358 if templ_7745c5c3_Err != nil {
13501359 return templ_7745c5c3_Err
13511360 }
@@ -1354,13 +1363,13 @@ func commentView(c CommentView, suffix string) templ.Component {
13541363 var templ_7745c5c3_Var67 string
13551364 templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
13561365 if templ_7745c5c3_Err != nil {
1357- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 245, Col: 9}
1366+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 244, Col: 9}
13581367 }
13591368 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
13601369 if templ_7745c5c3_Err != nil {
13611370 return templ_7745c5c3_Err
13621371 }
1363- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "<span class=\"c-state\" title=\"written on an earlier head; the line may have moved\">outdated</span> ")
1372+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "<span class=\"c-state\" title=\"written on an earlier head; the line may have moved\">outdated</span> ")
13641373 if templ_7745c5c3_Err != nil {
13651374 return templ_7745c5c3_Err
13661375 }
@@ -1369,111 +1378,282 @@ func commentView(c CommentView, suffix string) templ.Component {
13691378 var templ_7745c5c3_Var68 string
13701379 templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
13711380 if templ_7745c5c3_Err != nil {
1372- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 248, Col: 9}
1381+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 247, Col: 9}
13731382 }
13741383 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
13751384 if templ_7745c5c3_Err != nil {
13761385 return templ_7745c5c3_Err
13771386 }
1378- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<span class=\"c-where mute\">")
1387+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "<span class=\"c-where mute\">")
13791388 if templ_7745c5c3_Err != nil {
13801389 return templ_7745c5c3_Err
13811390 }
13821391 var templ_7745c5c3_Var69 string
13831392 templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(c.Where)
13841393 if templ_7745c5c3_Err != nil {
1385- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 248, Col: 47}
1394+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 247, Col: 47}
13861395 }
13871396 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
13881397 if templ_7745c5c3_Err != nil {
13891398 return templ_7745c5c3_Err
13901399 }
1391- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</span>")
1400+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</span>")
13921401 if templ_7745c5c3_Err != nil {
13931402 return templ_7745c5c3_Err
13941403 }
13951404 }
1396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "</p><p class=\"c-text\">")
1405+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "</p>")
13971406 if templ_7745c5c3_Err != nil {
13981407 return templ_7745c5c3_Err
13991408 }
1400- templ_7745c5c3_Err = userText(c.Parts).Render(ctx, templ_7745c5c3_Buffer)
1401- if templ_7745c5c3_Err != nil {
1402- return templ_7745c5c3_Err
1409+ if c.Deleted {
1410+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "<p class=\"mute\">deleted note</p>")
1411+ if templ_7745c5c3_Err != nil {
1412+ return templ_7745c5c3_Err
1413+ }
1414+ } else {
1415+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "<div class=\"c-body prose\">")
1416+ if templ_7745c5c3_Err != nil {
1417+ return templ_7745c5c3_Err
1418+ }
1419+ templ_7745c5c3_Err = templ.Raw(string(c.Body)).Render(ctx, templ_7745c5c3_Buffer)
1420+ if templ_7745c5c3_Err != nil {
1421+ return templ_7745c5c3_Err
1422+ }
1423+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "</div>")
1424+ if templ_7745c5c3_Err != nil {
1425+ return templ_7745c5c3_Err
1426+ }
14031427 }
1404- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</p>")
1405- if templ_7745c5c3_Err != nil {
1406- return templ_7745c5c3_Err
1428+ if c.Edited {
1429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "<span class=\"mute\">edited</span> ")
1430+ if templ_7745c5c3_Err != nil {
1431+ return templ_7745c5c3_Err
1432+ }
14071433 }
1408- if c.Resolve != nil {
1409- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "<form class=\"c-act\" method=\"post\" action=\"")
1434+ if c.Verdict != "" {
1435+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<span class=\"c-state\">")
14101436 if templ_7745c5c3_Err != nil {
14111437 return templ_7745c5c3_Err
14121438 }
1413- var templ_7745c5c3_Var70 templ.SafeURL
1414- templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Resolve.Action))
1439+ var templ_7745c5c3_Var70 string
1440+ templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(c.Verdict)
14151441 if templ_7745c5c3_Err != nil {
1416- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 253, Col: 77}
1442+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 261, Col: 36}
14171443 }
14181444 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
14191445 if templ_7745c5c3_Err != nil {
14201446 return templ_7745c5c3_Err
14211447 }
1422- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "\"><input type=\"hidden\" name=\"resolved\" value=\"")
1448+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "</span> ")
1449+ if templ_7745c5c3_Err != nil {
1450+ return templ_7745c5c3_Err
1451+ }
1452+ }
1453+ if c.Range != "" {
1454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<span class=\"mute\">")
14231455 if templ_7745c5c3_Err != nil {
14241456 return templ_7745c5c3_Err
14251457 }
14261458 var templ_7745c5c3_Var71 string
1427- templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolText(c.Resolve.Value))
1459+ templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(c.Range)
1460+ if templ_7745c5c3_Err != nil {
1461+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 264, Col: 31}
1462+ }
1463+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71))
1464+ if templ_7745c5c3_Err != nil {
1465+ return templ_7745c5c3_Err
1466+ }
1467+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "</span> ")
1468+ if templ_7745c5c3_Err != nil {
1469+ return templ_7745c5c3_Err
1470+ }
1471+ }
1472+ if c.Quote != "" {
1473+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "<button type=\"button\" class=\"btn\" data-js data-quote=\"")
1474+ if templ_7745c5c3_Err != nil {
1475+ return templ_7745c5c3_Err
1476+ }
1477+ var templ_7745c5c3_Var72 string
1478+ templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Quote)
1479+ if templ_7745c5c3_Err != nil {
1480+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 267, Col: 65}
1481+ }
1482+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
14281483 if templ_7745c5c3_Err != nil {
1429- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 254, Col: 74}
1484+ return templ_7745c5c3_Err
1485+ }
1486+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "\">Quote</button> ")
1487+ if templ_7745c5c3_Err != nil {
1488+ return templ_7745c5c3_Err
14301489 }
1431- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)
1490+ }
1491+ if c.Edit != nil {
1492+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "<details><summary>Edit</summary><form method=\"post\" action=\"")
14321493 if templ_7745c5c3_Err != nil {
14331494 return templ_7745c5c3_Err
14341495 }
1435- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "\"> ")
1496+ var templ_7745c5c3_Var73 templ.SafeURL
1497+ templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Edit.Action))
1498+ if templ_7745c5c3_Err != nil {
1499+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 270, Col: 92}
1500+ }
1501+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))
1502+ if templ_7745c5c3_Err != nil {
1503+ return templ_7745c5c3_Err
1504+ }
1505+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "\"><input type=\"hidden\" name=\"from\" value=\"")
1506+ if templ_7745c5c3_Err != nil {
1507+ return templ_7745c5c3_Err
1508+ }
1509+ var templ_7745c5c3_Var74 string
1510+ templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Edit.From))
1511+ if templ_7745c5c3_Err != nil {
1512+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 270, Col: 153}
1513+ }
1514+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var74)
1515+ if templ_7745c5c3_Err != nil {
1516+ return templ_7745c5c3_Err
1517+ }
1518+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "\"><textarea name=\"text\" maxlength=\"8000\">")
1519+ if templ_7745c5c3_Err != nil {
1520+ return templ_7745c5c3_Err
1521+ }
1522+ var templ_7745c5c3_Var75 string
1523+ templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(c.Edit.Text)
1524+ if templ_7745c5c3_Err != nil {
1525+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 270, Col: 209}
1526+ }
1527+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75))
1528+ if templ_7745c5c3_Err != nil {
1529+ return templ_7745c5c3_Err
1530+ }
1531+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</textarea><button class=\"btn\" type=\"submit\">Save</button></form></details> ")
1532+ if templ_7745c5c3_Err != nil {
1533+ return templ_7745c5c3_Err
1534+ }
1535+ }
1536+ if c.Delete != nil {
1537+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<details><summary>Delete</summary><form method=\"post\" action=\"")
1538+ if templ_7745c5c3_Err != nil {
1539+ return templ_7745c5c3_Err
1540+ }
1541+ var templ_7745c5c3_Var76 templ.SafeURL
1542+ templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Delete.Action))
1543+ if templ_7745c5c3_Err != nil {
1544+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 273, Col: 96}
1545+ }
1546+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76))
1547+ if templ_7745c5c3_Err != nil {
1548+ return templ_7745c5c3_Err
1549+ }
1550+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "\"><input type=\"hidden\" name=\"from\" value=\"")
1551+ if templ_7745c5c3_Err != nil {
1552+ return templ_7745c5c3_Err
1553+ }
1554+ var templ_7745c5c3_Var77 string
1555+ templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Delete.From))
1556+ if templ_7745c5c3_Err != nil {
1557+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 273, Col: 159}
1558+ }
1559+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var77)
1560+ if templ_7745c5c3_Err != nil {
1561+ return templ_7745c5c3_Err
1562+ }
1563+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "\"><p>Delete this note?</p><button class=\"btn\" type=\"submit\">Confirm delete</button></form></details> ")
1564+ if templ_7745c5c3_Err != nil {
1565+ return templ_7745c5c3_Err
1566+ }
1567+ }
1568+ for _, reply := range c.Replies {
1569+ templ_7745c5c3_Err = commentView(reply, suffix).Render(ctx, templ_7745c5c3_Buffer)
1570+ if templ_7745c5c3_Err != nil {
1571+ return templ_7745c5c3_Err
1572+ }
1573+ }
1574+ if c.Reply != nil {
1575+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "<details data-reply><summary>Reply</summary>")
1576+ if templ_7745c5c3_Err != nil {
1577+ return templ_7745c5c3_Err
1578+ }
1579+ templ_7745c5c3_Err = commentForm(*c.Reply, suffix).Render(ctx, templ_7745c5c3_Buffer)
1580+ if templ_7745c5c3_Err != nil {
1581+ return templ_7745c5c3_Err
1582+ }
1583+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "</details> ")
1584+ if templ_7745c5c3_Err != nil {
1585+ return templ_7745c5c3_Err
1586+ }
1587+ }
1588+ if c.Resolve != nil {
1589+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<form class=\"c-act\" method=\"post\" action=\"")
1590+ if templ_7745c5c3_Err != nil {
1591+ return templ_7745c5c3_Err
1592+ }
1593+ var templ_7745c5c3_Var78 templ.SafeURL
1594+ templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Resolve.Action))
1595+ if templ_7745c5c3_Err != nil {
1596+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 284, Col: 77}
1597+ }
1598+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78))
1599+ if templ_7745c5c3_Err != nil {
1600+ return templ_7745c5c3_Err
1601+ }
1602+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "\"><input type=\"hidden\" name=\"resolved\" value=\"")
1603+ if templ_7745c5c3_Err != nil {
1604+ return templ_7745c5c3_Err
1605+ }
1606+ var templ_7745c5c3_Var79 string
1607+ templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolText(c.Resolve.Value))
1608+ if templ_7745c5c3_Err != nil {
1609+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 285, Col: 74}
1610+ }
1611+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var79)
1612+ if templ_7745c5c3_Err != nil {
1613+ return templ_7745c5c3_Err
1614+ }
1615+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "\"> ")
14361616 if templ_7745c5c3_Err != nil {
14371617 return templ_7745c5c3_Err
14381618 }
14391619 if c.Resolve.From > 0 {
1440- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "<input type=\"hidden\" name=\"from\" value=\"")
1620+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "<input type=\"hidden\" name=\"from\" value=\"")
14411621 if templ_7745c5c3_Err != nil {
14421622 return templ_7745c5c3_Err
14431623 }
1444- var templ_7745c5c3_Var72 string
1445- templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Resolve.From))
1624+ var templ_7745c5c3_Var80 string
1625+ templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Resolve.From))
14461626 if templ_7745c5c3_Err != nil {
1447- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 256, Col: 66}
1627+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 287, Col: 66}
14481628 }
1449- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
1629+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80)
14501630 if templ_7745c5c3_Err != nil {
14511631 return templ_7745c5c3_Err
14521632 }
1453- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "\"> ")
1633+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "\"> ")
14541634 if templ_7745c5c3_Err != nil {
14551635 return templ_7745c5c3_Err
14561636 }
14571637 }
1458- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<button type=\"submit\" class=\"btn\">")
1638+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<button type=\"submit\" class=\"btn\">")
14591639 if templ_7745c5c3_Err != nil {
14601640 return templ_7745c5c3_Err
14611641 }
1462- var templ_7745c5c3_Var73 string
1463- templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(c.Resolve.Label)
1642+ var templ_7745c5c3_Var81 string
1643+ templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinStringErrs(c.Resolve.Label)
14641644 if templ_7745c5c3_Err != nil {
1465- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 258, Col: 55}
1645+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 289, Col: 55}
14661646 }
1467- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))
1647+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81))
14681648 if templ_7745c5c3_Err != nil {
14691649 return templ_7745c5c3_Err
14701650 }
1471- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "</button></form>")
1651+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</button></form>")
14721652 if templ_7745c5c3_Err != nil {
14731653 return templ_7745c5c3_Err
14741654 }
14751655 }
1476- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "</div>")
1656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "</div>")
14771657 if templ_7745c5c3_Err != nil {
14781658 return templ_7745c5c3_Err
14791659 }
@@ -1505,165 +1685,203 @@ func commentForm(f CommentForm, suffix string) templ.Component {
15051685 }()
15061686 }
15071687 ctx = templ.InitializeContext(ctx)
1508- templ_7745c5c3_Var74 := templ.GetChildren(ctx)
1509- if templ_7745c5c3_Var74 == nil {
1510- templ_7745c5c3_Var74 = templ.NopComponent
1688+ templ_7745c5c3_Var82 := templ.GetChildren(ctx)
1689+ if templ_7745c5c3_Var82 == nil {
1690+ templ_7745c5c3_Var82 = templ.NopComponent
15111691 }
15121692 ctx = templ.ClearChildren(ctx)
1513- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<form class=\"cform\" method=\"post\" action=\"")
1693+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "<form class=\"cform\" method=\"post\" action=\"")
15141694 if templ_7745c5c3_Err != nil {
15151695 return templ_7745c5c3_Err
15161696 }
1517- var templ_7745c5c3_Var75 templ.SafeURL
1518- templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Action))
1697+ var templ_7745c5c3_Var83 templ.SafeURL
1698+ templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Action))
15191699 if templ_7745c5c3_Err != nil {
1520- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 273, Col: 67}
1700+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 304, Col: 67}
15211701 }
1522- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75))
1702+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))
15231703 if templ_7745c5c3_Err != nil {
15241704 return templ_7745c5c3_Err
15251705 }
1526- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "\"><label for=\"")
1706+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "\"><label for=\"")
15271707 if templ_7745c5c3_Err != nil {
15281708 return templ_7745c5c3_Err
15291709 }
1530- var templ_7745c5c3_Var76 string
1531- templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)
1710+ var templ_7745c5c3_Var84 string
1711+ templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)
15321712 if templ_7745c5c3_Err != nil {
1533- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 274, Col: 28}
1713+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 305, Col: 28}
15341714 }
1535- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var76)
1715+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var84)
15361716 if templ_7745c5c3_Err != nil {
15371717 return templ_7745c5c3_Err
15381718 }
1539- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "\">")
1719+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "\">")
15401720 if templ_7745c5c3_Err != nil {
15411721 return templ_7745c5c3_Err
15421722 }
1543- var templ_7745c5c3_Var77 string
1544- templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(f.Title)
1723+ var templ_7745c5c3_Var85 string
1724+ templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(f.Title)
15451725 if templ_7745c5c3_Err != nil {
1546- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 274, Col: 40}
1726+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 305, Col: 40}
15471727 }
1548- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77))
1728+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))
15491729 if templ_7745c5c3_Err != nil {
15501730 return templ_7745c5c3_Err
15511731 }
1552- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</label> <textarea id=\"")
1732+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "</label> <textarea id=\"")
15531733 if templ_7745c5c3_Err != nil {
15541734 return templ_7745c5c3_Err
15551735 }
1556- var templ_7745c5c3_Var78 string
1557- templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)
1736+ var templ_7745c5c3_Var86 string
1737+ templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)
15581738 if templ_7745c5c3_Err != nil {
1559- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 276, Col: 21}
1739+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 307, Col: 21}
15601740 }
1561- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var78)
1741+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var86)
15621742 if templ_7745c5c3_Err != nil {
15631743 return templ_7745c5c3_Err
15641744 }
1565- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "\" name=\"text\" rows=\"3\" maxlength=\"8000\" required placeholder=\"plain text, 8000 characters at most\"")
1745+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "\" name=\"text\" rows=\"3\" maxlength=\"8000\" placeholder=\"Markdown, 8000 characters at most\"")
15661746 if templ_7745c5c3_Err != nil {
15671747 return templ_7745c5c3_Err
15681748 }
15691749 if f.Focus {
1570- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, " autofocus")
1750+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, " autofocus")
15711751 if templ_7745c5c3_Err != nil {
15721752 return templ_7745c5c3_Err
15731753 }
15741754 }
1575- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "></textarea> ")
1755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "></textarea> ")
15761756 if templ_7745c5c3_Err != nil {
15771757 return templ_7745c5c3_Err
15781758 }
15791759 if f.Path != "" {
1580- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<input type=\"hidden\" name=\"path\" value=\"")
1760+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "<input type=\"hidden\" name=\"path\" value=\"")
15811761 if templ_7745c5c3_Err != nil {
15821762 return templ_7745c5c3_Err
15831763 }
1584- var templ_7745c5c3_Var79 string
1585- templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Path)
1764+ var templ_7745c5c3_Var87 string
1765+ templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Path)
15861766 if templ_7745c5c3_Err != nil {
1587- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 287, Col: 50}
1767+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 317, Col: 50}
15881768 }
1589- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var79)
1769+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var87)
15901770 if templ_7745c5c3_Err != nil {
15911771 return templ_7745c5c3_Err
15921772 }
1593- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "\"> <input type=\"hidden\" name=\"line\" value=\"")
1773+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "\"> <input type=\"hidden\" name=\"line\" value=\"")
15941774 if templ_7745c5c3_Err != nil {
15951775 return templ_7745c5c3_Err
15961776 }
1597- var templ_7745c5c3_Var80 string
1598- templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Line)
1777+ var templ_7745c5c3_Var88 string
1778+ templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Line)
15991779 if templ_7745c5c3_Err != nil {
1600- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 288, Col: 50}
1780+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 318, Col: 50}
16011781 }
1602- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80)
1782+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var88)
16031783 if templ_7745c5c3_Err != nil {
16041784 return templ_7745c5c3_Err
16051785 }
1606- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "\"> <input type=\"hidden\" name=\"side\" value=\"")
1786+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "\"> <input type=\"hidden\" name=\"side\" value=\"")
16071787 if templ_7745c5c3_Err != nil {
16081788 return templ_7745c5c3_Err
16091789 }
1610- var templ_7745c5c3_Var81 string
1611- templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Side)
1790+ var templ_7745c5c3_Var89 string
1791+ templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Side)
1792+ if templ_7745c5c3_Err != nil {
1793+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 319, Col: 50}
1794+ }
1795+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var89)
1796+ if templ_7745c5c3_Err != nil {
1797+ return templ_7745c5c3_Err
1798+ }
1799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "\"> <label>from line <input type=\"number\" name=\"start_line\" min=\"1\" value=\"")
1800+ if templ_7745c5c3_Err != nil {
1801+ return templ_7745c5c3_Err
1802+ }
1803+ var templ_7745c5c3_Var90 string
1804+ templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.StartLine)
1805+ if templ_7745c5c3_Err != nil {
1806+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 320, Col: 86}
1807+ }
1808+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var90)
16121809 if templ_7745c5c3_Err != nil {
1613- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 289, Col: 50}
1810+ return templ_7745c5c3_Err
1811+ }
1812+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "\"></label> ")
1813+ if templ_7745c5c3_Err != nil {
1814+ return templ_7745c5c3_Err
16141815 }
1615- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var81)
1816+ }
1817+ if f.ReplyTo != "" {
1818+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "<input type=\"hidden\" name=\"reply_to\" value=\"")
16161819 if templ_7745c5c3_Err != nil {
16171820 return templ_7745c5c3_Err
16181821 }
1619- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "\"> ")
1822+ var templ_7745c5c3_Var91 string
1823+ templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ReplyTo)
1824+ if templ_7745c5c3_Err != nil {
1825+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 323, Col: 57}
1826+ }
1827+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var91)
1828+ if templ_7745c5c3_Err != nil {
1829+ return templ_7745c5c3_Err
1830+ }
1831+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "\"> ")
1832+ if templ_7745c5c3_Err != nil {
1833+ return templ_7745c5c3_Err
1834+ }
1835+ }
1836+ if f.Path == "" && f.ReplyTo == "" {
1837+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "<fieldset><legend>Verdict</legend><label><input type=\"radio\" name=\"verdict\" value=\"\" checked>Note</label><label><input type=\"radio\" name=\"verdict\" value=\"approve\">Approve</label><label><input type=\"radio\" name=\"verdict\" value=\"changes_requested\">Request changes</label></fieldset>")
16201838 if templ_7745c5c3_Err != nil {
16211839 return templ_7745c5c3_Err
16221840 }
16231841 }
16241842 if f.From > 0 {
1625- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "<input type=\"hidden\" name=\"from\" value=\"")
1843+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "<input type=\"hidden\" name=\"from\" value=\"")
16261844 if templ_7745c5c3_Err != nil {
16271845 return templ_7745c5c3_Err
16281846 }
1629- var templ_7745c5c3_Var82 string
1630- templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(f.From))
1847+ var templ_7745c5c3_Var92 string
1848+ templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(f.From))
16311849 if templ_7745c5c3_Err != nil {
1632- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 292, Col: 56}
1850+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 329, Col: 56}
16331851 }
1634- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var82)
1852+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var92)
16351853 if templ_7745c5c3_Err != nil {
16361854 return templ_7745c5c3_Err
16371855 }
1638- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "\">")
1856+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "\">")
16391857 if templ_7745c5c3_Err != nil {
16401858 return templ_7745c5c3_Err
16411859 }
16421860 }
1643- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "<div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Comment</button> ")
1861+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "<div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Comment</button> ")
16441862 if templ_7745c5c3_Err != nil {
16451863 return templ_7745c5c3_Err
16461864 }
16471865 if f.Cancel != "" {
1648- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "<a class=\"btn\" href=\"")
1866+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "<a class=\"btn\" href=\"")
16491867 if templ_7745c5c3_Err != nil {
16501868 return templ_7745c5c3_Err
16511869 }
1652- var templ_7745c5c3_Var83 templ.SafeURL
1653- templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Cancel))
1870+ var templ_7745c5c3_Var93 templ.SafeURL
1871+ templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Cancel))
16541872 if templ_7745c5c3_Err != nil {
1655- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 297, Col: 49}
1873+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 334, Col: 49}
16561874 }
1657- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))
1875+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93))
16581876 if templ_7745c5c3_Err != nil {
16591877 return templ_7745c5c3_Err
16601878 }
1661- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "\">Cancel</a>")
1879+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "\">Cancel</a>")
16621880 if templ_7745c5c3_Err != nil {
16631881 return templ_7745c5c3_Err
16641882 }
16651883 }
1666- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "</div></form>")
1884+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "</div></form>")
16671885 if templ_7745c5c3_Err != nil {
16681886 return templ_7745c5c3_Err
16691887 }
@@ -1687,25 +1905,25 @@ func mrRail(p MRPage) templ.Component {
16871905 }()
16881906 }
16891907 ctx = templ.InitializeContext(ctx)
1690- templ_7745c5c3_Var84 := templ.GetChildren(ctx)
1691- if templ_7745c5c3_Var84 == nil {
1692- templ_7745c5c3_Var84 = templ.NopComponent
1908+ templ_7745c5c3_Var94 := templ.GetChildren(ctx)
1909+ if templ_7745c5c3_Var94 == nil {
1910+ templ_7745c5c3_Var94 = templ.NopComponent
16931911 }
16941912 ctx = templ.ClearChildren(ctx)
1695- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<section aria-labelledby=\"r-files\" class=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")
1913+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "<section aria-labelledby=\"r-files\" class=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")
16961914 if templ_7745c5c3_Err != nil {
16971915 return templ_7745c5c3_Err
16981916 }
1699- var templ_7745c5c3_Var85 string
1700- templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))
1917+ var templ_7745c5c3_Var95 string
1918+ templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))
17011919 if templ_7745c5c3_Err != nil {
1702- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 306, Col: 49}
1920+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 343, Col: 49}
17031921 }
1704- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))
1922+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))
17051923 if templ_7745c5c3_Err != nil {
17061924 return templ_7745c5c3_Err
17071925 }
1708- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</span><kbd aria-hidden=\"true\">g f</kbd> <button type=\"button\" class=\"btn icon fold\" data-js aria-expanded=\"true\" title=\"fold the list of files\" hx-on:click=\"ermiteMR.foldFiles()\"><span class=\"ico i-chevron\" aria-hidden=\"true\"></span><span class=\"sr\">fold the list of files</span></button></h2>")
1926+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</span><kbd aria-hidden=\"true\">g f</kbd> <button type=\"button\" class=\"btn icon fold\" data-js aria-expanded=\"true\" title=\"fold the list of files\" hx-on:click=\"ermiteMR.foldFiles()\"><span class=\"ico i-chevron\" aria-hidden=\"true\"></span><span class=\"sr\">fold the list of files</span></button></h2>")
17091927 if templ_7745c5c3_Err != nil {
17101928 return templ_7745c5c3_Err
17111929 }
@@ -1713,82 +1931,147 @@ func mrRail(p MRPage) templ.Component {
17131931 if templ_7745c5c3_Err != nil {
17141932 return templ_7745c5c3_Err
17151933 }
1716- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "</section>")
1934+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "</section>")
17171935 if templ_7745c5c3_Err != nil {
17181936 return templ_7745c5c3_Err
17191937 }
1938+ if len(p.Verdicts) > 0 {
1939+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "<section><h2>Verdicts</h2><ul>")
1940+ if templ_7745c5c3_Err != nil {
1941+ return templ_7745c5c3_Err
1942+ }
1943+ for _, v := range p.Verdicts {
1944+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "<li><a href=\"")
1945+ if templ_7745c5c3_Err != nil {
1946+ return templ_7745c5c3_Err
1947+ }
1948+ var templ_7745c5c3_Var96 templ.SafeURL
1949+ templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(v.Href))
1950+ if templ_7745c5c3_Err != nil {
1951+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 356, Col: 37}
1952+ }
1953+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))
1954+ if templ_7745c5c3_Err != nil {
1955+ return templ_7745c5c3_Err
1956+ }
1957+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "\">")
1958+ if templ_7745c5c3_Err != nil {
1959+ return templ_7745c5c3_Err
1960+ }
1961+ var templ_7745c5c3_Var97 string
1962+ templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(v.Author)
1963+ if templ_7745c5c3_Err != nil {
1964+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 356, Col: 50}
1965+ }
1966+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97))
1967+ if templ_7745c5c3_Err != nil {
1968+ return templ_7745c5c3_Err
1969+ }
1970+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, ": ")
1971+ if templ_7745c5c3_Err != nil {
1972+ return templ_7745c5c3_Err
1973+ }
1974+ var templ_7745c5c3_Var98 string
1975+ templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(v.Label)
1976+ if templ_7745c5c3_Err != nil {
1977+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 356, Col: 63}
1978+ }
1979+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98))
1980+ if templ_7745c5c3_Err != nil {
1981+ return templ_7745c5c3_Err
1982+ }
1983+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "</a> ")
1984+ if templ_7745c5c3_Err != nil {
1985+ return templ_7745c5c3_Err
1986+ }
1987+ if v.Stale {
1988+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "<span class=\"mute\">(stale)</span>")
1989+ if templ_7745c5c3_Err != nil {
1990+ return templ_7745c5c3_Err
1991+ }
1992+ }
1993+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "</li>")
1994+ if templ_7745c5c3_Err != nil {
1995+ return templ_7745c5c3_Err
1996+ }
1997+ }
1998+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "</ul></section>")
1999+ if templ_7745c5c3_Err != nil {
2000+ return templ_7745c5c3_Err
2001+ }
2002+ }
17202003 templ_7745c5c3_Err = MRChecks(p.Checks).Render(ctx, templ_7745c5c3_Buffer)
17212004 if templ_7745c5c3_Err != nil {
17222005 return templ_7745c5c3_Err
17232006 }
1724- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "<section aria-labelledby=\"r-commits\"><h2 id=\"r-commits\">Commits <span class=\"count\">")
2007+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "<section aria-labelledby=\"r-commits\"><h2 id=\"r-commits\">Commits <span class=\"count\">")
17252008 if templ_7745c5c3_Err != nil {
17262009 return templ_7745c5c3_Err
17272010 }
1728- var templ_7745c5c3_Var86 string
1729- templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Commits)))
2011+ var templ_7745c5c3_Var99 string
2012+ templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Commits)))
17302013 if templ_7745c5c3_Err != nil {
1731- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 315, Col: 71}
2014+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 367, Col: 71}
17322015 }
1733- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))
2016+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99))
17342017 if templ_7745c5c3_Err != nil {
17352018 return templ_7745c5c3_Err
17362019 }
1737- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</span></h2><ul class=\"pcommits\">")
2020+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "</span></h2><ul class=\"pcommits\">")
17382021 if templ_7745c5c3_Err != nil {
17392022 return templ_7745c5c3_Err
17402023 }
17412024 for _, c := range p.Commits {
1742- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "<li")
2025+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "<li")
17432026 if templ_7745c5c3_Err != nil {
17442027 return templ_7745c5c3_Err
17452028 }
17462029 if c.New {
1747- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, " data-new")
2030+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, " data-new")
17482031 if templ_7745c5c3_Err != nil {
17492032 return templ_7745c5c3_Err
17502033 }
17512034 }
1752- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "><a href=\"")
2035+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "><a href=\"")
17532036 if templ_7745c5c3_Err != nil {
17542037 return templ_7745c5c3_Err
17552038 }
1756- var templ_7745c5c3_Var87 templ.SafeURL
1757- templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))
2039+ var templ_7745c5c3_Var100 templ.SafeURL
2040+ templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))
17582041 if templ_7745c5c3_Err != nil {
1759- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 319, Col: 36}
2042+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 371, Col: 36}
17602043 }
1761- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87))
2044+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))
17622045 if templ_7745c5c3_Err != nil {
17632046 return templ_7745c5c3_Err
17642047 }
1765- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "\">")
2048+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "\">")
17662049 if templ_7745c5c3_Err != nil {
17672050 return templ_7745c5c3_Err
17682051 }
1769- var templ_7745c5c3_Var88 string
1770- templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
2052+ var templ_7745c5c3_Var101 string
2053+ templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
17712054 if templ_7745c5c3_Err != nil {
1772- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 319, Col: 48}
2055+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 371, Col: 48}
17732056 }
1774- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))
2057+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101))
17752058 if templ_7745c5c3_Err != nil {
17762059 return templ_7745c5c3_Err
17772060 }
1778- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "</a> <span class=\"s\">")
2061+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "</a> <span class=\"s\">")
17792062 if templ_7745c5c3_Err != nil {
17802063 return templ_7745c5c3_Err
17812064 }
1782- var templ_7745c5c3_Var89 string
1783- templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
2065+ var templ_7745c5c3_Var102 string
2066+ templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
17842067 if templ_7745c5c3_Err != nil {
1785- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 320, Col: 32}
2068+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 372, Col: 32}
17862069 }
1787- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89))
2070+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
17882071 if templ_7745c5c3_Err != nil {
17892072 return templ_7745c5c3_Err
17902073 }
1791- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "</span> ")
2074+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "</span> ")
17922075 if templ_7745c5c3_Err != nil {
17932076 return templ_7745c5c3_Err
17942077 }
@@ -1798,138 +2081,138 @@ func mrRail(p MRPage) templ.Component {
17982081 return templ_7745c5c3_Err
17992082 }
18002083 }
1801- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "<span class=\"mute\">")
2084+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "<span class=\"mute\">")
18022085 if templ_7745c5c3_Err != nil {
18032086 return templ_7745c5c3_Err
18042087 }
1805- var templ_7745c5c3_Var90 string
1806- templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
2088+ var templ_7745c5c3_Var103 string
2089+ templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
18072090 if templ_7745c5c3_Err != nil {
1808- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 325, Col: 16}
2091+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 377, Col: 16}
18092092 }
1810- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90))
2093+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))
18112094 if templ_7745c5c3_Err != nil {
18122095 return templ_7745c5c3_Err
18132096 }
1814- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, " ")
2097+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, " ")
18152098 if templ_7745c5c3_Err != nil {
18162099 return templ_7745c5c3_Err
18172100 }
18182101 if c.WhenAt != "" {
1819- var templ_7745c5c3_Var91 string
1820- templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
2102+ var templ_7745c5c3_Var104 string
2103+ templ_7745c5c3_Var104, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
18212104 if templ_7745c5c3_Err != nil {
1822- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 327, Col: 15}
2105+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 379, Col: 15}
18232106 }
1824- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var91))
2107+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var104))
18252108 if templ_7745c5c3_Err != nil {
18262109 return templ_7745c5c3_Err
18272110 }
1828- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<time datetime=\"")
2111+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "<time datetime=\"")
18292112 if templ_7745c5c3_Err != nil {
18302113 return templ_7745c5c3_Err
18312114 }
1832- var templ_7745c5c3_Var92 string
1833- templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
2115+ var templ_7745c5c3_Var105 string
2116+ templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
18342117 if templ_7745c5c3_Err != nil {
1835- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 327, Col: 42}
2118+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 379, Col: 42}
18362119 }
1837- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var92)
2120+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var105)
18382121 if templ_7745c5c3_Err != nil {
18392122 return templ_7745c5c3_Err
18402123 }
1841- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "\">")
2124+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "\">")
18422125 if templ_7745c5c3_Err != nil {
18432126 return templ_7745c5c3_Err
18442127 }
1845- var templ_7745c5c3_Var93 string
1846- templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
2128+ var templ_7745c5c3_Var106 string
2129+ templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
18472130 if templ_7745c5c3_Err != nil {
1848- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 327, Col: 53}
2131+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 379, Col: 53}
18492132 }
1850- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93))
2133+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var106))
18512134 if templ_7745c5c3_Err != nil {
18522135 return templ_7745c5c3_Err
18532136 }
1854- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "</time> ")
2137+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "</time> ")
18552138 if templ_7745c5c3_Err != nil {
18562139 return templ_7745c5c3_Err
18572140 }
18582141 }
18592142 if c.New {
1860- var templ_7745c5c3_Var94 string
1861- templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(" · new")
2143+ var templ_7745c5c3_Var107 string
2144+ templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.JoinStringErrs(" · new")
18622145 if templ_7745c5c3_Err != nil {
1863- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 330, Col: 18}
2146+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 382, Col: 18}
18642147 }
1865- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94))
2148+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var107))
18662149 if templ_7745c5c3_Err != nil {
18672150 return templ_7745c5c3_Err
18682151 }
18692152 }
1870- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "</span></li>")
2153+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "</span></li>")
18712154 if templ_7745c5c3_Err != nil {
18722155 return templ_7745c5c3_Err
18732156 }
18742157 }
1875- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "</ul>")
2158+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "</ul>")
18762159 if templ_7745c5c3_Err != nil {
18772160 return templ_7745c5c3_Err
18782161 }
18792162 if len(p.Commits) == 0 {
1880- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "<p class=\"dim\">no commit to merge.</p>")
2163+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<p class=\"dim\">no commit to merge.</p>")
18812164 if templ_7745c5c3_Err != nil {
18822165 return templ_7745c5c3_Err
18832166 }
18842167 }
18852168 if p.CommitsNote != "" {
1886- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "<p class=\"note\">")
2169+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "<p class=\"note\">")
18872170 if templ_7745c5c3_Err != nil {
18882171 return templ_7745c5c3_Err
18892172 }
1890- var templ_7745c5c3_Var95 string
1891- templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(p.CommitsNote)
2173+ var templ_7745c5c3_Var108 string
2174+ templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.JoinStringErrs(p.CommitsNote)
18922175 if templ_7745c5c3_Err != nil {
1893- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 340, Col: 34}
2176+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 392, Col: 34}
18942177 }
1895- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))
2178+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var108))
18962179 if templ_7745c5c3_Err != nil {
18972180 return templ_7745c5c3_Err
18982181 }
1899- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "</p>")
2182+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "</p>")
19002183 if templ_7745c5c3_Err != nil {
19012184 return templ_7745c5c3_Err
19022185 }
19032186 }
1904- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "</section><section aria-labelledby=\"r-settings\"><h2 id=\"r-settings\">Settings</h2><dl class=\"kv\"><dt>modes</dt><dd>")
2187+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "</section><section aria-labelledby=\"r-settings\"><h2 id=\"r-settings\">Settings</h2><dl class=\"kv\"><dt>modes</dt><dd>")
19052188 if templ_7745c5c3_Err != nil {
19062189 return templ_7745c5c3_Err
19072190 }
1908- var templ_7745c5c3_Var96 string
1909- templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(p.Modes, ", "))
2191+ var templ_7745c5c3_Var109 string
2192+ templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(p.Modes, ", "))
19102193 if templ_7745c5c3_Err != nil {
1911- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 347, Col: 36}
2194+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 399, Col: 36}
19122195 }
1913- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))
2196+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))
19142197 if templ_7745c5c3_Err != nil {
19152198 return templ_7745c5c3_Err
19162199 }
1917- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "</dd><dt>checks</dt><dd>")
2200+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "</dd><dt>checks</dt><dd>")
19182201 if templ_7745c5c3_Err != nil {
19192202 return templ_7745c5c3_Err
19202203 }
19212204 if p.ChecksBlock {
1922- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "failing ones block the merge")
2205+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "failing ones block the merge")
19232206 if templ_7745c5c3_Err != nil {
19242207 return templ_7745c5c3_Err
19252208 }
19262209 } else {
1927- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "do not block the merge")
2210+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "do not block the merge")
19282211 if templ_7745c5c3_Err != nil {
19292212 return templ_7745c5c3_Err
19302213 }
19312214 }
1932- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</dd></dl></section>")
2215+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "</dd></dl></section>")
19332216 if templ_7745c5c3_Err != nil {
19342217 return templ_7745c5c3_Err
19352218 }
@@ -1957,51 +2240,51 @@ func mrBar(p MRPage) templ.Component {
19572240 }()
19582241 }
19592242 ctx = templ.InitializeContext(ctx)
1960- templ_7745c5c3_Var97 := templ.GetChildren(ctx)
1961- if templ_7745c5c3_Var97 == nil {
1962- templ_7745c5c3_Var97 = templ.NopComponent
2243+ templ_7745c5c3_Var110 := templ.GetChildren(ctx)
2244+ if templ_7745c5c3_Var110 == nil {
2245+ templ_7745c5c3_Var110 = templ.NopComponent
19632246 }
19642247 ctx = templ.ClearChildren(ctx)
1965- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<div class=\"diff-tools mr-bar\" role=\"group\" aria-label=\"Diff view and merge\"><span class=\"dim\">")
2248+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "<div class=\"diff-tools mr-bar\" role=\"group\" aria-label=\"Diff view and merge\"><span class=\"dim\">")
19662249 if templ_7745c5c3_Err != nil {
19672250 return templ_7745c5c3_Err
19682251 }
1969- var templ_7745c5c3_Var98 string
1970- templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))
2252+ var templ_7745c5c3_Var111 string
2253+ templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))
19712254 if templ_7745c5c3_Err != nil {
1972- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 366, Col: 55}
2255+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 55}
19732256 }
1974- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98))
2257+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var111))
19752258 if templ_7745c5c3_Err != nil {
19762259 return templ_7745c5c3_Err
19772260 }
1978- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, " · <span class=\"plus\">+")
2261+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, " · <span class=\"plus\">+")
19792262 if templ_7745c5c3_Err != nil {
19802263 return templ_7745c5c3_Err
19812264 }
1982- var templ_7745c5c3_Var99 string
1983- templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))
2265+ var templ_7745c5c3_Var112 string
2266+ templ_7745c5c3_Var112, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))
19842267 if templ_7745c5c3_Err != nil {
1985- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 366, Col: 95}
2268+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 95}
19862269 }
1987- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99))
2270+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var112))
19882271 if templ_7745c5c3_Err != nil {
19892272 return templ_7745c5c3_Err
19902273 }
1991- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "</span> <span class=\"minus\">-")
2274+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "</span> <span class=\"minus\">-")
19922275 if templ_7745c5c3_Err != nil {
19932276 return templ_7745c5c3_Err
19942277 }
1995- var templ_7745c5c3_Var100 string
1996- templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))
2278+ var templ_7745c5c3_Var113 string
2279+ templ_7745c5c3_Var113, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))
19972280 if templ_7745c5c3_Err != nil {
1998- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 366, Col: 140}
2281+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 140}
19992282 }
2000- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))
2283+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var113))
20012284 if templ_7745c5c3_Err != nil {
20022285 return templ_7745c5c3_Err
20032286 }
2004- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "</span></span>")
2287+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "</span></span>")
20052288 if templ_7745c5c3_Err != nil {
20062289 return templ_7745c5c3_Err
20072290 }
@@ -2017,7 +2300,7 @@ func mrBar(p MRPage) templ.Component {
20172300 if templ_7745c5c3_Err != nil {
20182301 return templ_7745c5c3_Err
20192302 }
2020- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "<span class=\"grow\"></span>")
2303+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "<span class=\"grow\"></span>")
20212304 if templ_7745c5c3_Err != nil {
20222305 return templ_7745c5c3_Err
20232306 }
@@ -2030,7 +2313,7 @@ func mrBar(p MRPage) templ.Component {
20302313 if templ_7745c5c3_Err != nil {
20312314 return templ_7745c5c3_Err
20322315 }
2033- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, " ")
2316+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, " ")
20342317 if templ_7745c5c3_Err != nil {
20352318 return templ_7745c5c3_Err
20362319 }
@@ -2039,7 +2322,7 @@ func mrBar(p MRPage) templ.Component {
20392322 return templ_7745c5c3_Err
20402323 }
20412324 }
2042- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "</div>")
2325+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "</div>")
20432326 if templ_7745c5c3_Err != nil {
20442327 return templ_7745c5c3_Err
20452328 }
@@ -2066,219 +2349,219 @@ func mergeMenu(p MRPage) templ.Component {
20662349 }()
20672350 }
20682351 ctx = templ.InitializeContext(ctx)
2069- templ_7745c5c3_Var101 := templ.GetChildren(ctx)
2070- if templ_7745c5c3_Var101 == nil {
2071- templ_7745c5c3_Var101 = templ.NopComponent
2352+ templ_7745c5c3_Var114 := templ.GetChildren(ctx)
2353+ if templ_7745c5c3_Var114 == nil {
2354+ templ_7745c5c3_Var114 = templ.NopComponent
20722355 }
20732356 ctx = templ.ClearChildren(ctx)
2074- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "<details class=\"menu mmenu\" id=\"merge-menu\" data-state=\"open\"><summary class=\"btn primary\" aria-keyshortcuts=\"Shift+M\">")
2357+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "<details class=\"menu mmenu\" id=\"merge-menu\" data-state=\"open\"><summary class=\"btn primary\" aria-keyshortcuts=\"Shift+M\">")
20752358 if templ_7745c5c3_Err != nil {
20762359 return templ_7745c5c3_Err
20772360 }
20782361 if p.Box.ReadyAction != "" {
2079- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "Draft ")
2362+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "Draft ")
20802363 if templ_7745c5c3_Err != nil {
20812364 return templ_7745c5c3_Err
20822365 }
20832366 } else {
2084- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "Merge ")
2367+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "Merge ")
20852368 if templ_7745c5c3_Err != nil {
20862369 return templ_7745c5c3_Err
20872370 }
20882371 }
2089- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "<kbd aria-hidden=\"true\">M</kbd></summary><div class=\"menu-pop\" role=\"group\" aria-label=\"Merge\"><dl class=\"kv\"><dt>head</dt><dd>")
2372+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<kbd aria-hidden=\"true\">M</kbd></summary><div class=\"menu-pop\" role=\"group\" aria-label=\"Merge\"><dl class=\"kv\"><dt>head</dt><dd>")
20902373 if templ_7745c5c3_Err != nil {
20912374 return templ_7745c5c3_Err
20922375 }
20932376 if p.Box.HeadGone {
2094- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "gone: the source branch was deleted")
2377+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "gone: the source branch was deleted")
20952378 if templ_7745c5c3_Err != nil {
20962379 return templ_7745c5c3_Err
20972380 }
20982381 } else {
2099- var templ_7745c5c3_Var102 string
2100- templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(p.HeadShort)
2382+ var templ_7745c5c3_Var115 string
2383+ templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.JoinStringErrs(p.HeadShort)
21012384 if templ_7745c5c3_Err != nil {
2102- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 399, Col: 19}
2385+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 451, Col: 19}
21032386 }
2104- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
2387+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var115))
21052388 if templ_7745c5c3_Err != nil {
21062389 return templ_7745c5c3_Err
21072390 }
21082391 }
2109- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "</dd><dt>into</dt><dd>")
2392+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "</dd><dt>into</dt><dd>")
21102393 if templ_7745c5c3_Err != nil {
21112394 return templ_7745c5c3_Err
21122395 }
2113- var templ_7745c5c3_Var103 string
2114- templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2396+ var templ_7745c5c3_Var116 string
2397+ templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
21152398 if templ_7745c5c3_Err != nil {
2116- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 403, Col: 18}
2399+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 455, Col: 18}
21172400 }
2118- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))
2401+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var116))
21192402 if templ_7745c5c3_Err != nil {
21202403 return templ_7745c5c3_Err
21212404 }
2122- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "</dd></dl>")
2405+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "</dd></dl>")
21232406 if templ_7745c5c3_Err != nil {
21242407 return templ_7745c5c3_Err
21252408 }
21262409 if p.Box.ReadyAction != "" {
2127- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "<p class=\"d-draft\">this is a draft: it cannot be merged until it is ready for review.</p><form class=\"d-btns\" method=\"post\" action=\"")
2410+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "<p class=\"d-draft\">this is a draft: it cannot be merged until it is ready for review.</p><form class=\"d-btns\" method=\"post\" action=\"")
21282411 if templ_7745c5c3_Err != nil {
21292412 return templ_7745c5c3_Err
21302413 }
2131- var templ_7745c5c3_Var104 templ.SafeURL
2132- templ_7745c5c3_Var104, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadyAction))
2414+ var templ_7745c5c3_Var117 templ.SafeURL
2415+ templ_7745c5c3_Var117, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadyAction))
21332416 if templ_7745c5c3_Err != nil {
2134- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 407, Col: 80}
2417+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 459, Col: 80}
21352418 }
2136- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var104))
2419+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var117))
21372420 if templ_7745c5c3_Err != nil {
21382421 return templ_7745c5c3_Err
21392422 }
2140- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "\"><button type=\"submit\" class=\"btn primary\" data-default title=\"remove the draft prefix from the title\">Ready for review</button></form>")
2423+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "\"><button type=\"submit\" class=\"btn primary\" data-default title=\"remove the draft prefix from the title\">Ready for review</button></form>")
21412424 if templ_7745c5c3_Err != nil {
21422425 return templ_7745c5c3_Err
21432426 }
21442427 } else if !p.Box.HeadGone {
2145- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "<form class=\"d-acts\" method=\"post\" action=\"")
2428+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "<form class=\"d-acts\" method=\"post\" action=\"")
21462429 if templ_7745c5c3_Err != nil {
21472430 return templ_7745c5c3_Err
21482431 }
2149- var templ_7745c5c3_Var105 templ.SafeURL
2150- templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.MergeAction))
2432+ var templ_7745c5c3_Var118 templ.SafeURL
2433+ templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.MergeAction))
21512434 if templ_7745c5c3_Err != nil {
2152- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 411, Col: 80}
2435+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 463, Col: 80}
21532436 }
2154- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var105))
2437+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
21552438 if templ_7745c5c3_Err != nil {
21562439 return templ_7745c5c3_Err
21572440 }
2158- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "\"><input type=\"hidden\" name=\"head_id\" value=\"")
2441+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "\"><input type=\"hidden\" name=\"head_id\" value=\"")
21592442 if templ_7745c5c3_Err != nil {
21602443 return templ_7745c5c3_Err
21612444 }
2162- var templ_7745c5c3_Var106 string
2163- templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)
2445+ var templ_7745c5c3_Var119 string
2446+ templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)
21642447 if templ_7745c5c3_Err != nil {
2165- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 412, Col: 59}
2448+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 464, Col: 59}
21662449 }
2167- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var106)
2450+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var119)
21682451 if templ_7745c5c3_Err != nil {
21692452 return templ_7745c5c3_Err
21702453 }
2171- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "\"><fieldset class=\"d-modes\"><legend class=\"sr\">merge mode</legend> ")
2454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 224, "\"><fieldset class=\"d-modes\"><legend class=\"sr\">merge mode</legend> ")
21722455 if templ_7745c5c3_Err != nil {
21732456 return templ_7745c5c3_Err
21742457 }
21752458 for i, m := range p.Box.Modes {
2176- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "<label><input type=\"radio\" name=\"mode\" value=\"")
2459+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 225, "<label><input type=\"radio\" name=\"mode\" value=\"")
21772460 if templ_7745c5c3_Err != nil {
21782461 return templ_7745c5c3_Err
21792462 }
2180- var templ_7745c5c3_Var107 string
2181- templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.ResolveAttributeValue(m.Value)
2463+ var templ_7745c5c3_Var120 string
2464+ templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.ResolveAttributeValue(m.Value)
21822465 if templ_7745c5c3_Err != nil {
2183- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 417, Col: 55}
2466+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 469, Col: 55}
21842467 }
2185- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var107)
2468+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var120)
21862469 if templ_7745c5c3_Err != nil {
21872470 return templ_7745c5c3_Err
21882471 }
2189- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "\"")
2472+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 226, "\"")
21902473 if templ_7745c5c3_Err != nil {
21912474 return templ_7745c5c3_Err
21922475 }
21932476 if i == 0 {
2194- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, " checked")
2477+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 227, " checked")
21952478 if templ_7745c5c3_Err != nil {
21962479 return templ_7745c5c3_Err
21972480 }
21982481 }
2199- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "> ")
2482+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 228, "> ")
22002483 if templ_7745c5c3_Err != nil {
22012484 return templ_7745c5c3_Err
22022485 }
2203- var templ_7745c5c3_Var108 string
2204- templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.JoinStringErrs(m.Label)
2486+ var templ_7745c5c3_Var121 string
2487+ templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinStringErrs(m.Label)
22052488 if templ_7745c5c3_Err != nil {
2206- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 17}
2489+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 470, Col: 17}
22072490 }
2208- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var108))
2491+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))
22092492 if templ_7745c5c3_Err != nil {
22102493 return templ_7745c5c3_Err
22112494 }
2212- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, " ")
2495+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 229, " ")
22132496 if templ_7745c5c3_Err != nil {
22142497 return templ_7745c5c3_Err
22152498 }
22162499 if i < 9 {
2217- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<kbd aria-hidden=\"true\">")
2500+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 230, "<kbd aria-hidden=\"true\">")
22182501 if templ_7745c5c3_Err != nil {
22192502 return templ_7745c5c3_Err
22202503 }
2221- var templ_7745c5c3_Var109 string
2222- templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(i + 1))
2504+ var templ_7745c5c3_Var122 string
2505+ templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(i + 1))
22232506 if templ_7745c5c3_Err != nil {
2224- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 420, Col: 46}
2507+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 472, Col: 46}
22252508 }
2226- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))
2509+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))
22272510 if templ_7745c5c3_Err != nil {
22282511 return templ_7745c5c3_Err
22292512 }
2230- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "</kbd>")
2513+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 231, "</kbd>")
22312514 if templ_7745c5c3_Err != nil {
22322515 return templ_7745c5c3_Err
22332516 }
22342517 }
2235- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "</label>")
2518+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 232, "</label>")
22362519 if templ_7745c5c3_Err != nil {
22372520 return templ_7745c5c3_Err
22382521 }
22392522 }
2240- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "</fieldset>")
2523+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 233, "</fieldset>")
22412524 if templ_7745c5c3_Err != nil {
22422525 return templ_7745c5c3_Err
22432526 }
22442527 if p.Box.CanDeleteBranch {
2245- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "<label class=\"d-check\"><input type=\"checkbox\" name=\"delete_branch\" value=\"1\"> delete ")
2528+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 234, "<label class=\"d-check\"><input type=\"checkbox\" name=\"delete_branch\" value=\"1\"> delete ")
22462529 if templ_7745c5c3_Err != nil {
22472530 return templ_7745c5c3_Err
22482531 }
2249- var templ_7745c5c3_Var110 string
2250- templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
2532+ var templ_7745c5c3_Var123 string
2533+ templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
22512534 if templ_7745c5c3_Err != nil {
2252- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 426, Col: 102}
2535+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 478, Col: 102}
22532536 }
2254- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var110))
2537+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))
22552538 if templ_7745c5c3_Err != nil {
22562539 return templ_7745c5c3_Err
22572540 }
2258- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, " after the merge <kbd aria-hidden=\"true\">D</kbd></label>")
2541+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 235, " after the merge <kbd aria-hidden=\"true\">D</kbd></label>")
22592542 if templ_7745c5c3_Err != nil {
22602543 return templ_7745c5c3_Err
22612544 }
22622545 }
2263- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "<div class=\"d-btns\"><button type=\"submit\" class=\"btn primary\" data-default>Merge into ")
2546+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 236, "<div class=\"d-btns\"><button type=\"submit\" class=\"btn primary\" data-default>Merge into ")
22642547 if templ_7745c5c3_Err != nil {
22652548 return templ_7745c5c3_Err
22662549 }
2267- var templ_7745c5c3_Var111 string
2268- templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2550+ var templ_7745c5c3_Var124 string
2551+ templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
22692552 if templ_7745c5c3_Err != nil {
2270- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 429, Col: 82}
2553+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 481, Col: 82}
22712554 }
2272- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var111))
2555+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))
22732556 if templ_7745c5c3_Err != nil {
22742557 return templ_7745c5c3_Err
22752558 }
2276- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "</button></div></form>")
2559+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 237, "</button></div></form>")
22772560 if templ_7745c5c3_Err != nil {
22782561 return templ_7745c5c3_Err
22792562 }
22802563 }
2281- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "</div></details>")
2564+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 238, "</div></details>")
22822565 if templ_7745c5c3_Err != nil {
22832566 return templ_7745c5c3_Err
22842567 }
@@ -2304,66 +2587,66 @@ func moreMenu(p MRPage) templ.Component {
23042587 }()
23052588 }
23062589 ctx = templ.InitializeContext(ctx)
2307- templ_7745c5c3_Var112 := templ.GetChildren(ctx)
2308- if templ_7745c5c3_Var112 == nil {
2309- templ_7745c5c3_Var112 = templ.NopComponent
2590+ templ_7745c5c3_Var125 := templ.GetChildren(ctx)
2591+ if templ_7745c5c3_Var125 == nil {
2592+ templ_7745c5c3_Var125 = templ.NopComponent
23102593 }
23112594 ctx = templ.ClearChildren(ctx)
2312- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "<details class=\"menu omenu\"><summary class=\"btn icon\" aria-label=\"More actions\" title=\"more actions\">⋯</summary><div class=\"menu-pop\" role=\"group\" aria-label=\"More actions\">")
2595+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 239, "<details class=\"menu omenu\"><summary class=\"btn icon\" aria-label=\"More actions\" title=\"more actions\">⋯</summary><div class=\"menu-pop\" role=\"group\" aria-label=\"More actions\">")
23132596 if templ_7745c5c3_Err != nil {
23142597 return templ_7745c5c3_Err
23152598 }
23162599 if p.Box.CanMarkRead {
2317- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "<form method=\"post\" action=\"")
2600+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 240, "<form method=\"post\" action=\"")
23182601 if templ_7745c5c3_Err != nil {
23192602 return templ_7745c5c3_Err
23202603 }
2321- var templ_7745c5c3_Var113 templ.SafeURL
2322- templ_7745c5c3_Var113, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadAction))
2604+ var templ_7745c5c3_Var126 templ.SafeURL
2605+ templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadAction))
23232606 if templ_7745c5c3_Err != nil {
2324- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 444, Col: 64}
2607+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 64}
23252608 }
2326- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var113))
2609+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126))
23272610 if templ_7745c5c3_Err != nil {
23282611 return templ_7745c5c3_Err
23292612 }
2330- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "\"><input type=\"hidden\" name=\"head_id\" value=\"")
2613+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 241, "\"><input type=\"hidden\" name=\"head_id\" value=\"")
23312614 if templ_7745c5c3_Err != nil {
23322615 return templ_7745c5c3_Err
23332616 }
2334- var templ_7745c5c3_Var114 string
2335- templ_7745c5c3_Var114, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)
2617+ var templ_7745c5c3_Var127 string
2618+ templ_7745c5c3_Var127, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)
23362619 if templ_7745c5c3_Err != nil {
2337- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 445, Col: 59}
2620+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 497, Col: 59}
23382621 }
2339- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var114)
2622+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var127)
23402623 if templ_7745c5c3_Err != nil {
23412624 return templ_7745c5c3_Err
23422625 }
2343- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "\"> <button type=\"submit\" title=\"the next visit can show only what changed after this head\" class=\"btn\" aria-keyshortcuts=\"Shift+R\" data-read>Mark read <kbd aria-hidden=\"true\">R</kbd></button></form>")
2626+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 242, "\"> <button type=\"submit\" title=\"the next visit can show only what changed after this head\" class=\"btn\" aria-keyshortcuts=\"Shift+R\" data-read>Mark read <kbd aria-hidden=\"true\">R</kbd></button></form>")
23442627 if templ_7745c5c3_Err != nil {
23452628 return templ_7745c5c3_Err
23462629 }
23472630 }
2348- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "<form method=\"post\" action=\"")
2631+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 243, "<form method=\"post\" action=\"")
23492632 if templ_7745c5c3_Err != nil {
23502633 return templ_7745c5c3_Err
23512634 }
2352- var templ_7745c5c3_Var115 templ.SafeURL
2353- templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.CloseAction))
2635+ var templ_7745c5c3_Var128 templ.SafeURL
2636+ templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.CloseAction))
23542637 if templ_7745c5c3_Err != nil {
2355- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 449, Col: 64}
2638+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 501, Col: 64}
23562639 }
2357- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var115))
2640+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128))
23582641 if templ_7745c5c3_Err != nil {
23592642 return templ_7745c5c3_Err
23602643 }
2361- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "\"><button type=\"submit\" title=\"close without merging; the branch is kept\" class=\"btn danger\">Close</button></form>")
2644+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 244, "\"><button type=\"submit\" title=\"close without merging; the branch is kept\" class=\"btn danger\">Close</button></form>")
23622645 if templ_7745c5c3_Err != nil {
23632646 return templ_7745c5c3_Err
23642647 }
23652648 if !p.Box.HeadGone && p.Box.ReadyAction == "" {
2366- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "<details class=\"d-or\"><summary class=\"mute\">or from a terminal</summary>")
2649+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 245, "<details class=\"d-or\"><summary class=\"mute\">or from a terminal</summary>")
23672650 if templ_7745c5c3_Err != nil {
23682651 return templ_7745c5c3_Err
23692652 }
@@ -2371,12 +2654,12 @@ func moreMenu(p MRPage) templ.Component {
23712654 if templ_7745c5c3_Err != nil {
23722655 return templ_7745c5c3_Err
23732656 }
2374- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "</details>")
2657+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 246, "</details>")
23752658 if templ_7745c5c3_Err != nil {
23762659 return templ_7745c5c3_Err
23772660 }
23782661 }
2379- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "</div></details>")
2662+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 247, "</div></details>")
23802663 if templ_7745c5c3_Err != nil {
23812664 return templ_7745c5c3_Err
23822665 }
@@ -2400,12 +2683,12 @@ func Compare(l Layout, p ComparePage) templ.Component {
24002683 }()
24012684 }
24022685 ctx = templ.InitializeContext(ctx)
2403- templ_7745c5c3_Var116 := templ.GetChildren(ctx)
2404- if templ_7745c5c3_Var116 == nil {
2405- templ_7745c5c3_Var116 = templ.NopComponent
2686+ templ_7745c5c3_Var129 := templ.GetChildren(ctx)
2687+ if templ_7745c5c3_Var129 == nil {
2688+ templ_7745c5c3_Var129 = templ.NopComponent
24062689 }
24072690 ctx = templ.ClearChildren(ctx)
2408- templ_7745c5c3_Var117 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
2691+ templ_7745c5c3_Var130 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
24092692 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
24102693 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
24112694 if !templ_7745c5c3_IsBuffer {
@@ -2417,263 +2700,263 @@ func Compare(l Layout, p ComparePage) templ.Component {
24172700 }()
24182701 }
24192702 ctx = templ.InitializeContext(ctx)
2420- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<header class=\"show mr-h\"><h1>")
2703+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 248, "<header class=\"show mr-h\"><h1>")
24212704 if templ_7745c5c3_Err != nil {
24222705 return templ_7745c5c3_Err
24232706 }
2424- var templ_7745c5c3_Var118 string
2425- templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
2707+ var templ_7745c5c3_Var131 string
2708+ templ_7745c5c3_Var131, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
24262709 if templ_7745c5c3_Err != nil {
2427- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 465, Col: 17}
2710+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 517, Col: 17}
24282711 }
2429- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
2712+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var131))
24302713 if templ_7745c5c3_Err != nil {
24312714 return templ_7745c5c3_Err
24322715 }
2433- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, " → ")
2716+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 249, " → ")
24342717 if templ_7745c5c3_Err != nil {
24352718 return templ_7745c5c3_Err
24362719 }
2437- var templ_7745c5c3_Var119 string
2438- templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2720+ var templ_7745c5c3_Var132 string
2721+ templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
24392722 if templ_7745c5c3_Err != nil {
2440- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 465, Col: 34}
2723+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 517, Col: 34}
24412724 }
2442- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var119))
2725+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))
24432726 if templ_7745c5c3_Err != nil {
24442727 return templ_7745c5c3_Err
24452728 }
2446- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "</h1><dl class=\"kv show-kv\"><dt>branch</dt><dd><a href=\"")
2729+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 250, "</h1><dl class=\"kv show-kv\"><dt>branch</dt><dd><a href=\"")
24472730 if templ_7745c5c3_Err != nil {
24482731 return templ_7745c5c3_Err
24492732 }
2450- var templ_7745c5c3_Var120 templ.SafeURL
2451- templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.SourceHref))
2733+ var templ_7745c5c3_Var133 templ.SafeURL
2734+ templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.SourceHref))
24522735 if templ_7745c5c3_Err != nil {
2453- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 468, Col: 45}
2736+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 520, Col: 45}
24542737 }
2455- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var120))
2738+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))
24562739 if templ_7745c5c3_Err != nil {
24572740 return templ_7745c5c3_Err
24582741 }
2459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "\">")
2742+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 251, "\">")
24602743 if templ_7745c5c3_Err != nil {
24612744 return templ_7745c5c3_Err
24622745 }
2463- var templ_7745c5c3_Var121 string
2464- templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
2746+ var templ_7745c5c3_Var134 string
2747+ templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
24652748 if templ_7745c5c3_Err != nil {
2466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 468, Col: 58}
2749+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 520, Col: 58}
24672750 }
2468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))
2751+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))
24692752 if templ_7745c5c3_Err != nil {
24702753 return templ_7745c5c3_Err
24712754 }
2472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "</a></dd><dt>target</dt><dd><a href=\"")
2755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 252, "</a></dd><dt>target</dt><dd><a href=\"")
24732756 if templ_7745c5c3_Err != nil {
24742757 return templ_7745c5c3_Err
24752758 }
2476- var templ_7745c5c3_Var122 templ.SafeURL
2477- templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.TargetHref))
2759+ var templ_7745c5c3_Var135 templ.SafeURL
2760+ templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.TargetHref))
24782761 if templ_7745c5c3_Err != nil {
2479- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 470, Col: 45}
2762+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 522, Col: 45}
24802763 }
2481- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))
2764+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135))
24822765 if templ_7745c5c3_Err != nil {
24832766 return templ_7745c5c3_Err
24842767 }
2485- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "\">")
2768+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 253, "\">")
24862769 if templ_7745c5c3_Err != nil {
24872770 return templ_7745c5c3_Err
24882771 }
2489- var templ_7745c5c3_Var123 string
2490- templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2772+ var templ_7745c5c3_Var136 string
2773+ templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
24912774 if templ_7745c5c3_Err != nil {
2492- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 470, Col: 58}
2775+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 522, Col: 58}
24932776 }
2494- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))
2777+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136))
24952778 if templ_7745c5c3_Err != nil {
24962779 return templ_7745c5c3_Err
24972780 }
2498- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "</a></dd><dt>ahead</dt><dd>")
2781+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 254, "</a></dd><dt>ahead</dt><dd>")
24992782 if templ_7745c5c3_Err != nil {
25002783 return templ_7745c5c3_Err
25012784 }
2502- var templ_7745c5c3_Var124 string
2503- templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(p.AheadText)
2785+ var templ_7745c5c3_Var137 string
2786+ templ_7745c5c3_Var137, templ_7745c5c3_Err = templ.JoinStringErrs(p.AheadText)
25042787 if templ_7745c5c3_Err != nil {
2505- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 472, Col: 21}
2788+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 524, Col: 21}
25062789 }
2507- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))
2790+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var137))
25082791 if templ_7745c5c3_Err != nil {
25092792 return templ_7745c5c3_Err
25102793 }
2511- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "</dd></dl></header>")
2794+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 255, "</dd></dl></header>")
25122795 if templ_7745c5c3_Err != nil {
25132796 return templ_7745c5c3_Err
25142797 }
25152798 if p.Note != "" {
2516- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 224, "<p class=\"notice\">")
2799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 256, "<p class=\"notice\">")
25172800 if templ_7745c5c3_Err != nil {
25182801 return templ_7745c5c3_Err
25192802 }
2520- var templ_7745c5c3_Var125 string
2521- templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
2803+ var templ_7745c5c3_Var138 string
2804+ templ_7745c5c3_Var138, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
25222805 if templ_7745c5c3_Err != nil {
2523- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 476, Col: 29}
2806+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 528, Col: 29}
25242807 }
2525- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var125))
2808+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var138))
25262809 if templ_7745c5c3_Err != nil {
25272810 return templ_7745c5c3_Err
25282811 }
2529- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 225, "</p>")
2812+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 257, "</p>")
25302813 if templ_7745c5c3_Err != nil {
25312814 return templ_7745c5c3_Err
25322815 }
25332816 }
2534- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 226, " ")
2817+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 258, " ")
25352818 if templ_7745c5c3_Err != nil {
25362819 return templ_7745c5c3_Err
25372820 }
25382821 if p.Existing != nil {
2539- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 227, "<p class=\"notice\">an open merge request already covers these branches: <a href=\"")
2822+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 259, "<p class=\"notice\">an open merge request already covers these branches: <a href=\"")
25402823 if templ_7745c5c3_Err != nil {
25412824 return templ_7745c5c3_Err
25422825 }
2543- var templ_7745c5c3_Var126 templ.SafeURL
2544- templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Existing.Href))
2826+ var templ_7745c5c3_Var139 templ.SafeURL
2827+ templ_7745c5c3_Var139, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Existing.Href))
25452828 if templ_7745c5c3_Err != nil {
2546- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 479, Col: 114}
2829+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 531, Col: 114}
25472830 }
2548- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126))
2831+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var139))
25492832 if templ_7745c5c3_Err != nil {
25502833 return templ_7745c5c3_Err
25512834 }
2552- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 228, "\">")
2835+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 260, "\">")
25532836 if templ_7745c5c3_Err != nil {
25542837 return templ_7745c5c3_Err
25552838 }
2556- var templ_7745c5c3_Var127 string
2557- templ_7745c5c3_Var127, templ_7745c5c3_Err = templ.JoinStringErrs(p.Existing.Label)
2839+ var templ_7745c5c3_Var140 string
2840+ templ_7745c5c3_Var140, templ_7745c5c3_Err = templ.JoinStringErrs(p.Existing.Label)
25582841 if templ_7745c5c3_Err != nil {
2559- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 479, Col: 135}
2842+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 531, Col: 135}
25602843 }
2561- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var127))
2844+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var140))
25622845 if templ_7745c5c3_Err != nil {
25632846 return templ_7745c5c3_Err
25642847 }
2565- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 229, "</a></p>")
2848+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 261, "</a></p>")
25662849 if templ_7745c5c3_Err != nil {
25672850 return templ_7745c5c3_Err
25682851 }
25692852 }
2570- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 230, " ")
2853+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 262, " ")
25712854 if templ_7745c5c3_Err != nil {
25722855 return templ_7745c5c3_Err
25732856 }
25742857 if p.Create != nil {
2575- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 231, "<form class=\"cform create\" method=\"post\" action=\"")
2858+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 263, "<form class=\"cform create\" method=\"post\" action=\"")
25762859 if templ_7745c5c3_Err != nil {
25772860 return templ_7745c5c3_Err
25782861 }
2579- var templ_7745c5c3_Var128 templ.SafeURL
2580- templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Create.Action))
2862+ var templ_7745c5c3_Var141 templ.SafeURL
2863+ templ_7745c5c3_Var141, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Create.Action))
25812864 if templ_7745c5c3_Err != nil {
2582- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 482, Col: 83}
2865+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 534, Col: 83}
25832866 }
2584- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128))
2867+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var141))
25852868 if templ_7745c5c3_Err != nil {
25862869 return templ_7745c5c3_Err
25872870 }
2588- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 232, "\"><input type=\"hidden\" name=\"source\" value=\"")
2871+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 264, "\"><input type=\"hidden\" name=\"source\" value=\"")
25892872 if templ_7745c5c3_Err != nil {
25902873 return templ_7745c5c3_Err
25912874 }
2592- var templ_7745c5c3_Var129 string
2593- templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Source)
2875+ var templ_7745c5c3_Var142 string
2876+ templ_7745c5c3_Var142, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Source)
25942877 if templ_7745c5c3_Err != nil {
2595- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 483, Col: 62}
2878+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 535, Col: 62}
25962879 }
2597- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var129)
2880+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var142)
25982881 if templ_7745c5c3_Err != nil {
25992882 return templ_7745c5c3_Err
26002883 }
2601- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 233, "\"> <input type=\"hidden\" name=\"target\" value=\"")
2884+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 265, "\"> <input type=\"hidden\" name=\"target\" value=\"")
26022885 if templ_7745c5c3_Err != nil {
26032886 return templ_7745c5c3_Err
26042887 }
2605- var templ_7745c5c3_Var130 string
2606- templ_7745c5c3_Var130, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Target)
2888+ var templ_7745c5c3_Var143 string
2889+ templ_7745c5c3_Var143, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Target)
26072890 if templ_7745c5c3_Err != nil {
2608- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 484, Col: 62}
2891+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 536, Col: 62}
26092892 }
2610- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var130)
2893+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var143)
26112894 if templ_7745c5c3_Err != nil {
26122895 return templ_7745c5c3_Err
26132896 }
2614- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 234, "\"> <label for=\"mr-title\">title</label> <input id=\"mr-title\" name=\"title\" type=\"text\" maxlength=\"120\" required value=\"")
2897+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 266, "\"> <label for=\"mr-title\">title</label> <input id=\"mr-title\" name=\"title\" type=\"text\" maxlength=\"120\" required value=\"")
26152898 if templ_7745c5c3_Err != nil {
26162899 return templ_7745c5c3_Err
26172900 }
2618- var templ_7745c5c3_Var131 string
2619- templ_7745c5c3_Var131, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Title)
2901+ var templ_7745c5c3_Var144 string
2902+ templ_7745c5c3_Var144, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Title)
26202903 if templ_7745c5c3_Err != nil {
2621- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 486, Col: 97}
2904+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 97}
26222905 }
2623- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var131)
2906+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var144)
26242907 if templ_7745c5c3_Err != nil {
26252908 return templ_7745c5c3_Err
26262909 }
2627- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 235, "\" autocomplete=\"off\"> <label for=\"mr-desc\">description</label> <textarea id=\"mr-desc\" name=\"description\" rows=\"4\" maxlength=\"8000\" placeholder=\"Markdown, optional\"></textarea><div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Create merge request</button></div></form>")
2910+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 267, "\" autocomplete=\"off\"> <label for=\"mr-desc\">description</label> <textarea id=\"mr-desc\" name=\"description\" rows=\"4\" maxlength=\"8000\" placeholder=\"Markdown, optional\"></textarea><div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Create merge request</button></div></form>")
26282911 if templ_7745c5c3_Err != nil {
26292912 return templ_7745c5c3_Err
26302913 }
26312914 }
2632- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 236, " ")
2915+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 268, " ")
26332916 if templ_7745c5c3_Err != nil {
26342917 return templ_7745c5c3_Err
26352918 }
26362919 if p.HasDiff {
2637- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 237, "<div class=\"diff-tools\" role=\"group\" aria-label=\"Diff view\"><span class=\"dim\">")
2920+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 269, "<div class=\"diff-tools\" role=\"group\" aria-label=\"Diff view\"><span class=\"dim\">")
26382921 if templ_7745c5c3_Err != nil {
26392922 return templ_7745c5c3_Err
26402923 }
2641- var templ_7745c5c3_Var132 string
2642- templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))
2924+ var templ_7745c5c3_Var145 string
2925+ templ_7745c5c3_Var145, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))
26432926 if templ_7745c5c3_Err != nil {
2644- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 57}
2927+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 548, Col: 57}
26452928 }
2646- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))
2929+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var145))
26472930 if templ_7745c5c3_Err != nil {
26482931 return templ_7745c5c3_Err
26492932 }
2650- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 238, " · <span class=\"plus\">+")
2933+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 270, " · <span class=\"plus\">+")
26512934 if templ_7745c5c3_Err != nil {
26522935 return templ_7745c5c3_Err
26532936 }
2654- var templ_7745c5c3_Var133 string
2655- templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))
2937+ var templ_7745c5c3_Var146 string
2938+ templ_7745c5c3_Var146, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))
26562939 if templ_7745c5c3_Err != nil {
2657- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 97}
2940+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 548, Col: 97}
26582941 }
2659- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))
2942+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var146))
26602943 if templ_7745c5c3_Err != nil {
26612944 return templ_7745c5c3_Err
26622945 }
2663- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 239, "</span> <span class=\"minus\">-")
2946+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 271, "</span> <span class=\"minus\">-")
26642947 if templ_7745c5c3_Err != nil {
26652948 return templ_7745c5c3_Err
26662949 }
2667- var templ_7745c5c3_Var134 string
2668- templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))
2950+ var templ_7745c5c3_Var147 string
2951+ templ_7745c5c3_Var147, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))
26692952 if templ_7745c5c3_Err != nil {
2670- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 142}
2953+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 548, Col: 142}
26712954 }
2672- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))
2955+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var147))
26732956 if templ_7745c5c3_Err != nil {
26742957 return templ_7745c5c3_Err
26752958 }
2676- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 240, "</span></span> <span class=\"grow\"></span>")
2959+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 272, "</span></span> <span class=\"grow\"></span>")
26772960 if templ_7745c5c3_Err != nil {
26782961 return templ_7745c5c3_Err
26792962 }
@@ -2689,64 +2972,64 @@ func Compare(l Layout, p ComparePage) templ.Component {
26892972 if templ_7745c5c3_Err != nil {
26902973 return templ_7745c5c3_Err
26912974 }
2692- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 241, "</div>")
2975+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 273, "</div>")
26932976 if templ_7745c5c3_Err != nil {
26942977 return templ_7745c5c3_Err
26952978 }
26962979 if p.Invisible {
2697- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 242, "<p class=\"notice\">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class=\"inv\">&lt;U+XXXX&gt;</span> markers, never applied.</p>")
2980+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 274, "<p class=\"notice\">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class=\"inv\">&lt;U+XXXX&gt;</span> markers, never applied.</p>")
26982981 if templ_7745c5c3_Err != nil {
26992982 return templ_7745c5c3_Err
27002983 }
27012984 }
2702- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 243, " <div id=\"diffs\"")
2985+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 275, " <div id=\"diffs\"")
27032986 if templ_7745c5c3_Err != nil {
27042987 return templ_7745c5c3_Err
27052988 }
27062989 if p.Both {
2707- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 244, " data-diff=\"")
2990+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 276, " data-diff=\"")
27082991 if templ_7745c5c3_Err != nil {
27092992 return templ_7745c5c3_Err
27102993 }
2711- var templ_7745c5c3_Var135 string
2712- templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)
2994+ var templ_7745c5c3_Var148 string
2995+ templ_7745c5c3_Var148, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)
27132996 if templ_7745c5c3_Err != nil {
2714- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 511, Col: 23}
2997+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 563, Col: 23}
27152998 }
2716- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var135)
2999+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var148)
27173000 if templ_7745c5c3_Err != nil {
27183001 return templ_7745c5c3_Err
27193002 }
2720- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 245, "\"")
3003+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 277, "\"")
27213004 if templ_7745c5c3_Err != nil {
27223005 return templ_7745c5c3_Err
27233006 }
27243007 } else {
2725- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 246, " data-view=\"")
3008+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 278, " data-view=\"")
27263009 if templ_7745c5c3_Err != nil {
27273010 return templ_7745c5c3_Err
27283011 }
2729- var templ_7745c5c3_Var136 string
2730- templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)
3012+ var templ_7745c5c3_Var149 string
3013+ templ_7745c5c3_Var149, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)
27313014 if templ_7745c5c3_Err != nil {
2732- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 513, Col: 23}
3015+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 565, Col: 23}
27333016 }
2734- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var136)
3017+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var149)
27353018 if templ_7745c5c3_Err != nil {
27363019 return templ_7745c5c3_Err
27373020 }
2738- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 247, "\"")
3021+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 279, "\"")
27393022 if templ_7745c5c3_Err != nil {
27403023 return templ_7745c5c3_Err
27413024 }
27423025 }
27433026 if p.Both && !p.Pinned {
2744- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 248, " hx-live=\"this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff\"")
3027+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 280, " hx-live=\"this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff\"")
27453028 if templ_7745c5c3_Err != nil {
27463029 return templ_7745c5c3_Err
27473030 }
27483031 }
2749- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 249, ">")
3032+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 281, ">")
27503033 if templ_7745c5c3_Err != nil {
27513034 return templ_7745c5c3_Err
27523035 }
@@ -2756,14 +3039,14 @@ func Compare(l Layout, p ComparePage) templ.Component {
27563039 return templ_7745c5c3_Err
27573040 }
27583041 }
2759- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 250, "</div>")
3042+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 282, "</div>")
27603043 if templ_7745c5c3_Err != nil {
27613044 return templ_7745c5c3_Err
27623045 }
27633046 }
27643047 return nil
27653048 })
2766- templ_7745c5c3_Err = Shell(l, compareRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var117), templ_7745c5c3_Buffer)
3049+ templ_7745c5c3_Err = Shell(l, compareRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var130), templ_7745c5c3_Buffer)
27673050 if templ_7745c5c3_Err != nil {
27683051 return templ_7745c5c3_Err
27693052 }
@@ -2787,173 +3070,173 @@ func compareRail(p ComparePage) templ.Component {
27873070 }()
27883071 }
27893072 ctx = templ.InitializeContext(ctx)
2790- templ_7745c5c3_Var137 := templ.GetChildren(ctx)
2791- if templ_7745c5c3_Var137 == nil {
2792- templ_7745c5c3_Var137 = templ.NopComponent
3073+ templ_7745c5c3_Var150 := templ.GetChildren(ctx)
3074+ if templ_7745c5c3_Var150 == nil {
3075+ templ_7745c5c3_Var150 = templ.NopComponent
27933076 }
27943077 ctx = templ.ClearChildren(ctx)
2795- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 251, "<section aria-labelledby=\"r-ahead\"><h2 id=\"r-ahead\">Commits <span class=\"count\">")
3078+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 283, "<section aria-labelledby=\"r-ahead\"><h2 id=\"r-ahead\">Commits <span class=\"count\">")
27963079 if templ_7745c5c3_Err != nil {
27973080 return templ_7745c5c3_Err
27983081 }
2799- var templ_7745c5c3_Var138 string
2800- templ_7745c5c3_Var138, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Ahead)))
3082+ var templ_7745c5c3_Var151 string
3083+ templ_7745c5c3_Var151, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Ahead)))
28013084 if templ_7745c5c3_Err != nil {
2802- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 529, Col: 67}
3085+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 581, Col: 67}
28033086 }
2804- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var138))
3087+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var151))
28053088 if templ_7745c5c3_Err != nil {
28063089 return templ_7745c5c3_Err
28073090 }
2808- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 252, "</span></h2><ul class=\"pcommits\">")
3091+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 284, "</span></h2><ul class=\"pcommits\">")
28093092 if templ_7745c5c3_Err != nil {
28103093 return templ_7745c5c3_Err
28113094 }
28123095 for _, c := range p.Ahead {
2813- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 253, "<li><a href=\"")
3096+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 285, "<li><a href=\"")
28143097 if templ_7745c5c3_Err != nil {
28153098 return templ_7745c5c3_Err
28163099 }
2817- var templ_7745c5c3_Var139 templ.SafeURL
2818- templ_7745c5c3_Var139, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
3100+ var templ_7745c5c3_Var152 templ.SafeURL
3101+ templ_7745c5c3_Var152, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
28193102 if templ_7745c5c3_Err != nil {
2820- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 533, Col: 37}
3103+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 585, Col: 37}
28213104 }
2822- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var139))
3105+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var152))
28233106 if templ_7745c5c3_Err != nil {
28243107 return templ_7745c5c3_Err
28253108 }
2826- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 254, "\">")
3109+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 286, "\">")
28273110 if templ_7745c5c3_Err != nil {
28283111 return templ_7745c5c3_Err
28293112 }
2830- var templ_7745c5c3_Var140 string
2831- templ_7745c5c3_Var140, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
3113+ var templ_7745c5c3_Var153 string
3114+ templ_7745c5c3_Var153, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
28323115 if templ_7745c5c3_Err != nil {
2833- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 533, Col: 49}
3116+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 585, Col: 49}
28343117 }
2835- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var140))
3118+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var153))
28363119 if templ_7745c5c3_Err != nil {
28373120 return templ_7745c5c3_Err
28383121 }
2839- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 255, "</a> <span class=\"s\">")
3122+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 287, "</a> <span class=\"s\">")
28403123 if templ_7745c5c3_Err != nil {
28413124 return templ_7745c5c3_Err
28423125 }
2843- var templ_7745c5c3_Var141 string
2844- templ_7745c5c3_Var141, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
3126+ var templ_7745c5c3_Var154 string
3127+ templ_7745c5c3_Var154, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
28453128 if templ_7745c5c3_Err != nil {
2846- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 534, Col: 32}
3129+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 586, Col: 32}
28473130 }
2848- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var141))
3131+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var154))
28493132 if templ_7745c5c3_Err != nil {
28503133 return templ_7745c5c3_Err
28513134 }
2852- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 256, "</span> <span class=\"mute\">")
3135+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 288, "</span> <span class=\"mute\">")
28533136 if templ_7745c5c3_Err != nil {
28543137 return templ_7745c5c3_Err
28553138 }
2856- var templ_7745c5c3_Var142 string
2857- templ_7745c5c3_Var142, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
3139+ var templ_7745c5c3_Var155 string
3140+ templ_7745c5c3_Var155, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
28583141 if templ_7745c5c3_Err != nil {
2859- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 536, Col: 16}
3142+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 588, Col: 16}
28603143 }
2861- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var142))
3144+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var155))
28623145 if templ_7745c5c3_Err != nil {
28633146 return templ_7745c5c3_Err
28643147 }
2865- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 257, " ")
3148+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 289, " ")
28663149 if templ_7745c5c3_Err != nil {
28673150 return templ_7745c5c3_Err
28683151 }
28693152 if c.WhenAt != "" {
2870- var templ_7745c5c3_Var143 string
2871- templ_7745c5c3_Var143, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
3153+ var templ_7745c5c3_Var156 string
3154+ templ_7745c5c3_Var156, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
28723155 if templ_7745c5c3_Err != nil {
2873- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 15}
3156+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 590, Col: 15}
28743157 }
2875- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var143))
3158+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var156))
28763159 if templ_7745c5c3_Err != nil {
28773160 return templ_7745c5c3_Err
28783161 }
2879- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 258, "<time datetime=\"")
3162+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 290, "<time datetime=\"")
28803163 if templ_7745c5c3_Err != nil {
28813164 return templ_7745c5c3_Err
28823165 }
2883- var templ_7745c5c3_Var144 string
2884- templ_7745c5c3_Var144, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
3166+ var templ_7745c5c3_Var157 string
3167+ templ_7745c5c3_Var157, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
28853168 if templ_7745c5c3_Err != nil {
2886- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 42}
3169+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 590, Col: 42}
28873170 }
2888- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var144)
3171+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var157)
28893172 if templ_7745c5c3_Err != nil {
28903173 return templ_7745c5c3_Err
28913174 }
2892- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 259, "\">")
3175+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 291, "\">")
28933176 if templ_7745c5c3_Err != nil {
28943177 return templ_7745c5c3_Err
28953178 }
2896- var templ_7745c5c3_Var145 string
2897- templ_7745c5c3_Var145, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
3179+ var templ_7745c5c3_Var158 string
3180+ templ_7745c5c3_Var158, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
28983181 if templ_7745c5c3_Err != nil {
2899- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 53}
3182+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 590, Col: 53}
29003183 }
2901- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var145))
3184+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var158))
29023185 if templ_7745c5c3_Err != nil {
29033186 return templ_7745c5c3_Err
29043187 }
2905- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 260, "</time>")
3188+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 292, "</time>")
29063189 if templ_7745c5c3_Err != nil {
29073190 return templ_7745c5c3_Err
29083191 }
29093192 }
2910- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 261, "</span></li>")
3193+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 293, "</span></li>")
29113194 if templ_7745c5c3_Err != nil {
29123195 return templ_7745c5c3_Err
29133196 }
29143197 }
2915- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 262, "</ul>")
3198+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 294, "</ul>")
29163199 if templ_7745c5c3_Err != nil {
29173200 return templ_7745c5c3_Err
29183201 }
29193202 if len(p.Ahead) == 0 {
2920- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 263, "<p class=\"dim\">no commit ahead of ")
3203+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 295, "<p class=\"dim\">no commit ahead of ")
29213204 if templ_7745c5c3_Err != nil {
29223205 return templ_7745c5c3_Err
29233206 }
2924- var templ_7745c5c3_Var146 string
2925- templ_7745c5c3_Var146, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
3207+ var templ_7745c5c3_Var159 string
3208+ templ_7745c5c3_Var159, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
29263209 if templ_7745c5c3_Err != nil {
2927- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 545, Col: 47}
3210+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 597, Col: 47}
29283211 }
2929- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var146))
3212+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var159))
29303213 if templ_7745c5c3_Err != nil {
29313214 return templ_7745c5c3_Err
29323215 }
2933- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 264, ".</p>")
3216+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 296, ".</p>")
29343217 if templ_7745c5c3_Err != nil {
29353218 return templ_7745c5c3_Err
29363219 }
29373220 }
2938- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 265, "</section>")
3221+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 297, "</section>")
29393222 if templ_7745c5c3_Err != nil {
29403223 return templ_7745c5c3_Err
29413224 }
29423225 if p.HasDiff {
2943- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 266, "<section aria-labelledby=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")
3226+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 298, "<section aria-labelledby=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")
29443227 if templ_7745c5c3_Err != nil {
29453228 return templ_7745c5c3_Err
29463229 }
2947- var templ_7745c5c3_Var147 string
2948- templ_7745c5c3_Var147, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))
3230+ var templ_7745c5c3_Var160 string
3231+ templ_7745c5c3_Var160, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))
29493232 if templ_7745c5c3_Err != nil {
2950- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 550, Col: 66}
3233+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 602, Col: 66}
29513234 }
2952- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var147))
3235+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var160))
29533236 if templ_7745c5c3_Err != nil {
29543237 return templ_7745c5c3_Err
29553238 }
2956- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 267, "</span></h2>")
3239+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 299, "</span></h2>")
29573240 if templ_7745c5c3_Err != nil {
29583241 return templ_7745c5c3_Err
29593242 }
@@ -2961,12 +3244,12 @@ func compareRail(p ComparePage) templ.Component {
29613244 if templ_7745c5c3_Err != nil {
29623245 return templ_7745c5c3_Err
29633246 }
2964- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 268, "</section>")
3247+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 300, "</section>")
29653248 if templ_7745c5c3_Err != nil {
29663249 return templ_7745c5c3_Err
29673250 }
29683251 }
2969- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 269, "<section aria-labelledby=\"r-cli\"><h2 id=\"r-cli\">From a terminal</h2>")
3252+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 301, "<section aria-labelledby=\"r-cli\"><h2 id=\"r-cli\">From a terminal</h2>")
29703253 if templ_7745c5c3_Err != nil {
29713254 return templ_7745c5c3_Err
29723255 }
@@ -2974,7 +3257,7 @@ func compareRail(p ComparePage) templ.Component {
29743257 if templ_7745c5c3_Err != nil {
29753258 return templ_7745c5c3_Err
29763259 }
2977- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 270, "</section>")
3260+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 302, "</section>")
29783261 if templ_7745c5c3_Err != nil {
29793262 return templ_7745c5c3_Err
29803263 }
@@ -1215,8 +1215,7 @@ func lineNotes(notes []CommentView, form *CommentForm, suffix string) templ.Comp
1215 }1215 }
1216 1216
1217 // commentView is a note: a ruled block, under its line in the diff or in the1217 // commentView is a note: a ruled block, under its line in the diff or in the
1218-// list of comments. The text is plain: escaped, its line breaks kept, its1218+// list of comments. Body comes only from the safe Markdown renderer.
1219-// http(s) addresses linked.
1220 func commentView(c CommentView, suffix string) templ.Component {1219 func commentView(c CommentView, suffix string) templ.Component {
1221 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {1220 return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
1222 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context1221 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -1245,7 +1244,7 @@ func commentView(c CommentView, suffix string) templ.Component {
1245 var templ_7745c5c3_Var59 string1244 var templ_7745c5c3_Var59 string
1246 templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue("c" + c.ID + suffix)1245 templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.ResolveAttributeValue("c" + c.ID + suffix)
1247 if templ_7745c5c3_Err != nil {1246 if templ_7745c5c3_Err != nil {
1248- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 235, Col: 42}1247+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 234, Col: 42}
1249 }1248 }
1250 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)1249 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var59)
1251 if templ_7745c5c3_Err != nil {1250 if templ_7745c5c3_Err != nil {
@@ -1258,26 +1257,36 @@ func commentView(c CommentView, suffix string) templ.Component {
1258 var templ_7745c5c3_Var60 string1257 var templ_7745c5c3_Var60 string
1259 templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolText(c.Resolved))1258 templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolText(c.Resolved))
1260 if templ_7745c5c3_Err != nil {1259 if templ_7745c5c3_Err != nil {
1261- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 235, Col: 81}1260+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 234, Col: 81}
1262 }1261 }
1263 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60)1262 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60)
1264 if templ_7745c5c3_Err != nil {1263 if templ_7745c5c3_Err != nil {
1265 return templ_7745c5c3_Err1264 return templ_7745c5c3_Err
1266 }1265 }
1267- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\"><p class=\"c-head\"><span class=\"c-who\">")1266+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "\"")
1267+ if templ_7745c5c3_Err != nil {
1268+ return templ_7745c5c3_Err
1269+ }
1270+ if c.Reply != nil {
1271+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, " data-thread")
1272+ if templ_7745c5c3_Err != nil {
1273+ return templ_7745c5c3_Err
1274+ }
1275+ }
1276+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "><p class=\"c-head\"><span class=\"c-who\">")
1268 if templ_7745c5c3_Err != nil {1277 if templ_7745c5c3_Err != nil {
1269 return templ_7745c5c3_Err1278 return templ_7745c5c3_Err
1270 }1279 }
1271 var templ_7745c5c3_Var61 string1280 var templ_7745c5c3_Var61 string
1272 templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)1281 templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
1273 if templ_7745c5c3_Err != nil {1282 if templ_7745c5c3_Err != nil {
1274- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 237, Col: 33}1283+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 236, Col: 33}
1275 }1284 }
1276 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))1285 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
1277 if templ_7745c5c3_Err != nil {1286 if templ_7745c5c3_Err != nil {
1278 return templ_7745c5c3_Err1287 return templ_7745c5c3_Err
1279 }1288 }
1280- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</span> ")1289+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</span> ")
1281 if templ_7745c5c3_Err != nil {1290 if templ_7745c5c3_Err != nil {
1282 return templ_7745c5c3_Err1291 return templ_7745c5c3_Err
1283 }1292 }
@@ -1285,52 +1294,52 @@ func commentView(c CommentView, suffix string) templ.Component {
1285 var templ_7745c5c3_Var62 string1294 var templ_7745c5c3_Var62 string
1286 templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(" ")1295 templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
1287 if templ_7745c5c3_Err != nil {1296 if templ_7745c5c3_Err != nil {
1288- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 9}1297+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 9}
1289 }1298 }
1290 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))1299 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
1291 if templ_7745c5c3_Err != nil {1300 if templ_7745c5c3_Err != nil {
1292 return templ_7745c5c3_Err1301 return templ_7745c5c3_Err
1293 }1302 }
1294- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "<time class=\"mute\" datetime=\"")1303+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "<time class=\"mute\" datetime=\"")
1295 if templ_7745c5c3_Err != nil {1304 if templ_7745c5c3_Err != nil {
1296 return templ_7745c5c3_Err1305 return templ_7745c5c3_Err
1297 }1306 }
1298 var templ_7745c5c3_Var63 string1307 var templ_7745c5c3_Var63 string
1299 templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)1308 templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
1300 if templ_7745c5c3_Err != nil {1309 if templ_7745c5c3_Err != nil {
1301- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 49}1310+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 49}
1302 }1311 }
1303 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63)1312 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var63)
1304 if templ_7745c5c3_Err != nil {1313 if templ_7745c5c3_Err != nil {
1305 return templ_7745c5c3_Err1314 return templ_7745c5c3_Err
1306 }1315 }
1307- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "\" title=\"")1316+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "\" title=\"")
1308 if templ_7745c5c3_Err != nil {1317 if templ_7745c5c3_Err != nil {
1309 return templ_7745c5c3_Err1318 return templ_7745c5c3_Err
1310 }1319 }
1311 var templ_7745c5c3_Var64 string1320 var templ_7745c5c3_Var64 string
1312 templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Abs)1321 templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Abs)
1313 if templ_7745c5c3_Err != nil {1322 if templ_7745c5c3_Err != nil {
1314- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 65}1323+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 65}
1315 }1324 }
1316 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)1325 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var64)
1317 if templ_7745c5c3_Err != nil {1326 if templ_7745c5c3_Err != nil {
1318 return templ_7745c5c3_Err1327 return templ_7745c5c3_Err
1319 }1328 }
1320- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "\">")1329+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "\">")
1321 if templ_7745c5c3_Err != nil {1330 if templ_7745c5c3_Err != nil {
1322 return templ_7745c5c3_Err1331 return templ_7745c5c3_Err
1323 }1332 }
1324 var templ_7745c5c3_Var65 string1333 var templ_7745c5c3_Var65 string
1325 templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)1334 templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
1326 if templ_7745c5c3_Err != nil {1335 if templ_7745c5c3_Err != nil {
1327- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 239, Col: 76}1336+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 238, Col: 76}
1328 }1337 }
1329 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))1338 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
1330 if templ_7745c5c3_Err != nil {1339 if templ_7745c5c3_Err != nil {
1331 return templ_7745c5c3_Err1340 return templ_7745c5c3_Err
1332 }1341 }
1333- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "</time> ")1342+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</time> ")
1334 if templ_7745c5c3_Err != nil {1343 if templ_7745c5c3_Err != nil {
1335 return templ_7745c5c3_Err1344 return templ_7745c5c3_Err
1336 }1345 }
@@ -1339,13 +1348,13 @@ func commentView(c CommentView, suffix string) templ.Component {
1339 var templ_7745c5c3_Var66 string1348 var templ_7745c5c3_Var66 string
1340 templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(" ")1349 templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
1341 if templ_7745c5c3_Err != nil {1350 if templ_7745c5c3_Err != nil {
1342- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 242, Col: 9}1351+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 241, Col: 9}
1343 }1352 }
1344 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))1353 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
1345 if templ_7745c5c3_Err != nil {1354 if templ_7745c5c3_Err != nil {
1346 return templ_7745c5c3_Err1355 return templ_7745c5c3_Err
1347 }1356 }
1348- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "<span class=\"c-state\">resolved</span> ")1357+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<span class=\"c-state\">resolved</span> ")
1349 if templ_7745c5c3_Err != nil {1358 if templ_7745c5c3_Err != nil {
1350 return templ_7745c5c3_Err1359 return templ_7745c5c3_Err
1351 }1360 }
@@ -1354,13 +1363,13 @@ func commentView(c CommentView, suffix string) templ.Component {
1354 var templ_7745c5c3_Var67 string1363 var templ_7745c5c3_Var67 string
1355 templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(" ")1364 templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
1356 if templ_7745c5c3_Err != nil {1365 if templ_7745c5c3_Err != nil {
1357- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 245, Col: 9}1366+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 244, Col: 9}
1358 }1367 }
1359 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))1368 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
1360 if templ_7745c5c3_Err != nil {1369 if templ_7745c5c3_Err != nil {
1361 return templ_7745c5c3_Err1370 return templ_7745c5c3_Err
1362 }1371 }
1363- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "<span class=\"c-state\" title=\"written on an earlier head; the line may have moved\">outdated</span> ")1372+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "<span class=\"c-state\" title=\"written on an earlier head; the line may have moved\">outdated</span> ")
1364 if templ_7745c5c3_Err != nil {1373 if templ_7745c5c3_Err != nil {
1365 return templ_7745c5c3_Err1374 return templ_7745c5c3_Err
1366 }1375 }
@@ -1369,111 +1378,282 @@ func commentView(c CommentView, suffix string) templ.Component {
1369 var templ_7745c5c3_Var68 string1378 var templ_7745c5c3_Var68 string
1370 templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(" ")1379 templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(" ")
1371 if templ_7745c5c3_Err != nil {1380 if templ_7745c5c3_Err != nil {
1372- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 248, Col: 9}1381+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 247, Col: 9}
1373 }1382 }
1374 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))1383 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
1375 if templ_7745c5c3_Err != nil {1384 if templ_7745c5c3_Err != nil {
1376 return templ_7745c5c3_Err1385 return templ_7745c5c3_Err
1377 }1386 }
1378- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "<span class=\"c-where mute\">")1387+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "<span class=\"c-where mute\">")
1379 if templ_7745c5c3_Err != nil {1388 if templ_7745c5c3_Err != nil {
1380 return templ_7745c5c3_Err1389 return templ_7745c5c3_Err
1381 }1390 }
1382 var templ_7745c5c3_Var69 string1391 var templ_7745c5c3_Var69 string
1383 templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(c.Where)1392 templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(c.Where)
1384 if templ_7745c5c3_Err != nil {1393 if templ_7745c5c3_Err != nil {
1385- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 248, Col: 47}1394+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 247, Col: 47}
1386 }1395 }
1387 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))1396 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
1388 if templ_7745c5c3_Err != nil {1397 if templ_7745c5c3_Err != nil {
1389 return templ_7745c5c3_Err1398 return templ_7745c5c3_Err
1390 }1399 }
1391- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "</span>")1400+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</span>")
1392 if templ_7745c5c3_Err != nil {1401 if templ_7745c5c3_Err != nil {
1393 return templ_7745c5c3_Err1402 return templ_7745c5c3_Err
1394 }1403 }
1395 }1404 }
1396- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "</p><p class=\"c-text\">")1405+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "</p>")
1397 if templ_7745c5c3_Err != nil {1406 if templ_7745c5c3_Err != nil {
1398 return templ_7745c5c3_Err1407 return templ_7745c5c3_Err
1399 }1408 }
1400- templ_7745c5c3_Err = userText(c.Parts).Render(ctx, templ_7745c5c3_Buffer)1409+ if c.Deleted {
1401- if templ_7745c5c3_Err != nil {1410+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "<p class=\"mute\">deleted note</p>")
1402- return templ_7745c5c3_Err1411+ if templ_7745c5c3_Err != nil {
1412+ return templ_7745c5c3_Err
1413+ }
1414+ } else {
1415+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "<div class=\"c-body prose\">")
1416+ if templ_7745c5c3_Err != nil {
1417+ return templ_7745c5c3_Err
1418+ }
1419+ templ_7745c5c3_Err = templ.Raw(string(c.Body)).Render(ctx, templ_7745c5c3_Buffer)
1420+ if templ_7745c5c3_Err != nil {
1421+ return templ_7745c5c3_Err
1422+ }
1423+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "</div>")
1424+ if templ_7745c5c3_Err != nil {
1425+ return templ_7745c5c3_Err
1426+ }
1403 }1427 }
1404- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "</p>")1428+ if c.Edited {
1405- if templ_7745c5c3_Err != nil {1429+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "<span class=\"mute\">edited</span> ")
1406- return templ_7745c5c3_Err1430+ if templ_7745c5c3_Err != nil {
1431+ return templ_7745c5c3_Err
1432+ }
1407 }1433 }
1408- if c.Resolve != nil {1434+ if c.Verdict != "" {
1409- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "<form class=\"c-act\" method=\"post\" action=\"")1435+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<span class=\"c-state\">")
1410 if templ_7745c5c3_Err != nil {1436 if templ_7745c5c3_Err != nil {
1411 return templ_7745c5c3_Err1437 return templ_7745c5c3_Err
1412 }1438 }
1413- var templ_7745c5c3_Var70 templ.SafeURL1439+ var templ_7745c5c3_Var70 string
1414- templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Resolve.Action))1440+ templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(c.Verdict)
1415 if templ_7745c5c3_Err != nil {1441 if templ_7745c5c3_Err != nil {
1416- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 253, Col: 77}1442+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 261, Col: 36}
1417 }1443 }
1418 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))1444 _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
1419 if templ_7745c5c3_Err != nil {1445 if templ_7745c5c3_Err != nil {
1420 return templ_7745c5c3_Err1446 return templ_7745c5c3_Err
1421 }1447 }
1422- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "\"><input type=\"hidden\" name=\"resolved\" value=\"")1448+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "</span> ")
1449+ if templ_7745c5c3_Err != nil {
1450+ return templ_7745c5c3_Err
1451+ }
1452+ }
1453+ if c.Range != "" {
1454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "<span class=\"mute\">")
1423 if templ_7745c5c3_Err != nil {1455 if templ_7745c5c3_Err != nil {
1424 return templ_7745c5c3_Err1456 return templ_7745c5c3_Err
1425 }1457 }
1426 var templ_7745c5c3_Var71 string1458 var templ_7745c5c3_Var71 string
1427- templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolText(c.Resolve.Value))1459+ templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(c.Range)
1460+ if templ_7745c5c3_Err != nil {
1461+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 264, Col: 31}
1462+ }
1463+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71))
1464+ if templ_7745c5c3_Err != nil {
1465+ return templ_7745c5c3_Err
1466+ }
1467+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "</span> ")
1468+ if templ_7745c5c3_Err != nil {
1469+ return templ_7745c5c3_Err
1470+ }
1471+ }
1472+ if c.Quote != "" {
1473+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "<button type=\"button\" class=\"btn\" data-js data-quote=\"")
1474+ if templ_7745c5c3_Err != nil {
1475+ return templ_7745c5c3_Err
1476+ }
1477+ var templ_7745c5c3_Var72 string
1478+ templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Quote)
1479+ if templ_7745c5c3_Err != nil {
1480+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 267, Col: 65}
1481+ }
1482+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)
1428 if templ_7745c5c3_Err != nil {1483 if templ_7745c5c3_Err != nil {
1429- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 254, Col: 74}1484+ return templ_7745c5c3_Err
1485+ }
1486+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "\">Quote</button> ")
1487+ if templ_7745c5c3_Err != nil {
1488+ return templ_7745c5c3_Err
1430 }1489 }
1431- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var71)1490+ }
1491+ if c.Edit != nil {
1492+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "<details><summary>Edit</summary><form method=\"post\" action=\"")
1432 if templ_7745c5c3_Err != nil {1493 if templ_7745c5c3_Err != nil {
1433 return templ_7745c5c3_Err1494 return templ_7745c5c3_Err
1434 }1495 }
1435- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "\"> ")1496+ var templ_7745c5c3_Var73 templ.SafeURL
1497+ templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Edit.Action))
1498+ if templ_7745c5c3_Err != nil {
1499+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 270, Col: 92}
1500+ }
1501+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))
1502+ if templ_7745c5c3_Err != nil {
1503+ return templ_7745c5c3_Err
1504+ }
1505+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "\"><input type=\"hidden\" name=\"from\" value=\"")
1506+ if templ_7745c5c3_Err != nil {
1507+ return templ_7745c5c3_Err
1508+ }
1509+ var templ_7745c5c3_Var74 string
1510+ templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Edit.From))
1511+ if templ_7745c5c3_Err != nil {
1512+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 270, Col: 153}
1513+ }
1514+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var74)
1515+ if templ_7745c5c3_Err != nil {
1516+ return templ_7745c5c3_Err
1517+ }
1518+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "\"><textarea name=\"text\" maxlength=\"8000\">")
1519+ if templ_7745c5c3_Err != nil {
1520+ return templ_7745c5c3_Err
1521+ }
1522+ var templ_7745c5c3_Var75 string
1523+ templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(c.Edit.Text)
1524+ if templ_7745c5c3_Err != nil {
1525+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 270, Col: 209}
1526+ }
1527+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75))
1528+ if templ_7745c5c3_Err != nil {
1529+ return templ_7745c5c3_Err
1530+ }
1531+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "</textarea><button class=\"btn\" type=\"submit\">Save</button></form></details> ")
1532+ if templ_7745c5c3_Err != nil {
1533+ return templ_7745c5c3_Err
1534+ }
1535+ }
1536+ if c.Delete != nil {
1537+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<details><summary>Delete</summary><form method=\"post\" action=\"")
1538+ if templ_7745c5c3_Err != nil {
1539+ return templ_7745c5c3_Err
1540+ }
1541+ var templ_7745c5c3_Var76 templ.SafeURL
1542+ templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Delete.Action))
1543+ if templ_7745c5c3_Err != nil {
1544+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 273, Col: 96}
1545+ }
1546+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76))
1547+ if templ_7745c5c3_Err != nil {
1548+ return templ_7745c5c3_Err
1549+ }
1550+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "\"><input type=\"hidden\" name=\"from\" value=\"")
1551+ if templ_7745c5c3_Err != nil {
1552+ return templ_7745c5c3_Err
1553+ }
1554+ var templ_7745c5c3_Var77 string
1555+ templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Delete.From))
1556+ if templ_7745c5c3_Err != nil {
1557+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 273, Col: 159}
1558+ }
1559+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var77)
1560+ if templ_7745c5c3_Err != nil {
1561+ return templ_7745c5c3_Err
1562+ }
1563+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "\"><p>Delete this note?</p><button class=\"btn\" type=\"submit\">Confirm delete</button></form></details> ")
1564+ if templ_7745c5c3_Err != nil {
1565+ return templ_7745c5c3_Err
1566+ }
1567+ }
1568+ for _, reply := range c.Replies {
1569+ templ_7745c5c3_Err = commentView(reply, suffix).Render(ctx, templ_7745c5c3_Buffer)
1570+ if templ_7745c5c3_Err != nil {
1571+ return templ_7745c5c3_Err
1572+ }
1573+ }
1574+ if c.Reply != nil {
1575+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "<details data-reply><summary>Reply</summary>")
1576+ if templ_7745c5c3_Err != nil {
1577+ return templ_7745c5c3_Err
1578+ }
1579+ templ_7745c5c3_Err = commentForm(*c.Reply, suffix).Render(ctx, templ_7745c5c3_Buffer)
1580+ if templ_7745c5c3_Err != nil {
1581+ return templ_7745c5c3_Err
1582+ }
1583+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "</details> ")
1584+ if templ_7745c5c3_Err != nil {
1585+ return templ_7745c5c3_Err
1586+ }
1587+ }
1588+ if c.Resolve != nil {
1589+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "<form class=\"c-act\" method=\"post\" action=\"")
1590+ if templ_7745c5c3_Err != nil {
1591+ return templ_7745c5c3_Err
1592+ }
1593+ var templ_7745c5c3_Var78 templ.SafeURL
1594+ templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Resolve.Action))
1595+ if templ_7745c5c3_Err != nil {
1596+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 284, Col: 77}
1597+ }
1598+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78))
1599+ if templ_7745c5c3_Err != nil {
1600+ return templ_7745c5c3_Err
1601+ }
1602+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "\"><input type=\"hidden\" name=\"resolved\" value=\"")
1603+ if templ_7745c5c3_Err != nil {
1604+ return templ_7745c5c3_Err
1605+ }
1606+ var templ_7745c5c3_Var79 string
1607+ templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.ResolveAttributeValue(boolText(c.Resolve.Value))
1608+ if templ_7745c5c3_Err != nil {
1609+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 285, Col: 74}
1610+ }
1611+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var79)
1612+ if templ_7745c5c3_Err != nil {
1613+ return templ_7745c5c3_Err
1614+ }
1615+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "\"> ")
1436 if templ_7745c5c3_Err != nil {1616 if templ_7745c5c3_Err != nil {
1437 return templ_7745c5c3_Err1617 return templ_7745c5c3_Err
1438 }1618 }
1439 if c.Resolve.From > 0 {1619 if c.Resolve.From > 0 {
1440- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "<input type=\"hidden\" name=\"from\" value=\"")1620+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "<input type=\"hidden\" name=\"from\" value=\"")
1441 if templ_7745c5c3_Err != nil {1621 if templ_7745c5c3_Err != nil {
1442 return templ_7745c5c3_Err1622 return templ_7745c5c3_Err
1443 }1623 }
1444- var templ_7745c5c3_Var72 string1624+ var templ_7745c5c3_Var80 string
1445- templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Resolve.From))1625+ templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(c.Resolve.From))
1446 if templ_7745c5c3_Err != nil {1626 if templ_7745c5c3_Err != nil {
1447- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 256, Col: 66}1627+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 287, Col: 66}
1448 }1628 }
1449- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var72)1629+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80)
1450 if templ_7745c5c3_Err != nil {1630 if templ_7745c5c3_Err != nil {
1451 return templ_7745c5c3_Err1631 return templ_7745c5c3_Err
1452 }1632 }
1453- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "\"> ")1633+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "\"> ")
1454 if templ_7745c5c3_Err != nil {1634 if templ_7745c5c3_Err != nil {
1455 return templ_7745c5c3_Err1635 return templ_7745c5c3_Err
1456 }1636 }
1457 }1637 }
1458- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "<button type=\"submit\" class=\"btn\">")1638+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<button type=\"submit\" class=\"btn\">")
1459 if templ_7745c5c3_Err != nil {1639 if templ_7745c5c3_Err != nil {
1460 return templ_7745c5c3_Err1640 return templ_7745c5c3_Err
1461 }1641 }
1462- var templ_7745c5c3_Var73 string1642+ var templ_7745c5c3_Var81 string
1463- templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(c.Resolve.Label)1643+ templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinStringErrs(c.Resolve.Label)
1464 if templ_7745c5c3_Err != nil {1644 if templ_7745c5c3_Err != nil {
1465- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 258, Col: 55}1645+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 289, Col: 55}
1466 }1646 }
1467- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))1647+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81))
1468 if templ_7745c5c3_Err != nil {1648 if templ_7745c5c3_Err != nil {
1469 return templ_7745c5c3_Err1649 return templ_7745c5c3_Err
1470 }1650 }
1471- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "</button></form>")1651+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</button></form>")
1472 if templ_7745c5c3_Err != nil {1652 if templ_7745c5c3_Err != nil {
1473 return templ_7745c5c3_Err1653 return templ_7745c5c3_Err
1474 }1654 }
1475 }1655 }
1476- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "</div>")1656+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "</div>")
1477 if templ_7745c5c3_Err != nil {1657 if templ_7745c5c3_Err != nil {
1478 return templ_7745c5c3_Err1658 return templ_7745c5c3_Err
1479 }1659 }
@@ -1505,165 +1685,203 @@ func commentForm(f CommentForm, suffix string) templ.Component {
1505 }()1685 }()
1506 }1686 }
1507 ctx = templ.InitializeContext(ctx)1687 ctx = templ.InitializeContext(ctx)
1508- templ_7745c5c3_Var74 := templ.GetChildren(ctx)1688+ templ_7745c5c3_Var82 := templ.GetChildren(ctx)
1509- if templ_7745c5c3_Var74 == nil {1689+ if templ_7745c5c3_Var82 == nil {
1510- templ_7745c5c3_Var74 = templ.NopComponent1690+ templ_7745c5c3_Var82 = templ.NopComponent
1511 }1691 }
1512 ctx = templ.ClearChildren(ctx)1692 ctx = templ.ClearChildren(ctx)
1513- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "<form class=\"cform\" method=\"post\" action=\"")1693+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "<form class=\"cform\" method=\"post\" action=\"")
1514 if templ_7745c5c3_Err != nil {1694 if templ_7745c5c3_Err != nil {
1515 return templ_7745c5c3_Err1695 return templ_7745c5c3_Err
1516 }1696 }
1517- var templ_7745c5c3_Var75 templ.SafeURL1697+ var templ_7745c5c3_Var83 templ.SafeURL
1518- templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Action))1698+ templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Action))
1519 if templ_7745c5c3_Err != nil {1699 if templ_7745c5c3_Err != nil {
1520- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 273, Col: 67}1700+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 304, Col: 67}
1521 }1701 }
1522- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75))1702+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))
1523 if templ_7745c5c3_Err != nil {1703 if templ_7745c5c3_Err != nil {
1524 return templ_7745c5c3_Err1704 return templ_7745c5c3_Err
1525 }1705 }
1526- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "\"><label for=\"")1706+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "\"><label for=\"")
1527 if templ_7745c5c3_Err != nil {1707 if templ_7745c5c3_Err != nil {
1528 return templ_7745c5c3_Err1708 return templ_7745c5c3_Err
1529 }1709 }
1530- var templ_7745c5c3_Var76 string1710+ var templ_7745c5c3_Var84 string
1531- templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)1711+ templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)
1532 if templ_7745c5c3_Err != nil {1712 if templ_7745c5c3_Err != nil {
1533- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 274, Col: 28}1713+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 305, Col: 28}
1534 }1714 }
1535- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var76)1715+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var84)
1536 if templ_7745c5c3_Err != nil {1716 if templ_7745c5c3_Err != nil {
1537 return templ_7745c5c3_Err1717 return templ_7745c5c3_Err
1538 }1718 }
1539- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "\">")1719+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "\">")
1540 if templ_7745c5c3_Err != nil {1720 if templ_7745c5c3_Err != nil {
1541 return templ_7745c5c3_Err1721 return templ_7745c5c3_Err
1542 }1722 }
1543- var templ_7745c5c3_Var77 string1723+ var templ_7745c5c3_Var85 string
1544- templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(f.Title)1724+ templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(f.Title)
1545 if templ_7745c5c3_Err != nil {1725 if templ_7745c5c3_Err != nil {
1546- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 274, Col: 40}1726+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 305, Col: 40}
1547 }1727 }
1548- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77))1728+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))
1549 if templ_7745c5c3_Err != nil {1729 if templ_7745c5c3_Err != nil {
1550 return templ_7745c5c3_Err1730 return templ_7745c5c3_Err
1551 }1731 }
1552- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</label> <textarea id=\"")1732+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "</label> <textarea id=\"")
1553 if templ_7745c5c3_Err != nil {1733 if templ_7745c5c3_Err != nil {
1554 return templ_7745c5c3_Err1734 return templ_7745c5c3_Err
1555 }1735 }
1556- var templ_7745c5c3_Var78 string1736+ var templ_7745c5c3_Var86 string
1557- templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)1737+ templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ID + suffix)
1558 if templ_7745c5c3_Err != nil {1738 if templ_7745c5c3_Err != nil {
1559- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 276, Col: 21}1739+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 307, Col: 21}
1560 }1740 }
1561- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var78)1741+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var86)
1562 if templ_7745c5c3_Err != nil {1742 if templ_7745c5c3_Err != nil {
1563 return templ_7745c5c3_Err1743 return templ_7745c5c3_Err
1564 }1744 }
1565- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "\" name=\"text\" rows=\"3\" maxlength=\"8000\" required placeholder=\"plain text, 8000 characters at most\"")1745+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "\" name=\"text\" rows=\"3\" maxlength=\"8000\" placeholder=\"Markdown, 8000 characters at most\"")
1566 if templ_7745c5c3_Err != nil {1746 if templ_7745c5c3_Err != nil {
1567 return templ_7745c5c3_Err1747 return templ_7745c5c3_Err
1568 }1748 }
1569 if f.Focus {1749 if f.Focus {
1570- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, " autofocus")1750+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, " autofocus")
1571 if templ_7745c5c3_Err != nil {1751 if templ_7745c5c3_Err != nil {
1572 return templ_7745c5c3_Err1752 return templ_7745c5c3_Err
1573 }1753 }
1574 }1754 }
1575- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "></textarea> ")1755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "></textarea> ")
1576 if templ_7745c5c3_Err != nil {1756 if templ_7745c5c3_Err != nil {
1577 return templ_7745c5c3_Err1757 return templ_7745c5c3_Err
1578 }1758 }
1579 if f.Path != "" {1759 if f.Path != "" {
1580- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "<input type=\"hidden\" name=\"path\" value=\"")1760+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "<input type=\"hidden\" name=\"path\" value=\"")
1581 if templ_7745c5c3_Err != nil {1761 if templ_7745c5c3_Err != nil {
1582 return templ_7745c5c3_Err1762 return templ_7745c5c3_Err
1583 }1763 }
1584- var templ_7745c5c3_Var79 string1764+ var templ_7745c5c3_Var87 string
1585- templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Path)1765+ templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Path)
1586 if templ_7745c5c3_Err != nil {1766 if templ_7745c5c3_Err != nil {
1587- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 287, Col: 50}1767+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 317, Col: 50}
1588 }1768 }
1589- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var79)1769+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var87)
1590 if templ_7745c5c3_Err != nil {1770 if templ_7745c5c3_Err != nil {
1591 return templ_7745c5c3_Err1771 return templ_7745c5c3_Err
1592 }1772 }
1593- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "\"> <input type=\"hidden\" name=\"line\" value=\"")1773+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "\"> <input type=\"hidden\" name=\"line\" value=\"")
1594 if templ_7745c5c3_Err != nil {1774 if templ_7745c5c3_Err != nil {
1595 return templ_7745c5c3_Err1775 return templ_7745c5c3_Err
1596 }1776 }
1597- var templ_7745c5c3_Var80 string1777+ var templ_7745c5c3_Var88 string
1598- templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Line)1778+ templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Line)
1599 if templ_7745c5c3_Err != nil {1779 if templ_7745c5c3_Err != nil {
1600- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 288, Col: 50}1780+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 318, Col: 50}
1601 }1781 }
1602- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80)1782+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var88)
1603 if templ_7745c5c3_Err != nil {1783 if templ_7745c5c3_Err != nil {
1604 return templ_7745c5c3_Err1784 return templ_7745c5c3_Err
1605 }1785 }
1606- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "\"> <input type=\"hidden\" name=\"side\" value=\"")1786+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "\"> <input type=\"hidden\" name=\"side\" value=\"")
1607 if templ_7745c5c3_Err != nil {1787 if templ_7745c5c3_Err != nil {
1608 return templ_7745c5c3_Err1788 return templ_7745c5c3_Err
1609 }1789 }
1610- var templ_7745c5c3_Var81 string1790+ var templ_7745c5c3_Var89 string
1611- templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Side)1791+ templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Side)
1792+ if templ_7745c5c3_Err != nil {
1793+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 319, Col: 50}
1794+ }
1795+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var89)
1796+ if templ_7745c5c3_Err != nil {
1797+ return templ_7745c5c3_Err
1798+ }
1799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "\"> <label>from line <input type=\"number\" name=\"start_line\" min=\"1\" value=\"")
1800+ if templ_7745c5c3_Err != nil {
1801+ return templ_7745c5c3_Err
1802+ }
1803+ var templ_7745c5c3_Var90 string
1804+ templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.StartLine)
1805+ if templ_7745c5c3_Err != nil {
1806+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 320, Col: 86}
1807+ }
1808+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var90)
1612 if templ_7745c5c3_Err != nil {1809 if templ_7745c5c3_Err != nil {
1613- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 289, Col: 50}1810+ return templ_7745c5c3_Err
1811+ }
1812+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "\"></label> ")
1813+ if templ_7745c5c3_Err != nil {
1814+ return templ_7745c5c3_Err
1614 }1815 }
1615- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var81)1816+ }
1817+ if f.ReplyTo != "" {
1818+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "<input type=\"hidden\" name=\"reply_to\" value=\"")
1616 if templ_7745c5c3_Err != nil {1819 if templ_7745c5c3_Err != nil {
1617 return templ_7745c5c3_Err1820 return templ_7745c5c3_Err
1618 }1821 }
1619- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "\"> ")1822+ var templ_7745c5c3_Var91 string
1823+ templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.ReplyTo)
1824+ if templ_7745c5c3_Err != nil {
1825+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 323, Col: 57}
1826+ }
1827+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var91)
1828+ if templ_7745c5c3_Err != nil {
1829+ return templ_7745c5c3_Err
1830+ }
1831+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "\"> ")
1832+ if templ_7745c5c3_Err != nil {
1833+ return templ_7745c5c3_Err
1834+ }
1835+ }
1836+ if f.Path == "" && f.ReplyTo == "" {
1837+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "<fieldset><legend>Verdict</legend><label><input type=\"radio\" name=\"verdict\" value=\"\" checked>Note</label><label><input type=\"radio\" name=\"verdict\" value=\"approve\">Approve</label><label><input type=\"radio\" name=\"verdict\" value=\"changes_requested\">Request changes</label></fieldset>")
1620 if templ_7745c5c3_Err != nil {1838 if templ_7745c5c3_Err != nil {
1621 return templ_7745c5c3_Err1839 return templ_7745c5c3_Err
1622 }1840 }
1623 }1841 }
1624 if f.From > 0 {1842 if f.From > 0 {
1625- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "<input type=\"hidden\" name=\"from\" value=\"")1843+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "<input type=\"hidden\" name=\"from\" value=\"")
1626 if templ_7745c5c3_Err != nil {1844 if templ_7745c5c3_Err != nil {
1627 return templ_7745c5c3_Err1845 return templ_7745c5c3_Err
1628 }1846 }
1629- var templ_7745c5c3_Var82 string1847+ var templ_7745c5c3_Var92 string
1630- templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(f.From))1848+ templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.ResolveAttributeValue(itoa(f.From))
1631 if templ_7745c5c3_Err != nil {1849 if templ_7745c5c3_Err != nil {
1632- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 292, Col: 56}1850+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 329, Col: 56}
1633 }1851 }
1634- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var82)1852+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var92)
1635 if templ_7745c5c3_Err != nil {1853 if templ_7745c5c3_Err != nil {
1636 return templ_7745c5c3_Err1854 return templ_7745c5c3_Err
1637 }1855 }
1638- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "\">")1856+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "\">")
1639 if templ_7745c5c3_Err != nil {1857 if templ_7745c5c3_Err != nil {
1640 return templ_7745c5c3_Err1858 return templ_7745c5c3_Err
1641 }1859 }
1642 }1860 }
1643- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "<div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Comment</button> ")1861+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "<div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Comment</button> ")
1644 if templ_7745c5c3_Err != nil {1862 if templ_7745c5c3_Err != nil {
1645 return templ_7745c5c3_Err1863 return templ_7745c5c3_Err
1646 }1864 }
1647 if f.Cancel != "" {1865 if f.Cancel != "" {
1648- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "<a class=\"btn\" href=\"")1866+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "<a class=\"btn\" href=\"")
1649 if templ_7745c5c3_Err != nil {1867 if templ_7745c5c3_Err != nil {
1650 return templ_7745c5c3_Err1868 return templ_7745c5c3_Err
1651 }1869 }
1652- var templ_7745c5c3_Var83 templ.SafeURL1870+ var templ_7745c5c3_Var93 templ.SafeURL
1653- templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Cancel))1871+ templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(f.Cancel))
1654 if templ_7745c5c3_Err != nil {1872 if templ_7745c5c3_Err != nil {
1655- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 297, Col: 49}1873+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 334, Col: 49}
1656 }1874 }
1657- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))1875+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93))
1658 if templ_7745c5c3_Err != nil {1876 if templ_7745c5c3_Err != nil {
1659 return templ_7745c5c3_Err1877 return templ_7745c5c3_Err
1660 }1878 }
1661- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "\">Cancel</a>")1879+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "\">Cancel</a>")
1662 if templ_7745c5c3_Err != nil {1880 if templ_7745c5c3_Err != nil {
1663 return templ_7745c5c3_Err1881 return templ_7745c5c3_Err
1664 }1882 }
1665 }1883 }
1666- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "</div></form>")1884+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "</div></form>")
1667 if templ_7745c5c3_Err != nil {1885 if templ_7745c5c3_Err != nil {
1668 return templ_7745c5c3_Err1886 return templ_7745c5c3_Err
1669 }1887 }
@@ -1687,25 +1905,25 @@ func mrRail(p MRPage) templ.Component {
1687 }()1905 }()
1688 }1906 }
1689 ctx = templ.InitializeContext(ctx)1907 ctx = templ.InitializeContext(ctx)
1690- templ_7745c5c3_Var84 := templ.GetChildren(ctx)1908+ templ_7745c5c3_Var94 := templ.GetChildren(ctx)
1691- if templ_7745c5c3_Var84 == nil {1909+ if templ_7745c5c3_Var94 == nil {
1692- templ_7745c5c3_Var84 = templ.NopComponent1910+ templ_7745c5c3_Var94 = templ.NopComponent
1693 }1911 }
1694 ctx = templ.ClearChildren(ctx)1912 ctx = templ.ClearChildren(ctx)
1695- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "<section aria-labelledby=\"r-files\" class=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")1913+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "<section aria-labelledby=\"r-files\" class=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")
1696 if templ_7745c5c3_Err != nil {1914 if templ_7745c5c3_Err != nil {
1697 return templ_7745c5c3_Err1915 return templ_7745c5c3_Err
1698 }1916 }
1699- var templ_7745c5c3_Var85 string1917+ var templ_7745c5c3_Var95 string
1700- templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))1918+ templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))
1701 if templ_7745c5c3_Err != nil {1919 if templ_7745c5c3_Err != nil {
1702- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 306, Col: 49}1920+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 343, Col: 49}
1703 }1921 }
1704- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))1922+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))
1705 if templ_7745c5c3_Err != nil {1923 if templ_7745c5c3_Err != nil {
1706 return templ_7745c5c3_Err1924 return templ_7745c5c3_Err
1707 }1925 }
1708- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "</span><kbd aria-hidden=\"true\">g f</kbd> <button type=\"button\" class=\"btn icon fold\" data-js aria-expanded=\"true\" title=\"fold the list of files\" hx-on:click=\"ermiteMR.foldFiles()\"><span class=\"ico i-chevron\" aria-hidden=\"true\"></span><span class=\"sr\">fold the list of files</span></button></h2>")1926+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</span><kbd aria-hidden=\"true\">g f</kbd> <button type=\"button\" class=\"btn icon fold\" data-js aria-expanded=\"true\" title=\"fold the list of files\" hx-on:click=\"ermiteMR.foldFiles()\"><span class=\"ico i-chevron\" aria-hidden=\"true\"></span><span class=\"sr\">fold the list of files</span></button></h2>")
1709 if templ_7745c5c3_Err != nil {1927 if templ_7745c5c3_Err != nil {
1710 return templ_7745c5c3_Err1928 return templ_7745c5c3_Err
1711 }1929 }
@@ -1713,82 +1931,147 @@ func mrRail(p MRPage) templ.Component {
1713 if templ_7745c5c3_Err != nil {1931 if templ_7745c5c3_Err != nil {
1714 return templ_7745c5c3_Err1932 return templ_7745c5c3_Err
1715 }1933 }
1716- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "</section>")1934+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "</section>")
1717 if templ_7745c5c3_Err != nil {1935 if templ_7745c5c3_Err != nil {
1718 return templ_7745c5c3_Err1936 return templ_7745c5c3_Err
1719 }1937 }
1938+ if len(p.Verdicts) > 0 {
1939+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "<section><h2>Verdicts</h2><ul>")
1940+ if templ_7745c5c3_Err != nil {
1941+ return templ_7745c5c3_Err
1942+ }
1943+ for _, v := range p.Verdicts {
1944+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "<li><a href=\"")
1945+ if templ_7745c5c3_Err != nil {
1946+ return templ_7745c5c3_Err
1947+ }
1948+ var templ_7745c5c3_Var96 templ.SafeURL
1949+ templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(v.Href))
1950+ if templ_7745c5c3_Err != nil {
1951+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 356, Col: 37}
1952+ }
1953+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))
1954+ if templ_7745c5c3_Err != nil {
1955+ return templ_7745c5c3_Err
1956+ }
1957+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "\">")
1958+ if templ_7745c5c3_Err != nil {
1959+ return templ_7745c5c3_Err
1960+ }
1961+ var templ_7745c5c3_Var97 string
1962+ templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(v.Author)
1963+ if templ_7745c5c3_Err != nil {
1964+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 356, Col: 50}
1965+ }
1966+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97))
1967+ if templ_7745c5c3_Err != nil {
1968+ return templ_7745c5c3_Err
1969+ }
1970+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, ": ")
1971+ if templ_7745c5c3_Err != nil {
1972+ return templ_7745c5c3_Err
1973+ }
1974+ var templ_7745c5c3_Var98 string
1975+ templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(v.Label)
1976+ if templ_7745c5c3_Err != nil {
1977+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 356, Col: 63}
1978+ }
1979+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98))
1980+ if templ_7745c5c3_Err != nil {
1981+ return templ_7745c5c3_Err
1982+ }
1983+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "</a> ")
1984+ if templ_7745c5c3_Err != nil {
1985+ return templ_7745c5c3_Err
1986+ }
1987+ if v.Stale {
1988+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "<span class=\"mute\">(stale)</span>")
1989+ if templ_7745c5c3_Err != nil {
1990+ return templ_7745c5c3_Err
1991+ }
1992+ }
1993+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "</li>")
1994+ if templ_7745c5c3_Err != nil {
1995+ return templ_7745c5c3_Err
1996+ }
1997+ }
1998+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "</ul></section>")
1999+ if templ_7745c5c3_Err != nil {
2000+ return templ_7745c5c3_Err
2001+ }
2002+ }
1720 templ_7745c5c3_Err = MRChecks(p.Checks).Render(ctx, templ_7745c5c3_Buffer)2003 templ_7745c5c3_Err = MRChecks(p.Checks).Render(ctx, templ_7745c5c3_Buffer)
1721 if templ_7745c5c3_Err != nil {2004 if templ_7745c5c3_Err != nil {
1722 return templ_7745c5c3_Err2005 return templ_7745c5c3_Err
1723 }2006 }
1724- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "<section aria-labelledby=\"r-commits\"><h2 id=\"r-commits\">Commits <span class=\"count\">")2007+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "<section aria-labelledby=\"r-commits\"><h2 id=\"r-commits\">Commits <span class=\"count\">")
1725 if templ_7745c5c3_Err != nil {2008 if templ_7745c5c3_Err != nil {
1726 return templ_7745c5c3_Err2009 return templ_7745c5c3_Err
1727 }2010 }
1728- var templ_7745c5c3_Var86 string2011+ var templ_7745c5c3_Var99 string
1729- templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Commits)))2012+ templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Commits)))
1730 if templ_7745c5c3_Err != nil {2013 if templ_7745c5c3_Err != nil {
1731- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 315, Col: 71}2014+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 367, Col: 71}
1732 }2015 }
1733- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))2016+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99))
1734 if templ_7745c5c3_Err != nil {2017 if templ_7745c5c3_Err != nil {
1735 return templ_7745c5c3_Err2018 return templ_7745c5c3_Err
1736 }2019 }
1737- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</span></h2><ul class=\"pcommits\">")2020+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "</span></h2><ul class=\"pcommits\">")
1738 if templ_7745c5c3_Err != nil {2021 if templ_7745c5c3_Err != nil {
1739 return templ_7745c5c3_Err2022 return templ_7745c5c3_Err
1740 }2023 }
1741 for _, c := range p.Commits {2024 for _, c := range p.Commits {
1742- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "<li")2025+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "<li")
1743 if templ_7745c5c3_Err != nil {2026 if templ_7745c5c3_Err != nil {
1744 return templ_7745c5c3_Err2027 return templ_7745c5c3_Err
1745 }2028 }
1746 if c.New {2029 if c.New {
1747- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, " data-new")2030+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, " data-new")
1748 if templ_7745c5c3_Err != nil {2031 if templ_7745c5c3_Err != nil {
1749 return templ_7745c5c3_Err2032 return templ_7745c5c3_Err
1750 }2033 }
1751 }2034 }
1752- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "><a href=\"")2035+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "><a href=\"")
1753 if templ_7745c5c3_Err != nil {2036 if templ_7745c5c3_Err != nil {
1754 return templ_7745c5c3_Err2037 return templ_7745c5c3_Err
1755 }2038 }
1756- var templ_7745c5c3_Var87 templ.SafeURL2039+ var templ_7745c5c3_Var100 templ.SafeURL
1757- templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))2040+ templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.Href))
1758 if templ_7745c5c3_Err != nil {2041 if templ_7745c5c3_Err != nil {
1759- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 319, Col: 36}2042+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 371, Col: 36}
1760 }2043 }
1761- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87))2044+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))
1762 if templ_7745c5c3_Err != nil {2045 if templ_7745c5c3_Err != nil {
1763 return templ_7745c5c3_Err2046 return templ_7745c5c3_Err
1764 }2047 }
1765- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "\">")2048+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "\">")
1766 if templ_7745c5c3_Err != nil {2049 if templ_7745c5c3_Err != nil {
1767 return templ_7745c5c3_Err2050 return templ_7745c5c3_Err
1768 }2051 }
1769- var templ_7745c5c3_Var88 string2052+ var templ_7745c5c3_Var101 string
1770- templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)2053+ templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
1771 if templ_7745c5c3_Err != nil {2054 if templ_7745c5c3_Err != nil {
1772- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 319, Col: 48}2055+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 371, Col: 48}
1773 }2056 }
1774- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))2057+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101))
1775 if templ_7745c5c3_Err != nil {2058 if templ_7745c5c3_Err != nil {
1776 return templ_7745c5c3_Err2059 return templ_7745c5c3_Err
1777 }2060 }
1778- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "</a> <span class=\"s\">")2061+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "</a> <span class=\"s\">")
1779 if templ_7745c5c3_Err != nil {2062 if templ_7745c5c3_Err != nil {
1780 return templ_7745c5c3_Err2063 return templ_7745c5c3_Err
1781 }2064 }
1782- var templ_7745c5c3_Var89 string2065+ var templ_7745c5c3_Var102 string
1783- templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)2066+ templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
1784 if templ_7745c5c3_Err != nil {2067 if templ_7745c5c3_Err != nil {
1785- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 320, Col: 32}2068+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 372, Col: 32}
1786 }2069 }
1787- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89))2070+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
1788 if templ_7745c5c3_Err != nil {2071 if templ_7745c5c3_Err != nil {
1789 return templ_7745c5c3_Err2072 return templ_7745c5c3_Err
1790 }2073 }
1791- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "</span> ")2074+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "</span> ")
1792 if templ_7745c5c3_Err != nil {2075 if templ_7745c5c3_Err != nil {
1793 return templ_7745c5c3_Err2076 return templ_7745c5c3_Err
1794 }2077 }
@@ -1798,138 +2081,138 @@ func mrRail(p MRPage) templ.Component {
1798 return templ_7745c5c3_Err2081 return templ_7745c5c3_Err
1799 }2082 }
1800 }2083 }
1801- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "<span class=\"mute\">")2084+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "<span class=\"mute\">")
1802 if templ_7745c5c3_Err != nil {2085 if templ_7745c5c3_Err != nil {
1803 return templ_7745c5c3_Err2086 return templ_7745c5c3_Err
1804 }2087 }
1805- var templ_7745c5c3_Var90 string2088+ var templ_7745c5c3_Var103 string
1806- templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)2089+ templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
1807 if templ_7745c5c3_Err != nil {2090 if templ_7745c5c3_Err != nil {
1808- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 325, Col: 16}2091+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 377, Col: 16}
1809 }2092 }
1810- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90))2093+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))
1811 if templ_7745c5c3_Err != nil {2094 if templ_7745c5c3_Err != nil {
1812 return templ_7745c5c3_Err2095 return templ_7745c5c3_Err
1813 }2096 }
1814- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, " ")2097+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, " ")
1815 if templ_7745c5c3_Err != nil {2098 if templ_7745c5c3_Err != nil {
1816 return templ_7745c5c3_Err2099 return templ_7745c5c3_Err
1817 }2100 }
1818 if c.WhenAt != "" {2101 if c.WhenAt != "" {
1819- var templ_7745c5c3_Var91 string2102+ var templ_7745c5c3_Var104 string
1820- templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")2103+ templ_7745c5c3_Var104, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
1821 if templ_7745c5c3_Err != nil {2104 if templ_7745c5c3_Err != nil {
1822- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 327, Col: 15}2105+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 379, Col: 15}
1823 }2106 }
1824- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var91))2107+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var104))
1825 if templ_7745c5c3_Err != nil {2108 if templ_7745c5c3_Err != nil {
1826 return templ_7745c5c3_Err2109 return templ_7745c5c3_Err
1827 }2110 }
1828- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "<time datetime=\"")2111+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "<time datetime=\"")
1829 if templ_7745c5c3_Err != nil {2112 if templ_7745c5c3_Err != nil {
1830 return templ_7745c5c3_Err2113 return templ_7745c5c3_Err
1831 }2114 }
1832- var templ_7745c5c3_Var92 string2115+ var templ_7745c5c3_Var105 string
1833- templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)2116+ templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
1834 if templ_7745c5c3_Err != nil {2117 if templ_7745c5c3_Err != nil {
1835- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 327, Col: 42}2118+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 379, Col: 42}
1836 }2119 }
1837- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var92)2120+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var105)
1838 if templ_7745c5c3_Err != nil {2121 if templ_7745c5c3_Err != nil {
1839 return templ_7745c5c3_Err2122 return templ_7745c5c3_Err
1840 }2123 }
1841- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "\">")2124+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "\">")
1842 if templ_7745c5c3_Err != nil {2125 if templ_7745c5c3_Err != nil {
1843 return templ_7745c5c3_Err2126 return templ_7745c5c3_Err
1844 }2127 }
1845- var templ_7745c5c3_Var93 string2128+ var templ_7745c5c3_Var106 string
1846- templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)2129+ templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
1847 if templ_7745c5c3_Err != nil {2130 if templ_7745c5c3_Err != nil {
1848- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 327, Col: 53}2131+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 379, Col: 53}
1849 }2132 }
1850- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93))2133+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var106))
1851 if templ_7745c5c3_Err != nil {2134 if templ_7745c5c3_Err != nil {
1852 return templ_7745c5c3_Err2135 return templ_7745c5c3_Err
1853 }2136 }
1854- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "</time> ")2137+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "</time> ")
1855 if templ_7745c5c3_Err != nil {2138 if templ_7745c5c3_Err != nil {
1856 return templ_7745c5c3_Err2139 return templ_7745c5c3_Err
1857 }2140 }
1858 }2141 }
1859 if c.New {2142 if c.New {
1860- var templ_7745c5c3_Var94 string2143+ var templ_7745c5c3_Var107 string
1861- templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(" · new")2144+ templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.JoinStringErrs(" · new")
1862 if templ_7745c5c3_Err != nil {2145 if templ_7745c5c3_Err != nil {
1863- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 330, Col: 18}2146+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 382, Col: 18}
1864 }2147 }
1865- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94))2148+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var107))
1866 if templ_7745c5c3_Err != nil {2149 if templ_7745c5c3_Err != nil {
1867 return templ_7745c5c3_Err2150 return templ_7745c5c3_Err
1868 }2151 }
1869 }2152 }
1870- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "</span></li>")2153+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "</span></li>")
1871 if templ_7745c5c3_Err != nil {2154 if templ_7745c5c3_Err != nil {
1872 return templ_7745c5c3_Err2155 return templ_7745c5c3_Err
1873 }2156 }
1874 }2157 }
1875- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "</ul>")2158+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "</ul>")
1876 if templ_7745c5c3_Err != nil {2159 if templ_7745c5c3_Err != nil {
1877 return templ_7745c5c3_Err2160 return templ_7745c5c3_Err
1878 }2161 }
1879 if len(p.Commits) == 0 {2162 if len(p.Commits) == 0 {
1880- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "<p class=\"dim\">no commit to merge.</p>")2163+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<p class=\"dim\">no commit to merge.</p>")
1881 if templ_7745c5c3_Err != nil {2164 if templ_7745c5c3_Err != nil {
1882 return templ_7745c5c3_Err2165 return templ_7745c5c3_Err
1883 }2166 }
1884 }2167 }
1885 if p.CommitsNote != "" {2168 if p.CommitsNote != "" {
1886- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "<p class=\"note\">")2169+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "<p class=\"note\">")
1887 if templ_7745c5c3_Err != nil {2170 if templ_7745c5c3_Err != nil {
1888 return templ_7745c5c3_Err2171 return templ_7745c5c3_Err
1889 }2172 }
1890- var templ_7745c5c3_Var95 string2173+ var templ_7745c5c3_Var108 string
1891- templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(p.CommitsNote)2174+ templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.JoinStringErrs(p.CommitsNote)
1892 if templ_7745c5c3_Err != nil {2175 if templ_7745c5c3_Err != nil {
1893- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 340, Col: 34}2176+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 392, Col: 34}
1894 }2177 }
1895- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))2178+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var108))
1896 if templ_7745c5c3_Err != nil {2179 if templ_7745c5c3_Err != nil {
1897 return templ_7745c5c3_Err2180 return templ_7745c5c3_Err
1898 }2181 }
1899- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "</p>")2182+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "</p>")
1900 if templ_7745c5c3_Err != nil {2183 if templ_7745c5c3_Err != nil {
1901 return templ_7745c5c3_Err2184 return templ_7745c5c3_Err
1902 }2185 }
1903 }2186 }
1904- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "</section><section aria-labelledby=\"r-settings\"><h2 id=\"r-settings\">Settings</h2><dl class=\"kv\"><dt>modes</dt><dd>")2187+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "</section><section aria-labelledby=\"r-settings\"><h2 id=\"r-settings\">Settings</h2><dl class=\"kv\"><dt>modes</dt><dd>")
1905 if templ_7745c5c3_Err != nil {2188 if templ_7745c5c3_Err != nil {
1906 return templ_7745c5c3_Err2189 return templ_7745c5c3_Err
1907 }2190 }
1908- var templ_7745c5c3_Var96 string2191+ var templ_7745c5c3_Var109 string
1909- templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(p.Modes, ", "))2192+ templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(p.Modes, ", "))
1910 if templ_7745c5c3_Err != nil {2193 if templ_7745c5c3_Err != nil {
1911- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 347, Col: 36}2194+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 399, Col: 36}
1912 }2195 }
1913- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))2196+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))
1914 if templ_7745c5c3_Err != nil {2197 if templ_7745c5c3_Err != nil {
1915 return templ_7745c5c3_Err2198 return templ_7745c5c3_Err
1916 }2199 }
1917- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "</dd><dt>checks</dt><dd>")2200+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "</dd><dt>checks</dt><dd>")
1918 if templ_7745c5c3_Err != nil {2201 if templ_7745c5c3_Err != nil {
1919 return templ_7745c5c3_Err2202 return templ_7745c5c3_Err
1920 }2203 }
1921 if p.ChecksBlock {2204 if p.ChecksBlock {
1922- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "failing ones block the merge")2205+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "failing ones block the merge")
1923 if templ_7745c5c3_Err != nil {2206 if templ_7745c5c3_Err != nil {
1924 return templ_7745c5c3_Err2207 return templ_7745c5c3_Err
1925 }2208 }
1926 } else {2209 } else {
1927- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "do not block the merge")2210+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "do not block the merge")
1928 if templ_7745c5c3_Err != nil {2211 if templ_7745c5c3_Err != nil {
1929 return templ_7745c5c3_Err2212 return templ_7745c5c3_Err
1930 }2213 }
1931 }2214 }
1932- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "</dd></dl></section>")2215+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "</dd></dl></section>")
1933 if templ_7745c5c3_Err != nil {2216 if templ_7745c5c3_Err != nil {
1934 return templ_7745c5c3_Err2217 return templ_7745c5c3_Err
1935 }2218 }
@@ -1957,51 +2240,51 @@ func mrBar(p MRPage) templ.Component {
1957 }()2240 }()
1958 }2241 }
1959 ctx = templ.InitializeContext(ctx)2242 ctx = templ.InitializeContext(ctx)
1960- templ_7745c5c3_Var97 := templ.GetChildren(ctx)2243+ templ_7745c5c3_Var110 := templ.GetChildren(ctx)
1961- if templ_7745c5c3_Var97 == nil {2244+ if templ_7745c5c3_Var110 == nil {
1962- templ_7745c5c3_Var97 = templ.NopComponent2245+ templ_7745c5c3_Var110 = templ.NopComponent
1963 }2246 }
1964 ctx = templ.ClearChildren(ctx)2247 ctx = templ.ClearChildren(ctx)
1965- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "<div class=\"diff-tools mr-bar\" role=\"group\" aria-label=\"Diff view and merge\"><span class=\"dim\">")2248+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "<div class=\"diff-tools mr-bar\" role=\"group\" aria-label=\"Diff view and merge\"><span class=\"dim\">")
1966 if templ_7745c5c3_Err != nil {2249 if templ_7745c5c3_Err != nil {
1967 return templ_7745c5c3_Err2250 return templ_7745c5c3_Err
1968 }2251 }
1969- var templ_7745c5c3_Var98 string2252+ var templ_7745c5c3_Var111 string
1970- templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))2253+ templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))
1971 if templ_7745c5c3_Err != nil {2254 if templ_7745c5c3_Err != nil {
1972- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 366, Col: 55}2255+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 55}
1973 }2256 }
1974- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98))2257+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var111))
1975 if templ_7745c5c3_Err != nil {2258 if templ_7745c5c3_Err != nil {
1976 return templ_7745c5c3_Err2259 return templ_7745c5c3_Err
1977 }2260 }
1978- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, " · <span class=\"plus\">+")2261+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, " · <span class=\"plus\">+")
1979 if templ_7745c5c3_Err != nil {2262 if templ_7745c5c3_Err != nil {
1980 return templ_7745c5c3_Err2263 return templ_7745c5c3_Err
1981 }2264 }
1982- var templ_7745c5c3_Var99 string2265+ var templ_7745c5c3_Var112 string
1983- templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))2266+ templ_7745c5c3_Var112, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))
1984 if templ_7745c5c3_Err != nil {2267 if templ_7745c5c3_Err != nil {
1985- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 366, Col: 95}2268+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 95}
1986 }2269 }
1987- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99))2270+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var112))
1988 if templ_7745c5c3_Err != nil {2271 if templ_7745c5c3_Err != nil {
1989 return templ_7745c5c3_Err2272 return templ_7745c5c3_Err
1990 }2273 }
1991- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "</span> <span class=\"minus\">-")2274+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "</span> <span class=\"minus\">-")
1992 if templ_7745c5c3_Err != nil {2275 if templ_7745c5c3_Err != nil {
1993 return templ_7745c5c3_Err2276 return templ_7745c5c3_Err
1994 }2277 }
1995- var templ_7745c5c3_Var100 string2278+ var templ_7745c5c3_Var113 string
1996- templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))2279+ templ_7745c5c3_Var113, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))
1997 if templ_7745c5c3_Err != nil {2280 if templ_7745c5c3_Err != nil {
1998- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 366, Col: 140}2281+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 140}
1999 }2282 }
2000- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100))2283+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var113))
2001 if templ_7745c5c3_Err != nil {2284 if templ_7745c5c3_Err != nil {
2002 return templ_7745c5c3_Err2285 return templ_7745c5c3_Err
2003 }2286 }
2004- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "</span></span>")2287+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "</span></span>")
2005 if templ_7745c5c3_Err != nil {2288 if templ_7745c5c3_Err != nil {
2006 return templ_7745c5c3_Err2289 return templ_7745c5c3_Err
2007 }2290 }
@@ -2017,7 +2300,7 @@ func mrBar(p MRPage) templ.Component {
2017 if templ_7745c5c3_Err != nil {2300 if templ_7745c5c3_Err != nil {
2018 return templ_7745c5c3_Err2301 return templ_7745c5c3_Err
2019 }2302 }
2020- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "<span class=\"grow\"></span>")2303+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "<span class=\"grow\"></span>")
2021 if templ_7745c5c3_Err != nil {2304 if templ_7745c5c3_Err != nil {
2022 return templ_7745c5c3_Err2305 return templ_7745c5c3_Err
2023 }2306 }
@@ -2030,7 +2313,7 @@ func mrBar(p MRPage) templ.Component {
2030 if templ_7745c5c3_Err != nil {2313 if templ_7745c5c3_Err != nil {
2031 return templ_7745c5c3_Err2314 return templ_7745c5c3_Err
2032 }2315 }
2033- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, " ")2316+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, " ")
2034 if templ_7745c5c3_Err != nil {2317 if templ_7745c5c3_Err != nil {
2035 return templ_7745c5c3_Err2318 return templ_7745c5c3_Err
2036 }2319 }
@@ -2039,7 +2322,7 @@ func mrBar(p MRPage) templ.Component {
2039 return templ_7745c5c3_Err2322 return templ_7745c5c3_Err
2040 }2323 }
2041 }2324 }
2042- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "</div>")2325+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "</div>")
2043 if templ_7745c5c3_Err != nil {2326 if templ_7745c5c3_Err != nil {
2044 return templ_7745c5c3_Err2327 return templ_7745c5c3_Err
2045 }2328 }
@@ -2066,219 +2349,219 @@ func mergeMenu(p MRPage) templ.Component {
2066 }()2349 }()
2067 }2350 }
2068 ctx = templ.InitializeContext(ctx)2351 ctx = templ.InitializeContext(ctx)
2069- templ_7745c5c3_Var101 := templ.GetChildren(ctx)2352+ templ_7745c5c3_Var114 := templ.GetChildren(ctx)
2070- if templ_7745c5c3_Var101 == nil {2353+ if templ_7745c5c3_Var114 == nil {
2071- templ_7745c5c3_Var101 = templ.NopComponent2354+ templ_7745c5c3_Var114 = templ.NopComponent
2072 }2355 }
2073 ctx = templ.ClearChildren(ctx)2356 ctx = templ.ClearChildren(ctx)
2074- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "<details class=\"menu mmenu\" id=\"merge-menu\" data-state=\"open\"><summary class=\"btn primary\" aria-keyshortcuts=\"Shift+M\">")2357+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "<details class=\"menu mmenu\" id=\"merge-menu\" data-state=\"open\"><summary class=\"btn primary\" aria-keyshortcuts=\"Shift+M\">")
2075 if templ_7745c5c3_Err != nil {2358 if templ_7745c5c3_Err != nil {
2076 return templ_7745c5c3_Err2359 return templ_7745c5c3_Err
2077 }2360 }
2078 if p.Box.ReadyAction != "" {2361 if p.Box.ReadyAction != "" {
2079- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "Draft ")2362+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "Draft ")
2080 if templ_7745c5c3_Err != nil {2363 if templ_7745c5c3_Err != nil {
2081 return templ_7745c5c3_Err2364 return templ_7745c5c3_Err
2082 }2365 }
2083 } else {2366 } else {
2084- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "Merge ")2367+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "Merge ")
2085 if templ_7745c5c3_Err != nil {2368 if templ_7745c5c3_Err != nil {
2086 return templ_7745c5c3_Err2369 return templ_7745c5c3_Err
2087 }2370 }
2088 }2371 }
2089- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "<kbd aria-hidden=\"true\">M</kbd></summary><div class=\"menu-pop\" role=\"group\" aria-label=\"Merge\"><dl class=\"kv\"><dt>head</dt><dd>")2372+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<kbd aria-hidden=\"true\">M</kbd></summary><div class=\"menu-pop\" role=\"group\" aria-label=\"Merge\"><dl class=\"kv\"><dt>head</dt><dd>")
2090 if templ_7745c5c3_Err != nil {2373 if templ_7745c5c3_Err != nil {
2091 return templ_7745c5c3_Err2374 return templ_7745c5c3_Err
2092 }2375 }
2093 if p.Box.HeadGone {2376 if p.Box.HeadGone {
2094- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "gone: the source branch was deleted")2377+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "gone: the source branch was deleted")
2095 if templ_7745c5c3_Err != nil {2378 if templ_7745c5c3_Err != nil {
2096 return templ_7745c5c3_Err2379 return templ_7745c5c3_Err
2097 }2380 }
2098 } else {2381 } else {
2099- var templ_7745c5c3_Var102 string2382+ var templ_7745c5c3_Var115 string
2100- templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(p.HeadShort)2383+ templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.JoinStringErrs(p.HeadShort)
2101 if templ_7745c5c3_Err != nil {2384 if templ_7745c5c3_Err != nil {
2102- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 399, Col: 19}2385+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 451, Col: 19}
2103 }2386 }
2104- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))2387+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var115))
2105 if templ_7745c5c3_Err != nil {2388 if templ_7745c5c3_Err != nil {
2106 return templ_7745c5c3_Err2389 return templ_7745c5c3_Err
2107 }2390 }
2108 }2391 }
2109- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "</dd><dt>into</dt><dd>")2392+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "</dd><dt>into</dt><dd>")
2110 if templ_7745c5c3_Err != nil {2393 if templ_7745c5c3_Err != nil {
2111 return templ_7745c5c3_Err2394 return templ_7745c5c3_Err
2112 }2395 }
2113- var templ_7745c5c3_Var103 string2396+ var templ_7745c5c3_Var116 string
2114- templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)2397+ templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2115 if templ_7745c5c3_Err != nil {2398 if templ_7745c5c3_Err != nil {
2116- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 403, Col: 18}2399+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 455, Col: 18}
2117 }2400 }
2118- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))2401+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var116))
2119 if templ_7745c5c3_Err != nil {2402 if templ_7745c5c3_Err != nil {
2120 return templ_7745c5c3_Err2403 return templ_7745c5c3_Err
2121 }2404 }
2122- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "</dd></dl>")2405+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "</dd></dl>")
2123 if templ_7745c5c3_Err != nil {2406 if templ_7745c5c3_Err != nil {
2124 return templ_7745c5c3_Err2407 return templ_7745c5c3_Err
2125 }2408 }
2126 if p.Box.ReadyAction != "" {2409 if p.Box.ReadyAction != "" {
2127- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "<p class=\"d-draft\">this is a draft: it cannot be merged until it is ready for review.</p><form class=\"d-btns\" method=\"post\" action=\"")2410+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "<p class=\"d-draft\">this is a draft: it cannot be merged until it is ready for review.</p><form class=\"d-btns\" method=\"post\" action=\"")
2128 if templ_7745c5c3_Err != nil {2411 if templ_7745c5c3_Err != nil {
2129 return templ_7745c5c3_Err2412 return templ_7745c5c3_Err
2130 }2413 }
2131- var templ_7745c5c3_Var104 templ.SafeURL2414+ var templ_7745c5c3_Var117 templ.SafeURL
2132- templ_7745c5c3_Var104, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadyAction))2415+ templ_7745c5c3_Var117, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadyAction))
2133 if templ_7745c5c3_Err != nil {2416 if templ_7745c5c3_Err != nil {
2134- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 407, Col: 80}2417+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 459, Col: 80}
2135 }2418 }
2136- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var104))2419+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var117))
2137 if templ_7745c5c3_Err != nil {2420 if templ_7745c5c3_Err != nil {
2138 return templ_7745c5c3_Err2421 return templ_7745c5c3_Err
2139 }2422 }
2140- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "\"><button type=\"submit\" class=\"btn primary\" data-default title=\"remove the draft prefix from the title\">Ready for review</button></form>")2423+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "\"><button type=\"submit\" class=\"btn primary\" data-default title=\"remove the draft prefix from the title\">Ready for review</button></form>")
2141 if templ_7745c5c3_Err != nil {2424 if templ_7745c5c3_Err != nil {
2142 return templ_7745c5c3_Err2425 return templ_7745c5c3_Err
2143 }2426 }
2144 } else if !p.Box.HeadGone {2427 } else if !p.Box.HeadGone {
2145- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "<form class=\"d-acts\" method=\"post\" action=\"")2428+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "<form class=\"d-acts\" method=\"post\" action=\"")
2146 if templ_7745c5c3_Err != nil {2429 if templ_7745c5c3_Err != nil {
2147 return templ_7745c5c3_Err2430 return templ_7745c5c3_Err
2148 }2431 }
2149- var templ_7745c5c3_Var105 templ.SafeURL2432+ var templ_7745c5c3_Var118 templ.SafeURL
2150- templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.MergeAction))2433+ templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.MergeAction))
2151 if templ_7745c5c3_Err != nil {2434 if templ_7745c5c3_Err != nil {
2152- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 411, Col: 80}2435+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 463, Col: 80}
2153 }2436 }
2154- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var105))2437+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
2155 if templ_7745c5c3_Err != nil {2438 if templ_7745c5c3_Err != nil {
2156 return templ_7745c5c3_Err2439 return templ_7745c5c3_Err
2157 }2440 }
2158- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "\"><input type=\"hidden\" name=\"head_id\" value=\"")2441+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "\"><input type=\"hidden\" name=\"head_id\" value=\"")
2159 if templ_7745c5c3_Err != nil {2442 if templ_7745c5c3_Err != nil {
2160 return templ_7745c5c3_Err2443 return templ_7745c5c3_Err
2161 }2444 }
2162- var templ_7745c5c3_Var106 string2445+ var templ_7745c5c3_Var119 string
2163- templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)2446+ templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)
2164 if templ_7745c5c3_Err != nil {2447 if templ_7745c5c3_Err != nil {
2165- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 412, Col: 59}2448+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 464, Col: 59}
2166 }2449 }
2167- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var106)2450+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var119)
2168 if templ_7745c5c3_Err != nil {2451 if templ_7745c5c3_Err != nil {
2169 return templ_7745c5c3_Err2452 return templ_7745c5c3_Err
2170 }2453 }
2171- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "\"><fieldset class=\"d-modes\"><legend class=\"sr\">merge mode</legend> ")2454+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 224, "\"><fieldset class=\"d-modes\"><legend class=\"sr\">merge mode</legend> ")
2172 if templ_7745c5c3_Err != nil {2455 if templ_7745c5c3_Err != nil {
2173 return templ_7745c5c3_Err2456 return templ_7745c5c3_Err
2174 }2457 }
2175 for i, m := range p.Box.Modes {2458 for i, m := range p.Box.Modes {
2176- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "<label><input type=\"radio\" name=\"mode\" value=\"")2459+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 225, "<label><input type=\"radio\" name=\"mode\" value=\"")
2177 if templ_7745c5c3_Err != nil {2460 if templ_7745c5c3_Err != nil {
2178 return templ_7745c5c3_Err2461 return templ_7745c5c3_Err
2179 }2462 }
2180- var templ_7745c5c3_Var107 string2463+ var templ_7745c5c3_Var120 string
2181- templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.ResolveAttributeValue(m.Value)2464+ templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.ResolveAttributeValue(m.Value)
2182 if templ_7745c5c3_Err != nil {2465 if templ_7745c5c3_Err != nil {
2183- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 417, Col: 55}2466+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 469, Col: 55}
2184 }2467 }
2185- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var107)2468+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var120)
2186 if templ_7745c5c3_Err != nil {2469 if templ_7745c5c3_Err != nil {
2187 return templ_7745c5c3_Err2470 return templ_7745c5c3_Err
2188 }2471 }
2189- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "\"")2472+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 226, "\"")
2190 if templ_7745c5c3_Err != nil {2473 if templ_7745c5c3_Err != nil {
2191 return templ_7745c5c3_Err2474 return templ_7745c5c3_Err
2192 }2475 }
2193 if i == 0 {2476 if i == 0 {
2194- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, " checked")2477+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 227, " checked")
2195 if templ_7745c5c3_Err != nil {2478 if templ_7745c5c3_Err != nil {
2196 return templ_7745c5c3_Err2479 return templ_7745c5c3_Err
2197 }2480 }
2198 }2481 }
2199- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "> ")2482+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 228, "> ")
2200 if templ_7745c5c3_Err != nil {2483 if templ_7745c5c3_Err != nil {
2201 return templ_7745c5c3_Err2484 return templ_7745c5c3_Err
2202 }2485 }
2203- var templ_7745c5c3_Var108 string2486+ var templ_7745c5c3_Var121 string
2204- templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.JoinStringErrs(m.Label)2487+ templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinStringErrs(m.Label)
2205 if templ_7745c5c3_Err != nil {2488 if templ_7745c5c3_Err != nil {
2206- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 418, Col: 17}2489+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 470, Col: 17}
2207 }2490 }
2208- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var108))2491+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))
2209 if templ_7745c5c3_Err != nil {2492 if templ_7745c5c3_Err != nil {
2210 return templ_7745c5c3_Err2493 return templ_7745c5c3_Err
2211 }2494 }
2212- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, " ")2495+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 229, " ")
2213 if templ_7745c5c3_Err != nil {2496 if templ_7745c5c3_Err != nil {
2214 return templ_7745c5c3_Err2497 return templ_7745c5c3_Err
2215 }2498 }
2216 if i < 9 {2499 if i < 9 {
2217- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<kbd aria-hidden=\"true\">")2500+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 230, "<kbd aria-hidden=\"true\">")
2218 if templ_7745c5c3_Err != nil {2501 if templ_7745c5c3_Err != nil {
2219 return templ_7745c5c3_Err2502 return templ_7745c5c3_Err
2220 }2503 }
2221- var templ_7745c5c3_Var109 string2504+ var templ_7745c5c3_Var122 string
2222- templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(i + 1))2505+ templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(i + 1))
2223 if templ_7745c5c3_Err != nil {2506 if templ_7745c5c3_Err != nil {
2224- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 420, Col: 46}2507+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 472, Col: 46}
2225 }2508 }
2226- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))2509+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))
2227 if templ_7745c5c3_Err != nil {2510 if templ_7745c5c3_Err != nil {
2228 return templ_7745c5c3_Err2511 return templ_7745c5c3_Err
2229 }2512 }
2230- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "</kbd>")2513+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 231, "</kbd>")
2231 if templ_7745c5c3_Err != nil {2514 if templ_7745c5c3_Err != nil {
2232 return templ_7745c5c3_Err2515 return templ_7745c5c3_Err
2233 }2516 }
2234 }2517 }
2235- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "</label>")2518+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 232, "</label>")
2236 if templ_7745c5c3_Err != nil {2519 if templ_7745c5c3_Err != nil {
2237 return templ_7745c5c3_Err2520 return templ_7745c5c3_Err
2238 }2521 }
2239 }2522 }
2240- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "</fieldset>")2523+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 233, "</fieldset>")
2241 if templ_7745c5c3_Err != nil {2524 if templ_7745c5c3_Err != nil {
2242 return templ_7745c5c3_Err2525 return templ_7745c5c3_Err
2243 }2526 }
2244 if p.Box.CanDeleteBranch {2527 if p.Box.CanDeleteBranch {
2245- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "<label class=\"d-check\"><input type=\"checkbox\" name=\"delete_branch\" value=\"1\"> delete ")2528+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 234, "<label class=\"d-check\"><input type=\"checkbox\" name=\"delete_branch\" value=\"1\"> delete ")
2246 if templ_7745c5c3_Err != nil {2529 if templ_7745c5c3_Err != nil {
2247 return templ_7745c5c3_Err2530 return templ_7745c5c3_Err
2248 }2531 }
2249- var templ_7745c5c3_Var110 string2532+ var templ_7745c5c3_Var123 string
2250- templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)2533+ templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
2251 if templ_7745c5c3_Err != nil {2534 if templ_7745c5c3_Err != nil {
2252- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 426, Col: 102}2535+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 478, Col: 102}
2253 }2536 }
2254- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var110))2537+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))
2255 if templ_7745c5c3_Err != nil {2538 if templ_7745c5c3_Err != nil {
2256 return templ_7745c5c3_Err2539 return templ_7745c5c3_Err
2257 }2540 }
2258- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, " after the merge <kbd aria-hidden=\"true\">D</kbd></label>")2541+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 235, " after the merge <kbd aria-hidden=\"true\">D</kbd></label>")
2259 if templ_7745c5c3_Err != nil {2542 if templ_7745c5c3_Err != nil {
2260 return templ_7745c5c3_Err2543 return templ_7745c5c3_Err
2261 }2544 }
2262 }2545 }
2263- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "<div class=\"d-btns\"><button type=\"submit\" class=\"btn primary\" data-default>Merge into ")2546+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 236, "<div class=\"d-btns\"><button type=\"submit\" class=\"btn primary\" data-default>Merge into ")
2264 if templ_7745c5c3_Err != nil {2547 if templ_7745c5c3_Err != nil {
2265 return templ_7745c5c3_Err2548 return templ_7745c5c3_Err
2266 }2549 }
2267- var templ_7745c5c3_Var111 string2550+ var templ_7745c5c3_Var124 string
2268- templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)2551+ templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2269 if templ_7745c5c3_Err != nil {2552 if templ_7745c5c3_Err != nil {
2270- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 429, Col: 82}2553+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 481, Col: 82}
2271 }2554 }
2272- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var111))2555+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))
2273 if templ_7745c5c3_Err != nil {2556 if templ_7745c5c3_Err != nil {
2274 return templ_7745c5c3_Err2557 return templ_7745c5c3_Err
2275 }2558 }
2276- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "</button></div></form>")2559+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 237, "</button></div></form>")
2277 if templ_7745c5c3_Err != nil {2560 if templ_7745c5c3_Err != nil {
2278 return templ_7745c5c3_Err2561 return templ_7745c5c3_Err
2279 }2562 }
2280 }2563 }
2281- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "</div></details>")2564+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 238, "</div></details>")
2282 if templ_7745c5c3_Err != nil {2565 if templ_7745c5c3_Err != nil {
2283 return templ_7745c5c3_Err2566 return templ_7745c5c3_Err
2284 }2567 }
@@ -2304,66 +2587,66 @@ func moreMenu(p MRPage) templ.Component {
2304 }()2587 }()
2305 }2588 }
2306 ctx = templ.InitializeContext(ctx)2589 ctx = templ.InitializeContext(ctx)
2307- templ_7745c5c3_Var112 := templ.GetChildren(ctx)2590+ templ_7745c5c3_Var125 := templ.GetChildren(ctx)
2308- if templ_7745c5c3_Var112 == nil {2591+ if templ_7745c5c3_Var125 == nil {
2309- templ_7745c5c3_Var112 = templ.NopComponent2592+ templ_7745c5c3_Var125 = templ.NopComponent
2310 }2593 }
2311 ctx = templ.ClearChildren(ctx)2594 ctx = templ.ClearChildren(ctx)
2312- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "<details class=\"menu omenu\"><summary class=\"btn icon\" aria-label=\"More actions\" title=\"more actions\">⋯</summary><div class=\"menu-pop\" role=\"group\" aria-label=\"More actions\">")2595+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 239, "<details class=\"menu omenu\"><summary class=\"btn icon\" aria-label=\"More actions\" title=\"more actions\">⋯</summary><div class=\"menu-pop\" role=\"group\" aria-label=\"More actions\">")
2313 if templ_7745c5c3_Err != nil {2596 if templ_7745c5c3_Err != nil {
2314 return templ_7745c5c3_Err2597 return templ_7745c5c3_Err
2315 }2598 }
2316 if p.Box.CanMarkRead {2599 if p.Box.CanMarkRead {
2317- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "<form method=\"post\" action=\"")2600+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 240, "<form method=\"post\" action=\"")
2318 if templ_7745c5c3_Err != nil {2601 if templ_7745c5c3_Err != nil {
2319 return templ_7745c5c3_Err2602 return templ_7745c5c3_Err
2320 }2603 }
2321- var templ_7745c5c3_Var113 templ.SafeURL2604+ var templ_7745c5c3_Var126 templ.SafeURL
2322- templ_7745c5c3_Var113, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadAction))2605+ templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.ReadAction))
2323 if templ_7745c5c3_Err != nil {2606 if templ_7745c5c3_Err != nil {
2324- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 444, Col: 64}2607+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 64}
2325 }2608 }
2326- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var113))2609+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126))
2327 if templ_7745c5c3_Err != nil {2610 if templ_7745c5c3_Err != nil {
2328 return templ_7745c5c3_Err2611 return templ_7745c5c3_Err
2329 }2612 }
2330- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "\"><input type=\"hidden\" name=\"head_id\" value=\"")2613+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 241, "\"><input type=\"hidden\" name=\"head_id\" value=\"")
2331 if templ_7745c5c3_Err != nil {2614 if templ_7745c5c3_Err != nil {
2332 return templ_7745c5c3_Err2615 return templ_7745c5c3_Err
2333 }2616 }
2334- var templ_7745c5c3_Var114 string2617+ var templ_7745c5c3_Var127 string
2335- templ_7745c5c3_Var114, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)2618+ templ_7745c5c3_Var127, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Box.Head)
2336 if templ_7745c5c3_Err != nil {2619 if templ_7745c5c3_Err != nil {
2337- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 445, Col: 59}2620+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 497, Col: 59}
2338 }2621 }
2339- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var114)2622+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var127)
2340 if templ_7745c5c3_Err != nil {2623 if templ_7745c5c3_Err != nil {
2341 return templ_7745c5c3_Err2624 return templ_7745c5c3_Err
2342 }2625 }
2343- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "\"> <button type=\"submit\" title=\"the next visit can show only what changed after this head\" class=\"btn\" aria-keyshortcuts=\"Shift+R\" data-read>Mark read <kbd aria-hidden=\"true\">R</kbd></button></form>")2626+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 242, "\"> <button type=\"submit\" title=\"the next visit can show only what changed after this head\" class=\"btn\" aria-keyshortcuts=\"Shift+R\" data-read>Mark read <kbd aria-hidden=\"true\">R</kbd></button></form>")
2344 if templ_7745c5c3_Err != nil {2627 if templ_7745c5c3_Err != nil {
2345 return templ_7745c5c3_Err2628 return templ_7745c5c3_Err
2346 }2629 }
2347 }2630 }
2348- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "<form method=\"post\" action=\"")2631+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 243, "<form method=\"post\" action=\"")
2349 if templ_7745c5c3_Err != nil {2632 if templ_7745c5c3_Err != nil {
2350 return templ_7745c5c3_Err2633 return templ_7745c5c3_Err
2351 }2634 }
2352- var templ_7745c5c3_Var115 templ.SafeURL2635+ var templ_7745c5c3_Var128 templ.SafeURL
2353- templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.CloseAction))2636+ templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Box.CloseAction))
2354 if templ_7745c5c3_Err != nil {2637 if templ_7745c5c3_Err != nil {
2355- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 449, Col: 64}2638+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 501, Col: 64}
2356 }2639 }
2357- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var115))2640+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128))
2358 if templ_7745c5c3_Err != nil {2641 if templ_7745c5c3_Err != nil {
2359 return templ_7745c5c3_Err2642 return templ_7745c5c3_Err
2360 }2643 }
2361- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "\"><button type=\"submit\" title=\"close without merging; the branch is kept\" class=\"btn danger\">Close</button></form>")2644+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 244, "\"><button type=\"submit\" title=\"close without merging; the branch is kept\" class=\"btn danger\">Close</button></form>")
2362 if templ_7745c5c3_Err != nil {2645 if templ_7745c5c3_Err != nil {
2363 return templ_7745c5c3_Err2646 return templ_7745c5c3_Err
2364 }2647 }
2365 if !p.Box.HeadGone && p.Box.ReadyAction == "" {2648 if !p.Box.HeadGone && p.Box.ReadyAction == "" {
2366- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "<details class=\"d-or\"><summary class=\"mute\">or from a terminal</summary>")2649+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 245, "<details class=\"d-or\"><summary class=\"mute\">or from a terminal</summary>")
2367 if templ_7745c5c3_Err != nil {2650 if templ_7745c5c3_Err != nil {
2368 return templ_7745c5c3_Err2651 return templ_7745c5c3_Err
2369 }2652 }
@@ -2371,12 +2654,12 @@ func moreMenu(p MRPage) templ.Component {
2371 if templ_7745c5c3_Err != nil {2654 if templ_7745c5c3_Err != nil {
2372 return templ_7745c5c3_Err2655 return templ_7745c5c3_Err
2373 }2656 }
2374- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "</details>")2657+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 246, "</details>")
2375 if templ_7745c5c3_Err != nil {2658 if templ_7745c5c3_Err != nil {
2376 return templ_7745c5c3_Err2659 return templ_7745c5c3_Err
2377 }2660 }
2378 }2661 }
2379- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "</div></details>")2662+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 247, "</div></details>")
2380 if templ_7745c5c3_Err != nil {2663 if templ_7745c5c3_Err != nil {
2381 return templ_7745c5c3_Err2664 return templ_7745c5c3_Err
2382 }2665 }
@@ -2400,12 +2683,12 @@ func Compare(l Layout, p ComparePage) templ.Component {
2400 }()2683 }()
2401 }2684 }
2402 ctx = templ.InitializeContext(ctx)2685 ctx = templ.InitializeContext(ctx)
2403- templ_7745c5c3_Var116 := templ.GetChildren(ctx)2686+ templ_7745c5c3_Var129 := templ.GetChildren(ctx)
2404- if templ_7745c5c3_Var116 == nil {2687+ if templ_7745c5c3_Var129 == nil {
2405- templ_7745c5c3_Var116 = templ.NopComponent2688+ templ_7745c5c3_Var129 = templ.NopComponent
2406 }2689 }
2407 ctx = templ.ClearChildren(ctx)2690 ctx = templ.ClearChildren(ctx)
2408- templ_7745c5c3_Var117 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {2691+ templ_7745c5c3_Var130 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
2409 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context2692 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
2410 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)2693 templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
2411 if !templ_7745c5c3_IsBuffer {2694 if !templ_7745c5c3_IsBuffer {
@@ -2417,263 +2700,263 @@ func Compare(l Layout, p ComparePage) templ.Component {
2417 }()2700 }()
2418 }2701 }
2419 ctx = templ.InitializeContext(ctx)2702 ctx = templ.InitializeContext(ctx)
2420- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "<header class=\"show mr-h\"><h1>")2703+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 248, "<header class=\"show mr-h\"><h1>")
2421 if templ_7745c5c3_Err != nil {2704 if templ_7745c5c3_Err != nil {
2422 return templ_7745c5c3_Err2705 return templ_7745c5c3_Err
2423 }2706 }
2424- var templ_7745c5c3_Var118 string2707+ var templ_7745c5c3_Var131 string
2425- templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)2708+ templ_7745c5c3_Var131, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
2426 if templ_7745c5c3_Err != nil {2709 if templ_7745c5c3_Err != nil {
2427- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 465, Col: 17}2710+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 517, Col: 17}
2428 }2711 }
2429- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))2712+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var131))
2430 if templ_7745c5c3_Err != nil {2713 if templ_7745c5c3_Err != nil {
2431 return templ_7745c5c3_Err2714 return templ_7745c5c3_Err
2432 }2715 }
2433- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, " → ")2716+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 249, " → ")
2434 if templ_7745c5c3_Err != nil {2717 if templ_7745c5c3_Err != nil {
2435 return templ_7745c5c3_Err2718 return templ_7745c5c3_Err
2436 }2719 }
2437- var templ_7745c5c3_Var119 string2720+ var templ_7745c5c3_Var132 string
2438- templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)2721+ templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2439 if templ_7745c5c3_Err != nil {2722 if templ_7745c5c3_Err != nil {
2440- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 465, Col: 34}2723+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 517, Col: 34}
2441 }2724 }
2442- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var119))2725+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))
2443 if templ_7745c5c3_Err != nil {2726 if templ_7745c5c3_Err != nil {
2444 return templ_7745c5c3_Err2727 return templ_7745c5c3_Err
2445 }2728 }
2446- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "</h1><dl class=\"kv show-kv\"><dt>branch</dt><dd><a href=\"")2729+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 250, "</h1><dl class=\"kv show-kv\"><dt>branch</dt><dd><a href=\"")
2447 if templ_7745c5c3_Err != nil {2730 if templ_7745c5c3_Err != nil {
2448 return templ_7745c5c3_Err2731 return templ_7745c5c3_Err
2449 }2732 }
2450- var templ_7745c5c3_Var120 templ.SafeURL2733+ var templ_7745c5c3_Var133 templ.SafeURL
2451- templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.SourceHref))2734+ templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.SourceHref))
2452 if templ_7745c5c3_Err != nil {2735 if templ_7745c5c3_Err != nil {
2453- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 468, Col: 45}2736+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 520, Col: 45}
2454 }2737 }
2455- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var120))2738+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))
2456 if templ_7745c5c3_Err != nil {2739 if templ_7745c5c3_Err != nil {
2457 return templ_7745c5c3_Err2740 return templ_7745c5c3_Err
2458 }2741 }
2459- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "\">")2742+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 251, "\">")
2460 if templ_7745c5c3_Err != nil {2743 if templ_7745c5c3_Err != nil {
2461 return templ_7745c5c3_Err2744 return templ_7745c5c3_Err
2462 }2745 }
2463- var templ_7745c5c3_Var121 string2746+ var templ_7745c5c3_Var134 string
2464- templ_7745c5c3_Var121, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)2747+ templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(p.Source)
2465 if templ_7745c5c3_Err != nil {2748 if templ_7745c5c3_Err != nil {
2466- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 468, Col: 58}2749+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 520, Col: 58}
2467 }2750 }
2468- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var121))2751+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))
2469 if templ_7745c5c3_Err != nil {2752 if templ_7745c5c3_Err != nil {
2470 return templ_7745c5c3_Err2753 return templ_7745c5c3_Err
2471 }2754 }
2472- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, "</a></dd><dt>target</dt><dd><a href=\"")2755+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 252, "</a></dd><dt>target</dt><dd><a href=\"")
2473 if templ_7745c5c3_Err != nil {2756 if templ_7745c5c3_Err != nil {
2474 return templ_7745c5c3_Err2757 return templ_7745c5c3_Err
2475 }2758 }
2476- var templ_7745c5c3_Var122 templ.SafeURL2759+ var templ_7745c5c3_Var135 templ.SafeURL
2477- templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.TargetHref))2760+ templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.TargetHref))
2478 if templ_7745c5c3_Err != nil {2761 if templ_7745c5c3_Err != nil {
2479- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 470, Col: 45}2762+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 522, Col: 45}
2480 }2763 }
2481- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var122))2764+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135))
2482 if templ_7745c5c3_Err != nil {2765 if templ_7745c5c3_Err != nil {
2483 return templ_7745c5c3_Err2766 return templ_7745c5c3_Err
2484 }2767 }
2485- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 221, "\">")2768+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 253, "\">")
2486 if templ_7745c5c3_Err != nil {2769 if templ_7745c5c3_Err != nil {
2487 return templ_7745c5c3_Err2770 return templ_7745c5c3_Err
2488 }2771 }
2489- var templ_7745c5c3_Var123 string2772+ var templ_7745c5c3_Var136 string
2490- templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)2773+ templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2491 if templ_7745c5c3_Err != nil {2774 if templ_7745c5c3_Err != nil {
2492- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 470, Col: 58}2775+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 522, Col: 58}
2493 }2776 }
2494- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))2777+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136))
2495 if templ_7745c5c3_Err != nil {2778 if templ_7745c5c3_Err != nil {
2496 return templ_7745c5c3_Err2779 return templ_7745c5c3_Err
2497 }2780 }
2498- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 222, "</a></dd><dt>ahead</dt><dd>")2781+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 254, "</a></dd><dt>ahead</dt><dd>")
2499 if templ_7745c5c3_Err != nil {2782 if templ_7745c5c3_Err != nil {
2500 return templ_7745c5c3_Err2783 return templ_7745c5c3_Err
2501 }2784 }
2502- var templ_7745c5c3_Var124 string2785+ var templ_7745c5c3_Var137 string
2503- templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(p.AheadText)2786+ templ_7745c5c3_Var137, templ_7745c5c3_Err = templ.JoinStringErrs(p.AheadText)
2504 if templ_7745c5c3_Err != nil {2787 if templ_7745c5c3_Err != nil {
2505- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 472, Col: 21}2788+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 524, Col: 21}
2506 }2789 }
2507- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124))2790+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var137))
2508 if templ_7745c5c3_Err != nil {2791 if templ_7745c5c3_Err != nil {
2509 return templ_7745c5c3_Err2792 return templ_7745c5c3_Err
2510 }2793 }
2511- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 223, "</dd></dl></header>")2794+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 255, "</dd></dl></header>")
2512 if templ_7745c5c3_Err != nil {2795 if templ_7745c5c3_Err != nil {
2513 return templ_7745c5c3_Err2796 return templ_7745c5c3_Err
2514 }2797 }
2515 if p.Note != "" {2798 if p.Note != "" {
2516- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 224, "<p class=\"notice\">")2799+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 256, "<p class=\"notice\">")
2517 if templ_7745c5c3_Err != nil {2800 if templ_7745c5c3_Err != nil {
2518 return templ_7745c5c3_Err2801 return templ_7745c5c3_Err
2519 }2802 }
2520- var templ_7745c5c3_Var125 string2803+ var templ_7745c5c3_Var138 string
2521- templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)2804+ templ_7745c5c3_Var138, templ_7745c5c3_Err = templ.JoinStringErrs(p.Note)
2522 if templ_7745c5c3_Err != nil {2805 if templ_7745c5c3_Err != nil {
2523- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 476, Col: 29}2806+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 528, Col: 29}
2524 }2807 }
2525- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var125))2808+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var138))
2526 if templ_7745c5c3_Err != nil {2809 if templ_7745c5c3_Err != nil {
2527 return templ_7745c5c3_Err2810 return templ_7745c5c3_Err
2528 }2811 }
2529- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 225, "</p>")2812+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 257, "</p>")
2530 if templ_7745c5c3_Err != nil {2813 if templ_7745c5c3_Err != nil {
2531 return templ_7745c5c3_Err2814 return templ_7745c5c3_Err
2532 }2815 }
2533 }2816 }
2534- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 226, " ")2817+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 258, " ")
2535 if templ_7745c5c3_Err != nil {2818 if templ_7745c5c3_Err != nil {
2536 return templ_7745c5c3_Err2819 return templ_7745c5c3_Err
2537 }2820 }
2538 if p.Existing != nil {2821 if p.Existing != nil {
2539- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 227, "<p class=\"notice\">an open merge request already covers these branches: <a href=\"")2822+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 259, "<p class=\"notice\">an open merge request already covers these branches: <a href=\"")
2540 if templ_7745c5c3_Err != nil {2823 if templ_7745c5c3_Err != nil {
2541 return templ_7745c5c3_Err2824 return templ_7745c5c3_Err
2542 }2825 }
2543- var templ_7745c5c3_Var126 templ.SafeURL2826+ var templ_7745c5c3_Var139 templ.SafeURL
2544- templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Existing.Href))2827+ templ_7745c5c3_Var139, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Existing.Href))
2545 if templ_7745c5c3_Err != nil {2828 if templ_7745c5c3_Err != nil {
2546- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 479, Col: 114}2829+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 531, Col: 114}
2547 }2830 }
2548- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126))2831+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var139))
2549 if templ_7745c5c3_Err != nil {2832 if templ_7745c5c3_Err != nil {
2550 return templ_7745c5c3_Err2833 return templ_7745c5c3_Err
2551 }2834 }
2552- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 228, "\">")2835+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 260, "\">")
2553 if templ_7745c5c3_Err != nil {2836 if templ_7745c5c3_Err != nil {
2554 return templ_7745c5c3_Err2837 return templ_7745c5c3_Err
2555 }2838 }
2556- var templ_7745c5c3_Var127 string2839+ var templ_7745c5c3_Var140 string
2557- templ_7745c5c3_Var127, templ_7745c5c3_Err = templ.JoinStringErrs(p.Existing.Label)2840+ templ_7745c5c3_Var140, templ_7745c5c3_Err = templ.JoinStringErrs(p.Existing.Label)
2558 if templ_7745c5c3_Err != nil {2841 if templ_7745c5c3_Err != nil {
2559- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 479, Col: 135}2842+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 531, Col: 135}
2560 }2843 }
2561- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var127))2844+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var140))
2562 if templ_7745c5c3_Err != nil {2845 if templ_7745c5c3_Err != nil {
2563 return templ_7745c5c3_Err2846 return templ_7745c5c3_Err
2564 }2847 }
2565- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 229, "</a></p>")2848+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 261, "</a></p>")
2566 if templ_7745c5c3_Err != nil {2849 if templ_7745c5c3_Err != nil {
2567 return templ_7745c5c3_Err2850 return templ_7745c5c3_Err
2568 }2851 }
2569 }2852 }
2570- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 230, " ")2853+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 262, " ")
2571 if templ_7745c5c3_Err != nil {2854 if templ_7745c5c3_Err != nil {
2572 return templ_7745c5c3_Err2855 return templ_7745c5c3_Err
2573 }2856 }
2574 if p.Create != nil {2857 if p.Create != nil {
2575- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 231, "<form class=\"cform create\" method=\"post\" action=\"")2858+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 263, "<form class=\"cform create\" method=\"post\" action=\"")
2576 if templ_7745c5c3_Err != nil {2859 if templ_7745c5c3_Err != nil {
2577 return templ_7745c5c3_Err2860 return templ_7745c5c3_Err
2578 }2861 }
2579- var templ_7745c5c3_Var128 templ.SafeURL2862+ var templ_7745c5c3_Var141 templ.SafeURL
2580- templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Create.Action))2863+ templ_7745c5c3_Var141, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(p.Create.Action))
2581 if templ_7745c5c3_Err != nil {2864 if templ_7745c5c3_Err != nil {
2582- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 482, Col: 83}2865+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 534, Col: 83}
2583 }2866 }
2584- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var128))2867+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var141))
2585 if templ_7745c5c3_Err != nil {2868 if templ_7745c5c3_Err != nil {
2586 return templ_7745c5c3_Err2869 return templ_7745c5c3_Err
2587 }2870 }
2588- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 232, "\"><input type=\"hidden\" name=\"source\" value=\"")2871+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 264, "\"><input type=\"hidden\" name=\"source\" value=\"")
2589 if templ_7745c5c3_Err != nil {2872 if templ_7745c5c3_Err != nil {
2590 return templ_7745c5c3_Err2873 return templ_7745c5c3_Err
2591 }2874 }
2592- var templ_7745c5c3_Var129 string2875+ var templ_7745c5c3_Var142 string
2593- templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Source)2876+ templ_7745c5c3_Var142, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Source)
2594 if templ_7745c5c3_Err != nil {2877 if templ_7745c5c3_Err != nil {
2595- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 483, Col: 62}2878+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 535, Col: 62}
2596 }2879 }
2597- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var129)2880+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var142)
2598 if templ_7745c5c3_Err != nil {2881 if templ_7745c5c3_Err != nil {
2599 return templ_7745c5c3_Err2882 return templ_7745c5c3_Err
2600 }2883 }
2601- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 233, "\"> <input type=\"hidden\" name=\"target\" value=\"")2884+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 265, "\"> <input type=\"hidden\" name=\"target\" value=\"")
2602 if templ_7745c5c3_Err != nil {2885 if templ_7745c5c3_Err != nil {
2603 return templ_7745c5c3_Err2886 return templ_7745c5c3_Err
2604 }2887 }
2605- var templ_7745c5c3_Var130 string2888+ var templ_7745c5c3_Var143 string
2606- templ_7745c5c3_Var130, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Target)2889+ templ_7745c5c3_Var143, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Target)
2607 if templ_7745c5c3_Err != nil {2890 if templ_7745c5c3_Err != nil {
2608- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 484, Col: 62}2891+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 536, Col: 62}
2609 }2892 }
2610- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var130)2893+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var143)
2611 if templ_7745c5c3_Err != nil {2894 if templ_7745c5c3_Err != nil {
2612 return templ_7745c5c3_Err2895 return templ_7745c5c3_Err
2613 }2896 }
2614- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 234, "\"> <label for=\"mr-title\">title</label> <input id=\"mr-title\" name=\"title\" type=\"text\" maxlength=\"120\" required value=\"")2897+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 266, "\"> <label for=\"mr-title\">title</label> <input id=\"mr-title\" name=\"title\" type=\"text\" maxlength=\"120\" required value=\"")
2615 if templ_7745c5c3_Err != nil {2898 if templ_7745c5c3_Err != nil {
2616 return templ_7745c5c3_Err2899 return templ_7745c5c3_Err
2617 }2900 }
2618- var templ_7745c5c3_Var131 string2901+ var templ_7745c5c3_Var144 string
2619- templ_7745c5c3_Var131, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Title)2902+ templ_7745c5c3_Var144, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Create.Title)
2620 if templ_7745c5c3_Err != nil {2903 if templ_7745c5c3_Err != nil {
2621- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 486, Col: 97}2904+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 97}
2622 }2905 }
2623- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var131)2906+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var144)
2624 if templ_7745c5c3_Err != nil {2907 if templ_7745c5c3_Err != nil {
2625 return templ_7745c5c3_Err2908 return templ_7745c5c3_Err
2626 }2909 }
2627- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 235, "\" autocomplete=\"off\"> <label for=\"mr-desc\">description</label> <textarea id=\"mr-desc\" name=\"description\" rows=\"4\" maxlength=\"8000\" placeholder=\"Markdown, optional\"></textarea><div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Create merge request</button></div></form>")2910+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 267, "\" autocomplete=\"off\"> <label for=\"mr-desc\">description</label> <textarea id=\"mr-desc\" name=\"description\" rows=\"4\" maxlength=\"8000\" placeholder=\"Markdown, optional\"></textarea><div class=\"f-btns\"><button type=\"submit\" class=\"btn primary\">Create merge request</button></div></form>")
2628 if templ_7745c5c3_Err != nil {2911 if templ_7745c5c3_Err != nil {
2629 return templ_7745c5c3_Err2912 return templ_7745c5c3_Err
2630 }2913 }
2631 }2914 }
2632- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 236, " ")2915+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 268, " ")
2633 if templ_7745c5c3_Err != nil {2916 if templ_7745c5c3_Err != nil {
2634 return templ_7745c5c3_Err2917 return templ_7745c5c3_Err
2635 }2918 }
2636 if p.HasDiff {2919 if p.HasDiff {
2637- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 237, "<div class=\"diff-tools\" role=\"group\" aria-label=\"Diff view\"><span class=\"dim\">")2920+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 269, "<div class=\"diff-tools\" role=\"group\" aria-label=\"Diff view\"><span class=\"dim\">")
2638 if templ_7745c5c3_Err != nil {2921 if templ_7745c5c3_Err != nil {
2639 return templ_7745c5c3_Err2922 return templ_7745c5c3_Err
2640 }2923 }
2641- var templ_7745c5c3_Var132 string2924+ var templ_7745c5c3_Var145 string
2642- templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))2925+ templ_7745c5c3_Var145, templ_7745c5c3_Err = templ.JoinStringErrs(plural(p.NFiles, "file", "files"))
2643 if templ_7745c5c3_Err != nil {2926 if templ_7745c5c3_Err != nil {
2644- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 57}2927+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 548, Col: 57}
2645 }2928 }
2646- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var132))2929+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var145))
2647 if templ_7745c5c3_Err != nil {2930 if templ_7745c5c3_Err != nil {
2648 return templ_7745c5c3_Err2931 return templ_7745c5c3_Err
2649 }2932 }
2650- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 238, " · <span class=\"plus\">+")2933+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 270, " · <span class=\"plus\">+")
2651 if templ_7745c5c3_Err != nil {2934 if templ_7745c5c3_Err != nil {
2652 return templ_7745c5c3_Err2935 return templ_7745c5c3_Err
2653 }2936 }
2654- var templ_7745c5c3_Var133 string2937+ var templ_7745c5c3_Var146 string
2655- templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))2938+ templ_7745c5c3_Var146, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Adds))
2656 if templ_7745c5c3_Err != nil {2939 if templ_7745c5c3_Err != nil {
2657- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 97}2940+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 548, Col: 97}
2658 }2941 }
2659- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))2942+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var146))
2660 if templ_7745c5c3_Err != nil {2943 if templ_7745c5c3_Err != nil {
2661 return templ_7745c5c3_Err2944 return templ_7745c5c3_Err
2662 }2945 }
2663- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 239, "</span> <span class=\"minus\">-")2946+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 271, "</span> <span class=\"minus\">-")
2664 if templ_7745c5c3_Err != nil {2947 if templ_7745c5c3_Err != nil {
2665 return templ_7745c5c3_Err2948 return templ_7745c5c3_Err
2666 }2949 }
2667- var templ_7745c5c3_Var134 string2950+ var templ_7745c5c3_Var147 string
2668- templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))2951+ templ_7745c5c3_Var147, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(p.Dels))
2669 if templ_7745c5c3_Err != nil {2952 if templ_7745c5c3_Err != nil {
2670- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 496, Col: 142}2953+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 548, Col: 142}
2671 }2954 }
2672- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))2955+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var147))
2673 if templ_7745c5c3_Err != nil {2956 if templ_7745c5c3_Err != nil {
2674 return templ_7745c5c3_Err2957 return templ_7745c5c3_Err
2675 }2958 }
2676- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 240, "</span></span> <span class=\"grow\"></span>")2959+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 272, "</span></span> <span class=\"grow\"></span>")
2677 if templ_7745c5c3_Err != nil {2960 if templ_7745c5c3_Err != nil {
2678 return templ_7745c5c3_Err2961 return templ_7745c5c3_Err
2679 }2962 }
@@ -2689,64 +2972,64 @@ func Compare(l Layout, p ComparePage) templ.Component {
2689 if templ_7745c5c3_Err != nil {2972 if templ_7745c5c3_Err != nil {
2690 return templ_7745c5c3_Err2973 return templ_7745c5c3_Err
2691 }2974 }
2692- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 241, "</div>")2975+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 273, "</div>")
2693 if templ_7745c5c3_Err != nil {2976 if templ_7745c5c3_Err != nil {
2694 return templ_7745c5c3_Err2977 return templ_7745c5c3_Err
2695 }2978 }
2696 if p.Invisible {2979 if p.Invisible {
2697- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 242, "<p class=\"notice\">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class=\"inv\">&lt;U+XXXX&gt;</span> markers, never applied.</p>")2980+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 274, "<p class=\"notice\">this diff holds invisible characters (direction controls, zero-width characters). They are shown as <span class=\"inv\">&lt;U+XXXX&gt;</span> markers, never applied.</p>")
2698 if templ_7745c5c3_Err != nil {2981 if templ_7745c5c3_Err != nil {
2699 return templ_7745c5c3_Err2982 return templ_7745c5c3_Err
2700 }2983 }
2701 }2984 }
2702- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 243, " <div id=\"diffs\"")2985+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 275, " <div id=\"diffs\"")
2703 if templ_7745c5c3_Err != nil {2986 if templ_7745c5c3_Err != nil {
2704 return templ_7745c5c3_Err2987 return templ_7745c5c3_Err
2705 }2988 }
2706 if p.Both {2989 if p.Both {
2707- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 244, " data-diff=\"")2990+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 276, " data-diff=\"")
2708 if templ_7745c5c3_Err != nil {2991 if templ_7745c5c3_Err != nil {
2709 return templ_7745c5c3_Err2992 return templ_7745c5c3_Err
2710 }2993 }
2711- var templ_7745c5c3_Var135 string2994+ var templ_7745c5c3_Var148 string
2712- templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)2995+ templ_7745c5c3_Var148, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)
2713 if templ_7745c5c3_Err != nil {2996 if templ_7745c5c3_Err != nil {
2714- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 511, Col: 23}2997+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 563, Col: 23}
2715 }2998 }
2716- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var135)2999+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var148)
2717 if templ_7745c5c3_Err != nil {3000 if templ_7745c5c3_Err != nil {
2718 return templ_7745c5c3_Err3001 return templ_7745c5c3_Err
2719 }3002 }
2720- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 245, "\"")3003+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 277, "\"")
2721 if templ_7745c5c3_Err != nil {3004 if templ_7745c5c3_Err != nil {
2722 return templ_7745c5c3_Err3005 return templ_7745c5c3_Err
2723 }3006 }
2724 } else {3007 } else {
2725- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 246, " data-view=\"")3008+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 278, " data-view=\"")
2726 if templ_7745c5c3_Err != nil {3009 if templ_7745c5c3_Err != nil {
2727 return templ_7745c5c3_Err3010 return templ_7745c5c3_Err
2728 }3011 }
2729- var templ_7745c5c3_Var136 string3012+ var templ_7745c5c3_Var149 string
2730- templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)3013+ templ_7745c5c3_Var149, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.View)
2731 if templ_7745c5c3_Err != nil {3014 if templ_7745c5c3_Err != nil {
2732- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 513, Col: 23}3015+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 565, Col: 23}
2733 }3016 }
2734- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var136)3017+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var149)
2735 if templ_7745c5c3_Err != nil {3018 if templ_7745c5c3_Err != nil {
2736 return templ_7745c5c3_Err3019 return templ_7745c5c3_Err
2737 }3020 }
2738- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 247, "\"")3021+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 279, "\"")
2739 if templ_7745c5c3_Err != nil {3022 if templ_7745c5c3_Err != nil {
2740 return templ_7745c5c3_Err3023 return templ_7745c5c3_Err
2741 }3024 }
2742 }3025 }
2743 if p.Both && !p.Pinned {3026 if p.Both && !p.Pinned {
2744- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 248, " hx-live=\"this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff\"")3027+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 280, " hx-live=\"this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff\"")
2745 if templ_7745c5c3_Err != nil {3028 if templ_7745c5c3_Err != nil {
2746 return templ_7745c5c3_Err3029 return templ_7745c5c3_Err
2747 }3030 }
2748 }3031 }
2749- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 249, ">")3032+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 281, ">")
2750 if templ_7745c5c3_Err != nil {3033 if templ_7745c5c3_Err != nil {
2751 return templ_7745c5c3_Err3034 return templ_7745c5c3_Err
2752 }3035 }
@@ -2756,14 +3039,14 @@ func Compare(l Layout, p ComparePage) templ.Component {
2756 return templ_7745c5c3_Err3039 return templ_7745c5c3_Err
2757 }3040 }
2758 }3041 }
2759- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 250, "</div>")3042+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 282, "</div>")
2760 if templ_7745c5c3_Err != nil {3043 if templ_7745c5c3_Err != nil {
2761 return templ_7745c5c3_Err3044 return templ_7745c5c3_Err
2762 }3045 }
2763 }3046 }
2764 return nil3047 return nil
2765 })3048 })
2766- templ_7745c5c3_Err = Shell(l, compareRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var117), templ_7745c5c3_Buffer)3049+ templ_7745c5c3_Err = Shell(l, compareRail(p)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var130), templ_7745c5c3_Buffer)
2767 if templ_7745c5c3_Err != nil {3050 if templ_7745c5c3_Err != nil {
2768 return templ_7745c5c3_Err3051 return templ_7745c5c3_Err
2769 }3052 }
@@ -2787,173 +3070,173 @@ func compareRail(p ComparePage) templ.Component {
2787 }()3070 }()
2788 }3071 }
2789 ctx = templ.InitializeContext(ctx)3072 ctx = templ.InitializeContext(ctx)
2790- templ_7745c5c3_Var137 := templ.GetChildren(ctx)3073+ templ_7745c5c3_Var150 := templ.GetChildren(ctx)
2791- if templ_7745c5c3_Var137 == nil {3074+ if templ_7745c5c3_Var150 == nil {
2792- templ_7745c5c3_Var137 = templ.NopComponent3075+ templ_7745c5c3_Var150 = templ.NopComponent
2793 }3076 }
2794 ctx = templ.ClearChildren(ctx)3077 ctx = templ.ClearChildren(ctx)
2795- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 251, "<section aria-labelledby=\"r-ahead\"><h2 id=\"r-ahead\">Commits <span class=\"count\">")3078+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 283, "<section aria-labelledby=\"r-ahead\"><h2 id=\"r-ahead\">Commits <span class=\"count\">")
2796 if templ_7745c5c3_Err != nil {3079 if templ_7745c5c3_Err != nil {
2797 return templ_7745c5c3_Err3080 return templ_7745c5c3_Err
2798 }3081 }
2799- var templ_7745c5c3_Var138 string3082+ var templ_7745c5c3_Var151 string
2800- templ_7745c5c3_Var138, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Ahead)))3083+ templ_7745c5c3_Var151, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Ahead)))
2801 if templ_7745c5c3_Err != nil {3084 if templ_7745c5c3_Err != nil {
2802- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 529, Col: 67}3085+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 581, Col: 67}
2803 }3086 }
2804- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var138))3087+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var151))
2805 if templ_7745c5c3_Err != nil {3088 if templ_7745c5c3_Err != nil {
2806 return templ_7745c5c3_Err3089 return templ_7745c5c3_Err
2807 }3090 }
2808- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 252, "</span></h2><ul class=\"pcommits\">")3091+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 284, "</span></h2><ul class=\"pcommits\">")
2809 if templ_7745c5c3_Err != nil {3092 if templ_7745c5c3_Err != nil {
2810 return templ_7745c5c3_Err3093 return templ_7745c5c3_Err
2811 }3094 }
2812 for _, c := range p.Ahead {3095 for _, c := range p.Ahead {
2813- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 253, "<li><a href=\"")3096+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 285, "<li><a href=\"")
2814 if templ_7745c5c3_Err != nil {3097 if templ_7745c5c3_Err != nil {
2815 return templ_7745c5c3_Err3098 return templ_7745c5c3_Err
2816 }3099 }
2817- var templ_7745c5c3_Var139 templ.SafeURL3100+ var templ_7745c5c3_Var152 templ.SafeURL
2818- templ_7745c5c3_Var139, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))3101+ templ_7745c5c3_Var152, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(c.CHref))
2819 if templ_7745c5c3_Err != nil {3102 if templ_7745c5c3_Err != nil {
2820- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 533, Col: 37}3103+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 585, Col: 37}
2821 }3104 }
2822- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var139))3105+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var152))
2823 if templ_7745c5c3_Err != nil {3106 if templ_7745c5c3_Err != nil {
2824 return templ_7745c5c3_Err3107 return templ_7745c5c3_Err
2825 }3108 }
2826- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 254, "\">")3109+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 286, "\">")
2827 if templ_7745c5c3_Err != nil {3110 if templ_7745c5c3_Err != nil {
2828 return templ_7745c5c3_Err3111 return templ_7745c5c3_Err
2829 }3112 }
2830- var templ_7745c5c3_Var140 string3113+ var templ_7745c5c3_Var153 string
2831- templ_7745c5c3_Var140, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)3114+ templ_7745c5c3_Var153, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
2832 if templ_7745c5c3_Err != nil {3115 if templ_7745c5c3_Err != nil {
2833- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 533, Col: 49}3116+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 585, Col: 49}
2834 }3117 }
2835- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var140))3118+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var153))
2836 if templ_7745c5c3_Err != nil {3119 if templ_7745c5c3_Err != nil {
2837 return templ_7745c5c3_Err3120 return templ_7745c5c3_Err
2838 }3121 }
2839- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 255, "</a> <span class=\"s\">")3122+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 287, "</a> <span class=\"s\">")
2840 if templ_7745c5c3_Err != nil {3123 if templ_7745c5c3_Err != nil {
2841 return templ_7745c5c3_Err3124 return templ_7745c5c3_Err
2842 }3125 }
2843- var templ_7745c5c3_Var141 string3126+ var templ_7745c5c3_Var154 string
2844- templ_7745c5c3_Var141, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)3127+ templ_7745c5c3_Var154, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
2845 if templ_7745c5c3_Err != nil {3128 if templ_7745c5c3_Err != nil {
2846- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 534, Col: 32}3129+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 586, Col: 32}
2847 }3130 }
2848- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var141))3131+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var154))
2849 if templ_7745c5c3_Err != nil {3132 if templ_7745c5c3_Err != nil {
2850 return templ_7745c5c3_Err3133 return templ_7745c5c3_Err
2851 }3134 }
2852- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 256, "</span> <span class=\"mute\">")3135+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 288, "</span> <span class=\"mute\">")
2853 if templ_7745c5c3_Err != nil {3136 if templ_7745c5c3_Err != nil {
2854 return templ_7745c5c3_Err3137 return templ_7745c5c3_Err
2855 }3138 }
2856- var templ_7745c5c3_Var142 string3139+ var templ_7745c5c3_Var155 string
2857- templ_7745c5c3_Var142, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)3140+ templ_7745c5c3_Var155, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
2858 if templ_7745c5c3_Err != nil {3141 if templ_7745c5c3_Err != nil {
2859- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 536, Col: 16}3142+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 588, Col: 16}
2860 }3143 }
2861- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var142))3144+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var155))
2862 if templ_7745c5c3_Err != nil {3145 if templ_7745c5c3_Err != nil {
2863 return templ_7745c5c3_Err3146 return templ_7745c5c3_Err
2864 }3147 }
2865- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 257, " ")3148+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 289, " ")
2866 if templ_7745c5c3_Err != nil {3149 if templ_7745c5c3_Err != nil {
2867 return templ_7745c5c3_Err3150 return templ_7745c5c3_Err
2868 }3151 }
2869 if c.WhenAt != "" {3152 if c.WhenAt != "" {
2870- var templ_7745c5c3_Var143 string3153+ var templ_7745c5c3_Var156 string
2871- templ_7745c5c3_Var143, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")3154+ templ_7745c5c3_Var156, templ_7745c5c3_Err = templ.JoinStringErrs(" · ")
2872 if templ_7745c5c3_Err != nil {3155 if templ_7745c5c3_Err != nil {
2873- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 15}3156+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 590, Col: 15}
2874 }3157 }
2875- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var143))3158+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var156))
2876 if templ_7745c5c3_Err != nil {3159 if templ_7745c5c3_Err != nil {
2877 return templ_7745c5c3_Err3160 return templ_7745c5c3_Err
2878 }3161 }
2879- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 258, "<time datetime=\"")3162+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 290, "<time datetime=\"")
2880 if templ_7745c5c3_Err != nil {3163 if templ_7745c5c3_Err != nil {
2881 return templ_7745c5c3_Err3164 return templ_7745c5c3_Err
2882 }3165 }
2883- var templ_7745c5c3_Var144 string3166+ var templ_7745c5c3_Var157 string
2884- templ_7745c5c3_Var144, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)3167+ templ_7745c5c3_Var157, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.WhenAt)
2885 if templ_7745c5c3_Err != nil {3168 if templ_7745c5c3_Err != nil {
2886- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 42}3169+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 590, Col: 42}
2887 }3170 }
2888- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var144)3171+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var157)
2889 if templ_7745c5c3_Err != nil {3172 if templ_7745c5c3_Err != nil {
2890 return templ_7745c5c3_Err3173 return templ_7745c5c3_Err
2891 }3174 }
2892- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 259, "\">")3175+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 291, "\">")
2893 if templ_7745c5c3_Err != nil {3176 if templ_7745c5c3_Err != nil {
2894 return templ_7745c5c3_Err3177 return templ_7745c5c3_Err
2895 }3178 }
2896- var templ_7745c5c3_Var145 string3179+ var templ_7745c5c3_Var158 string
2897- templ_7745c5c3_Var145, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)3180+ templ_7745c5c3_Var158, templ_7745c5c3_Err = templ.JoinStringErrs(c.When)
2898 if templ_7745c5c3_Err != nil {3181 if templ_7745c5c3_Err != nil {
2899- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 538, Col: 53}3182+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 590, Col: 53}
2900 }3183 }
2901- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var145))3184+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var158))
2902 if templ_7745c5c3_Err != nil {3185 if templ_7745c5c3_Err != nil {
2903 return templ_7745c5c3_Err3186 return templ_7745c5c3_Err
2904 }3187 }
2905- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 260, "</time>")3188+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 292, "</time>")
2906 if templ_7745c5c3_Err != nil {3189 if templ_7745c5c3_Err != nil {
2907 return templ_7745c5c3_Err3190 return templ_7745c5c3_Err
2908 }3191 }
2909 }3192 }
2910- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 261, "</span></li>")3193+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 293, "</span></li>")
2911 if templ_7745c5c3_Err != nil {3194 if templ_7745c5c3_Err != nil {
2912 return templ_7745c5c3_Err3195 return templ_7745c5c3_Err
2913 }3196 }
2914 }3197 }
2915- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 262, "</ul>")3198+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 294, "</ul>")
2916 if templ_7745c5c3_Err != nil {3199 if templ_7745c5c3_Err != nil {
2917 return templ_7745c5c3_Err3200 return templ_7745c5c3_Err
2918 }3201 }
2919 if len(p.Ahead) == 0 {3202 if len(p.Ahead) == 0 {
2920- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 263, "<p class=\"dim\">no commit ahead of ")3203+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 295, "<p class=\"dim\">no commit ahead of ")
2921 if templ_7745c5c3_Err != nil {3204 if templ_7745c5c3_Err != nil {
2922 return templ_7745c5c3_Err3205 return templ_7745c5c3_Err
2923 }3206 }
2924- var templ_7745c5c3_Var146 string3207+ var templ_7745c5c3_Var159 string
2925- templ_7745c5c3_Var146, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)3208+ templ_7745c5c3_Var159, templ_7745c5c3_Err = templ.JoinStringErrs(p.Target)
2926 if templ_7745c5c3_Err != nil {3209 if templ_7745c5c3_Err != nil {
2927- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 545, Col: 47}3210+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 597, Col: 47}
2928 }3211 }
2929- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var146))3212+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var159))
2930 if templ_7745c5c3_Err != nil {3213 if templ_7745c5c3_Err != nil {
2931 return templ_7745c5c3_Err3214 return templ_7745c5c3_Err
2932 }3215 }
2933- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 264, ".</p>")3216+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 296, ".</p>")
2934 if templ_7745c5c3_Err != nil {3217 if templ_7745c5c3_Err != nil {
2935 return templ_7745c5c3_Err3218 return templ_7745c5c3_Err
2936 }3219 }
2937 }3220 }
2938- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 265, "</section>")3221+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 297, "</section>")
2939 if templ_7745c5c3_Err != nil {3222 if templ_7745c5c3_Err != nil {
2940 return templ_7745c5c3_Err3223 return templ_7745c5c3_Err
2941 }3224 }
2942 if p.HasDiff {3225 if p.HasDiff {
2943- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 266, "<section aria-labelledby=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")3226+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 298, "<section aria-labelledby=\"r-files\"><h2 id=\"r-files\">Files <span class=\"count\">")
2944 if templ_7745c5c3_Err != nil {3227 if templ_7745c5c3_Err != nil {
2945 return templ_7745c5c3_Err3228 return templ_7745c5c3_Err
2946 }3229 }
2947- var templ_7745c5c3_Var147 string3230+ var templ_7745c5c3_Var160 string
2948- templ_7745c5c3_Var147, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))3231+ templ_7745c5c3_Var160, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(p.Files)))
2949 if templ_7745c5c3_Err != nil {3232 if templ_7745c5c3_Err != nil {
2950- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 550, Col: 66}3233+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/adapters/web/mr.templ`, Line: 602, Col: 66}
2951 }3234 }
2952- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var147))3235+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var160))
2953 if templ_7745c5c3_Err != nil {3236 if templ_7745c5c3_Err != nil {
2954 return templ_7745c5c3_Err3237 return templ_7745c5c3_Err
2955 }3238 }
2956- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 267, "</span></h2>")3239+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 299, "</span></h2>")
2957 if templ_7745c5c3_Err != nil {3240 if templ_7745c5c3_Err != nil {
2958 return templ_7745c5c3_Err3241 return templ_7745c5c3_Err
2959 }3242 }
@@ -2961,12 +3244,12 @@ func compareRail(p ComparePage) templ.Component {
2961 if templ_7745c5c3_Err != nil {3244 if templ_7745c5c3_Err != nil {
2962 return templ_7745c5c3_Err3245 return templ_7745c5c3_Err
2963 }3246 }
2964- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 268, "</section>")3247+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 300, "</section>")
2965 if templ_7745c5c3_Err != nil {3248 if templ_7745c5c3_Err != nil {
2966 return templ_7745c5c3_Err3249 return templ_7745c5c3_Err
2967 }3250 }
2968 }3251 }
2969- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 269, "<section aria-labelledby=\"r-cli\"><h2 id=\"r-cli\">From a terminal</h2>")3252+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 301, "<section aria-labelledby=\"r-cli\"><h2 id=\"r-cli\">From a terminal</h2>")
2970 if templ_7745c5c3_Err != nil {3253 if templ_7745c5c3_Err != nil {
2971 return templ_7745c5c3_Err3254 return templ_7745c5c3_Err
2972 }3255 }
@@ -2974,7 +3257,7 @@ func compareRail(p ComparePage) templ.Component {
2974 if templ_7745c5c3_Err != nil {3257 if templ_7745c5c3_Err != nil {
2975 return templ_7745c5c3_Err3258 return templ_7745c5c3_Err
2976 }3259 }
2977- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 270, "</section>")3260+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 302, "</section>")
2978 if templ_7745c5c3_Err != nil {3261 if templ_7745c5c3_Err != nil {
2979 return templ_7745c5c3_Err3262 return templ_7745c5c3_Err
2980 }3263 }
M internal/adapters/web/static/css/55-diff.css +4 -0
@@ -6,3 +6,7 @@
66 }
77 .eol { color: var(--ink-mute); padding-left: 1ch; user-select: none; }
88 }
9+
10+@layer components {
11+ .sel { outline: 1px solid var(--ink-dim); outline-offset: -1px; }
12+}
@@ -6,3 +6,7 @@
6 }6 }
7 .eol { color: var(--ink-mute); padding-left: 1ch; user-select: none; }7 .eol { color: var(--ink-mute); padding-left: 1ch; user-select: none; }
8 }8 }
9+
10+@layer components {
11+ .sel { outline: 1px solid var(--ink-dim); outline-offset: -1px; }
12+}
M internal/adapters/web/static/css/60-mr.css +7 -0
@@ -190,3 +190,10 @@
190190 .list.mrs .c-who { display: none; }
191191 }
192192 }
193+
194+@layer components {
195+ .c-body { white-space: normal; overflow-wrap: anywhere; }
196+ .c-body.prose > * { margin-block: .5em; }
197+ .c-body .ref { font-variant-numeric: tabular-nums; text-decoration: underline; }
198+ .cmt > .cmt { margin-inline-start: 2ch; }
199+}
@@ -190,3 +190,10 @@
190 .list.mrs .c-who { display: none; }190 .list.mrs .c-who { display: none; }
191 }191 }
192 }192 }
193+
194+@layer components {
195+ .c-body { white-space: normal; overflow-wrap: anywhere; }
196+ .c-body.prose > * { margin-block: .5em; }
197+ .c-body .ref { font-variant-numeric: tabular-nums; text-decoration: underline; }
198+ .cmt > .cmt { margin-inline-start: 2ch; }
199+}
M internal/adapters/web/static/js/mr.js +31 -0
@@ -18,6 +18,25 @@
1818 window.ermiteMR = (() => {
1919 // core: the decisions, without the DOM.
2020 const core = {
21+ rangeURL(previous, current, base) {
22+ const a = new URL(previous, base), b = new URL(current, base);
23+ const parse = (u) => /^(new|old):(\d+):(.+)$/.exec(u.searchParams.get('on') || '');
24+ const x = parse(a), y = parse(b);
25+ if (!x || !y || a.pathname !== b.pathname || x[1] !== y[1] || x[3] !== y[3] || x[2] === y[2]) return null;
26+ const nums = [Number(x[2]), Number(y[2])].sort((a,b) => a-b);
27+ b.searchParams.set('on', `${y[1]}:${nums[0]}-${nums[1]}:${y[3]}`);
28+ return b.href;
29+ },
30+ quote(button) {
31+ const thread = button.closest('.cmt').closest('[data-thread]');
32+ const details = thread?.querySelector(':scope > details[data-reply]');
33+ const field = details?.querySelector('textarea');
34+ if (!field) return false;
35+ details.open = true;
36+ field.value = button.dataset.quote;
37+ field.focus();
38+ return true;
39+ },
2140 // windowOf is the start of the window of file i (files are f1, f2, ...).
2241 windowOf(id, size) {
2342 const n = Number(String(id).replace(/^f/, '')) - 1;
@@ -262,6 +281,18 @@ window.ermiteMR = (() => {
262281 };
263282 addEventListener('hashchange', hash);
264283 hash();
284+ let lastLine = null;
285+ try { lastLine = sessionStorage.getItem('mr-last-line'); } catch { /* storage optional */ }
286+ addEventListener('click', (e) => {
287+ const quote = e.target.closest?.('[data-quote]');
288+ if (quote) { core.quote(quote); return; }
289+ const link = e.target.closest?.('a[href*="on="]');
290+ if (!link || e.ctrlKey || e.metaKey || e.altKey) return;
291+ const next = e.shiftKey && lastLine && core.rangeURL(lastLine, link.href, location.href);
292+ lastLine = link.href;
293+ try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ }
294+ if (next) { e.preventDefault(); location.assign(next); }
295+ });
265296 // A click outside an open menu closes it.
266297 addEventListener('click', (e) => {
267298 for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;
@@ -18,6 +18,25 @@
18 window.ermiteMR = (() => {18 window.ermiteMR = (() => {
19 // core: the decisions, without the DOM.19 // core: the decisions, without the DOM.
20 const core = {20 const core = {
21+ rangeURL(previous, current, base) {
22+ const a = new URL(previous, base), b = new URL(current, base);
23+ const parse = (u) => /^(new|old):(\d+):(.+)$/.exec(u.searchParams.get('on') || '');
24+ const x = parse(a), y = parse(b);
25+ if (!x || !y || a.pathname !== b.pathname || x[1] !== y[1] || x[3] !== y[3] || x[2] === y[2]) return null;
26+ const nums = [Number(x[2]), Number(y[2])].sort((a,b) => a-b);
27+ b.searchParams.set('on', `${y[1]}:${nums[0]}-${nums[1]}:${y[3]}`);
28+ return b.href;
29+ },
30+ quote(button) {
31+ const thread = button.closest('.cmt').closest('[data-thread]');
32+ const details = thread?.querySelector(':scope > details[data-reply]');
33+ const field = details?.querySelector('textarea');
34+ if (!field) return false;
35+ details.open = true;
36+ field.value = button.dataset.quote;
37+ field.focus();
38+ return true;
39+ },
21 // windowOf is the start of the window of file i (files are f1, f2, ...).40 // windowOf is the start of the window of file i (files are f1, f2, ...).
22 windowOf(id, size) {41 windowOf(id, size) {
23 const n = Number(String(id).replace(/^f/, '')) - 1;42 const n = Number(String(id).replace(/^f/, '')) - 1;
@@ -262,6 +281,18 @@ window.ermiteMR = (() => {
262 };281 };
263 addEventListener('hashchange', hash);282 addEventListener('hashchange', hash);
264 hash();283 hash();
284+ let lastLine = null;
285+ try { lastLine = sessionStorage.getItem('mr-last-line'); } catch { /* storage optional */ }
286+ addEventListener('click', (e) => {
287+ const quote = e.target.closest?.('[data-quote]');
288+ if (quote) { core.quote(quote); return; }
289+ const link = e.target.closest?.('a[href*="on="]');
290+ if (!link || e.ctrlKey || e.metaKey || e.altKey) return;
291+ const next = e.shiftKey && lastLine && core.rangeURL(lastLine, link.href, location.href);
292+ lastLine = link.href;
293+ try { sessionStorage.setItem('mr-last-line', lastLine); } catch { /* storage optional */ }
294+ if (next) { e.preventDefault(); location.assign(next); }
295+ });
265 // A click outside an open menu closes it.296 // A click outside an open menu closes it.
266 addEventListener('click', (e) => {297 addEventListener('click', (e) => {
267 for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;298 for (const d of $$('details.menu[open]')) if (!d.contains(e.target)) d.open = false;
M internal/adapters/web/web.go +2 -0
@@ -190,6 +190,8 @@ func (s *Server) routes() *http.ServeMux {
190190 mux.HandleFunc("POST /{repo}/merge-requests", s.mrAction(s.mrCreate))
191191 mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments", s.mrAction(s.mrComment))
192192 mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments/{id}/resolve", s.mrAction(s.mrResolve))
193+ mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments/{id}/edit", s.mrAction(s.mrEditComment))
194+ mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments/{id}/delete", s.mrAction(s.mrDeleteComment))
193195 mux.HandleFunc("POST /{repo}/merge-requests/{n}/merge", s.mrAction(s.mrMerge))
194196 mux.HandleFunc("POST /{repo}/merge-requests/{n}/close", s.mrAction(s.mrClose))
195197 mux.HandleFunc("POST /{repo}/merge-requests/{n}/ready", s.mrAction(s.mrReady))
@@ -190,6 +190,8 @@ func (s *Server) routes() *http.ServeMux {
190 mux.HandleFunc("POST /{repo}/merge-requests", s.mrAction(s.mrCreate))190 mux.HandleFunc("POST /{repo}/merge-requests", s.mrAction(s.mrCreate))
191 mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments", s.mrAction(s.mrComment))191 mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments", s.mrAction(s.mrComment))
192 mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments/{id}/resolve", s.mrAction(s.mrResolve))192 mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments/{id}/resolve", s.mrAction(s.mrResolve))
193+ mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments/{id}/edit", s.mrAction(s.mrEditComment))
194+ mux.HandleFunc("POST /{repo}/merge-requests/{n}/comments/{id}/delete", s.mrAction(s.mrDeleteComment))
193 mux.HandleFunc("POST /{repo}/merge-requests/{n}/merge", s.mrAction(s.mrMerge))195 mux.HandleFunc("POST /{repo}/merge-requests/{n}/merge", s.mrAction(s.mrMerge))
194 mux.HandleFunc("POST /{repo}/merge-requests/{n}/close", s.mrAction(s.mrClose))196 mux.HandleFunc("POST /{repo}/merge-requests/{n}/close", s.mrAction(s.mrClose))
195 mux.HandleFunc("POST /{repo}/merge-requests/{n}/ready", s.mrAction(s.mrReady))197 mux.HandleFunc("POST /{repo}/merge-requests/{n}/ready", s.mrAction(s.mrReady))
M internal/app/web_mr_test.go +6 -3
@@ -284,7 +284,7 @@ func TestWebMergeRequestReviewAndFastForward(t *testing.T) {
284284 require.GreaterOrEqual(t, i, 0, "a note row under the line")
285285 note := anon.body[i:]
286286 assert.Less(t, strings.Index(note, "Why &lt;i&gt;empty&lt;/i&gt;?"), strings.Index(note, "</tr>"), "the note sits in the row under its line")
287- assert.Contains(t, anon.body, `<a href="https://example.org/a" rel="noopener nofollow ugc">https://example.org/a</a>.`)
287+ assert.Contains(t, anon.body, `<a href="https://example.org/a" rel="nofollow noopener noreferrer">https://example.org/a</a>.`)
288288 assert.NotContains(t, anon.body, "<i>empty</i>")
289289 assert.Contains(t, anon.body, "Looks fine overall.")
290290 assert.Equal(t, 2, len(reNoteID.FindAllString(anon.body, -1)))
@@ -338,7 +338,8 @@ func TestWebMergeRequestReviewAndFastForward(t *testing.T) {
338338 assert.Contains(t, since.body, "changes since "+head1[:7])
339339 assert.Contains(t, since.body, "extra</td>")
340340 assert.NotContains(t, since.body, `<span class="kn">package</span> search</td>`, "only what changed after the read head")
341- assert.Contains(t, since.body, "Why &lt;i&gt;empty&lt;/i&gt;?", "the notes are listed below, their lines are not in this diff")
341+ assert.Contains(t, since.body, "empty", "the notes are listed below, their lines are not in this diff")
342+ assert.NotContains(t, since.body, "<i>", "raw HTML is dropped")
342343 assert.Contains(t, since.body, "search.go:3 (new)")
343344 assert.NotContains(t, since.body, `<form class="cform"`, "no comment form on this diff")
344345
@@ -554,7 +555,9 @@ func TestWebMergeRequestEscapesText(t *testing.T) {
554555 assert.NotContains(t, body, "<img src=x")
555556 assert.NotContains(t, body, "<script>x</script>")
556557 assert.Contains(t, html.UnescapeString(body), `"><script>alert(1)</script>`)
557- assert.Contains(t, body, "&lt;script&gt;x&lt;/script&gt; &amp; more")
558+ // A note is safe Markdown: a line that opens with an HTML tag is an HTML
559+ // block, dropped whole.
560+ assert.Contains(t, body, `class="c-body prose"><!-- raw HTML omitted -->`)
558561 assertNoInline(t, body)
559562 assert.NotContains(t, body, `onmouseover="alert`, "an address stops at a quote")
560563 }
@@ -284,7 +284,7 @@ func TestWebMergeRequestReviewAndFastForward(t *testing.T) {
284 require.GreaterOrEqual(t, i, 0, "a note row under the line")284 require.GreaterOrEqual(t, i, 0, "a note row under the line")
285 note := anon.body[i:]285 note := anon.body[i:]
286 assert.Less(t, strings.Index(note, "Why &lt;i&gt;empty&lt;/i&gt;?"), strings.Index(note, "</tr>"), "the note sits in the row under its line")286 assert.Less(t, strings.Index(note, "Why &lt;i&gt;empty&lt;/i&gt;?"), strings.Index(note, "</tr>"), "the note sits in the row under its line")
287- assert.Contains(t, anon.body, `<a href="https://example.org/a" rel="noopener nofollow ugc">https://example.org/a</a>.`)287+ assert.Contains(t, anon.body, `<a href="https://example.org/a" rel="nofollow noopener noreferrer">https://example.org/a</a>.`)
288 assert.NotContains(t, anon.body, "<i>empty</i>")288 assert.NotContains(t, anon.body, "<i>empty</i>")
289 assert.Contains(t, anon.body, "Looks fine overall.")289 assert.Contains(t, anon.body, "Looks fine overall.")
290 assert.Equal(t, 2, len(reNoteID.FindAllString(anon.body, -1)))290 assert.Equal(t, 2, len(reNoteID.FindAllString(anon.body, -1)))
@@ -338,7 +338,8 @@ func TestWebMergeRequestReviewAndFastForward(t *testing.T) {
338 assert.Contains(t, since.body, "changes since "+head1[:7])338 assert.Contains(t, since.body, "changes since "+head1[:7])
339 assert.Contains(t, since.body, "extra</td>")339 assert.Contains(t, since.body, "extra</td>")
340 assert.NotContains(t, since.body, `<span class="kn">package</span> search</td>`, "only what changed after the read head")340 assert.NotContains(t, since.body, `<span class="kn">package</span> search</td>`, "only what changed after the read head")
341- assert.Contains(t, since.body, "Why &lt;i&gt;empty&lt;/i&gt;?", "the notes are listed below, their lines are not in this diff")341+ assert.Contains(t, since.body, "empty", "the notes are listed below, their lines are not in this diff")
342+ assert.NotContains(t, since.body, "<i>", "raw HTML is dropped")
342 assert.Contains(t, since.body, "search.go:3 (new)")343 assert.Contains(t, since.body, "search.go:3 (new)")
343 assert.NotContains(t, since.body, `<form class="cform"`, "no comment form on this diff")344 assert.NotContains(t, since.body, `<form class="cform"`, "no comment form on this diff")
344 345
@@ -554,7 +555,9 @@ func TestWebMergeRequestEscapesText(t *testing.T) {
554 assert.NotContains(t, body, "<img src=x")555 assert.NotContains(t, body, "<img src=x")
555 assert.NotContains(t, body, "<script>x</script>")556 assert.NotContains(t, body, "<script>x</script>")
556 assert.Contains(t, html.UnescapeString(body), `"><script>alert(1)</script>`)557 assert.Contains(t, html.UnescapeString(body), `"><script>alert(1)</script>`)
557- assert.Contains(t, body, "&lt;script&gt;x&lt;/script&gt; &amp; more")558+ // A note is safe Markdown: a line that opens with an HTML tag is an HTML
559+ // block, dropped whole.
560+ assert.Contains(t, body, `class="c-body prose"><!-- raw HTML omitted -->`)
558 assertNoInline(t, body)561 assertNoInline(t, body)
559 assert.NotContains(t, body, `onmouseover="alert`, "an address stops at a quote")562 assert.NotContains(t, body, `onmouseover="alert`, "an address stops at a quote")
560 }563 }

Keyboard shortcuts

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

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

Branches 2

Tags 45

↑↓ moveenter openesc close