When a workspace action (like Save) opens a confirmation modal, using the legacy waiting state causes incorrect UI behavior (e.g., showing a success tick when a user cancels). To fix this, implement the deferred-feedback contract using UmbWorkspaceActionExecutionOptions.
1. Action Side
Subclass UmbWorkspaceActionBase, call this.setExecuting(false) in the constructor to opt-in, and use onActionStarting to trigger the loading state only when real work begins.
export class MyWorkspaceAction extends UmbWorkspaceActionBase {
constructor(host: UmbControllerHost, args: UmbWorkspaceActionArgs) {
super(host, args);
// Opt in — exposes `isExecuting` so the button waits for real work.
this.setExecuting(false);
}
override async execute() {
try {
await this._workspaceContext?.doTheirThing({
onActionStarting: () => this.setExecuting(true),
});
} finally {
this.setExecuting(false);
}
}
}
2. Context Side
In your workspace-context method, await the modal result. If the user cancels (result is falsy), return silently. If they proceed, call notifyWorkspaceActionStarting(options) before performing the actual work.
public async doTheirThing(options?: UmbWorkspaceActionExecutionOptions): Promise<void> {
const result = await umbOpenModal(this, MY_CONFIRM_MODAL, { /* ... */ })
.catch(() => undefined);
if (!result) return; // user cancelled — silent return, no spinner, no tick
notifyWorkspaceActionStarting(options);
// real work below
await this.#repository.doTheirThing(result);
}
export class MyWorkspaceAction extends UmbWorkspaceActionBase {
constructor(host: UmbControllerHost, args: UmbWorkspaceActionArgs) {
super(host, args);
// Opt in — exposes `isExecuting` so the button waits for real work.
this.setExecuting(false);
}
override async execute() {
try {
await this._workspaceContext?.doTheirThing({
onActionStarting: () => this.setExecuting(true),
});
} finally {
this.setExecuting(false);
}
}
}
public async doTheirThing(options?: UmbWorkspaceActionExecutionOptions): Promise<void> {
const result = await umbOpenModal(this, MY_CONFIRM_MODAL, { /* ... */ })
.catch(() => undefined);
if (!result) return; // user cancelled — silent return, no spinner, no tick
notifyWorkspaceActionStarting(options);
// real work below
await this.#repository.doTheirThing(result);
}