You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Since the playground is very large, browsers very aggressively cache the code. As a result, users often report problems that are already fixed. When it is built, it should invalidate the cache without deleting local storage unless the schema change is reported
Still reproduces on main (d86740e) and on playground.opendatacapture.org. Checked today: / has Last-Modified and ETag but no Cache-Control, and /assets/does-not-exist.js and /runtime/v1/zod@3.x/does-not-exist.js both return 200 text/html with index.html's ETag.
Since 3a798fc the image is Caddy, and apps/playground/Caddyfile:9-12 is apps/web/Caddyfile plus a /config.json handle, so this is the #1771 mechanism. Fix both files in one PR with the layout and CI check proposed on #1771: immutable only for /assets/* files that exist, no-cache for /runtime/* and the SPA fallback, and try_files inside the catch-all handle. The playground-specific parts follow.
The image's /config.json handle never runs
Caddy sorts site-level directives by its directive order, and try_files comes before handle. /config.json is not a file in /srv, so try_files rewrites it to /index.html before handle /config.json is evaluated, and the handle never matches. The image answers /config.json with index.html, and fetchPlaygroundConfig (src/preview/config.ts:14-20) throws on response.json(). No test sees it because the e2e suite drives the Vite dev server, whose playgroundConfig plugin serves /config.json itself. Moving try_files into the catch-all fixes this too. The CI check on /config.json should assert the JSON body as well as no-store.
The hosted site does answer /config.json with the JSON, so it is not serving the committed Caddyfile unchanged. Whatever serves it needs the same headers, or the stale-build reports continue after the image is fixed. The note for other web servers in docs/en/2-tutorials/2.4-playground-deployment.md:105 should list the cache headers next to config.json. #1822 edits that doc and .github/workflows/ci.yaml, so whichever lands second rebases.
Other playground notes
preview.html falls into the catch-all and gets no-cache, which it needs. The editor and the frame are cached on different origins, and neither $EditorMessage nor $PreviewMessage (src/preview/protocol.ts:34-50) carries a version, so a stale frame cannot tell it is out of step.
No vite:preloadError listener here either. The two dynamic imports, IndexPage (src/App.tsx:7) and esbuild-wasm (src/pages/IndexPage.tsx:14), both run at boot. What a tab left open across a deploy fetches later is a Monaco worker (src/components/Editor/setup.ts:20-33), created when a file of that language is first opened, and a worker that fails to load does not fire that event.
Local storage
Cache headers never touch IndexedDB, so the HTTP fix cannot delete saved work. A persist version change can. The store has version: 1 and no migrate (src/store/index.ts:84). On a mismatch with no migrate, zustand 5.0.15 logs a console.error and calls merge(undefined, currentState). The merge at lines 49-75 then keeps only the bundled instruments, and the next set writes that over the app entry.
This has happened before. d04566b (June 2024) bumped version from 0 to 1 with no migrate, and zustand 4.5.2 wrote the reset state back during hydration, discarding every instrument saved before it. Then 7123b27 (November 2025) moved the store from localStorage to IndexedDB without reading the old entry, so instruments saved before that are still under app in localStorage, unread.
#1756's suggested fix is the next version bump. Its migrate should validate the old shape. On failure it should keep the raw entry under another key and tell the user, with an en and fr string through useTranslation, instead of returning undefined. A one-time import of the old localStorage entry when IndexedDB has none can go in the same change. Cover it with a unit test in apps/playground/src/store/__tests__/ (mock idb-keyval) and a step in testing/src/specs/playground.spec.ts that seeds an old-shape entry, reloads, and finds the saved instrument.
Separately: on the hosted site /plsb/js/script.js, the Plausible script that apps/playground/index.html:8-13 loads, also returns index.html, so the playground records no visits. Nothing in front of the files routes /plsb. That needs its own issue, like #1777 for the marketing site.
Triage note written with Claude Code. Code references point at main @ d86740e.
Activity
[-]Handle agressive caching of playground[/-][+]Handle aggressive caching of playground[/+]joshunrau commentedon Oct 26, 2026
Still reproduces on
main(d86740e) and onplayground.opendatacapture.org. Checked today:/hasLast-ModifiedandETagbut noCache-Control, and/assets/does-not-exist.jsand/runtime/v1/zod@3.x/does-not-exist.jsboth return200 text/htmlwithindex.html'sETag.Since 3a798fc the image is Caddy, and
apps/playground/Caddyfile:9-12isapps/web/Caddyfileplus a/config.jsonhandle, so this is the #1771 mechanism. Fix both files in one PR with the layout and CI check proposed on #1771:immutableonly for/assets/*files that exist,no-cachefor/runtime/*and the SPA fallback, andtry_filesinside the catch-allhandle. The playground-specific parts follow.The image's
/config.jsonhandle never runsCaddy sorts site-level directives by its directive order, and
try_filescomes beforehandle./config.jsonis not a file in/srv, sotry_filesrewrites it to/index.htmlbeforehandle /config.jsonis evaluated, and the handle never matches. The image answers/config.jsonwithindex.html, andfetchPlaygroundConfig(src/preview/config.ts:14-20) throws onresponse.json(). No test sees it because the e2e suite drives the Vite dev server, whoseplaygroundConfigplugin serves/config.jsonitself. Movingtry_filesinto the catch-all fixes this too. The CI check on/config.jsonshould assert the JSON body as well asno-store.The hosted site does answer
/config.jsonwith the JSON, so it is not serving the committed Caddyfile unchanged. Whatever serves it needs the same headers, or the stale-build reports continue after the image is fixed. The note for other web servers indocs/en/2-tutorials/2.4-playground-deployment.md:105should list the cache headers next toconfig.json. #1822 edits that doc and.github/workflows/ci.yaml, so whichever lands second rebases.Other playground notes
preview.htmlfalls into the catch-all and getsno-cache, which it needs. The editor and the frame are cached on different origins, and neither$EditorMessagenor$PreviewMessage(src/preview/protocol.ts:34-50) carries a version, so a stale frame cannot tell it is out of step.vite:preloadErrorlistener here either. The two dynamic imports,IndexPage(src/App.tsx:7) andesbuild-wasm(src/pages/IndexPage.tsx:14), both run at boot. What a tab left open across a deploy fetches later is a Monaco worker (src/components/Editor/setup.ts:20-33), created when a file of that language is first opened, and a worker that fails to load does not fire that event.Local storage
Cache headers never touch IndexedDB, so the HTTP fix cannot delete saved work. A persist
versionchange can. The store hasversion: 1and nomigrate(src/store/index.ts:84). On a mismatch with nomigrate, zustand 5.0.15 logs aconsole.errorand callsmerge(undefined, currentState). Themergeat lines 49-75 then keeps only the bundled instruments, and the nextsetwrites that over theappentry.This has happened before. d04566b (June 2024) bumped
versionfrom 0 to 1 with nomigrate, and zustand 4.5.2 wrote the reset state back during hydration, discarding every instrument saved before it. Then 7123b27 (November 2025) moved the store fromlocalStorageto IndexedDB without reading the old entry, so instruments saved before that are still underappinlocalStorage, unread.#1756's suggested fix is the next
versionbump. Itsmigrateshould validate the old shape. On failure it should keep the raw entry under another key and tell the user, with anenandfrstring throughuseTranslation, instead of returningundefined. A one-time import of the oldlocalStorageentry when IndexedDB has none can go in the same change. Cover it with a unit test inapps/playground/src/store/__tests__/(mockidb-keyval) and a step intesting/src/specs/playground.spec.tsthat seeds an old-shape entry, reloads, and finds the saved instrument.Separately: on the hosted site
/plsb/js/script.js, the Plausible script thatapps/playground/index.html:8-13loads, also returnsindex.html, so the playground records no visits. Nothing in front of the files routes/plsb. That needs its own issue, like #1777 for the marketing site.Triage note written with Claude Code. Code references point at
main@d86740e.