Skip to main content

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 cloudflare skill 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 under site/. 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 edit site/docs/ output by hand. To change a doc, publish it to jknash/docsite (see the github-docsite skill); 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-sync job 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.