Skip to content

Dashboard

Flask host for the Python-generated root dashboard and incrementally migrated React dashboard, with shared same-origin APIs.

Details

Web dashboard for system observability + control. Served as a sidecar-managed Flask service on port 5127. Accessible remotely via Tailscale Serve.

Two frontends, one authority

The service hosts two distinct frontends during incremental migration:

  • / is the Python-generated root dashboard documented at services/dashboard/frontend.
  • /app is the React dashboard documented at services/dashboard/react.

The React surface is the primary desktop entry point, but it does not imply that every root-dashboard tab or mutation has moved. Tasks is an explicit completed migration: /app/tasks owns the UI, the old root task route redirects there, and same-origin /api/tasks uses native TaskStore authority. Both frontends use the same Flask process and same-origin API authority; neither browser may call sibling localhost service ports directly.

Tabs

Static: Overview, Threads, Today, Tasks, Jobs, Chats, Contracts, Projects, Costs. Plus a Settings panel reached via a gear icon in the header (off the nav bar by design — Settings is a configuration surface, not a peer of the daily-use tabs).

Dynamic: Threads, Triage, and Notifications appear via workflow views, the thread system, and the notification log.

Settings tab

Primary consumer of the control graph (see architecture/control-graph for the aggregator; architecture/health for the four-layer mental model the graph fuses). The Settings panel has two sub-tabs: Status — the control-graph tree (domain → subsystem → component hierarchy with effective_state badges, preference toggles (Want / No thanks / Undecided, hidden for is_core components), Configure / Walk me through action buttons for fixable requirements, universal ? help buttons that spawn interactive Claude Code sessions with structured briefs, a per-component ↻ reprobe button, and clickable bulk-state chips that jump to the first problem node of that state) — and Activity, a registry-driven set of cards (Obsidian bridge sparkline, sidecar event log, recent-notifications log). See the Card registry section below.

The Settings panel also has Embeddings and Inference sub-views. Settings › Inference hosts the per-call provenance activity feed and, above it, a Local model fleet section — one card per machine (reachability, loaded models, multi-GPU hardware), load-on-open + manual refresh, live-updated via the fleet.changed SSE event. Reads serve from GET /api/fleet (cached per-machine snapshot); the inline roster editor writes via POST /api/fleet/roster (add/update or clear a machine's inference.fleet entry; _reject_read_only-gated, mirrors /api/embeddings/vault). See architecture/inference/fleet.

Modes and endpoints

  • Dev mode: python -m work_buddy.dashboard --dev (auto-reloads on file changes). Not enabled in sidecar config — use manually for local development only.
  • Root frontend: the UI at / is generated by Python modules under frontend/ and served as content-hashed cached assets. See services/dashboard/frontend for the module structure, concatenation order, event delegation, escaping, shared widgets, and asset serving.
  • React app: GET /app/ serves the Vite build from dashboard-react/dist. It is the primary desktop UI opened by wbuddy launch and by the installer’s console-less native launcher; views move into it incrementally while unmigrated surfaces remain available in the root dashboard. GET /app/manifest.webmanifest and GET /app/icons/* provide optional PWA install metadata and branded icons with explicit media types. The PWA is an identity/standalone-window enhancement, not the runtime bootstrap. Release packaging builds this frontend before assembling the source-tree payload and refuses to build a payload when the dist is absent.
  • Remote access: Published privately via tailscale serve --bg 5127 — the tailscale component (registered in COMPONENT_CATALOG) gates this with click-to-fix requirements; see architecture/health/components and status/tailscale-status-directions. The browser only hits same-origin /api/... routes; all local service reads happen server-side.
  • Frame boundary: Every response sets Content-Security-Policy: frame-ancestors 'none' and X-Frame-Options: DENY so another site cannot embed dashboard controls.
  • Read-only mode: dashboard.read_only: true in config.yaml gates every mutating HTTP method (403) and hides or disables mutation controls in both frontends.

The React dashboard's standardized widget runtime, appearance contract, calendar presentation, and native Tasks view are documented under services/dashboard/react. Registry-driven configuration authority is documented at settings.

Card registry (feature cards)

The Settings → Activity sub-view is registry-driven: its widgets (Obsidian bridge sparkline, sidecar event log, recent-notifications log) are DashboardCards, not hand-coded render blocks. loadActivity() calls window.wbMountCards('activity', ...), which fetches the active card list and renders each registered renderer. A card may carry a gate — a boolean expression over component-active state — so a card whose component is opted out simply does not mount (no placeholder). The bridge card is gated on the obsidian component; opting Obsidian out also stops the backend bridge probe in get_system_state(). See architecture/feature-cards for the full pattern — gate AST, registry, endpoint, and how to add a card (including from a plugin).

  • GET /api/dashboard/cards/<mount_point> — active card descriptors for a mount point, gates evaluated against current component preferences. Read-only.

Right-rail surface (chat sidebar)

The root dashboard retains the right-side wb-chat-sidebar surface, which hosts the legacy conversation_chat renderer in pane mode. See services/dashboard/chat-sidebar for its compatibility API. React authoring uses the shared Co-work chat primitives and widget-native assisted drafts documented at services/dashboard/react/assisted-drafts; Jobs assistance is available at /app/jobs.

Distinct from the conversation_chat workflow-view tab — same renderer, different mount point: a workflow-view tab is a full-tab pane reached via the CHAT toast, while the chat sidebar opens directly without a toast and squishes the active tab rather than replacing it.

Agent ↔ form bridge

The root-dashboard form bridge is frozen compatibility infrastructure, not an extension point. See services/dashboard/form-bridge for the retained protocol. dashboard_interact rejects every jobs-add-job action with form_migrated and a link to /app/jobs. New React forms declare host-owned drafts and accept advisory typed patches through services/dashboard/react/assisted-drafts; the user retains final submission authority.

Real-time updates

The dashboard updates in real time from server-pushed events delivered over GET /api/events (Server-Sent Events). Each event mutates only the specific row(s) it concerns; panels are never wholesale-rewritten. bus.heartbeat published every 10 s as a liveness signal. See architecture/event-bus for the full design.

Control-graph endpoints (added with the Settings tab)

  • GET /api/control/graph[?force=1] — serialized graph + cache info.
  • POST /api/control/preference — toggle component preferences.
  • POST /api/control/fix/<req_id> — apply a fix (programmatic / input_required / agent_handoff).
  • POST /api/control/help/<node_id> — spawn an interactive help session.
  • POST /api/control/reprobe — re-run every tool probe, rebuild the graph.
  • POST /api/reprobe/<component_id> — pre-existing; per-component reprobe, reused by Settings' ↻ button.

All mutating control endpoints are gated by _reject_read_only() and auto-grant the relevant consent (the click IS the consent, same pattern as workflow-launch).

Settings broker endpoints

  • GET /api/settings/registry — definitions, pages, sections, and placements.
  • GET /api/settings/values[?context_id=...] — authoritative effective values for a page or view context.
  • POST /api/settings/values/<setting_id>/preview — validate and describe a proposed value without storing or publishing it.
  • PATCH /api/settings/values/<setting_id> — revision-checked update through the setting's declared authority.
  • DELETE /api/settings/values/<setting_id> — revision-checked reset; POST /api/settings/reset is the body-addressed compatibility form.

Settings responses are no-store. Writes and resets honor dashboard read-only mode and publish settings.changed after success. See settings for registry identity, navigation placement, authority, and persistence.

Form-bridge endpoints

  • POST /api/dashboard/interact — typed entry point for agents driving forms (called by the dashboard_interact MCP capability and any other process). Body {action, form_id, field?, value?, timeout_seconds?}.
  • POST /api/dashboard/interact/result/<request_id> — frontend's postback for rendezvous-backed actions (form_submit, form_get_state). Body {ok, error?, errors_by_field?, fields?}.

Both gated by _reject_read_only(). These are retained compatibility routes and cannot drive the React Jobs authoring form. See services/dashboard/form-bridge for the protocol.

User-job endpoints

  • GET /api/jobs/authoring — report authoring access and the configured time zone without creating a job.
  • POST /api/jobs/authoring — human submission from /app/jobs, gated by dashboard read-only mode and enrolled identity. It validates the draft and delegates to the normal user-job capability and exclusive-create writer.
  • POST /api/user_jobs — retained legacy user-job creation route, not an assisted-draft submission path.
  • POST /api/user_jobs/help — retained migration response: HTTP 410 with job_authoring_moved and /app/jobs, subject to read-only rejection. It does not spawn an agent or create a conversation.

Existing job management and editing remain in the root Jobs tab. See features/user-jobs and services/dashboard/react/assisted-drafts.

Assisted capture and task proposals

Tasks Quick Add, Journal Quick Capture, and Jobs authoring reuse widget-native draft assistance rather than a separate form-driving chat stack. Model assistance is opt-in and advisory; explicit user actions own submission. Task proposals are durable Threads records reviewed through /app/tasks?proposal=<threadId> and realized through the native TaskStore boundary. Journal retains exact capture input in Sources before routing or proposing a follow-up. See services/dashboard/react/assisted-drafts, services/dashboard/react/tasks-view, and journal/source-backed-capture.

Triage flow (no separate dashboard endpoints)

Triage runs through the unified source pipeline (run_source_pipeline capability, dispatching to EmailTriagePipeline / ChromeTriagePipeline / JournalBacklogPipeline / inline-capture). Spawned Threads land on the Threads tab for the user to approve/reject/defer per child. There is no separate Review-tab surface or Resolution-Surface endpoints — those were retired in the clarify → Threads migration. Per-cluster actions resolve via the standard Threads action-chip dispatch path.

CRITICAL for all agents modifying dashboard code

  • Never add browser-side fetches to sibling localhost ports (5123, 5124, 27125, etc.) — these break on mobile and over Tailscale. All cross-service reads must happen server-side.
  • Gate new POST routes with _reject_read_only() so read-only deployments stay read-only.
  • Same-origin only for any fetch from the frontend.
  • Silent conversation create for sidebar-bound chats — call conversations.store.create_conversation directly, NOT the conversation_create capability, so _notify_conversation_created does not double-mount the conversation as both a CHAT toast/workflow-view tab and a sidebar.
  • Keep the legacy form bridge frozen. Existing handlers route dashboard.form.* events through wbFormBridge; new consumers use widget-native assisted drafts and human-only submission, not DOM-driving events.
  • Keep request handlers off the hot-path anti-patterns — no per-request config parse, per-open schema work, N+1 store opens, or unbounded synchronous bridge/subprocess calls. Serve expensive reads from a background-refreshed cache and pre-warm at startup. See architecture/hot-path-discipline.

Children