The UI is SvelteKit with Svelte 5 runes, Tailwind 4 and layerchart. The whole client is built around one idea: a small set of reactive resource stores that fetch from the JSON API, driven by a shared scope (the selected team and month range) that is mirrored into the URL.
| Route | What it shows |
|---|---|
/ |
Team overview: delivery velocity, top repos, contributors, awards, PR cadence. |
/attention |
Open-PR worklist: changes requested, unreviewed, stale and aging PRs. |
/flow |
Cycle time and review health: time to first review, merge latency, coverage. |
/signals |
Automated health checks across cycle time, throughput, review load, bus factor. |
/charts |
Detailed charts: commits, PRs, reviews by member and repo, tickets. |
/global |
Org-wide delivery trends across all repos. |
/breakdown |
Org trends filtered to a team or repo subset. |
/teams |
Create and edit private teams. |
/bots |
Bot review activity on merged PRs. |
/people/[login] |
One contributor's commits, PRs, reviews, repos and lines. |
/settings |
Admin configuration (admins only). |
/logs |
Audit trail (admins only). |
client/resource.svelte.ts defines a small Resource<T> base class with three
runes: loading, error and data. It exposes run(key, fetcher) for explicit
loads and ensureKey(key, fetcher) for idempotent loads that only fire when the
key changes. A sequence guard makes sure a slower earlier request can never
overwrite a newer one, and a 401 redirects to sign-in.
The concrete stores extend it, one per API endpoint:
metricsandglobalMetricsPOST a selection to/api/metrics.attentionPOSTs the team's repos to/api/attention.flowPOSTs repos plus the month window to/api/flow.discoveryfetches the org repo and member lists once.
A page reads store.data (with a server-rendered initial value as fallback),
store.loading and store.error, and the UI reacts automatically.
client/scope.svelte.ts holds the shared selection as runes: active team id, month
windows and the "to" anchor. Changing the team or range updates the state, persists
it to localStorage, mirrors it into the URL query string (client/url.ts, which
retries until the router is ready and merges with other param writers), and calls
reload() to refetch the active store. This is why a dashboard link is shareable:
the scope round-trips through the URL.
State management follows the standard Svelte 5 idiom rather than external stores:
$statefor the reactive sources (store fields, scope, theme, the drawer).$derivedand$derived.byfor read-only computations: picking the active store by pathname, the page title, ranked top repos and authors, monthly rollups.$effectfor side effects: initializing scope from server data once, syncing the URL after navigation, and callingensure(...)on a store when the active team's repos change.
Charts use layerchart with d3-shape curves, wrapped by MetricChart.svelte. It
takes a data array, an x column, and a series description, and renders a line, area
or bar chart with overlap, stack or group layouts. Two details matter:
- Series keys (logins, repo names) are mapped to safe CSS variable names so special
characters do not break the per-series colors emitted by
chart-style.svelte. - Charts mount lazily through an
IntersectionObserver, so a long charts page does not render everything at once. Print mode forces them all to render so PDF export captures the full page.
Some views are pure transforms of the fetched data, computed in the browser:
charts.tsreshapes the metrics result into per-repo series, per-member monthly series, the org trend with weighted averages, and the tickets breakdown.awards.tsfinds the standout contributor per dimension (most commits, merged PRs, reviews, comments, lines, breadth) and renders them as trophy cards.signals.tsturns flow, attention and metrics into a sorted list of health signals (ok, warn, bad) covering cycle time, review coverage, throughput, knowledge concentration and the open-PR backlog.
theme.svelte.ts toggles a dark class on the root element and remembers the
choice in localStorage; a boot script in app.html sets it before first paint to
avoid a flash. print.svelte.ts drives PDF export: it flips a printing flag, waits
for layerchart to measure and paint the now-forced charts, then calls
window.print().