feat(desktop,host-service): local-first projects — host.db owns projects, workspace cloud sync retired
Merged
KI
4mo ago+2588+2588+0123456789012345678901234567890123456789-1537-1537-0123456789012345678901234567890123456789
Links
- Design doc:
plans/20260716-local-first-projects.md(in this PR, includes the decision log)
Summary
- v2 goes fully local. Projects live in host.db only (
name+updatedAtcolumns, migration 0010): create, rename, and delete commit locally with zero cloud involvement, so they work fully offline. #5649's silentmy-app-2duplicates die structurally — nothing ever walks a cloud slug. - Workspace cloud dual-write retires (decision 5 in the doc):
workspace-cloud-sync.ts, tombstones, re-keying, and the reconciler are deleted;ensureHostRegisteredwas folded away (host registration viatunnel/connect.tsis untouched — relay access keeps working). - Desktop reads projects from a host fan-out (
useHostProjects: per-hostproject.list+project:changedlive events + IndexedDB last-seen snapshots, workspace-pattern settings). Sidebar, project settings, and the new-workspace modal are migrated off Electricv2Projects.
How It Works
- Host
project.list/updateserve full identity rows;project:changedbroadcasts on the existing event bus (newworkspace-clientpayload type). A startup backfill fillsnamefrom the legacy cloud row when reachable, else the folder basename — never blocks boot. project.setupgets a local-first fast path: an existing local row completes without any cloud call; the legacy cloud lookup only runs when adopting an old cloud-created project onto a new device.findByPathis local-only (a hit = this host already has it; no hit = create fresh).project.removeflips its kill point: local deletes always run (offline-capable); the legacy cloud delete is best-effort cleanup for the mixed-version window.- Renames commit through the host everywhere (settings NameSection, sidebar inline rename) — the optimistic cloud rename is gone.
Manual QA — CDP-verified against the live dev app
Fresh dev host.db with 29 legacy projects; drills driven over CDP:
- Migration 0010 + name backfill on boot: 28 rows filled from legacy cloud names; a transient-error row correctly kept its empty-name sentinel and filled on the next boot (retry-next-boot rule proven)
- Create from folder: 171 ms local commit while fully online; cloud
v2Project.getfor the new id returns NOT_FOUND (no cloud row minted) - Full offline lifecycle with the API server SIGSTOP'd +
navigator.onLine=false: create 179 ms, rename 18 ms, workspace+worktree create 379 ms, sidebar auto-updated in 101 ms — nothing hung, proving no hidden cloud awaits anywhere in the path - Rename →
project:changed→ sidebar DOM update in 130 ms (no refetch) - Settings: project list + detail render from host fan-out; NameSection rename through the real UI commits to host.db; repository field read-only ("No git remote detected")
- Delete ×3: ~100 ms each, local rows + sidebar cleared; best-effort legacy cloud delete correctly logged "failed; continuing locally"
- Zero
cloud push failed/ reconciler lines across the entire session log
Testing
bun testinpackages/host-service— 885 tests green (3 tests asserting the retired cloud behavior updated to the new local contracts)bunx tsc --noEmitinpackages/host-service,packages/workspace-client,apps/desktop;bun run lintclean
Known Limitations / conscious trade-offs
- Mobile loses workspace visibility until the future cloud layer (team cloud projects w/ sandboxes) returns — accepted in the decision walkthrough.
- ~15 secondary desktop surfaces (task filters, command palette, history) still read the frozen Electric
v2Projectscollection; migrating them + the Electricv2Workspacesmerge source is follow-up work before the cloud tables retire. - Custom project icons (cloud
uploadIcon) are retired with the cloud row; thumbnails derive from the GitHub owner avatar. Repository URL in settings is read-only (derived from the git remote).
Rollout
- Desktop-only in substance; no cloud/API deploy needed (cloud endpoints stay up for old clients). Nothing destructive; host sqlite migration 0010 is additive.
Summary by CodeRabbit
-
New Features
- Projects are now managed locally on the host (create, update, rename, delete).
- The project list aggregates results from available hosts, uses cached snapshots when hosts are unreachable, and stays in sync across devices in real time.
- Deleting a project can be applied across reachable hosts, with warnings for unreachable ones.
- Project settings now source and display host-based project/repo details.
-
Bug Fixes
- Improved readiness gating to avoid flashing incomplete project settings.
- Added one-time backfill for existing project names and timestamps.