For maintainers. Using T3 Code? See docs/user.
T3 Code uses Vite+. Install the global vp command, install
dependencies, then start the dev stack:
curl -fsSL https://vite.plus | bash # Windows: irm https://vite.plus/ps1 | iex
vp i
vp run devNode 24 is required. Bun is not: the server picks Bun adapters when it detects Bun and falls back to Node otherwise, and nothing in contributor setup needs it.
vp run dev prints a one-time pairing URL. Open it so the first browser navigation is
authenticated.
vp run dev: Starts contracts, server, and web in watch mode.vp run dev --share: Also publishes the web port over HTTPS on this machine's tailnet. The startup pairing URL is built against the shared origin, and the mapping is removed on exit.vp run dev --browser: Auto-opens a browser. Off by default. The dev runner writesT3CODE_NO_BROWSERitself from this flag, so settingT3CODE_NO_BROWSER=0in your environment has no effect; use--browser.vp run dev:server: Starts just the server. It runs on Node (node --watch src/bin.ts), so without Bun present it selectsNodePtyAdapterandNodeHttpServer.vp run dev:web: Starts just the Vite dev server for the web app.vp run dev:desktop: Starts the Electron shell against the dev server.vp run dev:marketing: Starts the Astro marketing site.- Pass dev-runner flags directly after the root task name, for example:
vp run dev --home-dir /tmp/t3code-dev
- Dev commands run from a linked git worktree default to that worktree's gitignored
.t3, even whenT3CODE_HOMEis set, storing state in<worktree>/.t3/userdata. Pass--home-dir <path>to choose another isolated directory explicitly. Submodules are not worktrees and keep the normal precedence. - From the main checkout, dev commands implicitly use
~/.t3/dev, keeping development state separate from~/.t3/userdata. An explicit--home-dir <path>stores state under<path>/userdata; the base directory remains available for caches, worktrees, and other shared data.
vp run build: Fans out overapps/*,packages/*,oxlint-plugin-t3code, andscripts. Workspaces that define a build task run one: desktop, marketing, server (which depends on web), and web. Shared packages are consumed and bundled transitively rather than built separately.vp run build:desktop: Builds the desktop pipeline (desktop plus server).vp run start: Runs the production server (serves the built web app as static files).vp check: Vite+ format, lint, and type checks. This repo setstypeCheck: falsein its lint options, so workspace type checking runs separately.vp run typecheck: Strict TypeScript checks for all packages.vp run test: Runs workspace tests.vp run lint:mobile: Mobile native static analysis (scripts/mobile-native-static-check.ts).node apps/server/scripts/t3-sqlite-state.ts <query|exec> --base-dir <path> ...: Inspects or seeds an isolated T3 SQLite database; writes create a private backup first.
vp run dist:desktop:artifact --platform <mac|linux|win> --target <target> --arch <arch>: Builds a desktop artifact for a specific platform/target/arch.vp run dist:desktop:dmg: Builds a shareable macOS.dmginto./release. Architecture defaults to the host, so this produces an arm64 DMG on Apple Silicon. Usedist:desktop:dmg:arm64ordist:desktop:dmg:x64, or pass--arch <arm64|x64|universal>, to force one.vp run dist:desktop:linux: Builds a Linux AppImage into./release.vp run dist:desktop:win: Builds a Windows NSIS installer into./release.:arm64and:x64variants exist.
- Default build is unsigned/not notarized for local sharing.
- The DMG build uses
assets/prod/black-macos-1024.pngas the production app icon source. - Desktop production windows load the bundled UI from the
t3code://app/root URL (not a127.0.0.1document URL, and not an explicitindex.htmlpath). - Desktop packaging includes
apps/server/dist(thet3backend) and starts it on loopback with an auth token for WebSocket/API traffic. - Your tester can still open it on macOS by right-clicking the app and choosing Open on first launch.
- To keep staging files for debugging package contents, run:
vp run dist:desktop:dmg --keep-stage - To allow code-signing/notarization when configured in CI/secrets, add:
--signed. - Signed macOS builds also require
T3CODE_APPLE_TEAM_IDandT3CODE_MACOS_PROVISIONING_PROFILE. The passkey RP domain is derived fromT3CODE_CLERK_PUBLISHABLE_KEYunlessT3CODE_CLERK_PASSKEY_RP_DOMAINSoverrides it. - Windows
--signeduses Azure Trusted Signing and expects:AZURE_TRUSTED_SIGNING_ENDPOINT,AZURE_TRUSTED_SIGNING_ACCOUNT_NAME,AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME, andAZURE_TRUSTED_SIGNING_PUBLISHER_NAME. - Azure authentication env vars are also required (for example service principal with secret):
AZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRET.
dev and dev:web leave VITE_HTTP_URL and VITE_WS_URL unset so the browser resolves the backend
from window.location.origin. Vite proxies /api, /ws, /oauth, and /.well-known to the
server, allowing the same bundle to work from localhost or a tailnet hostname.
Worktrees derive a preferred port offset from their path.
- Default ports: server
13773, web5733 - Shifted ports:
base + offset - Example:
T3CODE_DEV_INSTANCE=branch-a vp run dev:desktop
Offset resolution, in order:
T3CODE_PORT_OFFSET, which must be a non-negative integer. Negative values are rejected.T3CODE_DEV_INSTANCE. An all-digit value is used directly as the offset; any other non-empty value is hashed into one.- The worktree path hash.
Collision scanning depends on the mode. dev:web scans only the web port and shifts only the web
offset. dev:server scans only the server port. dev and dev:desktop scan both and shift them
together as one shared offset. Explicit server or dev-URL overrides remove the corresponding port
from the availability check. Treat the [dev-runner] output as authoritative.