Dashboard — Getting Started
The dashboard is a TanStack Start React application that provides a visual interface over the entire aa-news-encoder pipeline. Through it, you can review ML-classified news articles, inspect individual model predictions, explore system analytics, manage the pending classification queue, and trigger on-demand producer fetch runs. All data and authentication flow exclusively through the API service — the dashboard has no direct connection to the database, Kafka, or any other backend system.
Prerequisites
The API service must be running and reachable before the dashboard is usable. The dashboard makes all its requests to relative /api/... paths, relying on a reverse proxy (or TanStack Start's own server layer) to forward them to the API on port 3001. This means authentication cookies are shared seamlessly between the dashboard and the API on the same origin.
There is no separate authentication configuration in the dashboard. Session management is handled entirely by the API — when the API issues a session cookie after a successful magic link verification, that cookie is automatically carried on all subsequent dashboard requests via credentials: "include".
Tech stack
TanStack Router provides file-based routing with SSR support. Routes are defined as files under src/routes/, and the router generates a typed route tree at build time. Layout routes, authentication guards, and redirects are all expressed through router conventions rather than ad-hoc logic.
TanStack Query manages all server state. It handles caching, background revalidation, and the invalidateQueries calls that keep the UI consistent after mutations. The contents list pre-fetches a large dataset upfront; analytics fires multiple queries in parallel — both patterns are natural fits for Query's concurrent fetching model.
TanStack Form with zod handles form state and validation. The login form uses a z.string().email() schema to validate the email address before the magic link request is sent. Validation errors surface inline rather than on submission.
TanStack Table drives the data tables on the contents list, pending, and analytics pages. Filtering, sorting, and pagination all run client-side against whatever rows the table currently holds.
better-auth (magic link plugin) is the authentication client. The dashboard uses magicLinkClient to call authClient.signIn.magicLink and authClient.magicLink.verify, both of which ultimately hit the API's /api/auth/* endpoints. The dashboard is configured with baseURL: "http://localhost:3000/api/auth".
@cloudflare/kumo is the UI component library. Sidebar, badges, banners, toasts, meters, inputs, dropdowns, and tables all come from this library. It keeps the UI consistent without custom component work.
ECharts renders the charts on the analytics page using a canvas renderer. The category bar chart, daily line chart, and model-vs-human donut pie are all ECharts instances configured and mounted directly in React components.
Tailwind CSS v4 handles all layout and spacing outside of Kumo components.
Authentication overview
The dashboard uses passwordless magic link authentication. There are no passwords anywhere in the system. The flow works like this:
- The user visits
/loginand submits their email address. - The dashboard calls the API's magic link sign-in endpoint. The API generates a signed verification URL and (in development) prints it to stdout rather than sending an email.
- The user visits that URL, which lands them on
/auth/callbackwith a token in the query string. - The callback page calls the API's magic link verify endpoint. The API validates the signed token, creates a session, and issues a session cookie.
- The dashboard navigates to
/contents.
From that point on, the session cookie is included on every request. All routes under the /_authenticated layout re-check the session on each navigation — if the cookie is missing or expired, the user is redirected back to /login immediately.
API communication
Every HTTP request the dashboard makes goes through a single apiFetch helper. It sets credentials: "include" so the session cookie is always forwarded, sets the Content-Type header to application/json for requests with a body, and throws on any non-2xx response. All endpoint paths are relative (/api/...), so the dashboard never needs to know what port the API is on — the same-origin assumption handles that entirely.