fix(tui): scope global forms by location
This commit is contained in:
parent
bfa959847f
commit
3056187950
4 changed files with 125 additions and 9 deletions
|
|
@ -714,12 +714,15 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
},
|
||||
},
|
||||
form: {
|
||||
list(sessionID: string) {
|
||||
return store.session.form[sessionID]
|
||||
list(sessionID: string, ref?: LocationRef) {
|
||||
const forms = store.session.form[sessionID]
|
||||
if (sessionID !== "global" || !ref) return forms
|
||||
const key = locationKey(ref)
|
||||
return forms?.filter((form) => form.location && locationKey(form.location) === key)
|
||||
},
|
||||
async refresh(sessionID: string) {
|
||||
async refresh(sessionID: string, ref?: LocationRef) {
|
||||
if (sessionID === "global") {
|
||||
const result = await sdk.api.form.listRequests({ location: locationQuery(defaultLocation()) })
|
||||
const result = await sdk.api.form.listRequests({ location: locationQuery(ref ?? defaultLocation()) })
|
||||
const location = { directory: result.location.directory, workspaceID: result.location.workspaceID }
|
||||
setStore(
|
||||
"session",
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ export function Session() {
|
|||
const sessionIDs = session()?.parentID ? [route.sessionID] : [route.sessionID, ...descendantSessionIDs()]
|
||||
return [
|
||||
...sessionIDs.flatMap((sessionID) => data.session.form.list(sessionID) ?? []),
|
||||
...(data.session.form.list("global") ?? []),
|
||||
...(data.session.form.list("global", location()) ?? []),
|
||||
]
|
||||
})
|
||||
const [composer, setComposer] = createStore({
|
||||
|
|
@ -266,7 +266,7 @@ export function Session() {
|
|||
await Promise.all([
|
||||
data.session.permission.refresh(sessionID),
|
||||
data.session.form.refresh(sessionID),
|
||||
data.session.form.refresh("global"),
|
||||
data.session.form.refresh("global", info.location),
|
||||
])
|
||||
|
||||
project.workspace.set(info.location.workspaceID)
|
||||
|
|
@ -949,8 +949,8 @@ export function Session() {
|
|||
<PermissionPrompt request={permissions()[0]} directory={session()?.location.directory} />
|
||||
</Match>
|
||||
<Match when={forms().length > 0}>
|
||||
<Show when={forms()[0]} keyed>
|
||||
{(form) => <FormPrompt form={form} />}
|
||||
<Show when={forms()[0]?.id} keyed>
|
||||
{() => <Show when={forms()[0]}>{(form) => <FormPrompt form={form()} />}</Show>}
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={composer.open || !!session()?.parentID}>{null}</Match>
|
||||
|
|
|
|||
|
|
@ -698,6 +698,118 @@ test("adds and dismisses permission requests from live events", async () => {
|
|||
}
|
||||
})
|
||||
|
||||
test("tracks global forms by location", async () => {
|
||||
const events = createEventStream()
|
||||
const calls = createFetch(undefined, events)
|
||||
const other = { directory: "/tmp/opencode-other", workspaceID: "wrk_other" }
|
||||
let data!: ReturnType<typeof useData>
|
||||
|
||||
function Probe() {
|
||||
data = useData()
|
||||
return <box />
|
||||
}
|
||||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
</DataProvider>
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</TestTuiContexts>
|
||||
))
|
||||
|
||||
try {
|
||||
await wait(() => data.connection.status() === "connected")
|
||||
events.emit({
|
||||
id: "evt_form_created_1",
|
||||
created: 0,
|
||||
location: other,
|
||||
type: "form.created",
|
||||
data: {
|
||||
form: {
|
||||
id: "frm_1",
|
||||
sessionID: "global",
|
||||
mode: "form",
|
||||
fields: [],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await wait(() => data.session.form.list("global", other)?.length === 1)
|
||||
expect(data.session.form.list("global", { directory }) ?? []).toEqual([])
|
||||
|
||||
events.emit({
|
||||
id: "evt_form_replied_1",
|
||||
created: 1,
|
||||
location: other,
|
||||
type: "form.replied",
|
||||
data: { id: "frm_1", sessionID: "global", answer: {} },
|
||||
})
|
||||
await wait(() => data.session.form.list("global", other)?.length === 0)
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("refreshes global forms for the requested location", async () => {
|
||||
const events = createEventStream()
|
||||
const requests: URL[] = []
|
||||
const other = { directory: "/tmp/opencode-other", workspaceID: "wrk_other" }
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname !== "/api/form/request") return
|
||||
requests.push(url)
|
||||
const requestedDirectory = url.searchParams.get("location[directory]") ?? directory
|
||||
const requestedWorkspace = url.searchParams.get("location[workspace]") ?? undefined
|
||||
return json({
|
||||
location: {
|
||||
directory: requestedDirectory,
|
||||
workspaceID: requestedWorkspace,
|
||||
project: { id: "proj_test", directory: requestedDirectory },
|
||||
},
|
||||
data:
|
||||
requestedDirectory === other.directory
|
||||
? [{ id: "frm_other", sessionID: "global", mode: "form", fields: [] }]
|
||||
: [],
|
||||
})
|
||||
}, events)
|
||||
let data!: ReturnType<typeof useData>
|
||||
|
||||
function Probe() {
|
||||
data = useData()
|
||||
return <box />
|
||||
}
|
||||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
</DataProvider>
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</TestTuiContexts>
|
||||
))
|
||||
|
||||
try {
|
||||
await wait(() => requests.length > 0)
|
||||
requests.length = 0
|
||||
|
||||
await data.session.form.refresh("global", other)
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.searchParams.get("location[directory]")).toBe(other.directory)
|
||||
expect(requests[0]?.searchParams.get("location[workspace]")).toBe(other.workspaceID)
|
||||
expect(data.session.form.list("global", other)?.map((form) => form.id)).toEqual(["frm_other"])
|
||||
expect(data.session.form.list("global", { directory }) ?? []).toEqual([])
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("settles pending tools when a live failure arrives", async () => {
|
||||
const events = createEventStream()
|
||||
const calls = createFetch(undefined, events)
|
||||
|
|
|
|||
|
|
@ -100,7 +100,8 @@ export function createFetch(override?: FetchHandler, events?: ReturnType<typeof
|
|||
if (url.pathname === "/api/form/request") return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
|
||||
if (url.pathname === "/api/session") return json({ data: [], cursor: {} })
|
||||
if (url.pathname === "/api/session/active") return json({ data: {}, watermarks: {} })
|
||||
if (/^\/api\/session\/[^/]+\/(permission|form)$/.test(url.pathname)) return json([])
|
||||
if (/^\/api\/session\/[^/]+\/permission$/.test(url.pathname)) return json([])
|
||||
if (/^\/api\/session\/[^/]+\/form$/.test(url.pathname)) return json({ data: [] })
|
||||
if (
|
||||
["/api/agent", "/api/model", "/api/provider", "/api/integration", "/api/command", "/api/skill"].includes(
|
||||
url.pathname,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue