6.5 KiB
Desktop Startup Refactor Plan
Goal
- Tauri runs
paseo start, then pollspaseo daemon status --jsonuntil ready daemon status --jsonreturns ALL needed data: serverId, listen address, hostname, status, pid- App takes that data and adds it to the registry like any other host
- App blocks on startup splash until any host runtime goes online (race)
- 30s timeout → redirect to welcome/add-connection screen
Core Principles
- The daemon is autonomous. Tauri does not pass
--home,--listen, or read any files. daemon status --jsonis the single source of truth. All data comes from it.- The host registry has no concept of "managed" daemons. A host is just a serverId + connections.
Implementation Plan
Phase 1: Add serverId to daemon status --json
File: packages/cli/src/commands/daemon/status.ts
DaemonStatus currently has: status, home, listen, pid, startedAt, owner, logPath, etc.
Add:
serverId: string | nullhostname: string | null(already available from pidInfo, just not in the output)
The status command already calls resolveLocalDaemonState() which reads the pid file (has hostname) and resolves home. From home it can call getOrCreateServerId(home) to get the serverId. All internal to the status command — Tauri never touches these files.
Phase 2: Tauri — just start + poll status
File: packages/desktop/src-tauri/src/runtime_manager.rs
2a. start_managed_daemon_internal becomes:
- Run
paseo start(no--home, no--listen) - Poll
paseo daemon status --jsonin a loop untilstatus == "running"andserverIdis present - Return the parsed JSON to the app
2b. Remove all daemon plumbing from Rust
Delete:
managed_transport_target(),ManagedTransportTargetdefault_transport_path(),default_transport_type()default_managed_home()resolve_paths()/ManagedPathsread_server_id(),read_hostname_from_home(),read_pid_from_home()cli_env()— noPASEO_HOMEorPASEO_HOSTinjection- State file reading/writing for transport config
- All direct file I/O for daemon state
Keep:
bundled_runtime_root()— to find bundled Node.js + CLIcli_command()— to invoke the bundled CLIensure_runtime_ready_internal()— to verify runtime extraction
2c. ManagedDaemonStatus mirrors daemon status --json
serverId: stringstatus: stringlisten: stringhostname: string | nullpid: number | nullhome: string- Plus:
runtimeId,runtimeVersion(from bundled runtime manifest)
App parses listen to determine transport type:
\\.\pipe\*orpipe://→ pipe/orunix://→ sockethost:port→ tcp
Phase 3: App — remove "managed" concept from registry
File: packages/app/src/contexts/daemon-registry-context.tsx
3a. Delete managed-specific code
ManagedHostReconciliationInputDesktopStartupReconciliationInputreconcileManagedHostInProfiles()reconcileDesktopStartupRegistry()probeManagedConnectionUntilReady()probeManagedStartupTarget()resolveManagedDesktopStartupStatus()DEFAULT_LOCAL_TRANSPORT_BOOTSTRAP_*constants
3b. Remove managed from HostLifecycle
Remove managed, managedRuntimeId, managedRuntimeVersion, associatedServerId. Remove all code that checks lifecycle.managed.
3c. Desktop startup effect becomes:
const bootstrap = async () => {
const daemon = await startManagedDaemon()
if (cancelled || !daemon.serverId) return
const connection = connectionFromListen(daemon.listen)
if (!connection) return
await upsertHostConnection({
serverId: daemon.serverId,
label: daemon.hostname ?? undefined,
connection,
})
}
3d. Remove isReconciling
No longer needed. Registry update triggers MultiDaemonSessionHost → syncHosts() → HostRuntimeController connects.
Phase 4: Index screen — race until online
File: packages/app/src/app/index.tsx
- New hook:
useAnyHostOnline(serverIds)— subscribes toHostRuntimeStore - Show splash while: registry loading OR (hosts exist, none online, within 30s)
- Any host goes online → navigate to it
- 30s timeout → WelcomeScreen
Both managed daemon and localhost:6767 race. First to connect wins.
Phase 5: Localhost bootstrap
Non-desktop: keep the lightweight probe (2.5s, needs serverId from handshake). Desktop: localhost:6767 added as a race participant alongside the daemon connection.
Phase 6: Metro must ignore app test files
Files: packages/app/metro.config.js and any directly related Metro resolver helpers/tests
The app already contains many *.test.ts and *.test.tsx files under packages/app/src. Those files must remain in the repo and must NOT be deleted to make Metro happy.
Implement Metro-side protection so Expo/Metro does not try to bundle app test files from packages/app/src during normal app/web startup:
- Treat
*.test.ts,*.test.tsx,*.spec.ts, and*.spec.tsxunderpackages/app/srcas ignored/non-bundle inputs for Metro app resolution - If the current Metro resolver hook needs to special-case local imports or extensions, update it so test files are skipped cleanly instead of causing bundle resolution crashes
- Preserve Vitest usage for those files; the change is only about Metro bundling behavior, not removing or renaming tests
- If a test index/helper file is needed for app tests, keep it and make Metro ignore it rather than deleting it
What stays the same
HostRuntimeController,HostRuntimeStore,MultiDaemonSessionHost— no changesDaemonClienttransports — no changes- The daemon itself — no changes (already autonomous)
Acceptance Criteria
paseo daemon status --jsonreturnsserverId,listen,hostname,status, andpid, and desktop startup consumes that shape as the source of truth- The app registry no longer contains managed-daemon lifecycle concepts; desktop startup upserts the managed daemon like any other host
- Desktop startup blocks on the startup splash during the connection race and only leaves it when a host goes online or the 30s timeout expires
- Desktop still races the managed daemon and
localhost:6767; non-desktop keeps the lightweight localhost probe - Metro does not try to bundle
packages/app/src/**/*.test.*orpackages/app/src/**/*.spec.*during normal app startup - Existing app test files, including any
index.test.tshelper needed by tests, remain in the repo and are not deleted as a workaround npm run typecheckpasses