fix(web): toasts survive an open dialog; empty-state actions can be gated

Both from the final review, and the first one overturns a call I got wrong.

The aria-hidden sweep that makes aria-modal true also swallowed the toasts.
ToastProvider renders inside the app root, and every modal-raised confirmation
is toasted *before* its dialog closes — "Saved …", "Deleted …", "Removed …" —
so each one was inserted into a hidden subtree and never announced. Un-hiding
a live region afterwards does not replay what it missed. The toast layer is
portalled to the body carrying the dialog-layer attribute, which exempts it
from the sweep, and sits above the dialog: a toast explaining why a dialog's
action failed is no use behind it.

EmptyState's action was narrower than the call site it replaced. The old
first-workflow button carried loading={isPending}; the new one carried
nothing, so a double click created two workflows. The action is a union now —
a link takes no pending state, a handler takes loading and disabled.
This commit is contained in:
2026-08-10 10:01:47 +01:00
parent fe1dfe472a
commit e434beec7a
4 changed files with 54 additions and 11 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ export default function WorkflowsPage() {
<path strokeLinecap="round" strokeLinejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
}
action={{ label: "Create your first workflow", onClick: () => create() }}
action={{ label: "Create your first workflow", onClick: () => create(), loading: isPending }}
/>
}
>