Skip to content

Add control-plane setup readiness surfaces#416

Open
vsumner wants to merge 9 commits intospacedriveapp:mainfrom
vsumner:feat/doctor-cli-slice-1
Open

Add control-plane setup readiness surfaces#416
vsumner wants to merge 9 commits intospacedriveapp:mainfrom
vsumner:feat/doctor-cli-slice-1

Conversation

@vsumner
Copy link
Contributor

@vsumner vsumner commented Mar 12, 2026

Summary

  • add control-plane setup readiness surfaces instead of a public doctor command
  • add a System Health settings panel for warmup and MCP runtime state, plus the follow-up review fixes for routing and reconnect targeting
  • update quickstart and roadmap docs to describe the control-plane readiness flow

Changes

  • add shared setup readiness classification and surfaces in the shell banner and Overview
  • add frontend warmup and MCP client methods, including agent-scoped MCP reconnect for the System Health panel
  • add targeted frontend tests for readiness classification and runtime action payloads
  • fix two pre-existing clippy warnings in send_message_to_another_channel.rs so just gate-pr passes cleanly

Test plan

  • bun test interface/src/components/SetupReadiness.test.ts interface/src/api/client.test.ts
  • bun run build in interface/
  • just preflight
  • just gate-pr

Note

AI Summary: This PR replaces the doctor CLI with integrated readiness surfaces. New UI surfaces show setup status (missing providers, secrets, MCP connectivity) in the shell banner and Overview page. A new System Health settings panel provides per-agent warmup state and MCP server status. Includes frontend warmup/reconnect methods and targeted tests. Fixes two pre-existing clippy warnings. Changes span frontend components (SetupReadiness.tsx), client API methods, documentation, and backend tests.

Written by Tembo for commit 1e872df.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds runtime readiness types/endpoints, a readiness classifier/hook, UI surfaces (shell banner, Overview card, Settings → System Health), client mutations for warmup and MCP reconnect, frontend and Rust tests, and doc updates describing the visible setup/runtime health UX.

Changes

Cohort / File(s) Summary
Documentation
docs/content/docs/(deployment)/roadmap.mdx, docs/content/docs/(getting-started)/quickstart.mdx
Expanded prose describing UI visibility of setup/readiness signals (shell banner, Overview, Settings → System Health) and immediate channel validation UX.
API client (types & endpoints)
interface/src/api/client.ts, interface/src/api/client.test.ts
Added warmup and MCP types and client endpoints (warmupStatus, triggerWarmup, mcpStatus, reconnectMcpServer), readApiError helper, and tests validating request bodies and error propagation.
Setup readiness logic & UI
interface/src/components/SetupReadiness.tsx, interface/src/components/SetupReadiness.test.ts, interface/src/components/SetupBanner.tsx
Introduced readiness types, classifySetupReadiness, useSetupReadiness, components (SetupBanner, SetupReadinessCard), styling mappings, tests; SetupBanner.tsx now re-exports from SetupReadiness.
Routing & Overview integration
interface/src/router.tsx, interface/src/routes/Overview.tsx
Rendered <SetupBanner /> in RootLayout and added <SetupReadinessCard /> to the Overview page.
Settings: System Health
interface/src/routes/Settings.tsx
Added system-health section and SystemHealthSection with warmup & MCP queries and mutations (trigger warmup, reconnect MCP), per-agent UI/actions, and error handling; diff contains duplicated blocks to review.
Rust messaging tests
src/tools/send_message_to_another_channel.rs
Added test scaffolding and a test verifying Signal-prefix targeting uses the named current adapter and records broadcasts; test-only additions, no public API changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested reviewers

  • jamiepine
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and concisely describes the main change: introducing control-plane setup readiness surfaces as a replacement for the doctor CLI command.
Description check ✅ Passed The pull request description is comprehensive and directly related to the changeset, covering the summary of changes, specific modifications, and test plan for implementing control-plane setup readiness surfaces.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/tools/send_message_to_another_channel.rs (1)

153-159: Add a call-path regression test for the named Signal adapter rewrite.

This branch is the only place that remaps an explicit signal:... target onto current_adapter. The parser tests below will still pass if a future refactor sends these messages through plain signal instead of signal:gvoice1, so a small SendMessageTool::call test would lock down the account-selection behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/send_message_to_another_channel.rs` around lines 153 - 159, Add a
regression test that exercises the named Signal adapter rewrite in
SendMessageTool::call: construct a SendMessageTool with current_adapter
Some("signal:gvoice1") and call SendMessageTool::call with a target whose
adapter is "signal:someaccount" (or plain "signal") and assert the actual
adapter used to send the message equals the current_adapter ("signal:gvoice1");
place the test alongside other SendMessageTool tests and ensure it fails if the
code routes to plain "signal" instead of remapping to current_adapter, verifying
the branch that checks target.adapter and
self.current_adapter.as_ref().filter(|adapter| adapter.starts_with("signal:"))
is exercised.
interface/src/components/SetupReadiness.test.ts (1)

139-145: Inconsistent indentation.

The expect block is outdented relative to the surrounding code in this test. This appears to be an unintentional formatting inconsistency.

✏️ Suggested fix
-	expect(items).toContainEqual(
-		expect.objectContaining({
-			id: "warmup_degraded",
-			severity: "warning",
-			tab: "system-health",
-		}),
-	);
+		expect(items).toContainEqual(
+			expect.objectContaining({
+				id: "warmup_degraded",
+				severity: "warning",
+				tab: "system-health",
+			}),
+		);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/components/SetupReadiness.test.ts` around lines 139 - 145, The
expect assertion for the "warmup_degraded" item is mis-indented compared to
surrounding test code; re-indent the block so it aligns with the other
assertions in the same test (match the surrounding indentation level for the
expect(...) call and its nested expect.objectContaining({...}) properties),
ensuring the lines containing expect(objectContaining({ id: "warmup_degraded",
severity: "warning", tab: "system-health" })) follow the same indentation
pattern as the other expect assertions in SetupReadiness.test.ts.
interface/src/routes/Settings.tsx (2)

1823-1829: Consider using index for React keys in detail list.

Using detail (the string content) as the React key could cause warnings if formatWarmupDetails ever returns duplicate strings. While currently unlikely, using the index would be more robust.

✏️ Suggested fix
 {details.length > 0 && (
 	<div className="mt-2 flex flex-col gap-1 text-sm text-ink-dull">
-		{details.map((detail) => (
-			<p key={detail}>{detail}</p>
+		{details.map((detail, idx) => (
+			<p key={idx}>{detail}</p>
 		))}
 	</div>
 )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/Settings.tsx` around lines 1823 - 1829, The current JSX
uses the detail string as the React key in the details.map (render block
referencing details and formatWarmupDetails) which can produce duplicate-key
warnings if identical strings appear; update the map to use the array index as
the key (e.g., map((detail, idx) => ... key={idx})) so each <p> has a stable
unique key for this list rendering.

1831-1838: Shared loading state across all buttons.

The loading={triggerWarmupMutation.isPending} applies to all Refresh buttons simultaneously. If a user triggers warmup for multiple agents in quick succession, all buttons will show the loading spinner until the last mutation completes. The same pattern appears for the MCP Reconnect buttons at line 1894.

This is acceptable for the current implementation, but for better UX you could track the pending mutation target (e.g., pendingAgentId) to show loading only on the relevant button.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/Settings.tsx` around lines 1831 - 1838, The Refresh
buttons all use the shared triggerWarmupMutation.isPending so every button shows
loading; fix by tracking the targeted agent id (e.g., add a local state
pendingAgentId) and set pendingAgentId = entry.agent_id when calling
triggerWarmupMutation.mutate({ agentId: entry.agent_id, force: true }), then use
loading={pendingAgentId === entry.agent_id && triggerWarmupMutation.isPending}
on the Button; clear pendingAgentId in the mutation callbacks
(onSuccess/onError/onSettled) to stop showing loading only for the completed
target. Apply the same pattern for the MCP reconnect mutation (the reconnect
mutation instance and its Button) so only the affected agent's button shows a
spinner.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@interface/src/components/SetupReadiness.tsx`:
- Around line 209-230: The readiness UI is hidden when any probe fails because
isLoading treats errored queries like missing data; update the isLoading
calculation in the useMemo (the block that builds
items/actionableItems/blockerCount/warningCount) to ignore queries that have
errored (e.g., use .some(q => q.isLoading && !q.isError)) so a failed probe will
not suppress the banner, and also ensure classifySetupReadiness receives enough
info to surface probe errors (pass query.error or the full query objects instead
of only query.data if classifySetupReadiness needs error context).
- Around line 147-170: The readiness check currently hides the
"messaging_unbound" item whenever any configured instance has a binding because
it only triggers when boundInstances.length === 0; update the logic that builds
the messaging checklist (use the existing messaging, configuredInstances,
boundInstances variables) to instead detect if any configured instance remains
unbound (e.g., configuredInstances.some(instance => instance.binding_count ===
0)) and emit the item with id "messaging_unbound" when that condition is true so
unbound adapters are shown even in multi-platform setups.

---

Nitpick comments:
In `@interface/src/components/SetupReadiness.test.ts`:
- Around line 139-145: The expect assertion for the "warmup_degraded" item is
mis-indented compared to surrounding test code; re-indent the block so it aligns
with the other assertions in the same test (match the surrounding indentation
level for the expect(...) call and its nested expect.objectContaining({...})
properties), ensuring the lines containing expect(objectContaining({ id:
"warmup_degraded", severity: "warning", tab: "system-health" })) follow the same
indentation pattern as the other expect assertions in SetupReadiness.test.ts.

In `@interface/src/routes/Settings.tsx`:
- Around line 1823-1829: The current JSX uses the detail string as the React key
in the details.map (render block referencing details and formatWarmupDetails)
which can produce duplicate-key warnings if identical strings appear; update the
map to use the array index as the key (e.g., map((detail, idx) => ...
key={idx})) so each <p> has a stable unique key for this list rendering.
- Around line 1831-1838: The Refresh buttons all use the shared
triggerWarmupMutation.isPending so every button shows loading; fix by tracking
the targeted agent id (e.g., add a local state pendingAgentId) and set
pendingAgentId = entry.agent_id when calling triggerWarmupMutation.mutate({
agentId: entry.agent_id, force: true }), then use loading={pendingAgentId ===
entry.agent_id && triggerWarmupMutation.isPending} on the Button; clear
pendingAgentId in the mutation callbacks (onSuccess/onError/onSettled) to stop
showing loading only for the completed target. Apply the same pattern for the
MCP reconnect mutation (the reconnect mutation instance and its Button) so only
the affected agent's button shows a spinner.

In `@src/tools/send_message_to_another_channel.rs`:
- Around line 153-159: Add a regression test that exercises the named Signal
adapter rewrite in SendMessageTool::call: construct a SendMessageTool with
current_adapter Some("signal:gvoice1") and call SendMessageTool::call with a
target whose adapter is "signal:someaccount" (or plain "signal") and assert the
actual adapter used to send the message equals the current_adapter
("signal:gvoice1"); place the test alongside other SendMessageTool tests and
ensure it fails if the code routes to plain "signal" instead of remapping to
current_adapter, verifying the branch that checks target.adapter and
self.current_adapter.as_ref().filter(|adapter| adapter.starts_with("signal:"))
is exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9764a36-16e0-4d4f-aca8-44a0bad1192d

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe99c6 and 1e872df.

📒 Files selected for processing (11)
  • docs/content/docs/(deployment)/roadmap.mdx
  • docs/content/docs/(getting-started)/quickstart.mdx
  • interface/src/api/client.test.ts
  • interface/src/api/client.ts
  • interface/src/components/SetupBanner.tsx
  • interface/src/components/SetupReadiness.test.ts
  • interface/src/components/SetupReadiness.tsx
  • interface/src/router.tsx
  • interface/src/routes/Overview.tsx
  • interface/src/routes/Settings.tsx
  • src/tools/send_message_to_another_channel.rs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@interface/src/routes/Settings.tsx`:
- Around line 1673-1683: The warmup and MCP status queries use useQuery but
don't surface fetch errors; update the two hooks that call useQuery for queryFn:
api.warmupStatus and api.mcpStatus to also destructure isError and error (e.g.,
const { data: warmupData, isLoading: warmupLoading, isError: warmupError, error:
warmupFetchError } = useQuery(...), and similarly for mcp). Then update the UI
branches that currently render the empty states ("No agents…" and "No enabled
MCP servers…") to check the new isError flags first and render an error
message/component showing the fetch error (use the error object) before falling
back to the empty-state or loading behaviors; reference the warmup* and mcp*
symbols in the rendering logic so failures are clearly distinguished from empty
results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e584920-7799-41ea-b7a7-22aaa11cdfdb

📥 Commits

Reviewing files that changed from the base of the PR and between 1e872df and 7def3ac.

📒 Files selected for processing (6)
  • interface/src/api/client.test.ts
  • interface/src/api/client.ts
  • interface/src/components/SetupReadiness.test.ts
  • interface/src/components/SetupReadiness.tsx
  • interface/src/routes/Settings.tsx
  • src/tools/send_message_to_another_channel.rs

@vsumner vsumner force-pushed the feat/doctor-cli-slice-1 branch from 7def3ac to d2656de Compare March 13, 2026 03:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
interface/src/routes/Settings.tsx (1)

1715-1717: Pending-action state should track multiple in-flight mutations.

A single pendingWarmupTarget / pendingReconnectTarget can be cleared by one settle while another request is still running, causing transient incorrect button/loading state. Consider tracking pending targets as a Set<string>.

Based on learnings: For changes in async/stateful paths (worker lifecycle, cancellation, retrigger, recall cache behavior), include explicit race/terminal-state reasoning in the PR summary and run targeted tests in addition to just gate-pr.

Also applies to: 1731-1733, 1738-1740, 1751-1753

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/routes/Settings.tsx` around lines 1715 - 1717, The current
single-value pendingWarmupTarget/pendingReconnectTarget state causes races;
change these to Set<string> (e.g., pendingWarmupTargets,
pendingReconnectTargets) and update the mutation handlers: in onMutate (and
similar spots referencing setPendingWarmupTarget/setPendingReconnectTarget) use
a functional update to add params?.agentId ?? "__all__" to the Set, and in
onSettled/onError remove that id from the Set; ensure UI checks set.has(id) (or
set.size>0 for global) and always copy/replace the Set immutably when updating
to avoid stale closures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@interface/src/components/SetupReadiness.test.ts`:
- Around line 19-41: The providers object inside the configuredProviders() test
fixture is missing the required github_copilot field declared by ProviderStatus;
add "github_copilot: false" (or the appropriate boolean) to the providers map in
the configuredProviders() fixture in SetupReadiness.test.ts so the fixture
conforms to ProviderStatus and type-checks correctly.

In `@interface/src/components/SetupReadiness.tsx`:
- Around line 225-233: The probeErrors array-of-tuples is being inferred as
(string | UseQueryResult)[] which fails strict typing when passed to
classifySetupReadiness's probeErrors?: string[]; fix by narrowing the tuple
types—either annotate the tuple array as an array of [string, UseQueryResult]
tuples (e.g., const probes: [string, UseQueryResult][] = [...]) or append as
const to the literal array so the first elements are string literals, then
filter and map to produce a string[] before passing to classifySetupReadiness
(refer to the probeErrors variable and the classifySetupReadiness call).

In `@interface/src/routes/Settings.tsx`:
- Around line 1660-1662: The code currently pushes raw backend error text
(entry.status.last_error) into the UI details array; replace this by
sanitizing/redacting that string before rendering (e.g., map
entry.status.last_error through a sanitizeError or redactError utility that
strips provider/runtime details and returns either a sanitized message or a
generic "Error occurred (code: XYZ)" plus a non-sensitive structured code).
Update both occurrences that push entry.status.last_error (the block around
details.push(entry.status.last_error) and the similar block at 1871-1875) to use
the sanitizer function and surface only the sanitized message or generic
fallback, ensuring no raw backend error text is directly rendered.

---

Nitpick comments:
In `@interface/src/routes/Settings.tsx`:
- Around line 1715-1717: The current single-value
pendingWarmupTarget/pendingReconnectTarget state causes races; change these to
Set<string> (e.g., pendingWarmupTargets, pendingReconnectTargets) and update the
mutation handlers: in onMutate (and similar spots referencing
setPendingWarmupTarget/setPendingReconnectTarget) use a functional update to add
params?.agentId ?? "__all__" to the Set, and in onSettled/onError remove that id
from the Set; ensure UI checks set.has(id) (or set.size>0 for global) and always
copy/replace the Set immutably when updating to avoid stale closures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88ffaff2-4f9f-4f11-b631-3b8a5121b3ca

📥 Commits

Reviewing files that changed from the base of the PR and between 7def3ac and d2656de.

📒 Files selected for processing (11)
  • docs/content/docs/(deployment)/roadmap.mdx
  • docs/content/docs/(getting-started)/quickstart.mdx
  • interface/src/api/client.test.ts
  • interface/src/api/client.ts
  • interface/src/components/SetupBanner.tsx
  • interface/src/components/SetupReadiness.test.ts
  • interface/src/components/SetupReadiness.tsx
  • interface/src/router.tsx
  • interface/src/routes/Overview.tsx
  • interface/src/routes/Settings.tsx
  • src/tools/send_message_to_another_channel.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • interface/src/router.tsx
  • interface/src/routes/Overview.tsx
  • interface/src/api/client.test.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
interface/src/components/SetupReadiness.tsx (1)

97-122: Consider surfacing warmup info even without providers.

Currently, warmup status is only evaluated when providers?.has_any is true (line 97). If providers fail to load but warmup data is available, users won't see warmup-related items. This may be intentional (warmup is meaningless without providers), but consider whether a probe failure for providers should still allow warmup items to render when warmup data exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@interface/src/components/SetupReadiness.tsx` around lines 97 - 122, The
warmup block is currently gated by providers?.has_any so warmup items won't
render when provider probe fails; change the guard to check warmup data instead
(e.g., if (warmup?.statuses?.length)) so the warmup evaluation always runs when
warmup exists; keep the existing logic that computes degradedAgents via
warmup.statuses.filter(...).map(...), warmAgents, and the items.push(...) calls
(ids "warmup_degraded" and "warmup_pending" and use of formatAgentList)
unchanged so only the outer condition is adjusted to rely on warmup presence
rather than providers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@interface/src/components/SetupReadiness.tsx`:
- Around line 97-122: The warmup block is currently gated by providers?.has_any
so warmup items won't render when provider probe fails; change the guard to
check warmup data instead (e.g., if (warmup?.statuses?.length)) so the warmup
evaluation always runs when warmup exists; keep the existing logic that computes
degradedAgents via warmup.statuses.filter(...).map(...), warmAgents, and the
items.push(...) calls (ids "warmup_degraded" and "warmup_pending" and use of
formatAgentList) unchanged so only the outer condition is adjusted to rely on
warmup presence rather than providers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a67e1f3e-1d25-4273-a925-4c62c4fa12f0

📥 Commits

Reviewing files that changed from the base of the PR and between d2656de and 21ba7ec.

⛔ Files ignored due to path filters (3)
  • interface/bun.lock is excluded by !**/*.lock, !**/*.lock
  • interface/package.json is excluded by !**/*.json
  • interface/tsconfig.json is excluded by !**/*.json
📒 Files selected for processing (4)
  • interface/src/api/client.test.ts
  • interface/src/components/SetupReadiness.test.ts
  • interface/src/components/SetupReadiness.tsx
  • interface/src/routes/Settings.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • interface/src/api/client.test.ts

@vsumner vsumner force-pushed the feat/doctor-cli-slice-1 branch from 6ea89b9 to e220f2e Compare March 13, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant