fix(desktop): local-first project icons

Merged
GI
3mo ago
+1442-419

Problem

The v2 project icon picker (IconUploadField) called the cloud v2Project.uploadIcon/resetIconToGitHub/removeIcon mutations, which are keyed by v2Projects.id. But v2 projects are host-local (useHostProjects fan-out over client.project.list) — their id has no cloud v2Projects row except for legacy cloud-created projects. So setting an icon failed with "Project not found" on every local-first project (the common case), and even on the projects where it "worked" the uploaded icon was never displayed (the v2 UI derived the icon from repoOwner). That's the #5843 regression.

CDP-confirmed before the fix: upload → Project not found on a local-first project; success only on a legacy cloud-linked project.

Fix — move icons to the local-first model

Project icons now work the same way name, branch prefix, and worktree location already do: host-service local storage.

Host-service

  • Add an icon column to the local projects table (migration 0012_dear_morph.sql, generated via drizzle-kit).
  • New project.setIcon mutation (mirrors setBranchPrefix), writing through updateLocalProject so it bumps updatedAt and broadcasts project:changed. Input validated as an image data-URI, capped at 256KB.
  • icon threaded through project.list/get, toProjectSnapshot, and the ProjectSnapshot event type.

Renderer

  • useHostProjects merge layer carries icon (local-host-wins).
  • V2ProjectSettings resolves the icon as custom → GitHub avatar → placeholder, so an uploaded icon shows immediately in both the header and the picker.
  • IconUploadField rewritten to call the host client project.setIcon, downscale the chosen image to a 128×128 PNG data-URI client-side (no cloud blob), and offer Upload image… / Reset to default.

Cloud teardown

  • Deleted the orphaned cloud uploadIcon/resetIconToGitHub/removeIcon procs + their tests. The renderer now has zero v2Project.* calls. (The v2Projects.iconUrl column is left in place; dropping it belongs to the broader teardown — see plans/remove-cloud-project-model.md.)

Verification

  • typecheck clean (host-service, trpc, desktop); biome clean.
  • Tests: host-service integration 9 pass (2 new setIcon tests, mutation-verified to fail without the fix); trpc v2-project 18 pass; V2ProjectSettings 1 pass.
  • CDP end-to-end on the local-first superset project that returned "Project not found" before: upload → icon persists as data:image/png and displays; Reset to default → reverts to the GitHub avatar. No more "Project not found".

Context

This supersedes the original cloud-based wiring on this branch. It's Phase A of a broader effort to stop the local-first desktop app from querying the cloud for app data (chat, tasks, automations, secrets) — scoped in plans/remove-cloud-project-model.md.

Closes #5843