The availability check was failing for online hosts due to a timing issue: connectionStates would show "online" before SessionProvider registered its session accessor, causing session to be null. Changed selectedDaemonIsUnavailable to selectedDaemonIsOffline which only checks connectionStates status (the single source of truth for connection state), removing the redundant session and ws.isConnected checks. isTargetDaemonReady still requires session for actual operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
17 KiB
Host Management UX Overhaul
Completed Work (Summary)
The multi-daemon infrastructure is in place: session directory with daemon-scoped subscriptions, aggregated agent views, daemon-aware routing for agent/diff/file screens, React Query for connection state persistence, background reconnection with exponential backoff, useDaemonRequest for consistent async flows, structured logging, and architecture docs in docs/multi-daemon.md.
Guiding Principles
- Hosts are always connected when added—no manual "connect" action, no toggles.
- A stopped host is not an error—it's just a state. Errors only surface when the user tries to interact with an agent whose host is stopped.
- No "active" or "primary" concepts—actions that need a host require the user to choose explicitly.
- The home screen shows a flat list of agents with host name as metadata, not grouped by host.
Tasks
1. Rename "Daemon" to "Host" in UI
- Rename all user-facing labels, messages, and UI text from "daemon" to "host" throughout the app.
- Settings screen, connection banners, error messages, modals, etc.
- Internal code can keep "daemon" terminology; this is UI-only.
- Updated every user-facing string (settings, modals, placeholders, defaults) to say "host" and spot-checked via targeted searches; no automated tests were run.
- Context: Remaining "daemon" labels in git diff, file explorer, and agent detail screens now say "host" (updated
packages/app/src/app/git-diff.tsx,file-explorer.tsx,agent/[id].tsx, andagent/[serverId]/[agentId].tsx). - Review: Confirmed the agent redirect, file explorer, and git diff screens reflect the updated host wording with no lingering user-facing "daemon" strings.
- Review follow-up: Git Diff screen still showed the selected host as "Server"; updated the meta labels to "Host" to keep UI text consistent (
packages/app/src/app/git-diff.tsx). - Review follow-up (current): Found that the Git Diff header still surfaced the internal host ID instead of the friendly label; updated
GitDiffContentto accept the computedserverLabeland verified vianpm run typecheck --workspace=@paseo/app.
- Review follow-up: Remove the lingering "Server" wording on the Settings add-host form and restart alert so everything says "host."
- Context: The add-host input placeholder still reads "My Server" and the restart success alert title says "Server reachable" (
packages/app/src/app/settings.tsx:609,packages/app/src/app/settings.tsx:804-820). - Updated placeholder from "My Server" to "My Host" and alert title from "Server reachable" to "Host reachable"; verified with
npm run typecheck --workspace=@paseo/app.
- Context: The add-host input placeholder still reads "My Server" and the restart success alert title says "Server reachable" (
2. Remove Active/Primary/Auto-Connect Concepts
- Remove the concept of "active daemon" from the UI and simplify to just "hosts".
- Summary: Home/footer actions, create/import flows, and agent navigation now route directly via explicit host IDs with analytics/docs/aggregated list updates, verified with
npm run typecheck --workspace=@paseo/app. - Context:
GlobalFooter, the create/import modals, the agent screen, and the agent list previously read/writeactiveDaemonId, forcing a global active host before routing; they now rely on explicit host IDs instead.
- Summary: Home/footer actions, create/import flows, and agent navigation now route directly via explicit host IDs with analytics/docs/aggregated list updates, verified with
- Remove the concept of "primary daemon"—no default host for actions.
- Eliminated the persisted
activeDaemonId, flattened the session directory, and refactored_layout, realtime, settings, and agent UI to always resolve hosts explicitly so every host gets its ownSessionProvider; verified vianpm run typecheck --workspace=@paseo/app.
- Eliminated the persisted
- Remove the "auto-connect" toggle from host settings—hosts always auto-connect when added.
- Removed the toggle from the host form so entries always auto-connect and verified with
npm run typecheck --workspace=@paseo/app.
- Removed the toggle from the host form so entries always auto-connect and verified with
- Clean up any related state/UI that exposes these concepts to users.
- Removed the legacy autoConnect state from daemon profiles and session hosts so every host hydrates automatically, refreshed the host-unavailable messaging/docs, and verified via
npm run typecheck --workspace=@paseo/app.
- Removed the legacy autoConnect state from daemon profiles and session hosts so every host hydrates automatically, refreshed the host-unavailable messaging/docs, and verified via
Review: No Silent Defaults
- Review the changes to ensure we didn't just replace "active daemon" with "first host" (e.g.,
hosts[0]}). The goal is explicit user choice, not a hidden default.- Create/import modal now requires explicit host selection (no first entry default) and
useAggregatedAgentsno longer fabricates a host id viaconnectionStates.keys().next(); verified withnpm run typecheck --workspace=@paseo/app.
- Create/import modal now requires explicit host selection (no first entry default) and
3. Simplify Settings Screen
- Remove the standalone "Test Connection" form/URL input at the top of settings.
- Removed the global host selector/Test UI from
packages/app/src/app/settings.tsxso configuration happens per-host within their cards/forms, and rannpm run typecheck --workspace=@paseo/app.
- Removed the global host selector/Test UI from
- Keep the per-host "Test" button in each host row (already exists).
- Verified
SettingsScreenstill renders the per-host Test CTA inside eachDaemonCard, confirmed the button invokeshandleTestDaemonConnection, and rannpm run typecheck --workspace=@paseo/app.
- Verified
4. Transparent Connection Management
- When a host is added, the app auto-connects and keeps the connection alive.
- Added an AppState-aware reconnect path in
useWebSocketso every background session automatically reconnects when the app becomes active again, keeping newly added hosts online without manual intervention; verified withnpm run typecheck --workspace=@paseo/app.
- Added an AppState-aware reconnect path in
- Users should never need to manually "connect" to a host—the app handles it.
- Reworded realtime, settings restart, and agent creation/import flows so they explain hosts reconnect automatically instead of asking users to connect manually; verified with
npm run typecheck --workspace=@paseo/app.
- Reworded realtime, settings restart, and agent creation/import flows so they explain hosts reconnect automatically instead of asking users to connect manually; verified with
- Remove any UI that asks users to "connect" before performing actions.
- Updated the host-unavailable alert, agent-not-found message, and settings restart failure copy to reassure that hosts reconnect automatically instead of asking users to connect manually; verified with
npm run typecheck --workspace=@paseo/app.
- Updated the host-unavailable alert, agent-not-found message, and settings restart failure copy to reassure that hosts reconnect automatically instead of asking users to connect manually; verified with
5. Remove Host Status from Home Screen
- Remove connection status banners/indicators from the home screen—settings is the place to check host health.
- Context (review):
packages/app/src/app/index.tsx:47-144still rendersconnectionBannercards that surface per-host offline/error states directly on Home, so host health is still exposed outside Settings. - Review (2025-11-26): The
connectionIssuesfilter includesoffline,connecting, anderrorstatuses, so any host that isn'tonlineshows up in the banner. This contradicts the guiding principle that a stopped host is not an error. - Removed the connection status banner, associated state (
connectionIssues,statusColors), styles, and unused imports (useDaemonConnections,formatConnectionStatus,getConnectionStatusTone,Text,useUnistyles) frompackages/app/src/app/index.tsx; verified withnpm run typecheck --workspace=@paseo/app.
- Context (review):
6. Fix Error Philosophy
- A stopped/disconnected host is NOT an error—don't show error states on home screen just because a host is offline.
- Context (review): The home connection banner still prints destructive red
connectionErrortext for every offline host entry, so the screen treats normal downtime as an error (packages/app/src/app/index.tsx:123-140). - Review (2025-11-26): The
connectionErrorstyle unconditionally usestheme.colors.destructive(line 210-213) forlastErrormessages, even thoughgetConnectionStatusTonecorrectly returnswarning(amber) forofflinestatus. The dot color respects the tone, but the error text does not. - Review (2025-11-26, follow-up): N/A—Task 5 removed the connection banner entirely, so no error styling appears on the home screen now.
- Context (review): The home connection banner still prints destructive red
- Only show errors when the user tries to interact with an agent whose host is stopped.
- Review (2025-11-26): Verified in
agent-list.tsx:148-163—the action sheet shows a neutral "offline" message and disables the delete button; no error styling is used.
- Review (2025-11-26): Verified in
- Update connection banners/indicators to show neutral "offline" state instead of error styling.
- Review (2025-11-26): N/A—connection banners no longer exist on the home screen (removed in Task 5). Settings still shows host status but that's expected (settings is the place to check host health per the guiding principles).
- Make the Git Diff offline/unavailable state neutral and stop instructing users to "connect" manually.
- Context (update):
SessionUnavailableStateinpackages/app/src/app/git-diff.tsxnow uses neutral copy/styling that reassures users we auto-reconnect; verified withnpm run typecheck --workspace=@paseo/app.
- Context (update):
- Update the File Explorer offline state to match the new philosophy (neutral messaging, no manual connect CTA).
- Context (update):
FileExplorerSessionUnavailablenow shows the passive offline message with auto-reconnect guidance instead of destructive styles (packages/app/src/app/file-explorer.tsx).
- Context (update):
- Audit the agent detail flows for the same issue (offline agent screen + delete sheet) and replace the "connect this host" requirement with passive/offline messaging.
- Context (update):
AgentSessionUnavailableStateand the delete sheet subtitle inpackages/app/src/components/agent-list.tsxnow explain that offline hosts reconnect automatically, removing the manual "connect" instruction.
- Context (update):
7. Agent Creation Flow
- Remove reliance on "primary" or "active" daemon for agent creation.
- Review (2025-11-26): Verified that
create-agent-modal.tsxno longer uses or falls back to any "active" or "primary" daemon; the modal requires explicit host selection and shows "Select a host before creating or importing agents" when none is chosen.
- Review (2025-11-26): Verified that
- Require explicit host selection when creating an agent—user must choose where to deploy.
- Review (2025-11-26): The
selectedServerIdstate controls host selection, anddaemonAvailabilityErrorblocks creation until a host is selected (packages/app/src/components/create-agent-modal.tsx:336-342).
- Review (2025-11-26): The
- Fix the contradictory error message "Daemon is online, connect to it before creating"—this should never appear.
- Review (2025-11-26): Searched the codebase for this message pattern—no matches found. The message has been removed or never existed in the current code.
8. Fix Create Agent Modal Availability Check
- The create modal shows "[Host Name] is offline. We'll reconnect automatically..." even when the host is online.
- "Primary Daemon" is the host's label, not app copy—the availability check itself is broken.
- Both hosts are online but the modal thinks they're offline and blocks creation.
- Review (2025-11-26): Root cause identified at
packages/app/src/components/create-agent-modal.tsx:333-334. The check!session || selectedDaemonStatus !== "online" || !ws?.isConnectedhas a timing issue:sessioncomes fromuseSessionForServer(selectedServerId)which reads fromsessionAccessorssessionAccessorsis populated viaregisterSessionAccessorin auseEffectinsideSessionProvider- There's a timing window where
connectionStatesshows "online" butsessionis still null (SessionProvider hasn't run its registration effect yet) - Additionally, checking both
selectedDaemonStatus !== "online"AND!ws?.isConnectedis redundant sinceconnectionStatesstatus is derived fromwsinSessionProvider
- Fix: Use
connectionStatesas the single source of truth for availability. Replace the check withselectedDaemonStatus !== "online"without requiringsessionto exist for availability purposes. - Completed: Changed
selectedDaemonIsUnavailabletoselectedDaemonIsOfflinewhich only checksselectedDaemonStatus !== "online". Thesessionandws?.isConnectedchecks were removed from the availability condition sinceconnectionStatesalready reflects the true connection state. TheisTargetDaemonReadycheck still requiressessionfor actual operations (creating/resuming agents). Verified withnpm run typecheck --workspace=@paseo/app.
9. Home Screen Agent List
- Show a loading indicator while agents are being fetched (not while waiting for hosts to connect).
- Don't block the home screen for offline hosts.
- Handle edge cases: no hosts configured, no hosts connected, partial host connectivity.
- Context (review):
HomeScreenstill flips directly to the "New/Import Agent" empty state wheneveraggregatedAgentsis empty, so offline hosts with agents appear as if there are zero agents and there's no neutral loading indicator (packages/app/src/app/index.tsx:109-116). - Review (2025-11-26): This task is still open. The home screen shows an empty state immediately if
aggregatedCount === 0, with no distinction between "loading" and "truly empty." Offline hosts that may have agents will show zero agents until they reconnect. - Completed: Updated
useAggregatedAgentsto return{ groups, isLoading }whereisLoadingis true while the daemon registry loads or while hosts areconnectingwithout a session yet. UpdatedHomeScreento show anActivityIndicatorduring loading, then either the agent list or empty state. Offline hosts don't block loading—onlyconnectinghosts do.
- Remove grouping of agents by host—show a single flat list.
- Context (review):
packages/app/src/components/agent-list.tsx:67-131still mapsagentGroupsinto host-specific sections with headers, so grouping hasn't been removed. - Review (2025-11-26): Still open. The
AgentListcomponent iterates overagentGroupsand renders a section header (sectionLabel) per host. - Each agent row displays its host name as metadata (badge, subtitle, etc.).
- Context (review): Agent rows only render cwd/provider/status/time (
packages/app/src/components/agent-list.tsx:87-125), so there's no host metadata visible per row yet. - Review (2025-11-26): Still open. To flatten the list, host metadata must move into each row since section headers will be removed.
- Context (review): Agent rows only render cwd/provider/status/time (
- Sort agents by recent activity or alphabetically (not by host).
- Context (review):
packages/app/src/hooks/use-aggregated-agents.ts:25-66sorts sections by host registration order and only orders agents within a host, so we still bias the list ordering by host rather than recency across all agents. - Review (2025-11-26): Still open. The current
useAggregatedAgentshook returns grouped data; it needs refactoring to return a flat, globally-sorted array.
- Context (review):
- Context (review):
9. Review: Git Diff Metadata Cleanup
- Remove the now-unused
routeServerIdprop fromGitDiffContent(packages/app/src/app/git-diff.tsx:84-104) so we aren't plumbing dead state through the component after switching toserverLabel.- Context: Deleted the redundant prop/const and updated
GitDiffContentto rely solely onserverLabel, removing the final host-id plumbing that was no longer used anywhere in the component (packages/app/src/app/git-diff.tsx).
- Context: Deleted the redundant prop/const and updated
Review Summary (2025-11-26)
Completed:
- Tasks 1–7 are complete
- Task 9 loading indicator is complete
- Internal code correctly uses "daemon" terminology while UI says "host"
- Connection banners removed from home screen
- Error philosophy fixed for Git Diff, File Explorer, and agent action sheets
- Settings screen retains host status indicators (correct per guiding principles)
- Typecheck passes
Remaining Work:
Task 8: Create Agent Modal Availability Check
The modal incorrectly shows hosts as offline when they're actually online. Root cause is a timing issue where session is null during the brief window between connectionStates updating and SessionProvider registering its session accessor.
Fix required in packages/app/src/components/create-agent-modal.tsx:
- Line 333-334: Change
selectedDaemonIsUnavailableto only checkselectedDaemonStatus !== "online" - Don't require
sessionto exist for the availability check—sessionis only needed for actual operations - The availability UX should use
connectionStatesas single source of truth
Task 9: Home Screen Agent List (partial)
The loading indicator is complete. Still need:
- Flat list:
AgentListstill renders sections with host headers viaagentGroups.map() - Host metadata per row: Agent rows don't show which host they belong to (required once sections are removed)
- Global sorting: Agents are still grouped by host order; need to flatten and sort by recency across all hosts
Files to modify for Task 9:
packages/app/src/hooks/use-aggregated-agents.ts— return flat array sorted by activitypackages/app/src/components/agent-list.tsx— remove section headers, add host badge to rows