Uh oh!
There was an error while loading. Please reload this page.
feat(emcn/toast): toast redesign — intent variants, stacking, hover reveal, dismiss-all - #4909
Conversation
…k, hover text reveal, dismiss-all Component (apps/sim/components/emcn/components/toast/toast.tsx): - Variants default/info/success/warning/error, each with a distinct outline icon (CircleAlert/TriangleAlert/CircleCheck/Info/Bell) rendered inline with the message in a neutral color — no badge; intent reads from icon + copy. - Stacking modeled on Sonner/Base-UI: a collapsed pile that fans open upward only when the cards are hovered. One fixed-duration expo-out tween drives all cards so rapid arrivals move in unison (no lagging card); cards arrive collapsed (expand is scoped to a wrapper around the cards, not the dismiss control). - Title vs subtext hierarchy: message is a medium, primary-color title; the optional description is lighter/smaller subtext. - Truncated text reveals its hidden lines on hover (RevealText): only the previously hidden lines blur in; the card height tracks the content so the action button stays pinned (no clipping). Larger bottom gradient fade hints at more text. - Concentric corner radius (16px = chip 8px + 8px padding); single-line cards use a tighter 12px so they don't read as pills. - Dismiss-all control: a small circular chip just outside the stack's bottom-left, shown at 2+ toasts. Linear auto-dismiss ring that restarts on each new arrival, pauses on hover, click to clear all; spring 'pop' entrance. - Bug fixes: route-scoped clearing (toasts no longer trail across navigation), dedup of the add/update double-fire, actionable toasts persist by default. Source/usage: - stores/terminal/console/store.ts: notifyBlockError now passes the block name as the title and the error as the description (title/subtext), plus the dedup window. - app/playground/page.tsx: Toast section added to the EMCN gallery. - New EMCN icons: circle-alert, circle-check, info, triangle-alert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Call-site behavior: block errors use block name as title and error as description, with ~1.5s dedup per execution; workspace reconnect/offline toasts set Playground wraps in Reviewed by Cursor Bugbot for commit cedc175. Configure here. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Greptile SummaryFull redesign of the EMCN toast system: five intent variants (default/info/success/warning/error) with outline icons, Sonner-style collapsed stacking that fans open on hover, hover-reveal for truncated text via
Confidence Score: 5/5Safe to merge — the toast rewrite is self-contained, the animation and timer logic handles all edge cases correctly, and the callsite sweep is consistent. The timer logic (stack ring vs. per-toast timers), height reconciliation, persistent-toast pinning, and route-scoped clearing all behave correctly under the scenarios I traced. The only comment is a housekeeping note about editing globals.css, which is a cleanup deletion of dead keyframe code and has no runtime impact. No files require special attention beyond the globals.css edit noted in the review. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[toast.error / toast.success / etc.] --> B[addToast]
B --> C{stack length > STACK_LIMIT?}
C -- No --> D[append to toasts]
C -- Yes --> E[evict oldest auto-dismissable]
E --> D
D --> F[arrivalCount++]
F --> G{toasts.length >= 2?}
G -- Yes --> H[StackDismiss ring visible]
G -- No --> I[per-toast timer set]
H --> J{hasPersistentToast?}
J -- No --> K[ring auto-fires in 6s]
J -- Yes --> L[ring click-only, no countdown]
K --> M[dismissAllToasts]
I --> N[dismissToast after duration]
P[hover stack] --> Q[expanded = true, timers cleared, ring paused]
Q --> R[unhover]
R --> S{stackAutoDismiss?}
S -- Yes --> T[ring resumes]
S -- No --> U[per-toast timers restart full duration]
V[navigation] --> W[dismissRouteScopedToasts]
W --> X{persistAcrossRoutes?}
X -- Yes --> Y[toast survives]
X -- No --> Z[toast removed]
Reviews (7): Last reviewed commit: "fix(emcn/toast): reset expanded when the..." | Re-trigger Greptile |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The toast redesign added an `Info` icon, but the emcn barrel already re-exports an `Info` component (`export * from './components'` + `export * from './icons'`), so the top-level `@/components/emcn` had a duplicate `Info` export (TS2308). Rename the icon to CircleInfo — matching its circle-shaped siblings (CircleAlert, CircleCheck) and resolving the collision. The icon is consumed only by toast.tsx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… heights H1 (functional): a stack of 2+ toasts arms the StackDismiss ring, whose 6s countdown called dismissAllToasts() — wiping persistent (duration<=0) actionable toasts like 'Fix in Copilot' before the user could react. Add an `autoDismiss` flag (false when any toast is persistent) that suppresses the auto-countdown while keeping the manual dismiss-all button. H2 (memory): stack-limit eviction (slice) dropped the oldest toast without clearing its `heights` entry, leaking entries over a session. Reconcile heights to live toast ids, mirroring the timer effect's stale-entry cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…et, teardown - Req 6: per-variant icon tint (error/warning/success/info) from the shared intent palette, so error vs info is distinguishable pre-attentively in a mixed stack instead of only by reading the copy. Default stays neutral. - H3: wrap the stack in AnimatePresence so clear-all / route-change fades the frozen stack out instead of cutting abruptly; per-card exits still play for single dismissals. No pointer-events change (no hover regression). - Countdown reset now keys on a monotonic arrival counter, so dismissing the front card no longer restarts the whole stack's auto-dismiss timer. - Provider teardown only nulls the global toast bindings if they're still its own, guarding against an out-of-order unmount with a second provider. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the toast spec (modal errors stay in the modal; only page errors go to the corner), route two in-modal failures inline: - add-people-modal: failures now render via ChipModalError in the modal body (the modal stays open with the failed emails), not toast.error. - slack setup wizard: a clipboard-copy failure shows inline beneath the copy button (mirroring the existing 'copied' state), not a toast. Left as-is (verified correct, not violations): credential-detail save error (a routed page, not a modal — page toast is correct) and import-csv success (fires as the dialog closes — inline isn't possible). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the modal-error migration: the create-drain form's submit failure now renders via ChipModalError in the modal body (the modal stays open) instead of a toast.error that escaped to the screen corner. The success-on-close toast and the page-level drain-row action toasts are unchanged (correct). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ention
Remove inline // and JSX {/* */} comments (the project documents with TSDoc
only) and condense multi-paragraph TSDoc to 1-3 lines across toast.tsx, plus
the stray inline comments in the console store and slack wizard. Behavior and
code are unchanged — verified the non-comment source is byte-identical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>…t tints
- Positioning: the workflow-list index route /workspace/[id]/w renders the
panel + terminal but failed includes('/w/'), so a toast there rendered under
the panel. Match /\/w(\/|$)/ so both the editor and the index inset.
- Tint: unify all four intent icons onto the badge palette and use
--badge-success-text (a darker green) so success isn't ~1.9:1 / washed out
on the light card; error/success now match warning/info's token family.
- Gate the card enter animation on reduceMotion for consistency with the rest
of the file (behaviour was already correct via duration 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>… alert() The upgrade flow used native alert() for upgrade / switch-plan / switch-interval failures. Route them through the unified toast (toast.error) like the rest of the app — it's inside the ToastProvider tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes removing what the framer-motion toast redesign orphaned: the CountdownRing re-export and the zero-reference notification-/toast-* keyframes.
waleedlatif1
commented
Jun 9, 2026
waleedlatif1
commented
Jun 9, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
addConsole passes a normalizeConsoleError'd error while updateConsole passed the raw update.error, so the dedup key (String(error)) differed between the two paths and the duplicate block-error toast could still slip through. Normalize inside notifyBlockError so both paths produce the same key and description. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Suppressing the stack auto-countdown when a persistent toast is present (so it can't clear the action) also stopped timed toasts in that stack from expiring. Fall back to per-toast timers whenever the stack countdown isn't auto-firing, so timed toasts auto-dismiss individually while the persistent one stays. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rror The inline ChipModalError dropped the server/validation detail the toast used to show via description. Fold getErrorMessage(firstError) back into the message so users still see why the add failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waleedlatif1
commented
Jun 9, 2026
waleedlatif1
commented
Jun 9, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
slice(-STACK_LIMIT) dropped the oldest card on a 4th arrival even if it was a persistent (actionable) error meant to stay until dismissed. Evict the oldest auto-dismissable toast instead, falling back to the oldest only when every toast is persistent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waleedlatif1
commented
Jun 9, 2026
waleedlatif1
commented
Jun 9, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
The dedup keyed on block+message for 1.5s, but the toast stack clears on navigation — so a genuinely new same-block error within that window (e.g. a re-run, or after navigating) was suppressed with no replacement toast. Key the dedup on the block execution (getBlockExecutionKey) so only the same execution's addConsole/updateConsole double-fire collapses; a different execution always toasts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waleedlatif1
commented
Jun 9, 2026
waleedlatif1
commented
Jun 9, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
…AcrossRoutes) The route-clear dismissed every toast on navigation, including the persistent realtime connection/reconnect status toast — which then never re-showed (the provider's id ref short-circuited). Add a persistAcrossRoutes flag: the navigation clear now keeps flagged toasts and only drops route-scoped ones, and the WorkspacePermissionsProvider status toasts set it. Page-scoped toasts (including actionable block errors) still clear on navigation as before. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waleedlatif1
commented
Jun 9, 2026
waleedlatif1
commented
Jun 9, 2026
@cursor review |
Uh oh!
There was an error while loading. Please reload this page.
The hover wrapper unmounts without firing mouse-leave when the last toast goes (dismiss / clear-all / navigation), so expanded could stay true and stop the next toasts from auto-dismissing. Force expanded false whenever the stack is empty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waleedlatif1
commented
Jun 9, 2026
waleedlatif1
commented
Jun 9, 2026
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cedc175. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Reconciles staging's props-driven ChipModalFooter/ChipConfirmModal refactor (#4905) with this branch's chip-chrome consolidation: - chip-modal internals render Cancel/dismiss as bare default chips ('filled' is no longer a public Chip variant); footer action variant unions narrowed to 'primary' | 'destructive' - consumers take staging's footer/header/ChipConfirmModal structure plus this branch's ChipModalField bodies and chrome - staging features kept intact: stable column ids (#4898), async CSV imports (#4927), mothership v0.2 file-viewer refactor (#4923), toast redesign (#4909), suggested skills (#4912) - integration-skills-section 'Added' chip changed to the bare default variant to satisfy the new Chip API
Re-cut cleanly off latest
staging(the original #4907 was accidentally branched offimprovement/platform, which is now merged into staging — hence its conflicts). This is the same toast work as commit b92ea33, applied conflict-free on staging.What changed
notifyBlockErrorsplits block name (title) / error (subtext)./playground→ Toast section).