@the-portland-company/shell 0.36.2 → 0.37.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 +18 -0
- package/dist/app-chrome.cjs +134 -24
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +8 -1
- package/dist/app-chrome.d.ts +8 -1
- package/dist/app-chrome.js +60 -23
- package/dist/app-chrome.js.map +1 -1
- package/dist/chunk-6UHJV2FH.js +63 -0
- package/dist/chunk-6UHJV2FH.js.map +1 -0
- package/dist/{chunk-LIGKGXFT.js → chunk-CGSS2PDG.js} +20 -6
- package/dist/chunk-CGSS2PDG.js.map +1 -0
- package/dist/native.cjs +18 -3
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +18 -1
- package/dist/native.d.ts +18 -1
- package/dist/native.js +1 -1
- package/dist/nav-preference.cjs +75 -0
- package/dist/nav-preference.cjs.map +1 -0
- package/dist/nav-preference.d.cts +18 -0
- package/dist/nav-preference.d.ts +18 -0
- package/dist/nav-preference.js +3 -0
- package/dist/nav-preference.js.map +1 -0
- package/package.json +6 -1
- package/dist/chunk-LIGKGXFT.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 0.37.0 — 2026-07-27
|
|
2
|
+
|
|
3
|
+
### Fixed — organization logo renders in every app, not just VRM core
|
|
4
|
+
|
|
5
|
+
`organizations.logo_url` is authored by VRM core and stored as a ROOT-RELATIVE
|
|
6
|
+
core route (`/api/storage/public?bucket=organization-logos&path=<org>/logo.png`
|
|
7
|
+
— core's signed-storage proxy). VRM core rendered it fine because the path
|
|
8
|
+
resolved against its own origin; every feature app resolved the same path
|
|
9
|
+
against ITS origin (`forms.politogyvrm.com/api/storage/public`), 404'd, and
|
|
10
|
+
showed a broken image in the sidebar org pill and the org dropdown.
|
|
11
|
+
|
|
12
|
+
`resolveOrgLogoUrl` (exported from `./native`) now absolutizes a relative logo
|
|
13
|
+
path against `CORE_APP_ORIGIN` at the point of render, for all three org-badge
|
|
14
|
+
sites (active pill, dropdown rows, non-switcher pill). Absolute, protocol-
|
|
15
|
+
relative and `data:` URLs pass through untouched; empty values still fall back
|
|
16
|
+
to initials. This is the same treatment `absolutizeAvatarUrl` already applied to
|
|
17
|
+
`profiles.avatar_url` in the chrome API — the org logo was simply missed.
|
|
18
|
+
|
|
1
19
|
## 0.36.0 — 2026-07-24
|
|
2
20
|
|
|
3
21
|
### Added — cross-subdomain active-org + favicon cache-busting
|
package/dist/app-chrome.cjs
CHANGED
|
@@ -150,6 +150,12 @@ var CORE_APP_ORIGIN = "https://app.politogyvrm.com";
|
|
|
150
150
|
var CORE_BILLING_PATH = "/organization-settings/subscription-billing/text-email";
|
|
151
151
|
var CORE_BILLING_URL = `${CORE_APP_ORIGIN}${CORE_BILLING_PATH}`;
|
|
152
152
|
var QUARANTINED_CONTACTS_URL = `${CORE_APP_ORIGIN}/relationship/contacts/quarantined`;
|
|
153
|
+
function resolveOrgLogoUrl(url) {
|
|
154
|
+
const raw = url?.trim();
|
|
155
|
+
if (!raw) return void 0;
|
|
156
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(raw)) return raw;
|
|
157
|
+
return `${CORE_APP_ORIGIN}${raw.startsWith("/") ? "" : "/"}${raw}`;
|
|
158
|
+
}
|
|
153
159
|
function topBarIconButtonClass(tone = "default") {
|
|
154
160
|
const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--color-race-red)] hover:text-[color:var(--color-race-red)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
|
|
155
161
|
return [
|
|
@@ -1588,6 +1594,7 @@ function NavSidebar({
|
|
|
1588
1594
|
const activeFn = nav.isActive ?? isNavItemActive;
|
|
1589
1595
|
const ModeIcon = mode?.icon;
|
|
1590
1596
|
const orgBg = org?.brandColor || ORG_PILL_DEFAULT;
|
|
1597
|
+
const orgLogoSrc = resolveOrgLogoUrl(org?.logoUrl);
|
|
1591
1598
|
const activeGroupKey = react.useMemo(() => {
|
|
1592
1599
|
for (let i = 0; i < nav.items.length; i++) {
|
|
1593
1600
|
const item = nav.items[i];
|
|
@@ -1655,7 +1662,7 @@ function NavSidebar({
|
|
|
1655
1662
|
{
|
|
1656
1663
|
style: { backgroundColor: orgBg },
|
|
1657
1664
|
className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
|
|
1658
|
-
children:
|
|
1665
|
+
children: orgLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: orgLogoSrc, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
|
|
1659
1666
|
}
|
|
1660
1667
|
),
|
|
1661
1668
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1690,7 +1697,14 @@ function NavSidebar({
|
|
|
1690
1697
|
{
|
|
1691
1698
|
style: { backgroundColor: o.brandColor || ORG_PILL_DEFAULT },
|
|
1692
1699
|
className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-full text-[10px] font-bold text-white",
|
|
1693
|
-
children: o.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1700
|
+
children: resolveOrgLogoUrl(o.logoUrl) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1701
|
+
"img",
|
|
1702
|
+
{
|
|
1703
|
+
src: resolveOrgLogoUrl(o.logoUrl),
|
|
1704
|
+
alt: "",
|
|
1705
|
+
className: "h-full w-full object-cover"
|
|
1706
|
+
}
|
|
1707
|
+
) : (o.name ?? "Org").slice(0, 2).toUpperCase()
|
|
1694
1708
|
}
|
|
1695
1709
|
),
|
|
1696
1710
|
/* @__PURE__ */ jsxRuntime.jsx("span", { title: o.name ?? o.id, className: "truncate", children: o.name ?? o.id.slice(0, 8) })
|
|
@@ -1712,7 +1726,7 @@ function NavSidebar({
|
|
|
1712
1726
|
{
|
|
1713
1727
|
style: { backgroundColor: orgBg },
|
|
1714
1728
|
className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
|
|
1715
|
-
children:
|
|
1729
|
+
children: orgLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: orgLogoSrc, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
|
|
1716
1730
|
}
|
|
1717
1731
|
),
|
|
1718
1732
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold", children: org.name ?? "No organizations" })
|
|
@@ -2535,6 +2549,66 @@ function resolveActiveOrg(search) {
|
|
|
2535
2549
|
return readActiveOrgCookie();
|
|
2536
2550
|
}
|
|
2537
2551
|
|
|
2552
|
+
// src/nav-preference.ts
|
|
2553
|
+
var NAV_DISPLAY_STORAGE_KEY = "politogy_nav_display";
|
|
2554
|
+
var NAV_DISPLAY_COOKIE = "politogy_nav_display";
|
|
2555
|
+
var LEGACY_NAV_DISPLAY_KEY = "vrm.navDisplay";
|
|
2556
|
+
var NAV_DISPLAY_DEFAULT = "both";
|
|
2557
|
+
var VALID2 = ["menu", "tabs", "both"];
|
|
2558
|
+
function isNavDisplayPreference(v) {
|
|
2559
|
+
return typeof v === "string" && VALID2.includes(v);
|
|
2560
|
+
}
|
|
2561
|
+
function readNavDisplayCookie() {
|
|
2562
|
+
if (typeof document === "undefined") return null;
|
|
2563
|
+
try {
|
|
2564
|
+
const m = document.cookie.match(
|
|
2565
|
+
new RegExp(`(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)`)
|
|
2566
|
+
);
|
|
2567
|
+
const v = m ? decodeURIComponent(m[1]) : null;
|
|
2568
|
+
return isNavDisplayPreference(v) ? v : null;
|
|
2569
|
+
} catch {
|
|
2570
|
+
return null;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
function writeNavDisplayCookie(pref) {
|
|
2574
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
2575
|
+
const host = window.location.hostname;
|
|
2576
|
+
const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
|
|
2577
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
2578
|
+
document.cookie = `${NAV_DISPLAY_COOKIE}=${encodeURIComponent(
|
|
2579
|
+
pref
|
|
2580
|
+
)}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
|
|
2581
|
+
}
|
|
2582
|
+
function readNavDisplayLocal() {
|
|
2583
|
+
if (typeof localStorage === "undefined") return null;
|
|
2584
|
+
try {
|
|
2585
|
+
const current = localStorage.getItem(NAV_DISPLAY_STORAGE_KEY);
|
|
2586
|
+
if (isNavDisplayPreference(current)) return current;
|
|
2587
|
+
const legacy = localStorage.getItem(LEGACY_NAV_DISPLAY_KEY);
|
|
2588
|
+
return isNavDisplayPreference(legacy) ? legacy : null;
|
|
2589
|
+
} catch {
|
|
2590
|
+
return null;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
function writeNavDisplayLocal(pref) {
|
|
2594
|
+
if (typeof localStorage === "undefined") return;
|
|
2595
|
+
try {
|
|
2596
|
+
localStorage.setItem(NAV_DISPLAY_STORAGE_KEY, pref);
|
|
2597
|
+
} catch {
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
function resolveNavDisplay() {
|
|
2601
|
+
return readNavDisplayCookie() ?? readNavDisplayLocal() ?? NAV_DISPLAY_DEFAULT;
|
|
2602
|
+
}
|
|
2603
|
+
`(function(){try{
|
|
2604
|
+
var KEY=${JSON.stringify(NAV_DISPLAY_STORAGE_KEY)};
|
|
2605
|
+
var VALID=["menu","tabs","both"];
|
|
2606
|
+
function fromCookie(){try{var m=document.cookie.match(/(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}
|
|
2607
|
+
var cookie=fromCookie();
|
|
2608
|
+
if(cookie&&VALID.indexOf(cookie)!==-1){var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
|
|
2609
|
+
if(cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}}
|
|
2610
|
+
}catch(e){}})();`;
|
|
2611
|
+
|
|
2538
2612
|
// src/lib/breadcrumbs.ts
|
|
2539
2613
|
function deriveBreadcrumbs(items, pathname, opts) {
|
|
2540
2614
|
const search = opts?.search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
@@ -2688,6 +2762,7 @@ function watchLogout(onLogout, options = {}) {
|
|
|
2688
2762
|
return cleanup;
|
|
2689
2763
|
}
|
|
2690
2764
|
var CORE_APP = "vrm";
|
|
2765
|
+
var DEFAULT_SIGN_OUT_URL = "/auth/signout";
|
|
2691
2766
|
var POLITOGY_APPS = {
|
|
2692
2767
|
vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
|
|
2693
2768
|
contacts: { title: "Contacts", origin: "https://contacts.politogyvrm.com", storagePrefix: "contacts" },
|
|
@@ -2715,6 +2790,7 @@ function PolitogyAppFrame({
|
|
|
2715
2790
|
linkComponent,
|
|
2716
2791
|
session,
|
|
2717
2792
|
onSignOut,
|
|
2793
|
+
signOutUrl = DEFAULT_SIGN_OUT_URL,
|
|
2718
2794
|
buildMeta,
|
|
2719
2795
|
data,
|
|
2720
2796
|
children
|
|
@@ -2742,6 +2818,7 @@ function PolitogyAppFrame({
|
|
|
2742
2818
|
linkComponent,
|
|
2743
2819
|
session,
|
|
2744
2820
|
onSignOut,
|
|
2821
|
+
signOutUrl,
|
|
2745
2822
|
buildMeta,
|
|
2746
2823
|
data,
|
|
2747
2824
|
children
|
|
@@ -2757,6 +2834,7 @@ function FrameBody({
|
|
|
2757
2834
|
linkComponent,
|
|
2758
2835
|
session,
|
|
2759
2836
|
onSignOut,
|
|
2837
|
+
signOutUrl,
|
|
2760
2838
|
buildMeta,
|
|
2761
2839
|
data,
|
|
2762
2840
|
children
|
|
@@ -2770,14 +2848,12 @@ function FrameBody({
|
|
|
2770
2848
|
signingOutRef.current = true;
|
|
2771
2849
|
broadcastLogout();
|
|
2772
2850
|
signOutRef.current();
|
|
2773
|
-
window.
|
|
2774
|
-
|
|
2775
|
-
}, 2e3);
|
|
2776
|
-
}, []);
|
|
2851
|
+
window.location.assign(signOutUrl);
|
|
2852
|
+
}, [signOutUrl]);
|
|
2777
2853
|
react.useEffect(
|
|
2778
2854
|
() => (
|
|
2779
|
-
// A tab that learns of a sign-out elsewhere
|
|
2780
|
-
//
|
|
2855
|
+
// A tab that learns of a sign-out elsewhere ends its own session for
|
|
2856
|
+
// real, by navigating to the same sign-out route.
|
|
2781
2857
|
//
|
|
2782
2858
|
// Measured against production before this landed: calling the app's
|
|
2783
2859
|
// handler did nothing at all. Apps wire it as
|
|
@@ -2794,10 +2870,10 @@ function FrameBody({
|
|
|
2794
2870
|
// already on record for every tab.
|
|
2795
2871
|
watchLogout(() => {
|
|
2796
2872
|
if (signingOutRef.current) return;
|
|
2797
|
-
window.location.
|
|
2873
|
+
window.location.assign(signOutUrl);
|
|
2798
2874
|
})
|
|
2799
2875
|
),
|
|
2800
|
-
[]
|
|
2876
|
+
[signOutUrl]
|
|
2801
2877
|
);
|
|
2802
2878
|
const core = app === CORE_APP ? data?.core : void 0;
|
|
2803
2879
|
const identity = useChromeIdentity({ userName: session.userName, email: session.email });
|
|
@@ -2925,7 +3001,6 @@ var standardNavIcons = {
|
|
|
2925
3001
|
};
|
|
2926
3002
|
var ORG_STORAGE_KEY = "vrm.activeOrg";
|
|
2927
3003
|
var MODE_STORAGE_KEY = "vrm.activeMode";
|
|
2928
|
-
var NAV_DISPLAY_KEY = "vrm.navDisplay";
|
|
2929
3004
|
var CORE_BASE_URL = "https://app.politogyvrm.com";
|
|
2930
3005
|
var ChromeStateContext = react.createContext(null);
|
|
2931
3006
|
function useChromeState() {
|
|
@@ -2936,18 +3011,46 @@ function ChromeStateProvider({
|
|
|
2936
3011
|
children
|
|
2937
3012
|
}) {
|
|
2938
3013
|
const { orgs, loading: orgsFetching, error: orgsError } = useChromeOrgs();
|
|
3014
|
+
const chromeMe = useChromeMe();
|
|
3015
|
+
const chromeFetch = useNavChromeFetcher();
|
|
2939
3016
|
const [orgId, setOrgIdState] = react.useState(null);
|
|
2940
|
-
const [navDisplay, setNavDisplayState] = react.useState(
|
|
3017
|
+
const [navDisplay, setNavDisplayState] = react.useState(NAV_DISPLAY_DEFAULT);
|
|
3018
|
+
const navReconciled = react.useRef(false);
|
|
2941
3019
|
react.useEffect(() => {
|
|
2942
3020
|
try {
|
|
2943
3021
|
const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY);
|
|
2944
3022
|
if (storedOrg) setOrgIdState(storedOrg);
|
|
2945
|
-
const storedNav = localStorage.getItem(NAV_DISPLAY_KEY);
|
|
2946
|
-
if (storedNav === "menu" || storedNav === "tabs" || storedNav === "both") {
|
|
2947
|
-
setNavDisplayState(storedNav);
|
|
2948
|
-
}
|
|
2949
3023
|
} catch {
|
|
2950
3024
|
}
|
|
3025
|
+
setNavDisplayState(resolveNavDisplay());
|
|
3026
|
+
}, []);
|
|
3027
|
+
react.useEffect(() => {
|
|
3028
|
+
if (navReconciled.current || !chromeMe.me) return;
|
|
3029
|
+
const pref = chromeMe.me.navDisplay;
|
|
3030
|
+
if (!isNavDisplayPreference(pref)) return;
|
|
3031
|
+
navReconciled.current = true;
|
|
3032
|
+
setNavDisplayState(pref);
|
|
3033
|
+
writeNavDisplayCookie(pref);
|
|
3034
|
+
writeNavDisplayLocal(pref);
|
|
3035
|
+
}, [chromeMe]);
|
|
3036
|
+
react.useEffect(() => {
|
|
3037
|
+
const onStorage = (e) => {
|
|
3038
|
+
if (e.key === NAV_DISPLAY_STORAGE_KEY && isNavDisplayPreference(e.newValue)) {
|
|
3039
|
+
setNavDisplayState(e.newValue);
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
const onWake = () => {
|
|
3043
|
+
const cookie = readNavDisplayCookie();
|
|
3044
|
+
if (cookie) setNavDisplayState(cookie);
|
|
3045
|
+
};
|
|
3046
|
+
window.addEventListener("storage", onStorage);
|
|
3047
|
+
window.addEventListener("focus", onWake);
|
|
3048
|
+
document.addEventListener("visibilitychange", onWake);
|
|
3049
|
+
return () => {
|
|
3050
|
+
window.removeEventListener("storage", onStorage);
|
|
3051
|
+
window.removeEventListener("focus", onWake);
|
|
3052
|
+
document.removeEventListener("visibilitychange", onWake);
|
|
3053
|
+
};
|
|
2951
3054
|
}, []);
|
|
2952
3055
|
react.useEffect(() => {
|
|
2953
3056
|
if (!orgs || orgs.length === 0) return;
|
|
@@ -2961,13 +3064,20 @@ function ChromeStateProvider({
|
|
|
2961
3064
|
}
|
|
2962
3065
|
writeActiveOrgCookie(id);
|
|
2963
3066
|
}, []);
|
|
2964
|
-
const setNavDisplay = react.useCallback(
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
3067
|
+
const setNavDisplay = react.useCallback(
|
|
3068
|
+
(m) => {
|
|
3069
|
+
setNavDisplayState(m);
|
|
3070
|
+
writeNavDisplayLocal(m);
|
|
3071
|
+
writeNavDisplayCookie(m);
|
|
3072
|
+
chromeFetch?.("me/preferences", {
|
|
3073
|
+
method: "PATCH",
|
|
3074
|
+
headers: { "Content-Type": "application/json" },
|
|
3075
|
+
body: JSON.stringify({ navDisplay: m })
|
|
3076
|
+
})?.catch(() => {
|
|
3077
|
+
});
|
|
3078
|
+
},
|
|
3079
|
+
[chromeFetch]
|
|
3080
|
+
);
|
|
2971
3081
|
const value = react.useMemo(() => {
|
|
2972
3082
|
if (controlledOrg) {
|
|
2973
3083
|
return {
|