Web UI Development¶
The web UI is a SvelteKit single-page application in web/, written in
TypeScript and built with Bun (ADR-0002: Go Services and a Svelte Web UI).
It is compiled to static files (web/build) that Core serves.
Setup¶
cd web
bun install
Run locally¶
make run builds the UI into Core and runs the whole system on
http://localhost:8080 (Run Ballet locally). For UI work, run the
Vite dev server for hot reload instead:
go run ./core/cmd/core # Core on :8080
cd web && bun run dev # UI on http://localhost:5173
The dev server proxies /api (REST), /rpc (WebSocket),
/config.json and /healthz to Core on 127.0.0.1:8080. To sign
in, run the development Keycloak and point Core at it
(Configure OIDC Authentication), then open http://localhost:5173 (use
localhost, not 127.0.0.1: it is a registered redirect URI).
Authentication¶
The SPA reads the issuer and client ID from Core’s /config.json and
signs in with oidc-client-ts (authorization code flow with PKCE).
Tokens are kept in sessionStorage and renewed silently with the
refresh token. src/lib/session.ts creates the shared REST client
(api) and realtime client (realtime) once; pages obtain them with
getSession(). The realtime connection starts after sign-in; its state
is shown in the header.
Pages¶
Route |
Purpose |
|---|---|
|
Organizations the user can see; new organization (platform admins) |
|
Organization: rename, projects, new project |
|
Board: tickets by state, Blocked badge for ready tickets with
unresolved blockers, quick ticket creation, epics and milestones;
live through the |
|
Item: edit (title, description, type, criteria, policy, epic,
milestone), state transitions, dependencies, history; live through
the |
|
Knowledge space: entries filtered by kind and project, hybrid search (Knowledge) |
|
New entry; |
|
Entry: rendered Markdown, links to projects and items, edit (Markdown with preview), version history |
|
Skills of one scope ( |
|
Skill: draft editor (description, |
|
Planner chats of a project; start a chat |
|
Chat with the planner: streamed answers ( |
|
The project’s changesets by status; approve or reject; live through
the |
|
Effective skills of a project; pin a version, disable, re-enable |
|
Role bindings (users who manage access) |
The item page also lists the knowledge entries linked to the item.
Markdown written by humans and agents is untrusted: it is rendered with
marked and sanitized with DOMPurify (src/lib/markdown.ts) before
it reaches the page, so scripts, event handlers and javascript: URLs
are removed.
The changeset card (src/lib/components/ChangesetCard.svelte) keeps
the selection consistent with $ref dependencies
(src/lib/changesets.ts): selecting an operation selects the
operations it needs, deselecting one deselects those that need it — the
same rule Core enforces when applying.
Pages subscribe to their stream before loading the snapshot over REST,
refetch the changed item on each event, and reload on resync. Controls
are shown according to the user’s bindings (src/lib/permissions.svelte.ts);
Core enforces authorization regardless.
Commands¶
Command (in |
Purpose |
|---|---|
|
Development server with hot reload |
|
Static production build into |
|
Svelte and TypeScript type checking |
|
Prettier and ESLint |
|
Unit tests (Vitest), |
|
End-to-end tests (Playwright, Google Chrome), |
|
Live tests ( |
Conventions¶
Svelte 5 runes mode everywhere.
Server-side rendering is disabled (
src/routes/+layout.ts); all routes fall back toindex.html.API clients live in
src/lib/api/and takefetchas a parameter so they can be unit-tested without a server.The realtime client (
src/lib/realtime/) implements Realtime API: MessagePack/JSON, authentication with refresh, heartbeats, reconnect with backoff, and stream subscriptions that resume after reconnecting (onResyncwhen the gap is too large).statusStore(client)exposes the connection status as a Svelte store.*.integration.spec.tstests run the realtime client against the Go reference serverkit/rpc/cmd/rpc-testserver; they need the Go toolchain (bun run testbuilds the server).End-to-end tests stub backend calls with
page.route()unless they are explicitly integration tests.
End-to-end tests use Google Chrome. In the devcontainer it is installed
from Google’s RPM (Playwright’s installer does not support Fedora), and
Chrome runs with --no-sandbox because the container runs as root.