diff --git a/.changeset/stabilize-plugin-action-clicks.md b/.changeset/stabilize-plugin-action-clicks.md new file mode 100644 index 0000000..0354723 --- /dev/null +++ b/.changeset/stabilize-plugin-action-clicks.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Keep action-palette plugin actions responsive when they change workspace tools or routes. diff --git a/src/client/src/components/ActionPalette.ts b/src/client/src/components/ActionPalette.ts index 623f8b2..b4f10e4 100644 --- a/src/client/src/components/ActionPalette.ts +++ b/src/client/src/components/ActionPalette.ts @@ -8,7 +8,7 @@ import { actionPaletteStyles } from "./shared"; @customElement("action-palette") export class ActionPalette extends LitElement { @property({ attribute: false }) actions: AppAction[] = []; - @property({ attribute: false }) onRun?: (actionId: string) => void; + @property({ attribute: false }) onRun?: (action: AppAction) => void; @property({ attribute: false }) onCancel?: () => void; @query("input") private input?: HTMLInputElement; @state() private queryText = ""; @@ -89,7 +89,7 @@ export class ActionPalette extends LitElement { } private run(action: AppAction) { - this.onRun?.(action.id); + this.onRun?.(action); } static override styles = actionPaletteStyles; diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index b86b6b8..779e6f2 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -20,7 +20,7 @@ import { themePackPlugin } from "../plugins/themes"; import { loadExternalPlugins } from "../plugins/external"; import { PluginRegistry } from "../plugins/registry"; import { queryNamespace, readNamespacedString } from "../namespacedQueryArgs"; -import { readRoute, writeRoute } from "../route"; +import { readRoute, writeRoute, type AppRoute } from "../route"; import "./ProjectList"; import "./WorkspaceList"; import "./SessionList"; @@ -229,16 +229,33 @@ export class PiWebApp extends LitElement { const selectedDiffPath = readNamespacedString(queryNamespace("core:workspace.git"), "diff"); this.setState({ workspaceTool: route.tool ?? this.state.workspaceTool, mainView: route.view ?? this.defaultRouteView(), selectedFilePath, selectedDiffPath }); if (route.projectId === undefined || route.projectId === "") return; + if (this.routeMatchesCurrentSelection(route)) { + await this.refreshRestoredWorkspaceTool(route.tool, selectedFilePath); + this.git.updatePolling(); + return; + } const project = this.state.projects.find((p) => p.id === route.projectId); if (!project) return; await this.workspaces.selectProject(project, { workspaceId: route.workspaceId, sessionId: route.sessionId, updateUrl }); this.setState({ selectedFilePath, selectedDiffPath }); - if (route.tool === "core:workspace.files") await this.files.refreshFiles(); - if (route.tool === "core:workspace.files" && selectedFilePath !== undefined) await this.files.restoreFile(selectedFilePath); - if (route.tool === "core:workspace.git") await this.git.refreshGit(); + await this.refreshRestoredWorkspaceTool(route.tool, selectedFilePath); this.git.updatePolling(); } + private routeMatchesCurrentSelection(route: AppRoute): boolean { + return route.workspaceId !== undefined + && route.workspaceId !== "" + && this.state.selectedProject?.id === route.projectId + && this.state.selectedWorkspace?.id === route.workspaceId + && this.state.selectedSession?.id === route.sessionId; + } + + private async refreshRestoredWorkspaceTool(tool: QualifiedContributionId | undefined, selectedFilePath: string | undefined): Promise { + if (tool === "core:workspace.files") await this.files.refreshFiles(); + if (tool === "core:workspace.files" && selectedFilePath !== undefined) await this.files.restoreFile(selectedFilePath); + if (tool === "core:workspace.git") await this.git.refreshGit(); + } + private async withChatScrollTransition(action: () => Promise) { this.chatView?.saveScrollPosition(); await action(); @@ -518,9 +535,14 @@ export class PiWebApp extends LitElement { }; } - private runAction(actionId: string) { - const action = this.getActions().find((candidate) => candidate.id === actionId && candidate.enabled !== false); - if (action !== undefined) void action.run(); + private runAction(action: AppAction): void { + void Promise.resolve() + .then(() => action.run()) + .catch((error: unknown) => { + const message = error instanceof Error ? error.message : String(error); + console.warn(`Action failed: ${action.id}`, error); + this.setState({ error: `Action failed: ${message}` }); + }); } private async openModelDialog() { @@ -778,7 +800,7 @@ export class PiWebApp extends LitElement { ` : html`
Select or start a session.
`} ${this.renderWorkspacePanel()} - ${state.actionPaletteOpen ? html` { this.setState({ actionPaletteOpen: false }); this.runAction(actionId); }} .onCancel=${() => { this.setState({ actionPaletteOpen: false }); }}>` : null} + ${state.actionPaletteOpen ? html` { this.setState({ actionPaletteOpen: false }); this.runAction(action); }} .onCancel=${() => { this.setState({ actionPaletteOpen: false }); }}>` : null} ${state.projectDialogOpen ? html` this.projects.addProject(path, create)} .onCancel=${() => { this.setState({ projectDialogOpen: false }); }}>` : null} ${state.themeDialog !== undefined ? html` { this.pickTheme(value); }} .onCancel=${() => { this.setState({ themeDialog: undefined }); }}>` : null}