Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Serve repositories from the root of the URL space

ed03b3d1fa16ef6b55e2773aaf10e3be13282c3a · Keenan Nemetz · 4h ago · parent d37d301f

Serve repositories from the root of the URL space

A repository is now /<name> and its Git transport /<name>.git, so the URL a
person opens, the URL a clone uses, and the Go module path are the same name.
The forge's own pages moved under /-/ (admin, accounts, the directory, the
feed, sign-in, assets), a prefix no repository name can start with: names are
[a-z0-9...] and cannot begin with "-". That leaves the router with two disjoint
spaces and removes the /r/ and /git/ prefixes, which existed only to keep
repositories away from the forge's own paths.

Routes: repoRoutes lose /r/; global, auth and admin routes gain /-/; assets
move to /-/static/ because a subtree pattern like /static/ cannot coexist with
one-segment-wildcard repo routes — Go's mux reports them as conflicting
neither-more-specific patterns. The catch-all still renders the styled 404.

Transport: githttp.Route parses /<name>.git/<rest> instead of a /git/ prefix,
and forgepaths.go hands such requests to the proxy before the mux, because a
wildcard fills a whole segment and cannot match "<name>.git". LFS stays on the
mux (<name>.git/info/lfs/...), and the Go module discovery probe moves to the
same pre-router so a package path under a public repository is answered even
when its first segments name a page tab.

Names: repo.ValidateName now refuses the four root paths the forge must keep —
static, robots.txt, sitemap.xml, sitemap-pages.xml. Everything else is an
ordinary repository name, including admin, u, and activity.

Docs and the packaged URLs follow the new layout; robots.txt, the sitemaps and
the instance activity feed point at the new paths. No aliases: the old /r/ and
/git/ URLs are gone.

Verified with ./vole workflow check and ./vole workflow run verify.

server/web/templates/requests.html +32−32

@@ -7,7 +7,7 @@
7 7 <h1>Changes</h1>
8 8 <p class="page-sub">Proposed updates to the project, reviewed and approved before they are merged.</p>
9 9 </div>
10 {{if .CanOpen}}<div class="page-head-actions"><a class="button" href="/r/{{.Name}}/changes/new">{{icon "plus"}}New change</a></div>{{end}}
10 {{if .CanOpen}}<div class="page-head-actions"><a class="button" href="/{{.Name}}/changes/new">{{icon "plus"}}New change</a></div>{{end}}
11 11 </div>
12 12
13 13 {{template "change_list" .}}
@@ -21,18 +21,18 @@
21 21 {{/* One row: state chips on the left, search on the right. The chips live
22 22 inside the form so a search keeps the state, and the whole row is replaced
23 23 with the server's rendering after a search or a chip click. */}}
24 <form method="get" action="/r/{{.Name}}/changes" class="filters" role="search" hx-get="/r/{{.Name}}/changes" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">
24 <form method="get" action="/{{.Name}}/changes" class="filters" role="search" hx-get="/{{.Name}}/changes" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">
25 25 <div class="chips" hx-boost="true" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">
26 <a class="chip{{if eq .State "open"}} active{{end}}" href="/r/{{.Name}}/changes?state=open{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Open <span class="chip-count">{{.Open}}</span></a>
27 <a class="chip{{if eq .State "merged"}} active{{end}}" href="/r/{{.Name}}/changes?state=merged{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Merged <span class="chip-count">{{.Merged}}</span></a>
28 <a class="chip{{if eq .State "closed"}} active{{end}}" href="/r/{{.Name}}/changes?state=closed{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Closed <span class="chip-count">{{.Closed}}</span></a>
29 <a class="chip{{if eq .State "all"}} active{{end}}" href="/r/{{.Name}}/changes?state=all{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">All <span class="chip-count">{{.Total}}</span></a>
26 <a class="chip{{if eq .State "open"}} active{{end}}" href="/{{.Name}}/changes?state=open{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Open <span class="chip-count">{{.Open}}</span></a>
27 <a class="chip{{if eq .State "merged"}} active{{end}}" href="/{{.Name}}/changes?state=merged{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Merged <span class="chip-count">{{.Merged}}</span></a>
28 <a class="chip{{if eq .State "closed"}} active{{end}}" href="/{{.Name}}/changes?state=closed{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">Closed <span class="chip-count">{{.Closed}}</span></a>
29 <a class="chip{{if eq .State "all"}} active{{end}}" href="/{{.Name}}/changes?state=all{{if .Query}}&amp;q={{urlquery .Query}}{{end}}">All <span class="chip-count">{{.Total}}</span></a>
30 30 </div>
31 31 <input type="hidden" name="state" value="{{.State}}">
32 32 <div class="filters-search">
33 33 <input name="q" type="search" placeholder="Search title, description and author" aria-label="Search title, description and author" value="{{.Query}}">
34 34 <button type="submit">{{icon "search"}}Search</button>
35 {{if .Filtered}}<a class="button secondary" href="/r/{{.Name}}/changes" hx-get="/r/{{.Name}}/changes" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">{{icon "x"}}Clear</a>{{end}}
35 {{if .Filtered}}<a class="button secondary" href="/{{.Name}}/changes" hx-get="/{{.Name}}/changes" hx-target="#change-list" hx-swap="outerHTML" hx-push-url="true">{{icon "x"}}Clear</a>{{end}}
36 36 </div>
37 37 </form>
38 38
@@ -47,7 +47,7 @@
47 47 <tr>
48 48 <td class="fit">{{template "change_status" .Status}}</td>
49 49 <td>
50 <a class="row-title" href="/r/{{$.Name}}/changes/{{.ID}}">{{.Title}}</a>
50 <a class="row-title" href="/{{$.Name}}/changes/{{.ID}}">{{.Title}}</a>
51 51 <div class="row-meta">
52 52 <span class="sha">{{num .Number}}</span>
53 53 <span>opened by {{template "userlink" (user .Author)}} {{since .Created.Unix}}</span>
@@ -91,7 +91,7 @@
91 91
92 92 {{define "request_changes"}}
93 93 <section id="request-changes"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
94 <h2 class="with-more"><span>{{icon "file"}}Changes</span>{{if .FileCount}} <a class="more" href="/r/{{.Name}}/changes/{{.Request.ID}}/files">View changes</a>{{end}}</h2>
94 <h2 class="with-more"><span>{{icon "file"}}Changes</span>{{if .FileCount}} <a class="more" href="/{{.Name}}/changes/{{.Request.ID}}/files">View changes</a>{{end}}</h2>
95 95 {{if .FileCount}}
96 96 <p class="muted">{{.FileCount}} file(s) changed{{if .Added}} · <span class="adds">+{{.Added}}</span>{{end}}{{if .Removed}} · <span class="dels">−{{.Removed}}</span>{{end}}</p>
97 97 {{else if .AlreadyMerged}}
@@ -107,7 +107,7 @@
107 107 target picker, or an open composer is not thrown away. */}}
108 108 {{define "request_aside"}}
109 109 <div id="request-aside"
110 hx-get="/r/{{.Name}}/changes/{{.Request.ID}}/live"
110 hx-get="/{{.Name}}/changes/{{.Request.ID}}/live"
111 111 hx-trigger="every 20s [document.visibilityState === 'visible']"
112 112 hx-sync="this:replace"
113 113 hx-swap="none">
@@ -158,7 +158,7 @@
158 158
159 159 {{define "request_review_status"}}
160 160 <div id="request-review-status"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
161 <h2 class="with-more"><span>{{icon "git-branch"}}Review{{template "help" "The request follows its head branch, so a push moves the head with it. Changing the base or head starts approvals over. A conflict here blocks landing."}}</span> <a class="more sha" href="/r/{{.Name}}/commit/{{.Request.Head}}" title="commit under review">{{short .Request.Head}}</a></h2>
161 <h2 class="with-more"><span>{{icon "git-branch"}}Review{{template "help" "The request follows its head branch, so a push moves the head with it. Changing the base or head starts approvals over. A conflict here blocks landing."}}</span> <a class="more sha" href="/{{.Name}}/commit/{{.Request.Head}}" title="commit under review">{{short .Request.Head}}</a></h2>
162 162 {{if .MergeChecked}}{{if .Conflicts}}
163 163 <p class="conflict">This branch no longer merges into {{.Request.Base}} cleanly:
164 164 {{range .Conflicts}}<code>{{.}}</code> {{end}}— rebase or merge {{.Request.Base}} before landing it.</p>
@@ -170,7 +170,7 @@
170 170
171 171 {{define "request_target"}}
172 172 {{if .CanRetarget}}
173 <form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/target" class="request-target">
173 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/target" class="request-target">
174 174 <input type="hidden" name="csrf" value="{{.CSRF}}">
175 175 <div class="target-fields">
176 176 <label class="target-field">
@@ -221,14 +221,14 @@
221 221
222 222 {{define "request_checks"}}
223 223 <section id="request-checks"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
224 <h2 class="with-more"><span>{{icon "circle-check"}}Checks{{template "help" "Workflow runs for the commit under review. If the base branch requires checks, a failing or missing run blocks landing."}}</span> <a class="more" href="/r/{{.Name}}/changes/{{.Request.ID}}/runs">View runs</a></h2>
224 <h2 class="with-more"><span>{{icon "circle-check"}}Checks{{template "help" "Workflow runs for the commit under review. If the base branch requires checks, a failing or missing run blocks landing."}}</span> <a class="more" href="/{{.Name}}/changes/{{.Request.ID}}/runs">View runs</a></h2>
225 225 {{with .ContributorCI}}
226 226 <p class="muted">CI is waiting for a maintainer to allow <code>{{.Ref}}</code> at this commit.</p>
227 227 {{end}}
228 228 {{if .Checks}}
229 229 <div class="side-list">
230 230 {{range .Checks}}
231 <a class="side-run" href="/r/{{$.Name}}/runs/{{.ID}}">
231 <a class="side-run" href="/{{$.Name}}/runs/{{.ID}}">
232 232 <span class="side-run-top">
233 233 <span class="status {{.Status}}">{{.Status}}</span>
234 234 <span class="side-subject">{{.JobTitle}}</span>
@@ -246,7 +246,7 @@
246 246 {{define "request_combined"}}
247 247 {{if .CombinedNeeded}}
248 248 <section id="request-combined"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
249 <h2 class="with-more"><span>{{icon "git-merge"}}Combined result{{template "help" "The merge that would land, built from this change and the latest base. It is tested automatically so the branch is ready when the review is. Only a conflict needs work on the branch."}}</span> {{if .CombinedSHA}}<a class="more sha" href="/r/{{.Name}}/commit/{{.CombinedSHA}}" title="combined result commit">{{short .CombinedSHA}}</a>{{end}}</h2>
249 <h2 class="with-more"><span>{{icon "git-merge"}}Combined result{{template "help" "The merge that would land, built from this change and the latest base. It is tested automatically so the branch is ready when the review is. Only a conflict needs work on the branch."}}</span> {{if .CombinedSHA}}<a class="more sha" href="/{{.Name}}/commit/{{.CombinedSHA}}" title="combined result commit">{{short .CombinedSHA}}</a>{{end}}</h2>
250 250 {{if eq .CombinedState "passed"}}
251 251 <p class="muted">Passed with the latest <code>{{.Request.Base}}</code>.</p>
252 252 {{else if eq .CombinedState "testing"}}
@@ -259,7 +259,7 @@
259 259 {{if .CombinedRuns}}
260 260 <div class="side-list">
261 261 {{range .CombinedRuns}}
262 <a class="side-run" href="/r/{{$.Name}}/runs/{{.ID}}">
262 <a class="side-run" href="/{{$.Name}}/runs/{{.ID}}">
263 263 <span class="side-run-top">
264 264 <span class="status {{.Status}}">{{.Status}}</span>
265 265 <span class="side-subject">{{.JobTitle}}</span>
@@ -282,12 +282,12 @@
282 282 <div class="side-list">
283 283 {{range .LinkedIssues}}
284 284 <div class="request-issue">
285 <a class="side-issue" href="/r/{{$.Name}}/issues/{{.ID}}">
285 <a class="side-issue" href="/{{$.Name}}/issues/{{.ID}}">
286 286 <span class="side-subject"><span class="side-num">{{num .Number}}</span>{{.Title}}</span>
287 287 <span class="side-meta">{{who .Author}}{{if not .Created.IsZero}}<span>{{since .Created.Unix}}</span>{{end}}</span>
288 288 </a>
289 289 {{if $.CanEditIssues}}
290 <form method="post" action="/r/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-post="/r/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-target="#request-issues" hx-swap="outerHTML">
290 <form method="post" action="/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-post="/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-target="#request-issues" hx-swap="outerHTML">
291 291 <input type="hidden" name="csrf" value="{{$.CSRF}}">
292 292 <input type="hidden" name="issue" value="{{.ID}}">
293 293 <button class="link request-unlink" type="submit" name="action" value="remove" title="Unlink this issue" aria-label="Unlink {{num .Number}} from this change">{{icon "unlink"}}</button>
@@ -301,7 +301,7 @@
301 301 {{end}}
302 302 {{if .CanEditIssues}}
303 303 <div class="request-issue-picker">
304 <form class="request-issue-search" hx-get="/r/{{.Name}}/changes/{{.Request.ID}}/issues/search" hx-trigger="input changed delay:250ms, search, submit" hx-target="#request-issue-results" hx-swap="innerHTML">
304 <form class="request-issue-search" hx-get="/{{.Name}}/changes/{{.Request.ID}}/issues/search" hx-trigger="input changed delay:250ms, search, submit" hx-target="#request-issue-results" hx-swap="innerHTML">
305 305 <input id="request-issue-search" name="q" type="search" placeholder="Search by title, #number, or ULID" aria-label="Search issues by title, number, or ULID" autocomplete="off">
306 306 </form>
307 307 <div id="request-issue-results" class="request-issue-results" aria-live="polite"></div>
@@ -318,7 +318,7 @@
318 318 {{else if .Results}}
319 319 <div class="request-issue-matches">
320 320 {{range .Results}}
321 <form method="post" action="/r/{{$.Name}}/changes/{{$.Request.ID}}/issues" class="request-issue-match" hx-post="/r/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-target="#request-issues" hx-swap="outerHTML">
321 <form method="post" action="/{{$.Name}}/changes/{{$.Request.ID}}/issues" class="request-issue-match" hx-post="/{{$.Name}}/changes/{{$.Request.ID}}/issues" hx-target="#request-issues" hx-swap="outerHTML">
322 322 <input type="hidden" name="csrf" value="{{$.CSRF}}">
323 323 <input type="hidden" name="issue" value="{{.ID}}">
324 324 <button type="submit" name="action" value="add" title="Link {{num .Number}}">
@@ -337,7 +337,7 @@
337 337 {{define "request_inline_comment"}}
338 338 <tr class="inline-comment">
339 339 <td colspan="4">
340 <form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/comments" class="inline-comment-form">
340 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/comments" class="inline-comment-form">
341 341 <input type="hidden" name="csrf" value="{{.CSRF}}">
342 342 <input type="hidden" name="path" value="{{.Target.Path}}">
343 343 <input type="hidden" name="line" value="{{.Target.Line}}">
@@ -362,9 +362,9 @@
362 362 <span class="page-num">{{num .Number}}</span>
363 363 <h1 class="details-title-view" data-details-title-view{{if .Details.Title.Active}} hidden{{end}}>{{.Request.Title}}</h1>
364 364 {{if .CanEdit}}
365 <a class="title-edit-toggle" href="/r/{{.Name}}/changes/{{.Request.ID}}/edit"{{if .Details.Inline}} data-title-edit{{end}}{{if .Details.Title.Active}} hidden{{end}} aria-label="Edit title" title="Edit title">{{icon "pencil"}}</a>
365 <a class="title-edit-toggle" href="/{{.Name}}/changes/{{.Request.ID}}/edit"{{if .Details.Inline}} data-title-edit{{end}}{{if .Details.Title.Active}} hidden{{end}} aria-label="Edit title" title="Edit title">{{icon "pencil"}}</a>
366 366 {{if .Details.Inline}}
367 <form class="title-edit-form" data-title-edit-form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/edit" hx-post="/r/{{.Name}}/changes/{{.Request.ID}}/edit" hx-target="#request-head" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Title.Active}} hidden{{end}}>
367 <form class="title-edit-form" data-title-edit-form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-post="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-target="#request-head" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Title.Active}} hidden{{end}}>
368 368 <input type="hidden" name="csrf" value="{{.CSRF}}">
369 369 <input type="hidden" name="field" value="title">
370 370 <input class="details-title-input" type="text" name="title" required maxlength="200" aria-label="Title" value="{{if .Details.Title.Active}}{{.Details.Title.Value}}{{else}}{{.Request.Title}}{{end}}" data-title-edit-input{{if .Details.Title.Active}} autofocus{{end}}>
@@ -402,7 +402,7 @@
402 402 {{if or .CanApprove .CanClose .CanMerge .MergeArmed .ContributorCI .Live}}
403 403 <div id="request-actions" class="page-head-actions"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>
404 404 {{with .ContributorCI}}{{if .CanAllow}}
405 <form method="post" action="/r/{{$.Name}}/ci/allow">
405 <form method="post" action="/{{$.Name}}/ci/allow">
406 406 <input type="hidden" name="csrf" value="{{$.CSRF}}">
407 407 <input type="hidden" name="request" value="{{$.Request.ID}}">
408 408 <input type="hidden" name="ref" value="refs/heads/{{.Ref}}">
@@ -410,7 +410,7 @@
410 410 </form>
411 411 {{end}}{{end}}
412 412 {{if .CanApprove}}
413 <form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/approve" hx-post="/r/{{.Name}}/changes/{{.Request.ID}}/approve" hx-swap="none" hx-disabled-elt="find button[type='submit']">
413 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/approve" hx-post="/{{.Name}}/changes/{{.Request.ID}}/approve" hx-swap="none" hx-disabled-elt="find button[type='submit']">
414 414 <input type="hidden" name="csrf" value="{{.CSRF}}">
415 415 <input type="hidden" name="base" value="{{.Request.Base}}">
416 416 <input type="hidden" name="head" value="{{.Request.Head}}">
@@ -418,18 +418,18 @@
418 418 </form>
419 419 {{end}}
420 420 {{if .MergeArmed}}
421 <form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/merge/cancel">
421 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/merge/cancel">
422 422 <input type="hidden" name="csrf" value="{{.CSRF}}">
423 423 <button class="secondary" type="submit">{{icon "circle-x"}}Cancel merge</button>
424 424 </form>
425 425 {{else if .CanMerge}}
426 <form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/merge">
426 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/merge">
427 427 <input type="hidden" name="csrf" value="{{.CSRF}}">
428 428 <button class="success" type="submit"{{if .MergeBlocked}} disabled{{end}}>{{icon "git-merge"}}{{if .MergePending}}Merge when ready{{else}}Merge{{end}}</button>
429 429 </form>
430 430 {{end}}
431 431 {{if .CanClose}}
432 <form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/state">
432 <form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/state">
433 433 <input type="hidden" name="csrf" value="{{.CSRF}}">
434 434 <input type="hidden" name="state" value="{{if eq .Request.State "open"}}closed{{else}}open{{end}}">
435 435 <button class="{{if eq .Request.State "open"}}danger{{else}}secondary{{end}}" type="submit">{{if eq .Request.State "open"}}{{icon "circle-x"}}Close{{else}}{{icon "rotate-ccw"}}Reopen{{end}}</button>
@@ -442,9 +442,9 @@
442 442 {{define "request_tabs"}}
443 443 <div class="tabrow subtabs">
444 444 <nav class="tabs" aria-label="Change sections">
445 <a class="{{if eq .Tab "conversation"}}active{{end}}" href="/r/{{.Name}}/changes/{{.Request.ID}}"{{if eq .Tab "conversation"}} aria-current="page"{{end}}>{{icon "message-square"}}Conversation</a>
446 <a class="{{if eq .Tab "files"}}active{{end}}" href="/r/{{.Name}}/changes/{{.Request.ID}}/files"{{if eq .Tab "files"}} aria-current="page"{{end}}>{{icon "file"}}Changes <span class="chip-count">{{.FileCount}}</span></a>
447 <a class="{{if eq .Tab "runs"}}active{{end}}" href="/r/{{.Name}}/changes/{{.Request.ID}}/runs"{{if eq .Tab "runs"}} aria-current="page"{{end}}>{{icon "play"}}Runs <span class="chip-count">{{len .Checks}}</span></a>
445 <a class="{{if eq .Tab "conversation"}}active{{end}}" href="/{{.Name}}/changes/{{.Request.ID}}"{{if eq .Tab "conversation"}} aria-current="page"{{end}}>{{icon "message-square"}}Conversation</a>
446 <a class="{{if eq .Tab "files"}}active{{end}}" href="/{{.Name}}/changes/{{.Request.ID}}/files"{{if eq .Tab "files"}} aria-current="page"{{end}}>{{icon "file"}}Changes <span class="chip-count">{{.FileCount}}</span></a>
447 <a class="{{if eq .Tab "runs"}}active{{end}}" href="/{{.Name}}/changes/{{.Request.ID}}/runs"{{if eq .Tab "runs"}} aria-current="page"{{end}}>{{icon "play"}}Runs <span class="chip-count">{{len .Checks}}</span></a>
448 448 </nav>
449 449 </div>
450 450 {{end}}
@@ -467,7 +467,7 @@
467 467 {{if .Description}}{{.Description}}{{else}}<p class="muted">No description.</p>{{end}}
468 468 </div>
469 469 {{if .CanEdit}}
470 <form class="comment-edit-form details-edit-form" data-comment-edit-form method="post" action="/r/{{.Name}}/changes/{{.Request.ID}}/edit" hx-post="/r/{{.Name}}/changes/{{.Request.ID}}/edit" hx-target="#request-description" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Description.Active}} hidden{{end}}>
470 <form class="comment-edit-form details-edit-form" data-comment-edit-form method="post" action="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-post="/{{.Name}}/changes/{{.Request.ID}}/edit" hx-target="#request-description" hx-swap="outerHTML" hx-disabled-elt="find button[type='submit']"{{if not .Details.Description.Active}} hidden{{end}}>
471 471 <input type="hidden" name="csrf" value="{{.CSRF}}">
472 472 <input type="hidden" name="field" value="body">
473 473 {{if .Details.Description.Error}}<p class="error">{{.Details.Description.Error}}</p>{{end}}