summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-18 14:31:13 -0600
committerAdam <[email protected]>2025-12-18 15:47:20 -0600
commit0ebcaff92717ba0cb3ca122064cabc7622a2dd68 (patch)
treeaef45cbeaedc1b463008c141e34eb280615feed2
parent15931fa170f507c340d0c263e12a466740d0afe5 (diff)
downloadopencode-0ebcaff92717ba0cb3ca122064cabc7622a2dd68.tar.gz
opencode-0ebcaff92717ba0cb3ca122064cabc7622a2dd68.zip
fix(desktop): expanded states
-rw-r--r--packages/desktop/src/pages/session.tsx42
-rw-r--r--packages/ui/src/components/session-turn.tsx55
2 files changed, 58 insertions, 39 deletions
diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx
index 1cc92f759..6e993ff8f 100644
--- a/packages/desktop/src/pages/session.tsx
+++ b/packages/desktop/src/pages/session.tsx
@@ -1,4 +1,17 @@
-import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect, on } from "solid-js"
+import {
+ For,
+ onCleanup,
+ onMount,
+ Show,
+ Match,
+ Switch,
+ createResource,
+ createMemo,
+ createEffect,
+ on,
+ createRenderEffect,
+ batch,
+} from "solid-js"
import { Dynamic } from "solid-js/web"
import { useLocal, type LocalFile } from "@/context/local"
import { createStore } from "solid-js/store"
@@ -130,7 +143,8 @@ export default function Page() {
clickTimer: undefined as number | undefined,
activeDraggable: undefined as string | undefined,
activeTerminalDraggable: undefined as string | undefined,
- stepsExpanded: false,
+ userInteracted: false,
+ stepsExpanded: true,
})
let inputRef!: HTMLDivElement
@@ -159,7 +173,28 @@ export default function Page() {
),
)
+ createEffect(() => {
+ params.id
+ const status = sync.data.session_status[params.id ?? ""] ?? { type: "idle" }
+ batch(() => {
+ setStore("userInteracted", false)
+ setStore("stepsExpanded", status.type !== "idle")
+ })
+ })
+
const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? { type: "idle" })
+ const working = createMemo(() => status().type !== "idle" && activeMessage()?.id === lastUserMessage()?.id)
+
+ createRenderEffect((prev) => {
+ const isWorking = working()
+ if (!prev && isWorking) {
+ setStore("stepsExpanded", true)
+ }
+ if (prev && !isWorking && !store.userInteracted) {
+ setStore("stepsExpanded", false)
+ }
+ return isWorking
+ }, working())
command.register(() => [
{
@@ -619,7 +654,8 @@ export default function Page() {
sessionID={params.id!}
messageID={activeMessage()!.id}
stepsExpanded={store.stepsExpanded}
- onStepsExpandedChange={(expanded) => setStore("stepsExpanded", expanded)}
+ onStepsExpandedToggle={() => setStore("stepsExpanded", (x) => !x)}
+ onUserInteracted={() => setStore("userInteracted", true)}
classes={{
root: "pb-20 flex-1 min-w-0",
content: "pb-20",
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index bae7a2a40..6a0e11422 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -25,7 +25,8 @@ export function SessionTurn(
sessionID: string
messageID: string
stepsExpanded?: boolean
- onStepsExpandedChange?: (expanded: boolean) => void
+ onStepsExpandedToggle?: () => void
+ onUserInteracted?: () => void
classes?: {
root?: string
content?: string
@@ -171,7 +172,6 @@ export function SessionTurn(
stickyHeaderHeight: 0,
retrySeconds: 0,
status: rawStatus(),
- stepsExpanded: props.stepsExpanded ?? working(),
duration: duration(),
})
@@ -192,18 +192,26 @@ export function SessionTurn(
function handleScroll() {
if (!scrollRef || store.autoScrolled) return
- const { scrollTop } = scrollRef
- // only mark as user scrolled if they actively scrolled upward
- // content growth increases scrollHeight but never decreases scrollTop
+ const scrollTop = scrollRef.scrollTop
+ const reset = scrollTop <= 0 && store.lastScrollTop > 100 && working() && !store.userScrolled
+ if (reset) {
+ setStore("lastScrollTop", scrollTop)
+ requestAnimationFrame(scrollToBottom)
+ return
+ }
const scrolledUp = scrollTop < store.lastScrollTop - 10
if (scrolledUp && working()) {
setStore("userScrolled", true)
+ props.onUserInteracted?.()
}
setStore("lastScrollTop", scrollTop)
}
function handleInteraction() {
- if (working()) setStore("userScrolled", true)
+ if (working()) {
+ setStore("userScrolled", true)
+ props.onUserInteracted?.()
+ }
}
function scrollToBottom() {
@@ -243,12 +251,6 @@ export function SessionTurn(
)
createEffect(() => {
- if (props.stepsExpanded !== undefined) {
- setStore("stepsExpanded", props.stepsExpanded)
- }
- })
-
- createEffect(() => {
const timer = setInterval(() => {
setStore("duration", duration())
}, 1000)
@@ -262,7 +264,6 @@ export function SessionTurn(
if (newStatus === store.status || !newStatus) return
const timeSinceLastChange = Date.now() - lastStatusChange
-
if (timeSinceLastChange >= 2500) {
setStore("status", newStatus)
lastStatusChange = Date.now()
@@ -280,19 +281,6 @@ export function SessionTurn(
}
})
- createEffect((prev) => {
- const isWorking = working()
- if (!prev && isWorking) {
- setStore("stepsExpanded", true)
- props.onStepsExpandedChange?.(true)
- }
- if (prev && !isWorking && !store.userScrolled) {
- setStore("stepsExpanded", false)
- props.onStepsExpandedChange?.(false)
- }
- return isWorking
- }, working())
-
return (
<div data-component="session-turn" class={props.classes?.root}>
<div ref={scrollRef} onScroll={handleScroll} data-slot="session-turn-content" class={props.classes?.content}>
@@ -336,12 +324,7 @@ export function SessionTurn(
data-slot="session-turn-collapsible-trigger-content"
variant="ghost"
size="small"
- onClick={() => {
- if (assistantMessages().length === 0) return
- const next = !store.stepsExpanded
- setStore("stepsExpanded", next)
- props.onStepsExpandedChange?.(next)
- }}
+ onClick={props.onStepsExpandedToggle ?? (() => {})}
>
<Show when={working()}>
<Spinner />
@@ -361,8 +344,8 @@ export function SessionTurn(
<span data-slot="session-turn-retry-attempt">(#{retry()?.attempt})</span>
</Match>
<Match when={working()}>{store.status ?? "Considering next steps"}</Match>
- <Match when={store.stepsExpanded}>Hide steps</Match>
- <Match when={!store.stepsExpanded}>Show steps</Match>
+ <Match when={props.stepsExpanded}>Hide steps</Match>
+ <Match when={!props.stepsExpanded}>Show steps</Match>
</Switch>
<span>ยท</span>
<span>{store.duration}</span>
@@ -373,7 +356,7 @@ export function SessionTurn(
</div>
</Show>
{/* Response */}
- <Show when={store.stepsExpanded && assistantMessages().length > 0}>
+ <Show when={props.stepsExpanded && assistantMessages().length > 0}>
<div data-slot="session-turn-collapsible-content-inner">
<For each={assistantMessages()}>
{(assistantMessage) => {
@@ -472,7 +455,7 @@ export function SessionTurn(
</Accordion>
</div>
</Show>
- <Show when={error() && !store.stepsExpanded}>
+ <Show when={error() && !props.stepsExpanded}>
<Card variant="error" class="error-card">
{error()?.data?.message as string}
</Card>