Each project's node now sits at the rung centre (x = CX) instead of at its strand's instantaneous extreme. The "line in between" the strands becomes the natural place for the project marker — true to the base-pair metaphor — and frees Y positions from having to coincide with strand-peak intervals. What this unlocks - Slot Y is decoupled from the wave period. SLOT_HEIGHT drops from 240 → 180 and the wave period from SLOT_HEIGHT*2 → a fixed 320, giving the helix a denser twist without re-spacing the cards. - Cards pack tighter (TOP/BOTTOM padding 120 → 80, summary clamp 3 → 2 lines, padding shaved). 3 projects now fit in ~620 px of helix section instead of ~960 px — empty space goes away. Visual additions - Per-project base-pair: a prominent strand-coloured line at each node's Y, drawn from strand A's x to strand B's x. This is the rung the node sits on. - Connector dash line continues from the node centre outward to the card edge. Side mapping - Strand identity is now purely the card's side + stripe + badge: A → left, B → right. (Previously the side followed the strand's instantaneous extreme, which gave inconsistent groupings.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HELIX
EVOLV and every R&D strand, one helix.
The R&D showcase platform of Waterschap Brabantse Delta. EVOLV at its core, every innovation along the strands.
HELIX collects projects, innovations, and updates from across the R&D team in
one place — with deep links to repos, dashboards, and demos. Sign-in is gated
to the RnD Gitea organisation; viewing is open.
Stack
- SvelteKit 2 + Svelte 5 + TypeScript
- Tailwind v4 (CSS-first design tokens)
- SQLite (single file) + Drizzle ORM
- Gitea OAuth2 for authoring (no passwords stored)
- Pure SVG + CSS helix animation — no WebGL
Run it locally — two ways
1. Docker (production-like, recommended for testing)
Mirrors how the EVOLV stack is exercised locally. One command, named volume for SQLite, healthcheck wired:
cd /path/to/helix
docker compose up -d --build # build image + start container
docker compose logs -f helix # tail logs
# → http://localhost:3000
If port 3000 is in use (Node-RED, another dev server, …) pick another:
HELIX_PORT=3030 \
ORIGIN=http://localhost:3030 \
GITEA_REDIRECT_URI=http://localhost:3030/auth/gitea/callback \
docker compose up -d --build
# → http://localhost:3030
Useful commands:
docker compose ps # status + health
docker inspect helix --format '{{.State.Health.Status}}'
docker compose exec helix sh # shell into the container
docker compose exec helix sqlite3 /data/helix.db # inspect the DB
docker compose down # stop (keeps the volume)
docker compose down -v # stop + DELETE all data
The SQLite database lives in the helix_helix-data named volume at
/data/helix.db. Migrations + the idempotent seed both run on every boot.
Once you have real content, set SEED_ON_BOOT=false in your env to stop
re-seeding the demo entries.
2. Native Node (faster dev loop, hot reload)
nvm use # .nvmrc → Node 20 (Tailwind v3 also works on 18)
cp .env.example .env # fill in Gitea OAuth — see below
npm install
npm run db:generate # generate drizzle/0000_*.sql from the schema
npm run db:migrate # apply migrations to ./helix.db
npm run db:seed # idempotent demo content
npm run dev # http://localhost:5173
Gitea OAuth setup
HELIX uses your existing Gitea identity. Create an OAuth2 application once:
- Open https://gitea.wbd-rd.nl/-/user/settings/applications
- Click Create new OAuth2 application
- Application name:
HELIX (local)(orHELIX (production)) - Redirect URI:
- local:
http://localhost:3000/auth/gitea/callback - prod:
https://<your-helix-host>/auth/gitea/callback
- local:
- Click Create application
- Copy the Client ID and Client Secret into your
.env:GITEA_CLIENT_ID=... GITEA_CLIENT_SECRET=...
GITEA_ALLOWED_ORG=RnD restricts authoring (sign-in) to members of the RnD
organisation. Leave it empty to allow any authenticated Gitea user.
Adding a dashboard embed allowlist host
src/lib/config.ts → DASHBOARD_ALLOWED_HOSTS controls which Grafana / dashboard
origins can be embedded inline on a project page. Add a hostname (no scheme,
no path) and redeploy. Hosts not on the list render as a "Open in new tab" card
instead — never blindly iframed.
Production deploy
Same compose file as local. On the production host:
cp .env.example .env
# Set: ORIGIN=https://your-host, GITEA_REDIRECT_URI=https://your-host/auth/gitea/callback,
# GITEA_CLIENT_ID, GITEA_CLIENT_SECRET (from a separate prod OAuth app)
docker compose up -d --build
Put a TLS-terminating reverse proxy (nginx/caddy/traefik) in front of port 3000. Back up the volume periodically:
docker compose exec helix sqlite3 /data/helix.db .dump | gzip > helix-backup-$(date +%F).sql.gz
Project layout
helix/
├── src/
│ ├── lib/
│ │ ├── components/ # Helix.svelte, ProjectCard, PostCard, Nav, Footer, DashboardEmbed
│ │ ├── server/
│ │ │ ├── db/ # Drizzle schema + client (better-sqlite3)
│ │ │ ├── auth.ts # session token + cookie helpers
│ │ │ └── gitea.ts # OAuth2 dance + user fetch
│ │ ├── config.ts # site name, tagline, dashboard allowlist
│ │ └── markdown.ts # marked wrapper + slug/id helpers
│ ├── routes/
│ │ ├── +page.svelte # landing (helix hero + recent projects/posts)
│ │ ├── projects/ # /projects, /[slug], /new
│ │ ├── posts/ # /posts, /[slug], /new
│ │ ├── login/ # sign-in page (kicks off Gitea OAuth)
│ │ ├── logout/+server.ts # POST → invalidates session
│ │ └── auth/gitea/ # OAuth start + callback
│ ├── app.html
│ ├── app.css # Tailwind v4 entry + design tokens
│ ├── app.d.ts # Locals types
│ └── hooks.server.ts # session hydration
├── drizzle/ # generated migrations
├── scripts/
│ ├── migrate.js # run pending migrations
│ └── seed.js # idempotent example data
├── static/favicon.svg
├── Dockerfile
├── docker-compose.yml
├── drizzle.config.ts
├── svelte.config.js
├── vite.config.ts
├── tailwind.config.ts (none — Tailwind v4 uses @theme in app.css)
├── tsconfig.json
└── package.json
How to post
After signing in (top-right → Sign in → Gitea), use:
- + New in the nav (or
/projects/new) — a project: title, summary, markdown body, links /posts/new— a shorter update / write-up
Both are auth-gated. Anyone in RnD on Gitea can post.
Contributing
This is internal R&D — open a PR against main on
gitea.wbd-rd.nl/RnD/helix.
License
Internal — Waterschap Brabantse Delta R&D.