Loop Studio is a static single-page app (dist/). It is deployed on
Cloudflare Pages, served at the domain root /.
Why Cloudflare Pages:
- short default URL (
cozy-loop-studio.pages.dev), served at/— no GitHub-Pages-style/loop-studio/base path to special-case - HTTPS everywhere ⇒ the Monte-Carlo Web Worker path is active (a
file://or insecure origin falls back to the slower cooperative path) - auto build + deploy on every push to
main - custom domain is a one-click add later
-
Cloudflare dashboard → Workers & Pages → Create → Pages → Connect to Git → authorise the GitHub app and pick
MerciHanrim/loop-studio. -
Build settings:
field value Project name cozy-loop-studio(⇒cozy-loop-studio.pages.dev)Production branch mainFramework preset None (Vite) Build command npm run buildBuild output directory distRoot directory (repo root) -
Node version: the repo pins it in
.nvmrc(22). Cloudflare reads.nvmrcautomatically; no dashboard env var needed. (Local dev may run a newer Node — Vite 8 supports^20.19 || ^22.12 || >=24.) -
Save. The first deploy runs immediately; every later push to
maintriggers an automatic production deploy, and every PR gets a preview URL.
No secrets or environment variables are required — the app is fully client-side.
vite.config.ts—base: './'(relative asset URLs; works at/and in the portable single-file build alike).index.html— favicon is an inlinedata:URI; no external asset requests.- Fonts (
@fontsource/*) are bundled intodist/assets/at build time — no CDN. - No client-side routing yet, so no
_redirects/ SPA fallback is needed. Addpublic/_redirectswith/* /index.html 200when URL-hash sharing lands. .nvmrc=22;package.jsonengines.node>=22.12.0.
npm run build # tsc -b && vite build → dist/
npm run e2e:dist # builds, serves dist/ with `vite preview`, runs e2e/dist.spec.tse2e/dist.spec.ts verifies the production build the way Cloudflare serves
it:
- boots at
/, no console errors, no failed or cross-origin requests, no dev bridge - File ▾ → Import
examples/risky-factory.json→ 18 nodes - MC dialog pre-filled from
recommendedRunConfig(500 × 40, seed 1) - runs on the real Worker path (
Workerconstructed, jobs dispatched) → Risky Factory 424 / 500, populated termination sparkline - Distribution panel's Export ▾ → JSON (
loop-mc/1,endedRuns[-1] === 424); File ▾ → Graph JSON carriesrecommendedRunConfig - hard reload → app re-boots and restores the graph from
localStorage
vite preview on localhost is a secure context, so the Worker path check is
valid; still run one manual smoke on the live *.pages.dev URL after the first
deploy.
- open
https://cozy-loop-studio.pages.dev/— canvas + toolbar render, no console errors - Templates ▾ → Flowing equilibrium, press Play — the timeline moves
- File ▾ → Import
examples/risky-factory.json; open Monte Carlo — fields show500 / 40 / 1; Run - DISTRIBUTION appears; termination sparkline flattens near 85 %; the pre-run cost line reads Parallel, N workers (Worker path, not "Local")
- Distribution panel's Export ▾ → JSON, reopen it —
"spec": "loop-mc/1",endedRuns.atOrBeforeSteplast value 424 - File ▾ → Graph JSON, File ▾ → New, File ▾ → Import that file back — graph returns and the MC dialog is pre-filled again
- hard-refresh the tab — the app reloads and the graph is still there