feat(desktop,host-service): v2 project colors — icon+color picker and v1 import carry-over
Links
- Linear: SUPER-1779 (Laurent Perrier's V2 feedback)
Summary
- Projects get a host-local accent
color(new column +project.setColor), shown as the v1-style tinted border/letter on v2 project thumbnails in settings and the dashboard sidebar. - The V2 project settings "Icon" field is now a simple icon+color picker: color swatches plus a 12-glyph grid, alongside the existing upload/reset.
- V1 → V2 import carries the project's accent color over (both the auto-migrator and the import wizard).
Why / Context
Laurent's V2 feedback: color-coded project icons are how users orient across many projects, and V1 colors were lost on import. His third item — "Remove icon" throwing No procedure found on path v2/project/removeIcon — was already fixed at the source by #5845 (the dead cloud icon machinery was deleted); no call sites remain and reset goes through project.setIcon(null). This PR covers the two remaining items.
How It Works
- Host-service:
projects.colorcolumn (drizzle-kit migration0015, applies on host boot), threaded throughProjectSnapshot,project.list/project.get, and theproject:changedevent. Newproject.setColormutation mirrorssetIcon(validates#rrggbb, null clears). - Renderer data layer:
useHostProjectsrows/merge and the dashboard sidebar tree carrycolor; older hosts and stale IndexedDB snapshots normalize tonull. - Picker:
IconUploadFieldbecomes a popover withColorSelectorswatches (extended with an opt-in Default option) and a lucide glyph grid. Glyphs are baked client-side into the same 128px PNG data-URI the upload path uses, so they flow throughresolveProjectIconUrland appear on every surface with zero resolver changes. Picking a color re-bakes a glyph chosen in the same popover session. - Migration:
importV1Projectbest-effort callssetColorafter a successful import; the v1"default"sentinel stays unset, and a failure logs without failing the import.
Manual QA Checklist
Icon + color picker (v2 project settings)
- Thumbnail opens the popover; color swatch persists and tints the header thumbnail + sidebar rows
- Glyph pick bakes a tinted tile; picking a color afterwards (same popover session) re-tints the glyph
- Upload image and Reset icon still work; Default swatch clears the color
- Offline/remote host shows the offline toast instead of crashing
V1 → V2 import
- Importing a v1 project with a custom color shows that color on the v2 project
- v1 projects with the "default" color import with no accent
Migration safety
- Host DB migration 0015 applies on an existing host.db (additive
ALTER TABLE ... ADD color text)
Testing
bun run typecheck— pass (all packages)bun run lint— cleanapps/desktopbun test— 2541 pass / 0 fail (includes new color carry-over test inrunV1Migration.test.ts)packages/host-servicebun test— 997 pass / 0 fail- Not manually QA'd in a running app — implemented and verified via tests/typecheck only; the QA checklist above is for reviewer validation.
Design Decisions
- Bake glyphs into PNG data-URIs instead of a new icon scheme: reuses the entire existing icon pipeline (
setIcon,resolveProjectIconUrl, every thumbnail surface) with no resolver or protocol changes. Trade-off: a baked glyph can't re-tint retroactively, so only a glyph picked in the same popover session follows a color change. - Color as a separate column, not baked into the icon: matches v1 semantics (tinted border works with uploaded images and GitHub avatars too) and is what the migration needs to carry v1 colors that have no glyph.
Known Limitations
- Picking a color against a host running an older host-service (no
setColor) surfaces "No procedure found" as a toast — same version-skew behavior as other new host procedures. - V1 project ordering is not carried over: v2 sidebar order lives in a renderer-local collection with its own tabOrder semantics, so it isn't a cheap add. Deferred.
Compatibility Matrix
- New desktop renderer + old host: color reads normalize to null (no accent); color writes toast a version error.
- Old desktop renderer + new host: extra
colorfield in list/get/events is ignored.
Summary by cubic
Add host-local project accent colors to v2 and let users remove the project icon. Ships an icon+color picker with tinted thumbnails/sidebar, carries v1 color and hide-image, and fixes picker edge cases (glyphs re-bake only after color saves, swatches disable while saving, strict #rrggbb parsing) (Linear SUPER-1779).
-
New Features
host-service: addprojects.colorandproject.setColor; thread through list/get/events.project.setIconnow accepts a"none"sentinel to force the letter tile (no avatar fallback).- Project settings: the "Icon" popover has color swatches (Default), a 12-glyph grid baked into PNG data-URIs, Upload, Remove icon, and Reset. Swatches disable during saves; a glyph re-bakes only after the new color persists.
- Thumbnails and the dashboard sidebar use the accent color (tinted border/letter), including the letter fallback when the icon is removed. Strict
#rrggbbparsing avoids malformed tints.
-
Migration
- DB migration
0016addsprojects.color(additive; applies on host boot). - V1 → V2 import copies real hex colors and maps
hide_imageto the"none"icon; the v1 "default" color sentinel stays unset; failures don’t block import. - Mixed versions: color writes and Remove icon against an old host show a version toast; old desktop ignores the extra color field.
- DB migration
Summary by CodeRabbit
- New Features
- Added customizable project accent colors across project thumbnails, sidebars, and settings.
- Added color selection alongside icon uploads, Lucide icons, icon removal, and reset-to-default controls.
- Projects can now explicitly hide icons instead of displaying a fallback avatar.
- Existing project colors and icon visibility are preserved during V1 imports.
- Color selectors and icon controls support disabled states and clearer default styling.