Skip to content

feat(notices): host-driven notice banners on the dashboard home - #2667

Open
camielvs wants to merge 3 commits into
banners-03-contractfrom
banners-04-strip
Open

feat(notices): host-driven notice banners on the dashboard home#2667
camielvs wants to merge 3 commits into
banners-03-contractfrom
banners-04-strip

Conversation

@camielvs

@camielvs camielvs commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Why

The dashboard banners this replaces were fed by a hardcoded config module, showed on one page, and had no way to look back at anything you'd dismissed. They should be host-driven — fed by the contract from PR 3 — and they should keep the placement people already know.

What

Notice banners inline at the top of the dashboard home, in the slot the old AnnouncementBanners occupied.

  • One notice per column. The banners share the dashboard's three-column grid, so the cards line up with the panels beneath them. Two notices, two columns; a third fills the row.
  • Most severe first. Errors, then warnings, then successes, then info.
  • Gets out of the way. The eye-off on a card takes that notice off the home page. A notice the host marked dismissible stays hidden across reloads; one it didn't comes back on the next load, since the host is still asking for it to be shown.
  • Nothing above the content. Banners scroll with the page rather than pinning under the top bar, so no full-height layout has to make room for them.

Deletes the old AnnouncementBanners and its config module.

⚠️ Coordinate the merge

The old system read a plain array from window.__TANGLE_ANNOUNCEMENTS__. The new one reads a versioned source from a different key. Any embedding page still injecting the old shape goes dark the moment this merges. Either land this after the host ships the new source, or say the word and I'll add a fallback that reads the old array for one release.

Reviewer notes

Per-notice hiding is the only inline control; clearing them all, and reading one back after you've hidden it, belong to the notices button in PR 5 — which is also the only notice surface on v2 pages, since those have no home page.

The store lands complete here, but its read-tracking and per-notice dismiss have no caller until PR 5 — they're covered by that PR's tests, not this one. I kept it whole because splitting it in half made the banners' own tests depend on an artificial reset hook, which seemed a worse trade than a little unused surface for one PR.

Copy link
Copy Markdown
Collaborator Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: banners-04-strip/69c580a

Comment thread react-compiler.config.js Outdated
Comment thread src/hooks/useBannerInbox.ts Outdated
Comment thread src/components/shared/Banners/BannerRegion.tsx Outdated
Comment thread src/components/shared/Banners/BannerRegion.tsx Outdated
Comment thread src/hooks/useNoticeInbox.ts
Comment thread src/hooks/useNotices.ts
@camielvs
camielvs force-pushed the banners-03-contract branch from a100783 to eb0c6de Compare August 26, 2026 23:45
@camielvs
camielvs force-pushed the banners-04-strip branch 2 times, most recently from 91b5f62 to 45113d5 Compare August 27, 2026 00:44
camielvs and others added 3 commits August 26, 2026 18:13
Renders notices from the host contract as a strip directly under the top nav,
so they are visible on every page rather than only on the dashboard home. The
strip scrolls horizontally when there are more notices than fit, sorts them
most-severe-first, and publishes its own height so full-height layouts stay
inside the viewport.

"Hide notices" clears the strip. A notice the host marked dismissible stays
hidden across reloads; one it did not comes back on the next load, since the
host is still asking for it to be shown.

Replaces the old dashboard-only AnnouncementBanners, which read a plain array
off the window and is deleted here along with its config module.

Note for reviewers: the store lands complete, but its read-tracking and
per-notice dismiss are unused until the header UI in the next PR.
Every notice in the strip can now be taken off it, whatever the host said
about dismissibility, because the inbox keeps it reachable. Hiding is
persisted for a dismissible notice and session-only for a mandatory one,
the same split hideStrip already used in bulk. The strip's control is an
eye-off "Hide notice", distinct from the inbox's destructive "Dismiss", so
the two actions do not look alike. isStripHidden becomes hasHiddenBanners,
since a partly-hidden strip must still offer to bring the rest back.

Register both new hooks in react-compiler.config.js — src/hooks is still
opt-out, so a new top-level hook file gets no coverage by default. The
compiler also takes care of the identity of the object useBannerInbox
returns, so a consumer can safely put it in a dep array.

dismissBanner now enforces the same rule hideStrip does: only a notice the
host marked dismissible is retired permanently, everything else is
session-only.

handleStorage ignores the synthetic same-tab event typedStorage dispatches
on every write, so an action publishes once instead of twice. A real
cross-tab event still refreshes, which is now pinned by a test.

isOpen moves into the cached snapshot rather than being read off module
state during render, and the tab-focus refresh is throttled.

BannerCard replaces bodyClassName with a clampBody flag, so the card owns
the classes and the strip only states its intent. The body keeps scrolling
inside the card with the action pinned below it.

resetBannerStateForTests clears the snapshot cache, the session sets, the
open flag and the refresh watcher, so the suites stop reserving ids to
avoid each other.

With banners.ts fully wired, its knip ignore comes off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follows the contract rename below it. Notices are the domain concept; the
banner row is one surface they appear on, so the store exposes both:
`notices` is everything the reader still has, `banners` the subset showing
on the page. Storage keys move with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@camielvs
camielvs force-pushed the banners-03-contract branch from d9ad21b to 4fb20f2 Compare August 27, 2026 01:18
@camielvs camielvs changed the title feat(notices): app-wide notice strip, replacing the dashboard banners feat(notices): host-driven notice banners on the dashboard home Aug 27, 2026
@camielvs
camielvs marked this pull request as ready for review August 28, 2026 00:02
@camielvs
camielvs requested a review from a team as a code owner August 28, 2026 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant