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.
Update the change page as live regions, not one sidebar swap
707dfd10f309461a77a4a8f73dcce74b07c91fd6 · Keenan Nemetz · 10d ago · parent 9f5efa3c
Update the change page as live regions, not one sidebar swap The 20-second poll replaced the entire sidebar, so every new widget needed its own reason to opt out and live updates stopped while it was in use. The sidebar is now a set of regions: review status, changes, approvals, checks, and the combined result are data-only live regions returned with hx-swap-oob; the target picker, issue search, and composers are outside every polled swap. Approval and the other multi-region writes use the same out-of-band group, and the page action group refreshes from the write without replacing unrelated state.
server/web/writes.go +6−6
| @@ -187,7 +187,7 @@ func (w *Web) ApproveRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 187 | 187 | return | |
| 188 | 188 | } | |
| 189 | 189 | sidebar := func(msg string) (string, any) { | |
| 190 | return "request_sidebar", w.requestSidebarWithLanding(r, m, req, "", msg) | ||
| 190 | return "request_live_write", w.requestLiveWrite(r, m, req, msg) | ||
| 191 | 191 | } | |
| 192 | 192 | // The form carries the base and head the page displayed. If a push moved the | |
| 193 | 193 | // followed branch, or the request was retargeted, the writer refuses rather than | |
| @@ -210,10 +210,10 @@ func (w *Web) ApproveRequest(rw http.ResponseWriter, r *http.Request) { | |||
| 210 | 210 | if htmxRequest(r) { | |
| 211 | 211 | updated, err := metadata.GetRequest(m.Git(), id) | |
| 212 | 212 | if err != nil { | |
| 213 | w.render(rw, "request_sidebar", w.requestSidebarWithLanding(r, m, req, "", err.Error()), 0) | ||
| 213 | w.render(rw, "request_live_write", w.requestLiveWrite(r, m, req, err.Error()), 0) | ||
| 214 | 214 | return | |
| 215 | 215 | } | |
| 216 | w.render(rw, "request_sidebar", w.requestSidebarWithLanding(r, m, updated, "", ""), 0) | ||
| 216 | w.render(rw, "request_live_write", w.requestLiveWrite(r, m, updated, ""), 0) | ||
| 217 | 217 | return | |
| 218 | 218 | } | |
| 219 | 219 | http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+id, http.StatusSeeOther) | |
| @@ -650,9 +650,9 @@ func (w *Web) armMerge(rw http.ResponseWriter, r *http.Request, m *repo.Meta, re | |||
| 650 | 650 | } | |
| 651 | 651 | w.audit("web.request.merge_when_ready", actor, m.Name, req.ID) | |
| 652 | 652 | if htmxRequest(r) { | |
| 653 | view := w.requestSidebarWithLanding(r, m, req, "", "") | ||
| 653 | view := w.requestLiveWrite(r, m, req, "") | ||
| 654 | 654 | view.Notice = notice | |
| 655 | w.render(rw, "request_sidebar", view, 0) | ||
| 655 | w.render(rw, "request_live_write", view, 0) | ||
| 656 | 656 | return | |
| 657 | 657 | } | |
| 658 | 658 | http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+req.ID+"?merging=1", http.StatusSeeOther) | |
| @@ -687,7 +687,7 @@ func (w *Web) CancelMerge(rw http.ResponseWriter, r *http.Request) { | |||
| 687 | 687 | // block and the controls that explain it. | |
| 688 | 688 | func (w *Web) mergeRefused(rw http.ResponseWriter, r *http.Request, m *repo.Meta, req *metadata.Request, msg string) { | |
| 689 | 689 | if htmxRequest(r) { | |
| 690 | w.render(rw, "request_sidebar", w.requestSidebarWithLanding(r, m, req, "", msg), 0) | ||
| 690 | w.render(rw, "request_live_write", w.requestLiveWrite(r, m, req, msg), 0) | ||
| 691 | 691 | return | |
| 692 | 692 | } | |
| 693 | 693 | w.renderRequestPage(rw, r, msg, http.StatusConflict) | |