Internal project workspace app built with Next.js, Convex, WorkOS, hosted PartyKit collaboration services, and an optional Electron desktop shell.
This README is intended for contributors joining the repo so they can get the project running locally without needing existing team context.
Next.jsApp Router for the web app and API routesConvexfor app data, queries, mutations, and generated bindingsPartyKit+Yjsfor live collaborative editing and transient chat presence- scoped read-model sync for bounded realtime refreshes, with legacy snapshot streaming kept as a rollback path
WorkOS AuthKitfor authentication and organization membershipResendfor email delivery100msfor video/call roomsElectronfor the desktop wrapperpnpmfor package management
app/: Next.js pages, layouts, auth routes, and API routescomponents/: app UI and shared UI primitivesconvex/: schema, functions, and generated Convex API bindingselectron/: desktop entrypointshooks/: client hooks for collaboration, scoped refresh, and retained UI statelib/: server helpers, auth helpers, Convex client code, collaboration utilities, and shared app logicservices/partykit/: hosted PartyKit collaboration runtimescripts/: operational scripts for bootstrapping and maintenancetests/: Vitest coverage for app, Convex, collaboration, scripts, and Electron behaviordocs/architecture/: operational runbooks and architecture notes
Before you start, make sure you have:
- A recent version of
Node.js pnpminstalled- Access to the required third-party services:
- Convex
- PartyKit / Cloudflare for deploying and inspecting the hosted collaboration runtime
- WorkOS
- Resend
- 100ms
Copy the example file and fill in real values locally:
cp .env.example .env.localThe app expects the following variables.
Use the exact runtime env names below in .env.local. Do not create parallel
*_DEVELOPMENT or *_PRODUCTION copies in the same file. If you want to point
local development at production services, keep the provider credentials here and
use local values only for the app URL fields.
If you want the file to also capture development and production reference
profiles, keep that information as comment-only blocks in .env.local, grouped
by provider. Do not add extra live env variable names for those references
unless the runtime actually reads them.
CONVEX_URL: server-side Convex deployment URLNEXT_PUBLIC_CONVEX_URL: browser-visible Convex deployment URLCONVEX_SERVER_TOKEN: shared server token used by Next.js routes and Convex functions for the active app environmentCONVEX_DEPLOY_KEY: Convex deploy key used for deployment/codegen workflows
NEXT_PUBLIC_PARTYKIT_URL: canonical hosted PartyKit base URL for the active environmentCOLLABORATION_TOKEN_SECRET: shared token secret used by the app and the matching PartyKit serviceNEXT_PUBLIC_ENABLE_COLLABORATION: optional collaborative editor flag, defaults to enabledNEXT_PUBLIC_ENABLE_SCOPED_SYNC: optional scoped read-model sync flag, defaults to enabledNEXT_PUBLIC_ENABLE_LEGACY_SNAPSHOT_STREAM: optional legacy snapshot stream flag, defaults to disabled
New config should use NEXT_PUBLIC_PARTYKIT_URL. The runtime still accepts the
older PARTYKIT_URL, NEXT_PUBLIC_COLLABORATION_SERVICE_URL, and
COLLABORATION_SERVICE_URL aliases, but they should not be used for new env
profiles.
WORKOS_CLIENT_ID: WorkOS client IDWORKOS_API_KEY: WorkOS server API keyWORKOS_COOKIE_PASSWORD: cookie encryption secretDESKTOP_SESSION_SECRET: server-only secret used by hosted routes to issue desktop session tokensWORKOS_COOKIE_DOMAIN: shared auth cookie domain, usually blank on localhostNEXT_PUBLIC_WORKOS_REDIRECT_URI: WorkOS callback URLDESKTOP_WORKOS_REDIRECT_URI: hosted WorkOS callback URL for desktop browser handoffDESKTOP_DEEP_LINK_SCHEME: custom desktop URL scheme used after hosted auth completesDESKTOP_API_ALLOWED_ORIGINS: optional comma-separated desktop renderer origins allowed to call hosted API routes; includenullfor the packagedfile://renderer
APP_URL: base app URL used by the app and email linksNEXT_PUBLIC_APP_URL: public app origin fallback used by email and script helpersTEAMS_URL: app/team entry URL used in auth routingNEXT_PUBLIC_API_BASE_URL: optional hosted Vercel API origin for packaged desktop builds; leave blank for same-origin web devNEXT_DEV_SERVER_URL: optional Electron dev-server overrideELECTRON_RENDERER_URL: explicit Electron renderer URL override for development and transition builds
RESEND_API_KEY: Resend API keyRESEND_FROM_EMAIL: sender address for outbound emailRESEND_FROM_NAME: optional sender display name for outbound emailCRON_SECRET: bearer secret used by Vercel cron routesDRY_RUN: optional script flag for previewing notification digests without sending them
HMS_ACCESS_KEY: 100ms access keyHMS_SECRET: 100ms secretHMS_TEMPLATE_ID: 100ms room template IDHMS_TEMPLATE_SUBDOMAIN: 100ms subdomain
These commands help verify the linked project and the provider-side env names without changing local runtime config:
vercel env ls
vercel env pull .vercel/.env.production.local
convex env listThe linked Vercel project metadata lives in .vercel/project.json. Keep that
metadata there instead of copying VERCEL_* values into .env.local.
- Install dependencies:
pnpm install- Create your local env file:
cp .env.example .env.local-
Fill in the required environment variables in
.env.local. -
Start the web app:
pnpm dev- Open
http://localhost:3000.
Local web development uses the hosted dev PartyKit service. Do not start a
separate local partykit dev process for normal app work.
PartyKit is the live room/runtime layer only. Convex remains canonical for app data, document content, work-item descriptions, and permission checks.
Hosted services are mapped 1:1 to Convex:
linear-collaboration-dev-> Convex devlinear-collaboration-prod-> Convex prod
Use these app env values with the matching provider stack:
- local/dev app:
NEXT_PUBLIC_PARTYKIT_URL=https://linear-collaboration-dev.<subdomain>.partykit.dev - production app:
NEXT_PUBLIC_PARTYKIT_URL=https://linear-collaboration-prod.<subdomain>.partykit.dev
For each hosted PartyKit service, provision these secrets directly in Cloudflare/PartyKit:
CONVEX_URLCONVEX_SERVER_TOKENCOLLABORATION_TOKEN_SECRET
The app and the matching PartyKit service must use the same
COLLABORATION_TOKEN_SECRET. Do not rely on .env.local to inject service
secrets during PartyKit deploys.
Current PartyKit scope:
- team and workspace document collaboration
- collaborative work-item descriptions, including main-section flushes that can persist title and description together
- ephemeral chat presence and typing state
Not in PartyKit scope:
- private documents
- non-collaborative Convex-only editor paths
- long-term storage ownership or permission decisions
Collaboration sessions are issued by the Next.js API routes under
app/api/collaboration/. The client receives a short-lived signed room token,
then connects to the hosted PartyKit room. Document rooms reseed from Convex on
connect and flush canonical content back to Convex on debounce, manual save, and
last-editor teardown paths.
Deploy the hosted runtime with pnpm partykit:deploy:dev or
pnpm partykit:deploy:prod, then inspect it with the matching
pnpm partykit:tail:* command. If a change touches PartyKit room behavior,
session issuance, token semantics, collaborative editor boot/save behavior, or
Convex-backed collaboration helpers, coordinate the web app, PartyKit, and Convex
deploys unless the change is explicitly backward compatible across mixed
versions.
If collaboration needs to be disabled without rolling back the whole app, set:
NEXT_PUBLIC_ENABLE_COLLABORATION=falsepnpm dev
pnpm build
pnpm start
pnpm lint
pnpm typecheck
pnpm test
pnpm test:watch
pnpm check
pnpm audit:deps
pnpm convex:codegen
pnpm convex:deploy
pnpm partykit:deploy:dev
pnpm partykit:deploy:prod
pnpm partykit:tail:dev
pnpm partykit:tail:prod
pnpm maintenance:backfill-lookups
pnpm maintenance:backfill-workspace-memberships
pnpm desktop:dev
pnpm desktop:start
pnpm desktop:start:local-server
pnpm desktop:smoke
pnpm desktop:package:macThese scripts require a correctly configured .env.local and usually talk to live services:
pnpm bootstrap:workspace: create/bootstrap a workspace for a userpnpm maintenance:backfill-lookups: backfill legacy lookup fields and label/workspace ownership metadatapnpm maintenance:backfill-workspace-memberships: backfill workspace membership recordspnpm emails:send-jobs: send queued outbound email jobs from the durable email outboxpnpm notifications:send-digests: send notification digest emails- production queued email delivery runs through the scheduled
/api/internal/email-jobsworker pnpm sync:workos:workspaces: sync Convex workspaces to WorkOS organizations
BACKFILL_BATCH_LIMIT=<n> can be used to reduce mutation batch size for the
backfill scripts.
Operational expectations for deploys, backfills, sync jobs, and desktop smoke checks are documented in docs/architecture/deployment-migration-runbook.md.
Collaboration-specific architecture and operations:
- docs/architecture/partykit-cloudflare-runbook.md
- docs/architecture/realtime-collaboration-rollout.md
- docs/architecture/collaboration-production-assessment.md
For web-only work, pnpm dev is enough.
The real-user desktop target is a packaged Electron frontend that calls hosted Vercel API routes and hosted provider services. Private server keys stay on Vercel, Convex, PartyKit, WorkOS, Resend, and 100ms; Electron packages only public client config and desktop code.
Desktop auth stores only user-scoped session tokens. Electron persists them
with safeStorage when OS encryption is available and falls back to in-memory
storage for the current run when it is not.
Native notifications are issued through a narrow Electron IPC bridge. The renderer does not receive broad browser notification permission.
If you need the Electron shell in development:
pnpm desktop:devThe transitional hosted-renderer shell run is:
pnpm desktop:startThe mac package defaults to hosted-renderer transition mode:
pnpm desktop:package:macBuild the local packaged renderer with:
pnpm desktop:renderer:buildBuild the mac package with the packaged renderer copied into Electron:
pnpm desktop:package:mac:packaged-rendererCheck packaged-renderer coverage with:
pnpm desktop:renderer:readinessInspect the packaged app for release blockers with:
pnpm desktop:release:preflightThe legacy local standalone smoke path is:
pnpm build
pnpm desktop:start:local-serverThe local standalone path is for smoke coverage and is not the target real-user desktop architecture. The package/frontend migration plan lives in docs/architecture/electron-packaged-frontend-plan.md.
- Do not commit real secrets or copied
.env.localvalues - Update
.env.examplewhen new required environment variables are added convex/_generated/is generated output; runpnpm convex:codegenif Convex types drift- Some scripts mutate shared data or external services, so do not run them casually against production credentials