@the-portland-company/shell 0.76.15 → 0.76.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ## 0.76.18
2
+
3
+ Consolidates 0.76.16 and 0.76.17, neither of which reached npm (their publish gates failed). Specs cited: Global-Rules v38, UI-Platform v46, Specs/Left-Navigation (no Tribes row, §9.1), Specs/System-Communications §6.3 and §12.2 (Binding, 2026-10-03).
4
+
5
+ - **`NotificationsAdapter` (comms bell).** `src/notifications/adapter.ts`: `list(orgId)`, `markRead`, `archive`, `resolve`. The default reads `me/notifications` through the chrome proxy with the viewer's session (comms `GET /v1/me/notifications`, `POST /v1/me/notifications/:id/{read,archive,resolve}`, politogy-comms #14). Any failure (401, 404, network, malformed body) is an empty bell, never an error; owner actions resolve `false` instead of throwing. `NavChromeProvider` and `PolitogyAppChrome` take `notificationsAdapter`: omitted = comms (default); `null` = legacy contacts-shaped feed (replies, imports, quarantine). Alert rows read the server `read_at`; the badge is the server count.
6
+ - **Behavior change for consumers.** Apps that rely on the legacy contacts replies, imports or quarantine rows in the bell must pass `notificationsAdapter={null}`.
7
+ - **Tests.** `__tests__/notificationsAdapter.test.ts` (new). `__tests__/chromeFetchDedupe.test.tsx` counts `me/notifications`. `__tests__/chromeActions.test.tsx` pins its legacy `NotificationsAction` tests to `notificationsAdapter={null}`; this is what fixed the 8 CI-only failures that blocked the 0.76.16 publish gate.
8
+ - **Tribes.** No `tribes` registry row in this release. Tribes lives inside Contacts; a `tribes.politogyvrm.com` entry would point at an empty scaffold.
9
+ - **Tab containers are left-aligned and fluid** (Spencer, 2026-10-11: "all apps, all tabs, all screens"; Reference/Global-Rules "Tabs & UI Styling": the strip spans the full width of the content area, tabs keep their natural width, left-aligned). `HeaderTabBar` (deprecated) no longer stretches each tab with `flex-1`: tabs sit at natural width packed from the left on a full-width strip (40px hit area). `NavTabStrip` already met this (full-width block, `flex` with default start alignment, natural-width tabs) and is unchanged; `__tests__/tabsLeftAligned.test.tsx` (new) pins both so neither grows `mx-auto`, `justify-center` or a fixed width.
10
+
11
+ ## 0.76.17
12
+
13
+ Tooltips on the Spec tooltip theme, and the new-reply toast no longer paints over the bell's tooltip. Specs cited: Brand guideline "Tooltips point at their trigger" (`tooltips-point-at-their-trigger`, Spencer 2026-10-06), Reference/UI-Platform "Top-bar tooltips and panels - one shared treatment", Reference/Global-Rules "Tooltip on every tab / UX", tokens v150 `shellChrome.button.tooltipBg/Fg`.
14
+
15
+ - **`TopBarTooltip` is styled on the tooltip tokens.** Body, border and arrow use `--app-btn-tooltip-bg` (v150: #2F3136), title and description use `--app-btn-tooltip-fg` (#FFFFFF, description at 75% opacity). It was on the panel surface (`--panel-elevated` / `--line`), which is why it rendered as a light card. Opens on keyboard focus (`group-focus-visible/tbtip`) as well as hover, per the guideline.
16
+ - **Toast/tooltip overlap (a2p dashboard, reported 2026-10-11).** The new-reply toast and the bell's hover tooltip are both z-50 under the same trigger; hovering the bell while a toast was up painted the toast over "Alerts & Notifications". `TopBarPopover` takes `hideTooltip`; `NotificationsAction` passes it while the toast is visible and `FuelAction` while the low-fuel hint is up. One surface per trigger.
17
+ - **`HeaderTabBar` (deprecated) uses the shared tooltip** instead of Chakra's `Tooltip`; the shell no longer ships a second tooltip.
18
+ - Tests: `__tests__/tooltipTheme.test.tsx` (new).
19
+ - **Not changed:** `src/css/chrome.css` still carries the pre-v150 fallbacks (`--app-btn-tooltip-bg: #0F172A`, mode primaries). `npm run gen:tokens` against Specs v150 produces a 508-line diff including `--mode-relationship-primary` #D32F3B -> #2F3136, which is a Brand-token question, not this fix; the runtime token sheet (`ensureRuntimeBrandTokens`) already overrides every fallback live.
20
+ - **Still open:** `aria-describedby` from trigger to tooltip (guideline) is not wired; apps outside the chrome still use native `title=` attributes rather than `TopBarTooltip` (exported from `./native`).
21
+
22
+ ## 0.76.16
23
+
24
+ The shell's Notifications bell reads the comms feed through a `NotificationsAdapter`. Specs cited: Global-Rules v38, UI-Platform v46, Specs/Left-Navigation (no Tribes row, §9.1), Specs/System-Communications §6.3 and §12.2 (Binding, 2026-10-03).
25
+
26
+ - **`NotificationsAdapter` (bell).** New `src/notifications/adapter.ts`: `list(orgId)`, `markRead`, `archive`, `resolve`. The default reads `me/notifications` through the chrome proxy with the viewer's session, which is the comms `GET /v1/me/notifications` and `POST /v1/me/notifications/:id/{read,archive,resolve}` (politogy-comms #14). Any failure (401, 404, network, malformed body) is an empty bell, never an error state; owner actions resolve `false` instead of throwing.
27
+ - **Bell wiring.** `NavChromeProvider` and `PolitogyAppChrome` take `notificationsAdapter`. Omitted, the comms adapter is used. `null` keeps the legacy contacts-shaped feed (replies, imports, quarantine) for any app that needs it. Alert rows read the server `read_at` state; the badge is the server's single count.
28
+ - Exports: `createCommsNotificationsAdapter`, `normalizeNotificationsFeed`, `emptyNotificationsFeed` and the adapter types from the package root.
29
+ - Tests: `__tests__/notificationsAdapter.test.ts` (new); `__tests__/chromeFetchDedupe.test.tsx` now counts `me/notifications`. The 41 pre-existing localStorage/cookie failures are unchanged and not addressed.
30
+ - **Still open:** `subscribe` (realtime) is not implemented; the bell refreshes on open and after a mark-read. Resolve and archive are on the adapter but have no panel control yet.
31
+
1
32
  ## 0.76.15
2
33
 
3
34
  TITLEBAR: the chrome no longer draws the 56px page-title `<h1>` strip (for example "A2P Messaging" above the "CAMPAIGN MODE" eyebrow) for feature apps. The Design shows the page opening on its own eyebrow + H2, so `PolitogyAppFrame` now passes only core's own page title as `topBar.title`. The `title` grant is kept but inert; removing it is a follow-up.
@@ -415,6 +415,79 @@ function SpeculationRulesScript({ items }) {
415
415
  return null;
416
416
  }
417
417
 
418
+ // src/notifications/adapter.ts
419
+ function emptyNotificationsFeed() {
420
+ return { notifications: [], badge: 0 };
421
+ }
422
+ function asString(value) {
423
+ return typeof value === "string" ? value : null;
424
+ }
425
+ function normalizeNotificationsFeed(raw) {
426
+ if (!raw || typeof raw !== "object") return emptyNotificationsFeed();
427
+ const body = raw;
428
+ const rows = Array.isArray(body.notifications) ? body.notifications : [];
429
+ const notifications = [];
430
+ for (const row of rows) {
431
+ if (!row || typeof row !== "object") continue;
432
+ const r = row;
433
+ const id = asString(r.id);
434
+ if (!id || asString(r.archived_at)) continue;
435
+ notifications.push({
436
+ id,
437
+ kind: r.kind === "alert" ? "alert" : "notification",
438
+ title: asString(r.title) ?? "",
439
+ body: asString(r.body) ?? "",
440
+ icon: asString(r.icon),
441
+ link: asString(r.link),
442
+ readAt: asString(r.read_at),
443
+ orgId: asString(r.org_id),
444
+ createdAt: asString(r.created_at)
445
+ });
446
+ }
447
+ const badge = typeof body.badge === "number" && Number.isFinite(body.badge) ? Math.max(0, Math.floor(body.badge)) : notifications.filter((n) => n.kind === "alert" || n.readAt === null).length;
448
+ return { notifications, badge };
449
+ }
450
+ function createCommsNotificationsAdapter(fetchJson) {
451
+ const act = async (id, action) => {
452
+ if (!fetchJson || !id) return false;
453
+ try {
454
+ await fetchJson(`me/notifications/${encodeURIComponent(id)}/${action}`, { method: "POST" });
455
+ return true;
456
+ } catch {
457
+ return false;
458
+ }
459
+ };
460
+ return {
461
+ async list(orgId) {
462
+ if (!fetchJson) return emptyNotificationsFeed();
463
+ try {
464
+ const query = orgId ? `?org_id=${encodeURIComponent(orgId)}` : "";
465
+ return normalizeNotificationsFeed(await fetchJson(`me/notifications${query}`));
466
+ } catch {
467
+ return emptyNotificationsFeed();
468
+ }
469
+ },
470
+ markRead: (id) => act(id, "read"),
471
+ archive: (id) => act(id, "archive"),
472
+ resolve: (id) => act(id, "resolve")
473
+ };
474
+ }
475
+ function notificationsFeedToPayload(feed, orgId) {
476
+ return {
477
+ organizationId: orgId ?? "",
478
+ alerts: feed.notifications.map((n) => ({
479
+ id: n.id,
480
+ title: n.title,
481
+ description: n.body,
482
+ href: n.link,
483
+ kind: n.kind,
484
+ read: n.readAt !== null
485
+ })),
486
+ replies: [],
487
+ badge: feed.badge
488
+ };
489
+ }
490
+
418
491
  // src/theme-preference.ts
419
492
  var THEME_STORAGE_KEY = "politogy_theme";
420
493
  var THEME_COOKIE = "politogy_theme";
@@ -558,17 +631,17 @@ function TopBarTooltip({
558
631
  "span",
559
632
  {
560
633
  role: "tooltip",
561
- className: "pointer-events-none absolute left-1/2 top-full z-50 mt-2 hidden w-max max-w-56 -translate-x-1/2 flex-col rounded-lg border border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-3 py-2 text-left shadow-[var(--shadow-soft)] group-hover/tbtip:flex",
634
+ className: "pointer-events-none absolute left-1/2 top-full z-50 mt-2 hidden w-max max-w-56 -translate-x-1/2 flex-col rounded-lg border border-[color:var(--app-btn-tooltip-bg,#2F3136)] bg-[color:var(--app-btn-tooltip-bg,#2F3136)] px-3 py-2 text-left shadow-[var(--shadow-soft)] group-hover/tbtip:flex group-focus-visible/tbtip:flex",
562
635
  children: [
563
636
  /* @__PURE__ */ jsxRuntime.jsx(
564
637
  "span",
565
638
  {
566
639
  "aria-hidden": true,
567
- className: "absolute -top-[5px] left-1/2 h-2.5 w-2.5 -translate-x-1/2 rotate-45 border-l border-t border-[color:var(--line)] bg-[color:var(--panel-elevated)]"
640
+ className: "absolute -top-[5px] left-1/2 h-2.5 w-2.5 -translate-x-1/2 rotate-45 border-l border-t border-[color:var(--app-btn-tooltip-bg,#2F3136)] bg-[color:var(--app-btn-tooltip-bg,#2F3136)]"
568
641
  }
569
642
  ),
570
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-[color:var(--foreground)]", children: title }),
571
- description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 text-[11px] leading-snug text-[color:var(--muted)]", children: description }) : null
643
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-[color:var(--app-btn-tooltip-fg,#FFFFFF)]", children: title }),
644
+ description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 text-[11px] leading-snug text-[color:var(--app-btn-tooltip-fg,#FFFFFF)] opacity-75", children: description }) : null
572
645
  ]
573
646
  }
574
647
  );
@@ -652,6 +725,7 @@ function createChromeFetchCache(getFetchJson) {
652
725
  var NavChromeContext = react.createContext(null);
653
726
  function NavChromeProvider({
654
727
  fetchJson,
728
+ notificationsAdapter,
655
729
  children
656
730
  }) {
657
731
  const fetchJsonRef = react.useRef(fetchJson);
@@ -661,6 +735,13 @@ function NavChromeProvider({
661
735
  cacheRef.current = createChromeFetchCache(() => fetchJsonRef.current);
662
736
  }
663
737
  const { dedupedFetch, invalidate: invalidateChromeCache } = cacheRef.current;
738
+ const defaultAdapterRef = react.useRef(null);
739
+ if (!defaultAdapterRef.current) {
740
+ defaultAdapterRef.current = createCommsNotificationsAdapter(
741
+ (path, init) => fetchJsonRef.current(path, init)
742
+ );
743
+ }
744
+ const adapter = notificationsAdapter === void 0 ? defaultAdapterRef.current : notificationsAdapter;
664
745
  const [me, setMe] = react.useState(null);
665
746
  const [meLoaded, setMeLoaded] = react.useState(false);
666
747
  react.useEffect(() => {
@@ -675,7 +756,13 @@ function NavChromeProvider({
675
756
  cancelled = true;
676
757
  };
677
758
  }, [dedupedFetch]);
678
- return /* @__PURE__ */ jsxRuntime.jsx(NavChromeContext.Provider, { value: { fetchJson, dedupedFetch, invalidateChromeCache, me, meLoaded }, children });
759
+ return /* @__PURE__ */ jsxRuntime.jsx(
760
+ NavChromeContext.Provider,
761
+ {
762
+ value: { fetchJson, dedupedFetch, invalidateChromeCache, me, meLoaded, notificationsAdapter: adapter },
763
+ children
764
+ }
765
+ );
679
766
  }
680
767
  function useChromeMe() {
681
768
  const ctx = react.useContext(NavChromeContext);
@@ -756,6 +843,7 @@ function TopBarPopover({
756
843
  onOpen,
757
844
  openToken,
758
845
  onOpenChange,
846
+ hideTooltip = false,
759
847
  children,
760
848
  panelClassName
761
849
  }) {
@@ -812,7 +900,7 @@ function TopBarPopover({
812
900
  children: [
813
901
  /* @__PURE__ */ jsxRuntime.jsx(SlotGlyph, { slot, fallback: fallbackIcon }),
814
902
  typeof badge === "number" ? /* @__PURE__ */ jsxRuntime.jsx(TopBarCountBadge, { count: badge }) : null,
815
- !open ? /* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title: label, description }) : null
903
+ !open && !hideTooltip ? /* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title: label, description }) : null
816
904
  ]
817
905
  }
818
906
  ),
@@ -950,7 +1038,11 @@ function subscribeLowFuelHintVisible(onChange) {
950
1038
  return () => lowFuelHintListeners.delete(onChange);
951
1039
  }
952
1040
  function useLowFuelHintVisible() {
953
- return react.useSyncExternalStore(subscribeLowFuelHintVisible, () => lowFuelHintVisible);
1041
+ return react.useSyncExternalStore(
1042
+ subscribeLowFuelHintVisible,
1043
+ () => lowFuelHintVisible,
1044
+ () => false
1045
+ );
954
1046
  }
955
1047
  var LOW_FUEL_HINT_CLEARANCE_PX = 96;
956
1048
  function LowFuelHint({ lowFuel, orgId }) {
@@ -1055,6 +1147,7 @@ function FuelAction({ orgId }) {
1055
1147
  };
1056
1148
  }, [dedupedFetch, orgId]);
1057
1149
  const low = !!fuel && (fuel.signal.lowFuel || fuel.broadcast.lowFuel);
1150
+ const lowFuelHintUp = useLowFuelHintVisible();
1058
1151
  return (
1059
1152
  // The nudge anchors to the trigger, so it sits OUTSIDE the popover panel in
1060
1153
  // its own relative wrapper (same shape the VRM widget used).
@@ -1067,6 +1160,7 @@ function FuelAction({ orgId }) {
1067
1160
  slot: "comms-fuel",
1068
1161
  fallbackIcon: BatteryGlyph,
1069
1162
  tone: low ? "warning" : "default",
1163
+ hideTooltip: lowFuelHintUp,
1070
1164
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
1071
1165
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Communications Fuel" }),
1072
1166
  error || !loading && !fuel ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-[color:var(--muted)]", children: "Fuel balance is unavailable right now." }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
@@ -1256,7 +1350,7 @@ function NewReplyToast({
1256
1350
  );
1257
1351
  }
1258
1352
  function NotificationsAction({ orgId }) {
1259
- const { dedupedFetch } = useNavChrome("NotificationsAction");
1353
+ const { dedupedFetch, notificationsAdapter } = useNavChrome("NotificationsAction");
1260
1354
  const [payload, setPayload] = react.useState(null);
1261
1355
  const [loading, setLoading] = react.useState(true);
1262
1356
  const [error, setError] = react.useState(false);
@@ -1265,6 +1359,7 @@ function NotificationsAction({ orgId }) {
1265
1359
  const [toastsMuted, setToastsMuted] = react.useState(() => readToastsMuted());
1266
1360
  const [openToken, setOpenToken] = react.useState(0);
1267
1361
  const [panelOpen, setPanelOpen] = react.useState(false);
1362
+ const [reloadToken, setReloadToken] = react.useState(0);
1268
1363
  const anyPanelOpen = useAnyTopBarPanelOpen();
1269
1364
  react.useEffect(() => {
1270
1365
  setNotif(readNotifState(orgId));
@@ -1276,6 +1371,19 @@ function NotificationsAction({ orgId }) {
1276
1371
  setLoading(false);
1277
1372
  return;
1278
1373
  }
1374
+ if (notificationsAdapter) {
1375
+ setError(false);
1376
+ notificationsAdapter.list(orgId).then((feed) => {
1377
+ if (!cancelled) setPayload(notificationsFeedToPayload(feed, orgId));
1378
+ }).catch(() => {
1379
+ if (!cancelled) setPayload(notificationsFeedToPayload(emptyNotificationsFeed(), orgId));
1380
+ }).finally(() => {
1381
+ if (!cancelled) setLoading(false);
1382
+ });
1383
+ return () => {
1384
+ cancelled = true;
1385
+ };
1386
+ }
1279
1387
  setLoading(true);
1280
1388
  setError(false);
1281
1389
  setPayload(null);
@@ -1293,9 +1401,14 @@ function NotificationsAction({ orgId }) {
1293
1401
  return () => {
1294
1402
  cancelled = true;
1295
1403
  };
1296
- }, [dedupedFetch, orgId]);
1404
+ }, [dedupedFetch, notificationsAdapter, orgId, reloadToken]);
1297
1405
  const markRead = react.useCallback(
1298
1406
  (id) => {
1407
+ if (notificationsAdapter) {
1408
+ void notificationsAdapter.markRead(id).then((ok) => {
1409
+ if (ok) setReloadToken((t) => t + 1);
1410
+ });
1411
+ }
1299
1412
  setNotif((prev) => {
1300
1413
  if (prev.readAlertIds.includes(id)) return prev;
1301
1414
  const next = { ...prev, readAlertIds: [...prev.readAlertIds, id] };
@@ -1303,7 +1416,7 @@ function NotificationsAction({ orgId }) {
1303
1416
  return next;
1304
1417
  });
1305
1418
  },
1306
- [orgId]
1419
+ [orgId, notificationsAdapter]
1307
1420
  );
1308
1421
  const markUnread = react.useCallback(
1309
1422
  (id) => {
@@ -1373,14 +1486,16 @@ function NotificationsAction({ orgId }) {
1373
1486
  }
1374
1487
  setOpenToken((t) => t + 1);
1375
1488
  }, [toastReply]);
1376
- const unreadAlertCount = alerts.filter((a) => !notif.readAlertIds.includes(a.id)).length;
1489
+ const isAlertRead = (a) => a.read === true || notif.readAlertIds.includes(a.id);
1490
+ const unreadAlertCount = alerts.filter((a) => !isAlertRead(a)).length;
1377
1491
  const unseenReplyCount = replies.filter((r) => {
1378
1492
  if (!notif.lastSeenAt) return true;
1379
1493
  if (!r.createdAt) return false;
1380
1494
  return r.createdAt > notif.lastSeenAt;
1381
1495
  }).length;
1382
- const badgeCount = unreadAlertCount + unseenReplyCount;
1496
+ const badgeCount = notificationsAdapter && payload?.badge !== void 0 ? payload.badge : unreadAlertCount + unseenReplyCount;
1383
1497
  const isEmpty = alerts.length === 0 && replies.length === 0 && imports.length === 0 && quarantined.length === 0;
1498
+ const toastVisible = toastReply !== null && !panelOpen && !anyPanelOpen;
1384
1499
  return (
1385
1500
  // Relative wrapper so the new-reply toast anchors to the bell trigger,
1386
1501
  // outside the popover panel (same pattern as the fuel low-fuel nudge).
@@ -1396,6 +1511,7 @@ function NotificationsAction({ orgId }) {
1396
1511
  onOpen,
1397
1512
  openToken,
1398
1513
  onOpenChange: setPanelOpen,
1514
+ hideTooltip: toastVisible,
1399
1515
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
1400
1516
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
1401
1517
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Notifications" }),
@@ -1415,7 +1531,7 @@ function NotificationsAction({ orgId }) {
1415
1531
  ] }) : error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-[color:var(--muted)]", children: "Notifications are unavailable right now." }) : isEmpty ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-1 text-sm text-[color:var(--muted)]", children: "You\u2019re all caught up." }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-h-80 flex-col gap-2 overflow-y-auto", children: [
1416
1532
  alerts.length > 0 || replies.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "flex flex-col gap-1", children: [
1417
1533
  alerts.map((a) => {
1418
- const isRead = notif.readAlertIds.includes(a.id);
1534
+ const isRead = isAlertRead(a);
1419
1535
  return /* @__PURE__ */ jsxRuntime.jsx("li", { className: isRead ? "opacity-50" : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1 rounded-lg px-2 py-2 hover:bg-[color:var(--panel)]", children: [
1420
1536
  /* @__PURE__ */ jsxRuntime.jsxs(
1421
1537
  "a",
@@ -1434,7 +1550,7 @@ function NotificationsAction({ orgId }) {
1434
1550
  {
1435
1551
  type: "button",
1436
1552
  "aria-label": isRead ? "Mark as unread" : "Mark as read",
1437
- onClick: () => isRead ? markUnread(a.id) : markRead(a.id),
1553
+ onClick: () => isRead && !notificationsAdapter ? markUnread(a.id) : markRead(a.id),
1438
1554
  className: "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[color:var(--muted)] transition hover:bg-[color:var(--panel-elevated)] hover:text-[color:var(--foreground)]",
1439
1555
  children: /* @__PURE__ */ jsxRuntime.jsx(CheckGlyph, {})
1440
1556
  }
@@ -1493,7 +1609,7 @@ function NotificationsAction({ orgId }) {
1493
1609
  ] })
1494
1610
  }
1495
1611
  ),
1496
- toastReply && !panelOpen && !anyPanelOpen ? /* @__PURE__ */ jsxRuntime.jsx(
1612
+ toastVisible && toastReply ? /* @__PURE__ */ jsxRuntime.jsx(
1497
1613
  NewReplyToast,
1498
1614
  {
1499
1615
  reply: toastReply,
@@ -5388,6 +5504,7 @@ function PolitogyAppChrome({
5388
5504
  storagePrefix,
5389
5505
  proxyBasePath = "/api/chrome",
5390
5506
  fetchJson: fetchJsonOverride,
5507
+ notificationsAdapter,
5391
5508
  devNotes,
5392
5509
  org,
5393
5510
  tokenOrg,
@@ -5419,7 +5536,7 @@ function PolitogyAppChrome({
5419
5536
  children
5420
5537
  }
5421
5538
  );
5422
- return /* @__PURE__ */ jsxRuntime.jsx(NavChromeProvider, { fetchJson, children: tokenOrg ? /* @__PURE__ */ jsxRuntime.jsx(TokenChromeStateProvider, { tokenOrg, children: devNotesTree }) : /* @__PURE__ */ jsxRuntime.jsx(ChromeStateProvider, { controlledOrg: org, children: devNotesTree }) });
5539
+ return /* @__PURE__ */ jsxRuntime.jsx(NavChromeProvider, { fetchJson, notificationsAdapter, children: tokenOrg ? /* @__PURE__ */ jsxRuntime.jsx(TokenChromeStateProvider, { tokenOrg, children: devNotesTree }) : /* @__PURE__ */ jsxRuntime.jsx(ChromeStateProvider, { controlledOrg: org, children: devNotesTree }) });
5423
5540
  }
5424
5541
  function ChromeTopBarActions() {
5425
5542
  const state = useChromeState();