The hosted board /demo
The board runs as a public, always-on demo at pgxsinkit.github.io/demo
— sign in as a seeded identity and try offline-first sync, membership fan-out, optimistic writes, and
conflict convergence in the browser, with zero setup. It is the same apps/board code as the local and
cloud runs, backed by a managed Supabase Cloud + Electric Cloud
project. Board
ADR-0009.
How it is published
Section titled “How it is published”The board is built into apps/docs/dist/demo/ as a step in the docs deploy (.github/workflows/docs.yml),
so the docs site and the demo deploy as one artifact to the pgxsinkit.github.io repo. The docs deploy
replaces the whole publish (force_orphan), so co-publishing — not a second workflow — is what keeps the
demo from being clobbered.
The board runs in worker mode, so the static build also ships a SharedWorker
chunk (the sync engine — board-sync.worker.ts) alongside the app bundle; Vite emits and fingerprints it
under /demo/ like any other asset. A visitor on a browser without SharedWorker transparently falls back
to the in-process engine (correct, just on the main thread), so the demo works everywhere — it only loses
the off-thread isolation on that browser.
Two things make the static build work under a subpath:
- Subpath assets —
bun run demo:buildsets the Vite base to/demo/and outputs into the docsdist/. - Hash routing — the build sets
VITE_BOARD_HASH_ROUTING=1, flipping the router to hash history (/demo/#/login). GitHub Pages serves the root/404.htmlfor any unknown path, and that 404 belongs to this docs site — so a path-based deep-link into/demo/loginwould render the docs 404. Hash routing keeps every route under/demo/index.html, so deep-links and refreshes always boot the SPA. Local dev andboard:cloud:devkeep clean path URLs.
Offline return
Section titled “Offline return”A signed-in visitor who closes the demo and reopens it without connectivity boots to a usable board. A small runtime-capture service worker (no precache — it caches only what that visitor’s own boots already fetched) replays the app shell and the PGlite engine assets; the data is whatever each table’s declared retention kept in the local store — every eager table, plus the Admin’s chat once activated. The Member’s chat is ephemeral by design and instead shows an explicit connection-needed state, as does sign-in itself — the capability is offline return, not first-visit offline. Board ADR-0010.
Reset nightly (purge → migrate → reseed)
Section titled “Reset nightly (purge → migrate → reseed)”The demo is public and writable — anyone can create and move issues and post chat. A separate workflow,
.github/workflows/demo-reset.yml, rebuilds the backend on a nightly cron (0 3 * * *) plus
workflow_dispatch: purge:board drops every migration-created board object (model-derived drop list
plus the drizzle bookkeeping schema), db:board:migrate re-applies the latest committed history from
scratch, and seed:board recreates the seeded fixtures. Any vandalism (offensive issue titles, chat
spam) is gone by morning, and a manual run resets it on demand.
Because the schema is rebuilt, not just the rows, the cloud database is effectively ephemeral — the same
posture as every other database these migrations target. A rewritten or collapsed migration history
(docs/runbooks/regenerate-migrations.md) ships by simply dispatching this workflow; the function bundles
are the separate bun run board:cloud:functions step, explicitly targeted by
BOARD_SUPABASE_PROJECT_REF.
All three steps are the same scripts used locally and by board:cloud:*, pointed at the cloud project via
env — no Postgres/Electric containers, no Pages deploy, just the GoTrue admin API + the project’s database
via the Supavisor session pooler (role privileges, not the connection path, are what the DDL needs).
Operator setup
Section titled “Operator setup”The public demo points at a real project provisioned per the board-on-cloud runbook. On top of that one-time setup, the hosted demo needs:
| GitHub setting | Kind | Purpose |
|---|---|---|
DEMO_BOARD_SUPABASE_URL |
variable (public) | Project URL — baked into the build; the reset’s GoTrue admin gateway. |
DEMO_BOARD_PUBLISHABLE_KEY |
variable (public) | sb_publishable_… key — baked into the build as the apikey. |
DEMO_BOARD_FUNCTIONS_REGION |
variable (public) | The project’s region (e.g. eu-central-1) — sent as x-region on the write function (board-write) only, so its DB-bound worker executes next to the database instead of next to each visitor. Without it, every write function→DB statement pays a cross-region round trip. The read proxy (board-sync) is left unpinned — its upstream is Electric Cloud’s global CDN, so it should follow the caller. |
DEMO_BOARD_SECRET_KEY |
secret | sb_secret_… key — the reset’s admin API auth. |
DEMO_BOARD_DATABASE_URL |
secret | Session pooler connection (pooler host, port 5432, user postgres.<ref>) — the reset drops, migrates, and inserts as postgres. Not the direct connection: it is IPv6-only, and GitHub-hosted runners have no IPv6. |
PGXSINKIT_PAGES_DEPLOY_KEY |
secret | Already required by the docs deploy. |
The build values are variables, not secrets on purpose: the project URL and publishable key are public (they ship in client JS), and gating the demo build on a variable lets a fork get a clean docs deploy with the demo step skipped.
Then, on the Supabase project:
- Set
BOARD_ALLOWED_ORIGINSto includehttps://pgxsinkit.github.io(a CORS origin is scheme + host — the/demopath is irrelevant) alongside your localhost dev origins, and redeploy secrets (bun run board:cloud:secrets). Without this the functions reject the github.io origin’s requests. - Disable open email signups (Auth settings). The reset truncates all board rows regardless of author (so vandal content is always wiped) but only deletes the fixture auth identities — disabling signups keeps the user set to exactly the seeded fixtures.
- Activate the Electric subquery preview on your source — see The Electric subquery requirement. Without it, ordinary members’ membership-scoped shapes 400 while admin works.
What’s verified
Section titled “What’s verified”The static build is exercised by bun run demo:build; the live page is operator-verified, like the rest
of the cloud path (it needs the managed backend). The local stack remains the CI-gated proof of the topology
(bun run test:integration:board, 8/8).