The Republic
Nations, ShopKeepers, holdings — the political/inventory layer of the fleet.
The Republic plane is the answer to "who are the participating organizations, what do they own, and who do we buy capability from?" — a small CRM-shaped data model with three primary entities.
Entities
| Entity | What it represents | Source |
|---|---|---|
| Member nations | The participating organizations using the fleet. Each has a callsign, contact info, time zone, and a list of holdings. | republic-config.yaml + TrailBase |
| ShopKeepers | Vendors / suppliers. Think "the people who sell us radars, drones, comms gear." | TrailBase |
| Holdings | Inventory items — owned by a nation, optionally supplied by a ShopKeeper. The shape is intentionally loose (kind, model, serial, status). | TrailBase |
| Platforms & Buildouts | The "know-how" library: documented platforms (M300, SeaFLIR, radar profiles) and how to integrate them. Reference content, not transactional. | MDX/yaml in republic-config.yaml |
Why it lives in two places
republic-config.yaml holds the declarative slow-moving truth — the nations roster,
the structural definitions, the configured platforms. It's editable in the Settings →
The Republic page and committed to git.
TrailBase holds the transactional fast-moving truth — individual holdings, ShopKeeper rows, statuses. Operators edit these inline; they're not committed to git because the volume and rate of change make that a bad fit.
If you're adding a new field, the question is "does this change rarely (config) or often (TrailBase)?". If you can't tell, default to TrailBase — config is more painful to evolve.
Routes
The dashboard exposes:
GET /api/republic— returns the merged config (nations + structure). The TrailBase record APIs are called directly from the client for ShopKeepers and holdings (TrailBase ships a typed JS client; we use it without a Next route in front of it).
That's deliberate: the rest of the dashboard's APIs are server-routed because they either need server-only secrets, or they wrap an external service. The Republic's TrailBase record APIs don't need a server hop — they're already same-origin via TrailBase's HTTP surface.
Page surfaces
| Page | What it does |
|---|---|
/the-republic | Top-level overview — nations roster, recent activity, holdings summary |
/the-republic/member-nations | CRUD on nations (config-backed, requires commit-and-push) |
/the-republic/shopkeepers | CRUD on ShopKeepers (TrailBase, no commit) |
/the-republic/know-how | "Platforms & Buildouts" — read-only reference content |
/the-republic/configuration | YAML editor for republic-config.yaml (Settings parity) |
Why we keep the politicized vocabulary
"Nations", "Republic", "ShopKeepers", "holdings" — the names are an internal joke that turned into stable schema. The model behind them is unsurprising (organizations, vendors, inventory), but the vocabulary surfaces in route paths, table names, and the sidebar. Renaming would touch every layer; it doesn't carry its weight today. New features should follow the same naming when it fits cleanly.
Built on
| Layer | Tech | Page |
|---|---|---|
| Tables (nations, ShopKeepers, holdings) | @tanstack/react-table via DataView | TanStack Table |
| Flag rendering | flag-icons + CountryFlag primitive | (stack page TBD) |
| Slow-moving config | republic-config.yaml | (config) |
| Transactional rows | TrailBase records (typed JS client, no Next hop) | TrailBase |
| YAML editor (Settings → Republic) | @monaco-editor/react | Monaco |
| Custom hook | useRepublicData | (see src/hooks/useRepublicData.ts) |