/** * RemoteProjects (Batch 9) — projects stored on your other computers. * * Lists each online node's projects (mirror `projects.list`) with a * "Stored on Studio PC" badge, honest offline state (Batch 6), and an Open * action that selects that computer so chat runs on it (Batch 5). Read-only * over existing whitelisted RPC — NO project data is copied to the cloud. * * ADDITIVE and flag-gated: renders null when the Account & Computers flag is * off, so the Projects page is unchanged by default. */ import React, { useEffect, useMemo, useState } from 'react' import { FolderKanban, RefreshCw } from 'lucide-react' import { isAccountsUxEnabled } from './featureFlags' import { useAccount } from './HomePilotAccountProvider' import { useComputer } from './ComputerContext' import { MirrorError, mirrorClient } from './mirrorClient' interface RemoteProject { id?: string; name?: string; project_type?: string } interface Row { hpuri: string; nodeId: string; nodeName: string; online: boolean; id: string; name: string; type: string } export function RemoteProjects(): JSX.Element | null { if (!isAccountsUxEnabled()) return null return } function RemoteProjectsInner(): JSX.Element | null { const { computers, loading, refresh } = useAccount() const { selectComputer } = useComputer() const [byNode, setByNode] = useState>({}) useEffect(() => { let cancelled = false computers.filter((c) => c.online && byNode[c.node_id] === undefined).forEach(async (c) => { try { const list = await mirrorClient.rpc(c.node_id, 'projects.list', {}) if (!cancelled) setByNode((p) => ({ ...p, [c.node_id]: Array.isArray(list) ? list : [] })) } catch (e) { if (!(e instanceof MirrorError && e.isNodeOffline) && !cancelled) { setByNode((p) => ({ ...p, [c.node_id]: null })) } } }) return () => { cancelled = true } }, [computers, byNode]) const rows = useMemo(() => { const out: Row[] = [] for (const c of computers) { for (const p of byNode[c.node_id] || []) { if (!p.id) continue out.push({ hpuri: `hpnode://${c.node_id}/project/${p.id}`, nodeId: c.node_id, nodeName: c.node_name || c.node_id, online: c.online, id: p.id, name: p.name || p.id, type: p.project_type || 'project', }) } } return out }, [computers, byNode]) const openRemote = (r: Row) => { // Open = run on the owning computer. Select it (Batch-5 routes chat there) // and remember the project context. No cloud copy is made. selectComputer(r.nodeId) try { localStorage.setItem('homepilot_current_project', r.id) } catch { /* ignore */ } window.dispatchEvent(new CustomEvent('homepilot:open-remote-project', { detail: { nodeId: r.nodeId, projectId: r.id }, })) } // Nothing to show unless at least one computer exists. if (computers.length === 0) return null return (

Projects on your computers

{/* Offline computers: honest state, never a silent downgrade. */} {computers.filter((c) => !c.online).map((c) => (
{c.node_name || c.node_id} is offline — its projects are available when you turn it on.
))}
{rows.map((r) => ( ))}
{rows.length === 0 && (
{loading ? 'Loading…' : 'No projects found on your online computers.'}
)}
) }