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
@@ -56,6 +56,7 @@ function projectDescriptorFromTestWorkspace(workspace: WorkspaceDescriptor): Pro
projectKey: workspace.projectId,
projectDisplayName: workspace.projectDisplayName,
projectCustomName: workspace.projectCustomName ?? null,
projectCustomIconRevision: workspace.projectCustomIconRevision ?? null,
projectRootPath: workspace.projectRootPath,
projectKind: workspace.projectKind,
};
@@ -348,6 +349,7 @@ describe("workspace structure composition", () => {
projectKey: "project-a",
projectDisplayName: "Project A",
projectCustomName: null,
projectCustomIconRevision: null,
projectRootPath: "/repo/a",
projectKind: "git",
};
+4
View File
@@ -129,6 +129,7 @@ export interface WorkspaceDescriptor {
projectId: string;
projectDisplayName: string;
projectCustomName?: string | null;
projectCustomIconRevision?: string | null;
projectRootPath: string;
workspaceDirectory: string;
projectKind: WorkspaceDescriptorPayload["projectKind"];
@@ -160,6 +161,7 @@ export function normalizeWorkspaceDescriptor(
projectId: payload.projectId,
projectDisplayName: payload.projectDisplayName,
projectCustomName: payload.projectCustomName ?? null,
projectCustomIconRevision: payload.projectCustomIconRevision ?? null,
projectRootPath: payload.projectRootPath,
// Canonicalize the workspace directory once, at the store boundary, so every
// consumer can read workspace.workspaceDirectory directly. Empty means "no
@@ -187,6 +189,7 @@ export interface ProjectDescriptor {
projectKey?: string | null;
projectDisplayName: string;
projectCustomName: string | null;
projectCustomIconRevision?: string | null;
projectRootPath: string;
projectKind: WorkspaceDescriptorPayload["projectKind"];
}
@@ -199,6 +202,7 @@ export function normalizeProjectDescriptor(
projectKey: payload.projectKey ?? null,
projectDisplayName: payload.projectDisplayName,
projectCustomName: payload.projectCustomName ?? null,
projectCustomIconRevision: payload.projectCustomIconRevision ?? null,
projectRootPath: payload.projectRootPath,
projectKind: payload.projectKind,
};