Skip to content

Commit a4d1824

Browse files
committed
fix(app): no more favicons
1 parent cac35bc commit a4d1824

6 files changed

Lines changed: 51 additions & 24 deletions

File tree

packages/app/src/components/dialog-edit-project.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
2222
const [store, setStore] = createStore({
2323
name: defaultName(),
2424
color: props.project.icon?.color || "pink",
25-
iconUrl: props.project.icon?.url || "",
25+
iconUrl: props.project.icon?.override || "",
2626
saving: false,
2727
})
2828

@@ -74,7 +74,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
7474
await globalSDK.client.project.update({
7575
projectID: props.project.id,
7676
name,
77-
icon: { color: store.color, url: store.iconUrl },
77+
icon: { color: store.color, override: store.iconUrl },
7878
})
7979
setStore("saving", false)
8080
dialog.close()

packages/app/src/context/layout.tsx

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
208208
})
209209
})
210210

211-
const usedColors = new Set<AvatarColorKey>()
211+
const [colors, setColors] = createStore<Record<string, AvatarColorKey>>({})
212212

213-
function pickAvailableColor(): AvatarColorKey {
214-
const available = AVATAR_COLOR_KEYS.filter((c) => !usedColors.has(c))
213+
function pickAvailableColor(used: Set<string>): AvatarColorKey {
214+
const available = AVATAR_COLOR_KEYS.filter((c) => !used.has(c))
215215
if (available.length === 0) return AVATAR_COLOR_KEYS[Math.floor(Math.random() * AVATAR_COLOR_KEYS.length)]
216216
return available[Math.floor(Math.random() * available.length)]
217217
}
@@ -222,24 +222,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
222222
const metadata = projectID
223223
? globalSync.data.project.find((x) => x.id === projectID)
224224
: globalSync.data.project.find((x) => x.worktree === project.worktree)
225-
return [
226-
{
227-
...(metadata ?? {}),
228-
...project,
229-
icon: { url: metadata?.icon?.url, color: metadata?.icon?.color },
225+
return {
226+
...(metadata ?? {}),
227+
...project,
228+
icon: {
229+
url: metadata?.icon?.url,
230+
override: metadata?.icon?.override,
231+
color: metadata?.icon?.color,
230232
},
231-
]
232-
}
233-
234-
function colorize(project: LocalProject) {
235-
if (project.icon?.color) return project
236-
const color = pickAvailableColor()
237-
usedColors.add(color)
238-
project.icon = { ...project.icon, color }
239-
if (project.id) {
240-
globalSdk.client.project.update({ projectID: project.id, icon: { color } })
241233
}
242-
return project
243234
}
244235

245236
const roots = createMemo(() => {
@@ -277,8 +268,37 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
277268
})
278269
})
279270

280-
const enriched = createMemo(() => server.projects.list().flatMap(enrich))
281-
const list = createMemo(() => enriched().flatMap(colorize))
271+
const enriched = createMemo(() => server.projects.list().map(enrich))
272+
const list = createMemo(() => {
273+
const projects = enriched()
274+
return projects.map((project) => {
275+
const color = project.icon?.color ?? colors[project.worktree]
276+
if (!color) return project
277+
const icon = project.icon ? { ...project.icon, color } : { color }
278+
return { ...project, icon }
279+
})
280+
})
281+
282+
createEffect(() => {
283+
const projects = enriched()
284+
if (projects.length === 0) return
285+
286+
const used = new Set<string>()
287+
for (const project of projects) {
288+
const color = project.icon?.color ?? colors[project.worktree]
289+
if (color) used.add(color)
290+
}
291+
292+
for (const project of projects) {
293+
if (project.icon?.color) continue
294+
if (colors[project.worktree]) continue
295+
const color = pickAvailableColor(used)
296+
used.add(color)
297+
setColors(project.worktree, color)
298+
if (!project.id) continue
299+
void globalSdk.client.project.update({ projectID: project.id, icon: { color } })
300+
}
301+
})
282302

283303
onMount(() => {
284304
Promise.all(

packages/app/src/pages/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,7 @@ export default function Layout(props: ParentProps) {
12841284
<div class="size-full rounded overflow-clip">
12851285
<Avatar
12861286
fallback={name()}
1287-
src={props.project.id === opencode ? "https://opencode.ai/favicon-v2.svg" : props.project.icon?.url}
1287+
src={props.project.id === opencode ? "https://opencode.ai/favicon-v2.svg" : props.project.icon?.override}
12881288
{...getAvatarColors(props.project.icon?.color)}
12891289
class="size-full rounded"
12901290
style={

packages/opencode/src/project/project.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export namespace Project {
2525
icon: z
2626
.object({
2727
url: z.string().optional(),
28+
override: z.string().optional(),
2829
color: z.string().optional(),
2930
})
3031
.optional(),
@@ -190,6 +191,7 @@ export namespace Project {
190191
if (!existing.sandboxes) existing.sandboxes = []
191192

192193
if (Flag.OPENCODE_EXPERIMENTAL_ICON_DISCOVERY) discover(existing)
194+
193195
const result: Info = {
194196
...existing,
195197
worktree,
@@ -213,6 +215,7 @@ export namespace Project {
213215

214216
export async function discover(input: Info) {
215217
if (input.vcs !== "git") return
218+
if (input.icon?.override) return
216219
if (input.icon?.url) return
217220
const glob = new Bun.Glob("**/{favicon}.{ico,png,svg,jpg,jpeg,webp}")
218221
const matches = await Array.fromAsync(
@@ -293,6 +296,7 @@ export namespace Project {
293296
...draft.icon,
294297
}
295298
if (input.icon.url !== undefined) draft.icon.url = input.icon.url
299+
if (input.icon.override !== undefined) draft.icon.override = input.icon.override || undefined
296300
if (input.icon.color !== undefined) draft.icon.color = input.icon.color
297301
}
298302
draft.time.updated = Date.now()

packages/sdk/js/src/v2/gen/sdk.gen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ export class Project extends HeyApiClient {
302302
name?: string
303303
icon?: {
304304
url?: string
305+
override?: string
305306
color?: string
306307
}
307308
},

packages/sdk/js/src/v2/gen/types.gen.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type Project = {
2525
name?: string
2626
icon?: {
2727
url?: string
28+
override?: string
2829
color?: string
2930
}
3031
time: {
@@ -2229,6 +2230,7 @@ export type ProjectUpdateData = {
22292230
name?: string
22302231
icon?: {
22312232
url?: string
2233+
override?: string
22322234
color?: string
22332235
}
22342236
}

0 commit comments

Comments
 (0)