@the-portland-company/shell 0.34.0 → 0.35.1

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.
@@ -1569,9 +1569,25 @@ function NavSidebar({
1569
1569
  org ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
1570
1570
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between px-1", children: [
1571
1571
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Organization" }),
1572
- org.id ? /* @__PURE__ */ jsxRuntime.jsx(OrgIdCopyButton, { id: org.id }) : null
1572
+ org.id && !org.loading ? /* @__PURE__ */ jsxRuntime.jsx(OrgIdCopyButton, { id: org.id }) : null
1573
1573
  ] }),
1574
- org.organizations && org.organizations.length > 0 && org.onSwitch ? /* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group relative", children: [
1574
+ org.loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2.5 rounded-md border border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-2.5 py-2 shadow-sm", children: [
1575
+ /* @__PURE__ */ jsxRuntime.jsx(
1576
+ "span",
1577
+ {
1578
+ "aria-hidden": true,
1579
+ className: "h-7 w-7 shrink-0 animate-pulse rounded-full bg-[color:var(--panel)]"
1580
+ }
1581
+ ),
1582
+ /* @__PURE__ */ jsxRuntime.jsx(
1583
+ "span",
1584
+ {
1585
+ "aria-hidden": true,
1586
+ className: "h-4 flex-1 animate-pulse rounded bg-[color:var(--panel)]"
1587
+ }
1588
+ ),
1589
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Loading organization" })
1590
+ ] }) : org.organizations && org.organizations.length > 0 && org.onSwitch ? /* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group relative", children: [
1575
1591
  /* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex w-full cursor-pointer list-none items-center justify-between gap-2 rounded-md border border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-2.5 py-2 text-left text-[color:var(--foreground)] shadow-sm transition hover:bg-[color:var(--panel)] [&::-webkit-details-marker]:hidden", children: [
1576
1592
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
1577
1593
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2479,6 +2495,7 @@ function resolveIdentity(input) {
2479
2495
  loaded: input.loaded ?? false
2480
2496
  };
2481
2497
  }
2498
+ var CORE_APP = "vrm";
2482
2499
  var POLITOGY_APPS = {
2483
2500
  vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
2484
2501
  contacts: { title: "Contacts", origin: "https://contacts.politogyvrm.com", storagePrefix: "contacts" },
@@ -2554,21 +2571,24 @@ function FrameBody({
2554
2571
  }) {
2555
2572
  const meta = POLITOGY_APPS[app];
2556
2573
  const chrome = useChromeState();
2574
+ const core = app === CORE_APP ? data?.core : void 0;
2557
2575
  const identity = useChromeIdentity({ userName: session.userName, email: session.email });
2558
- const userMenu = useChromeUserMenu({
2576
+ const derivedUserMenu = useChromeUserMenu({
2559
2577
  userId: session.userId,
2560
2578
  userName: identity.userName,
2561
2579
  email: identity.email
2562
2580
  });
2581
+ const userMenu = core?.userMenu ?? derivedUserMenu;
2563
2582
  const footer = useChromeFooter(buildMeta ?? {});
2564
2583
  const appMode = meta.mode;
2565
- const mode = useChromeMode(appMode ? { pinnedModeId: appMode } : void 0);
2584
+ const derivedMode = useChromeMode(appMode ? { pinnedModeId: appMode } : void 0);
2585
+ const mode = core?.mode ?? derivedMode;
2566
2586
  const coreOrgs = chrome?.orgs ?? [];
2567
2587
  const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null;
2568
2588
  const navDisplay = chrome?.navDisplay ?? "both";
2569
2589
  const navMode = appMode ?? "relationship";
2570
2590
  const activeOrgId = chrome?.orgId ?? null;
2571
- const navItems = react.useMemo(
2591
+ const derivedNavItems = react.useMemo(
2572
2592
  () => standardPolitogyNav({
2573
2593
  coreBaseUrl: POLITOGY_APPS.vrm.origin,
2574
2594
  icons: standardNavIcons,
@@ -2580,17 +2600,19 @@ function FrameBody({
2580
2600
  }),
2581
2601
  [data?.contactsItem, navMode, activeOrgId]
2582
2602
  );
2583
- const breadcrumbs = react.useMemo(
2603
+ const navItems = core?.nav ?? derivedNavItems;
2604
+ const derivedBreadcrumbs = react.useMemo(
2584
2605
  () => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),
2585
2606
  [navItems, pathname, search, meta.origin]
2586
2607
  );
2608
+ const breadcrumbs = core ? core.page?.breadcrumbs : derivedBreadcrumbs;
2587
2609
  return /* @__PURE__ */ jsxRuntime.jsx(
2588
2610
  NavAppShell,
2589
2611
  {
2590
2612
  sidebar: {
2591
2613
  pathname,
2592
2614
  brand: {
2593
- logoSrc: "/brand/logos/relationship-mode-logo.png",
2615
+ logoSrc: core?.brandLogoSrc ?? "/brand/logos/relationship-mode-logo.png",
2594
2616
  logoAlt: "Politogy",
2595
2617
  badgeLabel: "P",
2596
2618
  title: "Politogy"
@@ -2598,6 +2620,11 @@ function FrameBody({
2598
2620
  nav: {
2599
2621
  items: navDisplay === "tabs" ? navItems.map(({ children: _sub, ...item }) => item) : navItems
2600
2622
  },
2623
+ // The app's own workspace is NOT the organization — it is a per-app
2624
+ // bootstrap account with a different id in every app. Showing it while
2625
+ // the chrome API resolves flashed the wrong org on every load ("not
2626
+ // connected to the account"). Shimmer while in flight; only fall back
2627
+ // to the app label once the chrome lookup has actually failed.
2601
2628
  org: activeCoreOrg ? {
2602
2629
  name: activeCoreOrg.name,
2603
2630
  id: activeCoreOrg.id,
@@ -2605,14 +2632,17 @@ function FrameBody({
2605
2632
  brandColor: activeCoreOrg.brandColor,
2606
2633
  organizations: coreOrgs,
2607
2634
  onSwitch: chrome?.setOrgId
2608
- } : { name: session.accountName, id: session.accountId },
2635
+ } : chrome?.orgsLoading ? { name: null, id: null, loading: true } : { name: session.accountName, id: session.accountId },
2609
2636
  mode,
2610
2637
  footer,
2611
2638
  linkComponent
2612
2639
  },
2613
2640
  betaBanner: {},
2641
+ contentWrapper: core?.contentWrapper,
2614
2642
  topBar: {
2615
- title: meta.title,
2643
+ // Core's pages publish their own identity; every other app is one
2644
+ // product surface and takes its registry title.
2645
+ title: core?.page?.title ?? meta.title,
2616
2646
  breadcrumbs,
2617
2647
  user: {
2618
2648
  name: identity.userName,
@@ -2624,7 +2654,13 @@ function FrameBody({
2624
2654
  },
2625
2655
  userMenu,
2626
2656
  onSignOut,
2627
- actions: /* @__PURE__ */ jsxRuntime.jsx(ChromeTopBarActions, {})
2657
+ // The action cluster is IDENTICAL everywhere — core included. Page
2658
+ // actions sit BEFORE it, never inside it, so the Alerts bell keeps its
2659
+ // required position: farthest right, immediately before My Account.
2660
+ actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2661
+ core?.page?.actions,
2662
+ /* @__PURE__ */ jsxRuntime.jsx(ChromeTopBarActions, {})
2663
+ ] })
2628
2664
  },
2629
2665
  children
2630
2666
  }
@@ -2671,7 +2707,7 @@ function ChromeStateProvider({
2671
2707
  controlledOrg,
2672
2708
  children
2673
2709
  }) {
2674
- const { orgs } = useChromeOrgs();
2710
+ const { orgs, loading: orgsFetching, error: orgsError } = useChromeOrgs();
2675
2711
  const [orgId, setOrgIdState] = react.useState(null);
2676
2712
  const [navDisplay, setNavDisplayState] = react.useState("both");
2677
2713
  react.useEffect(() => {
@@ -2716,6 +2752,7 @@ function ChromeStateProvider({
2716
2752
  role: o.role,
2717
2753
  rawRole: o.rawRole ?? null
2718
2754
  })),
2755
+ orgsLoading: false,
2719
2756
  navDisplay,
2720
2757
  setNavDisplay
2721
2758
  };
@@ -2731,10 +2768,13 @@ function ChromeStateProvider({
2731
2768
  role: o.role,
2732
2769
  rawRole: o.rawRole ?? null
2733
2770
  })),
2771
+ // Loading until the fetch settles. On error we stop loading so the frame
2772
+ // falls back to the app label rather than shimmering forever.
2773
+ orgsLoading: orgsFetching && !orgsError && !orgs,
2734
2774
  navDisplay,
2735
2775
  setNavDisplay
2736
2776
  };
2737
- }, [controlledOrg, orgId, setOrgId, orgs, navDisplay, setNavDisplay]);
2777
+ }, [controlledOrg, orgId, setOrgId, orgs, orgsFetching, orgsError, navDisplay, setNavDisplay]);
2738
2778
  return /* @__PURE__ */ jsxRuntime.jsx(ChromeStateContext.Provider, { value, children });
2739
2779
  }
2740
2780
  function AppDevNotes({
@@ -2770,6 +2810,7 @@ function AppDevNotes({
2770
2810
  }),
2771
2811
  [storagePrefix, identity.role]
2772
2812
  );
2813
+ if (devNotes?.providedByApp) return /* @__PURE__ */ jsxRuntime.jsx(DevNotesSurface, { children });
2773
2814
  return /* @__PURE__ */ jsxRuntime.jsx(devnotes.DevNotesProvider, { adapter, user, config, children: /* @__PURE__ */ jsxRuntime.jsx(DevNotesSurface, { children }) });
2774
2815
  }
2775
2816
  var DevNotesTasksContext = react.createContext({