diff options
| author | Adam <[email protected]> | 2025-12-18 14:31:13 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-18 15:47:20 -0600 |
| commit | 0ebcaff92717ba0cb3ca122064cabc7622a2dd68 (patch) | |
| tree | aef45cbeaedc1b463008c141e34eb280615feed2 | |
| parent | 15931fa170f507c340d0c263e12a466740d0afe5 (diff) | |
| download | opencode-0ebcaff92717ba0cb3ca122064cabc7622a2dd68.tar.gz opencode-0ebcaff92717ba0cb3ca122064cabc7622a2dd68.zip | |
fix(desktop): expanded states
| -rw-r--r-- | packages/desktop/src/pages/session.tsx | 42 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 55 |
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> |
