Development
Prerequisites
git clone https://github.com/zschiller/screenplay.git
cd screenplay
pnpm installThe monorepo
apps/app/CONTEXT.md is the glossary for the product’s domain language.
Design decisions are recorded as ADRs in apps/app/docs/adr/. Read both
before a larger change.
Running the product
There are two ways to run apps/app locally.
Local mode (no services)
The same build switches the desktop app uses let you run the full product with no external services: embedded Postgres, a local Yjs server, and each chat’s code as a git worktree. You need a coding CLI such as Claude Code installed for the agent.
cd apps/app
NEXT_PUBLIC_SCREENPLAY_LOCAL=1 NEXT_PUBLIC_YJS_HOST=local NEXT_PUBLIC_BASE_PATH= \
SANDBOX_BACKEND=local SCREENPLAY_DB=pglite BLOB_STORE=local-fs AGENT_ENGINE=external \
ENCRYPTION_KEY=$(openssl rand -hex 32) TERMINAL_AUTH_SECRET=$(openssl rand -hex 32) \
pnpm devOpen http://localhost:3000. Data lives under
apps/app (.pglite/, .data/yjs/, .screenplay/blobs/), and worktrees go
under ~/.screenplay/.
pnpm dev at the repository root starts every app through Turborepo.
Run it inside apps/app to start just the product.
Commands
From the repository root (runs across all workspaces via Turborepo):
pnpm dev # dev servers
pnpm build # production builds (apps/app runs its migrations first)
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit
pnpm format # Prettier
pnpm test # VitestIn apps/app, pnpm test:watch runs Vitest in watch mode. Database helpers:
pnpm db:generate # generate a migration from schema changes (commit it)
pnpm db:migrate # apply migrations to $DATABASE_URL
pnpm db:push # sync the schema without a migration (throwaway dev only)
pnpm db:studio # open Drizzle StudioSee Database for the migration workflow.
Desktop app
pnpm --filter desktop build:sidecar # build apps/app as the bundled sidecar
pnpm --filter desktop dev # run the Tauri app
pnpm --filter desktop build # package Screenplay.appThis needs the Rust and Tauri toolchains. See Desktop app.
Releasing the desktop app
Releases are cut on a Mac with one command:
pnpm --filter desktop release patch # or minor, major, or an exact X.Y.ZIt bumps the version, builds a signed and notarized .dmg, checks it with
Gatekeeper, then commits the bump, tags desktop-v<version> and publishes a
GitHub Release with the
.dmg attached, once under its version and once as Screenplay.dmg, the name
the homepage’s Download buttons link to. Nothing is tagged or published unless
the build verifies.
The Release’s notes are a short changelog for people using the app, not a list
of commits. Before the build, the script collects the pull requests merged
since the last release that change the app (skipping the homepage, docs, tests
and CI), and asks claude -p to write them up as New, Improved and Fixed. It
shows the draft so you can accept it, edit it in $EDITOR, or stop. Without
Claude Code installed, the draft lists the pull request titles. To write the
notes yourself, pass a Markdown file with --notes notes.md.
Releasing needs Node 23 or later, the gh CLI signed in, a clean working tree, and the
Apple signing keys in apps/desktop/.env.release (copy
.env.release.example).
Screenshots for design review
Design-polish tickets ship before/after screenshots on the PR. The screenshot harness boots the local build against a seeded fixture world and captures a named list of screens in light and dark:
pnpm --filter app screenshots:boot # a browsable, fully-populated local build
pnpm --filter app screenshots:shots --screens canvas # named screens, both themesThe harness README covers the before/after workflow, how to add a screen, and what the local build leaves out.
Writing docs
These docs live in apps/docs/content as MDX. Run the site with
pnpm --filter docs dev, then open
http://localhost:3002/docs.
Screenshots are framed WebP images in apps/docs/public/screenshots, in a
light and a dark variant (name.light.webp, name.dark.webp). Embed one with:
<Screenshot name="hero" alt="Describe what the screenshot shows" />The component shows the variant that matches the reader’s theme. Put two related shots side by side (they stack on a phone) with:
<ScreenshotRow>
<Screenshot name="frame-ask" caption="Just drawn" alt="…" />
<Screenshot name="frame-unanswered" caption="Skipped" alt="…" />
</ScreenshotRow>Keep full-window shots for the Introduction and Core concepts, where the
whole layout is the point. Everywhere else, crop to the part the section is
about. Every
screenshot is generated: run pnpm --filter docs screenshots to regenerate them
after a UI change, and see Docs screenshots to
add one.
Issues
Bugs and feature requests are tracked in GitHub issues.