Development
Prerequisites
- Node.js >= 20
- pnpm >= 8
task(go-task)
npm install -g pnpm
Setup
# Install all workspace dependencies
task setup
# or: pnpm install
Dev Servers
task dev:assistant # http://localhost:3003
task dev:roi # http://localhost:3004
task dev:webapp # http://localhost:3005
task dev:grid # http://localhost:3006
Workspace Structure
packages/
ui/ @celine-eu/ui — shared design system
assistant-ui/ @celine-eu/assistant-ui — AI chat components
roi-ui/ @celine-eu/roi-ui — ROI calculator components
apps/
assistant/ Standalone AI assistant (v0.15.0)
roi/ PV ROI calculator (v0.14.0)
webapp/ REC participant webapp (v0.17.0)
grid/ Grid resilience dashboard (v0.12.0)
pnpm-workspace.yaml
taskfile.yaml
Adding an Icon
Icons are defined in packages/ui/src/Icon.svelte. Add a new icon by inserting its SVG path into the icons object:
const icons = {
// existing icons...
"my-icon": "M10 10 L20 20...", // SVG path data
};
Use Lucide icon paths for consistency.
Creating a New Component
- Choose the correct package:
uifor generic UI,assistant-uifor chat,roi-uifor ROI calculator. - Create the
.sveltefile inpackages/<package>/src/. - Export it from
packages/<package>/src/index.ts:typescript export { default as MyComponent } from './MyComponent.svelte';
Build
# Build all
task build
# Build individual apps
task build:webapp
task build:assistant
task build:roi
task build:grid
Content-Security-Policy
Every app sends its own Content-Security-Policy header, configured in kit.csp of its
svelte.config.js with mode: 'nonce'. Pages are rendered on request (adapter-node, none
prerendered), so each response gets a fresh nonce: SvelteKit puts it on its inline bootstrap
and in script-src, and %sveltekit.nonce% puts it on any inline script of app.html (the
theme script). script-src never allows 'unsafe-inline'. An inline script added to
app.html needs nonce="%sveltekit.nonce%", or the browser refuses it.
- webapp, assistant, community: the whole policy (default
'self',data:/blob:images, community's OpenStreetMap tiles,object-src 'none',frame-ancestors 'none'). - grid, roi: the script directives only (grid: MapLibre's
blob:worker); the other directives are still a Report-Only trial sent by the ingress. style-srckeeps'unsafe-inline': Svelte rendersstyle=attributes and Leaflet and MapLibre set inline styles.
The deployment's ingress sends no Content-Security-Policy for these hosts, since it would
replace the app's (infra, README "Security headers"). vite dev sends the same policy, so a
refused script shows up in development. e2e/content-security-policy.spec.ts checks each app.
Docker
task docker:webapp # Build webapp image
task docker:assistant # Build assistant image
task docker:grid # Build grid image
task docker:all # Build all images
Release
Each app is independently versioned via release-it with conventional commits. Tags follow <app>-v<version>.
task release:<app> # bump, changelog, tag, push
task release:<app>:dry # preview (assistant, webapp, grid)
task release # release all apps
API Requirements
Each app connects to its backend service:
| App | Backend | API Port |
|---|---|---|
| assistant | celine-ai-assistant | 8012 |
| webapp | celine-webapp (BFF) | 8014 |
| roi | celine-roi | 8013 |
| grid | celine-grid | 8015 |