summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components/prompt-input
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-13 07:38:03 -0500
committerGitHub <[email protected]>2026-03-13 07:38:03 -0500
commit1a3735b6197a192be10a2576ed21edee93f9da21 (patch)
treebc55b2d672b378b8838411718fc1e9522d041121 /packages/app/src/components/prompt-input
parentd4ae13f2a0e7748dd8f3a94ec21ee05050ec2cf7 (diff)
downloadopencode-1a3735b6197a192be10a2576ed21edee93f9da21.tar.gz
opencode-1a3735b6197a192be10a2576ed21edee93f9da21.zip
fix(app): better optimistic prompt submit (#17337)
Diffstat (limited to 'packages/app/src/components/prompt-input')
-rw-r--r--packages/app/src/components/prompt-input/submit.test.ts66
-rw-r--r--packages/app/src/components/prompt-input/submit.ts23
2 files changed, 83 insertions, 6 deletions
diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts
index 4109417d2..9f7fac69d 100644
--- a/packages/app/src/components/prompt-input/submit.test.ts
+++ b/packages/app/src/components/prompt-input/submit.test.ts
@@ -7,12 +7,16 @@ const createdClients: string[] = []
const createdSessions: string[] = []
const enabledAutoAccept: Array<{ sessionID: string; directory: string }> = []
const optimistic: Array<{
+ directory?: string
+ sessionID?: string
message: {
agent: string
model: { providerID: string; modelID: string }
variant?: string
}
}> = []
+const optimisticSeeded: boolean[] = []
+const storedSessions: Record<string, Array<{ id: string; title?: string }>> = {}
const sentShell: string[] = []
const syncedDirectories: string[] = []
@@ -28,7 +32,12 @@ const clientFor = (directory: string) => {
session: {
create: async () => {
createdSessions.push(directory)
- return { data: { id: `session-${createdSessions.length}` } }
+ return {
+ data: {
+ id: `session-${createdSessions.length}`,
+ title: `New session ${createdSessions.length}`,
+ },
+ }
},
shell: async () => {
sentShell.push(directory)
@@ -129,9 +138,16 @@ beforeAll(async () => {
session: {
optimistic: {
add: (value: {
+ directory?: string
+ sessionID?: string
message: { agent: string; model: { providerID: string; modelID: string }; variant?: string }
}) => {
optimistic.push(value)
+ optimisticSeeded.push(
+ !!value.directory &&
+ !!value.sessionID &&
+ !!storedSessions[value.directory]?.find((item) => item.id === value.sessionID)?.title,
+ )
},
remove: () => undefined,
},
@@ -144,7 +160,21 @@ beforeAll(async () => {
useGlobalSync: () => ({
child: (directory: string) => {
syncedDirectories.push(directory)
- return [{}, () => undefined]
+ storedSessions[directory] ??= []
+ return [
+ { session: storedSessions[directory] },
+ (...args: unknown[]) => {
+ if (args[0] !== "session") return
+ const next = args[1]
+ if (typeof next === "function") {
+ storedSessions[directory] = next(storedSessions[directory]) as Array<{ id: string; title?: string }>
+ return
+ }
+ if (Array.isArray(next)) {
+ storedSessions[directory] = next as Array<{ id: string; title?: string }>
+ }
+ },
+ ]
},
}),
}))
@@ -170,11 +200,13 @@ beforeEach(() => {
createdSessions.length = 0
enabledAutoAccept.length = 0
optimistic.length = 0
+ optimisticSeeded.length = 0
params = {}
sentShell.length = 0
syncedDirectories.length = 0
selected = "/repo/worktree-a"
variant = undefined
+ for (const key of Object.keys(storedSessions)) delete storedSessions[key]
})
describe("prompt submit worktree selection", () => {
@@ -207,7 +239,7 @@ describe("prompt submit worktree selection", () => {
expect(createdClients).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
expect(createdSessions).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
expect(sentShell).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
- expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
+ expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-a", "/repo/worktree-b", "/repo/worktree-b"])
})
test("applies auto-accept to newly created sessions", async () => {
@@ -271,4 +303,32 @@ describe("prompt submit worktree selection", () => {
},
})
})
+
+ test("seeds new sessions before optimistic prompts are added", async () => {
+ const submit = createPromptSubmit({
+ info: () => undefined,
+ imageAttachments: () => [],
+ commentCount: () => 0,
+ autoAccept: () => false,
+ mode: () => "normal",
+ working: () => false,
+ editor: () => undefined,
+ queueScroll: () => undefined,
+ promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
+ addToHistory: () => undefined,
+ resetHistoryNavigation: () => undefined,
+ setMode: () => undefined,
+ setPopover: () => undefined,
+ newSessionWorktree: () => selected,
+ onNewSessionWorktreeReset: () => undefined,
+ onSubmit: () => undefined,
+ })
+
+ const event = { preventDefault: () => undefined } as unknown as Event
+
+ await submit.handleSubmit(event)
+
+ expect(storedSessions["/repo/worktree-a"]).toEqual([{ id: "session-1", title: "New session 1" }])
+ expect(optimisticSeeded).toEqual([true])
+ })
})
diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts
index eb3e0c82f..e8d765cd9 100644
--- a/packages/app/src/components/prompt-input/submit.ts
+++ b/packages/app/src/components/prompt-input/submit.ts
@@ -1,6 +1,7 @@
-import type { Message } from "@opencode-ai/sdk/v2/client"
+import type { Message, Session } from "@opencode-ai/sdk/v2/client"
import { showToast } from "@opencode-ai/ui/toast"
import { base64Encode } from "@opencode-ai/util/encode"
+import { Binary } from "@opencode-ai/util/binary"
import { useNavigate, useParams } from "@solidjs/router"
import type { Accessor } from "solid-js"
import type { FileSelection } from "@/context/file"
@@ -266,6 +267,20 @@ export function createPromptSubmit(input: PromptSubmitInput) {
}
}
+ const seed = (dir: string, info: Session) => {
+ const [, setStore] = globalSync.child(dir)
+ setStore("session", (list: Session[]) => {
+ const result = Binary.search(list, info.id, (item) => item.id)
+ const next = [...list]
+ if (result.found) {
+ next[result.index] = info
+ return next
+ }
+ next.splice(result.index, 0, info)
+ return next
+ })
+ }
+
const handleSubmit = async (event: Event) => {
event.preventDefault()
@@ -341,7 +356,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
let session = input.info()
if (!session && isNewSession) {
- session = await client.session
+ const created = await client.session
.create()
.then((x) => x.data ?? undefined)
.catch((err) => {
@@ -351,7 +366,9 @@ export function createPromptSubmit(input: PromptSubmitInput) {
})
return undefined
})
- if (session) {
+ if (created) {
+ seed(sessionDirectory, created)
+ session = created
if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory)
layout.handoff.setTabs(base64Encode(sessionDirectory), session.id)
navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)