| Project ID | Preview Worker | Production Worker | Product route status |
|---|---|---|---|
web |
sketchi-web-pr-<number> |
sketchi-web |
sketchi.app, www.sketchi.app |
playground |
sketchi-studio-pr-<number> |
sketchi-studio |
playground.sketchi.app; authenticated Studio unexposed |
icons |
sketchi-icons-pr-<number> |
sketchi-icons |
icons.sketchi.app |
eval-harness |
sketchi-playground-pr-<number> |
sketchi-playground |
internal eval harness |
excalidraw |
sketchi-excalidraw-pr-<number> |
sketchi-excalidraw |
internal rendering workspace |
scripts/lib/worker-apps.mjs records projectId, workerName, and
previewWorkerPrefix as separate fields. Preview, production, cleanup, and
domain-detach helpers validate the selected project/Worker pair and fail closed
on a mismatch. Repository project renames do not rename durable Cloudflare
Workers, buckets, bindings, or Pipeline streams.
Each Worker build is isolated under dist/apps/<project>/: browser assets go to
client/, Worker code and the generated deploy snapshot go to server/, and
the generated config is server/wrangler.json. The Cloudflare Vite plugin's
deploy redirect is also isolated at apps/<project>/.wrangler/deploy/config.json.
Parallel Nx builds therefore never write another app's deployable output.
flowchart LR
PR["Pull request"] --> Matrix["preview matrix"]
Matrix --> Resolve["resolve project ID and Worker identity"]
Resolve --> Build["pnpm nx build <project-id>"]
Build --> Config["generated preview wrangler config"]
Config --> Deploy["wrangler deploy --keep-vars --no-x-provision"]
Deploy --> Comment["sticky PR comment"]
Closed["PR closed"] --> Cleanup["delete preview Worker"]
Pull requests to main deploy matrix apps to PR-specific Cloudflare Workers.
- uses the same pnpm 11.5.0, Node 24,
pnpm install --frozen-lockfilesetup as the requiredciworkflow; - builds each Nx app in an isolated matrix job;
- reads the project-scoped
dist/apps/<project>/server/wrangler.jsonbuild snapshot; - writes
dist/apps/<project>/server/wrangler.preview.jsonwith the preview Worker name and no custom production routes; - runs
wrangler deploy --keep-vars --no-x-provision; - writes or updates one sticky PR comment per app with the preview URL and an explicit public/internal surface policy.
For the web preview, the workflow also reads the account workers.dev
subdomain from Cloudflare and injects sibling preview URLs into the generated
Wrangler vars. SKETCHI_PLAYGROUND_URL maps from the playground project to
its durable sketchi-studio-pr-<number> preview Worker;
apps/eval-harness remains the internal eval harness.
SKETCHI_ICONS_URLSKETCHI_PLAYGROUND_URL
That keeps Web preview navigation inside the same PR's preview Workers instead of sending reviewers to production domains.
Preview comments intentionally distinguish product previews from internal tool
previews. web, playground, and icons are public-product previews.
eval-harness and excalidraw are internal previews only; their comments exist
for reviewer smoke tests and cleanup visibility, not public navigation.
CHROMATIC_PROJECT_TOKEN:stagingenvironment secret for Storybook publish and visual checks.GRAPHITE_TOKEN: optional repository secret only if Graphite CI optimization is reintroduced; the canonical required and preview workflows do not depend on it.CLOUDFLARE_ACCOUNT_ID:stagingenvironment variable or secret.CLOUDFLARE_API_TOKEN:stagingenvironment secret with Workers edit/deploy access.
| Source | Target | Purpose |
|---|---|---|
Infisical sketchi /github staging |
GitHub staging environment |
CI, Graphite optimizer, and PR preview deploys |
Infisical sketchi /github prod |
GitHub production environment |
production deploys |
The canonical source for those GitHub Actions values is the Infisical sketchi
project under /github, synced to GitHub environment secrets:
staging: GitHubstagingenvironment for CI and PR preview deploys.prod: GitHubproductionenvironment for production deploys.
Do not sync both Infisical environments into the same repository-secret namespace; environment-scoped GitHub secrets keep preview and production values from overwriting each other when the values eventually diverge.
Cloudflare documents that non-interactive CI deploys require an API token and
account ID. The token should stay in GitHub Secrets, not in source control.
Preview deploys pass --no-x-provision so CI only uploads explicit Worker
configuration. Preview deploys must not create, discover, or mutate KV, D1, or
R2 resources.
Cleanup runs automatically when a PR closes and deletes the PR-specific Worker. Manual cleanup is also available:
CLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... \
node scripts/04-delete-preview-worker.mjs \
--project eval-harness \
--pr-number 123The deploy command scripts are numbered because they are operational steps:
scripts/00-resolve-worker-app.mjsscripts/01-prepare-preview-deploy.mjsscripts/02-extract-preview-url.mjsscripts/03-upsert-preview-comment.mjsscripts/04-delete-preview-worker.mjsscripts/05-prepare-production-domain-deploy.mjs
Pass --project eval-harness, --project playground, --project web,
--project excalidraw, or --project icons to the resolve, prepare, domain,
and cleanup scripts. Project selection is required; deploy helpers do not
default to a project or accept an independent Worker override.
The app-production-deploy workflow runs on pushes to main and deploys the
five wired production Workers without assigning final custom domains. Production
deploys also pass --no-x-provision; Workers may bind existing resources, but
CI deploys do not create or discover storage.
Those deploys keep workers_dev enabled so the app can be verified from
Cloudflare-owned workers.dev URLs before any DNS or registrar cutover.
Production deploy summaries include the same public/internal route policy and
the custom domains that would be attached by a manual domain dispatch. Internal
apps report no custom domains.
The Studio Worker binds Code Mode artifacts to R2 and Code Mode usage analytics to Cloudflare Pipelines:
| Surface | Binding | Remote target |
|---|---|---|
| Studio preview Workers | SKETCHI_ARTIFACTS |
sketchi-studio-codemode-artifacts-preview |
| Studio production Worker | SKETCHI_ARTIFACTS |
sketchi-studio-codemode-artifacts-production |
| Studio preview Workers | CODEMODE_USAGE_EVENTS |
e9fc3bcd35314fa39fc6a89018207acc |
| Studio preview Workers | CODEMODE_USAGE_ISSUES |
d95a1767edf246af8c637c5b9bf5a5c5 |
| Studio production Worker | CODEMODE_USAGE_EVENTS |
d9044253316f4273a60298098f444a62 |
| Studio production Worker | CODEMODE_USAGE_ISSUES |
f687dab6e7d742c1a76834089e709462 |
Preview Wrangler configs rewrite the Studio artifact binding to the preview
bucket and the Studio Pipeline bindings to preview streams. Production deploys
keep production buckets and production streams. All Worker-bound buckets and
streams must exist before their Workers deploy. The downstream R2 Data Catalog
sinks are long-lived Cloudflare Pipeline resources, not Worker bindings. They
write into sketchi-codemode-usage-analytics-production-v4 and
sketchi-codemode-usage-analytics-preview-v4; run
pnpm r2sql:codemode:resources to print the sink, pipeline, bucket, and table
map. Preview deploys disable Wrangler resource provisioning, so the CI token
does not need R2 object read access just to deploy the Worker.
R2 Data Catalog verification must prove an aggregate R2 SQL data scan through
the direct R2 SQL API, not just SHOW TABLES or DESCRIBE, because
metadata-only catalog calls can succeed before rows are queryable. The
2026-06-29 real end-to-end check called production and preview Studio APIs with
unique x-sketchi-run-id headers, then verified both usage_events and
usage_issues rows in the v4 catalog buckets with
pnpm r2sql:codemode:verify-run -- --require-issues. The verifier polls R2
SQL by default. For normal successful API runs, issue rows may be absent; the
command only requires issue rows when --require-issues or explicit
issue-run-id options are supplied.
Wrangler accepts Pipeline stream names in local dry-runs, but the deploy API
requires stream IDs for Worker bindings. Keep apps/playground/wrangler.jsonc on
the production stream IDs and keep the preview deploy helper's ID rewrite table
in sync with Cloudflare Pipeline stream creation.
Custom domains are a post-merge operator action. The production workflow writes
a generated dist/apps/<project>/server/wrangler.domains.json config from
scripts/05-prepare-production-domain-deploy.mjs and deploys that route-bearing
config only when explicitly dispatched with domain_action=attach. Follow
Production domain cutover; never attach domains
from a pull request or before the Cloudflare zone is active.
When domain_action=attach, the production domain helper attaches
playground.sketchi.app to the playground project while retaining the
sketchi-studio Worker service. Authenticated Studio is not exposed, even
though the Playground host has /projects and persisted
/diagrams/:diagramId route foundations. The
eval-harness has no public domain patterns; it deploys to the durable internal
sketchi-playground Worker.
The eval harness and standalone Excalidraw workspace should remain unlinked from
public navigation. Do not attach a public excalidraw.sketchi.app product route
unless that product-route decision is explicitly reopened.