Develop helixCore
A practical guide to developing Helix and its client extensions from a Honeywick workstation. The everyday loop is bare-metal, no container; the container is for testing and shipping deployments.
1. What you’re building
Section titled “1. What you’re building”- helixCore — the client-agnostic platform: a React SPA + a TypeScript backend (
crud,dispatch) that wraps the proprietary Spiral optimiser (spiral.l64), backed by MongoDB. - Client extensions — per-client repos (e.g. AA) that plug into helixCore via the published
@helix/client-sdk: domain/map config + React config/components. helixCore ships without any client baked in. - Spiral — the optimisation engine, built separately.
The split: anything client-specific (wording, behaviour, templates, icons, custom components) lives in the client repo / domain file — never hardcoded in helixCore.
2. Repo layout on a Honeywick box
Section titled “2. Repo layout on a Honeywick box”~/Honeywick/ Helix/ # the helixCore platform repo helixCore/ frontend/ # React SPA (Vite) *.ts # backend: crud, dispatch, spiral, queue, crudObject, … client-sdk/ # @helix/client-sdk source startHelix # bare-metal + container launcher config # env contract (ports, paths, domain/map) release/helixctl # container launcher (host-side) Dockerfile.release # client-agnostic runtime image publishHelixCore.sh # build + push the image / SDK AA -> ../Clients/AA/helix/custom # symlink to the active client's customisation (per client) Clients/<CLIENT>/ # each client's repo (git) helix/ custom/ # client extension SOURCE (= Helix/<CLIENT> via the symlink) <CLIENT>.domain.json # Spiral/domain config (stopParams, resourceParams, helix.* blocks) <CLIENT>.map.json # Spiral map <CLIENT>.ui.json # UI params config # client env overrides (sourced by helixCore/config) frontend/clientConfig.tsx# the client extension (templates, helixOverrides, logo, editors) vite.client.config.ts # builds the extension -> ../runtime/client package.json # depends on @helix/client-sdk runtime/ # launcher stub + version pins + built bundle (client/, served as /client) scenarios/ # Helix-only client scenarios tests/ # client tests + results (ROOT — shared with the Spiral-only world) release/ # legacy Honeywick/Spiral artifacts Spiral/ # the Spiral optimiser source (builds spiral.l64)Each client provides Helix/<CLIENT> as a symlink to Clients/<CLIENT>/helix/custom — that’s what startHelix <CLIENT> resolves.
3. Prerequisites (one-time)
Section titled “3. Prerequisites (one-time)”- On PATH:
node,npm,mongod,mongosh. - Environment (typically in
~/.bashrc):SPIRAL_LICENCE— a Spiral command, e.g.{"set":{"licence":"<hex>"}}(not the raw key; Spiral reads this env var via aninclude environmentdirective). The app refuses to start without it.GOOGLE_MAPS_API_KEY— for the map.- Optional:
SPIRAL_JSON,SPIRAL_TRAVEL_CALCULATOR,AA_VEHICLE_API_KEY.
- Install deps:
(cd helixCore/frontend && npm install)and(cd Clients/<CLIENT>/helix/custom && npm install).
4. The everyday loop — bare-metal dev (no container)
Section titled “4. The everyday loop — bare-metal dev (no container)”From the Helix repo:
./helixCore/startHelix <CLIENT> --dev # e.g. ./helixCore/startHelix AA --devThis brings up, with dynamic ports (printed) and opens the browser:
- mongod (data under
~/helix/<CLIENT>/logs/dev/mongoData), - dispatch + crud (the backend, wrapping Spiral),
- a Vite dev server with HMR.
Live reload covers both sides:
- editing the helixCore frontend (
helixCore/frontend/src/**) hot-reloads, - editing the client extension (
Clients/<CLIENT>/helix/custom/frontend/**, resolved via the@clientalias) hot-reloads too.
Domain/map are read from Clients/<CLIENT>/helix/custom/<CLIENT>.domain.json + .map.json.
Logs (everything you need to debug): ~/helix/<CLIENT>/logs/dev/ —
crud.log, dispatch.log, spiral.log, crud2spiral.log, vite.log, mongo.
Other bare-metal modes (same script)
Section titled “Other bare-metal modes (same script)”--solo— single node, no persistence, no Vite HMR (logs in…/logs/solo-<pid>).--spiral <runfile>— read-only Spiral streaming: feed a run file straight to Spiral; no entity edits.--master/--replicate <primaryHost>/--readOnly <primaryHost>— cluster nodes (mongo replica set).
This is the primary Honeywick workflow — every run mode is testable here with no container.
5. The client-extension model
Section titled “5. The client-extension model”clientConfig.tsxexportsconfig: ClientConfig—templates(entity defaults viacreateTemplates),helixOverrides(customhelix.*functions),about,clientLogo, and optional data-extensions / full-control editors.@helix/client-sdkprovides the types + helpers (ClientConfig,createTemplates,StopState,colors, …). In Honeywick dev it resolves to the helixCore source via a Vite alias; a standalone client build resolves the published package from the GitLab npm registry.- Domain config drives the UI.
stopParamsentries carry ahelixblock that the frontend reads — icons,lineLetter, warning templates, the stoptemplates(option generator), add/morph-stop menus, state colours, etc. Change behaviour/wording there, not in the frontend. helix.*parameters are string expressions (e.g."helixColorDelay()") bound toclientModulefunctions and clienthelixOverrides, evaluated withthis= the stop/resource (StopContext, with typedgetInStop()/getOutStop()accessors).{prop|default}placeholders interpolate values. Resolution chains clienthelixOverridesfirst, then coreclientModule, and a function returningundefineddefers to the previous override — so overrides can handle only the cases they care about.- Icons live in
helixCore/frontend/public/icons/*.png(core) plus client icons, named via the icon registry (icons.tsx).
6. Backend development
Section titled “6. Backend development”- The backend is the root
*.tsinhelixCore/(crud.ts,dispatch.ts,spiral.ts,queue.ts,crudObject.ts,crudHandlers.ts, …). crudspawnsspiral.l64and talks to it over stdin/stdout, persisting to MongoDB;dispatchtails Mongo change streams and broadcasts to the SPA over WebSockets.- Tags correlate a command with Spiral’s per-command “done” echo (used for REST replies, progress, and run-file completion). Input and output run on two independent async queues so command delivery isn’t throttled by output processing.
- Client backend hooks (
crudClientModule.tscore + the client’scrudClientModule.js, merged incrud.ts): the crud calls module functions named by the domain —stopParams[type].helix.check(validation, input path,crudObject.ts),resourceParams[type].helix.onCreate/onDelete(entity lifecycle, input path, live mutations only), andstopParams[type].helix.onState[from][to](stop transitions, Spiral-output path,crudHandlers.tsupdateStopTable). Each getsthis= entity/stop and aCrudHookInfofirst arg;info.now=ctx.now()(wall-clock + Spiral’stimeDelta).nullmatrix keys mark create/delete edges. Only entity types with a params block (currentlyresource) can carry entity hooks. The contract types are exported from@helix/client-sdk. - Functional tests are run files:
Clients/<CLIENT>/tests/regression/*.run.json(repo root; shared with the Spiral-only world; replayed through the CRUD pipeline). There is no unit-test framework.
7. Type-check & quality gate
Section titled “7. Type-check & quality gate”(cd helixCore/frontend && npx tsc --noEmit) # frontend — aim for 0 errors(cd helixCore && npx tsc --noEmit) # backend — aim for 0 errorsRun both before committing. The regression .run.json files are the functional safety net.
8. Container development & deployment (helixctl)
Section titled “8. Container development & deployment (helixctl)”The container is for deployment (and testing it) — not the daily loop. The launcher helixctl is
client-agnostic; the client name is always explicit (mirrors startHelix <CLIENT>).
Build/refresh the image locally (when testing the container path):
./publishHelixCore.sh <ver> # build + push; HELIX_OFFLINE_TAR=1 to save a local tar insteadFetch the launcher from the image (registry-only):
podman run --rm --entrypoint cat <image> /app/helixctl > helixctl && chmod +x helixctlContainer client dev (live rebuild, browser refresh — no HMR). To develop a client extension the way a
client does (no helixCore source, no bare metal), use the launcher that ships in the client repo’s
helix/runtime/ and run from that repo’s root:
cd Clients/<CLIENT> # the client repo root (holds helix/ + tests/)./helix/runtime/helixctl pull./helix/runtime/helixctl <CLIENT> dev # mounts helix/ (+ root tests/), runs `vite build --watch`# edit client source -> ~1s rebuild (helix/runtime/logs/dev/clientbuild.log) -> refresh the browserSee Develop a client extension for the full client-side flow.
Deploy to a node:
(cd Clients/<CLIENT>/helix/custom && npm run build:client) # or publish-<client>.sh <ver> -> a bundle tarhelixctl deploy <bundle.tar> # unpacks client/ + helix-config/helixctl <CLIENT> run | spiral | master | replicant | readOnlyrun= writable single node; cluster modes needHELIX_PUBLIC_HOST(andHELIX_PRIMARYfor replicant/readOnly).HELIX_LOG_DIR_HOST=<dir>bind-mounts the container’s/app/logsto the host so logs survive--rm(defaulted fordev).
9. Testing client modes from Honeywick (no registry needed)
Section titled “9. Testing client modes from Honeywick (no registry needed)”- Bare-metal already exercises every mode:
startHelix <CLIENT> --dev | --solo | --spiral <file> | --master | --replicate | --readOnly. Start here. - Local container loop to validate the deployed path: build the image locally
(
HELIX_OFFLINE_TAR=1 ./publishHelixCore.sh <ver>), build the client bundle, then point helixctl at the local image (HELIX_IMAGE=<local-ref> helixctl <CLIENT> dev …or… deploy && … run). No publish required.
10. Release flow
Section titled “10. Release flow”- helixCore image →
./publishHelixCore.sh <ver>→ registry. Each build pushes three tags::<ver>,:latest, and the floating:sdk-<MAJOR.MINOR>(e.g.:sdk-0.1) computed fromhelixCore/client-sdk/package.json. The SDK version is also baked into the image (/app/SDK_VERSION+helix.sdklabel).:sdk-0.1always points at the newest core on SDK 0.1.x — that’s what clients pin. @helix/client-sdk→ GitLab npm registry; bump the version when the SDK contract changes. A breaking bump to0.2.xstarts a fresh:sdk-0.2line and leaves:sdk-0.1frozen at the last 0.1 core.- Client bundle →
publish-<client>.sh <ver>→ tar (client/+helix-config/) →helixctl deployon the node. Clients pinhelix/runtime/VERSION=sdk-0.1(not an exact core tag), sohelixctl pullauto-upgrades the core within the SDK line;helixctl deployenforces minor-compatibility against the image’s baked SDK.
11. Conventions
Section titled “11. Conventions”- Domain-driven: client wording/behaviour lives in
<CLIENT>.domain.json(stopParams.helix.*), not the frontend. spiral.l64is a built artifact (from~/Honeywick/Spiral) — it is not maintained/committed from the Helix repo.- Logs: bare-metal under
~/helix/<CLIENT>/logs/{dev,solo-<pid>}; container via theHELIX_LOG_DIR_HOSTbind mount. - Repos are separate: Helix (platform) and each
Clients/<CLIENT>are independent git repos — commit each on its own; don’t cross-commit. - Commit only when asked; finish commit messages with the agreed
Co-Authored-Bytrailer.