feat: add host-local custom project icons (#2335)

* feat(projects): add local icons and colors

* refactor(projects): simplify appearance handling

* fix(projects): serialize appearance saves

* test(app): update project appearance expectations

* test: fix project appearance CI expectations

* refactor(projects): organize appearance code

* fix(server): adopt main's reconcile model in project appearance updates

* fix(projects): preserve host-local appearance targets

* fix(desktop): restore Linux development startup

Ubuntu can block Electron's unprivileged user-namespace fallback while the npm-installed sandbox helper is not SUID. Disable the Chromium process sandbox only in the Linux development runner so packaged desktop policy remains unchanged.

* feat(projects): add custom project icons

Keep URL acquisition in the client so the host daemon only validates uploaded bytes. Project name and icon changes share one adaptive edit sheet.

* refactor(settings): scope projects by host

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
nikuscs
2026-08-01 01:34:42 +08:00
committed by GitHub
co-authored by Mohamed Boudra
parent 706ae3d9bb
commit 800bd2bef1
67 changed files with 2158 additions and 912 deletions
+24
View File
@@ -62,7 +62,9 @@ import type {
DirectorySuggestionsResponse,
PaseoWorktreeListResponse,
PaseoWorktreeArchiveResponse,
ProjectIconSource,
ProjectIconResponse,
ProjectIconGetResponse,
ProjectAddResponse,
ProjectCreateDirectoryResponse,
OpenProjectResponseMessage,
@@ -2527,6 +2529,18 @@ export class DaemonClient {
return { customName: payload.customName };
}
async setProjectIcon(
projectId: string,
source: ProjectIconSource,
requestId?: string,
): Promise<void> {
const payload = await this.sendNamespacedCorrelatedSessionRequest<"project.icon.set.response">({
requestId,
message: { type: "project.icon.set.request", projectId, source },
});
if (!payload.accepted) throw new Error(payload.error ?? "setProjectIcon rejected");
}
async removeProject(
projectId: string,
requestId?: string,
@@ -4296,6 +4310,16 @@ export class DaemonClient {
});
}
async getProjectIcon(
projectId: string,
requestId?: string,
): Promise<ProjectIconGetResponse["payload"]> {
return this.sendNamespacedCorrelatedSessionRequest<"project.icon.get.response">({
requestId,
message: { type: "project.icon.get.request", projectId },
});
}
// ============================================================================
// Provider Models / Commands
// ============================================================================