R2-D2
Dashboard
Node Red
Restreaming
The Force
UpdatedNever
v1.0.0

Overview

Loading…
R2-D2
Dashboard
Node Red
Restreaming
The Force
UpdatedNever
v1.0.0
DDroidspeak / Docs
Operator handbook
Droidspeak
What is R2-D2?
Runtime architectureAuth — Keycloak migration plan
FleetRestreamingGalaxy MapThe RepublicTemple ArchivesSTANAG 4817The Force
Tech StackNext.js 15 + React 19Tailwind v4ZustandTanStack Table + DataViewhls.jsLow-latency playerreagraphoglreact-grid-layoutMonacoScalar API Referencefumadocs
Cluster InfraTrailBaseReductStoreRestreamer (datarhei/core)TBMQKeycloakLonghornkube-vipIngress (Caddy + nginx-ingress + Traefik)Netbird
Operator QA Runbook
Install — RKE2Install — Dokploy
Contributor guideRelease Notes
Tech Stack

Zustand

The four stores that hold cross-page client state — and why there are only four.

zustand (^5.0.0) is the dashboard's client-side store. There are four stores today. New stores are rare — most "shared state" should live in the URL or be derived from a route handler, not in zustand.

The four stores

StoreFileWhat it ownsRead by
Fleetsrc/store/useFleetStore.tsLive per-instance health (Node-RED reachability, last-seen, version)Fleet pages, Sidebar status pip, Galaxy Map site rings
Layoutsrc/store/useLayoutStore.tsActive Holoprojector layout + per-slot bindings during editHoloprojector page, LayoutEditor
Toastsrc/store/useToastStore.tsToast queue (mutation success / error notifications)ToastContainer mounted in root layout
Sensesrc/store/useSenseStore.tsk8s cluster snapshot (nodes, pods, namespaces) for the Force/Sense planethe-force/Sense pages, reagraph stack graph

Why so few stores

A store is justified when state is (a) shared across pages and (b) live. Per- page state, derived state, and "happens to be in multiple components on the same page" state should not be a store.

  • Per-page transient state — useState in the component.
  • Derived state — a selector or memo over an existing store.
  • State that survives a refresh — URL params, then localStorage as a last resort.

The dashboard has resisted the temptation to add a "feed store", a "panel store", a "republic store", etc. — those are page-local, the route handlers are the source of truth, and re-fetching on mount is cheaper than maintaining cache invalidation.

Selectors, not the whole store

Subscribe by selector to avoid pointless re-renders:

// good — re-renders only when this instance's status changes
const status = useFleetStore(s => s.instances[id]?.status);

// bad — re-renders on any fleet change
const { instances } = useFleetStore();

This matters most for components rendered in a loop (every row in the InstanceTable, every ring on the Galaxy Map) where a naive subscription would re-render the entire list on a single instance's heartbeat.

Where each store is populated

StorePopulated by
FleetA polling hook in the root layout that calls /api/instances every N seconds and writes deltas
LayoutThe LayoutEditor on mount; cleared on unmount
Toastnotify.ts helpers (notify.ok(...), notify.err(...)) — these are the only writers
SenseA polling hook on the Sense page that calls /api/sense/cluster (or the equivalent) and writes the snapshot

What zustand is not used for

  • HTTP cache. No react-query, no swr. Route handlers are fast; pages re-fetch on mount. If a future page needs cache, prefer adding a tiny per-page hook over a global store.
  • Form state. Forms are local useState. Submission goes straight to a route handler.
  • Auth state. The Keycloak session cookie is the source of truth; client-side auth hints come from jose-verified JWT claims surfaced via a layout-level RSC.

Before adding a fifth store, ask whether the data could live in URL params or be re-fetched per-page. If it must be a store, document its lifetime and writers here.

See also

  • Next.js + React — RSC/client split and where routes own their data
  • The Force — Sense store consumer
  • Holoprojector — Layout store consumer

Tailwind v4

Design tokens, the HUD palette, and the dark-only design language.

TanStack Table + DataView

The only sanctioned way to render a list. Never hand-roll a <table>.

On this page

The four storesWhy so few storesSelectors, not the whole storeWhere each store is populatedWhat zustand is not used forSee also