Today Platform Web — Dev Docs
Workflow

Parallel worktrees

Run multiple branches in parallel via `git worktree add`. What env files to mirror, the cookie-jar caveat, and the future per-worktree hostname plan.

The repo expects you to use git worktrees for parallel branches. Each worktree is a complete checkout pointing at a different branch, with shared .git/ storage. Use .agents/worktrees/ for local agent worktrees; it is gitignored alongside the legacy .worktrees/ path.

Creating a worktree

git fetch origin dev
git worktree add -b pr-x/topic .agents/worktrees/pr-x-topic origin/dev
cd .agents/worktrees/pr-x-topic

You now have a complete checkout at .agents/worktrees/pr-x-topic/ on a new branch pr-x/topic tracking origin/dev. Run pnpm install once and the worktree is ready (pnpm shares the global store, so it's fast).

Env files to mirror

Fresh worktree checkouts have an empty working tree for every gitignored file. The next-app per-package .env*.local files plus the root .env.cloud are gitignored but required for the dev server and cloud-credential scripts to work. Mirror them from your main checkout the first time:

SRC="/path/to/your/main/checkout"
for f in \
  .env.cloud \
  apps/web/.env.development.local \
  design-system/docs/.env.local; do
  [ -f "$SRC/$f" ] && cp "$SRC/$f" "./$f" && echo "copied $f"
done

What each one is:

FilePurpose
.env.cloudClaude Code cloud-session env vars (VERCEL_TOKEN, AWS, GH PAT, E2E secrets, Slack tokens, bypass token). Template: .env.cloud.example
apps/web/.env.development.localOIDC client_id + secret for web's dev profile. Without it, auth init fails
design-system/docs/.env.localDocs-site overrides

pnpm cloud:bootstrap regenerates apps/web/.env.development.local via vercel pull if VERCEL_TOKEN is in .env.cloud — useful when the upstream values change.

Do not run pnpm dev:remote in a fresh worktree before mirroring — they delete or overwrite .env.development.local and you'll lose your OIDC creds.

Per-worktree dev ports — only apps/web is automatic today

apps/web/scripts/dev-server.mts scans 4060-4069 for the first free port. So if worktree A is running apps/web's pnpm dev on :4060, worktree B's pnpm dev lands on :4061 without configuration.

Other apps do not use the scanner. They are hardcoded:

  • apps/tangiblenext dev --port 4070
  • apps/dev-docsnext dev --turbopack -p 4070
  • apps/webview-bridge-docsnext dev --turbopack (Next default port)

So two worktrees can run their apps/web side by side, but port conflicts are still possible for the other apps. Generalizing the scanner beyond web is on the follow-up list.

Two worktrees on localhost:<different ports> share one cookie jar within a single browser profile (RFC 6265 — port is not part of cookie scope). Logging into one replaces the other's session in that profile.

Workarounds:

  • Automation: Playwright browser.newContext() and agent-browser --session <name> each isolate their own cookie jar. Verified by the two-worktree simultaneous-login E2E in PR #656.
  • Humans: use a separate Chrome profile per worktree when you need two worktrees logged in at the same time. Clunky but it's the only workaround at the port layer today.

Future direction — per-worktree hostnames

There's a planned (not yet built) scheme to give each worktree its own hostname instead of port. Schema:

<project>-<feature>.localhost:<port>

Examples: today-web-localhost-bff.localhost:4060, today-web-pr-ca-dev-docs.localhost:4061. *.localhost resolves to loopback in Chrome and Firefox without /etc/hosts edits, so a distinct hostname = distinct cookie jar = true same-profile parallel login.

The constraint to know: keep the name single-label (one dot before .localhost). A multi-level form like today.dev-docs.localhost is unreliable behind DNS-intercepting proxies (Surge, Proxyman) which can route depth-greater-than-1 .localhost names to the real internet. The project name is therefore folded into the single label with a dash.

Tracked in the team-lead memory feedback_dev_preview_url_scheme.

Cleanup

# From the main checkout
git worktree list                          # see all live worktrees
git worktree remove .agents/worktrees/pr-x-topic  # delete checkout + branch metadata
git worktree prune                         # clean up stale worktree records

Don't delete .agents/worktrees/<name>/ manually with rm -rf — git keeps a record of every worktree under .git/worktrees/ that gets stale.

Local state noise to ignore

git status --ignored in the main checkout may show:

  • D .claude/scheduled_tasks.lock — Claude Code session state, ephemeral
  • !! .agents/worktrees/ — ignored local checkouts for parallel agent work

Both are local agent state and are ignored. Don't stage them.

On this page