This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Lightning is an open source workflow platform for governments and non-profits to move health and survey data between systems. It's built on Elixir/Phoenix with PostgreSQL, featuring React components and real-time collaborative editing via Yjs CRDTs.
./bin/bootstrap # Initial setup (run once, or when switching branches)
iex -S mix phx.server # Run development server
mix verify # Run all code quality checks before committingmix test # Run all tests (don't use -v flag)
mix test path/to/test.exs # Run single file
mix test path/to/test.exs:42 # Run test at specific linecd assets
npm test # Run unit tests once
npm run test:watch # Run tests in watch mode
npm run test:e2e # Run E2E tests
npm run test:e2e:ui # E2E with interactive UI
npm run lint # Lint TypeScript
npx tsc --noEmit --project ./tsconfig.browser.json # Type checkmix format # Format Elixir code (before committing)
mix credo --strict --all # Static analysis
mix dialyzer # Type checking
mix sobelow # Security analysismix ecto.migrate # Run migrations
mix ecto.reset # Drop and recreate database
mix ecto.gen.migration short_descriptive_name # Generate migrationImportant Notes:
- Phoenix auto-builds assets; use
mix esbuild defaultor Tidewave MCP to check for build errors - For JS/TS issues, prefer
mcp__ide__getDiagnosticsovertsc - Never commit the
.contextdirectory (symlink to shared folder)
- Workflows - DAGs with jobs, triggers, edges (
lib/lightning/workflows.ex) - Jobs - JavaScript execution units with NPM adaptors
- Accounts - User management, authentication
- Projects - Main organizational unit
- Credentials - External service authentication (encrypted)
- Runs / WorkOrders - Workflow execution management
- Collections - Key-value data store
Workflows are directed acyclic graphs (DAGs):
- Triggers: Webhook, Cron, or Kafka initiation
- Jobs: JavaScript code executed with NPM adaptors
- Edges: Flow control with conditions (
:always,:on_job_success,:on_job_failure,:js_expression)
Key features:
- Snapshot system with
lock_versionoptimistic locking - Real-time collaborative editing via Yjs CRDTs
- Presence system for edit priority
Real-time multi-user workflow editing using:
- Yjs - CRDT for conflict-free collaborative editing
- y-phoenix-channel - Yjs sync over Phoenix Channels
- Y_ex - Elixir Yjs bindings (see
.claude/guidelines/yex-guidelines.md)
Y.Doc transactions have non-obvious deadlock hazards on the BEAM. See
.claude/guidelines/yex-guidelines.md §Transaction Deadlock Rulesbefore writing any server-side Y.Doc code.
Store Architecture (see .claude/guidelines/store-structure.md):
- SessionStore - Y.Doc, connection state, sync status
- WorkflowStore - Jobs, triggers, edges, positions (Y.Doc backed)
- AwarenessStore - User presence, cursors, selections
- SessionContextStore - User, project, permissions (Phoenix Channel)
- AdaptorStore / CredentialStore - Reference data
All stores use useSyncExternalStore + Immer and integrate with Redux DevTools
in development.
- PostgreSQL with Ecto ORM
- Dev:
postgres://postgres:postgres@localhost:5432/lightning_dev - Test:
lightning_test, no manual setup step; thetestalias atmix.exs:227runsecto.create --quietandecto.migrate --quietbefore the suite - Schemas alongside contexts in
lib/lightning/
- Use
{}brace syntax in HEEx templates warnings_as_errors: true- code must compile without warnings
Before writing or reviewing any supervisor, GenServer, or named process, see
.claude/guidelines/testable-supervision-trees.md. Don't bakename: __MODULE__into a process or resolve collaborators from global state — it forces the test suite serial.
- On the
ReactComponenthook, props arrive as the element's raw attribute names:data-prefixed kebab-case, not camelCase - Use
cn()utility from#/utils/cnfor conditional CSS classes - Use heroicons via Tailwind:
className="hero-check-micro h-4 w-4" - See
.claude/guidelines/toast-notifications.mdfor notification patterns
- Backend: ExUnit with ExMachina factories
- Frontend: Vitest (see
.claude/guidelines/testing-essentials.md) - E2E: Playwright (see
.claude/guidelines/e2e-testing.md) - Assertion grouping and test file length: see
.claude/guidelines/testing-essentials.md
External Node.js workers (@openfn/ws-worker) execute JavaScript jobs:
- WebSocket communication with JWT authentication
- Two-layer security: Worker Token + Run Token
- Generate keys:
mix lightning.gen_worker_keys - Required ENVs:
WORKER_RUNS_PRIVATE_KEY,WORKER_SECRET,WORKER_LIGHTNING_PUBLIC_KEY
Claude Code injects every agent's name and description each session, so the
roster is not restated here; the agent files themselves are in
.claude/agents/. Command files (create-plan.md, implement-plan.md,
research-codebase.md) cross-ref this section rather than repeating it.
One convention that no agent file carries: dispatch web-search-researcher on
request, not by default.
These files cite implementation as file:line rather than copying it, so read the
source at the citation instead of trusting a transcription.
.claude/guidelines/store-structure.md- store responsibilities in the collaborative editor; where new state belongs.claude/guidelines/yex-guidelines.md- Yex (Yjs/Elixir) usage rules.claude/guidelines/testable-supervision-trees.md- naming and wiring OTP processes so tests can address them and still runasync: true.claude/guidelines/toast-notifications.md- notification patterns.claude/guidelines/testing-essentials.md- Vitest unit testing, and the entry point for:.claude/guidelines/testing/react-patterns.md- React Testing Library.claude/guidelines/testing/vitest-advanced.md- fixtures and test data.claude/guidelines/testing/collaborative-editor.md- Y.Doc and Phoenix channel mocks
.claude/guidelines/e2e-testing.md- Playwright E2E, and the entry point for:.claude/guidelines/e2e/phoenix-liveview.md- driving LiveView pages; the canonical wait patterns.claude/guidelines/e2e/page-objects.md- Page Object Model structure.claude/guidelines/e2e/collaborative-testing.md- multi-user collaborative features
.claude/rules/logging.md (Logger levels, Sentry noise) and
.claude/rules/ui-patterns.md (button variants, Tailwind conventions) are not in
that list because they are not read on request: each declares a paths: glob and
loads by itself when you work on a file it matches.