diff options
| author | Adam <[email protected]> | 2026-01-07 10:07:34 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-01-08 17:48:15 -0600 |
| commit | 1c717d62e4bfd20078cfce223cfd5152669d1c9f (patch) | |
| tree | 650b94a6495fc7dafadaf6d8b6690c4f468da73b | |
| parent | 27675dfd70c12d535ec9906a6bab354402a7fc89 (diff) | |
| download | opencode-1c717d62e4bfd20078cfce223cfd5152669d1c9f.tar.gz opencode-1c717d62e4bfd20078cfce223cfd5152669d1c9f.zip | |
feat(app): gate hydration side-effects, handoff while stores load
| -rw-r--r-- | packages/app/src/pages/session.tsx | 178 |
1 files changed, 118 insertions, 60 deletions
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 4a40ae7d2..99306a2e7 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -55,6 +55,12 @@ import { same } from "@/utils/same" type DiffStyle = "unified" | "split" +const handoff = { + prompt: "", + terminals: [] as string[], + files: {} as Record<string, SelectedLineRange | null>, +} + interface SessionReviewTabProps { diffs: () => FileDiff[] view: () => ReturnType<ReturnType<typeof useLayout>["view"]> @@ -362,11 +368,10 @@ export default function Page() { }) createEffect(() => { - if (layout.terminal.opened()) { - if (terminal.all().length === 0) { - terminal.new() - } - } + if (!layout.terminal.opened()) return + if (!terminal.ready()) return + if (terminal.all().length !== 0) return + terminal.new() }) createEffect( @@ -839,7 +844,30 @@ export default function Page() { document.addEventListener("keydown", handleKeyDown) }) + const previewPrompt = () => + prompt + .current() + .map((part) => { + if (part.type === "file") return `[file:${part.path}]` + if (part.type === "agent") return `@${part.name}` + if (part.type === "image") return `[image:${part.filename}]` + return part.content + }) + .join("") + .trim() + onCleanup(() => { + handoff.prompt = previewPrompt() + handoff.terminals = terminal.all().map((t) => t.title) + handoff.files = Object.fromEntries( + tabs() + .all() + .flatMap((tab) => { + const path = file.pathFromTab(tab) + if (!path) return [] + return [[path, file.selectedLines(path) ?? null] as const] + }), + ) document.removeEventListener("keydown", handleKeyDown) if (scrollSpyFrame !== undefined) cancelAnimationFrame(scrollSpyFrame) }) @@ -1046,13 +1074,22 @@ export default function Page() { "md:max-w-200": !showTabs(), }} > - <PromptInput - ref={(el) => { - inputRef = el - }} - newSessionWorktree={newSessionWorktree()} - onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")} - /> + <Show + when={prompt.ready()} + fallback={ + <div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none"> + {handoff.prompt || "Loading prompt..."} + </div> + } + > + <PromptInput + ref={(el) => { + inputRef = el + }} + newSessionWorktree={newSessionWorktree()} + onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")} + /> + </Show> </div> </div> @@ -1214,7 +1251,8 @@ export default function Page() { const selectedLines = createMemo(() => { const p = path() if (!p) return null - return file.selectedLines(p) ?? null + if (file.ready()) return file.selectedLines(p) ?? null + return handoff.files[p] ?? null }) const selection = createMemo(() => { const range = selectedLines() @@ -1423,54 +1461,74 @@ export default function Page() { onResize={layout.terminal.resize} onCollapse={layout.terminal.close} /> - <DragDropProvider - onDragStart={handleTerminalDragStart} - onDragEnd={handleTerminalDragEnd} - onDragOver={handleTerminalDragOver} - collisionDetector={closestCenter} - > - <DragDropSensors /> - <ConstrainDragYAxis /> - <Tabs variant="alt" value={terminal.active()} onChange={terminal.open}> - <Tabs.List class="h-10"> - <SortableProvider ids={terminal.all().map((t: LocalPTY) => t.id)}> - <For each={terminal.all()}>{(pty) => <SortableTerminalTab terminal={pty} />}</For> - </SortableProvider> - <div class="h-full flex items-center justify-center"> - <TooltipKeybind - title="New terminal" - keybind={command.keybind("terminal.new")} - class="flex items-center" - > - <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} /> - </TooltipKeybind> + <Show + when={terminal.ready()} + fallback={ + <div class="flex flex-col h-full pointer-events-none"> + <div class="h-10 flex items-center gap-2 px-2 border-b border-border-weak-base bg-background-stronger overflow-hidden"> + <For each={handoff.terminals}> + {(title) => ( + <div class="px-2 py-1 rounded-md bg-surface-base text-14-regular text-text-weak truncate max-w-40"> + {title} + </div> + )} + </For> + <div class="flex-1" /> + <div class="text-text-weak pr-2">Loading...</div> </div> - </Tabs.List> - <For each={terminal.all()}> - {(pty) => ( - <Tabs.Content value={pty.id}> - <Terminal pty={pty} onCleanup={terminal.update} onConnectError={() => terminal.clone(pty.id)} /> - </Tabs.Content> - )} - </For> - </Tabs> - <DragOverlay> - <Show when={store.activeTerminalDraggable}> - {(draggedId) => { - const pty = createMemo(() => terminal.all().find((t: LocalPTY) => t.id === draggedId())) - return ( - <Show when={pty()}> - {(t) => ( - <div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular"> - {t().title} - </div> - )} - </Show> - ) - }} - </Show> - </DragOverlay> - </DragDropProvider> + <div class="flex-1 flex items-center justify-center text-text-weak">Loading terminal...</div> + </div> + } + > + <DragDropProvider + onDragStart={handleTerminalDragStart} + onDragEnd={handleTerminalDragEnd} + onDragOver={handleTerminalDragOver} + collisionDetector={closestCenter} + > + <DragDropSensors /> + <ConstrainDragYAxis /> + <Tabs variant="alt" value={terminal.active()} onChange={terminal.open}> + <Tabs.List class="h-10"> + <SortableProvider ids={terminal.all().map((t: LocalPTY) => t.id)}> + <For each={terminal.all()}>{(pty) => <SortableTerminalTab terminal={pty} />}</For> + </SortableProvider> + <div class="h-full flex items-center justify-center"> + <TooltipKeybind + title="New terminal" + keybind={command.keybind("terminal.new")} + class="flex items-center" + > + <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} /> + </TooltipKeybind> + </div> + </Tabs.List> + <For each={terminal.all()}> + {(pty) => ( + <Tabs.Content value={pty.id}> + <Terminal pty={pty} onCleanup={terminal.update} onConnectError={() => terminal.clone(pty.id)} /> + </Tabs.Content> + )} + </For> + </Tabs> + <DragOverlay> + <Show when={store.activeTerminalDraggable}> + {(draggedId) => { + const pty = createMemo(() => terminal.all().find((t: LocalPTY) => t.id === draggedId())) + return ( + <Show when={pty()}> + {(t) => ( + <div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular"> + {t().title} + </div> + )} + </Show> + ) + }} + </Show> + </DragOverlay> + </DragDropProvider> + </Show> </div> </Show> </div> |
