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.
Celebrate a landed change request, once
c8fa5759b293126f1b247abc746bc339791568cb · Keenan Nemetz · 13d ago · parent 53ff967b
Celebrate a landed change request, once A successful landing now redirects with ?merged=1, and app.js answers that parameter by fetching static/confetti.browser.js (canvas-confetti 1.9.3, ISC) and firing one burst. The flag is removed from the address as soon as it is read, so a reload, a bookmark or a shared link is an ordinary visit, and no page without the parameter ever requests the bundle. The library honours prefers-reduced-motion through disableForReducedMotion, and reset() before firing means a page restored from the back/forward cache cannot stack a second canvas. The asset is vendored the way htmx and Lucide are: the file is unmodified from the published npm tarball, with the license beside it, so script-src 'self' stays as it is. It is deliberately not in the page shell, because Static() serves assets uncompressed and a shell script would make every page pay for a celebration only one page uses. Refs #01M5XZ7T4R88BA9KV1KTSV7ZXP
server/web/static/app.js +30
| @@ -337,3 +337,33 @@ const copyText = (value, done) => { | |||
| 337 | 337 | } | |
| 338 | 338 | }); | |
| 339 | 339 | })(); | |
| 340 | |||
| 341 | // Landing a change request is the end of the review loop, so the page it lands on | ||
| 342 | // gets one burst of confetti. The library is a separate 25 KB asset and is fetched | ||
| 343 | // only on the redirect that carries ?merged=1: no other page pays for it, and a | ||
| 344 | // reload, bookmark or shared link is an ordinary visit because the flag is dropped | ||
| 345 | // from the address as soon as it has been read. The library honours | ||
| 346 | // prefers-reduced-motion itself (disableForReducedMotion), and reset() first means a | ||
| 347 | // page restored from the back/forward cache cannot stack a second canvas. | ||
| 348 | (() => { | ||
| 349 | const params = new URLSearchParams(window.location.search); | ||
| 350 | if (!params.has('merged')) return; | ||
| 351 | params.delete('merged'); | ||
| 352 | const rest = params.toString(); | ||
| 353 | window.history.replaceState(null, '', | ||
| 354 | window.location.pathname + (rest ? '?' + rest : '') + window.location.hash); | ||
| 355 | |||
| 356 | const script = document.createElement('script'); | ||
| 357 | script.src = '/static/confetti.browser.js'; | ||
| 358 | script.onload = () => { | ||
| 359 | if (typeof window.confetti !== 'function') return; | ||
| 360 | window.confetti.reset(); | ||
| 361 | window.confetti({ | ||
| 362 | particleCount: 120, | ||
| 363 | spread: 70, | ||
| 364 | origin: { x: 0.5, y: 0.6 }, | ||
| 365 | disableForReducedMotion: true, | ||
| 366 | }); | ||
| 367 | }; | ||
| 368 | document.head.appendChild(script); | ||
| 369 | })(); | ||