@the-portland-company/shell 0.82.0 → 0.85.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.
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { S as StandardNavIconKey, f as NavIcon, N as NavItem, g as NavBreadcrumb, h as StandardNavMode, i as NavLinkComponent, j as NavChromeFetcher, k as NavMode, l as NavUserMenu, m as NavOrgOption, b as NotificationsAdapter, o as NavFooterLink, p as NavFooter, q as NavDisplayMode } from './index-C3PFq1hl.cjs';
3
+ import { S as StandardNavIconKey, f as NavIcon, N as NavItem, g as NavBreadcrumb, h as StandardNavMode, i as NavLinkComponent, j as NavChromeFetcher, k as NavMode, l as NavUserMenu, m as NavOrgOption, b as NotificationsAdapter, o as NavFooterLink, p as NavFooter, q as NavDisplayMode } from './index-bfbWLC7_.cjs';
4
4
  import './theme-preference.cjs';
5
5
 
6
6
  /**
@@ -401,6 +401,21 @@ type ChromeState = {
401
401
  navDisplay: NavDisplayMode;
402
402
  setNavDisplay: (m: NavDisplayMode) => void;
403
403
  };
404
+ /**
405
+ * Where an organization switch goes (Specs/Left-Navigation §6, §11.2, decided
406
+ * 2026-10-15 by Ben): the new organization's Relationship Dashboard, from every
407
+ * app, Mode and page, behind the Loading Screen. The Relationship Dashboard is
408
+ * the one page every organization has, so nothing the user was looking at is
409
+ * carried into an organization that may not have it. (Measured before this: a
410
+ * switch from Campaign Mode to an organization without it kept the Campaign
411
+ * rail, then showed an access error that needed a sign-out.)
412
+ *
413
+ * An object so tests can replace `navigate` (jsdom cannot navigate).
414
+ */
415
+ declare const orgSwitchLanding: {
416
+ href(orgId: string): string | null;
417
+ navigate(href: string): void;
418
+ };
404
419
  declare function useChromeState(): ChromeState | null;
405
420
  type ControlledOrgState = {
406
421
  orgId: string | null;
@@ -580,4 +595,4 @@ type UseChromeModeArg = ModeId | {
580
595
  */
581
596
  declare function useChromeMode(arg?: UseChromeModeArg): NavMode;
582
597
 
583
- export { type BootPhase, type BootProgress, type BootStageId, type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, LoadingScreen, MODE_SEARCH_PLACEHOLDER, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, type TokenOrgState, deriveBreadcrumbs, footerLinks, looksLikeEmail, modeLandingHref, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
598
+ export { type BootPhase, type BootProgress, type BootStageId, type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, LoadingScreen, MODE_SEARCH_PLACEHOLDER, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, type TokenOrgState, deriveBreadcrumbs, footerLinks, looksLikeEmail, modeLandingHref, orgSwitchLanding, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { S as StandardNavIconKey, f as NavIcon, N as NavItem, g as NavBreadcrumb, h as StandardNavMode, i as NavLinkComponent, j as NavChromeFetcher, k as NavMode, l as NavUserMenu, m as NavOrgOption, b as NotificationsAdapter, o as NavFooterLink, p as NavFooter, q as NavDisplayMode } from './index-CwTnsR9R.js';
3
+ import { S as StandardNavIconKey, f as NavIcon, N as NavItem, g as NavBreadcrumb, h as StandardNavMode, i as NavLinkComponent, j as NavChromeFetcher, k as NavMode, l as NavUserMenu, m as NavOrgOption, b as NotificationsAdapter, o as NavFooterLink, p as NavFooter, q as NavDisplayMode } from './index-C31E7M9l.js';
4
4
  import './theme-preference.js';
5
5
 
6
6
  /**
@@ -401,6 +401,21 @@ type ChromeState = {
401
401
  navDisplay: NavDisplayMode;
402
402
  setNavDisplay: (m: NavDisplayMode) => void;
403
403
  };
404
+ /**
405
+ * Where an organization switch goes (Specs/Left-Navigation §6, §11.2, decided
406
+ * 2026-10-15 by Ben): the new organization's Relationship Dashboard, from every
407
+ * app, Mode and page, behind the Loading Screen. The Relationship Dashboard is
408
+ * the one page every organization has, so nothing the user was looking at is
409
+ * carried into an organization that may not have it. (Measured before this: a
410
+ * switch from Campaign Mode to an organization without it kept the Campaign
411
+ * rail, then showed an access error that needed a sign-out.)
412
+ *
413
+ * An object so tests can replace `navigate` (jsdom cannot navigate).
414
+ */
415
+ declare const orgSwitchLanding: {
416
+ href(orgId: string): string | null;
417
+ navigate(href: string): void;
418
+ };
404
419
  declare function useChromeState(): ChromeState | null;
405
420
  type ControlledOrgState = {
406
421
  orgId: string | null;
@@ -580,4 +595,4 @@ type UseChromeModeArg = ModeId | {
580
595
  */
581
596
  declare function useChromeMode(arg?: UseChromeModeArg): NavMode;
582
597
 
583
- export { type BootPhase, type BootProgress, type BootStageId, type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, LoadingScreen, MODE_SEARCH_PLACEHOLDER, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, type TokenOrgState, deriveBreadcrumbs, footerLinks, looksLikeEmail, modeLandingHref, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
598
+ export { type BootPhase, type BootProgress, type BootStageId, type ChromeDevNotesConfig, type ChromeIdentity, ChromeTopBarActions, type ControlledOrgState, LoadingScreen, MODE_SEARCH_PLACEHOLDER, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, type PolitogyAppKey, type PolitogyAppMode, type PolitogyAppSession, type PolitogyBuildMeta, type PolitogyFrameData, type PolitogyFrameGrant, type PolitogyGrantedFrame, type ResolvedIdentity, type TokenOrgState, deriveBreadcrumbs, footerLinks, looksLikeEmail, modeLandingHref, orgSwitchLanding, resolveIdentity, standardNavIcons, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { broadcastLogout, watchLogout } from './chunk-LL6LSJAH.js';
3
- import { clearBetaBannerDismissal, standardPolitogyNav, standardNavIcons, isNavItemActive, NavAppShell } from './chunk-VWY6IIAR.js';
4
- export { standardNavIcons } from './chunk-VWY6IIAR.js';
5
- import { warnDeprecated, NavChromeProvider, TopBarWidgetSlot, FuelAction, SpecOverlayAction, NotificationsAction, SupportAction, useChromeMe, useNavChromeDedupedFetch, useChromeOrgs, useNavChromeFetcher } from './chunk-L73FZKGE.js';
3
+ import { clearBetaBannerDismissal, standardPolitogyNav, standardNavIcons, isNavItemActive, NavAppShell } from './chunk-OOVS5D2K.js';
4
+ export { standardNavIcons } from './chunk-OOVS5D2K.js';
5
+ import { warnDeprecated, NavChromeProvider, TopBarWidgetSlot, FuelAction, SpecOverlayAction, NotificationsAction, SupportAction, useChromeMe, useNavChromeDedupedFetch, useChromeOrgs, useNavChromeFetcher } from './chunk-PRZP2SB6.js';
6
6
  import './chunk-BHRVZPZ7.js';
7
7
  import { NAV_DISPLAY_DEFAULT, resolveNavDisplay, writeNavDisplayLocal, writeNavDisplayCookie, isNavDisplayPreference, NAV_DISPLAY_STORAGE_KEY, readNavDisplayCookie } from './chunk-7YVJ7HQG.js';
8
8
  import { authBaseUrl, orgIdTail, settingsBaseUrl, ORG_ID_QUERY_PARAM, readOrgSelectorFromUrl, matchOrgSelector, syncOrgIdInUrl, isOrgId, switchActiveOrg, ACTIVE_ORG_QUERY_PARAM, writeActiveOrgCookie, resolveActiveOrg, readActiveOrgCookie } from './chunk-5LO5W7W5.js';
@@ -520,10 +520,19 @@ var CORE_APP = "vrm";
520
520
  var DEFAULT_SIGN_OUT_URL = "/auth/signout";
521
521
  var POLITOGY_APPS = {
522
522
  vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
523
+ // Contacts, Forms, Surveys and CTAs are Relationship rows only (Specs/
524
+ // Left-Navigation §9.1; Campaign has none of them, §9.2), so they are
525
+ // PINNED to Relationship like the Dashboard below. Left unscoped, the mode
526
+ // switcher and rail colour took the user's last mode choice while the rows
527
+ // stayed Relationship: after picking Campaign, Contacts drew the red
528
+ // "Campaign mode" rail around Relationship rows, even after a reload
529
+ // (measured 2026-10-16 on We need Steve; §11.1 "the rail never shows a mix
530
+ // of two modes").
523
531
  contacts: {
524
532
  title: "Contacts",
525
533
  origin: "https://contacts.politogyvrm.com",
526
534
  storagePrefix: "contacts",
535
+ mode: "relationship",
527
536
  pageTitle: false
528
537
  },
529
538
  // 'title' for forms/surveys/polls/ctas for the same reason as petitions
@@ -532,11 +541,11 @@ var POLITOGY_APPS = {
532
541
  // page header falls back to the static registry title forever, so the
533
542
  // designed "<mode> mode" eyebrow + H2 + subtitle block never gets the real
534
543
  // per-page title an app passes it (UI-Platform → Page header).
535
- forms: { title: "Forms", origin: "https://forms.politogyvrm.com", storagePrefix: "forms", grants: ["title"] },
536
- surveys: { title: "Surveys", origin: "https://surveys.politogyvrm.com", storagePrefix: "surveys", grants: ["title"] },
544
+ forms: { title: "Forms", origin: "https://forms.politogyvrm.com", storagePrefix: "forms", mode: "relationship", grants: ["title"] },
545
+ surveys: { title: "Surveys", origin: "https://surveys.politogyvrm.com", storagePrefix: "surveys", mode: "relationship", grants: ["title"] },
537
546
  // Specs/Left-Navigation §9.2: Polling is a Campaign row (Research).
538
547
  polls: { title: "Polls", origin: "https://polls.politogyvrm.com", storagePrefix: "polls", mode: "campaign", grants: ["title"] },
539
- ctas: { title: "CTAs", origin: "https://ctas.politogyvrm.com", storagePrefix: "ctas", grants: ["title"] },
548
+ ctas: { title: "CTAs", origin: "https://ctas.politogyvrm.com", storagePrefix: "ctas", mode: "relationship", grants: ["title"] },
540
549
  // V2 apps that replace retired app.politogyvrm.com pages (Reference/
541
550
  // UI-Platform v42 "Retirement of app.politogyvrm.com"). Dashboard is the
542
551
  // Relationship Dashboard, the Relationship Mode landing page, and nothing
@@ -760,7 +769,7 @@ function FrameBody({
760
769
  const coreOrgs = chrome?.orgs ?? [];
761
770
  const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null;
762
771
  const navDisplay = chrome?.navDisplay ?? "both";
763
- const navMode = appMode ?? "relationship";
772
+ const navMode = appMode ?? (app !== CORE_APP && isStandardNavMode(mode.id) ? mode.id : "relationship");
764
773
  const activeOrgId = chrome?.orgId ?? null;
765
774
  const derivedNavItems = useMemo(
766
775
  () => standardPolitogyNav({
@@ -803,7 +812,7 @@ function FrameBody({
803
812
  {
804
813
  visible: bootVisible,
805
814
  bootProgress,
806
- syntheticReady: appReady ?? chromeResolved,
815
+ syntheticReady: (appReady ?? chromeResolved) && !chrome?.orgPending && (appMode !== void 0 || mode.resolved !== false),
807
816
  onExitComplete: () => setBootVisible(false)
808
817
  },
809
818
  bootGeneration
@@ -893,6 +902,18 @@ function FrameBody({
893
902
  }
894
903
  var ORG_STORAGE_KEY = "vrm.activeOrg";
895
904
  var MODE_STORAGE_KEY = "vrm.activeMode";
905
+ var orgSwitchLanding = {
906
+ href(orgId) {
907
+ return modeLandingHref("relationship", {
908
+ orgId,
909
+ hostname: typeof window === "undefined" ? null : window.location.hostname
910
+ });
911
+ },
912
+ navigate(href) {
913
+ window.location.assign(href);
914
+ }
915
+ };
916
+ var ORG_SWITCH_REEXCHANGE_BUDGET_MS = 4e3;
896
917
  var ChromeStateContext = createContext(null);
897
918
  function useChromeState() {
898
919
  return useContext(ChromeStateContext);
@@ -908,6 +929,7 @@ function TokenChromeStateProvider({
908
929
  const [pendingOrg, setPendingOrg] = useState(null);
909
930
  const tokenOrgRef = useRef(tokenOrg);
910
931
  tokenOrgRef.current = tokenOrg;
932
+ const leavingRef = useRef(false);
911
933
  useEffect(() => {
912
934
  setNavDisplayState(resolveNavDisplay());
913
935
  }, []);
@@ -948,6 +970,7 @@ function TokenChromeStateProvider({
948
970
  }, [overrideOrg, tokenOrg.orgId]);
949
971
  useEffect(() => {
950
972
  if (!pendingOrg || tokenOrg.orgId !== pendingOrg) return;
973
+ if (leavingRef.current) return;
951
974
  setPendingOrg(null);
952
975
  try {
953
976
  const next = syncOrgIdInUrl(window.location.href, pendingOrg);
@@ -970,8 +993,16 @@ function TokenChromeStateProvider({
970
993
  window.history.replaceState(window.history.state, "", url.toString());
971
994
  } catch {
972
995
  }
973
- await tokenOrgRef.current.onReexchange(claims.orgId, { perTab: false });
996
+ leavingRef.current = true;
997
+ await Promise.race([
998
+ Promise.resolve(tokenOrgRef.current.onReexchange(claims.orgId, { perTab: false })).catch(() => {
999
+ }),
1000
+ new Promise((resolve) => setTimeout(resolve, ORG_SWITCH_REEXCHANGE_BUDGET_MS))
1001
+ ]);
1002
+ const href = orgSwitchLanding.href(claims.orgId);
1003
+ if (href) orgSwitchLanding.navigate(href);
974
1004
  } catch (err) {
1005
+ leavingRef.current = false;
975
1006
  setPendingOrg(null);
976
1007
  tokenOrgRef.current.onSwitchError?.(err);
977
1008
  }
@@ -1036,10 +1067,13 @@ function ChromeStateProvider({
1036
1067
  const navReconciled = useRef(false);
1037
1068
  const orgReconciled = useRef(false);
1038
1069
  const hadBootSelector = useRef(false);
1070
+ const bootUrlTail = useRef(null);
1039
1071
  useEffect(() => {
1040
1072
  try {
1073
+ const urlSelector = readOrgSelectorFromUrl(window.location.search);
1074
+ if (urlSelector && !isOrgId(urlSelector)) bootUrlTail.current = urlSelector;
1041
1075
  const selector = resolveActiveOrg();
1042
- if (selector) hadBootSelector.current = true;
1076
+ if (selector || urlSelector) hadBootSelector.current = true;
1043
1077
  const storedOrg = selector ?? localStorage.getItem(ORG_STORAGE_KEY);
1044
1078
  if (storedOrg) setOrgIdState(storedOrg);
1045
1079
  } catch {
@@ -1076,7 +1110,9 @@ function ChromeStateProvider({
1076
1110
  }, []);
1077
1111
  useEffect(() => {
1078
1112
  if (!orgs || orgs.length === 0) return;
1079
- setOrgIdState((prev) => prev && orgs.some((o) => o.id === prev) ? prev : orgs[0].id);
1113
+ const fromUrl = matchOrgSelector(bootUrlTail.current, orgs)?.id ?? null;
1114
+ bootUrlTail.current = null;
1115
+ setOrgIdState((prev) => fromUrl ?? (prev && orgs.some((o) => o.id === prev) ? prev : orgs[0].id));
1080
1116
  }, [orgs]);
1081
1117
  const activeOrgRef = useRef(null);
1082
1118
  const memberOrgsRef = useRef([]);
@@ -1134,6 +1170,10 @@ function ChromeStateProvider({
1134
1170
  } catch {
1135
1171
  }
1136
1172
  writeActiveOrgCookie(id);
1173
+ if (isOrgId(id)) {
1174
+ const href = orgSwitchLanding.href(id);
1175
+ if (href) orgSwitchLanding.navigate(href);
1176
+ }
1137
1177
  }, []);
1138
1178
  const setNavDisplay = useCallback(
1139
1179
  (m) => {
@@ -1200,8 +1240,10 @@ function ChromeStateProvider({
1200
1240
  }
1201
1241
  try {
1202
1242
  const url = new URL(window.location.href);
1203
- if (url.searchParams.get(ACTIVE_ORG_QUERY_PARAM) !== activeOrgId) {
1204
- url.searchParams.set(ACTIVE_ORG_QUERY_PARAM, activeOrgId);
1243
+ const tail = orgIdTail(activeOrgId);
1244
+ if (url.searchParams.has(ACTIVE_ORG_QUERY_PARAM) || url.searchParams.get(ORG_ID_QUERY_PARAM) !== tail) {
1245
+ url.searchParams.delete(ACTIVE_ORG_QUERY_PARAM);
1246
+ url.searchParams.set(ORG_ID_QUERY_PARAM, tail);
1205
1247
  window.history.replaceState(window.history.state, "", url.toString());
1206
1248
  }
1207
1249
  } catch {
@@ -1490,6 +1532,7 @@ function useChromeMode(arg = {}) {
1490
1532
  const orgId = ctx?.orgId ?? null;
1491
1533
  const orgPending = ctx?.orgPending ?? false;
1492
1534
  const [entitled, setEntitled] = useState(null);
1535
+ const [settledOrgId, setSettledOrgId] = useState(null);
1493
1536
  const [selectedModeId, setSelectedModeId] = useState(null);
1494
1537
  useEffect(() => {
1495
1538
  try {
@@ -1504,6 +1547,7 @@ function useChromeMode(arg = {}) {
1504
1547
  const bootMe = me;
1505
1548
  if (bootMe && bootMe.account === orgId && bootMe.modes !== void 0) {
1506
1549
  setEntitled(bootMe.modes ?? null);
1550
+ setSettledOrgId(orgId);
1507
1551
  return;
1508
1552
  }
1509
1553
  let cancelled = false;
@@ -1511,7 +1555,9 @@ function useChromeMode(arg = {}) {
1511
1555
  if (cancelled) return;
1512
1556
  const modes = data?.modes ?? null;
1513
1557
  setEntitled(modes);
1558
+ setSettledOrgId(orgId);
1514
1559
  }).catch(() => {
1560
+ if (!cancelled) setSettledOrgId(orgId);
1515
1561
  });
1516
1562
  return () => {
1517
1563
  cancelled = true;
@@ -1525,6 +1571,9 @@ function useChromeMode(arg = {}) {
1525
1571
  id: current.id,
1526
1572
  label: current.label,
1527
1573
  icon: current.icon,
1574
+ // A registry pin is final at once; otherwise the mode is final when the
1575
+ // active org's Modes are in (or nothing can be read: no chrome or no org).
1576
+ resolved: Boolean(pinned) || !dedupedFetch || !orgId || settledOrgId === orgId && !orgPending,
1528
1577
  // Left-Navigation §7 / Reference/UI-Platform: always the row of three icon
1529
1578
  // buttons. A mode the organization does not hold is shown dimmed and not
1530
1579
  // selectable, never hidden. Before entitlements land only the current mode
@@ -1553,6 +1602,6 @@ function useChromeMode(arg = {}) {
1553
1602
  };
1554
1603
  }
1555
1604
 
1556
- export { ChromeTopBarActions, LoadingScreen, MODE_SEARCH_PLACEHOLDER, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, deriveBreadcrumbs, footerLinks, looksLikeEmail, modeLandingHref, resolveIdentity, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
1605
+ export { ChromeTopBarActions, LoadingScreen, MODE_SEARCH_PLACEHOLDER, POLITOGY_APPS, PolitogyAppChrome, PolitogyAppFrame, deriveBreadcrumbs, footerLinks, looksLikeEmail, modeLandingHref, orgSwitchLanding, resolveIdentity, useChromeFooter, useChromeIdentity, useChromeMode, useChromeState, useChromeUserMenu, useIsSuperAdmin };
1557
1606
  //# sourceMappingURL=app-chrome.js.map
1558
1607
  //# sourceMappingURL=app-chrome.js.map