@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 +31 -0
- package/dist/app-chrome.cjs +133 -16
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +7 -2
- package/dist/app-chrome.d.ts +7 -2
- package/dist/app-chrome.js +5 -4
- package/dist/app-chrome.js.map +1 -1
- package/dist/{chunk-HSC6EYRR.js → chunk-GGM4AD43.js} +6 -1769
- package/dist/chunk-GGM4AD43.js.map +1 -0
- package/dist/chunk-TUK2IEL5.js +2002 -0
- package/dist/chunk-TUK2IEL5.js.map +1 -0
- package/dist/index-Cu5frXRQ.d.cts +990 -0
- package/dist/index-DJ3qBzfQ.d.ts +990 -0
- package/dist/index.cjs +170 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37 -36
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +131 -15
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +4 -929
- package/dist/native.d.ts +4 -929
- package/dist/native.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-HSC6EYRR.js.map +0 -1
- package/dist/chunk-MY6G3LMN.js +0 -125
- package/dist/chunk-MY6G3LMN.js.map +0 -1
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.
|
package/dist/app-chrome.cjs
CHANGED
|
@@ -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(--
|
|
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(--
|
|
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(--
|
|
571
|
-
description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 text-[11px] leading-snug text-[color:var(--
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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();
|