Cache-bust the rest of _emanote-static in live-server mode#676
Conversation
Every asset loaded via the layer-URL splice (skylighting.css,
fonts/fonts.css, inverted-tree.css, emanote-logo.svg, Stork CSS+JS)
went around siteRouteUrl and shipped without the ?t=<mtime> suffix
that lets the live server invalidate cached files. Editing any of
them required restarting `emanote run`.
Add an attributed Heist splice <emanoteStaticUrl path="…">${url}…
that routes through siteRouteUrl, migrate every template that
referenced ${ema:emanoteStaticLayerUrl}, and refactor JsBundle.jsUrl
to share the same primitive. The legacy splice stays around for
third-party templates but is now documented as cache-buster-skipping.
Closes #666.
The 'skips siteRouteUrl' phrasing didn't match the code: the splice calls siteRouteUrl on inverted-tree.css, then strips the query string via T.breakOn while extracting the folder URL. Reword to match what actually happens.
Hickey/Lowy Analysis
Hickey rationaleBefore the diff, "URL for an The new Lowy rationaleThe cache-busting protocol ( The boundary survives the "is this functional grouping or volatility grouping?" diagnostic: it exists because what is behind it changes independently, and the cross-layer reuse (Haskell + Heist) is the signature of a genuine volatility boundary, not scaffolding. Consolidating |
EvidenceThis PR adds This branch — every affected asset carries Master baseline — same routes, no |
|
| Step | Status | Duration | Verification |
|---|---|---|---|
| sync | ✓ | 1s | git fetch ok; forge=github; noGit=false |
| research | ✓ | 20m 15s | Issue #666 understood; designed bind-style attributed splice + shared static-URL primitive |
| branch | ✓ | 16s | Worktree branch living-gang tracking origin/master |
| implement | ✓ | 5m 38s | Live server emits ?t=<mtime> on all 6 affected assets; importmap unchanged |
| check | ✓ | 5s | cabal build all succeeded |
| docs | ✓ | 16s | CHANGELOG + docs/guide/html-template/fonts.md updated |
| fmt | ✓ | 20s | pre-commit run --all-files clean |
| commit | ✓ | 18s | Primary commit f2964e1d pushed |
| hickey+lowy | ✓ | 3m 7s | 5 hickey findings all addressed by design; 5 lowy findings all "no action needed" |
| police | ✓ | 9m 50s | rules + fact-check clean; elegance found 1 misleading comment fixed in 6a4c6924 |
| test | ✓ | 7m 19s | cabal test all: 43/43 |
| create-pr | ✓ | 1m 13s | Draft PR + hickey/lowy comment posted |
| ci | ✓ | 1m 54s | vira ci signed off both architectures; e2e live/static/morph all pass |
| evidence | ✓ | 2m 53s | Branch-vs-master curl diff showing ?t=<mtime> propagation |
| Total | 54m 1s |
Slowest step: research (20m 15s)
Optimization suggestions
- research dominated (37% of total) — issue Stale-cache other static-layer assets in live-server mode (CSS, fonts, Stork, SVG) #666 required understanding Heist attributed splices, the existing legacy splice's hand-rolled folder-URL hack, and the JsBundle cache-busting design. Pre-reading
Emanote.View.JsBundleandEmanote.View.Common'sema:emanoteStaticLayerUrlsplice before invoking/dowould have shortened this materially. just testis interactive — the recipe shells out toghcidwhich never returns; this run usedcabal test allinstead. Consider splittingjust test(interactive watch) fromjust test-once(one-shot CI-friendly) so both/doand humans get the right tool by default.- police's elegance pass took the longest sub-step — fact-checking the
simplifyreviewers' suggestions against the codebase (especially the_emanote-staticliteral duplication question) ate cycles. Most "consolidate constants across modules" suggestions are out-of-scope for a single-issue PR; encoding that boundary in.agency/code-police.mdcould pre-empt future churn. - CI was already fast (1m 54s for
vira ci+ three e2e suites) — no optimization opportunity there.
Workflow completed at 2026-04-26 19:58 UTC.
The cache-busting fix from #663 only covered the JS module graph; every other asset under
_emanote-static/(skylighting CSS, self-hosted fonts, the inverted-tree CSS, the emanote logo, Stork's CSS+JS) was still served bare — editing any of them inemanote runleft the browser staring at a stale copy until you restarted the server. They went aroundsiteRouteUrlvia the legacy${ema:emanoteStaticLayerUrl}text splice, so the?t=<mtime>cache buster never reached them.This PR introduces an attributed Heist splice —
<emanoteStaticUrl path="…">${url}…</emanoteStaticUrl>— that routes throughsiteRouteUrl, migrates the five default-template call-sites onto it, and refactorsJsBundle.jsUrlto share the same primitive (Emanote.View.StaticUrl.emanoteStaticUrl). Now the splice is the one home for static-asset URL construction; the importmap and the<link>/<script>/<img>tags all flow through it.The legacy
${ema:emanoteStaticLayerUrl}splice stays around with a deprecation comment. Removing it would break third-party templates that still reference the bare folder URL — soft-deprecate now, retire at the next major version.Closes #666.
Try it locally
Then edit any of
emanote/default/_emanote-static/{skylighting.css,fonts/fonts.css,inverted-tree.css,emanote-logo.svg,stork/edible.css,stork/stork.js}and watch the browser pick up the change without a restart.Generated by
/doon Claude Code (modelclaude-opus-4-7).