Skip to content

Commit 536abea

Browse files
authored
fix(app): restore sidebar dash and sync session spinner colors (#17384)
1 parent c7a52b6 commit 536abea

4 files changed

Lines changed: 65 additions & 106 deletions

File tree

packages/app/src/components/session/session-header.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import { useLanguage } from "@/context/language"
1616
import { useLayout } from "@/context/layout"
1717
import { usePlatform } from "@/context/platform"
1818
import { useServer } from "@/context/server"
19+
import { useSync } from "@/context/sync"
1920
import { useTerminal } from "@/context/terminal"
2021
import { focusTerminalById } from "@/pages/session/helpers"
2122
import { useSessionLayout } from "@/pages/session/session-layout"
23+
import { messageAgentColor } from "@/utils/agent"
2224
import { decode64 } from "@/utils/base64"
2325
import { Persist, persisted } from "@/utils/persist"
2426
import { StatusPopover } from "../status-popover"
@@ -132,6 +134,7 @@ export function SessionHeader() {
132134
const server = useServer()
133135
const platform = usePlatform()
134136
const language = useLanguage()
137+
const sync = useSync()
135138
const terminal = useTerminal()
136139
const { params, view } = useSessionLayout()
137140

@@ -218,6 +221,9 @@ export function SessionHeader() {
218221
({ id: "finder", label: fileManager().label, icon: fileManager().icon } as const),
219222
)
220223
const opening = createMemo(() => openRequest.app !== undefined)
224+
const tint = createMemo(() =>
225+
messageAgentColor(params.id ? sync.data.message[params.id] : undefined, sync.data.agent),
226+
)
221227

222228
const selectApp = (app: OpenApp) => {
223229
if (!options().some((item) => item.id === app)) return
@@ -330,7 +336,7 @@ export function SessionHeader() {
330336
>
331337
<div class="flex size-5 shrink-0 items-center justify-center [&_[data-component=app-icon]]:size-5">
332338
<Show when={opening()} fallback={<AppIcon id={current().icon} />}>
333-
<Spinner class="size-3.5 text-icon-base" />
339+
<Spinner class="size-3.5" style={{ color: tint() ?? "var(--icon-base)" }} />
334340
</Show>
335341
</div>
336342
<span class="text-12-regular text-text-strong">{language.t("common.open")}</span>

packages/app/src/pages/layout/sidebar-items.tsx

Lines changed: 43 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
99
import { base64Encode } from "@opencode-ai/util/encode"
1010
import { getFilename } from "@opencode-ai/util/path"
1111
import { A, useNavigate, useParams } from "@solidjs/router"
12-
import { type Accessor, createEffect, createMemo, For, type JSX, on, onCleanup, Show } from "solid-js"
13-
import { createStore } from "solid-js/store"
12+
import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js"
1413
import { useGlobalSync } from "@/context/global-sync"
1514
import { useLanguage } from "@/context/language"
1615
import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
1716
import { useNotification } from "@/context/notification"
1817
import { usePermission } from "@/context/permission"
19-
import { agentColor } from "@/utils/agent"
18+
import { messageAgentColor } from "@/utils/agent"
2019
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
2120
import { hasProjectPermissions } from "./helpers"
2221

@@ -102,94 +101,46 @@ const SessionRow = (props: {
102101
warmPress: () => void
103102
warmFocus: () => void
104103
cancelHoverPrefetch: () => void
105-
}): JSX.Element => {
106-
const [slot, setSlot] = createStore({
107-
open: false,
108-
show: false,
109-
fade: false,
110-
})
111-
112-
let f: number | undefined
113-
const clear = () => {
114-
if (f !== undefined) window.clearTimeout(f)
115-
f = undefined
116-
}
117-
118-
onCleanup(clear)
119-
createEffect(
120-
on(
121-
() => props.isWorking(),
122-
(on, prev) => {
123-
clear()
124-
if (on) {
125-
setSlot({ open: true, show: true, fade: false })
126-
return
127-
}
128-
if (prev) {
129-
setSlot({ open: false, show: true, fade: true })
130-
f = window.setTimeout(() => setSlot({ show: false, fade: false }), 260)
131-
return
132-
}
133-
setSlot({ open: false, show: false, fade: false })
134-
},
135-
{ defer: true },
136-
),
137-
)
138-
139-
return (
140-
<A
141-
href={`/${props.slug}/session/${props.session.id}`}
142-
class={`relative flex items-center min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
143-
onPointerDown={props.warmPress}
144-
onPointerEnter={props.warmHover}
145-
onPointerLeave={props.cancelHoverPrefetch}
146-
onFocus={props.warmFocus}
147-
onClick={() => {
148-
props.setHoverSession(undefined)
149-
if (props.sidebarOpened()) return
150-
props.clearHoverProjectSoon()
151-
}}
152-
>
153-
<Show when={!props.isWorking() && (props.hasPermissions() || props.hasError() || props.unseenCount() > 0)}>
154-
<div
155-
classList={{
156-
"absolute left-0 top-1/2 -translate-y-1/2 size-1.5 rounded-full": true,
157-
"bg-surface-warning-strong": props.hasPermissions(),
158-
"bg-text-diff-delete-base": !props.hasPermissions() && props.hasError(),
159-
"bg-text-interactive-base": !props.hasPermissions() && !props.hasError() && props.unseenCount() > 0,
160-
}}
161-
aria-hidden="true"
162-
/>
163-
</Show>
164-
165-
<div class="flex items-center min-w-0 grow-1">
166-
<div
167-
class="shrink-0 flex items-center justify-center overflow-hidden transition-[width,margin] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]"
168-
style={{
169-
width: slot.open ? "16px" : "0px",
170-
"margin-right": slot.open ? "8px" : "0px",
171-
}}
172-
aria-hidden="true"
173-
>
174-
<Show when={slot.show}>
175-
<div
176-
class="transition-opacity duration-200 ease-out"
177-
classList={{
178-
"opacity-0": slot.fade,
179-
}}
180-
>
181-
<Spinner class="size-4" style={{ color: props.tint() ?? "var(--icon-interactive-base)" }} />
182-
</div>
183-
</Show>
184-
</div>
185-
186-
<span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate">
187-
{props.session.title}
188-
</span>
104+
}): JSX.Element => (
105+
<A
106+
href={`/${props.slug}/session/${props.session.id}`}
107+
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
108+
onPointerDown={props.warmPress}
109+
onPointerEnter={props.warmHover}
110+
onPointerLeave={props.cancelHoverPrefetch}
111+
onFocus={props.warmFocus}
112+
onClick={() => {
113+
props.setHoverSession(undefined)
114+
if (props.sidebarOpened()) return
115+
props.clearHoverProjectSoon()
116+
}}
117+
>
118+
<div class="flex items-center gap-1 w-full">
119+
<div
120+
class="shrink-0 size-6 flex items-center justify-center"
121+
style={{ color: props.tint() ?? "var(--icon-interactive-base)" }}
122+
>
123+
<Switch fallback={<Icon name="dash" size="small" class="text-icon-weak" />}>
124+
<Match when={props.isWorking()}>
125+
<Spinner class="size-[15px]" />
126+
</Match>
127+
<Match when={props.hasPermissions()}>
128+
<div class="size-1.5 rounded-full bg-surface-warning-strong" />
129+
</Match>
130+
<Match when={props.hasError()}>
131+
<div class="size-1.5 rounded-full bg-text-diff-delete-base" />
132+
</Match>
133+
<Match when={props.unseenCount() > 0}>
134+
<div class="size-1.5 rounded-full bg-text-interactive-base" />
135+
</Match>
136+
</Switch>
189137
</div>
190-
</A>
191-
)
192-
}
138+
<span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate">
139+
{props.session.title}
140+
</span>
141+
</div>
142+
</A>
143+
)
193144

194145
const SessionHoverPreview = (props: {
195146
mobile?: boolean
@@ -268,19 +219,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
268219
})
269220

270221
const tint = createMemo(() => {
271-
const messages = sessionStore.message[props.session.id]
272-
if (!messages) return undefined
273-
let user: Message | undefined
274-
for (let i = messages.length - 1; i >= 0; i--) {
275-
const message = messages[i]
276-
if (message.role !== "user") continue
277-
user = message
278-
break
279-
}
280-
if (!user?.agent) return undefined
281-
282-
const agent = sessionStore.agent.find((a) => a.name === user.agent)
283-
return agentColor(user.agent, agent?.color)
222+
return messageAgentColor(sessionStore.message[props.session.id], sessionStore.agent)
284223
})
285224

286225
const hoverMessages = createMemo(() =>
@@ -359,7 +298,7 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
359298
return (
360299
<div
361300
data-session-id={props.session.id}
362-
class="group/session relative w-full rounded-md cursor-default pl-3 pr-3 transition-colors
301+
class="group/session relative w-full rounded-md cursor-default pl-2 pr-3 transition-colors
363302
hover:bg-surface-raised-base-hover [&:has(:focus-visible)]:bg-surface-raised-base-hover has-[[data-expanded]]:bg-surface-raised-base-hover has-[.active]:bg-surface-base-active"
364303
>
365304
<Show

packages/app/src/pages/session/message-timeline.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { usePlatform } from "@/context/platform"
2727
import { useSettings } from "@/context/settings"
2828
import { useSDK } from "@/context/sdk"
2929
import { useSync } from "@/context/sync"
30+
import { messageAgentColor } from "@/utils/agent"
3031
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
3132

3233
type MessageComment = {
@@ -246,6 +247,7 @@ export function MessageTimeline(props: {
246247
return sync.data.session_status[id] ?? idle
247248
})
248249
const working = createMemo(() => !!pending() || sessionStatus().type !== "idle")
250+
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent))
249251

250252
const [slot, setSlot] = createStore({
251253
open: false,
@@ -689,7 +691,7 @@ export function MessageTimeline(props: {
689691
"opacity-0": slot.fade,
690692
}}
691693
>
692-
<Spinner class="size-4" style={{ color: "var(--icon-interactive-base)" }} />
694+
<Spinner class="size-4" style={{ color: tint() ?? "var(--icon-interactive-base)" }} />
693695
</div>
694696
</Show>
695697
</div>

packages/app/src/utils/agent.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,15 @@ export function agentColor(name: string, custom?: string) {
99
if (custom) return custom
1010
return defaults[name] ?? defaults[name.toLowerCase()]
1111
}
12+
13+
export function messageAgentColor(
14+
list: readonly { role: string; agent?: string }[] | undefined,
15+
agents: readonly { name: string; color?: string }[],
16+
) {
17+
if (!list) return undefined
18+
for (let i = list.length - 1; i >= 0; i--) {
19+
const item = list[i]
20+
if (item.role !== "user" || !item.agent) continue
21+
return agentColor(item.agent, agents.find((agent) => agent.name === item.agent)?.color)
22+
}
23+
}

0 commit comments

Comments
 (0)