@the-portland-company/shell 0.39.1 → 0.41.0
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 +97 -0
- package/dist/app-chrome.cjs +57 -14
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.js +1 -1
- package/dist/chrome.css +73 -18
- package/dist/{chunk-HRG7AKTN.js → chunk-IX664IMD.js} +60 -17
- package/dist/chunk-IX664IMD.js.map +1 -0
- package/dist/index.cjs +44 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +44 -2
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +57 -14
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +11 -3
- package/dist/native.d.ts +11 -3
- package/dist/native.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-HRG7AKTN.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,100 @@
|
|
|
1
|
+
## 0.41.0 — 2026-08-03
|
|
2
|
+
|
|
3
|
+
### Fixed — the expanded sub-menu was the same colour as the left bar
|
|
4
|
+
|
|
5
|
+
Reference/UI-Platform → Styling pins the nav ramp (page `#0A0A0A` → left/top bar
|
|
6
|
+
`#161616` → **expanded sub-menu `#212121`** → row hover `#2B2B2B` → borders
|
|
7
|
+
`#333333`). The expanded group painted `--panel`, which is `#161616` in dark —
|
|
8
|
+
the same value as the bar behind it — so nesting read as flat. New tokens
|
|
9
|
+
`--nav-submenu-bg` and `--nav-row-hover` carry the ramp's steps; the nested
|
|
10
|
+
sub-group's hover no longer reuses `--panel-elevated` (`#1F1F1F`), which was
|
|
11
|
+
DARKER than the panel it sits on and made a hovered row recede.
|
|
12
|
+
|
|
13
|
+
### Fixed — quarantined-contact rows looked clickable and were not
|
|
14
|
+
|
|
15
|
+
The rows in the Alerts panel carried a hover highlight on a non-interactive
|
|
16
|
+
`<li>`, so a row advertised interactivity and then did nothing. Each row is now
|
|
17
|
+
a real `<a>` — keyboard focusable, Enter-activatable, with a visible
|
|
18
|
+
focus-visible ring — opening the quarantined-contacts view filtered to that
|
|
19
|
+
number (`?phone=`), the same destination as "View all" but scoped. Counts cap at
|
|
20
|
+
`99+` and match the shared badge geometry.
|
|
21
|
+
|
|
22
|
+
### Fixed — the `.dark` block was missing whole token groups
|
|
23
|
+
|
|
24
|
+
Global-Rules → Dark Mode: "A var can never exist in one block and not the
|
|
25
|
+
other." The form-control, app-field, floating-label and `--app-color-gray-*`
|
|
26
|
+
groups were declared in `:root` only, so a control in dark mode kept its LIGHT
|
|
27
|
+
border, placeholder and label values — measured live as slate `#64748B` /
|
|
28
|
+
`#475569` against black, which is also the forbidden blue-tinted family. All are
|
|
29
|
+
now overridden, with the gray ramp INVERTED (pale surface → dark surface, dark
|
|
30
|
+
text → light text).
|
|
31
|
+
|
|
32
|
+
### Fixed — Chakra tooltips and subtle alerts in dark mode
|
|
33
|
+
|
|
34
|
+
Chakra's stock `Tooltip` inverts, so in dark mode every `div#tooltip-*` rendered
|
|
35
|
+
a near-white `#E8E8E8` slab against black chrome (15 of 29 pages). It is now
|
|
36
|
+
pinned to the shared panel surface, so a Chakra tooltip and the native chrome's
|
|
37
|
+
`TopBarTooltip` are one treatment, per UI-Platform. Chakra's `subtle` /
|
|
38
|
+
`left-accent` alerts resolved `<scheme>.200` for BOTH the surface and the icon,
|
|
39
|
+
so an error alert painted `#FEB2B2`; in dark they now use a neutral raised
|
|
40
|
+
surface and carry meaning in the icon/text accent, with red reconciled to the
|
|
41
|
+
brand ramp rather than Chakra's `#E53E3E`.
|
|
42
|
+
|
|
43
|
+
### Fixed — `gen:tokens` silently deleted hand-written chrome rules
|
|
44
|
+
|
|
45
|
+
`scripts/gen-tokens.mjs` rewrites `chrome.css` wholesale, but the widget-slot
|
|
46
|
+
badge normalization and the beta-banner defaults had been appended to the
|
|
47
|
+
committed file by hand. The next regeneration would have deleted them — 0.40.0's
|
|
48
|
+
badge fix included. They now live in the generator (`STATIC_CHROME_CSS`), so the
|
|
49
|
+
file regenerates losslessly.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
`__tests__/darkTokenCompleteness.test.ts` asserts that every colour-valued
|
|
54
|
+
`:root` var has a `.dark` override and that every dark chrome hex is hue-free
|
|
55
|
+
(R = G = B) — the arithmetic check the spec asks for, rather than an eyeball.
|
|
56
|
+
|
|
57
|
+
## 0.40.1 — 2026-08-03
|
|
58
|
+
|
|
59
|
+
### Fixed — a top-bar nudge could still cover an open panel
|
|
60
|
+
|
|
61
|
+
0.40.0 stopped the new-reply toast covering the notifications panel, but the
|
|
62
|
+
same collision existed from a NEIGHBOURING trigger: the low-fuel nudge is wider
|
|
63
|
+
than its own trigger and the panels are right-aligned, so with Notifications
|
|
64
|
+
open the nudge sat on its header (measured live on contacts.politogyvrm.com
|
|
65
|
+
after the 0.40.0 deploy). The top bar now keeps one module-scoped count of open
|
|
66
|
+
panels (`useAnyTopBarPanelOpen`); both transient nudges yield while any panel is
|
|
67
|
+
open and return when it closes. Still no z-index changes.
|
|
68
|
+
|
|
69
|
+
## 0.40.0 — 2026-08-03
|
|
70
|
+
|
|
71
|
+
### Fixed — the DevNotes badge was a second badge definition
|
|
72
|
+
|
|
73
|
+
Reference/UI-Platform "Badge Counters" allows exactly ONE badge definition, and
|
|
74
|
+
`.topbar-widget-slot` in `chrome.css` is supposed to restate it for a launcher
|
|
75
|
+
that brings its own badge markup. It did not: the only rule was
|
|
76
|
+
`.topbar-widget-slot button > span.relative { position: static }`, and DevNotes
|
|
77
|
+
0.6.x writes that wrapper with an INLINE `position: relative` and **no class**,
|
|
78
|
+
so the selector matched nothing. The badge kept anchoring to the glyph instead
|
|
79
|
+
of the 32px trigger, and its own inline styles (`top:-4 right:-8`, `#dc2626`,
|
|
80
|
+
`font-weight:700`, no `height`) rendered a visibly different bubble — measured
|
|
81
|
+
different on app.politogyvrm.com vs contacts.politogyvrm.com.
|
|
82
|
+
|
|
83
|
+
The slot CSS now (a) matches every direct span child of the launcher, not a
|
|
84
|
+
class, and (b) restates the whole badge definition — 16px circle, `#E63946`
|
|
85
|
+
via `--color-race-red`, 10px / weight 400 / line-height 1, `top:-4px`
|
|
86
|
+
`right:-4px` — with `!important` so inline styles cannot win. `TopBarCountBadge`
|
|
87
|
+
pins `font-weight: 400` and `line-height: 1` explicitly rather than inheriting.
|
|
88
|
+
|
|
89
|
+
### Fixed — the new-reply toast rendered on top of the notifications panel
|
|
90
|
+
|
|
91
|
+
Both the toast and the panel anchor to the bell trigger, and nothing suppressed
|
|
92
|
+
one while the other was up, so opening Notifications put a message preview over
|
|
93
|
+
the panel's "NOTIFICATIONS" header and first row. `TopBarPopover` gained
|
|
94
|
+
`onOpenChange`; `NotificationsAction` clears the toast when the panel opens,
|
|
95
|
+
refuses to raise a new one while it is open, and does not render it when open.
|
|
96
|
+
One surface per trigger, per Reference/UI-Platform. No z-index was changed.
|
|
97
|
+
|
|
1
98
|
## 0.37.1 — 2026-07-27
|
|
2
99
|
|
|
3
100
|
### Fixed — cross-app navigation kept the user's active organization
|
package/dist/app-chrome.cjs
CHANGED
|
@@ -154,6 +154,10 @@ function withOrgParam(url, orgId) {
|
|
|
154
154
|
if (!orgId) return url;
|
|
155
155
|
return `${url}${url.includes("?") ? "&" : "?"}org=${orgId}`;
|
|
156
156
|
}
|
|
157
|
+
function withPhoneParam(url, phone) {
|
|
158
|
+
if (!phone) return url;
|
|
159
|
+
return `${url}${url.includes("?") ? "&" : "?"}phone=${encodeURIComponent(phone)}`;
|
|
160
|
+
}
|
|
157
161
|
function resolveOrgLogoUrl(url) {
|
|
158
162
|
const raw = url?.trim();
|
|
159
163
|
if (!raw) return void 0;
|
|
@@ -169,9 +173,26 @@ function topBarIconButtonClass(tone = "default") {
|
|
|
169
173
|
toneClass
|
|
170
174
|
].join(" ");
|
|
171
175
|
}
|
|
176
|
+
var openPanelCount = 0;
|
|
177
|
+
var panelListeners = /* @__PURE__ */ new Set();
|
|
178
|
+
var emitPanelChange = () => panelListeners.forEach((l) => l());
|
|
179
|
+
function setPanelOpenGlobal(open) {
|
|
180
|
+
openPanelCount = Math.max(0, openPanelCount + (open ? 1 : -1));
|
|
181
|
+
emitPanelChange();
|
|
182
|
+
}
|
|
183
|
+
function useAnyTopBarPanelOpen() {
|
|
184
|
+
return react.useSyncExternalStore(
|
|
185
|
+
(cb) => {
|
|
186
|
+
panelListeners.add(cb);
|
|
187
|
+
return () => panelListeners.delete(cb);
|
|
188
|
+
},
|
|
189
|
+
() => openPanelCount > 0,
|
|
190
|
+
() => false
|
|
191
|
+
);
|
|
192
|
+
}
|
|
172
193
|
function TopBarCountBadge({ count }) {
|
|
173
194
|
if (!(count > 0)) return null;
|
|
174
|
-
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] text-white", children: count > 99 ? "99+" : count });
|
|
195
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] font-normal leading-none text-white", children: count > 99 ? "99+" : count });
|
|
175
196
|
}
|
|
176
197
|
function TopBarTooltip({
|
|
177
198
|
title,
|
|
@@ -343,10 +364,19 @@ function TopBarPopover({
|
|
|
343
364
|
tone,
|
|
344
365
|
onOpen,
|
|
345
366
|
openToken,
|
|
367
|
+
onOpenChange,
|
|
346
368
|
children,
|
|
347
369
|
panelClassName
|
|
348
370
|
}) {
|
|
349
371
|
const [open, setOpen] = react.useState(false);
|
|
372
|
+
const onOpenChangeRef = react.useRef(onOpenChange);
|
|
373
|
+
onOpenChangeRef.current = onOpenChange;
|
|
374
|
+
react.useEffect(() => {
|
|
375
|
+
onOpenChangeRef.current?.(open);
|
|
376
|
+
if (!open) return;
|
|
377
|
+
setPanelOpenGlobal(true);
|
|
378
|
+
return () => setPanelOpenGlobal(false);
|
|
379
|
+
}, [open]);
|
|
350
380
|
const rootRef = react.useRef(null);
|
|
351
381
|
const lastOpenTokenRef = react.useRef(openToken);
|
|
352
382
|
react.useEffect(() => {
|
|
@@ -524,7 +554,8 @@ function LowFuelHint({ lowFuel, orgId }) {
|
|
|
524
554
|
} catch {
|
|
525
555
|
}
|
|
526
556
|
}, [lowFuel]);
|
|
527
|
-
|
|
557
|
+
const panelOpen = useAnyTopBarPanelOpen();
|
|
558
|
+
if (!lowFuel || dismissed || panelOpen) return null;
|
|
528
559
|
const dismiss = () => {
|
|
529
560
|
try {
|
|
530
561
|
window.sessionStorage.setItem(LOW_FUEL_HINT_DISMISSED_KEY, "1");
|
|
@@ -757,6 +788,8 @@ function NotificationsAction({ orgId }) {
|
|
|
757
788
|
const [notif, setNotif] = react.useState(() => readNotifState(orgId));
|
|
758
789
|
const [toastReply, setToastReply] = react.useState(null);
|
|
759
790
|
const [openToken, setOpenToken] = react.useState(0);
|
|
791
|
+
const [panelOpen, setPanelOpen] = react.useState(false);
|
|
792
|
+
const anyPanelOpen = useAnyTopBarPanelOpen();
|
|
760
793
|
react.useEffect(() => {
|
|
761
794
|
setNotif(readNotifState(orgId));
|
|
762
795
|
setToastReply(null);
|
|
@@ -824,6 +857,7 @@ function NotificationsAction({ orgId }) {
|
|
|
824
857
|
[orgId]
|
|
825
858
|
);
|
|
826
859
|
const onOpen = react.useCallback(() => {
|
|
860
|
+
setToastReply(null);
|
|
827
861
|
setNotif((prev) => {
|
|
828
862
|
const next = { ...prev, lastSeenAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
829
863
|
writeNotifState(orgId, next);
|
|
@@ -835,7 +869,7 @@ function NotificationsAction({ orgId }) {
|
|
|
835
869
|
const imports = payload?.imports ?? [];
|
|
836
870
|
const quarantined = payload?.quarantined ?? [];
|
|
837
871
|
react.useEffect(() => {
|
|
838
|
-
if (!payload || toastReply) return;
|
|
872
|
+
if (!payload || toastReply || panelOpen) return;
|
|
839
873
|
const candidate = replies.filter((r) => {
|
|
840
874
|
if (!r.createdAt) return false;
|
|
841
875
|
if (notif.lastSeenAt && r.createdAt <= notif.lastSeenAt) return false;
|
|
@@ -851,7 +885,7 @@ function NotificationsAction({ orgId }) {
|
|
|
851
885
|
writeNotifState(orgId, next);
|
|
852
886
|
return next;
|
|
853
887
|
});
|
|
854
|
-
}, [payload, replies, toastReply, notif.lastSeenAt, notif.toastedReplyIds, orgId]);
|
|
888
|
+
}, [payload, replies, toastReply, panelOpen, notif.lastSeenAt, notif.toastedReplyIds, orgId]);
|
|
855
889
|
const onToastActivate = react.useCallback(() => {
|
|
856
890
|
const href = toastReply?.href;
|
|
857
891
|
setToastReply(null);
|
|
@@ -883,6 +917,7 @@ function NotificationsAction({ orgId }) {
|
|
|
883
917
|
badge: loading ? 0 : badgeCount,
|
|
884
918
|
onOpen,
|
|
885
919
|
openToken,
|
|
920
|
+
onOpenChange: setPanelOpen,
|
|
886
921
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
|
|
887
922
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Notifications" }),
|
|
888
923
|
loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 py-1", children: [
|
|
@@ -938,19 +973,27 @@ function NotificationsAction({ orgId }) {
|
|
|
938
973
|
}
|
|
939
974
|
)
|
|
940
975
|
] }),
|
|
941
|
-
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1", children: quarantined.map((q) => /* @__PURE__ */ jsxRuntime.
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
976
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1", children: quarantined.map((q) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
977
|
+
"a",
|
|
978
|
+
{
|
|
979
|
+
href: withPhoneParam(withOrgParam(QUARANTINED_CONTACTS_URL, orgId), q.phone),
|
|
980
|
+
"aria-label": `Review ${q.count} quarantined message${q.count === 1 ? "" : "s"} from ${formatPhone(q.phone)}`,
|
|
981
|
+
className: "block rounded-lg px-2 py-2 transition hover:bg-[color:var(--nav-row-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-race-red)]",
|
|
982
|
+
children: [
|
|
983
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
984
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: formatPhone(q.phone) }),
|
|
985
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--panel)] px-1 text-[10px] font-normal leading-none tabular-nums text-[color:var(--muted)]", children: q.count > 99 ? "99+" : q.count })
|
|
986
|
+
] }),
|
|
987
|
+
q.latestAt ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[color:var(--muted)]", children: new Date(q.latestAt).toLocaleString() }) : null
|
|
988
|
+
]
|
|
989
|
+
}
|
|
990
|
+
) }, q.phone)) })
|
|
948
991
|
] }) : null
|
|
949
992
|
] })
|
|
950
993
|
] })
|
|
951
994
|
}
|
|
952
995
|
),
|
|
953
|
-
toastReply ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
996
|
+
toastReply && !panelOpen && !anyPanelOpen ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
954
997
|
NewReplyToast,
|
|
955
998
|
{
|
|
956
999
|
reply: toastReply,
|
|
@@ -1992,7 +2035,7 @@ function NavGroup({
|
|
|
1992
2035
|
]
|
|
1993
2036
|
}
|
|
1994
2037
|
),
|
|
1995
|
-
open ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-b-md bg-[color:var(--
|
|
2038
|
+
open ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-b-md bg-[color:var(--nav-submenu-bg)] p-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", children: item.children.map((child, i) => {
|
|
1996
2039
|
const childActive = child.href ? activeFn(pathname, child.href) : false;
|
|
1997
2040
|
if (child.children && child.children.length > 0) {
|
|
1998
2041
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2040,7 +2083,7 @@ function NavSubGroup({
|
|
|
2040
2083
|
"aria-expanded": open,
|
|
2041
2084
|
className: [
|
|
2042
2085
|
"flex w-full items-center gap-2.5 rounded px-3 py-1.5 text-left text-[13px] font-medium transition",
|
|
2043
|
-
ownsActive ? "text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:bg-[color:var(--
|
|
2086
|
+
ownsActive ? "text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:bg-[color:var(--nav-row-hover)] hover:text-[color:var(--foreground)]"
|
|
2044
2087
|
].join(" "),
|
|
2045
2088
|
children: [
|
|
2046
2089
|
Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 15, className: "shrink-0 text-current" }) : null,
|