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.

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/request_view.go +25−17

@@ -168,8 +168,12 @@ type requestSidebarView struct {
168 168 CombinedRuns []*runs.Run
169 169 CombinedState string
170 170 // Notice is a non-error message a write wants beside the controls.
171 Notice string
172 CanDiscuss bool
171 Notice string
172 CanDiscuss bool
173 // Live says this render is an out-of-band update to the data-only regions,
174 // not a page or a targeted section swap. Each live region adds hx-swap-oob so
175 // one poll can update several places without replacing a form or a picker.
176 Live bool
173 177 ContributorCI *requestCI
174 178 Error string
175 179 }
@@ -189,6 +193,17 @@ func (w *Web) requestSidebarWithLanding(r *http.Request, m *repo.Meta, req *meta
189 193 return w.requestSidebar(r, m, req, title, errMsg).withLanding(w.requestLanding(m, req))
190 194 }
191 195
196 // requestLiveWrite is the same data as a full sidebar render, marked so every
197 // region that belongs to the live group carries hx-swap-oob. The response is a
198 // set of targeted updates, not a replacement for the sidebar: a write can update
199 // the status regions without throwing away an issue search or a target picker.
200 func (w *Web) requestLiveWrite(r *http.Request, m *repo.Meta, req *metadata.Request, errMsg string) requestSidebarView {
201 v := w.requestSidebarWithLanding(r, m, req, "", errMsg)
202 v.Live = true
203 return v
204 }
205
206 // requestPageView is the full change page: the sidebar plus the conversation.
192 207 type requestPageView struct {
193 208 requestSidebarView
194 209 Tab string
@@ -198,13 +213,6 @@ type requestPageView struct {
198 213 Thread threadView
199 214 }
200 215
201 // requestAsideView is the polling fragment: the Changes block and the sidebar
202 // together, with none of the page shell the handler that serves a full request
203 // needs.
204 type requestAsideView struct {
205 requestSidebarView
206 }
207
208 216 func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Request, title, errMsg string) requestSidebarView {
209 217 acl, _ := m.ACL()
210 218 approvals := make([]requestApproval, 0, len(req.Approvals))
@@ -559,11 +567,11 @@ func (w *Web) RequestView(rw http.ResponseWriter, r *http.Request) {
559 567 w.renderRequestPage(rw, r, "", http.StatusOK)
560 568 }
561 569
562 // RequestAside serves just the change sidebar for the page's poll. It is the
563 // same fragment the page renders, so an approval, a finished run, or a push from
564 // somewhere else appears without a reload and without the reader losing the
565 // discussion or diff they were reading.
566 func (w *Web) RequestAside(rw http.ResponseWriter, r *http.Request) {
570 // RequestLive serves the out-of-band updates for the change's data-only regions.
571 // The poll element keeps its own DOM (and every form, search, and picker inside
572 // it); the response only names the regions that can change behind the reader's
573 // back.
574 func (w *Web) RequestLive(rw http.ResponseWriter, r *http.Request) {
567 575 m, ok := w.open(rw, r)
568 576 if !ok {
569 577 return
@@ -573,9 +581,9 @@ func (w *Web) RequestAside(rw http.ResponseWriter, r *http.Request) {
573 581 w.notFound(rw, r)
574 582 return
575 583 }
576 w.render(rw, "request_aside", requestAsideView{
577 requestSidebarView: w.requestSidebar(r, m, req, "", "").withLanding(w.requestLanding(m, req)),
578 }, 0)
584 view := w.requestSidebar(r, m, req, "", "").withLanding(w.requestLanding(m, req))
585 view.Live = true
586 w.render(rw, "request_live", view, 0)
579 587 }
580 588
581 589 // requestLanding asks the two questions a reviewer has before approving: does the