Skip to content

feat(ui): animated top-right toast stack - #820

Merged
pat-lewczuk merged 6 commits into
mainfrom
feat/animated-top-right-toasts
Aug 11, 2026
Merged

pat-lewczuk merged 6 commits into
mainfrom
feat/animated-top-right-toasts

Conversation

@pat-lewczuk

@pat-lewczuk pat-lewczuk commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Closes #818
Tracking plan: .ai/runs/2026-08-09-animated-top-right-toasts.md
Status: complete

🎯 Goal

  • Move the cockpit's toast stack from bottom-centre to the top-right corner and give it a real enter/exit transition, so a toast no longer covers the controls a user reaches for next — the reported case is the "Added to the queue — PR docs(specs): Linked-PR chips on the GitHub Issues list #816" toast sitting on top of "Run again" and the "View task →" link — and no longer appears and vanishes with no transition, which reads as a rendering glitch rather than a notification.

What Changed

  • packages/web/src/components/ui/toaster.tsx — the store now dismisses in two phases. The old toast() dropped its item from the module store the instant the 5s timer fired, so the node unmounted before any exit animation could run; an exit transition was therefore impossible with CSS alone. ToastItem gains an exiting flag: at TOAST_MS the item is re-published as exiting, and a second timer removes it EXIT_MS (200ms) later. Every pending handle is tracked in a module set so resetToasts() cancels timers as well as clearing the queue — otherwise one test's orphaned timer would publish into the next test's store.
  • packages/web/src/components/ui/toaster.tsx — the stack is anchored top-right and animated. The wrapper moves from inset-x-0 bottom-[…] items-center to top-[calc(16px+env(safe-area-inset-top))] right-[calc(16px+env(safe-area-inset-right))] items-end, each item caps its width (max-w-[min(360px,calc(100vw-32px))]) so a long message wraps instead of spanning the viewport, and the wrapper moves from z-50 to z-[60]. That last part matters: dialogs do fire toasts, Radix portals its overlay to the end of <body>, and at an equal z-index the dialog paints over the toast — rare with a bottom-centre stack, common with a top-right one. Each toast emits data-state={open|closed} and carries tw-animate-css enter/exit classes (animate-in/fade-in-0/slide-in-from-right-4 and their data-[state=closed] counterparts) gated on motion-safe:, so prefers-reduced-motion: reduce keeps the instant appear/disappear it had before. No new dependency and no new @keyframes — the utilities come from the tw-animate-css import already in packages/web/src/styles/index.css.
  • No caller changes. <Toaster /> is still mounted once at the app root and toast() keeps its exact signature, so every call site is untouched.

🔍 Review

  • om-auto-review-pr ran in autofix mode and requested changes on one major finding: below the md breakpoint the new top-right anchor covered the app shell's own 52px mobile header, whose right end carries the run status dot and kebab — Implement: animated top-right toast stack in the cockpit #818's complaint relocated rather than fixed. Fixed in 69f27427 with a breakpoint pair (top-[calc(61px+…)] md:top-[calc(16px+…)]), measured in a real 390×844 browser at an 8px gap below the header. A minor finding (the EXIT_MSduration-200 coupling documented only in a comment) is now pinned by a test assertion. The re-review approved; the two remaining nits are documented and deliberately unchanged.

🧪 Tests

  • npm run typecheck — ✅ clean across api-client, server and web.
  • npm test — ✅ 5630 passed / 308 files (includes packages/web/src/design-guardian.test.ts).
  • npm run test:unit — ✅ 35 passed, 1 skipped.
  • npm run build — ✅ tsc + vite + check:pack (462 files, 88 under web/dist).
  • npm run test:package — ✅ 12 passed.
  • One intermittent failure was seen across five full npm test runs, in packages/cezar/src/server/open-in-app.test.ts — a file byte-identical to main, unrelated to this browser-only diff, and green 10/10 in isolation. Filed as Flaky: open-in-app.test.ts JetBrains stub assertion races under full-suite load #823 rather than absorbed silently; four of five full runs and CI on this head are green.
  • packages/web/src/components/ui/toaster.test.tsx grew from 3 tests to 5 and now locks in: the two-phase lifecycle (at 5s the node is still present with data-state="closed", at 5.2s it is gone), the top-right anchor (asserting the new classes are present and that the bottom-centre ones are gone — the regression guard for the actual ask), the data-state open→closed flip with the motion-safe: animation classes, and two toasts keeping independent clocks across the new exit timers.
  • The four new/changed tests were confirmed red against the pre-fix toaster.tsx (git stash push on the source, run, git stash pop), so none of them passes against the bug.

💥 Breaking Changes

  • None. The change is confined to the bundled cockpit UI (packages/web), which ships in lockstep with the server that serves it — BACKWARD_COMPATIBILITY.md covers the CLI surface, the /api/v1 routes and SSE vocabulary, and the .ai/cezar/ state formats, none of which are touched. The only internal contracts at stake are the data-slot="toaster" / data-slot="toast" / data-tone attributes and the role="status" live region queried by packages/web/e2e/{new-task,settings-skills,workflows}.e2e.ts, and all four are preserved byte-for-byte.

📋 Progress

See the Progress section in the tracking plan.

The cockpit's toasts rendered bottom-centre with no transition, landing on top of
the thread's action row (the reported case covered the "Run again" button) and
popping in and out in a way that read as a rendering glitch.

- Store: dismissal is now two-phase. At TOAST_MS the item is re-published with
  exiting: true instead of being dropped, and a second timer removes it after the
  exit animation window — the old single-phase drop unmounted the node before any
  exit transition could run. Pending timers are tracked so resetToasts() cancels
  them and one test's toasts cannot leak into the next.
- Renderer: the stack anchors to the top-right (top/right safe-area insets,
  items-end), each item caps its width so long messages wrap, and the wrapper
  moves to z-[60] so a toast fired from inside a dialog is not painted over by
  the Radix portal at z-50.
- Animation uses the tw-animate-css utilities already imported by styles/index.css,
  gated on motion-safe: so prefers-reduced-motion keeps the instant appear.

The data-slot, data-tone and role="status" hooks that three e2e specs query are
unchanged.

Refs #818
@pat-lewczuk pat-lewczuk added review Ready for code review feature New capability needs-qa Requires manual QA before merge labels Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

📦 npm preview published — 0.9.2-pr820.1160

Try this PR build (exact pinned version — copy-paste as-is):

npx cezar-cli@0.9.2-pr820.1160                                # cockpit at http://localhost:4321
npx cezar-cli@0.9.2-pr820.1160 run "…"                        # headless run
npx cezar-cli@0.9.2-pr820.1160 server-deploy --platform <id>  # roll a server to this exact build

Also tagged: npm install -g cezar-cli@pr-820 (moving tag for this PR).
Packages: cezar-cli@0.9.2-pr820.1160@open-mercato/cezar@0.9.2-pr820.1160@open-mercato/cezar-api-client@0.9.2-pr820.1160 (provenance attested).

@pat-lewczuk pat-lewczuk added priority-low Cosmetic or follow-up work risk-low Isolated, low blast radius labels Aug 9, 2026
@pat-lewczuk

pat-lewczuk commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-create-pr — 🏷️ label rationale

Updated after the om-auto-review-pr pass approved the PR; the pipeline label moved from review to merge-queue.

  • 🚀 merge-queue — the authoritative code-review pass ran, its one major finding was fixed in 69f27427, the re-review approved, and the full validation gate is green, so the PR has left review and is queued for merge. The QA-approval gate still holds the actual merge (see needs-qa).
  • feature — the change adds a presentation behaviour the cockpit did not have (a top-right anchored stack with a real enter/exit transition) rather than repairing something that regressed, which is how issue Implement: animated top-right toast stack in the cockpit #818 was classified when it was prepared.
  • 🧪 needs-qa — the diff changes where a toast appears on screen and how it enters and leaves, which is user-visible behaviour that only a human exercising the cockpit can fully judge (placement against real content, perceived animation speed, reduced motion, mobile safe areas). Screenshots and live measurements are attached, but they do not replace that judgement; with qaGate enabled this label blocks the merge until a QA reviewer adds qa-approved. Diff-derived QA routes are posted in their own comment.
  • 🔽 priority-low — the current toast is legible and dismisses correctly; the complaint is that it lands on the controls a user reaches for next and reads as a rendering glitch. That is cosmetic and ergonomic, not a functional break, matching the priority-low already carried by the issue.
  • 🟢 risk-low — the blast radius is one self-contained primitive plus its unit test. There is no auth, data-scoping, money, migration or shared-contract surface involved, every toast() call site is untouched, and the data-slot / data-tone / role="status" hooks that three e2e specs query are preserved byte-for-byte.
  • 🤖 in-progress — held by this run while the review and autofix loop completed; released as this run finishes.

@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-create-pr — verification

📸 UI evidence — captured against the real cockpit, booted from a production build by .ai/scripts/test-env-up.sh (CEZ_DRY_RUN=1) and driven through the repo's agent-browser provider at a 1280×800 viewport. The toast in both shots is a real one fired by a real interaction (the bookmarklets row's "Drag me to your bookmarks bar"), not injected markup.

1. The stack now sits top-right. Compare with the screenshot on #818, where the same primitive rendered bottom-centre on top of the thread's action row.

toast anchored to the top-right of the cockpit

2. A toast fired under an open dialog stays visible. The command palette is open and dimming the page behind it; the toast is not dimmed and not covered. This is the z-50z-[60] part of the change — the collision that bottom-centre placement made rare and a top-right stack would have made common. document.elementFromPoint() at the toast's centre returns the toast, not the Radix overlay.

toast painting above the open command-palette dialog

3. Measured in the live page, not asserted in jsdom. Settled geometry and computed style of the mounted toast:

What Measured
Distance from the viewport top 16px
Distance from the viewport right edge 16px
Toaster wrapper z-index 60
Enter animation enter, 0.2s
data-state flips to closed at 5012ms after the toast was fired
Exit animation exit, 0.2s
90ms into the exit opacity: 0.197, transform: matrix(1, 0, 0, 1, 12.85, 0) — fading while sliding right
After the exit window the node is removed from the DOM

The exit sample is the point of the store change: before this PR the node was gone the instant the 5s timer fired, so there was nothing left to animate.

Reduced motion: every animation utility here is emitted inside @media (prefers-reduced-motion: no-preference) (verified in the built stylesheet, packages/cezar/web/dist/assets/index-*.css), so a prefers-reduced-motion: reduce user keeps the instant appear/disappear the toast had before.

Validation gate:npm run typecheck · ✅ npm test (5630 passed / 308 files) · ✅ npm run test:unit (35 passed, 1 skipped) · ✅ npm run build · ✅ npm run test:package (12 passed).

@pat-lewczuk pat-lewczuk self-assigned this Aug 9, 2026
@pat-lewczuk pat-lewczuk added the in-progress Cezar agent is actively working this issue label Aug 9, 2026
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 Claiming this PR — starting om-auto-review-pr run (autofix mode; this run authored the PR). Started: 2026-08-09T19:29:28Z.

@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🔍 Code Review

🤖 om-auto-review-pr — review of PR #820 (feat/animated-top-right-toasts) against main, run in autofix mode because this run authored the PR.

ℹ️ GitHub refuses a formal review on one's own pull request, so this report is posted as a conversation comment rather than through the review API. The verdict below is authoritative for the pipeline label and for the autofix loop that follows it.

🎯 Summary

The PR reworks the cockpit's single toast primitive so the stack renders in the top-right corner with a real slide-and-fade transition, replacing a bottom-centre stack that had no transition at all. The interesting part is not the CSS: the old store deleted a toast from the module queue the moment its 5s timer fired, so the node unmounted before an exit animation could ever run. The PR splits dismissal into two phases — mark the item exiting, render it one beat longer, then remove it — which is the right shape for this problem and is the change that actually makes an exit transition possible. Timer handles are tracked in a module set so resetToasts() cancels them, which is what keeps the new second timer from leaking across tests. The z-layer move from z-50 to z-[60] is well-motivated and verified: Radix portals its dialog overlay to the end of <body>, so at an equal z-index the dialog wins, and a top-right stack collides with dialogs far more often than a bottom-centre one did.

Scope discipline is good — 47 changed lines in the primitive, no caller touched, toast()'s signature identical, and the data-slot / data-tone / role="status" hooks that three e2e specs query preserved byte-for-byte. No new dependency: the animation utilities come from the tw-animate-css import already present in packages/web/src/styles/index.css.

One real problem survives, and it is the same class of problem the PR was written to fix.

Verdict

Request changes — one major finding. There are no blockers, the validation gate is green, and no contract surface is touched, so the fix is a small one; the autofix loop takes it from here.

🧪 Validation Gate

Command Result Evidence
npm run typecheck ✅ pass Clean across packages/api-client, packages/cezar and packages/web.
npm test ✅ pass 5630 tests / 308 files, including packages/web/src/design-guardian.test.ts (no raw hex, no forbidden tokens introduced).
npm run test:unit ✅ pass 35 passed, 1 skipped (the node:test core-module suite).
npm run build ✅ pass tscdist/, vitepackages/cezar/web/dist/, then check:pack (462 files, 88 under web/dist).
npm run test:package ✅ pass 12 passed (packaged-CLI E2E).

CI on the head commit is green as well: Unit, build, E2E, and package, Publish npm snapshot and license/cla all report SUCCESS, and the branch is MERGEABLE with no conflicts against main. No required-status-check protection is configured on main (the API reports "Branch not protected"), so every reported check is treated as required.

Findings

🔴 Major

1. On the mobile breakpoint the new anchor puts the toast on top of the app shell's own header — the exact failure mode this PR exists to fix.packages/web/src/components/ui/toaster.tsx:89

The wrapper anchors unconditionally at top-[calc(16px+env(safe-area-inset-top))]. Below md, packages/web/src/components/app-shell.tsx:780-802 renders data-slot="mobile-top-bar" — a pt-[env(safe-area-inset-top)] header whose row is h-[52px] — and it ends with data-slot="mobile-status" pinned to its right edge (ml-auto), which is where the thread view mounts the run status dot and kebab. A toast anchored 16px below the safe-area inset lands squarely inside that 52px band, right-aligned, and covers those controls for the full 5 seconds. That is the reported complaint from #818 — "the toast sits directly over the controls a user reaches for next" — relocated from the bottom of the phone screen to the top of it, so the change is a net wash on mobile instead of a fix.

Why major rather than minor: it is a realistic path (the cockpit is used on a phone — there is an ios-sweep.e2e.ts and a whole mobile shell), it is user-visible, and it partially defeats the PR's stated purpose. It is not a blocker because nothing is broken or unsafe; the toast is still readable and still dismisses.

Fix: keep the 16px anchor from md up, and clear the mobile header below it. The header is 52px plus its border-b, so top-[calc(61px+env(safe-area-inset-top))] md:top-[calc(16px+env(safe-area-inset-top))] leaves an 8px gap under the bar on phones and changes nothing on desktop. The existing anchor test keeps passing (toContain still matches the md: form) and should gain an assertion for the mobile offset so the breakpoint pair is pinned.

🟡 Minor

2. EXIT_MS and the CSS animation duration are coupled by a comment, with nothing enforcing it.packages/web/src/components/ui/toaster.tsx:28-31 and :103

EXIT_MS = 200 is what removes the node, and motion-safe:duration-200 is what animates it; the comment at :28 says to keep them in step, but nothing fails if they drift. Raise the class to duration-300 and the toast is unmounted 100ms into a 300ms slide-out — a visibly clipped animation and exactly the kind of regression a comment does not catch. The new animation test already reads the class list, so asserting motion-safe:duration-200 there costs one line and turns the comment into a guard that points a future editor at EXIT_MS.

🔵 Nit

3. An exiting toast holds its slot in the column for the whole exit window.packages/web/src/components/ui/toaster.tsx:91-112

The item slides right and fades, but its box keeps its height in the flex-col until the removal timer fires, so a toast below it stays put and then jumps up 200ms later. The bottom-centre stack behaved the same way, so this is not a regression, and with gap-2 and a two-toast stack being rare the jump is barely perceptible. Collapsing the height as part of the exit would be the polished version; it is not worth the complexity here.

4. Each toast keeps pointer-events-auto while carrying no interactive affordance.packages/web/src/components/ui/toaster.tsx:99

For five seconds the toast swallows clicks in the top-right corner of the viewport, and there is nothing to click on it — #818 puts manual dismiss buttons explicitly out of scope, and the issue's implementation notes name this exact class, so leaving it is the right call for this PR. Flagging it only so the manual QA pass checks that nothing important sits under the stack's footprint on the views that fire toasts most.

💥 Breaking-Changes Checklist

  • Exported APIstoast(), resetToasts() and Toaster keep their exact signatures. ToastItem gains a required exiting field, but the interface is only ever constructed inside this module (grep finds no external constructor), so no consumer can be broken by it.
  • HTTP routes and response shapes — none touched; the diff is browser-only.
  • Event names / SSE vocabulary — none touched.
  • CLI flags — none touched.
  • DB schema / persisted state formats — none touched; nothing under .ai/cezar/ is read or written.
  • Config formats — none touched.
  • BACKWARD_COMPATIBILITY.md protected surfaces — verified: that document protects the CLI surface, the /api/v1 route inventory, the SSE vocabulary and the .ai/cezar/ state formats, and mentions neither the cockpit's DOM hooks nor this component. The change ships inside packages/web, whose output is an artifact of the server that serves it, so it cannot desynchronise from a client in the field.
  • Internal DOM contractsdata-slot="toaster", data-slot="toast", data-tone and role="status" all survive unchanged, which is what keeps packages/web/e2e/new-task.e2e.ts:253, settings-skills.e2e.ts:112 and workflows.e2e.ts:126 green. data-state is purely additive.

🧪 Test Coverage

Coverage for the changed behavior is present and, unusually, proven: the suite grew from 3 tests to 5, and the four new or rewritten ones were confirmed red against the pre-fix toaster.tsx (git stash push on the source, run, git stash pop) — so none of them is a green-either-way test. They cover the two-phase lifecycle (present-and-closed at 5s, gone at 5.2s), the top-right anchor asserted positively and negatively (the bottom-centre classes must be absent — the actual regression guard for #818), the data-state open→closed flip with its motion-safe: classes, and two toasts keeping independent clocks across the new exit timers, which is the part of a two-timer design most likely to break.

Gaps worth closing, both small: the EXIT_MSduration-200 coupling of finding 2, and — once finding 1 is fixed — an assertion pinning the mobile/desktop anchor pair so a future edit cannot silently drop the breakpoint. Real-browser behavior (placement, z-order above a dialog, the measured 0.2s enter/exit) is already captured in the verification comment on this PR; it is evidence, not a substitute for these unit guards.

Next

Autofix is eligible on this run, so findings 1 and 2 are being fixed now, with the full validation gate re-run and a re-review posted before the PR leaves in-progress. Findings 3 and 4 are nits and are deliberately left alone.

Review of #820 found the top-right anchor reproducing the very problem it
fixes on the mobile breakpoint: below `md` the app shell renders its own
52px header (app-shell.tsx) whose right end carries the run status dot and
kebab, and a toast anchored 16px under the safe-area inset covers exactly
those controls for the full five seconds.

- Anchor the stack below that header on phones and keep the 16px anchor from
  `md` up, where the header no longer renders.
- Pin the breakpoint pair in the anchor test so a future edit cannot drop it.
- Pin `motion-safe:duration-200` in the animation test: it and the store's
  EXIT_MS remove the node together, and until now only a comment said so.

Refs #818
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🔍 Code Review — re-review (post-autofix)

🤖 om-auto-review-pr — re-review of PR #820 at 69f27427, after the autofix loop landed the fixes for the previous pass's findings.

🎯 Summary

The two actionable findings from the first pass are fixed in one commit, and both previous blockers-in-waiting are now pinned by tests rather than by comments. The major finding — the top-right anchor covering the app shell's mobile header, which would have relocated #818's complaint rather than fixed it — is resolved with a breakpoint pair: the stack sits below the header on phones and keeps the 16px anchor from md up, where that header stops rendering. The minor finding is closed by asserting motion-safe:duration-200 in the animation test, so the animation duration and the store's EXIT_MS can no longer drift apart silently — a change to one now fails a test that names the other.

Both fixes are verified in the real cockpit, not just in jsdom: at a 390×844 viewport the toast's top edge measures 61px against a header bottom edge of 53px — an 8px gap, overlapsHeader: false — and the desktop measurements from the earlier verification comment are unchanged. The built stylesheet carries both anchor rules (top:calc(61px + env(safe-area-inset-top)) and, inside @media (width>=48rem), top:calc(16px + env(safe-area-inset-top))), so the breakpoint pair survives the Tailwind build rather than being purged.

Verdict

Approve. No blockers, no majors, no un-waivered findings remain. The two nits from the first pass (an exiting toast holding its layout slot for the 200ms exit window, and pointer-events-auto on a toast with no interactive affordance) are unchanged by choice: the first is pre-existing behavior the bottom-centre stack shared, and the second is prescribed by #818, which puts manual dismiss explicitly out of scope. Neither blocks.

🧪 Validation Gate

Command Result Evidence
npm run typecheck ✅ pass Clean across all three typechecked workspaces.
npm test ✅ pass 5630 / 5630 on the final tree. See the flake note below.
npm run test:unit ✅ pass 35 passed, 1 skipped.
npm run build ✅ pass tsc + vite + check:pack (462 files, 88 under web/dist); the new md: anchor rule is present in the emitted stylesheet.
npm run test:package ✅ pass 12 passed.

Gate honesty — one intermittent failure, investigated rather than waved off. Across five full npm test runs on this branch, one run failed a single test: packages/cezar/src/server/open-in-app.test.ts > … opens the resolved JetBrains stub with the worktree dir as its argument, with ENOENT on the stub calls.log its waitFor helper polls. It is not this PR's: the file is byte-identical to main (git diff origin/main -- is empty), it exercises server-side editor detection that this browser-only diff never reaches, and run in isolation it passed 10 times out of 10 — it only loses the race under full-suite parallel load, which the test's own comment above the assertion anticipates. Rather than absorb it silently into an unrelated PR's gate, it is filed as #823 with the reproduction numbers and a suggested direction. Four of five full runs, and CI on this head, are green.

Findings

✅ Resolved from the previous pass

1. (was Major) Mobile header overlap — fixed in 69f27427, packages/web/src/components/ui/toaster.tsx:93. The wrapper now reads top-[calc(61px+env(safe-area-inset-top))] … md:top-[calc(16px+env(safe-area-inset-top))]; 61px is the header's 52px row plus its border-b plus an 8px gap. packages/web/src/components/ui/toaster.test.tsx asserts both halves of the pair, so dropping either one fails the suite, and the comment in the source explains why md is the right breakpoint (it is where mobile-top-bar stops rendering) rather than just that it is.

2. (was Minor) EXIT_MS ↔ animation-duration coupling — fixed in 69f27427, packages/web/src/components/ui/toaster.test.tsx. The animation test now asserts motion-safe:duration-200, with a comment pointing at EXIT_MS, so the two values that jointly decide when the node disappears are held together by a failing test instead of by a hope.

No new findings were introduced by the fix commit: it changes two class strings and adds two assertions, touches no other file, and adds no logic.

💥 Breaking-Changes Checklist

Unchanged from the first pass and re-verified against the fix commit — the fix is presentational and additive. data-slot="toaster", data-slot="toast", data-tone and role="status" remain byte-for-byte identical (so new-task.e2e.ts:253, settings-skills.e2e.ts:112 and workflows.e2e.ts:126 are unaffected), toast() / resetToasts() / Toaster keep their signatures, and nothing under BACKWARD_COMPATIBILITY.md's protected surfaces — the CLI, the /api/v1 inventory, the SSE vocabulary, the .ai/cezar/ state formats — is touched.

🧪 Test Coverage

packages/web/src/components/ui/toaster.test.tsx holds 5 tests covering: the two-phase dismissal lifecycle, the anchor (positively for both breakpoints, negatively for the bottom-centre classes it replaced), the data-state open→closed flip with its motion-safe: classes including the duration, and two toasts keeping independent clocks across the exit timers. The four tests written for the original behavior change were confirmed red against the pre-fix source; the two assertions added by the autofix are guards on constants rather than on behavior, so they are green either way by design — the behavior they protect is exercised by the live-browser measurements above.

Next

Approved, so the PR moves to merge-queue; needs-qa stays on it and, with qaGate enabled, blocks the merge until a QA reviewer adds qa-approved. Manual-QA instructions are posted separately.

@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-create-pr — verification (mobile, post-autofix)

📸 The review's major finding was that the new top-right anchor covered the app shell's own mobile header. Re-captured at a 390×844 viewport against the real cockpit after the fix:

toast clearing the mobile header

Measured in the live page: the mobile header's bottom edge is at 53px, the toast's top edge at 61px — an 8px gap, overlapsHeader: false, right gap 16px. The menu button, the title and the mobile-status slot (where the thread view mounts the run status dot and kebab) are all clear of the toast. On desktop nothing moved: md:top-[calc(16px+env(safe-area-inset-top))] keeps the 16px anchor from the md breakpoint up, which is exactly where mobile-top-bar stops rendering.

Both anchor rules are present in the built stylesheet — top:calc(61px + env(safe-area-inset-top)) and, inside @media (width>=48rem), top:calc(16px + env(safe-area-inset-top)) — so the breakpoint pair survives the Tailwind build and is not purged.

@pat-lewczuk pat-lewczuk added merge-queue Approved, ready to merge and removed review Ready for code review labels Aug 9, 2026
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🧪 Manual QA instructions (needs-qa)

This PR is approved and requires manual QA (needs-qa, no skip-qa). It is queued in merge-queue but the QA-approval gate holds the merge until qa-approved is added. QA reviewer: when you pick it up, swap the labels (remove merge-queue, add qa), then run the paths below.

Everything here exercises one primitive — the cockpit's toast — so the fastest route is any interaction that fires one. Reliable triggers in the diff-free parts of the app: Settings → Bookmarklets, where clicking a bookmarklet button toasts "Drag me to your bookmarks bar" and the row's Copy toasts "Bookmarklet URL copied."; Settings → Skills, where Refresh toasts "… refreshed"; and handing a PR to an agent from the GitHub view, which is the interaction that produced the screenshot on #818.

P1 — Toast placement and animation (desktop)

Where to click

  • /p/<project>/settings/bookmarklets — click a bookmarklet button, then the Copy next to it.
  • /p/<project>/settings/skills — click Refresh.
  • Any task thread with a run — trigger something that toasts (e.g. copy the worktree path from the run header), so the toast is judged against real thread content rather than an empty settings page.

What to verify

  • The toast appears in the top-right corner, roughly 16px from the top and right edges — not bottom-centre, and not over the thread's action row (Run again, View task →), which is the complaint Implement: animated top-right toast stack in the cockpit #818 was filed for.
  • It slides in from the right while fading in over about a fifth of a second, rests for five seconds, then slides back out to the right while fading rather than disappearing instantly. The exit is the part that did not exist before, so watch for it specifically.
  • Fire two toasts a couple of seconds apart: they stack oldest on top, newest below, each dismisses on its own five-second clock, and the first one leaving does not disturb the second.
  • A long message wraps inside a capped-width card (roughly 360px) instead of stretching across the viewport.
  • Both tones render: a success/neutral toast and a failure one (danger, e.g. by triggering an action that fails) — colours should be unchanged from before this PR.

What can go wrong

  • The toast covers something the user needs. It now occupies the top-right corner for five seconds and still swallows clicks there (no dismiss button — that is deliberately out of scope per Implement: animated top-right toast stack in the cockpit #818), so check the views you use most for anything important under that footprint.
  • The exit animation looks clipped or the toast vanishes mid-slide — that would mean the removal timer and the CSS duration have drifted apart.
  • The stack "jumps" when the first of two toasts leaves. A small upward shift after the slide-out is expected and pre-existing; a jarring one is not.

P1 — Toasts fired from inside a dialog

Where to click

  • Open a dialog that fires a toast — the commit dialog under Git, or Settings → Accounts → Add account — and complete or fail the action so a toast fires while the dialog is open. Pressing ⌘K to open the command palette while a toast is on screen shows the same layering.

What to verify

  • The toast is fully visible above the dialog and its dimming overlay, not painted behind it or dimmed with the background. This is the z-50z-[60] part of the change and the reason it was needed: a top-right stack overlaps a centred dialog far more often than a bottom-centre one did.

What can go wrong

  • The toast renders behind the overlay (invisible or greyed) — the layering regressed.
  • The toast covers the dialog's close button or its top-right controls.

P1 — Mobile viewport

Where to click

  • The same routes at a phone width (real device or a ~390px-wide browser), including a task thread where the mobile header shows the run status dot and kebab on its right.

What to verify

  • The toast sits below the mobile top bar, not on top of it — the menu button, the title and the status/kebab on the right must all stay visible and tappable while a toast is up. (Measured at 8px below the header; screenshot attached to this PR.)
  • On a notched device the toast respects the safe-area insets on both the top and the right.
  • A long message still wraps rather than running off the right edge.

What can go wrong

  • The toast covers the header's kebab or status dot — that would be Implement: animated top-right toast stack in the cockpit #818's complaint moved to the top of the screen rather than fixed, which is exactly what the review pass caught and this PR now guards against.
  • On a very narrow screen the card touches or overflows the left edge.

P2 — Reduced motion

Where to click

  • Enable "Reduce motion" in the OS (macOS: System Settings → Accessibility → Display; iOS: Accessibility → Motion), reload the cockpit, and fire a toast.

What to verify

  • The toast appears and disappears instantly, with no slide and no fade, and still auto-dismisses after five seconds. Placement is unchanged.

What can go wrong

  • The animation still plays under reduced motion (the motion-safe: gating regressed), or the toast never appears at all because the instant path broke.

Pass/fail

  • All routes pass → remove the qa label and add merge-queue plus qa-approved (this clears the QA-approval gate).
  • Any route fails → remove the qa label, add qa-failed, and leave a comment describing the failure.

@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-review-pr completed: APPROVED (re-review at 69f27427, after autofix resolved the one major and one minor finding from the first pass). No CI follow-up is owed — the required checks on this head are already green — so no ci-monitoring label is applied. Lock retained: the chain continues under om-auto-create-pr, which releases it when the run ends.

@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-create-pr — run summary

Tracking plan: .ai/runs/2026-08-09-animated-top-right-toasts.md
Branch: feat/animated-top-right-toasts
Final status: complete — every Progress step is checked, the full validation gate is green, and the authoritative review pass approved after its findings were fixed.
Engine: Engine: om-auto-create-pr (steps: 7, --loop: no)

📋 Summary of changes

  • Phase 1 — the store learned to dismiss in two phases. The old toast() deleted its item from the module queue the instant the 5s timer fired, so the node unmounted before an exit animation could run; no amount of CSS could have produced one. ToastItem now carries an exiting flag: at TOAST_MS the item is re-published as exiting, and a second timer removes it EXIT_MS (200ms) later. Every pending handle is tracked in a module-level set so resetToasts() cancels timers as well as clearing the queue — without that, one test's orphaned timer would publish into the next test's store.
  • Phase 2 — the stack moved to the top-right and gained a real transition. The wrapper went from inset-x-0 bottom-[…] items-center to a right/top-anchored items-end column that respects the safe-area insets, each item caps its width (max-w-[min(360px,calc(100vw-32px))]) so long messages wrap, and the layer moved from z-50 to z-[60]. That last part is not cosmetic: dialogs fire toasts, Radix portals its overlay to the end of <body>, and at an equal z-index the dialog paints over the toast — rare with a bottom-centre stack, common with a top-right one. Each toast emits data-state={open|closed} and carries tw-animate-css enter/exit classes gated on motion-safe:, so reduced-motion users keep the instant appear/disappear they had before. No new dependency and no new @keyframes.
  • Phase 3 — tests, then the gate. The unit suite grew from 3 to 5 tests, all of the behavioral ones confirmed red against the pre-fix source.
  • Post-review — the fix the review caught. Below md, the flat 16px top anchor covered the app shell's own 52px mobile header, whose right end holds the run status dot and kebab. That is Implement: animated top-right toast stack in the cockpit #818's complaint relocated to the top of the phone screen rather than fixed, so the anchor became a breakpoint pair.
  • Files touched: packages/web/src/components/ui/toaster.tsx and packages/web/src/components/ui/toaster.test.tsx, plus the tracking plan under .ai/runs/. No caller changed; toast()'s signature is identical and <Toaster /> is still mounted once at the app root.

External references honored

None — no --skill-url was passed to this run. The only external input was issue #818's own analysis, which was followed except where the review pass found it incomplete (the mobile breakpoint, which the issue's implementation notes did not consider).

🧪 Verification phases completed

  • Targeted validation (per phase): npx vitest run packages/web/src/components/ui/toaster.test.tsx after the implementation phase and again after the autofix, plus npm run typecheck after the autofix. Before committing the implementation, the four new or rewritten tests were run against the pre-fix toaster.tsx (git stash push on the source, run, git stash pop) and all four failed — proof they are not green-either-way tests.
  • Full validation gate:npm run typecheck · ✅ npm test (5630 passed / 308 files, design-guardian included) · ✅ npm run test:unit (35 passed, 1 skipped) · ✅ npm run build (tsc + vite + check:pack, 462 files) · ✅ npm run test:package (12 passed). Run in full both before and after the autofix commit. One caveat, stated plainly: across five full npm test runs, one run failed a single unrelated test — packages/cezar/src/server/open-in-app.test.ts, ENOENT on the stub call log its waitFor polls. The file is byte-identical to main, exercises server-side editor detection this browser-only diff never reaches, and passes 10/10 in isolation; it only loses the race under full-suite parallel load, which its own comment anticipates. Filed as Flaky: open-in-app.test.ts JetBrains stub assertion races under full-suite load #823 rather than absorbed silently or fixed here.
  • om-auto-review-pr review/autofix pass: ran in autofix mode (this run authored the PR) and returned changes requested on one major and one minor finding, then approved on re-review. The major: the top-right anchor covered the mobile header's controls below md. The minor: EXIT_MS and the CSS duration-200 were coupled by a comment with nothing enforcing it. Both fixed in 69f27427 (the only follow-up commit; nothing was rewritten). Two nits were left deliberately — an exiting toast holds its layout slot for the 200ms exit window (pre-existing, shared with the old bottom-centre stack) and each toast keeps pointer-events-auto with no interactive affordance (Implement: animated top-right toast stack in the cockpit #818 puts manual dismiss explicitly out of scope). Compatibility, security, scope and breaking-change checks all came back clean: no contract surface is touched and BACKWARD_COMPATIBILITY.md's protected surfaces — CLI, /api/v1 inventory, SSE vocabulary, .ai/cezar/ state formats — are all untouched.
  • Live-browser evidence: three screenshots and a set of measurements taken against the real cockpit (booted from a production build by .ai/scripts/test-env-up.sh, driven through the repo's agent-browser provider), attached in this PR's two verification comments: the stack at top-right on desktop, a toast staying visible above an open dialog, and the toast clearing the mobile header at 390×844. Measured: 16px from the top and right edges on desktop, z-index: 60, enter/exit animations of 0.2s, data-state flipping to closed at 5012ms, and 90ms into the exit an opacity of 0.197 with a 12.85px rightward translate — the exit that could not exist before this change.

🔍 How to verify

  • Manual smoke test: run CEZ_DRY_RUN=1 npm run dev (no network or agent login needed), open the cockpit, and go to Settings → Bookmarklets. Clicking a bookmarklet button toasts "Drag me to your bookmarks bar"; the row's Copy toasts as well. Watch the top-right corner: the toast should slide in from the right, rest five seconds, then slide out. Press ⌘K while it is up to see it stay visible above the command palette's overlay. Narrow the window below 768px and fire another to see it clear the mobile header. Turn on the OS "Reduce motion" setting, reload, and fire one more: it should appear and disappear instantly. No fixtures are needed.
  • Areas to spot-check in the diff: toast() and resetToasts() in packages/web/src/components/ui/toaster.tsx — the two-timer bookkeeping is the part most worth a second pair of eyes — and the wrapper's className on the same file's Toaster, where the mobile/desktop anchor pair lives.
  • Commands the reviewer can re-run: npx vitest run packages/web/src/components/ui/toaster.test.tsx for the focused suite, then npm run typecheck && npm test && npm run test:unit && npm run build && npm run test:package for the whole gate.
  • Rollback plan: git revert 7e2558c3 69f27427 restores the previous bottom-centre, unanimated toaster exactly. There is no feature flag, no migration and no persisted state involved, so a revert is complete and instant — the component is self-contained and no caller depends on the new behavior.

⚠️ What can go wrong (risk analysis)

  • Most likely regression: the two-timer lifecycle. A leaked or mis-scheduled timer would either strand a toast on screen or publish into a later render; the resetToasts() timer cancellation and the "two toasts, independent clocks" test are what catch it, and the live measurement confirms the real timing (closed at 5012ms, removed after the exit window).
  • Second-order effects: the toast now sits over the top-right of every view for five seconds and, as before, swallows clicks in its own footprint. Views whose top-right corner carries frequently used controls will feel that; the mobile header was the one case severe enough to fix, and manual QA is asked to check the rest. The three e2e specs that poll [data-slot="toast"] / [data-slot="toaster"] are unaffected — those attributes are byte-identical, and the extra 200ms a toast now lingers only makes their "wait until present" polls easier to satisfy.
  • Security-sensitive surfaces: N/A. The diff touches no authentication, authorization, data scoping, input validation or secrets handling; it renders a string the caller already had into a <div> and reaches no network, no filesystem and no .ai/cezar/ state.
  • Breaking-change impact: No contract surface changes. toast(), resetToasts() and Toaster keep their signatures; ToastItem gains a required field but is only ever constructed inside its own module; the cockpit ships in lockstep with the server that serves it, so no client in the field can desynchronise.
  • Residual risk accepted: two nits are shipped as-is — an exiting toast holds its layout slot for 200ms (so the stack shifts up when it is finally removed) and a toast has no manual dismiss, both pre-existing or explicitly out of scope per Implement: animated top-right toast stack in the cockpit #818. The EXIT_MSduration-200 coupling remains a two-place constant, now guarded by a test assertion rather than made structurally impossible to break; making it structural (deriving one from the other) was judged not worth the indirection in a file whose stated virtue is being readable in one sitting. And Flaky: open-in-app.test.ts JetBrains stub assertion races under full-suite load #823's flake remains open — accepted here because it is unrelated to this diff and fixing it from a cockpit-UI PR would be scope creep.

@pat-lewczuk
pat-lewczuk marked this pull request as ready for review August 9, 2026 19:48
@pat-lewczuk pat-lewczuk removed the in-progress Cezar agent is actively working this issue label Aug 9, 2026
@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-create-pr — CI disclosure

The last commit on this branch (41745629, tracking-plan bookkeeping only) re-triggered the workflow, so Unit, build, E2E, and package is still running at the time this run finished reporting. Per this pipeline's rules the report is not held for CI: the labels, review, evidence and summary above all reflect the final state of the branch, and the same gate was green locally on this exact tree (npm run typecheck, npm test, npm run test:unit, npm run build, npm run test:package) and green in CI on the previous head. ci-monitoring is applied while this run watches for the result and will be removed when the outcome comment lands. This does not relax any merge gate — the checks still gate the merge.

@pat-lewczuk

Copy link
Copy Markdown
Collaborator Author

🤖 om-auto-create-pr — CI result

✅ All checks on the final head (41745629) are green: Unit, build, E2E, and package (run), Publish npm snapshot, and license/cla. This closes the follow-up promised in the disclosure comment above — no further CI follow-up will come from this run.

Worth noting for the flake filed as #823: the E2E-inclusive CI job passed on this branch, so the open-in-app.test.ts race did not reproduce there. It remains a local full-suite-under-load flake, which is what that issue records.

The PR is ready for review and sits in merge-queue; the QA-approval gate still holds the merge until a reviewer adds qa-approved (routes are in the manual-QA comment above).

@wojciechszyjka

Copy link
Copy Markdown
Contributor

🤖 om-auto-qa-pr started by @wojciechszyjka at 2026-08-10T09:22:09Z. UI QA verification in progress; other auto-skills will skip this PR until the lock is released.

@wojciechszyjka

Copy link
Copy Markdown
Contributor

📸 UI QA evidence — FAIL

Verdict: ❌ FAIL — the desktop half of this change is exactly what the issue asked for: the stack is anchored top-right, it slides in and fades, and it dismisses in two phases so the exit animation actually plays. On a phone-width viewport, though, the new anchor lands squarely on the task thread's own header controls: while a toast is up it covers and swallows taps on the "Run actions" kebab and the "Rename task" button for the toast's full five seconds. That is the same class of problem #818 filed — a toast parked on top of the controls underneath it — moved from the bottom action row to the top-right one.
Environment: http://127.0.0.1:4399 · role n/a — cezar exposes no login and the environment descriptor lists no credentials · browser agent-browser 0.33.2
Verified: feat/animated-top-right-toasts @ 41745629

Review gate: this PR reads REVIEW_REQUIRED on GitHub only because om-auto-review-pr cannot submit a formal approval on its own author's PR; it recorded APPROVED (re-review at 69f27427) as a comment on 2026-08-09. QA therefore ran against a reviewed change.

Scenario (P1 — cockpit toast stack)

Where to click: any cockpit action that fires a toast. This pass used the task thread's "take over interactively" copy-command button ("Command copied to clipboard.") and Open in… → Copy worktree path ("Worktree path copied"), at 1440×900 and at 390×844.

# Step Expected Observed Result
1 Fire a toast at 1440×900 The stack is fixed to the top-right, not the bottom centre [data-slot="toaster"] computed position: fixed, z-index: 60, align-items: flex-end; the toast rendered 16 px from the top and 16 px from the right
2 Inspect the toast element data-slot, data-tone and role="status" are unchanged, and the new data-state plus motion-safe classes are present role="status", data-tone="default", data-state="open", with motion-safe:animate-in, motion-safe:slide-in-from-right-4, motion-safe:duration-200 and the matching data-[state=closed] exit classes
3 Sample the lifecycle every 50 ms Two phases: marked closed at TOAST_MS, removed EXIT_MS later, so the exit animation has something to animate open at 0 ms → closed at 4 989 ms → gone at 5 193 ms; a 204 ms exit window, matching the pinned motion-safe:duration-200
4 Fire three toasts back to back They stack oldest-first, right-aligned, without overlapping Two live toasts at top 16 px and top 64 px, both right-aligned, independent clocks
5 Open a dialog and compare stacking values The toaster must clear the Radix overlay layer so a dialog cannot paint over a toast Dialog overlay computed z-index: 50; the toaster computes z-index: 60
6 Fire a toast at 390×844 on a task thread The anchor must clear the phone header and the run status dot and kebab — the controls the post-review fix names The toast occupied y 61–101 px at right 16 px, overlapping "Run actions" (y 65) and "Rename task" (y 69); document.elementFromPoint at the kebab's centre returned the toast, whose computed pointer-events is auto — so the control is both hidden and untappable for five seconds
7 Fire the same toast on an ordinary phone route (Settings) Nothing important sits in the toast's band No buttons or links intersect the 61–101 px band on that route

Screenshots

step-01-toast-top-right-desktop.png

step-02-toast-mobile-below-header.png

step-03-two-toasts-stacked.png

The defect, precisely

toaster.tsx anchors the stack at top-[calc(61px+env(safe-area-inset-top))] below md, chosen to clear the app shell's 52 px mobile header. On the task-thread route the shell header ends at 53 px, but the route then renders its own title row directly beneath it, and that row's right end is where "Rename task" (y 69–95) and the "Run actions" kebab (y 65–95) live. A 40 px toast starting at 61 px covers both of them. Because the toast item is pointer-events-auto (only the wrapper is pointer-events-none), a tap in that area hits the toast rather than the kebab — verified with elementFromPoint, not inferred from geometry.

Two shapes of fix suggest themselves, and the choice is the author's: raise the phone anchor below the route's own header row rather than below the shell header, or make the toast item non-interactive (pointer-events-none on the item too, since nothing in it is clickable today) so at worst it obscures rather than blocks. The first fixes visibility, the second fixes only the tap interception.

Notes for QA

  • Nothing else regressed. The three e2e specs that poll [data-slot="toast"] / [data-slot="toaster"] still have their attributes byte-identical, the tone and role hooks are unchanged, and the desktop anchor sits clear of the header's own badges (the toast starts at x 1262 while the "done" badge ends around x 1260).
  • Reduced motion was not driven. Every animation class is motion-safe:-gated in the DOM, which is the contract, but this pass did not emulate prefers-reduced-motion: reduce in the browser; the unit suite covers that gate.
  • The z-order check is a stacking-value check. No cockpit dialog on this fixture fires a toast of its own, so the claim was verified by comparing the dialog overlay's computed z-index: 50 with the toaster's 60 rather than by watching a dialog-fired toast paint. Someone exercising a dialog that toasts should confirm it visually.
  • Not covered. Safe-area insets on a real notched device; the toast's behaviour under the image lightbox (z-[100]); very long messages wrapping at the 360 px cap.
  • Automated coverage. The change ships toaster.test.tsx unit coverage for the lifecycle and the anchor classes, but no browser-level test — a follow-up UI test is suggested in a separate comment, and it would have caught step 6 had it asserted against the task-thread route rather than the class string.

@wojciechszyjka

Copy link
Copy Markdown
Contributor

🧪 Follow-up: add a UI/integration test

The change ships unit coverage that asserts the anchor class string, which is what let the phone-width overlap through: the classes are exactly what the post-review fix intended, but on the task-thread route the geometry they produce still lands on the header controls. A browser-level test measures the thing that actually matters. Run /om-integration-tests.

Scenario (derived from the manual run above):

  1. Setup: boot a spec-owned fixture server the way packages/web/e2e/agent-browser.ts documents (fixtureServeEnv over a throwaway dataRoot) with one seeded run, and open its task thread.
  2. Act: at 1440×900, click the thread's copy-command button so a toast fires; repeat at 390×844.
  3. Assert:
    • the [data-slot="toaster"] wrapper is position: fixed with align-items: flex-end, and the toast's rect is within 16 px of the viewport's top-right on desktop;
    • the toast carries role="status", its original data-slot/data-tone, data-state="open", and the motion-safe: enter/exit classes;
    • the lifecycle is two-phase: data-state flips to closed at TOAST_MS and the node is gone roughly EXIT_MS later (assert on the transition, not on a single sampled instant);
    • the regression guard this pass would have needed: at 390×844, for every visible control in the route header (Rename task, Run actions), document.elementFromPoint at that control's centre must NOT return a node inside [data-slot="toast"] while a toast is up;
    • firing two toasts leaves them stacked oldest-first with no overlap.
  4. Teardown: kill the fixture server and delete dataRoot.

@wojciechszyjka wojciechszyjka removed the in-progress Cezar agent is actively working this issue label Aug 10, 2026
@wojciechszyjka wojciechszyjka removed their assignment Aug 10, 2026
@wojciechszyjka

Copy link
Copy Markdown
Contributor

🤖 om-auto-qa-pr completed: FAIL. Six of the seven checks passed on the current head 41745629 — the top-right anchor, the preserved data-slot/data-tone/role hooks, the two-phase lifecycle measured at open → closed (4 989 ms) → gone (5 193 ms), oldest-first stacking, the z-60 clearance over the z-50 dialog overlay, and a clean band on ordinary phone routes. The one failure is specific and reproducible: at 390×844 on a task thread the toast occupies y 61–101 px and both covers and intercepts taps on the "Run actions" kebab (y 65) and "Rename task" (y 69), because the 61 px phone anchor clears the 52 px app-shell header but not the route's own title row beneath it. Three screenshots and the elementFromPoint evidence are in the comment above. Per this skill's evidence-only default no labels were changed — qa-failed was not applied, so the finding is a report for the author rather than a block. Lock released.

@wojciechszyjka wojciechszyjka added qa-failed Manual QA failed and removed merge-queue Approved, ready to merge labels Aug 10, 2026
@wojciechszyjka

Copy link
Copy Markdown
Contributor

🏷️ qa-failed applied — om-auto-qa-pr

Applying the failure label at the maintainer's explicit request, because the QA pass above found a reproducible defect that this PR itself introduces.

Why: at 390×844 on a task thread, a toast occupies y 61–101 px at right 16 px and lands on the route's own header row — it covers the "Run actions" kebab (y 65–95) and "Rename task" (y 69–95), and because the toast item is pointer-events: auto it also swallows taps there for the toast's full five seconds. document.elementFromPoint at the kebab's centre returns a node inside [data-slot="toast"] while the toast is up, so this is measured rather than inferred. The md: breakpoint pair added in the post-review fix clears the app shell's 52 px mobile header, but not the task-thread header row that sits directly beneath it — which is where the very controls the fix names actually live.

What this label means here: the desktop half of the change is good — top-right anchor, preserved data-slot/data-tone/role hooks, two-phase dismissal measured at open → closed (4 989 ms) → gone (5 193 ms), oldest-first stacking, and z-60 clearance over the z-50 dialog overlay all passed. Only the phone anchor needs another pass. Two shapes of fix, author's choice: move the sub-md anchor below the route's own header row instead of below the shell header, or make the toast item pointer-events-none as well (nothing inside it is clickable today), which fixes the tap interception but not the occlusion.

merge-queue came off with it, since the pipeline labels are mutually exclusive and this is no longer merge-ready. Re-run /om-auto-qa-pr 820 after the fix and the label will be re-evaluated against fresh evidence.

@pat-lewczuk
pat-lewczuk merged commit 0e02743 into main Aug 11, 2026
3 checks passed
@pat-lewczuk
pat-lewczuk deleted the feat/animated-top-right-toasts branch August 11, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New capability needs-qa Requires manual QA before merge priority-low Cosmetic or follow-up work qa-failed Manual QA failed risk-low Isolated, low blast radius

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement: animated top-right toast stack in the cockpit

2 participants