Landing a change request is the payoff for the whole review loop, but the page just reloads and looks exactly like any other view. A short confetti burst on the page you land on is a small, self-contained piece of feedback that costs nothing when nothing is being landed.
This is a pleasant-to-have, not a correctness issue.
Cost, measured
canvas-confetti 1.9.3 (dist/confetti.browser.min.js from the npm tarball):
| Asset | Raw | Gzipped |
|---|---|---|
| canvas-confetti 1.9.3 (min) | 10.8 KB | 4.5 KB |
static/htmx.min.js (already vendored) |
52 KB | ~16 KB |
static/vole.css |
68 KB | ~12 KB |
Zero dependencies, ISC license (same family as the vendored Lucide). It draws to
one fixed z-index:100 canvas on requestAnimationFrame, supports
prefers-reduced-motion through its disableForReducedMotion option, and falls
back to an OffscreenCanvas worker where available.
Constraints that shape the change
server/web/auth.gosetsscript-src 'self', so there is no CDN option. The library must be vendored intoserver/web/static/with acanvas-confetti.LICENSEbesidehtmx.LICENSEandlucide.LICENSE, per the rule inserver/web/templates.gothat scripts are static assets and never inlined into a template.Static()serveshttp.FileServerFSwith no gzip middleware, so the honest wire cost is the full 10.8 KB. Putting the tag inchrome.htmlwould pay that on every page load for everyone. It should therefore be lazy-loaded: the ~11 KB is paid once, by the person who just landed the request.- Nothing on the merge path currently marks a successful landing to the browser.
MergeRequestredirects 303 to the request page, which is also the URL of a normal visit. A?merged=1query parameter, mirroring the existing?saved=1idiom insettings.go, keeps the burst tied to the write. A refresh or a shared link then does not replay it.
Scope / checklist
- Vendor
canvas-confetti1.9.3 asstatic/confetti.browser.min.jswith its ISCcanvas-confetti.LICENSE. - Redirect a successful landing to
/r/<name>/requests/<id>?merged=1; leave every refusal path unchanged, including the HTMX fragment reply. - In
static/app.js, when?mergedis present, append the script tag, fire one burst from the merge control, and stop. Do not load the script otherwise. - Pass
disableForReducedMotion: trueand callconfetti.reset()before firing so a back/forward cache restore cannot stack canvases. - Guard the DOM work so the enhancement stays optional: no console error and no extra request on any page without the parameter.
- Cover the redirect in a test: a successful landing redirects with
?merged=1and the request page renders normally with the parameter present.
Acceptance criteria
- A successful merge lands on the request page with
?merged=1in the URL, and the merged banner it shows today is unchanged. /static/confetti.browser.min.jsis served from the embedded assets and is requested only on a page carrying?merged=1; a plain request page load issues no request for it.- A user with
prefers-reduced-motion: reducesees the same merged page and no animation. - The static asset set stays embed-only: no build step, no npm, no CDN, and
script-src 'self'is not loosened. go test ./...passes, andvole workflow checkis clean.