fix(tui): preserve global form ownership
This commit is contained in:
parent
684d288b9c
commit
57dd2fba7d
5 changed files with 58 additions and 13 deletions
|
|
@ -69,6 +69,12 @@ function locationKey(location: LocationRef) {
|
||||||
return JSON.stringify([location.directory, location.workspaceID])
|
return JSON.stringify([location.directory, location.workspaceID])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function matchesGlobalForm(form: FormInfo, id: string, location?: LocationRef) {
|
||||||
|
if (form.id !== id) return false
|
||||||
|
if (!form.location || !location) return true
|
||||||
|
return locationKey(form.location) === locationKey(location)
|
||||||
|
}
|
||||||
|
|
||||||
function locationQuery(ref?: LocationRef) {
|
function locationQuery(ref?: LocationRef) {
|
||||||
return ref ? { directory: ref.directory, workspace: ref.workspaceID } : undefined
|
return ref ? { directory: ref.directory, workspace: ref.workspaceID } : undefined
|
||||||
}
|
}
|
||||||
|
|
@ -589,7 +595,14 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case "form.created":
|
case "form.created":
|
||||||
if (store.session.form[event.data.form.sessionID]?.some((form) => form.id === event.data.form.id)) break
|
if (
|
||||||
|
store.session.form[event.data.form.sessionID]?.some((form) =>
|
||||||
|
event.data.form.sessionID === "global"
|
||||||
|
? matchesGlobalForm(form, event.data.form.id, event.location)
|
||||||
|
: form.id === event.data.form.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
break
|
||||||
setStore("session", "form", event.data.form.sessionID, [
|
setStore("session", "form", event.data.form.sessionID, [
|
||||||
...(store.session.form[event.data.form.sessionID] ?? []),
|
...(store.session.form[event.data.form.sessionID] ?? []),
|
||||||
mutable({ ...event.data.form, location: event.location }),
|
mutable({ ...event.data.form, location: event.location }),
|
||||||
|
|
@ -601,7 +614,11 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
"session",
|
"session",
|
||||||
"form",
|
"form",
|
||||||
event.data.sessionID,
|
event.data.sessionID,
|
||||||
(store.session.form[event.data.sessionID] ?? []).filter((form) => form.id !== event.data.id),
|
(store.session.form[event.data.sessionID] ?? []).filter((form) =>
|
||||||
|
event.data.sessionID === "global"
|
||||||
|
? !matchesGlobalForm(form, event.data.id, event.location)
|
||||||
|
: form.id !== event.data.id,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case "shell.created":
|
case "shell.created":
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ function sessionErrorMessage(error: SessionError) {
|
||||||
return "Session error"
|
return "Session error"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formKey(
|
||||||
|
event: Extract<V2Event, { type: "form.created" | "form.replied" | "form.cancelled" }>,
|
||||||
|
id: string,
|
||||||
|
) {
|
||||||
|
return JSON.stringify([event.location?.directory, event.location?.workspaceID, id])
|
||||||
|
}
|
||||||
|
|
||||||
const tui: TuiPlugin = async (api) => {
|
const tui: TuiPlugin = async (api) => {
|
||||||
const active = new Set<string>()
|
const active = new Set<string>()
|
||||||
const errored = new Set<string>()
|
const errored = new Set<string>()
|
||||||
|
|
@ -34,18 +41,18 @@ const tui: TuiPlugin = async (api) => {
|
||||||
const permissions = new Set<string>()
|
const permissions = new Set<string>()
|
||||||
|
|
||||||
api.event.on("form.created", (event) => {
|
api.event.on("form.created", (event) => {
|
||||||
if (event.data.form.sessionID === "global") return
|
const key = formKey(event, event.data.form.id)
|
||||||
if (forms.has(event.data.form.id)) return
|
if (forms.has(key)) return
|
||||||
forms.add(event.data.form.id)
|
forms.add(key)
|
||||||
notify(api, event.data.form.sessionID, "Input needs response", "question")
|
notify(api, event.data.form.sessionID, "Input needs response", "question")
|
||||||
})
|
})
|
||||||
|
|
||||||
api.event.on("form.replied", (event) => {
|
api.event.on("form.replied", (event) => {
|
||||||
forms.delete(event.data.id)
|
forms.delete(formKey(event, event.data.id))
|
||||||
})
|
})
|
||||||
|
|
||||||
api.event.on("form.cancelled", (event) => {
|
api.event.on("form.cancelled", (event) => {
|
||||||
forms.delete(event.data.id)
|
forms.delete(formKey(event, event.data.id))
|
||||||
})
|
})
|
||||||
|
|
||||||
api.event.on("question.asked", (event) => {
|
api.event.on("question.asked", (event) => {
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,11 @@ export function Session() {
|
||||||
...(data.session.form.list("global", location()) ?? []),
|
...(data.session.form.list("global", location()) ?? []),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
const formKey = createMemo(() => {
|
||||||
|
const form = forms()[0]
|
||||||
|
if (!form) return
|
||||||
|
return JSON.stringify([form.location?.directory, form.location?.workspaceID, form.id])
|
||||||
|
})
|
||||||
const [composer, setComposer] = createStore({
|
const [composer, setComposer] = createStore({
|
||||||
open: false,
|
open: false,
|
||||||
tab: undefined as string | undefined,
|
tab: undefined as string | undefined,
|
||||||
|
|
@ -261,6 +266,7 @@ export function Session() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await data.session.form.refresh("global", info.location)
|
await data.session.form.refresh("global", info.location)
|
||||||
|
if (route.sessionID !== sessionID) return
|
||||||
project.workspace.set(info.location.workspaceID)
|
project.workspace.set(info.location.workspaceID)
|
||||||
editor.reconnect(info.location.directory)
|
editor.reconnect(info.location.directory)
|
||||||
if (route.sessionID === sessionID && scroll) scroll.scrollBy(100_000)
|
if (route.sessionID === sessionID && scroll) scroll.scrollBy(100_000)
|
||||||
|
|
@ -932,17 +938,17 @@ export function Session() {
|
||||||
<box flexShrink={0}>
|
<box flexShrink={0}>
|
||||||
<Composer
|
<Composer
|
||||||
sessionID={route.sessionID}
|
sessionID={route.sessionID}
|
||||||
open={composer.open || !!session()?.parentID}
|
open={composer.open || (!!session()?.parentID && forms().length === 0)}
|
||||||
defaultTab={composer.tab ?? (session()?.parentID ? "subagents" : undefined)}
|
defaultTab={composer.tab ?? (session()?.parentID ? "subagents" : undefined)}
|
||||||
onClose={() => setComposer("open", false)}
|
onClose={() => setComposer("open", false)}
|
||||||
/>
|
/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={composer.open || !!session()?.parentID}>{null}</Match>
|
<Match when={composer.open || (!!session()?.parentID && forms().length === 0)}>{null}</Match>
|
||||||
<Match when={permissions().length > 0}>
|
<Match when={permissions().length > 0}>
|
||||||
<PermissionPrompt request={permissions()[0]} directory={session()?.location.directory} />
|
<PermissionPrompt request={permissions()[0]} directory={session()?.location.directory} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={forms().length > 0}>
|
<Match when={forms().length > 0}>
|
||||||
<Show when={forms()[0]?.id} keyed>
|
<Show when={formKey()} keyed>
|
||||||
{(_) => {
|
{(_) => {
|
||||||
const form = forms()[0]
|
const form = forms()[0]
|
||||||
return form ? <FormPrompt form={form} /> : null
|
return form ? <FormPrompt form={form} /> : null
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,11 @@ const formNotification: TuiAttentionNotifyInput = {
|
||||||
sound: { name: "question", when: "always" },
|
sound: { name: "question", when: "always" },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const globalFormNotification: TuiAttentionNotifyInput = {
|
||||||
|
...formNotification,
|
||||||
|
title: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
const permissionNotification: TuiAttentionNotifyInput = {
|
const permissionNotification: TuiAttentionNotifyInput = {
|
||||||
title: "Demo session",
|
title: "Demo session",
|
||||||
message: "Permission needs input",
|
message: "Permission needs input",
|
||||||
|
|
@ -173,12 +178,12 @@ describe("internal notifications TUI plugin", () => {
|
||||||
expect(harness.notifications).toEqual([formNotification, questionNotification, permissionNotification])
|
expect(harness.notifications).toEqual([formNotification, questionNotification, permissionNotification])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("ignores global forms until the TUI can render them", async () => {
|
test("notifies for global forms once the TUI can render them", async () => {
|
||||||
const harness = await setup()
|
const harness = await setup()
|
||||||
|
|
||||||
harness.emit({ id: "event-1", created: 0, type: "form.created", data: { form: form("form-1", "global") } })
|
harness.emit({ id: "event-1", created: 0, type: "form.created", data: { form: form("form-1", "global") } })
|
||||||
|
|
||||||
expect(harness.notifications).toEqual([])
|
expect(harness.notifications).toEqual([globalFormNotification])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("dedupes pending forms, questions, and permissions until they are resolved", async () => {
|
test("dedupes pending forms, questions, and permissions until they are resolved", async () => {
|
||||||
|
|
|
||||||
|
|
@ -914,13 +914,23 @@ test("tracks global forms by location", async () => {
|
||||||
expect(data.session.form.list("global", { directory }) ?? []).toEqual([])
|
expect(data.session.form.list("global", { directory }) ?? []).toEqual([])
|
||||||
|
|
||||||
events.emit({
|
events.emit({
|
||||||
id: "evt_form_replied_global",
|
id: "evt_form_created_global_default",
|
||||||
created: 1,
|
created: 1,
|
||||||
|
location: { directory },
|
||||||
|
type: "form.created",
|
||||||
|
data: { form: { id: "frm_global", sessionID: "global", mode: "form", fields: [] } },
|
||||||
|
})
|
||||||
|
await wait(() => data.session.form.list("global", { directory })?.length === 1)
|
||||||
|
|
||||||
|
events.emit({
|
||||||
|
id: "evt_form_replied_global",
|
||||||
|
created: 2,
|
||||||
location: other,
|
location: other,
|
||||||
type: "form.replied",
|
type: "form.replied",
|
||||||
data: { id: "frm_global", sessionID: "global", answer: {} },
|
data: { id: "frm_global", sessionID: "global", answer: {} },
|
||||||
})
|
})
|
||||||
await wait(() => data.session.form.list("global", other)?.length === 0)
|
await wait(() => data.session.form.list("global", other)?.length === 0)
|
||||||
|
expect(data.session.form.list("global", { directory })?.map((form) => form.id)).toEqual(["frm_global"])
|
||||||
} finally {
|
} finally {
|
||||||
app.renderer.destroy()
|
app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue