Files
VolcanicCore/CLAUDE.md
2026-08-28 16:38:55 +00:00

10 KiB

VolcanicCore — project context for Claude sessions

VolcanicCore is a self-hosted N8N-style workflow engine (C# backend + React frontend). Operator draws a directed graph of nodes (triggers → actions → terminals) in a browser canvas; each workflow can be activated independently. When active, trigger nodes subscribe/listen and each event fires one execution through the graph.

Documents to read first

  • Design spec — docs/superpowers/specs/2026-08-28-volcaniccore-workflow-engine-design.md. The whole architecture (execution model, extension contracts, webhook mechanics, persistence, deploy shape). Read this before writing plans or code.
  • Plan 1 (backend foundation, executed) — docs/superpowers/plans/2026-08-28-plan-1-backend-foundation.md. Shows the working style and the file structure conventions.
  • Plan 2 (workflow engine, executed) — docs/superpowers/plans/2026-08-29-plan-2-workflow-engine.md. Executor, WebhookRouter, TriggerHost, five nodes, NATS provider, retention.
  • Plan 3 (React SPA, executed) — docs/superpowers/plans/2026-08-30-plan-3-react-spa.md. Vite/React/TS/Tailwind/React Flow SPA under frontend/, bundled into wwwroot.
  • Follow-ups — docs/superpowers/followups.md. Cross-plan items surfaced during review that later plans must handle. Read before starting a plan; extend as new items surface.

Current status

Plan 3 (React SPA) shipped. Head commit at plan close: 294bd57. dotnet build clean, dotnet test 74/74, pnpm --dir frontend test 20/20, pnpm --dir frontend test:e2e 1/1 (Playwright smoke).

What's done (in addition to Plans 1 + 2):

  • Vite + React 18 + TypeScript + Tailwind SPA under frontend/ (pnpm).
  • Same-origin cookie auth via apiFetch (credentials: "include", typed ApiError / UnauthorizedError).
  • TanStack Query for server state; useSession / useLogin / useLogout + query hooks for every endpoint.
  • Router w/ PrivateRoute gate. Pages: Login / Workflows / Editor / Connections / Executions.
  • React Flow (@xyflow/react) editor w/ draggable palette from /api/node-types, right-panel schema-driven config form (auto-generated from each ConfigSchema), connection picker, per-node position + connection_id persisted.
  • Editor toolbar: Save (PUT /api/workflows/{id}), Test run (POST /api/workflows/{id}/executions), Activate/Deactivate, Executions link.
  • Executions viewer: per-workflow run list, click → per-node status/timing/expandable IO JSON.
  • MSW-driven Vitest suite (20 tests across apiFetch, useSession, SchemaForm, LoginPage, WorkflowsPage, ConnectionsPage, graphAdapter).
  • pnpm build emits into src/VolcanicCore.Api/wwwroot/; .Api serves w/ UseStaticFiles + MapFallbackToFile("index.html") (API + /wh routes still win — 4 new xUnit tests cover the ordering).
  • Vite dev proxy /api + /wh → http://127.0.0.1:5099 so pnpm dev co-exists with dotnet run.
  • Playwright happy-path smoke in frontend/e2e/smoke.spec.ts — login → create workflow → drop node → save → reload.

What's next: Plan 4 — Dockerfile + Coolify deploy. Not yet written. When authored, it will cover:

  • Multi-stage Dockerfile: pnpm build the SPA into wwwroot, dotnet publish the backend, distroless runtime image.
  • Coolify docker-compose using preparing-dotnet-react-app-for-coolify skill (named volume for /data, expose: [8080], SERVICE_FQDN_APP).
  • Wire UseForwardedHeaders + KnownProxies so Secure cookies work behind Traefik (Plan-2 follow-up).
  • Boot-time re-seed of legacy plaintext admin.password_hash_source (Plan-2 follow-up).
  • CI install of Playwright's Chromium (Plan-3 follow-up if needed).
  • v4 API deploy trigger — fire-and-forget per global CLAUDE.md rule.

Invariants (do NOT relearn from scratch)

  • Git commit identity. The repo has no configured identity. Every commit must pass -c user.email="claude@volcanic" -c user.name="Claude":
    git -c user.email="claude@volcanic" -c user.name="Claude" commit -m "..."
    
  • Working branch. main. The user opted in to this at project start. No feature branches unless requested.
  • Test parallelism is disabled at the assembly level. See the [assembly: CollectionBehavior(DisableTestParallelization = true)] attribute at the top of tests/VolcanicCore.Tests/WebFactory.cs. Multiple WebFactory instances share the process; pre-Configuration.Bind env-var reads made parallel classes flaky. Do not re-enable parallelism without a redesign.
  • Cookie authentication is in the shared framework. Do NOT add Microsoft.AspNetCore.Authentication.Cookies as a PackageReference. The last standalone version on NuGet is 2.3.0 (ASP.NET Core 2.x, 2019). .NET 9's Microsoft.AspNetCore.App shared framework provides the cookie-auth types with no PackageReference.
  • Program.cs env-var loop honors pre-set config. In tests, WebFactory.ConfigureWebHost uses builder.UseSetting(...) to inject DataDirectory and AdminPassword. The env-var loop in Program.cs skips keys already set by upstream sources (if (!string.IsNullOrEmpty(builder.Configuration[prop])) continue;). Do not remove this check.
  • Migration001Initial, 002Connections, 003Workflows, 004Executions, 005NodeExecutions are registered in the Migrator factory in Program.cs. Add new migrations to the same array in version order.
  • AdminBootstrap stores admin.password_hash_source in the admin_config table as the change-detector for the env-provided ADMIN_PASSWORD (see the comment in AdminBootstrap.cs). Do not "fix" this by hashing the source — it must be reversible so we can compare against the current env var on boot. The row is AES-GCM encrypted at rest as of Plan 2.
  • Fire-and-forget trigger sinks. TriggerActivationSink.EmitAsync returns Task.CompletedTask after spinning the execution into a background Task.Run. This prevents NATS pumps from blocking per-event on execution completion. WebhookRouter.Register (not the sink) is what the /wh endpoint awaits for the reply.
  • Cross-project cycle-avoidance for WebhookRegistry and WebhookRouter. Both types live in VolcanicCore.Core, NOT VolcanicCore.Api.Engine, because WebhookNode and WebResponseNode in .Nodes need them and .Api already references .Nodes (adding a back-reference would cycle). Do not move them back.
  • SPA bundles into src/VolcanicCore.Api/wwwroot/. Vite's build.outDir is repo-relative to frontend/ and points at the .Api project's wwwroot. The folder itself is .gitignore'd except for .gitkeep; the compiled JS/CSS is produced by pnpm build and consumed by dotnet publish (csproj has <Content Update="wwwroot\**\*" CopyToPublishDirectory="PreserveNewest" />). Do NOT commit wwwroot/*.js/*.css — CI produces them.
  • SPA fallback goes AFTER MapXxxEndpoints. MapFallbackToFile("index.html") in Program.cs must remain the last Map* call so /api/* and /wh/* still win. Static-file middleware (UseStaticFiles) goes right after UseAuthorization and is intentionally unauthenticated — the SPA itself sends users to /login when it sees a 401.
  • Frontend tsconfig has both references and paths. tsconfig.node.json is a composite project referenced from tsconfig.json, and needs noEmit: false + its own outDir (dist-tsconfig-node/, gitignored) to satisfy the composite constraint. tsconfig.json sets "paths": { "@/*": ["src/*"] } so tsc -b matches Vite's runtime @ alias — do NOT remove either.
  • vite.config.ts needs /// <reference types="vitest" /> on line 1. The test: block on defineConfig from "vite" won't typecheck without Vitest's config augmentation.

Working style

  • Design with superpowers:brainstorming, plan with superpowers:writing-plans, execute with superpowers:subagent-driven-development. This is the tempo Plan 1 shipped on.
  • Split large scopes into multiple plans (Plan 1 was foundation, Plan 2 is engine, Plan 3 is frontend, Plan 4 is deploy). Each plan should end in a testable state.
  • TDD in each task: write the failing test, watch it fail, implement, watch it pass, commit. Migrator, connection CRUD, workflow CRUD all follow this pattern — see their commits for examples.
  • Two-stage review per task: spec-compliance reviewer (haiku), then code-quality reviewer (sonnet). For crypto or wire-format work, escalate quality-reviewer to opus. For pure interface/type tasks, one combined reviewer is fine.
  • Follow-ups. When a reviewer surfaces a real concern that isn't in scope for the current task, add it to docs/superpowers/followups.md with which plan should handle it. Do not silently drop review feedback.
  • Commit style. Conventional Commits (feat(scope):, fix(scope):, chore:, docs:). One meaningful change per commit; TDD tasks land one commit per task at the end (test + impl together).

How to run

# Build + test (backend)
dotnet build
dotnet test

# Live server (boot with a real SQLite DB)
DATA_DIRECTORY=$(pwd)/_smoke ADMIN_PASSWORD=hunter2 \
  dotnet run --project src/VolcanicCore.Api --urls http://127.0.0.1:5099
# Then: curl -c jar -X POST -H 'content-type: application/json' \
#         -d '{"password":"hunter2"}' http://127.0.0.1:5099/api/session
# And follow up with -b jar for authenticated calls.

dotnet run --project X sets the content root to X's directory, so DATA_DIRECTORY=./relative lands under src/VolcanicCore.Api/ — always pass an absolute path.

SPA dev loop

# Terminal A — backend on :5099
DATA_DIRECTORY=$(pwd)/_smoke ADMIN_PASSWORD=hunter2 \
  dotnet run --project src/VolcanicCore.Api --urls http://127.0.0.1:5099

# Terminal B — Vite dev server on :5173 (proxies /api and /wh to :5099)
pnpm --dir frontend dev

# Production-ish (SPA bundled + served by the API)
pnpm --dir frontend build
DATA_DIRECTORY=$(pwd)/_smoke ADMIN_PASSWORD=hunter2 \
  dotnet run --project src/VolcanicCore.Api --urls http://127.0.0.1:5099
# → open http://127.0.0.1:5099

Frontend tests:

pnpm --dir frontend test         # Vitest unit + integration (20 tests)
pnpm --dir frontend test:e2e     # Playwright smoke; needs `pnpm exec playwright install chromium` once

Deploy target

Coolify — env vars COOLIFY_URL, COOLIFY_KEY, GIT_URL, GIT_USER, GIT_KEY are available in the container. Actual container packaging + Coolify wiring is Plan 4. Global CLAUDE.md rule applies: fire-and-forget deploys via the v4 API, never block on build.