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 underfrontend/, bundled intowwwroot. - 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", typedApiError/UnauthorizedError). - TanStack Query for server state;
useSession/useLogin/useLogout+ query hooks for every endpoint. - Router w/
PrivateRoutegate. 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 eachConfigSchema), 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 buildemits intosrc/VolcanicCore.Api/wwwroot/;.Apiserves w/UseStaticFiles+MapFallbackToFile("index.html")(API +/whroutes still win — 4 new xUnit tests cover the ordering).- Vite dev proxy
/api+/wh→http://127.0.0.1:5099sopnpm devco-exists withdotnet 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 publishthe backend, distroless runtime image. - Coolify docker-compose using
preparing-dotnet-react-app-for-coolifyskill (named volume for/data,expose: [8080],SERVICE_FQDN_APP). - Wire
UseForwardedHeaders+KnownProxiesso 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 oftests/VolcanicCore.Tests/WebFactory.cs. MultipleWebFactoryinstances share the process; pre-Configuration.Bindenv-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.Cookiesas aPackageReference. The last standalone version on NuGet is 2.3.0 (ASP.NET Core 2.x, 2019). .NET 9'sMicrosoft.AspNetCore.Appshared framework provides the cookie-auth types with no PackageReference. Program.csenv-var loop honors pre-set config. In tests,WebFactory.ConfigureWebHostusesbuilder.UseSetting(...)to injectDataDirectoryandAdminPassword. The env-var loop inProgram.csskips keys already set by upstream sources (if (!string.IsNullOrEmpty(builder.Configuration[prop])) continue;). Do not remove this check.Migration001Initial,002Connections,003Workflows,004Executions,005NodeExecutionsare registered in theMigratorfactory inProgram.cs. Add new migrations to the same array in version order.AdminBootstrapstoresadmin.password_hash_sourcein theadmin_configtable as the change-detector for the env-providedADMIN_PASSWORD(see the comment inAdminBootstrap.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.EmitAsyncreturnsTask.CompletedTaskafter spinning the execution into a backgroundTask.Run. This prevents NATS pumps from blocking per-event on execution completion.WebhookRouter.Register(not the sink) is what the/whendpoint awaits for the reply. - Cross-project cycle-avoidance for
WebhookRegistryandWebhookRouter. Both types live inVolcanicCore.Core, NOTVolcanicCore.Api.Engine, becauseWebhookNodeandWebResponseNodein.Nodesneed them and.Apialready references.Nodes(adding a back-reference would cycle). Do not move them back. - SPA bundles into
src/VolcanicCore.Api/wwwroot/. Vite'sbuild.outDiris repo-relative tofrontend/and points at the .Api project's wwwroot. The folder itself is.gitignore'd except for.gitkeep; the compiled JS/CSS is produced bypnpm buildand consumed bydotnet publish(csproj has<Content Update="wwwroot\**\*" CopyToPublishDirectory="PreserveNewest" />). Do NOT commitwwwroot/*.js/*.css— CI produces them. - SPA fallback goes AFTER
MapXxxEndpoints.MapFallbackToFile("index.html")inProgram.csmust remain the lastMap*call so/api/*and/wh/*still win. Static-file middleware (UseStaticFiles) goes right afterUseAuthorizationand is intentionally unauthenticated — the SPA itself sends users to/loginwhen it sees a 401. - Frontend tsconfig has both
referencesandpaths.tsconfig.node.jsonis a composite project referenced fromtsconfig.json, and needsnoEmit: false+ its ownoutDir(dist-tsconfig-node/, gitignored) to satisfy the composite constraint.tsconfig.jsonsets"paths": { "@/*": ["src/*"] }sotsc -bmatches Vite's runtime@alias — do NOT remove either. vite.config.tsneeds/// <reference types="vitest" />on line 1. Thetest:block ondefineConfigfrom"vite"won't typecheck without Vitest's config augmentation.
Working style
- Design with
superpowers:brainstorming, plan withsuperpowers:writing-plans, execute withsuperpowers: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.mdwith 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.