From 0ca758e13516d96180cc11631e02e5f7929ba4b0 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Thu, 11 Dec 2025 09:32:53 -0600
Subject: wip(desktop): progress

---
 packages/desktop/src/context/layout.tsx | 47 ++++++++++++++++++++-------------
 1 file changed, 29 insertions(+), 18 deletions(-)

(limited to 'packages/desktop/src/context/layout.tsx')

diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx
index 24ba55a53..ea5962b40 100644
--- a/packages/desktop/src/context/layout.tsx
+++ b/packages/desktop/src/context/layout.tsx
@@ -45,15 +45,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         name: "default-layout.v7",
       },
     )
-    const [ephemeral, setEphemeral] = createStore({
+    const [ephemeral, setEphemeral] = createStore<{
       connect: {
-        provider: undefined as undefined | string,
-        state: undefined as undefined | "pending" | "complete" | "error",
-        error: undefined as undefined | string,
-      },
+        provider?: string
+        state?: "pending" | "complete" | "error"
+        error?: string
+      }
       dialog: {
-        open: undefined as undefined | Dialog,
-      },
+        open?: Dialog
+      }
+    }>({
+      connect: {},
+      dialog: {},
     })
     const usedColors = new Set<string>()
 
@@ -177,22 +180,30 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       dialog: {
         opened: createMemo(() => ephemeral.dialog?.open),
         open(dialog: Dialog) {
-          setEphemeral("dialog", "open", dialog)
-          if (dialog !== "connect") {
-            setEphemeral("connect", {})
-          }
+          batch(() => {
+            // if (dialog !== "connect") {
+            //   setEphemeral("connect", {})
+            // }
+            setEphemeral("dialog", "open", dialog)
+          })
         },
         close(dialog: Dialog) {
-          if (ephemeral.dialog?.open === dialog) {
-            setEphemeral("dialog", "open", undefined)
-            setEphemeral("connect", {})
+          if (ephemeral.dialog.open === dialog) {
+            setEphemeral(
+              produce((state) => {
+                state.dialog.open = undefined
+                state.connect = {}
+              }),
+            )
           }
         },
         connect(provider: string) {
-          batch(() => {
-            setEphemeral("dialog", "open", "connect")
-            setEphemeral("connect", { provider, state: "pending" })
-          })
+          setEphemeral(
+            produce((state) => {
+              state.dialog.open = "connect"
+              state.connect = { provider, state: "pending" }
+            }),
+          )
         },
       },
       connect: {
-- 
cgit v1.2.3


From e149b7c1e276281b37a47997bf98d8954a4674ba Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Thu, 11 Dec 2025 15:39:38 -0600
Subject: fix: avatar colors

---
 packages/desktop/src/context/layout.tsx | 40 ++++++++++++++++++++-------------
 packages/desktop/src/pages/layout.tsx   |  8 +++----
 packages/ui/src/components/avatar.css   |  3 ++-
 packages/ui/src/components/avatar.tsx   | 13 ++++++++++-
 4 files changed, 42 insertions(+), 22 deletions(-)

(limited to 'packages/desktop/src/context/layout.tsx')

diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx
index ea5962b40..9cafdce96 100644
--- a/packages/desktop/src/context/layout.tsx
+++ b/packages/desktop/src/context/layout.tsx
@@ -6,18 +6,26 @@ import { useGlobalSync } from "./global-sync"
 import { useGlobalSDK } from "./global-sdk"
 import { Project } from "@opencode-ai/sdk/v2"
 
-const PASTEL_COLORS = [
-  "#FCEAFD", // pastel pink
-  "#FFDFBA", // pastel peach
-  "#FFFFBA", // pastel yellow
-  "#BAFFC9", // pastel green
-  "#EAF6FD", // pastel blue
-  "#EFEAFD", // pastel lavender
-  "#FEC8D8", // pastel rose
-  "#D4F0F0", // pastel cyan
-  "#FDF0EA", // pastel coral
-  "#C1E1C1", // pastel mint
-]
+const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const
+
+export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number]
+
+export function isAvatarColorKey(value: string): value is AvatarColorKey {
+  return AVATAR_COLOR_KEYS.includes(value as AvatarColorKey)
+}
+
+export function getAvatarColors(key?: string) {
+  if (key && isAvatarColorKey(key)) {
+    return {
+      background: `var(--avatar-background-${key})`,
+      foreground: `var(--avatar-text-${key})`,
+    }
+  }
+  return {
+    background: "var(--surface-info-base)",
+    foreground: "var(--text-base)",
+  }
+}
 
 type Dialog = "provider" | "model" | "connect"
 
@@ -58,11 +66,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       connect: {},
       dialog: {},
     })
-    const usedColors = new Set<string>()
+    const usedColors = new Set<AvatarColorKey>()
 
-    function pickAvailableColor() {
-      const available = PASTEL_COLORS.filter((c) => !usedColors.has(c))
-      if (available.length === 0) return PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)]
+    function pickAvailableColor(): AvatarColorKey {
+      const available = AVATAR_COLOR_KEYS.filter((c) => !usedColors.has(c))
+      if (available.length === 0) return AVATAR_COLOR_KEYS[Math.floor(Math.random() * AVATAR_COLOR_KEYS.length)]
       return available[Math.floor(Math.random() * available.length)]
     }
 
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx
index 05386c1fb..70764292f 100644
--- a/packages/desktop/src/pages/layout.tsx
+++ b/packages/desktop/src/pages/layout.tsx
@@ -1,7 +1,7 @@
 import { createEffect, createMemo, For, Match, onCleanup, onMount, ParentProps, Show, Switch, type JSX } from "solid-js"
 import { DateTime } from "luxon"
 import { A, useNavigate, useParams } from "@solidjs/router"
-import { useLayout } from "@/context/layout"
+import { useLayout, getAvatarColors } from "@/context/layout"
 import { useGlobalSync } from "@/context/global-sync"
 import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
 import { Mark } from "@opencode-ai/ui/logo"
@@ -180,7 +180,7 @@ export default function Layout(props: ParentProps) {
                 <Avatar
                   fallback={name()}
                   src={props.project.icon?.url}
-                  background={props.project.icon?.color ?? "var(--surface-info-base)"}
+                  {...getAvatarColors(props.project.icon?.color)}
                   class="size-full"
                 />
               </div>
@@ -200,7 +200,7 @@ export default function Layout(props: ParentProps) {
               <Avatar
                 fallback={name()}
                 src={props.project.icon?.url}
-                background={props.project.icon?.color ?? "var(--surface-info-base)"}
+                {...getAvatarColors(props.project.icon?.color)}
                 class="size-full"
               />
             </div>
@@ -231,7 +231,7 @@ export default function Layout(props: ParentProps) {
                     <Avatar
                       fallback={name()}
                       src={props.project.icon?.url}
-                      background={props.project.icon?.color ?? "var(--surface-info-base)"}
+                      {...getAvatarColors(props.project.icon?.color)}
                       class="size-full group-hover/session:hidden"
                     />
                     <Icon
diff --git a/packages/ui/src/components/avatar.css b/packages/ui/src/components/avatar.css
index 4e42e6f99..87be9a50a 100644
--- a/packages/ui/src/components/avatar.css
+++ b/packages/ui/src/components/avatar.css
@@ -1,5 +1,6 @@
 [data-component="avatar"] {
   --avatar-bg: var(--color-surface-info-base);
+  --avatar-fg: var(--color-text-base);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -10,7 +11,7 @@
   font-weight: 500;
   text-transform: uppercase;
   background-color: var(--avatar-bg);
-  color: oklch(from var(--avatar-bg) calc(l * 0.72) calc(c * 8) h);
+  color: var(--avatar-fg);
 }
 
 [data-component="avatar"][data-has-image] {
diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx
index fb5798b08..ab7b0d0e2 100644
--- a/packages/ui/src/components/avatar.tsx
+++ b/packages/ui/src/components/avatar.tsx
@@ -4,11 +4,21 @@ export interface AvatarProps extends ComponentProps<"div"> {
   fallback: string
   src?: string
   background?: string
+  foreground?: string
   size?: "small" | "normal" | "large"
 }
 
 export function Avatar(props: AvatarProps) {
-  const [split, rest] = splitProps(props, ["fallback", "src", "background", "size", "class", "classList", "style"])
+  const [split, rest] = splitProps(props, [
+    "fallback",
+    "src",
+    "background",
+    "foreground",
+    "size",
+    "class",
+    "classList",
+    "style",
+  ])
   const src = split.src // did this so i can zero it out to test fallback
   return (
     <div
@@ -23,6 +33,7 @@ export function Avatar(props: AvatarProps) {
       style={{
         ...(typeof split.style === "object" ? split.style : {}),
         ...(!src && split.background ? { "--avatar-bg": split.background } : {}),
+        ...(!src && split.foreground ? { "--avatar-fg": split.foreground } : {}),
       }}
     >
       <Show when={src} fallback={split.fallback?.[0]}>
-- 
cgit v1.2.3

