Skip to content

Studio

The Synapse dashboard is growing from a read-only cockpit into an operator Studio: a control plane that answers, in one glance, what is happening, what is at risk, what is safe to do next, and who should do it. It is built the way the rest of Synapse is — local-first, dependency-light, offline-safe — and rides on the read model the dashboard already serves.

This page tracks what has shipped. The full direction lives in the internal design plan; what is public here is real and running.

Design system

The Studio speaks an instrument-panel language rather than a marketing dashboard: a deep ink base, one indigo-violet brand hue, and red/amber/green reserved exclusively for verdicts so risk reads at a glance. It is a small set of CSS custom properties and components in dashboard_assets/studio.css, served by the dashboard at /studio.css — no build step, no external request, and it renders correctly with the hub offline.

  • Palette — ink-navy surfaces, an indigo-violet accent for links, focus, and the live pulse, and the three verdict colours used nowhere else.
  • Type — three roles: a display face for labels and numerals, a body face for prose, and a monospace face that carries the data (task ids, leases, paths, hashes). Space Grotesk, Inter, and JetBrains Mono now ship as pinned, same-origin variable WOFF2 assets behind those role variables. Latin and Latin Extended cover product copy and Central-European names in 217,608 bytes; other scripts retain the system fallbacks. The browser makes no font request to an external origin.
  • Components — panels and cards (elevation by tint and a hairline, never shadows), status dots, the verdict pill, mono data rows, the navigation rail, and an indigo focus ring. Motion is restrained and stilled under prefers-reduced-motion.

The reference page

Run the dashboard and open /studio to see the design system exercised on one page — every verdict tone, every status-dot state, the panels, cards, data rows, and numerals. It renders no live data, so it works even when the hub is down, and it is the visual reference the live command centre is assembled from.

synapse dashboard --port 8765
# front door (command centre): http://127.0.0.1:8765/
# design reference:           http://127.0.0.1:8765/studio
# classic hub HTML:           http://127.0.0.1:8765/classic

The Studio snapshot — /studio.json

The command centre reads one JSON contract, served live at /studio.json: a single risk verdict (the reserved red/amber/green signal), a row of headline counters, the agents, claims, task columns, conflicts, risk behind them, and a compact security-posture section. It is a pure projection of the same read model the dashboard already exposes — studio_snapshot.py reshaping /snapshot.json — so Studio adds no new hub call, only a curated command-centre view. Every headline count is derived from the list it summarises, so the instrument and the rows beneath it can never disagree. A partial payload from a degraded hub still projects to a renderable snapshot rather than failing.

The observed-peer panel uses the dashboard's direct --observed-peer fetches, not Fleet's offline mirror JSON. Reachability confirms a fetch, not completeness: a reachable peer without a reported log high-water has unknown lag and an amber row. observed_fleet.peers_lag_unknown counts these separately from lagging or unreachable peers. Green requires known zero lag; red fetch failures retain priority when mixed with unknown-lag peers. These observations remain advisory.

synapse dashboard --uri ws://127.0.0.1:8765
# then GET http://127.0.0.1:8765/studio.json

The command centre — /studio/command

Main snapshots use the same session bearer as access polling. The server supplies a separate request deadline: hub readiness plus hub response budget, the observed-peer timeout when peers are configured, and four seconds of reserve. Observed peers are collected concurrently. Collection itself has one second of reserve; waiting for the shared collection identity is bounded to one second, leaving two seconds for response delivery and browser handling. A busy collector or expired collection returns HTTP 503 instead of queuing indefinitely. These are operational deadlines, not hard real-time guarantees. Only one polling loop starts. Failed refreshes mark the view stale and retained data as not current; a client timeout does not prove the hub is down. The next successful snapshot replaces the data and clears the banner.

Studio access-descriptor polling has a five-second request deadline. A newer refresh cancels its predecessor; late replies cannot replace a newer role or unavailable state. These displayed capabilities remain presentation hints, not a replacement for server-side authorisation.

Optional Fleet mirror

The separate Fleet mirror panel consumes an explicitly configured same-host export, without importing Fleet or reading its journals. Core-only operation and the direct --observed-peer panel remain unchanged.

Have Fleet write a version-one export using synapse-fleet observed --state-dir STATE --export-file EXPORT --source-id lab or its opt-in daemon export. Then start Core with both --fleet-observed-file EXPORT and --fleet-observed-access-file GRANTS. No service is installed or started automatically. The export and grant files must be owner-only, regular, non-symlinked files with a single hardlink.

GRANTS is JSON such as {"version":1,"observers":["compatibility"]}. compatibility is the principal for a single dashboard bearer; with a multi-principal access file, list its explicit principal IDs instead. A dashboard read capability alone does not grant mirror disclosure. Revocation is checked before reading and again before returning data. Existing dashboard-access version-one capability keys are unchanged.

Authenticated /fleet-observed-access.json reports the separate grant; /fleet-observed.json returns the complete export. Responses are no-store: 404 means disabled, 401/403 locked, 409 incompatible, and 503 unavailable or invalid. The reader never returns a cached last-good export as current data. The panel clears previously displayed metadata on an unsuccessful refresh. Starting a new refresh cancels the previous request. Only the latest request may update the panel, including after JSON decoding or a network error; a late response cannot restore rows after a newer locked state. Each request has a five-second deadline, and a later successful refresh can restore the view. Studio uses its existing bearer mechanism; configuring this feed does not introduce a separate login flow.

The envelope contains version 1, an explicit source label, export time, advisory: true and the Fleet snapshot. Inputs are bounded to 4 MiB, 4096 peers and 10000 tasks; oversized or malformed envelopes are refused. The panel pages through 50 rows at a time and exposes task evidence through keyboard-operable disclosure controls. It retains source labels, peer status times, unknown counts, partial drains and unresolved conflict evidence. Changed exports preserve opened task details and keyboard focus by source and task identity, including when a task moves to another page. Pagination keeps focus on an enabled navigation control. If the focused task disappears, the source changes, or access becomes unavailable, focus moves to the panel status. Refreshes do not take focus from elsewhere in Studio. An export time is not peer freshness. These rows never enter the local board, claim controls, risk verdict or direct-peer reachability totals.

The live operator view, served at /studio/command, reads /studio.json and answers at a glance what the fleet is doing and what is at risk. Its signature instrument is the Coordination Clock — a radial gauge where every claim is a segment around the dial, coloured by lease health (green fresh, amber ageing, red stale), with conflicts marked on the rim and a slow radar sweep; the dial centre carries the verdict and the live claim count. Around it sit the verdict pill, the headline counters, a board-column view, and the agents, claims, tasks, and risk panels.

Shared-plan columns

The board view joins the blackboard plan with live claim leases by task id. It preserves the hub's exact values rather than inventing presentation-only lifecycle states:

  • blackboard open, in_progress, blocked, done, and cancelled;
  • claim claimed, working, input_required, done, and failed.

The visible columns are Open, Claimed, Working, Input required, Blocked, Closed, and a fail-visible Other column for future additive values. Cards retain both raw statuses, their source, readiness, unmet dependencies, owner, paths, and lease freshness. A live claim without a blackboard row appears as an explicitly labelled ad-hoc claim; the view does not fabricate a declaration. A bounded blackboard snapshot carries its truncation and total counts so the UI cannot present a partial page as the whole plan. Its complete ready-id set remains authoritative: dependencies whose task rows were omitted are shown as unknown when necessary, never fabricated as proven blockers of a ready task.

The projection is read-only. It does not claim, assign, route, reserve, approve, release, or update work, and the free Studio page renders no mutation controls. Evidence and approval notes remain progress-ledger facts; they do not become a fictional "review" status. board-columns.js creates DOM text nodes for task data, and the command shell, feed polling, board renderer, and styles ship as separate focused package assets.

The page shell is hub-independent: it loads with no hub running, shows an offline state, and fills in live as it polls. The persistent NavRail keeps the command view, reference view, fleet, LiveFeed, and security posture one click away; the HeaderBar shows the live hub id, version, verdict, and connection state from /studio.json. It honours prefers-reduced-motion — the sweep stills and a claims table pairs the dial so the same information is legible without animation. Vanilla HTML, the studio.css tokens, and dependency-free ES — no build step, no external request.

Browser roles and capabilities

Both Studio surfaces read the authenticated, server-authored /dashboard-access.json descriptor. The dependency-free command centre remains read-only and shows a neutral role · principal pill; malformed, unreachable, or unauthenticated access fails visibly as access unavailable. It adds no empty operator/admin controls.

The built React cockpit probes access before exposing its live shell. It uses the descriptor's capability booleans—not the display role—to build the command catalogue. Viewer DOM, search results, and keyboard selection contain no message/task write entry. Operator and admin currently see exactly message, task declare, and task update; admin has a distinct badge but no fabricated admin action. A capability downgrade closes the palette/form, moves focus to the command trigger, and announces that write controls were removed. The role is never persisted as authority.

Conditional rendering grants nothing. The browser bearer is independently resolved on every POST, then the dashboard applies the exact route capability, JSON/media/size/rate gates, principal-specific relay identity, and the existing hub ACL and durable audit. See Dashboard browser principals for the private token-file policy and status-code contract.

The security-posture panel sits beside the Coordination Clock and summarises five shipped safety surfaces: sandbox grants, ACL/role visibility, the dashboard exposure guard, signed federation / peer observation, and receipt evidence. Rows are evidence-bound: missing role bindings, peers, or receipts are shown as amber "not currently evidenced" instead of being treated as configured. The panel is read-only; server-side ACL, dashboard bind, federation, and sandbox enforcement remain in their existing modules.

The observed peers (advisory) panel projects dashboard --observed-peer rows (and any FLEET-style advisory mirrors folded into the same snapshot field) into /studio.json under observed_fleet: per-peer reachability, lag, clock skew, and observed claim-owner counts. Unreachable peers are red; lagging peers amber; no configured peers is an honest amber "not configured" state. Peer data never grants local claim authority.

The LiveFeed panel tails /events.json?since=SEQ&limit=N, the durable event-store feed served when the dashboard starts with --feeds-db. It starts at since=latest, then polls forward by next_cursor, so it shows new recorded events without walking a large history. If --feeds-db is absent, the panel says the event feed is not configured rather than implying a quiet log.

synapse dashboard --port 8765 --feeds-db ./hub.db
# open http://127.0.0.1:8765/  (same shell as /studio/command)

The dashboard CLI prints the Studio URL first. Root / and /studio/command serve the same command-centre shell; /classic keeps the pre-Studio hub HTML.

What comes next

Beyond the command centre come the workflow, trace, policy, routing, and channel surfaces, each reading the same kind of projection. The core read-only Studio stays free; an organisation-level workbench (saved views, exports, multi-project, managed) is planned as a separate layer.