diff options
| author | Adam <[email protected]> | 2026-03-11 16:34:48 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-11 21:34:48 +0000 |
| commit | fbd9b7cf4fb27433a066a10a197c7edb420336d1 (patch) | |
| tree | 39697e059b99369f370682a0f68da037adfe1016 /packages/app/src/pages/session/composer | |
| parent | 58f45ae22b0faeba4a98910b0d3b42146846462b (diff) | |
| download | opencode-fbd9b7cf4fb27433a066a10a197c7edb420336d1.tar.gz opencode-fbd9b7cf4fb27433a066a10a197c7edb420336d1.zip | |
feat(app): restore to message and fork session (#17092)
Diffstat (limited to 'packages/app/src/pages/session/composer')
| -rw-r--r-- | packages/app/src/pages/session/composer/session-composer-region.tsx | 40 | ||||
| -rw-r--r-- | packages/app/src/pages/session/composer/session-revert-dock.tsx | 92 |
2 files changed, 128 insertions, 4 deletions
diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx index 93ea3d465..08746b51a 100644 --- a/packages/app/src/pages/session/composer/session-composer-region.tsx +++ b/packages/app/src/pages/session/composer/session-composer-region.tsx @@ -8,6 +8,7 @@ import { usePrompt } from "@/context/prompt" import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff" import { SessionPermissionDock } from "@/pages/session/composer/session-permission-dock" import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock" +import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock" import type { SessionComposerState } from "@/pages/session/composer/session-composer-state" import { SessionTodoDock } from "@/pages/session/composer/session-todo-dock" @@ -20,6 +21,11 @@ export function SessionComposerRegion(props: { onNewSessionWorktreeReset: () => void onSubmit: () => void onResponseSubmit: () => void + revert?: { + items: { id: string; text: string }[] + restoring?: string + onRestore: (id: string) => void + } setPromptDockRef: (el: HTMLDivElement) => void visualDuration?: number bounce?: number @@ -116,6 +122,8 @@ export function SessionComposerRegion(props: { const value = createMemo(() => Math.max(0, Math.min(1, progress()))) const [height, setHeight] = createSignal(320) const dock = createMemo(() => (gate.ready && props.state.dock()) || value() > 0.001) + const rolled = createMemo(() => (props.revert?.items.length ? props.revert : undefined)) + const lift = createMemo(() => (rolled() ? 18 : 36 * value())) const full = createMemo(() => Math.max(78, height())) const [contentRef, setContentRef] = createSignal<HTMLDivElement>() @@ -170,9 +178,22 @@ export function SessionComposerRegion(props: { <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"> - {handoffPrompt() || language.t("prompt.loading")} - </div> + <> + <Show when={rolled()} keyed> + {(revert) => ( + <div class="pb-2"> + <SessionRevertDock + items={revert.items} + restoring={revert.restoring} + onRestore={revert.onRestore} + /> + </div> + )} + </Show> + <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"> + {handoffPrompt() || language.t("prompt.loading")} + </div> + </> } > <Show when={dock()}> @@ -209,12 +230,23 @@ export function SessionComposerRegion(props: { </div> </div> </Show> + <Show when={rolled()} keyed> + {(revert) => ( + <div + style={{ + "margin-top": `${-36 * value()}px`, + }} + > + <SessionRevertDock items={revert.items} restoring={revert.restoring} onRestore={revert.onRestore} /> + </div> + )} + </Show> <div classList={{ "relative z-10": true, }} style={{ - "margin-top": `${-36 * value()}px`, + "margin-top": `${-lift()}px`, }} > <PromptInput diff --git a/packages/app/src/pages/session/composer/session-revert-dock.tsx b/packages/app/src/pages/session/composer/session-revert-dock.tsx new file mode 100644 index 000000000..ce7644b67 --- /dev/null +++ b/packages/app/src/pages/session/composer/session-revert-dock.tsx @@ -0,0 +1,92 @@ +import { For, Show, createMemo } from "solid-js" +import { createStore } from "solid-js/store" +import { Button } from "@opencode-ai/ui/button" +import { DockTray } from "@opencode-ai/ui/dock-surface" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { useLanguage } from "@/context/language" + +export function SessionRevertDock(props: { + items: { id: string; text: string }[] + restoring?: string + onRestore: (id: string) => void +}) { + const language = useLanguage() + const [store, setStore] = createStore({ + collapsed: false, + }) + + const toggle = () => setStore("collapsed", (value) => !value) + const total = createMemo(() => props.items.length) + const label = createMemo(() => + language.t(total() === 1 ? "session.revertDock.summary.one" : "session.revertDock.summary.other", { + count: total(), + }), + ) + const preview = createMemo(() => props.items[0]?.text ?? "") + + return ( + <DockTray data-component="session-revert-dock"> + <div + class="pl-3 pr-2 py-2 flex items-center gap-2" + role="button" + tabIndex={0} + onClick={toggle} + onKeyDown={(event) => { + if (event.key !== "Enter" && event.key !== " ") return + event.preventDefault() + toggle() + }} + > + <span class="shrink-0 text-14-regular text-text-strong cursor-default">{label()}</span> + <Show when={store.collapsed && preview()}> + <span class="min-w-0 flex-1 truncate text-14-regular text-text-base cursor-default">{preview()}</span> + </Show> + <div class="ml-auto shrink-0"> + <IconButton + data-collapsed={store.collapsed ? "true" : "false"} + icon="chevron-down" + size="normal" + variant="ghost" + style={{ transform: `rotate(${store.collapsed ? 180 : 0}deg)` }} + onMouseDown={(event) => { + event.preventDefault() + event.stopPropagation() + }} + onClick={(event) => { + event.stopPropagation() + toggle() + }} + aria-label={ + store.collapsed ? language.t("session.revertDock.expand") : language.t("session.revertDock.collapse") + } + /> + </div> + </div> + + <Show when={store.collapsed}> + <div class="h-5" aria-hidden="true" /> + </Show> + + <Show when={!store.collapsed}> + <div class="px-3 pb-11 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar"> + <For each={props.items}> + {(item) => ( + <div class="flex items-center gap-2 min-w-0 rounded-[10px] border border-border-weak-base bg-background-stronger px-2.5 py-2"> + <span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span> + <Button + size="small" + variant="secondary" + class="shrink-0" + disabled={!!props.restoring} + onClick={() => props.onRestore(item.id)} + > + {language.t("session.revertDock.restore")} + </Button> + </div> + )} + </For> + </div> + </Show> + </DockTray> + ) +} |
