fix(plugins): composer commands run on current state, report failures, and reach the overflow sheet

Draft commands read the state captured at render, so a plugin's
`await setProvider(); await setModel()` acted on a stale provider. The draft
form and options are now plain stores that commands read live, and
`setProvider`/`setModel` reject an unavailable provider or a model the loaded
catalog does not list. Draft `options` include the plugin's pending edits.

Shared composer controls own the pending state and show a rejected command as
an error toast, like the built-in controls. Callback payloads are objects
(`{ value }`, `onPress(event)`) so they can grow without breaking plugins.

The overflow sheet's content portals to the app root and lost the composer's
agent, input, toast and query contexts, so plugin items drew nothing in it.
`useComposerSheetBridge` carries them across.
This commit is contained in:
Mohamed Boudra
2026-11-02 05:59:32 +01:00
parent abd4d2c1ea
commit d92b16afbf
27 changed files with 1005 additions and 208 deletions
+2 -2
View File
@@ -235,8 +235,8 @@ describe("plugin composer SDK boundary", () => {
composer.setMetadata({ fn: () => {} });
return (
<>
<Toggle title="Flag" icon="Flag" value={flag} onValueChange={(next: boolean) => composer.setMetadata({ flag: next })} />
<Select title="Size" icon="Gauge" value="a" options={[{ value: "a", label: "A" }, { value: "b", label: "B" }]} onValueChange={(next: "a" | "b") => {}} />
<Toggle title="Flag" icon="Flag" value={flag} onValueChange={({ value }: { value: boolean }) => composer.setMetadata({ flag: value })} />
<Select title="Size" icon="Gauge" value="a" options={[{ value: "a", label: "A" }, { value: "b", label: "B" }]} onValueChange={({ value }: { value: "a" | "b" }) => {}} />
<Action title="Insert" icon="Plus" onPress={() => composer.setPrompt("")} />
</>
);
+8 -3
View File
@@ -193,17 +193,22 @@ export interface ComposerControlProps {
icon: PluginIcon;
disabled?: boolean;
}
/**
* What a control's callback receives. A returned promise marks the control pending until it
* settles, and Paseo reports a rejection to the user.
*/
export interface ToggleProps extends ComposerControlProps {
value: boolean;
onValueChange(value: boolean): void;
onValueChange(event: { value: boolean }): void | Promise<void>;
}
export interface SelectProps<Value extends string = string> extends ComposerControlProps {
value: Value;
options: readonly { value: Value; label: string; description?: string }[];
onValueChange(value: Value): void;
onValueChange(event: { value: Value }): void | Promise<void>;
}
export interface ActionPressEvent {}
export interface ActionProps extends ComposerControlProps {
onPress(): void;
onPress(event: ActionPressEvent): void | Promise<void>;
}
/**
* Composer controls. Render them from a composer item's `Component`. Paseo draws them inline when