Dev server & ports
Every chat runs its repository’s run script (set in the repository settings, never in your repository) and previews whatever it serves. Screenplay picks the port for each chat. The one requirement on your app: the dev server must listen on the port it’s given.
Most frameworks do this out of the box, because Screenplay passes the port in
the standard PORT environment variable.
Desktop
Every chat’s dev server runs on your machine, so each one needs its own port. The
run script is started under portless, which sets
PORT to the port allocated to that chat:
# Next.js, Create React App, Express, Nuxt… honor $PORT already
npm run dev
# Vite and other tools that ignore $PORT: pass it explicitly
vite --port $PORT --strictPort
# A custom server
node server.js --port $PORTportless ships with the app, and Screenplay starts its proxy for you. It also
gives each chat a stable URL like
http://my-branch.my-app.localhost:1355 that survives dev server restarts.
Open in browser on a chat opens it.
If you run npx portless proxy start yourself (it asks for sudo to use
port 443 and trust a local HTTPS certificate), Screenplay uses that proxy
instead, and the stable URLs drop the port:
https://my-branch.my-app.localhost. npx portless service install does the
same and starts that proxy whenever your Mac starts.
A script that honors $PORT works on both.
When the preview never loads
On desktop, a dev server that never listens on its assigned port fails with
a clear error (DevServerPortIgnoredError) instead of leaving a blank frame.
The usual cause is a script that ignores $PORT:
- Fix the Run script in the repository settings (for example add
--port $PORT). - Use Restart → Restart dev server on the chat.
- Still stuck? The chat’s Dev server terminal (under its composer) shows the dev server’s and portless’s output. The chat’s agent can read the same output and restart the dev server, so you can also ask it to debug the preview.
Troubleshooting lists the other ways a preview can fail.
Monorepos
A repository on a canvas previews one app. Filter your monorepo’s dev command down to that app, and pass the port as a flag:
# Turborepo
turbo run dev --filter=web -- --port $PORT
# pnpm workspaces
pnpm --filter web dev -- --port $PORTWith Turborepo, pass the port as a flag, as above, instead of relying on the
PORT variable reaching the task. Turborepo 2’s strict env mode strips
undeclared variables, so $PORT is empty inside the task. Package
scripts that hardcode --port 3001 also win over the variable. The
appended flag handles both, because for Next.js and Vite the last --port
wins. Alternatively, add "passThroughEnv": ["PORT"] to the dev task in
turbo.json.
To work on a second app from the same monorepo, add the repository again with
its own run script and a name such as web or api; see
Monorepos.
Why it works this way
- On desktop, chats run directly on your machine, not in containers, so your already-authenticated tools (including coding CLIs on a subscription login) work inside every chat.
- Adopting Screenplay should never require changing your code. The run script lives in Screenplay’s settings, and Screenplay invokes portless itself, so your repository needs no config files or dependencies.