Workbench: deploy and verify the personal dashboard site
How to ship a change to Justin's personal workbench: the static site in the
private repo jknash/dashboard, hosted on Cloudflare Pages at
/.
Facts this procedure relies on (assigned subdomain, preview hosts, token
boundaries) are in the Knowledge entry
Cloudflare; read it first if anything here
looks surprising.
Scope: content and data updates to existing sections, and redeploys. Adding a brand-new section also needs the Access runbook Workbench Access before the section is announced or linked.
The procedure at a glance (the prose below governs):
Preconditions
- You are working in Muse's environment, where the
cloudflareskill and the stored token (custom.cloudflare) live. Never ask the owner to paste the token, and never print it. - The local tree is at
~/workspace/dashboard/, with the deployable site undersite/. The repo is the source of truth; the deployment uploads that tree.
Steps
1. Make the change in the tree
Edit pages under site/<section>/. Data-driven sections render from a JSON
file under site/<section>/data/; producers write the JSON, pages never
embed live data.
For the finance section, regenerate the snapshot instead of hand-editing.
Standing rule (owner, 2026-10-03): every Plaid finance pull ends with the dashboard updated. Any time finances are pulled, whether for an interactive request or a scheduled job such as the bill watch, finish the pull by rebuilding the snapshot, committing it (step 3), and deploying (step 4), so the finance page is never stale relative to the last pull. If the refresh fails, still deliver whatever the pull was for and note the refresh failure in one line; never block the main task on it.
mkdir -p /tmp/fin
plaid accounts > /tmp/fin/accounts.json
plaid transactions-recurring > /tmp/fin/recurring.json
plaid liabilities > /tmp/fin/liabilities.json
plaid transactions-get --start-date "$(date -d '30 days ago' +%F)" \
--end-date "$(date +%F)"
# A large transactions result is written to an output file instead of
# stdout; use that file's path for --transactions below.
python3 ~/workspace/dashboard/scripts/build_finance_snapshot.py \
--accounts /tmp/fin/accounts.json \
--recurring /tmp/fin/recurring.json \
--liabilities /tmp/fin/liabilities.json \
--transactions /tmp/fin/transactions.json \
--out ~/workspace/dashboard/site/finance/data/finance.json
Then commit site/finance/data/finance.json to jknash/dashboard and
deploy exactly as in steps 3 and 4.
Exit test: the script writes site/finance/data/finance.json with a fresh
generated_at timestamp and totals that match its own account rows.
Privacy gate before continuing: the tree must contain no API keys, tokens, bank exports, full account numbers, or credentials. Snapshots only.
2. The docsite section is a mirror — do not build it by hand
The /docs/ section mirrors the docsite, whose canonical source is the
separate repository jknash/docsite (owner decision, 2026-10-03,
reversing the 2026-10-02 migration: other agents publish there, so it is
the single source of truth). The mirror is maintained by the
docsite-workbench-sync job, which runs about every 30 minutes: it
checks the original repo's main, and when it changes, it rebuilds the
site from a scratch copy, replaces site/docs/ with the output, commits
the output to jknash/dashboard, deploys (step 4), and verifies
(step 5).
The build copy gets three patches that never touch the original repo:
docusaurus.config.js sets baseUrl to /docs/ and the docs plugin
routeBasePath to / (so page URLs are unchanged), and
src/pages/index.js is dropped from the copy because it collides with
the Documentation index at the /docs/ route.
Rules that follow:
- Never commit docsite source into
jknash/dashboard, and never editsite/docs/output by hand. To change a doc, publish it tojknash/docsite(see thegithub-docsiteskill); it appears on the workbench after the next sync run. - Deploying the workbench for other sections does not disturb the
mirror: a deploy uploads whatever
site/docs/currently holds. - If a doc must go live immediately, trigger the
docsite-workbench-syncjob manually instead of building by hand.
Environment note: the sync build runs from a tmpfs copy on Muse's VM
because npm ci fails on the overlayfs workspace with
EPERM ... chown while linking package bins (mount tmpfs, for example
mount -t tmpfs -o size=2500M tmpfs /mnt/build, copy the source there
without node_modules, install and build in the copy).
Exit test: after a sync run, site/docs/index.html exists, the build
finished without broken-link errors (the config throws on them), and the
step 5 checks pass.
3. Commit the tree to the repo
Commit the changed files to jknash/dashboard on main so repo and site
never drift. The repo's old GitHub Action deploy workflow was removed on
2026-10-02 (it had no secrets configured and only produced failure
notices); it is not coming back. Deploy through the API in step 4.
4. Deploy through the API
~/workspace/skills/cloudflare/.venv/bin/python \
~/workspace/skills/cloudflare/bin/cf.py deploy
The command builds the upload manifest from site/, uploads any blobs
Cloudflare does not already hold, and creates the deployment.
Exit test: the command prints the deployment URL and the project's actual
subdomain (dashboard-71j.pages.dev). A JWT or manifest error means the
token or the tree is wrong; stop and diagnose rather than retrying blindly.
5. Verify like an outsider
Run these checks unauthenticated. Expected results:
curl -s -o /dev/null -w '%{http_code}\n' /
# expect 200 (landing page is public by design)
curl -s -o /dev/null -w '%{http_code}\n' /finance/
curl -s -o /dev/null -w '%{http_code}\n' /finance/data/finance.json
curl -s -o /dev/null -w '%{http_code}\n' /divorce/
curl -s -o /dev/null -w '%{http_code}\n' /inbox/
curl -s -o /dev/null -w '%{http_code}\n' /docs/
# expect 302 (Access login redirect) for every protected URL
Exit test: landing 200, every protected URL 302. A 200 on any protected URL means Access is not covering it; switch to the Workbench Access runbook and do not announce the deploy until it returns 302.
Rollback
Direct upload deploys the full current tree each time, so rollback is:
restore the previous tree state in ~/workspace/dashboard/site/ (from git),
then repeat steps 4 and 5.
What this runbook must NOT do
- Never deploy a tree containing secrets or raw financial exports.
- Never re-add a deploy workflow or repo secrets for one without the owner's decision; the API path is the only deploy path.
- Never announce a section as private based on the production host alone; preview hosts count too (see the Access runbook).
Published by Muse · 2026-10-03.