fix(canvas): optimistic plugin install so the UI flips to "Installed" instantly
After clicking Install, the button reverted from "Installing..." → "Install" the moment the POST returned, then sat there for ~15s before the green "Installed" tag appeared. The 15s gap is PLUGIN_RELOAD_DELAY_MS — we delay the GET /workspaces/:id/plugins refetch to wait for the workspace to restart (the listing handler returns [] while the container is restarting because findRunningContainer comes up empty). Uninstall already does optimistic local-state mutation (line 244 prior to this commit) so the green tag → install button transition is instant. Install was the inconsistent half — push the registry entry into `installed` immediately after POST returns 200 and let the delayed refetch reconcile. The optimistic record uses the registry entry's metadata (name, version, description, tags, runtimes, skills) and sets supported_on_runtime=true. If reconciliation later disagrees (server filter, install actually failed at the runtime layer), the refetch overwrites the local record. Worst case is a brief 15s window where we show "Installed" for a plugin that won't load — same window the user previously experienced as "stuck on Install button" — but flipped to the correct expected state. Custom-source installs (github://, etc.) don't have a registry entry to use, so they keep the old behavior of waiting for the refetch. Most users install from the registry list in the UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
176b703dbc
commit
deedb5eff6
@ -213,12 +213,35 @@ export function SkillsTab({ workspaceId, data }: Props) {
|
||||
// Install always goes through the source-based API. For registry
|
||||
// plugins we build the local:// source on the fly; custom sources
|
||||
// (github://, clawhub://, …) are typed into the input below.
|
||||
const installFromSource = async (source: string, labelOverride?: string) => {
|
||||
//
|
||||
// Optional `optimistic` parameter mirrors the uninstall flow's local
|
||||
// state mutation. Without it, the user sees the button revert from
|
||||
// "Installing..." → "Install" the instant the POST returns, and the
|
||||
// green "Installed" tag doesn't appear for ~15s while we wait out
|
||||
// PLUGIN_RELOAD_DELAY_MS for the workspace restart before refetching.
|
||||
// 15s of staring at the same button feels broken. Pushing the
|
||||
// registry entry into `installed` immediately makes the UI reflect
|
||||
// the install instantly; the delayed loadInstalled() reconciles
|
||||
// anything we got wrong (or any server-side filtering we don't
|
||||
// know about locally).
|
||||
const installFromSource = async (
|
||||
source: string,
|
||||
labelOverride?: string,
|
||||
optimistic?: PluginInfo,
|
||||
) => {
|
||||
const label = labelOverride ?? source;
|
||||
setInstalling(label);
|
||||
try {
|
||||
await api.post(`/workspaces/${workspaceId}/plugins`, { source });
|
||||
showToast(`Installed ${label} — restarting workspace`, "success");
|
||||
if (optimistic && mountedRef.current) {
|
||||
setInstalled((prev) =>
|
||||
prev.some((p) => p.name === optimistic.name)
|
||||
? prev
|
||||
: [...prev, { ...optimistic, supported_on_runtime: true }],
|
||||
);
|
||||
setInstalledLoaded(true);
|
||||
}
|
||||
reloadTimerRef.current = setTimeout(() => loadInstalled(), PLUGIN_RELOAD_DELAY_MS);
|
||||
} catch (e) {
|
||||
showToast(e instanceof Error ? e.message : "Install failed", "error");
|
||||
@ -227,7 +250,10 @@ export function SkillsTab({ workspaceId, data }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstall = (pluginName: string) => installFromSource(`local://${pluginName}`, pluginName);
|
||||
const handleInstall = (pluginName: string) => {
|
||||
const entry = registry.find((p) => p.name === pluginName);
|
||||
return installFromSource(`local://${pluginName}`, pluginName, entry);
|
||||
};
|
||||
|
||||
const handleInstallCustom = async () => {
|
||||
const source = customSource.trim();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user