Contributing
Help improve Emdash - contribution guidelines and development setup
Thanks for your interest in contributing. We favor small, focused PRs with clear intent. This guide covers the local development setup, the commands that matter, and the conventions contributors should follow before opening a PR.
Quick Start
Prerequisites
- Git
- Node.js
24.14.0from.nvmrc [email protected]- Optional, but useful for integration work:
- GitHub CLI (
gh) - At least one supported coding agent CLI, see Providers
- Docker, when working on SSH development infrastructure
- GitHub CLI (
Use the pinned toolchain where possible:
nvm use
corepack enable
pnpm --versionGet The Source
Fork the repository on GitHub, then clone your fork:
git clone https://github.com/<you>/emdash.git
cd emdashInstall
From the repo root:
pnpm installThis repository is a pnpm workspace. The Electron app is in
apps/emdash-desktop/, and shared workspace packages live in packages/.
Start Development
For normal app development, run the full workspace dev command from the repo root:
pnpm run devThe root dev command now does two things:
- Builds all packages under
packages/. - Starts package watch builds and the Electron desktop app in parallel.
Use this command when you are changing code in packages/ or when you want the
same startup path a fresh contributor will use.
If you are only working inside apps/emdash-desktop/, you can run the Electron
dev server directly:
cd apps/emdash-desktop
pnpm run devFrom apps/emdash-desktop/, pnpm run d is a convenience command that runs
pnpm install and then starts pnpm run dev for the desktop app:
cd apps/emdash-desktop
pnpm run dImportant distinction:
pnpm run devfrom the repo root starts the workspace package watchers and the app together.pnpm run devfromapps/emdash-desktop/starts onlyelectron-vite devfor the desktop app.- If app code imports changed package output, prefer the root command so package
dist/files stay current.
Renderer changes usually hot reload. Main-process changes under
apps/emdash-desktop/src/main/ may require restarting the Electron dev app.
Repository Layout
This is a pnpm workspace monorepo.
apps/emdash-desktop/- Electron desktop app packageapps/emdash-desktop/src/main/- Electron main process, RPC controllers, services, database, PTY, SSH, Git, GitHub, updates, and integrationsapps/emdash-desktop/src/preload/- typed Electron preload bridgeapps/emdash-desktop/src/renderer/- React renderer appapps/emdash-desktop/src/shared/- shared app IPC, provider, event, MCP, skills, and domain typesapps/emdash-desktop/drizzle/- generated Drizzle migrations and metadataapps/emdash-desktop/scripts/- release, verification, and build scriptspackages/core/- transport-agnostic core runtime primitivespackages/shared/- shared workspace primitivespackages/ui/- shared UI components and theme systempackages/plugins/- plugin interfaces and helpersagents/- architecture, workflow, convention, integration, and risk docs
Root scripts are aggregate workspace scripts. Most app-specific commands live in
apps/emdash-desktop/package.json.
Common Commands
Run these from the repo root unless noted.
pnpm run dev # build packages, watch packages, and start the Electron app
pnpm run build # build every workspace package
pnpm run format # format with oxfmt
pnpm run format:check # check formatting without writing
pnpm run lint # lint with oxlint
pnpm run typecheck # run TypeScript checks
pnpm run test # run workspace testsUseful app-local commands from apps/emdash-desktop/:
pnpm run d # install dependencies, then start the desktop app
pnpm run dev # start electron-vite dev for the desktop app only
pnpm run dev:debug # start with debug logging
pnpm run dev:main # watch the Electron main process
pnpm run dev:renderer # watch the renderer
pnpm run build # build the Electron app
pnpm run build:main # build main process only
pnpm run build:renderer # build renderer only
pnpm run package # build and package desktop artifacts
pnpm run rebuild # rebuild native Electron dependencies
pnpm run reset # clean app dependencies and reinstallUseful package-local commands from a package under packages/:
pnpm run dev # watch-build that package with tsdown
pnpm run build # build that package with tsdown
pnpm run test
pnpm run typecheckLocal Validation
Before opening or merging a PR, run the local merge gate:
pnpm run format
pnpm run lint
pnpm run typecheck
pnpm run testThere are no pre-commit hooks. CI currently enforces:
pnpm run format:check
pnpm run typecheck
pnpm run lintTests are still expected locally even when a specific CI workflow does not run the full test suite.
Development Workflow
- Create a feature branch:
git checkout -b feat/<short-slug>- Keep PRs small and focused.
Update docs when behavior changes. Include screenshots or short recordings for UI changes where they help reviewers understand the result.
- Run validation locally.
Use the full merge gate above for broad changes. For narrow work, it is fine to run focused tests while iterating, then run the full gate before opening or merging the PR.
- Commit using Conventional Commits:
fix(opencode): change initialPromptFlag from -p to --prompt for TUI
feat(docs): add changelog tab with GitHub releases integration- Open a pull request.
Describe the change, the reason for it, and the validation you ran. Link related issues when relevant.
Code Style
- Use TypeScript strict mode.
- Use top-level
importstatements, notrequire(). - Do not introduce npm or yarn lockfiles.
- Use
pnpm. - Format with
oxfmt. - Lint with
oxlint. - Keep lines near the configured
printWidthof 100 characters. - Use 2 spaces, semicolons, single quotes in TypeScript, double quotes in JSX, LF endings, and trailing commas where valid in ES5.
- Avoid
any. If a boundary requires it, keep the escape local and document why. - Do not re-export as a shortcut. Import from the original source.
App Architecture Conventions
The app follows this high-level flow:
Renderer -> typed RPC client -> preload bridge -> Electron main -> controllers -> servicesMain process:
- RPC handlers live in
src/main/core/*/controller.ts. - Controllers should delegate to imported operation or service functions.
- Expected failures should use the
Result<T, E>pattern fromsrc/main/lib/result.ts. - Prefer
execFileoverexec. - Treat shell escaping, PTY spawning, SSH commands, and worktree paths as security-sensitive.
- Preserve secret redaction in logging and telemetry code.
Renderer:
- Feature UI lives under
src/renderer/features/<feature>/. - Shared renderer primitives, stores, hooks, commands, PTY, Monaco, modal
infrastructure, and UI live under
src/renderer/lib/. - Renderer RPC calls go through
rpcfromsrc/renderer/lib/ipc.ts. - New modals must be registered in
src/renderer/app/modal-registry.ts. - New views must be registered in
src/renderer/app/view-registry.ts. - New commands should use
src/renderer/lib/commands/registry.tsand view-levelcommandProviderhooks when possible. - Components use
PascalCase; hooks useuseXcamelCase or an existing local pattern.
State and stores:
- Access task managers through
getTaskManagerStore(projectId), notproject.taskManager. - Access mounted projects through
asMounted(getProjectStore(id)). - Never use
asProvisioned(...)!orasMounted(...)!; use explicit null checks. - State guards should check
kind !== 'ready'rather than enumerate non-ready states. - Task selectors live in
src/renderer/features/tasks/stores/task-selectors.ts. - Project selectors live in
src/renderer/features/projects/stores/project-selectors.ts.
Database And Migrations
Development database paths use Electron app.getPath('userData').
- macOS:
~/Library/Application Support/emdash-dev/emdash4.db - Linux:
~/.config/emdash-dev/emdash4.db - Windows:
%APPDATA%\emdash-dev\emdash4.db
Use an isolated scratch database when working on schema or migration changes. From the repo root:
EMDASH_DB_FILE=/tmp/emdash-scratch.db pnpm run devFor app-only development, change into apps/emdash-desktop/ first so this starts
only electron-vite dev:
cd apps/emdash-desktop
EMDASH_DB_FILE=/tmp/emdash-scratch.db pnpm run devReset dev databases from apps/emdash-desktop/:
pnpm run db:resetDatabase rules:
- Do not hand-edit numbered Drizzle migrations or
drizzle/meta/. - Use
pnpm run db:generatefor new migrations. - Update fixtures and migration tests when schema behavior changes.
- Run focused database validation from
apps/emdash-desktop/when relevant:
pnpm run db:setup
pnpm run db:fixtures
pnpm run test:migrationsRead agents/risky-areas/database.md before changing database internals.
Worktrees, PTY, SSH, And Providers
Emdash orchestrates coding agents in Git worktrees and PTY sessions. These areas are high impact.
- Do not delete worktree folders manually unless you know the matching Git state.
Prefer in-app cleanup or
git worktree prunefrom the main repository. - Do not weaken shell quoting, spawn behavior, environment allowlists, or secret redaction.
- PTY environment passthrough must use the allowlist in
src/main/core/pty/pty-env.ts. - Provider changes may need updates to shared provider metadata, dependency detection, PTY behavior, hooks/plugins, renderer assumptions, and tests.
Read the relevant risk or integration doc before touching these areas:
agents/risky-areas/pty.mdagents/risky-areas/ssh.mdagents/integrations/providers.mdagents/integrations/mcp.md
Testing Notes
- Unit tests use Vitest.
- Main database integration tests run in the
main-dbVitest project. - Migration tests run in the
migrationsproject. - Fixture generation runs in the
fixturesproject. - Renderer browser tests use Playwright-backed
@vitest/browser-playwright. - Main-process tests are colocated under
src/main/core/**/*.test.ts. - Renderer unit tests live under
src/renderer/tests/. - Renderer browser tests live under
src/renderer/tests/browser/. - Integration-style tests create temporary repos and worktrees in
os.tmpdir().
From apps/emdash-desktop/, the app test command is:
pnpm run testIt runs the app Vitest projects:
node, main-db, migrations, browser, scriptsNative Dependencies
After native dependency changes, rebuild Electron native modules from
apps/emdash-desktop/:
pnpm run rebuildThis is especially relevant for better-sqlite3 and node-pty.
Docker SSH Development
When working on Docker-backed SSH development infrastructure, start it from
apps/emdash-desktop/:
pnpm run run:docker-sshRead agents/workflows/remote-development.md and agents/risky-areas/ssh.md
before making SSH behavior changes.
Issue Reports And Feature Requests
Use GitHub Issues. Include:
- Operating system
- Emdash version or commit SHA
- Node and pnpm versions, if development-related
- Steps to reproduce
- Expected behavior
- Actual behavior
- Relevant logs, terminal output, or screenshots
Do not include secrets, tokens, private keys, local app databases, or private repository content in public issues.
Release Process For Maintainers
Do not dispatch release workflows, publish packages, or upload artifacts unless you are explicitly doing release work.
The app version lives in apps/emdash-desktop/package.json. For release version
bumps, run these from apps/emdash-desktop/:
pnpm version patch
pnpm version minor
pnpm version majorThis updates package.json and pnpm-lock.yaml, creates a version commit, and
creates a tag.
Production releases are dispatched through GitHub Actions:
gh workflow run release-prod.yml --ref main -f arch=bothCanary releases are dispatched through:
gh workflow run release-canary.yml --ref main -f arch=bothProduction releases publish artifacts to GitHub Releases as the primary update feed and Cloudflare R2 as fallback. Canary releases currently publish to R2 only.
Further Reading
agents/README.mdagents/quickstart.mdagents/architecture/overview.mdagents/architecture/main-process.mdagents/architecture/renderer.mdagents/conventions/ipc.mdagents/conventions/main-patterns.mdagents/conventions/renderer-patterns.mdagents/conventions/typescript.mdagents/workflows/testing.mdagents/workflows/worktrees.md