Skip to content
Development

Toast + undo for Greenhouse's vault action

By Victor Da Luz
svelteaccessibilitytauridev-loggreenhouse

Greenhouse’s vault action used to be a one-click, no-going-back kind of thing. You click Vault, the card vanishes, and that’s it. On the project detail screen, Vault sits right next to “I worked on this” and “Show in Finder,” so a mis-click is realistic. Recovery meant opening the Vault view, finding the item, and clicking Rescue. Not hard, but not obvious either, and the app gave you zero feedback that anything had even happened.

The fix was a small toast system: vault something, get a card at the bottom of the screen saying “X moved to the vault” with an Undo button. Click Undo, it calls the same rescue_item command the Vault view already uses. Nothing new on the Rust side at all, this was a pure frontend addition.

Two things made it less trivial than “add a div.”

First, this is the app’s first transient-feedback surface of any kind. Every existing banner in the codebase is a role="alert" that appears when something fails. There was no precedent for a success toast, and no aria-live region anywhere. I almost mounted the toast as a self-contained component with its own role="status", conditionally rendered only when there’s a message to show. That’s wrong: an aria-live region only announces changes to a node that already exists in the DOM. If you create the region and its content in the same instant, screen readers never get the “before” state to diff against, so the announcement just doesn’t fire. The fix is to keep a persistent, always-rendered role="status" aria-live="polite" host in the dashboard (empty when idle), and mount the actual toast content inside it. The host never unmounts; only its children change.

Second, one of the three places you can vault from is the project detail view, which has an inline rename feature. If you rename a project and then vault it in the same visit, which name should the toast show? The view closes itself right after vaulting, so it can’t hold onto toast state, it has to hand the message up to the dashboard via a callback. My first pass handed up the name the view was originally opened with. A review pass caught that this goes stale the moment you rename: vault right after a rename and the toast would show the old name. The actual fix reads the current detail state at the moment of vaulting, not the prop the view was created with.

Verification here mattered more than usual because “does the announcement fire” and “does Undo actually restore the item” aren’t things a component test alone proves convincingly. I ran the full thing through Greenhouse’s real WebDriver harness: captured a real idea, clicked the real Vault button, confirmed the toast and Undo rendered inside the real aria-live host, clicked Undo, and confirmed the item actually came back via a real IPC round trip to rescue_item, not a mock.

Small feature, but a good reminder that “just add a toast” has a couple of real traps hiding in it: the DOM-timing rule for live regions, and prop staleness whenever a child view can outlive the data it was opened with.

Related reading