summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages/session/composer
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-12 15:17:36 -0500
committerGitHub <[email protected]>2026-03-12 20:17:36 +0000
commit42a5af6c8f6998277cf69270ad12e2a64edac5d3 (patch)
treeeca5dff51dc694cce1e783425fc11b4bea1e6a12 /packages/app/src/pages/session/composer
parentf0542fae7a917fabb9e943c3112a3d0b4b03302d (diff)
downloadopencode-42a5af6c8f6998277cf69270ad12e2a64edac5d3.tar.gz
opencode-42a5af6c8f6998277cf69270ad12e2a64edac5d3.zip
feat(app): follow-up behavior (#17233)
Diffstat (limited to 'packages/app/src/pages/session/composer')
-rw-r--r--packages/app/src/pages/session/composer/session-composer-region.tsx26
-rw-r--r--packages/app/src/pages/session/composer/session-followup-dock.tsx109
-rw-r--r--packages/app/src/pages/session/composer/session-revert-dock.tsx4
3 files changed, 137 insertions, 2 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 0cca90180..2034fbead 100644
--- a/packages/app/src/pages/session/composer/session-composer-region.tsx
+++ b/packages/app/src/pages/session/composer/session-composer-region.tsx
@@ -8,9 +8,11 @@ import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff"
import { useSessionKey } from "@/pages/session/session-layout"
import { SessionPermissionDock } from "@/pages/session/composer/session-permission-dock"
import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock"
+import { SessionFollowupDock } from "@/pages/session/composer/session-followup-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"
+import type { FollowupDraft } from "@/components/prompt-input/submit"
export function SessionComposerRegion(props: {
state: SessionComposerState
@@ -21,6 +23,17 @@ export function SessionComposerRegion(props: {
onNewSessionWorktreeReset: () => void
onSubmit: () => void
onResponseSubmit: () => void
+ followup?: {
+ queue: () => boolean
+ items: { id: string; text: string }[]
+ sending?: string
+ edit?: { id: string; prompt: FollowupDraft["prompt"]; context: FollowupDraft["context"] }
+ onQueue: (draft: FollowupDraft) => void
+ onAbort: () => void
+ onSend: (id: string) => void
+ onEdit: (id: string) => void
+ onEditLoaded: () => void
+ }
revert?: {
items: { id: string; text: string }[]
restoring?: string
@@ -214,10 +227,23 @@ export function SessionComposerRegion(props: {
"margin-top": `${-lift()}px`,
}}
>
+ <Show when={props.followup?.items.length}>
+ <SessionFollowupDock
+ items={props.followup!.items}
+ sending={props.followup!.sending}
+ onSend={props.followup!.onSend}
+ onEdit={props.followup!.onEdit}
+ />
+ </Show>
<PromptInput
ref={props.inputRef}
newSessionWorktree={props.newSessionWorktree}
onNewSessionWorktreeReset={props.onNewSessionWorktreeReset}
+ edit={props.followup?.edit}
+ onEditLoaded={props.followup?.onEditLoaded}
+ shouldQueue={props.followup?.queue}
+ onQueue={props.followup?.onQueue}
+ onAbort={props.followup?.onAbort}
onSubmit={props.onSubmit}
/>
</div>
diff --git a/packages/app/src/pages/session/composer/session-followup-dock.tsx b/packages/app/src/pages/session/composer/session-followup-dock.tsx
new file mode 100644
index 000000000..7d744f4e6
--- /dev/null
+++ b/packages/app/src/pages/session/composer/session-followup-dock.tsx
@@ -0,0 +1,109 @@
+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 SessionFollowupDock(props: {
+ items: { id: string; text: string }[]
+ sending?: string
+ onSend: (id: string) => void
+ onEdit: (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.followupDock.summary.one" : "session.followupDock.summary.other", {
+ count: total(),
+ }),
+ )
+ const preview = createMemo(() => props.items[0]?.text ?? "")
+
+ return (
+ <DockTray
+ data-component="session-followup-dock"
+ style={{
+ "margin-bottom": "-0.875rem",
+ "border-bottom-left-radius": 0,
+ "border-bottom-right-radius": 0,
+ }}
+ >
+ <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-13-medium text-text-strong cursor-default">{label()}</span>
+ <Show when={store.collapsed && preview()}>
+ <span class="min-w-0 flex-1 truncate text-13-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.followupDock.expand") : language.t("session.followupDock.collapse")
+ }
+ />
+ </div>
+ </div>
+
+ <Show when={store.collapsed}>
+ <div class="h-5" aria-hidden="true" />
+ </Show>
+
+ <Show when={!store.collapsed}>
+ <div class="px-3 pb-7 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 py-1">
+ <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.sending}
+ onClick={() => props.onSend(item.id)}
+ >
+ {language.t("session.followupDock.sendNow")}
+ </Button>
+ <Button
+ size="small"
+ variant="ghost"
+ class="shrink-0"
+ disabled={!!props.sending}
+ onClick={() => props.onEdit(item.id)}
+ >
+ {language.t("session.followupDock.edit")}
+ </Button>
+ </div>
+ )}
+ </For>
+ </div>
+ </Show>
+ </DockTray>
+ )
+}
diff --git a/packages/app/src/pages/session/composer/session-revert-dock.tsx b/packages/app/src/pages/session/composer/session-revert-dock.tsx
index fd4b39ee7..5a5b8a9cd 100644
--- a/packages/app/src/pages/session/composer/session-revert-dock.tsx
+++ b/packages/app/src/pages/session/composer/session-revert-dock.tsx
@@ -75,10 +75,10 @@ export function SessionRevertDock(props: {
</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">
+ <div class="px-3 pb-7 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">
+ <div class="flex items-center gap-2 min-w-0 py-1">
<span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
<Button
size="small"