$npx -y skills add vercel/workflow --skill internal-dev-workbenchSpin up a portless + tmux dev session for the Workflow SDK that gives each git worktree isolated <branch>.<name>.localhost URLs for the Next.js workbench and the observability UI, plus a Claude statusline that surfaces those URLs. Use only when the user asks for a "portless dev
| 1 | # internal-dev-workbench |
| 2 | |
| 3 | Bootstraps an opinionated 3-pane tmux session for end-to-end Workflow SDK development. Each pane is launched through [portless](https://github.com/aleclarson/portless) so URLs are stable and worktree-scoped (e.g. `https://<branch>.turbopack.localhost`), letting multiple worktrees run concurrently without port conflicts. A companion statusline script surfaces the active URLs in Claude Code's prompt. |
| 4 | |
| 5 | This is **opt-in contributor tooling**. The repo's standard dev path (`pnpm dev` from a workbench, no portless) is unaffected. |
| 6 | |
| 7 | ## Prerequisites |
| 8 | |
| 9 | - `tmux` installed |
| 10 | - `portless` installed globally (`npm i -g portless` or via Homebrew). Verify with `portless --version`. |
| 11 | - Repo bootstrapped: `pnpm install && pnpm build`. The first run on a fresh worktree must complete both before any dev server can start (the workbench apps depend on built workspace packages — without `pnpm build` you get `MODULE_NOT_FOUND` for `workflow`). |
| 12 | - `WORKFLOW_PUBLIC_MANIFEST=1` is required on the dev server when running e2e tests against it (otherwise `/.well-known/workflow/v1/manifest.json` is gated). |
| 13 | |
| 14 | ## Layout |
| 15 | |
| 16 | `main-vertical` — the dev server takes the left column; the right column stacks the observability UI on top of a scratchpad shell: |
| 17 | |
| 18 | ``` |
| 19 | +----------------------+--------------------------+ |
| 20 | | | PANE_OBS: workflow web | |
| 21 | | | (observability UI | |
| 22 | | PANE_DEV: turbopack | scoped to the | |
| 23 | | (Next.js dev) | workbench app) | |
| 24 | | +--------------------------+ |
| 25 | | | PANE_SH: zsh scratchpad | |
| 26 | | | (repo root — for build, | |
| 27 | | | tests, e2e, git, etc.) | |
| 28 | +----------------------+--------------------------+ |
| 29 | ``` |
| 30 | |
| 31 | ## Setup |
| 32 | |
| 33 | The session name **must** match the worktree's portless prefix — the basename of the current branch — so the statusline (and any other tooling that derives the prefix from the branch) can locate it. Always run `tmux ls` first to confirm there's no pre-existing session with that name; never kill an existing one. |
| 34 | |
| 35 | Pane indices in tmux depend on `pane-base-index` (0 by default, 1 with the common dotfile override). To stay correct under either, capture each pane's ID at split time with `-P -F '#{pane_id}'` and use those IDs as targets: |
| 36 | |
| 37 | ```bash |
| 38 | REPO=/path/to/workflow--<worktree-suffix> |
| 39 | # Session name = basename of the branch (matches portless's subdomain prefix |
| 40 | # and the statusline's `tmux attach -t <prefix>` indicator). For branch |
| 41 | # `pgp/foo-bar` this resolves to `foo-bar`. |
| 42 | SESSION=$(git -C "$REPO" rev-parse --abbrev-ref HEAD) |
| 43 | SESSION="${SESSION##*/}" |
| 44 | |
| 45 | # Create the session and capture the initial pane ID |
| 46 | PANE_DEV=$(tmux new-session -d -s "$SESSION" -c "$REPO" -P -F '#{pane_id}') |
| 47 | PANE_OBS=$(tmux split-window -h -t "$PANE_DEV" -c "$REPO" -P -F '#{pane_id}') |
| 48 | PANE_SH=$(tmux split-window -v -t "$PANE_OBS" -c "$REPO" -P -F '#{pane_id}') |
| 49 | tmux select-layout -t "$SESSION" main-vertical |
| 50 | |
| 51 | # Pane DEV (left): Next.js turbopack workbench, with manifest exposed for e2e |
| 52 | tmux send-keys -t "$PANE_DEV" \ |
| 53 | 'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m |
| 54 | |
| 55 | # Pane OBS (top-right): observability UI scoped to the workbench app |
| 56 | tmux send-keys -t "$PANE_OBS" \ |
| 57 | 'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m |
| 58 | |
| 59 | # Pane SH (bottom-right): scratchpad at repo root |
| 60 | tmux send-keys -t "$PANE_SH" 'echo "scratchpad: $(pwd)"' C-m |
| 61 | |
| 62 | tmux attach -t "$SESSION" |
| 63 | ``` |
| 64 | |
| 65 | Once both servers are ready, `portless list` shows the routes. With `portless run`, each linked worktree gets a unique branch-prefixed subdomain (e.g. `stepflow-test.turbopack.localhost`), so multiple worktrees coexist without changing config. |
| 66 | |
| 67 | ## Why each piece |
| 68 | |
| 69 | - **`portless run --name <name>`** (instead of `portless <name> <cmd>`): `run` auto-detects git worktrees and prepends the sanitized branch name as a subdomain. The `--name` flag overrides the inferred base name while preserving the worktree prefix. |
| 70 | - **`pnpm workflow web --webPort $PORT --noBrowser`** (instead of `pnpm dev` in `packages/web`): the bundled CLI starts the observability UI configured against the **current workbench app**, hydrating it with that project's local World data. Running `packages/web`'s own `dev` script gives you the UI bu |