Skip to content

feat(notices): notices button in the header - #2668

Open
camielvs wants to merge 2 commits into
banners-04-stripfrom
banners-05-inbox
Open

feat(notices): notices button in the header#2668
camielvs wants to merge 2 commits into
banners-04-stripfrom
banners-05-inbox

Conversation

@camielvs

@camielvs camielvs commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Why

PR 4 left two gaps: hiding a banner was a one-way door for the rest of the session, and there was no way to look back at a notice you'd hidden. Banners are also dashboard-home-only, so every other page — including all of v2, which has no home page — had no notice surface at all.

What

A Megaphone button in the top bar, on every page, with an unread count badge.

  • Opening it shows every active notice and marks them read, clearing the badge.
  • Dismissible notices can be removed one at a time from here — permanently, not just for this session.
  • Show notices / Hide notices clears the home page's banners or brings them back, so hiding one is never a dead end.
  • With nothing to show it stays put and says "No notices", rather than vanishing out from under whoever just dismissed the last one.

Wired into both the v1 and the v2 top bar.

image.png

image.png

image.png

image.png

image.png

How to Test

Notices come from the host page, not from this app: the UI only reads a source installed on window, and with nothing installed it shows nothing. The whole contract is in src/config/NOTICES.md, and installing a source by hand is all you need to exercise the feature.

1. Run the app

gh pr checkout 2668
pnpm install && pnpm start

Point VITE_BACKEND_API_URL at whichever backend you normally develop against — notices don't come from it, so any working setup will do.

2. Install a notice source

Paste this into the browser console:

let notices = [
  {
    id: "maintenance",
    title: "Scheduled maintenance on Saturday",
    body: "Submissions pause between **09:00 and 11:00 UTC**. Runs already in flight finish normally.",
    variant: "info",
    dismissible: true,
    action: { url: "https://example.com/maintenance", text: "Read the notes" },
  },
  {
    id: "permissions",
    title: "Check your access before submitting a cloned pipeline",
    body: "Runs that inherit an identity you cannot impersonate will sit in pending.\n\nSee the [access docs](https://example.com/access).",
    variant: "warning",
  },
];

const listeners = new Set();

window.__TANGLE_NOTICE_SOURCE__ = {
  version: 1,
  getSnapshot: () => notices,
  subscribe: (listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  },
  refresh: () => console.log("[notices] refresh requested"),
};

window.dispatchEvent(new CustomEvent("tangle:notice-source"));

window.setNotices = (next) => {
  notices = next;
  listeners.forEach((listener) => listener());
};

Vary variant, dismissible and action from there, and call setNotices([...]) to publish an update the way a real host would.

For the reload checks below, put the same block in index.html just before </body> instead, so the source survives a refresh. Don't commit that edit.

Two things worth knowing if you'd rather drive it from real data: a host page that fetches notices from a backend and installs them through this same global lives outside this repo, in the deployment that embeds the app — the contract is identical either way, so anything that works here works there. And src/config/noticeTestSource.ts is the same installer the unit tests use, if you want to script it.

3. What to look for

  • On the dashboard home: banners at the top of the content, one notice per column of the existing three-column grid, so they line up with the Favorites / Recently Viewed / Recently Used panels beneath. Ordered error → warning → success → info. Markdown in the body renders; links carry the external icon and open in a new tab.
  • A long body scrolls inside its own card, with the action button staying put underneath.
  • The eye-off on a card takes that notice off the home page, and the rest of the content closes the gap.
  • On every other page/runs, /components, /settings, the v2 editor — there are no banners and no leftover gap under the top bar. Full-height views (components list, editor sidebar, context panel) still size correctly.
  • The megaphone is always in the header, everywhere, with the unread count on the badge (9+ past nine). Opening the list clears it.
  • The list holds every active notice, hidden or not, and its Show notices / Hide notices toggle restores or clears the home page's banners — a hidden banner is never a dead end.
  • Only a notice with dismissible: true gets an X in the list, and dismissing it removes it for good. After a reload, dismissals and hides of dismissible notices persist; hides of the rest come back.
  • Dismiss everything (or install an empty list) and the megaphone stays put, showing "No notices".
  • Junk is dropped rather than rendered: an entry with no id, or with a blank title and body, shouldn't appear at all; duplicate ids collapse to the first; and a relative action.url costs the notice its button but still renders the notice.
  • setNotices([...]) updates the banners and the list immediately, without a reload.

Reviewer notes

This is the last PR in the stack; at this point the feature is complete and the tree matches what was proposed in #2661.

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-05-inbox/b02bd10

Comment thread src/components/shared/Banners/BannerInbox.tsx Outdated
Comment thread src/components/shared/Notices/NoticeInbox.tsx
Comment thread src/components/shared/Notices/NoticeInbox.tsx
Comment thread src/components/shared/Banners/BannerInbox.test.tsx Outdated
Comment thread src/components/shared/Banners/BannerInbox.tsx Outdated
Comment thread src/components/shared/Banners/BannerInbox.tsx Outdated
@camielvs
camielvs force-pushed the banners-05-inbox branch 2 times, most recently from 9e7df61 to 0769ace Compare August 27, 2026 00:05
camielvs and others added 2 commits August 26, 2026 18:13
Adds a Megaphone button to the top bar that opens the full list of notices,
with an unread count badge. Opening the list marks everything in it as read;
notices the host marked dismissible can be removed one at a time from here.

The list is also the way back once the strip has been hidden, which until now
was a one-way door for the rest of the session. Its toggle mirrors the strip's,
and the strip's own controls stay out of the way while the list is open.
The notices button now stays in the header whether or not there is
anything to read, and opening it with nothing to show gives a "No notices"
empty state. Previously the whole affordance unmounted when the list
emptied, which also meant dismissing the last notice pulled the open
popover out from under the reader.

Cap the unread badge at 9+ so a host-supplied count cannot clip inside the
badge; the trigger's label still carries the exact number.

The strip toggle follows hasHiddenBanners, so it offers to restore the
strip as soon as one notice is hidden rather than only when all of them
are.

Move the viewport-fit sizing onto the PopoverContent primitive so no caller
has to name Radix custom properties, and let the header row use the fill
prop rather than a width class.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@camielvs camielvs mentioned this pull request Aug 27, 2026
8 tasks
@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