@the-portland-company/shell 0.36.3 → 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.
@@ -1,8 +1,9 @@
1
1
  "use client";
2
- import { standardPolitogyNav, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-LIGKGXFT.js';
2
+ import { broadcastLogout, watchLogout } from './chunk-LL6LSJAH.js';
3
+ import { standardPolitogyNav, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-CGSS2PDG.js';
3
4
  import './chunk-LL7RCJQN.js';
5
+ import { NAV_DISPLAY_DEFAULT, resolveNavDisplay, isNavDisplayPreference, writeNavDisplayCookie, writeNavDisplayLocal, NAV_DISPLAY_STORAGE_KEY, readNavDisplayCookie } from './chunk-6UHJV2FH.js';
4
6
  import { resolveActiveOrg, writeActiveOrgCookie, isOrgId, ACTIVE_ORG_QUERY_PARAM } from './chunk-W2HITMV5.js';
5
- import { broadcastLogout, watchLogout } from './chunk-LL6LSJAH.js';
6
7
  import { createContext, useRef, useCallback, useEffect, useMemo, useContext, useState } from 'react';
7
8
  import { DevNotesMenu, createDevNotesClient, DevNotesProvider, DevNotesTaskListModal, DevNotesOverlay } from '@the-portland-company/devnotes';
8
9
  import { FiFlag, FiTarget, FiMapPin, FiMap, FiPhone, FiShare2, FiDollarSign, FiCreditCard, FiSettings, FiBriefcase, FiBarChart2, FiUser, FiClock, FiSend, FiFileText, FiEdit3, FiUpload, FiList, FiGrid, FiLifeBuoy, FiActivity, FiLayout, FiMessageSquare, FiMail, FiUsers, FiHome } from 'react-icons/fi';
@@ -317,7 +318,6 @@ var standardNavIcons = {
317
318
  };
318
319
  var ORG_STORAGE_KEY = "vrm.activeOrg";
319
320
  var MODE_STORAGE_KEY = "vrm.activeMode";
320
- var NAV_DISPLAY_KEY = "vrm.navDisplay";
321
321
  var CORE_BASE_URL = "https://app.politogyvrm.com";
322
322
  var ChromeStateContext = createContext(null);
323
323
  function useChromeState() {
@@ -328,18 +328,46 @@ function ChromeStateProvider({
328
328
  children
329
329
  }) {
330
330
  const { orgs, loading: orgsFetching, error: orgsError } = useChromeOrgs();
331
+ const chromeMe = useChromeMe();
332
+ const chromeFetch = useNavChromeFetcher();
331
333
  const [orgId, setOrgIdState] = useState(null);
332
- const [navDisplay, setNavDisplayState] = useState("both");
334
+ const [navDisplay, setNavDisplayState] = useState(NAV_DISPLAY_DEFAULT);
335
+ const navReconciled = useRef(false);
333
336
  useEffect(() => {
334
337
  try {
335
338
  const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY);
336
339
  if (storedOrg) setOrgIdState(storedOrg);
337
- const storedNav = localStorage.getItem(NAV_DISPLAY_KEY);
338
- if (storedNav === "menu" || storedNav === "tabs" || storedNav === "both") {
339
- setNavDisplayState(storedNav);
340
- }
341
340
  } catch {
342
341
  }
342
+ setNavDisplayState(resolveNavDisplay());
343
+ }, []);
344
+ useEffect(() => {
345
+ if (navReconciled.current || !chromeMe.me) return;
346
+ const pref = chromeMe.me.navDisplay;
347
+ if (!isNavDisplayPreference(pref)) return;
348
+ navReconciled.current = true;
349
+ setNavDisplayState(pref);
350
+ writeNavDisplayCookie(pref);
351
+ writeNavDisplayLocal(pref);
352
+ }, [chromeMe]);
353
+ useEffect(() => {
354
+ const onStorage = (e) => {
355
+ if (e.key === NAV_DISPLAY_STORAGE_KEY && isNavDisplayPreference(e.newValue)) {
356
+ setNavDisplayState(e.newValue);
357
+ }
358
+ };
359
+ const onWake = () => {
360
+ const cookie = readNavDisplayCookie();
361
+ if (cookie) setNavDisplayState(cookie);
362
+ };
363
+ window.addEventListener("storage", onStorage);
364
+ window.addEventListener("focus", onWake);
365
+ document.addEventListener("visibilitychange", onWake);
366
+ return () => {
367
+ window.removeEventListener("storage", onStorage);
368
+ window.removeEventListener("focus", onWake);
369
+ document.removeEventListener("visibilitychange", onWake);
370
+ };
343
371
  }, []);
344
372
  useEffect(() => {
345
373
  if (!orgs || orgs.length === 0) return;
@@ -353,13 +381,20 @@ function ChromeStateProvider({
353
381
  }
354
382
  writeActiveOrgCookie(id);
355
383
  }, []);
356
- const setNavDisplay = useCallback((m) => {
357
- setNavDisplayState(m);
358
- try {
359
- localStorage.setItem(NAV_DISPLAY_KEY, m);
360
- } catch {
361
- }
362
- }, []);
384
+ const setNavDisplay = useCallback(
385
+ (m) => {
386
+ setNavDisplayState(m);
387
+ writeNavDisplayLocal(m);
388
+ writeNavDisplayCookie(m);
389
+ chromeFetch?.("me/preferences", {
390
+ method: "PATCH",
391
+ headers: { "Content-Type": "application/json" },
392
+ body: JSON.stringify({ navDisplay: m })
393
+ })?.catch(() => {
394
+ });
395
+ },
396
+ [chromeFetch]
397
+ );
363
398
  const value = useMemo(() => {
364
399
  if (controlledOrg) {
365
400
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/breadcrumbs.ts","../src/lib/identity.ts","../src/app-chrome/frame.tsx","../src/app-chrome/index.tsx"],"names":["trail","useEffect","useCallback","useMemo","jsx","jsxs","Fragment"],"mappings":"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","file":"app-chrome.js","sourcesContent":["// Breadcrumb derivation — pure, dependency-free logic (no React, no DevNotes),\n// so it is importable from a server module and testable in isolation.\n//\n// The standard is Dashboard > Sub > Item (HITL 2026-07-23), derived from the\n// same nav tree the sidebar renders. Apps previously hand-built their own\n// trails and disagreed: forms rendered \"Forms > Dashboard\" while polls rendered\n// a lone \"Polls\" crumb under a \"Polls\" title (\"Polls / Polls\").\n\nimport type { NavBreadcrumb, NavItem } from '../native/index'\n\nexport function deriveBreadcrumbs(\n items: NavItem[],\n pathname: string,\n opts?: { search?: string; origin?: string },\n): NavBreadcrumb[] {\n const search = opts?.search ?? (typeof window !== 'undefined' ? window.location.search : '')\n const origin = opts?.origin ?? (typeof window !== 'undefined' ? window.location.origin : '')\n\n // Same-origin comparison: cross-app hrefs are absolute, local ones relative.\n const matches = (href?: string): boolean => {\n if (!href) return false\n let path = href\n let query = ''\n if (/^https?:\\/\\//i.test(href)) {\n try {\n const u = new URL(href)\n if (origin && u.origin !== origin) return false\n path = u.pathname\n query = u.search\n } catch {\n return false\n }\n } else {\n const qi = href.indexOf('?')\n if (qi >= 0) {\n path = href.slice(0, qi) || '/'\n query = href.slice(qi)\n }\n }\n if (path !== pathname) return false\n if (!query) return true\n const want = new URLSearchParams(query)\n const have = new URLSearchParams(search)\n for (const [k, v] of want) if (have.get(k) !== v) return false\n return true\n }\n\n // Depth-first walk for the deepest item owning the current route.\n const walk = (nodes: NavItem[], trail: NavItem[]): NavItem[] | null => {\n for (const node of nodes) {\n const here = [...trail, node]\n if (node.children?.length) {\n const deeper = walk(node.children, here)\n if (deeper) return deeper\n }\n if (matches(node.href)) return here\n }\n return null\n }\n\n const trail = walk(items, [])\n // Always anchored at Dashboard; the last crumb is the current location and\n // carries no href (the shell marks it aria-current=\"page\").\n const dashboard = items.find((i) => i.label === 'Dashboard')\n const crumbs: NavBreadcrumb[] = [{ label: 'Dashboard', href: dashboard?.href }]\n if (!trail) return crumbs\n for (const node of trail) {\n if (node.label === 'Dashboard') continue\n crumbs.push({ label: node.label, href: node.href })\n }\n const last = crumbs[crumbs.length - 1]\n if (last) delete (last as { href?: string }).href\n return crumbs\n}\n","// Identity resolution — pure logic, shared by the chrome hook.\n//\n// The chrome API (VRM-core profile) is the AUTHORITY for a user's display name;\n// what an app knows from its own session token is a pre-hydration fallback. An\n// email is never a name: forms passed `user.name ?? user.email`, so a user with\n// no name set had their email rendered as their name in the top bar and the\n// shell's \"add your name\" affordance never fired (HITL 2026-07-23).\n\nexport type ResolvedIdentity = {\n userName: string | null\n email: string | null\n avatarUrl: string | null\n loaded: boolean\n}\n\n/** True when a \"name\" is really just an email address. */\nexport function looksLikeEmail(value: string | null | undefined): boolean {\n return typeof value === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value.trim())\n}\n\nexport function resolveIdentity(input: {\n apiName?: string | null\n fallbackName?: string | null\n email?: string | null\n avatarUrl?: string | null\n loaded?: boolean\n}): ResolvedIdentity {\n const apiName = looksLikeEmail(input.apiName) ? null : input.apiName ?? null\n const fallbackName = looksLikeEmail(input.fallbackName) ? null : input.fallbackName ?? null\n return {\n userName: apiName ?? fallbackName,\n email: input.email ?? null,\n avatarUrl: input.avatarUrl ?? null,\n loaded: input.loaded ?? false,\n }\n}\n","// ---------------------------------------------------------------------------\n// PolitogyAppFrame — ONE component that renders the entire app frame.\n//\n// Why this exists (HITL directive 2026-07-23): shipping the same npm package at\n// the same version is necessary but NOT sufficient. The package exposed\n// PRIMITIVES (NavAppShell + a dozen hooks) and every app hand-assembled them in\n// its own ~150-line Shell.tsx. Identical inputs were never guaranteed, so the\n// outputs diverged — measured on production: forms rendered its user's EMAIL as\n// their name, polls' breadcrumb read \"Polls / Polls\", forms and surveys pinned\n// the mode pill to a hardcoded \"relationship\", and each app declared its own\n// chrome CSS variables (surveys' accent was #C8102E where everyone else was\n// #E63946).\n//\n// The fix is to stop exporting assembly as an app responsibility. An app now\n// renders:\n//\n// <PolitogyAppFrame app=\"forms\" pathname={pathname} linkComponent={Link}\n// session={{ userId, email, role, accountName, accountId }}\n// onSignOut={signOut}>\n// {children}\n// </PolitogyAppFrame>\n//\n// and passes NOTHING about the chrome: no nav, no breadcrumbs, no title, no\n// mode, no user menu, no footer, no theme. Those are derived here, identically,\n// for every app. Anything an app cannot pass is something it cannot drift on.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport { useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react'\n\nimport { broadcastLogout, watchLogout } from '../logout-signal'\nimport {\n NavAppShell,\n standardPolitogyNav,\n type NavLinkComponent,\n} from '../native/index'\nimport {\n ChromeTopBarActions,\n PolitogyAppChrome,\n standardNavIcons,\n useChromeFooter,\n useChromeIdentity,\n useChromeMode,\n useChromeState,\n useChromeUserMenu,\n type ChromeDevNotesConfig,\n type ControlledOrgState,\n} from './index'\nimport type {\n NavBreadcrumb,\n NavChromeFetcher,\n NavItem,\n NavMode,\n NavUserMenu,\n} from '../native/index'\nimport type { StandardNavMode } from '../native/standardNav'\nimport { deriveBreadcrumbs } from '../lib/breadcrumbs'\n\n/**\n * The core app — the one that OWNS the canonical nav tree and routes in-app.\n * `PolitogyFrameData.core` is honored for this key and no other.\n */\nconst CORE_APP: PolitogyAppKey = 'vrm'\n\n/**\n * Template A convention: every feature app exposes GET /auth/signout, which\n * clears its session cookie and 302s to the IdP's end_session endpoint. Apps\n * that route sign-out elsewhere (core: /api/auth/logout) pass `signOutUrl`.\n */\nconst DEFAULT_SIGN_OUT_URL = '/auth/signout'\n\n/** Every app that renders the shared chrome. Keyed by its subdomain. */\nexport type PolitogyAppKey =\n | 'vrm'\n | 'contacts'\n | 'forms'\n | 'surveys'\n | 'polls'\n | 'petitions'\n // Campaign Mode standalone apps.\n | 'a2p'\n | 'phonebank'\n | 'canvass'\n | 'signs'\n\n/**\n * The operating Mode an app is scoped to, if any. It drives two things,\n * BOTH derived here from app identity so no app passes chrome config:\n * 1. the standardPolitogyNav tree the sidebar renders, and\n * 2. the static Mode pill the app shows (a campaign app shows a Campaign\n * pill, never Relationship).\n * An UNSCOPED app (no `mode`) keeps its prior behavior exactly: the\n * Relationship tree + an entitlement-driven Mode pill.\n */\nexport type PolitogyAppMode = StandardNavMode\n\n/**\n * The per-app registry — the ONLY place app identity lives. A new app adds a\n * row here and inherits every chrome behavior; it never re-derives one.\n */\nexport const POLITOGY_APPS: Record<\n PolitogyAppKey,\n { title: string; origin: string; storagePrefix: string; mode?: PolitogyAppMode }\n> = {\n vrm: { title: 'Politogy VRM', origin: 'https://app.politogyvrm.com', storagePrefix: 'vrm' },\n contacts: { title: 'Contacts', origin: 'https://contacts.politogyvrm.com', storagePrefix: 'contacts' },\n forms: { title: 'Forms', origin: 'https://forms.politogyvrm.com', storagePrefix: 'forms' },\n surveys: { title: 'Surveys', origin: 'https://surveys.politogyvrm.com', storagePrefix: 'surveys' },\n polls: { title: 'Polls', origin: 'https://polls.politogyvrm.com', storagePrefix: 'polls' },\n petitions: {\n title: 'Petitions',\n origin: 'https://petition-mode.politogyvrm.com',\n storagePrefix: 'petitions',\n mode: 'petition',\n },\n // Campaign Mode apps — each scoped to Campaign, so they render the Campaign\n // tree and a static Campaign pill. Not yet deployed; hidden from the Campaign\n // nav by CAMPAIGN_FEATURE_AVAILABILITY until their launch flips the flag.\n a2p: { title: 'A2P Messaging', origin: 'https://a2p.politogyvrm.com', storagePrefix: 'a2p', mode: 'campaign' },\n phonebank: { title: 'Phone Banking', origin: 'https://phonebank.politogyvrm.com', storagePrefix: 'phonebank', mode: 'campaign' },\n canvass: { title: 'Field Canvassing', origin: 'https://canvass.politogyvrm.com', storagePrefix: 'canvass', mode: 'campaign' },\n signs: { title: 'Sign Mapping', origin: 'https://signs.politogyvrm.com', storagePrefix: 'signs', mode: 'campaign' },\n}\n\nexport type PolitogyAppSession = {\n /** Stable IdP subject (`sub`). */\n userId: string\n email: string | null\n /** The app's own role string — feeds DevNotes admin gating. */\n role: string\n /** The app's own workspace label, used only until core orgs resolve. */\n accountName: string\n accountId: string\n /**\n * Pre-hydration display name ONLY. The chrome API is the authority; an email\n * passed here is discarded rather than rendered as a name.\n */\n userName?: string | null\n}\n\n/**\n * Optional DATA plumbing for apps whose session/auth differs — never styling.\n *\n * Everything here is about WHERE data comes from (which fetcher, which org\n * state, which routes an app owns), not what the chrome looks like. Adding a\n * visual option to this type would re-open the drift this component exists to\n * close.\n */\nexport type PolitogyFrameData = {\n /** Chrome-API fetcher override for apps that authenticate client-side. */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for those same direct-auth apps. */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state for apps that own active-org + switching. */\n org?: ControlledOrgState\n /** App-owned children for a standard nav entry this app owns (Contacts). */\n contactsItem?: { href?: string; children?: NavItem[] }\n /** Avatar fallback when the profile has no uploaded photo (memoji, default). */\n fallbackAvatarUrl?: string | null\n /**\n * CORE-ONLY frame inputs. **Honored only when `app` is the core app (`vrm`)\n * — silently ignored for every other app**, which is what keeps the\n * no-drift guarantee intact: a feature app cannot re-specify a chrome\n * element even by passing this, so the props are inert for it.\n *\n * Core is genuinely different, and pretending otherwise would have meant\n * REMOVING shipped behavior from app.politogyvrm.com to satisfy packaging —\n * which Reference/UI-Platform forbids (\"migration may not drop a shipped\n * element … extend the shell first, then migrate\"). Specifically, core:\n * - owns the canonical nav tree AND decorates it with live entitlement\n * state a static tree cannot express (10DLC gating, contact/group\n * counts, org-lock),\n * - routes in-app (react-router), so its rows must be app-relative paths,\n * not the absolute cross-app URLs `standardPolitogyNav` emits,\n * - publishes per-page title/breadcrumbs from 66 page components,\n * - switches Mode client-side rather than by full-page navigation,\n * - owns real linked-account switching (with its own modals).\n *\n * Everything here is still DATA or app-owned STATE. There is no styling\n * input, no ordering input, and no way to change the top-bar action cluster\n * — those stay identical for every app, core included.\n */\n core?: PolitogyCoreFrame\n}\n\n/** See `PolitogyFrameData.core` — core app (`vrm`) only. */\nexport type PolitogyCoreFrame = {\n /** The canonical nav tree, decorated with core's live entitlement state. */\n nav?: NavItem[]\n /** Per-page identity published by core's own pages. */\n page?: { title?: string; breadcrumbs?: NavBreadcrumb[]; actions?: ReactNode }\n /** Client-side Mode switching (core navigates in-app, not cross-origin). */\n mode?: NavMode\n /** Real linked-account switching + core's theme persistence. */\n userMenu?: NavUserMenu\n /** Mode-specific brand wordmark (core swaps it per operating Mode). */\n brandLogoSrc?: string\n /** Core wraps every page in PageContainer, which emits its own <main>. */\n contentWrapper?: 'padded' | 'bare'\n}\n\nexport type PolitogyBuildMeta = {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}\n\nexport function PolitogyAppFrame({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n signOutUrl = DEFAULT_SIGN_OUT_URL,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n /** Current path from the app's router (Next: usePathname()). */\n pathname: string\n /** Current query string, when the app routes by query (forms' ?tab=). */\n search?: string\n /** The app's routing primitive (Next: <Link>). */\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n /**\n * The app's sign-out ROUTE — a GET endpoint that clears the session cookie\n * and redirects to RP-initiated logout at the IdP. Defaults to the Template A\n * convention; core overrides it. This, not `onSignOut`, is what actually\n * signs the user out (see handleSignOut).\n */\n signOutUrl?: string\n buildMeta?: PolitogyBuildMeta\n /** Optional DATA plumbing (never styling) — see PolitogyFrameData. */\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n return (\n <PolitogyAppChrome\n storagePrefix={meta.storagePrefix}\n fetchJson={data?.fetchJson}\n devNotes={data?.devNotes}\n org={data?.org}\n identity={{\n userId: session.userId,\n userName: session.userName ?? null,\n email: session.email,\n role: session.role,\n }}\n >\n <FrameBody\n app={app}\n pathname={pathname}\n search={search}\n linkComponent={linkComponent}\n session={session}\n onSignOut={onSignOut}\n signOutUrl={signOutUrl}\n buildMeta={buildMeta}\n data={data}\n >\n {children}\n </FrameBody>\n </PolitogyAppChrome>\n )\n}\n\nfunction FrameBody({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n signOutUrl,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n pathname: string\n search?: string\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n signOutUrl: string\n buildMeta?: PolitogyBuildMeta\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n const chrome = useChromeState()\n\n // Sign-out is a PLATFORM event, not an app event. Each app holds its own\n // host-only session cookie with a long sliding window, so without this a user\n // who signed out in one tab stayed fully signed in in every other open tab\n // and on every other subdomain. Announce the sign-out to all of them, then\n // run the app's own sign-out.\n //\n // Every app reaches sign-out through this frame, so wiring it here covers the\n // whole estate without an app having to opt in — the same guarantee the rest\n // of the chrome relies on.\n const signOutRef = useRef(onSignOut)\n signOutRef.current = onSignOut\n // This tab started the sign-out, so its own watcher must not also fire and\n // run the app's sign-out a second time concurrently.\n const signingOutRef = useRef(false)\n\n const handleSignOut = useCallback(() => {\n signingOutRef.current = true\n broadcastLogout()\n // Navigate to the app's sign-out ROUTE. Apps also pass an onSignOut\n // handler, but it cannot be relied on: they wire it as\n // `onSignOut={() => { void signOutAction(); }}`, and a server action\n // invoked outside a transition can never have its redirect() applied by\n // the client. Measured against production 2026-07-25: clicking Sign out\n // left the user signed in, and an app they had never opened authenticated\n // with no credentials — the IdP session was still alive because the\n // browser never reached its end-session endpoint.\n //\n // A plain navigation to a GET route cannot fail that way: the route drops\n // the session cookie and 302s to RP-initiated logout at the IdP.\n signOutRef.current()\n window.location.assign(signOutUrl)\n }, [signOutUrl])\n\n useEffect(\n () =>\n // A tab that learns of a sign-out elsewhere ends its own session for\n // real, by navigating to the same sign-out route.\n //\n // Measured against production before this landed: calling the app's\n // handler did nothing at all. Apps wire it as\n // `onSignOut={() => { void signOutAction(); }}` — a server action invoked\n // outside a transition, so the `redirect()` inside it can never be\n // applied by the client and the tab just sits there. Building cross-tab\n // logout on that path made the whole feature a no-op.\n //\n // A hard navigation cannot fail that way, and it hands the decision to\n // the authority rather than guessing: the app's middleware sees a session\n // whose auth_time predates `politogy_logout_at`, treats it as revoked,\n // and redirects to Politogy ID. Server-side truth decides; the client\n // only forces the request. No re-broadcast — the signal that woke us is\n // already on record for every tab.\n watchLogout(() => {\n if (signingOutRef.current) return\n window.location.assign(signOutUrl)\n }),\n [signOutUrl],\n )\n // Core-only inputs are read through this ONE gate. For every other app it is\n // undefined no matter what the app passed, so the \"an app cannot re-specify a\n // chrome element\" guarantee holds by construction, not by convention.\n const core = app === CORE_APP ? data?.core : undefined\n\n // Identity: chrome API wins, app claims are a fallback, an email is never a\n // name (that is what forms was rendering in the top right).\n const identity = useChromeIdentity({ userName: session.userName, email: session.email })\n const derivedUserMenu = useChromeUserMenu({\n userId: session.userId,\n userName: identity.userName,\n email: identity.email,\n })\n const userMenu = core?.userMenu ?? derivedUserMenu\n const footer = useChromeFooter(buildMeta ?? {})\n // A mode-scoped app (Campaign apps, Petitions) pins its pill to its own mode;\n // an unscoped app stays entitlement + user-selection driven. Either way no\n // app passes a mode — it is read from the registry (app identity).\n const appMode = meta.mode\n const derivedMode = useChromeMode(appMode ? { pinnedModeId: appMode } : undefined)\n // Core switches Mode client-side; every other app jumps to the mode's home.\n const mode = core?.mode ?? derivedMode\n\n const coreOrgs = chrome?.orgs ?? []\n const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null\n const navDisplay = chrome?.navDisplay ?? 'both'\n\n // The nav tree follows the app's OWN mode (registry), not the live pill\n // selection — so it is deterministic from app identity. Unscoped apps render\n // Relationship exactly as before. Cross-app links in the Campaign/Petition\n // trees carry the active org the way the switcher/account menu do.\n const navMode: StandardNavMode = appMode ?? 'relationship'\n const activeOrgId = chrome?.orgId ?? null\n const derivedNavItems = useMemo(\n () =>\n standardPolitogyNav({\n coreBaseUrl: POLITOGY_APPS.vrm.origin,\n icons: standardNavIcons,\n // An app that OWNS a standard nav entry (Contacts) nests its own routes\n // under it. This is routing data, not chrome configuration.\n contactsItem: data?.contactsItem,\n mode: navMode,\n activeOrgId,\n }),\n [data?.contactsItem, navMode, activeOrgId],\n )\n // Core supplies the canonical tree already decorated with its live gating\n // state (and with app-relative hrefs so its router handles them in-app).\n const navItems = core?.nav ?? derivedNavItems\n\n // Dashboard › Sub › Item, derived from the same tree the sidebar renders.\n // Core publishes its trail from the page itself, because a core page's\n // location is finer-grained than its nav row (e.g. a contact detail view).\n const derivedBreadcrumbs = useMemo(\n () => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),\n [navItems, pathname, search, meta.origin],\n )\n const breadcrumbs = core ? core.page?.breadcrumbs : derivedBreadcrumbs\n\n return (\n <NavAppShell\n sidebar={{\n pathname,\n brand: {\n logoSrc: core?.brandLogoSrc ?? '/brand/logos/relationship-mode-logo.png',\n logoAlt: 'Politogy',\n badgeLabel: 'P',\n title: 'Politogy',\n },\n nav: {\n items:\n navDisplay === 'tabs'\n ? navItems.map(({ children: _sub, ...item }) => item)\n : navItems,\n },\n // The app's own workspace is NOT the organization — it is a per-app\n // bootstrap account with a different id in every app. Showing it while\n // the chrome API resolves flashed the wrong org on every load (\"not\n // connected to the account\"). Shimmer while in flight; only fall back\n // to the app label once the chrome lookup has actually failed.\n org: activeCoreOrg\n ? {\n name: activeCoreOrg.name,\n id: activeCoreOrg.id,\n logoUrl: activeCoreOrg.logoUrl,\n brandColor: activeCoreOrg.brandColor,\n organizations: coreOrgs,\n onSwitch: chrome?.setOrgId,\n }\n : chrome?.orgsLoading\n ? { name: null, id: null, loading: true }\n : { name: session.accountName, id: session.accountId },\n mode,\n footer,\n linkComponent,\n }}\n betaBanner={{}}\n contentWrapper={core?.contentWrapper}\n topBar={{\n // Core's pages publish their own identity; every other app is one\n // product surface and takes its registry title.\n title: core?.page?.title ?? meta.title,\n breadcrumbs,\n user: {\n name: identity.userName,\n email: identity.email,\n // The chrome API's avatar wins; the app may supply a fallback chain\n // (e.g. a stored memoji) for users with no uploaded photo.\n avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,\n role: session.role,\n },\n userMenu,\n onSignOut: handleSignOut,\n // The action cluster is IDENTICAL everywhere — core included. Page\n // actions sit BEFORE it, never inside it, so the Alerts bell keeps its\n // required position: farthest right, immediately before My Account.\n actions: (\n <>\n {core?.page?.actions}\n <ChromeTopBarActions />\n </>\n ),\n }}\n >\n {children}\n </NavAppShell>\n )\n}\n","// ---------------------------------------------------------------------------\n// App chrome wiring — the ONE shared implementation of the per-app glue that\n// previously lived as a copied ChromeWidgets.tsx in every Next.js feature app\n// (forms / surveys / polls / contacts). Copies drifted (org dropdown, badges);\n// this module makes that class of drift impossible: apps configure, never\n// re-implement (Reference/UI-Platform: consume via npm, never vendored).\n//\n// Provides:\n// <PolitogyAppChrome> — NavChromeProvider (same-origin proxy fetcher) +\n// chrome state (active core org, nav display) +\n// DevNotes provider, in one wrapper.\n// <ChromeTopBarActions> — the canonical top-bar cluster, in canonical order\n// (Fuel · Nav display · DevNotes · Support · Alerts\n// LAST, immediately left of My Account).\n// useChromeState / useChromeUserMenu / useIsSuperAdmin / useChromeFooter.\n//\n// The app still owns: its session (the /api/chrome proxy), page frames, build\n// metadata (env/commit/version), tab strips, and its own icons.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react'\nimport {\n NavChromeProvider,\n FuelAction,\n NotificationsAction,\n SupportAction,\n NavDisplayAction,\n TopBarWidgetSlot,\n useChromeOrgs,\n useThemePreference,\n type NavChromeFetcher,\n type NavDisplayMode,\n type NavOrgOption,\n type NavUserMenu,\n type NavFooter,\n type NavFooterLink,\n} from '../native/index'\nimport {\n DevNotesProvider,\n DevNotesMenu,\n DevNotesOverlay,\n DevNotesTaskListModal,\n createDevNotesClient,\n type DevNotesConfig,\n} from '@the-portland-company/devnotes'\nimport {\n FiActivity,\n FiBarChart2,\n FiBriefcase,\n FiClock,\n FiCreditCard,\n FiDollarSign,\n FiEdit3,\n FiFileText,\n FiFlag,\n FiGrid,\n FiHome,\n FiLayout,\n FiLifeBuoy,\n FiList,\n FiMail,\n FiMap,\n FiMapPin,\n FiMessageSquare,\n FiPhone,\n FiSend,\n FiSettings,\n FiShare2,\n FiTarget,\n FiUpload,\n FiUser,\n FiUsers,\n} from 'react-icons/fi'\nimport { useChromeMe, useNavChromeFetcher } from '../native/index'\nimport {\n ACTIVE_ORG_QUERY_PARAM,\n isOrgId,\n resolveActiveOrg,\n writeActiveOrgCookie,\n} from '../active-org'\nimport type { StandardNavIconKey } from '../native/standardNav'\nimport type { NavBreadcrumb, NavIcon, NavItem, NavMode } from '../native/index'\n\n/**\n * The canonical icon map for standardPolitogyNav — ONE definition so every\n * app renders identical glyphs (including the Organization Settings branch:\n * briefcase / settings / creditCard / dollarSign / share).\n */\nexport const standardNavIcons: Record<StandardNavIconKey, NavIcon> = {\n home: FiHome,\n users: FiUsers,\n mail: FiMail,\n messageSquare: FiMessageSquare,\n layout: FiLayout,\n activity: FiActivity,\n lifeBuoy: FiLifeBuoy,\n grid: FiGrid,\n list: FiList,\n upload: FiUpload,\n edit: FiEdit3,\n fileText: FiFileText,\n send: FiSend,\n clock: FiClock,\n user: FiUser,\n barChart: FiBarChart2,\n briefcase: FiBriefcase,\n settings: FiSettings,\n creditCard: FiCreditCard,\n dollarSign: FiDollarSign,\n share: FiShare2,\n // Campaign / Petition mode slots.\n phone: FiPhone,\n map: FiMap,\n mapPin: FiMapPin,\n target: FiTarget,\n flag: FiFlag,\n}\n\nconst ORG_STORAGE_KEY = 'vrm.activeOrg'\n// The user's mode choice, shared by every app on the origin's localStorage.\nconst MODE_STORAGE_KEY = 'vrm.activeMode'\nconst NAV_DISPLAY_KEY = 'vrm.navDisplay'\nconst CORE_BASE_URL = 'https://app.politogyvrm.com'\n\nexport type ChromeIdentity = {\n userId: string\n userName: string | null\n email: string | null\n /** The APP's own role string (any casing) — feeds DevNotes admin gating. */\n role: string\n}\n\ntype ChromeState = {\n orgId: string | null\n setOrgId: (id: string) => void\n orgs: (NavOrgOption & { rawRole?: string | null })[]\n /**\n * The org list is still in flight. Consumers must render a skeleton rather\n * than fall back to the app's own workspace label, which is a DIFFERENT\n * account and flashes as the wrong organization.\n */\n orgsLoading: boolean\n navDisplay: NavDisplayMode\n setNavDisplay: (m: NavDisplayMode) => void\n}\n\nconst ChromeStateContext = createContext<ChromeState | null>(null)\n\nexport function useChromeState(): ChromeState | null {\n return useContext(ChromeStateContext)\n}\n\nexport type ControlledOrgState = {\n orgId: string | null\n onSwitch: (id: string) => void\n /** Override the org list (e.g. the app's own session-scoped list). */\n orgs?: (NavOrgOption & { rawRole?: string | null })[]\n}\n\nfunction ChromeStateProvider({\n controlledOrg,\n children,\n}: {\n controlledOrg?: ControlledOrgState\n children: ReactNode\n}) {\n const { orgs, loading: orgsFetching, error: orgsError } = useChromeOrgs()\n const [orgId, setOrgIdState] = useState<string | null>(null)\n const [navDisplay, setNavDisplayState] = useState<NavDisplayMode>('both')\n\n // Boot: restore persisted choices. The cross-app active org (URL `?org=` or\n // the `.politogyvrm.com` cookie) wins over host-only localStorage so every\n // app + subdomain resolves the SAME org instead of each defaulting to its own\n // earliest-joined org.\n useEffect(() => {\n try {\n const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY)\n if (storedOrg) setOrgIdState(storedOrg)\n const storedNav = localStorage.getItem(NAV_DISPLAY_KEY)\n if (storedNav === 'menu' || storedNav === 'tabs' || storedNav === 'both') {\n setNavDisplayState(storedNav)\n }\n } catch {\n /* storage unavailable */\n }\n }, [])\n\n // Once orgs resolve: validate the stored org, default to the first.\n useEffect(() => {\n if (!orgs || orgs.length === 0) return\n setOrgIdState((prev) => (prev && orgs.some((o) => o.id === prev) ? prev : orgs[0].id))\n }, [orgs])\n\n const setOrgId = useCallback((id: string) => {\n setOrgIdState(id)\n try {\n localStorage.setItem(ORG_STORAGE_KEY, id)\n } catch {\n /* best effort */\n }\n // Remember the choice cross-subdomain so the next app opens on the same org.\n writeActiveOrgCookie(id)\n }, [])\n\n const setNavDisplay = useCallback((m: NavDisplayMode) => {\n setNavDisplayState(m)\n try {\n localStorage.setItem(NAV_DISPLAY_KEY, m)\n } catch {\n /* best effort */\n }\n }, [])\n\n const value = useMemo<ChromeState>(() => {\n // CONTROLLED mode (Contacts): the APP owns the active org + switching —\n // its switch does a full session re-scope. The shared widgets read the\n // app's state so there is exactly one org truth (unification directive).\n if (controlledOrg) {\n return {\n orgId: controlledOrg.orgId,\n setOrgId: controlledOrg.onSwitch,\n orgs:\n controlledOrg.orgs ??\n (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n orgsLoading: false,\n navDisplay,\n setNavDisplay,\n }\n }\n return {\n orgId,\n setOrgId,\n orgs: (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n // Loading until the fetch settles. On error we stop loading so the frame\n // falls back to the app label rather than shimmering forever.\n orgsLoading: orgsFetching && !orgsError && !orgs,\n navDisplay,\n setNavDisplay,\n }\n }, [controlledOrg, orgId, setOrgId, orgs, orgsFetching, orgsError, navDisplay, setNavDisplay])\n\n // Keep the shared cookie + address-bar `?org=` in sync with the ACTIVE org —\n // whichever path owns it, controlled (Contacts) or uncontrolled (feature\n // apps) — so every app + tab resolves the same org and every URL is\n // bookmarkable/shareable with its org context. Idempotent; no-op on server.\n const activeOrgId = value.orgId\n useEffect(() => {\n if (!isOrgId(activeOrgId)) return\n writeActiveOrgCookie(activeOrgId as string)\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get(ACTIVE_ORG_QUERY_PARAM) !== activeOrgId) {\n url.searchParams.set(ACTIVE_ORG_QUERY_PARAM, activeOrgId as string)\n window.history.replaceState(window.history.state, '', url.toString())\n }\n } catch {\n /* SSR / no window */\n }\n }, [activeOrgId])\n\n return <ChromeStateContext.Provider value={value}>{children}</ChromeStateContext.Provider>\n}\n\nexport type ChromeDevNotesConfig = {\n /** Override the DevNotes API base (default: `${proxyBasePath}/devnotes`). */\n basePath?: string\n /** Client-side token supplier (default: empty — same-origin proxy injects). */\n getAuthToken?: () => Promise<string> | string\n /** Custom fetch (e.g. credentials:'include' against a cross-origin API). */\n fetch?: typeof globalThis.fetch\n /**\n * The app already mounts its own `<DevNotesProvider>` ABOVE this chrome, so\n * the chrome must not mount a second one (VRM core, whose provider carries\n * capabilities the generic client has no equivalent for — user stories, its\n * own role gating). The working SURFACE still comes from here, so the\n * launcher, the overlay and the task modal behave identically everywhere;\n * only the data adapter is the app's.\n */\n providedByApp?: boolean\n}\n\nfunction AppDevNotes({\n storagePrefix,\n proxyBasePath,\n devNotes,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath: string\n devNotes?: ChromeDevNotesConfig\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const adapter = useMemo(\n () =>\n createDevNotesClient({\n basePath: devNotes?.basePath ?? `${proxyBasePath}/devnotes`,\n // Default: the same-origin proxy attaches the session's Bearer.\n getAuthToken: devNotes?.getAuthToken ?? (async () => ''),\n ...(devNotes?.fetch ? { fetch: devNotes.fetch } : {}),\n }),\n [proxyBasePath, devNotes?.basePath, devNotes?.getAuthToken, devNotes?.fetch],\n )\n\n const user = useMemo(\n () => ({\n id: identity.userId,\n email: identity.email ?? '',\n fullName: identity.userName ?? undefined,\n }),\n [identity.userId, identity.email, identity.userName],\n )\n\n const config = useMemo<DevNotesConfig>(\n () => ({\n storagePrefix,\n role: identity.role.toLowerCase() === 'admin' ? 'admin' : 'reporter',\n disableAi: true,\n getPagePath: () => window.location.pathname,\n }),\n [storagePrefix, identity.role],\n )\n\n // The app brought its own provider — mount only the surface, so a second\n // DevNotesProvider never shadows the app's adapter.\n if (devNotes?.providedByApp) return <DevNotesSurface>{children}</DevNotesSurface>\n\n return (\n <DevNotesProvider adapter={adapter} user={user} config={config}>\n <DevNotesSurface>{children}</DevNotesSurface>\n </DevNotesProvider>\n )\n}\n\n// \"View All Tasks\" is triggered from the menu in the top bar but the modal has\n// to render at the chrome ROOT, or it would be trapped inside the top bar's\n// stacking/blur context. The menu publishes the request through this context.\nconst DevNotesTasksContext = createContext<{ ready: boolean; openTasks: () => void }>({\n ready: false,\n openTasks: () => {},\n})\n\n/**\n * The DevNotes working surface, mounted once at the chrome root.\n *\n * `<DevNotesMenu>` only TOGGLES creation mode — `<DevNotesOverlay>` is what\n * renders the click-to-place capture layer, the report dots and the report\n * form. The shell shipped the provider and the menu but never the overlay, so\n * on every app that used this chrome (forms / surveys / polls) \"Create Task\"\n * closed the menu and did nothing at all: the mode flipped on with nothing\n * listening (HITL bug report 2026-07-23). Contacts didn't hit it only because\n * it still had its own hand-rolled surface.\n */\nfunction DevNotesSurface({ children }: { children: ReactNode }) {\n const [tasksOpen, setTasksOpen] = useState(false)\n const openTasks = useCallback(() => setTasksOpen(true), [])\n const tasksCtx = useMemo(() => ({ ready: true, openTasks }), [openTasks])\n\n return (\n <DevNotesTasksContext.Provider value={tasksCtx}>\n {children}\n {/* Mounted only while open: the modal loads data on mount regardless of\n its `open` prop, so keeping it unmounted avoids fetching every user's\n tasks before they ask for them. */}\n {tasksOpen ? <DevNotesTaskListModal open onClose={() => setTasksOpen(false)} /> : null}\n <DevNotesOverlay />\n </DevNotesTasksContext.Provider>\n )\n}\n\n/**\n * The app-chrome root. Wrap the authenticated frame once:\n *\n * <PolitogyAppChrome storagePrefix=\"forms\" identity={{userId, userName, email, role}}>\n * <ShellFrame …/>\n * </PolitogyAppChrome>\n */\nexport function PolitogyAppChrome({\n storagePrefix,\n proxyBasePath = '/api/chrome',\n fetchJson: fetchJsonOverride,\n devNotes,\n org,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath?: string\n /**\n * Override the same-origin proxy fetcher for apps that talk to the chrome\n * API directly with their own auth (Contacts sends a client-side Bearer).\n * Paths stay relative ('orgs', 'me', …) — the override owns base + auth.\n */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for direct-auth apps (Contacts). */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state — the app owns active-org + switching (Contacts). */\n org?: ControlledOrgState\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const proxyFetchJson = useMemo<NavChromeFetcher>(\n () =>\n async (path, init) => {\n const res = await fetch(`${proxyBasePath}/${path}`, {\n method: init?.method ?? 'GET',\n headers: init?.headers,\n body: init?.body,\n })\n if (!res.ok) throw new Error(`chrome ${path}: ${res.status}`)\n if (res.status === 204) return null\n const text = await res.text()\n return text ? JSON.parse(text) : null\n },\n [proxyBasePath],\n )\n const fetchJson = fetchJsonOverride ?? proxyFetchJson\n\n return (\n <NavChromeProvider fetchJson={fetchJson}>\n <ChromeStateProvider controlledOrg={org}>\n <AppDevNotes\n storagePrefix={storagePrefix}\n proxyBasePath={proxyBasePath}\n devNotes={devNotes}\n identity={identity}\n >\n {children}\n </AppDevNotes>\n </ChromeStateProvider>\n </NavChromeProvider>\n )\n}\n\n/** The standard top-bar action cluster. The Alerts bell renders LAST so it\n * sits farthest right, immediately left of the My Account badge. */\nexport function ChromeTopBarActions() {\n const state = useChromeState()\n if (!state) return null\n return (\n <>\n <FuelAction orgId={state.orgId} />\n <NavDisplayAction value={state.navDisplay} onChange={state.setNavDisplay} />\n <TopBarWidgetSlot\n title=\"DevNotes\"\n description=\"Capture bugs and notes, filed straight to Forge.\"\n >\n <ChromeDevNotesMenu />\n </TopBarWidgetSlot>\n <SupportAction orgId={state.orgId} />\n <NotificationsAction orgId={state.orgId} />\n </>\n )\n}\n\n/**\n * The DevNotes launcher. Renders nothing until the surface above is mounted, so\n * it never touches DevNotes context before the provider exists, and forwards\n * \"View All Tasks\" to the root-level modal.\n */\nfunction ChromeDevNotesMenu() {\n const { ready, openTasks } = useContext(DevNotesTasksContext)\n if (!ready) return null\n return <DevNotesMenu position=\"top-right\" onViewTasks={openTasks} />\n}\n\n/** True when any core-org membership carries a super-admin tier. */\nexport function useIsSuperAdmin(): boolean {\n const state = useChromeState()\n return (state?.orgs ?? []).some((o) => /super/i.test(o.rawRole ?? ''))\n}\n\nconst FOOTER_LINKS: NavFooterLink[] = [\n { label: 'Privacy', href: `${CORE_BASE_URL}/privacy-policy`, external: true },\n { label: 'Terms', href: `${CORE_BASE_URL}/terms-and-conditions`, external: true },\n {\n label: 'Subscriptions',\n href: 'https://politogyvrm.com/subscription-cancellation-policy',\n external: true,\n },\n]\n\n/**\n * Sidebar footer: legal links for everyone; build metadata (env / commit /\n * version / date / author) ONLY for Super Administrators\n * (Reference/UI-Platform). Pass the app's build-time metadata in.\n */\nexport function useChromeFooter(meta: {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}): NavFooter {\n const isSuperAdmin = useIsSuperAdmin()\n return useMemo<NavFooter>(\n () => (isSuperAdmin ? { links: FOOTER_LINKS, ...meta } : { links: FOOTER_LINKS }),\n [isSuperAdmin, meta.envLabel, meta.version, meta.commit, meta.commitDate, meta.commitAuthor],\n )\n}\n\n// ---------------------------------------------------------------------------\n// Identity — resolved by the PACKAGE, from the chrome API.\n//\n// Apps used to pass a display name straight from their own session claims, and\n// forms passed `user.name ?? user.email` — so a user with no name set had their\n// EMAIL rendered as their name, and the chrome API's real profile name could\n// never win (HITL 2026-07-23). The chrome API is the authority; what the app\n// passes is a pre-hydration fallback only, and an email is never a name.\n// ---------------------------------------------------------------------------\n\nexport function useChromeIdentity(fallback?: {\n userName?: string | null\n email?: string | null\n}): ResolvedIdentity {\n const { me, loaded } = useChromeMe()\n return resolveIdentity({\n apiName: me?.userName,\n fallbackName: fallback?.userName,\n email: fallback?.email,\n avatarUrl: me?.avatarUrl,\n loaded,\n })\n}\n\n// Breadcrumbs live in ../lib/breadcrumbs (pure, dependency-free) and are\n// re-exported here so apps keep a single import surface.\nexport { deriveBreadcrumbs } from '../lib/breadcrumbs'\nexport { looksLikeEmail, resolveIdentity, type ResolvedIdentity } from '../lib/identity'\nimport { resolveIdentity, type ResolvedIdentity } from '../lib/identity'\n\n// ---------------------------------------------------------------------------\n// Breadcrumbs — derived by the PACKAGE from the nav tree + the current path.\n//\n// Every app hand-built its own trail, so they disagreed: forms rendered\n// \"Forms › Dashboard\" while polls rendered \"Polls\" under a \"Polls\" title\n// (\"Polls / Polls\"). The standard is Dashboard › Sub › Item (HITL 2026-07-23),\n// derived from the same nav tree the sidebar renders, so no app can drift.\n// ---------------------------------------------------------------------------\n\n/**\n * The My Account menu config — profile/org links into VRM core, real roles\n * from the user's core organizations, the signed-in account, and the shared\n * Light/System/Dark theme control (auth-derived, cross-app).\n */\nexport function useChromeUserMenu({\n userId,\n userName,\n email,\n}: {\n userId: string\n userName: string | null\n email: string | null\n}): NavUserMenu {\n const state = useChromeState()\n const theme = useThemePreference()\n const activeOrg = state?.orgs.find((o) => o.id === state.orgId) ?? null\n const orgSuffix = activeOrg ? `?org=${activeOrg.id}` : ''\n return {\n // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).\n myProfileHref: `${CORE_BASE_URL}/my-profile`,\n organizationSettingsHref: `${CORE_BASE_URL}/organization-settings${orgSuffix}`,\n roles: (state?.orgs ?? [])\n .filter((o) => o.rawRole || o.role)\n .map((o) => ({ role: (o.rawRole ?? o.role) as string, orgName: o.name })),\n accounts: [{ id: userId, name: userName, email, avatarUrl: null, active: true }],\n addAccountHref: `${CORE_BASE_URL}/my-profile#accounts`,\n theme: { value: theme.value, onChange: theme.setValue },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mode selector — entitlement-driven (HITL 2026-07-23: an org with multiple\n// active modes selects them from the left nav). Entitlements come from the\n// chrome API: GET me with the active org header → modes booleans\n// {relationship, campaign, petition, polling}. Selecting a mode navigates to\n// that mode's VRM-core dashboard carrying the active org.\n// ---------------------------------------------------------------------------\n\nconst MODE_DEFS: {\n id: string\n label: string\n icon: NavIcon\n /** Absolute destination, or a VRM-core path prefixed with CORE_BASE_URL. */\n href: string\n orgQuery: boolean\n}[] = [\n {\n id: 'relationship',\n label: 'Relationship Mode',\n icon: FiUsers,\n href: `${CORE_BASE_URL}/relationship/dashboard`,\n orgQuery: true,\n },\n {\n id: 'campaign',\n label: 'Campaign Mode',\n icon: FiBarChart2,\n href: `${CORE_BASE_URL}/campaign/dashboard`,\n orgQuery: true,\n },\n {\n // Petition Mode lives in its own standalone app on its own subdomain — the\n // petitions repo deploys production to petition-mode.politogyvrm.com.\n id: 'petition',\n label: 'Petition Mode',\n icon: FiFlag,\n href: 'https://petition-mode.politogyvrm.com/',\n orgQuery: true,\n },\n]\n\ntype ModeId = 'relationship' | 'campaign' | 'petition'\n\ntype UseChromeModeArg =\n | ModeId\n | {\n /** Pre-entitlement fallback for first paint (default 'relationship'). */\n fallbackModeId?: ModeId\n /**\n * Registry-pinned mode for a MODE-SCOPED app (e.g. the Campaign apps\n * a2p / phonebank / canvass / signs, from `POLITOGY_APPS[app].mode`). The\n * pill DISPLAYS this mode regardless of the cross-app selection, so a\n * campaign app never shows a Relationship pill. The switcher still appears\n * when the active org is entitled to more than one mode, so the user can\n * jump out to another mode's home.\n */\n pinnedModeId?: ModeId\n }\n\n/**\n * The active mode pill / selector.\n *\n * For an UNSCOPED app the current mode is the USER'S OWN SELECTION (persisted\n * cross-app in localStorage under `vrm.activeMode`), narrowed to what the active\n * org is entitled to — never a literal an app hardcodes. Apps used to call\n * `useChromeMode('relationship')`, which pinned the pill to Relationship even\n * for an org entitled to Campaign or Petition (HITL 2026-07-23); the string\n * argument now only supplies the pre-entitlement fallback for first paint.\n *\n * A MODE-SCOPED app passes `{ pinnedModeId }` so its pill displays that mode\n * (its own mode from the app registry) rather than the cross-app selection —\n * while a multi-mode org still gets the switcher to leave.\n */\nexport function useChromeMode(arg: UseChromeModeArg = {}): NavMode {\n const { fallbackModeId = 'relationship', pinnedModeId } =\n typeof arg === 'string' ? { fallbackModeId: arg, pinnedModeId: undefined } : arg\n const ctx = useContext(ChromeStateContext)\n const nav = useNavChromeFetcher()\n const orgId = ctx?.orgId ?? null\n const [entitled, setEntitled] = useState<Record<string, boolean> | null>(null)\n const [selectedModeId, setSelectedModeId] = useState<string | null>(null)\n\n // Restore the user's last mode choice (shared key across every app).\n useEffect(() => {\n try {\n const stored = localStorage.getItem(MODE_STORAGE_KEY)\n if (stored && MODE_DEFS.some((m) => m.id === stored)) setSelectedModeId(stored)\n } catch {\n /* storage unavailable */\n }\n }, [])\n\n useEffect(() => {\n if (!nav || !orgId) return\n let cancelled = false\n nav('me', { headers: { 'x-contacts-org': orgId } })\n .then((data) => {\n if (cancelled) return\n const modes = (data as { modes?: Record<string, boolean> | null })?.modes ?? null\n setEntitled(modes)\n })\n .catch(() => {\n /* entitlements unavailable — single-mode pill stands */\n })\n return () => {\n cancelled = true\n }\n }, [nav, orgId])\n\n const options = MODE_DEFS.filter((m) => entitled?.[m.id])\n // Resolution order: a registry pin (mode-scoped app) wins outright → then the\n // user's selection (if the org is still entitled to it) → the first entitled\n // mode → the app's fallback. Entitlements are async, so before they land the\n // pin/fallback holds the pill's shape (no layout shift).\n const pinned = pinnedModeId ? MODE_DEFS.find((m) => m.id === pinnedModeId) ?? null : null\n const selected = selectedModeId\n ? options.find((m) => m.id === selectedModeId) ?? null\n : null\n const current =\n pinned ??\n selected ??\n options[0] ??\n MODE_DEFS.find((m) => m.id === fallbackModeId) ??\n MODE_DEFS[0]\n const suffix = orgId ? `?org=${orgId}` : ''\n\n return {\n label: current.label,\n icon: current.icon,\n options:\n options.length > 1\n ? options.map((o) => ({ id: o.id, label: o.label, icon: o.icon }))\n : undefined,\n onSelect:\n options.length > 1\n ? (id: string) => {\n const def = MODE_DEFS.find((m) => m.id === id)\n if (!def) return\n // Remember the choice BEFORE navigating so the destination app —\n // and every app after it — opens in the mode the user picked.\n setSelectedModeId(id)\n try {\n localStorage.setItem(MODE_STORAGE_KEY, id)\n } catch {\n /* storage unavailable */\n }\n const sep = def.href.includes('?') ? '&' : '?'\n window.location.assign(\n def.orgQuery && suffix ? `${def.href}${sep}${suffix.slice(1)}` : def.href,\n )\n }\n : undefined,\n }\n}\n\n// The one-component app frame (see ./frame.tsx). Re-exported here so apps have\n// a single import surface: `@the-portland-company/shell/app-chrome`.\nexport {\n PolitogyAppFrame,\n POLITOGY_APPS,\n type PolitogyAppKey,\n type PolitogyAppMode,\n type PolitogyAppSession,\n type PolitogyBuildMeta,\n type PolitogyFrameData,\n} from './frame'\n"]}
1
+ {"version":3,"sources":["../src/lib/breadcrumbs.ts","../src/lib/identity.ts","../src/app-chrome/frame.tsx","../src/app-chrome/index.tsx"],"names":["trail","useRef","useEffect","useCallback","useMemo","jsx","jsxs","Fragment"],"mappings":"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","file":"app-chrome.js","sourcesContent":["// Breadcrumb derivation — pure, dependency-free logic (no React, no DevNotes),\n// so it is importable from a server module and testable in isolation.\n//\n// The standard is Dashboard > Sub > Item (HITL 2026-07-23), derived from the\n// same nav tree the sidebar renders. Apps previously hand-built their own\n// trails and disagreed: forms rendered \"Forms > Dashboard\" while polls rendered\n// a lone \"Polls\" crumb under a \"Polls\" title (\"Polls / Polls\").\n\nimport type { NavBreadcrumb, NavItem } from '../native/index'\n\nexport function deriveBreadcrumbs(\n items: NavItem[],\n pathname: string,\n opts?: { search?: string; origin?: string },\n): NavBreadcrumb[] {\n const search = opts?.search ?? (typeof window !== 'undefined' ? window.location.search : '')\n const origin = opts?.origin ?? (typeof window !== 'undefined' ? window.location.origin : '')\n\n // Same-origin comparison: cross-app hrefs are absolute, local ones relative.\n const matches = (href?: string): boolean => {\n if (!href) return false\n let path = href\n let query = ''\n if (/^https?:\\/\\//i.test(href)) {\n try {\n const u = new URL(href)\n if (origin && u.origin !== origin) return false\n path = u.pathname\n query = u.search\n } catch {\n return false\n }\n } else {\n const qi = href.indexOf('?')\n if (qi >= 0) {\n path = href.slice(0, qi) || '/'\n query = href.slice(qi)\n }\n }\n if (path !== pathname) return false\n if (!query) return true\n const want = new URLSearchParams(query)\n const have = new URLSearchParams(search)\n for (const [k, v] of want) if (have.get(k) !== v) return false\n return true\n }\n\n // Depth-first walk for the deepest item owning the current route.\n const walk = (nodes: NavItem[], trail: NavItem[]): NavItem[] | null => {\n for (const node of nodes) {\n const here = [...trail, node]\n if (node.children?.length) {\n const deeper = walk(node.children, here)\n if (deeper) return deeper\n }\n if (matches(node.href)) return here\n }\n return null\n }\n\n const trail = walk(items, [])\n // Always anchored at Dashboard; the last crumb is the current location and\n // carries no href (the shell marks it aria-current=\"page\").\n const dashboard = items.find((i) => i.label === 'Dashboard')\n const crumbs: NavBreadcrumb[] = [{ label: 'Dashboard', href: dashboard?.href }]\n if (!trail) return crumbs\n for (const node of trail) {\n if (node.label === 'Dashboard') continue\n crumbs.push({ label: node.label, href: node.href })\n }\n const last = crumbs[crumbs.length - 1]\n if (last) delete (last as { href?: string }).href\n return crumbs\n}\n","// Identity resolution — pure logic, shared by the chrome hook.\n//\n// The chrome API (VRM-core profile) is the AUTHORITY for a user's display name;\n// what an app knows from its own session token is a pre-hydration fallback. An\n// email is never a name: forms passed `user.name ?? user.email`, so a user with\n// no name set had their email rendered as their name in the top bar and the\n// shell's \"add your name\" affordance never fired (HITL 2026-07-23).\n\nexport type ResolvedIdentity = {\n userName: string | null\n email: string | null\n avatarUrl: string | null\n loaded: boolean\n}\n\n/** True when a \"name\" is really just an email address. */\nexport function looksLikeEmail(value: string | null | undefined): boolean {\n return typeof value === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value.trim())\n}\n\nexport function resolveIdentity(input: {\n apiName?: string | null\n fallbackName?: string | null\n email?: string | null\n avatarUrl?: string | null\n loaded?: boolean\n}): ResolvedIdentity {\n const apiName = looksLikeEmail(input.apiName) ? null : input.apiName ?? null\n const fallbackName = looksLikeEmail(input.fallbackName) ? null : input.fallbackName ?? null\n return {\n userName: apiName ?? fallbackName,\n email: input.email ?? null,\n avatarUrl: input.avatarUrl ?? null,\n loaded: input.loaded ?? false,\n }\n}\n","// ---------------------------------------------------------------------------\n// PolitogyAppFrame — ONE component that renders the entire app frame.\n//\n// Why this exists (HITL directive 2026-07-23): shipping the same npm package at\n// the same version is necessary but NOT sufficient. The package exposed\n// PRIMITIVES (NavAppShell + a dozen hooks) and every app hand-assembled them in\n// its own ~150-line Shell.tsx. Identical inputs were never guaranteed, so the\n// outputs diverged — measured on production: forms rendered its user's EMAIL as\n// their name, polls' breadcrumb read \"Polls / Polls\", forms and surveys pinned\n// the mode pill to a hardcoded \"relationship\", and each app declared its own\n// chrome CSS variables (surveys' accent was #C8102E where everyone else was\n// #E63946).\n//\n// The fix is to stop exporting assembly as an app responsibility. An app now\n// renders:\n//\n// <PolitogyAppFrame app=\"forms\" pathname={pathname} linkComponent={Link}\n// session={{ userId, email, role, accountName, accountId }}\n// onSignOut={signOut}>\n// {children}\n// </PolitogyAppFrame>\n//\n// and passes NOTHING about the chrome: no nav, no breadcrumbs, no title, no\n// mode, no user menu, no footer, no theme. Those are derived here, identically,\n// for every app. Anything an app cannot pass is something it cannot drift on.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport { useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react'\n\nimport { broadcastLogout, watchLogout } from '../logout-signal'\nimport {\n NavAppShell,\n standardPolitogyNav,\n type NavLinkComponent,\n} from '../native/index'\nimport {\n ChromeTopBarActions,\n PolitogyAppChrome,\n standardNavIcons,\n useChromeFooter,\n useChromeIdentity,\n useChromeMode,\n useChromeState,\n useChromeUserMenu,\n type ChromeDevNotesConfig,\n type ControlledOrgState,\n} from './index'\nimport type {\n NavBreadcrumb,\n NavChromeFetcher,\n NavItem,\n NavMode,\n NavUserMenu,\n} from '../native/index'\nimport type { StandardNavMode } from '../native/standardNav'\nimport { deriveBreadcrumbs } from '../lib/breadcrumbs'\n\n/**\n * The core app — the one that OWNS the canonical nav tree and routes in-app.\n * `PolitogyFrameData.core` is honored for this key and no other.\n */\nconst CORE_APP: PolitogyAppKey = 'vrm'\n\n/**\n * Template A convention: every feature app exposes GET /auth/signout, which\n * clears its session cookie and 302s to the IdP's end_session endpoint. Apps\n * that route sign-out elsewhere (core: /api/auth/logout) pass `signOutUrl`.\n */\nconst DEFAULT_SIGN_OUT_URL = '/auth/signout'\n\n/** Every app that renders the shared chrome. Keyed by its subdomain. */\nexport type PolitogyAppKey =\n | 'vrm'\n | 'contacts'\n | 'forms'\n | 'surveys'\n | 'polls'\n | 'petitions'\n // Campaign Mode standalone apps.\n | 'a2p'\n | 'phonebank'\n | 'canvass'\n | 'signs'\n\n/**\n * The operating Mode an app is scoped to, if any. It drives two things,\n * BOTH derived here from app identity so no app passes chrome config:\n * 1. the standardPolitogyNav tree the sidebar renders, and\n * 2. the static Mode pill the app shows (a campaign app shows a Campaign\n * pill, never Relationship).\n * An UNSCOPED app (no `mode`) keeps its prior behavior exactly: the\n * Relationship tree + an entitlement-driven Mode pill.\n */\nexport type PolitogyAppMode = StandardNavMode\n\n/**\n * The per-app registry — the ONLY place app identity lives. A new app adds a\n * row here and inherits every chrome behavior; it never re-derives one.\n */\nexport const POLITOGY_APPS: Record<\n PolitogyAppKey,\n { title: string; origin: string; storagePrefix: string; mode?: PolitogyAppMode }\n> = {\n vrm: { title: 'Politogy VRM', origin: 'https://app.politogyvrm.com', storagePrefix: 'vrm' },\n contacts: { title: 'Contacts', origin: 'https://contacts.politogyvrm.com', storagePrefix: 'contacts' },\n forms: { title: 'Forms', origin: 'https://forms.politogyvrm.com', storagePrefix: 'forms' },\n surveys: { title: 'Surveys', origin: 'https://surveys.politogyvrm.com', storagePrefix: 'surveys' },\n polls: { title: 'Polls', origin: 'https://polls.politogyvrm.com', storagePrefix: 'polls' },\n petitions: {\n title: 'Petitions',\n origin: 'https://petition-mode.politogyvrm.com',\n storagePrefix: 'petitions',\n mode: 'petition',\n },\n // Campaign Mode apps — each scoped to Campaign, so they render the Campaign\n // tree and a static Campaign pill. Not yet deployed; hidden from the Campaign\n // nav by CAMPAIGN_FEATURE_AVAILABILITY until their launch flips the flag.\n a2p: { title: 'A2P Messaging', origin: 'https://a2p.politogyvrm.com', storagePrefix: 'a2p', mode: 'campaign' },\n phonebank: { title: 'Phone Banking', origin: 'https://phonebank.politogyvrm.com', storagePrefix: 'phonebank', mode: 'campaign' },\n canvass: { title: 'Field Canvassing', origin: 'https://canvass.politogyvrm.com', storagePrefix: 'canvass', mode: 'campaign' },\n signs: { title: 'Sign Mapping', origin: 'https://signs.politogyvrm.com', storagePrefix: 'signs', mode: 'campaign' },\n}\n\nexport type PolitogyAppSession = {\n /** Stable IdP subject (`sub`). */\n userId: string\n email: string | null\n /** The app's own role string — feeds DevNotes admin gating. */\n role: string\n /** The app's own workspace label, used only until core orgs resolve. */\n accountName: string\n accountId: string\n /**\n * Pre-hydration display name ONLY. The chrome API is the authority; an email\n * passed here is discarded rather than rendered as a name.\n */\n userName?: string | null\n}\n\n/**\n * Optional DATA plumbing for apps whose session/auth differs — never styling.\n *\n * Everything here is about WHERE data comes from (which fetcher, which org\n * state, which routes an app owns), not what the chrome looks like. Adding a\n * visual option to this type would re-open the drift this component exists to\n * close.\n */\nexport type PolitogyFrameData = {\n /** Chrome-API fetcher override for apps that authenticate client-side. */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for those same direct-auth apps. */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state for apps that own active-org + switching. */\n org?: ControlledOrgState\n /** App-owned children for a standard nav entry this app owns (Contacts). */\n contactsItem?: { href?: string; children?: NavItem[] }\n /** Avatar fallback when the profile has no uploaded photo (memoji, default). */\n fallbackAvatarUrl?: string | null\n /**\n * CORE-ONLY frame inputs. **Honored only when `app` is the core app (`vrm`)\n * — silently ignored for every other app**, which is what keeps the\n * no-drift guarantee intact: a feature app cannot re-specify a chrome\n * element even by passing this, so the props are inert for it.\n *\n * Core is genuinely different, and pretending otherwise would have meant\n * REMOVING shipped behavior from app.politogyvrm.com to satisfy packaging —\n * which Reference/UI-Platform forbids (\"migration may not drop a shipped\n * element … extend the shell first, then migrate\"). Specifically, core:\n * - owns the canonical nav tree AND decorates it with live entitlement\n * state a static tree cannot express (10DLC gating, contact/group\n * counts, org-lock),\n * - routes in-app (react-router), so its rows must be app-relative paths,\n * not the absolute cross-app URLs `standardPolitogyNav` emits,\n * - publishes per-page title/breadcrumbs from 66 page components,\n * - switches Mode client-side rather than by full-page navigation,\n * - owns real linked-account switching (with its own modals).\n *\n * Everything here is still DATA or app-owned STATE. There is no styling\n * input, no ordering input, and no way to change the top-bar action cluster\n * — those stay identical for every app, core included.\n */\n core?: PolitogyCoreFrame\n}\n\n/** See `PolitogyFrameData.core` — core app (`vrm`) only. */\nexport type PolitogyCoreFrame = {\n /** The canonical nav tree, decorated with core's live entitlement state. */\n nav?: NavItem[]\n /** Per-page identity published by core's own pages. */\n page?: { title?: string; breadcrumbs?: NavBreadcrumb[]; actions?: ReactNode }\n /** Client-side Mode switching (core navigates in-app, not cross-origin). */\n mode?: NavMode\n /** Real linked-account switching + core's theme persistence. */\n userMenu?: NavUserMenu\n /** Mode-specific brand wordmark (core swaps it per operating Mode). */\n brandLogoSrc?: string\n /** Core wraps every page in PageContainer, which emits its own <main>. */\n contentWrapper?: 'padded' | 'bare'\n}\n\nexport type PolitogyBuildMeta = {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}\n\nexport function PolitogyAppFrame({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n signOutUrl = DEFAULT_SIGN_OUT_URL,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n /** Current path from the app's router (Next: usePathname()). */\n pathname: string\n /** Current query string, when the app routes by query (forms' ?tab=). */\n search?: string\n /** The app's routing primitive (Next: <Link>). */\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n /**\n * The app's sign-out ROUTE — a GET endpoint that clears the session cookie\n * and redirects to RP-initiated logout at the IdP. Defaults to the Template A\n * convention; core overrides it. This, not `onSignOut`, is what actually\n * signs the user out (see handleSignOut).\n */\n signOutUrl?: string\n buildMeta?: PolitogyBuildMeta\n /** Optional DATA plumbing (never styling) — see PolitogyFrameData. */\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n return (\n <PolitogyAppChrome\n storagePrefix={meta.storagePrefix}\n fetchJson={data?.fetchJson}\n devNotes={data?.devNotes}\n org={data?.org}\n identity={{\n userId: session.userId,\n userName: session.userName ?? null,\n email: session.email,\n role: session.role,\n }}\n >\n <FrameBody\n app={app}\n pathname={pathname}\n search={search}\n linkComponent={linkComponent}\n session={session}\n onSignOut={onSignOut}\n signOutUrl={signOutUrl}\n buildMeta={buildMeta}\n data={data}\n >\n {children}\n </FrameBody>\n </PolitogyAppChrome>\n )\n}\n\nfunction FrameBody({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n signOutUrl,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n pathname: string\n search?: string\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n signOutUrl: string\n buildMeta?: PolitogyBuildMeta\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n const chrome = useChromeState()\n\n // Sign-out is a PLATFORM event, not an app event. Each app holds its own\n // host-only session cookie with a long sliding window, so without this a user\n // who signed out in one tab stayed fully signed in in every other open tab\n // and on every other subdomain. Announce the sign-out to all of them, then\n // run the app's own sign-out.\n //\n // Every app reaches sign-out through this frame, so wiring it here covers the\n // whole estate without an app having to opt in — the same guarantee the rest\n // of the chrome relies on.\n const signOutRef = useRef(onSignOut)\n signOutRef.current = onSignOut\n // This tab started the sign-out, so its own watcher must not also fire and\n // run the app's sign-out a second time concurrently.\n const signingOutRef = useRef(false)\n\n const handleSignOut = useCallback(() => {\n signingOutRef.current = true\n broadcastLogout()\n // Navigate to the app's sign-out ROUTE. Apps also pass an onSignOut\n // handler, but it cannot be relied on: they wire it as\n // `onSignOut={() => { void signOutAction(); }}`, and a server action\n // invoked outside a transition can never have its redirect() applied by\n // the client. Measured against production 2026-07-25: clicking Sign out\n // left the user signed in, and an app they had never opened authenticated\n // with no credentials — the IdP session was still alive because the\n // browser never reached its end-session endpoint.\n //\n // A plain navigation to a GET route cannot fail that way: the route drops\n // the session cookie and 302s to RP-initiated logout at the IdP.\n signOutRef.current()\n window.location.assign(signOutUrl)\n }, [signOutUrl])\n\n useEffect(\n () =>\n // A tab that learns of a sign-out elsewhere ends its own session for\n // real, by navigating to the same sign-out route.\n //\n // Measured against production before this landed: calling the app's\n // handler did nothing at all. Apps wire it as\n // `onSignOut={() => { void signOutAction(); }}` — a server action invoked\n // outside a transition, so the `redirect()` inside it can never be\n // applied by the client and the tab just sits there. Building cross-tab\n // logout on that path made the whole feature a no-op.\n //\n // A hard navigation cannot fail that way, and it hands the decision to\n // the authority rather than guessing: the app's middleware sees a session\n // whose auth_time predates `politogy_logout_at`, treats it as revoked,\n // and redirects to Politogy ID. Server-side truth decides; the client\n // only forces the request. No re-broadcast — the signal that woke us is\n // already on record for every tab.\n watchLogout(() => {\n if (signingOutRef.current) return\n window.location.assign(signOutUrl)\n }),\n [signOutUrl],\n )\n // Core-only inputs are read through this ONE gate. For every other app it is\n // undefined no matter what the app passed, so the \"an app cannot re-specify a\n // chrome element\" guarantee holds by construction, not by convention.\n const core = app === CORE_APP ? data?.core : undefined\n\n // Identity: chrome API wins, app claims are a fallback, an email is never a\n // name (that is what forms was rendering in the top right).\n const identity = useChromeIdentity({ userName: session.userName, email: session.email })\n const derivedUserMenu = useChromeUserMenu({\n userId: session.userId,\n userName: identity.userName,\n email: identity.email,\n })\n const userMenu = core?.userMenu ?? derivedUserMenu\n const footer = useChromeFooter(buildMeta ?? {})\n // A mode-scoped app (Campaign apps, Petitions) pins its pill to its own mode;\n // an unscoped app stays entitlement + user-selection driven. Either way no\n // app passes a mode — it is read from the registry (app identity).\n const appMode = meta.mode\n const derivedMode = useChromeMode(appMode ? { pinnedModeId: appMode } : undefined)\n // Core switches Mode client-side; every other app jumps to the mode's home.\n const mode = core?.mode ?? derivedMode\n\n const coreOrgs = chrome?.orgs ?? []\n const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null\n const navDisplay = chrome?.navDisplay ?? 'both'\n\n // The nav tree follows the app's OWN mode (registry), not the live pill\n // selection — so it is deterministic from app identity. Unscoped apps render\n // Relationship exactly as before. Cross-app links in the Campaign/Petition\n // trees carry the active org the way the switcher/account menu do.\n const navMode: StandardNavMode = appMode ?? 'relationship'\n const activeOrgId = chrome?.orgId ?? null\n const derivedNavItems = useMemo(\n () =>\n standardPolitogyNav({\n coreBaseUrl: POLITOGY_APPS.vrm.origin,\n icons: standardNavIcons,\n // An app that OWNS a standard nav entry (Contacts) nests its own routes\n // under it. This is routing data, not chrome configuration.\n contactsItem: data?.contactsItem,\n mode: navMode,\n activeOrgId,\n }),\n [data?.contactsItem, navMode, activeOrgId],\n )\n // Core supplies the canonical tree already decorated with its live gating\n // state (and with app-relative hrefs so its router handles them in-app).\n const navItems = core?.nav ?? derivedNavItems\n\n // Dashboard › Sub › Item, derived from the same tree the sidebar renders.\n // Core publishes its trail from the page itself, because a core page's\n // location is finer-grained than its nav row (e.g. a contact detail view).\n const derivedBreadcrumbs = useMemo(\n () => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),\n [navItems, pathname, search, meta.origin],\n )\n const breadcrumbs = core ? core.page?.breadcrumbs : derivedBreadcrumbs\n\n return (\n <NavAppShell\n sidebar={{\n pathname,\n brand: {\n logoSrc: core?.brandLogoSrc ?? '/brand/logos/relationship-mode-logo.png',\n logoAlt: 'Politogy',\n badgeLabel: 'P',\n title: 'Politogy',\n },\n nav: {\n items:\n navDisplay === 'tabs'\n ? navItems.map(({ children: _sub, ...item }) => item)\n : navItems,\n },\n // The app's own workspace is NOT the organization — it is a per-app\n // bootstrap account with a different id in every app. Showing it while\n // the chrome API resolves flashed the wrong org on every load (\"not\n // connected to the account\"). Shimmer while in flight; only fall back\n // to the app label once the chrome lookup has actually failed.\n org: activeCoreOrg\n ? {\n name: activeCoreOrg.name,\n id: activeCoreOrg.id,\n logoUrl: activeCoreOrg.logoUrl,\n brandColor: activeCoreOrg.brandColor,\n organizations: coreOrgs,\n onSwitch: chrome?.setOrgId,\n }\n : chrome?.orgsLoading\n ? { name: null, id: null, loading: true }\n : { name: session.accountName, id: session.accountId },\n mode,\n footer,\n linkComponent,\n }}\n betaBanner={{}}\n contentWrapper={core?.contentWrapper}\n topBar={{\n // Core's pages publish their own identity; every other app is one\n // product surface and takes its registry title.\n title: core?.page?.title ?? meta.title,\n breadcrumbs,\n user: {\n name: identity.userName,\n email: identity.email,\n // The chrome API's avatar wins; the app may supply a fallback chain\n // (e.g. a stored memoji) for users with no uploaded photo.\n avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,\n role: session.role,\n },\n userMenu,\n onSignOut: handleSignOut,\n // The action cluster is IDENTICAL everywhere — core included. Page\n // actions sit BEFORE it, never inside it, so the Alerts bell keeps its\n // required position: farthest right, immediately before My Account.\n actions: (\n <>\n {core?.page?.actions}\n <ChromeTopBarActions />\n </>\n ),\n }}\n >\n {children}\n </NavAppShell>\n )\n}\n","// ---------------------------------------------------------------------------\n// App chrome wiring — the ONE shared implementation of the per-app glue that\n// previously lived as a copied ChromeWidgets.tsx in every Next.js feature app\n// (forms / surveys / polls / contacts). Copies drifted (org dropdown, badges);\n// this module makes that class of drift impossible: apps configure, never\n// re-implement (Reference/UI-Platform: consume via npm, never vendored).\n//\n// Provides:\n// <PolitogyAppChrome> — NavChromeProvider (same-origin proxy fetcher) +\n// chrome state (active core org, nav display) +\n// DevNotes provider, in one wrapper.\n// <ChromeTopBarActions> — the canonical top-bar cluster, in canonical order\n// (Fuel · Nav display · DevNotes · Support · Alerts\n// LAST, immediately left of My Account).\n// useChromeState / useChromeUserMenu / useIsSuperAdmin / useChromeFooter.\n//\n// The app still owns: its session (the /api/chrome proxy), page frames, build\n// metadata (env/commit/version), tab strips, and its own icons.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react'\nimport {\n NavChromeProvider,\n FuelAction,\n NotificationsAction,\n SupportAction,\n NavDisplayAction,\n TopBarWidgetSlot,\n useChromeOrgs,\n useThemePreference,\n type NavChromeFetcher,\n type NavDisplayMode,\n type NavOrgOption,\n type NavUserMenu,\n type NavFooter,\n type NavFooterLink,\n} from '../native/index'\nimport {\n DevNotesProvider,\n DevNotesMenu,\n DevNotesOverlay,\n DevNotesTaskListModal,\n createDevNotesClient,\n type DevNotesConfig,\n} from '@the-portland-company/devnotes'\nimport {\n FiActivity,\n FiBarChart2,\n FiBriefcase,\n FiClock,\n FiCreditCard,\n FiDollarSign,\n FiEdit3,\n FiFileText,\n FiFlag,\n FiGrid,\n FiHome,\n FiLayout,\n FiLifeBuoy,\n FiList,\n FiMail,\n FiMap,\n FiMapPin,\n FiMessageSquare,\n FiPhone,\n FiSend,\n FiSettings,\n FiShare2,\n FiTarget,\n FiUpload,\n FiUser,\n FiUsers,\n} from 'react-icons/fi'\nimport { useChromeMe, useNavChromeFetcher } from '../native/index'\nimport {\n ACTIVE_ORG_QUERY_PARAM,\n isOrgId,\n resolveActiveOrg,\n writeActiveOrgCookie,\n} from '../active-org'\nimport {\n NAV_DISPLAY_DEFAULT,\n NAV_DISPLAY_STORAGE_KEY,\n isNavDisplayPreference,\n readNavDisplayCookie,\n resolveNavDisplay,\n writeNavDisplayCookie,\n writeNavDisplayLocal,\n} from '../nav-preference'\nimport type { StandardNavIconKey } from '../native/standardNav'\nimport type { NavBreadcrumb, NavIcon, NavItem, NavMode } from '../native/index'\n\n/**\n * The canonical icon map for standardPolitogyNav — ONE definition so every\n * app renders identical glyphs (including the Organization Settings branch:\n * briefcase / settings / creditCard / dollarSign / share).\n */\nexport const standardNavIcons: Record<StandardNavIconKey, NavIcon> = {\n home: FiHome,\n users: FiUsers,\n mail: FiMail,\n messageSquare: FiMessageSquare,\n layout: FiLayout,\n activity: FiActivity,\n lifeBuoy: FiLifeBuoy,\n grid: FiGrid,\n list: FiList,\n upload: FiUpload,\n edit: FiEdit3,\n fileText: FiFileText,\n send: FiSend,\n clock: FiClock,\n user: FiUser,\n barChart: FiBarChart2,\n briefcase: FiBriefcase,\n settings: FiSettings,\n creditCard: FiCreditCard,\n dollarSign: FiDollarSign,\n share: FiShare2,\n // Campaign / Petition mode slots.\n phone: FiPhone,\n map: FiMap,\n mapPin: FiMapPin,\n target: FiTarget,\n flag: FiFlag,\n}\n\nconst ORG_STORAGE_KEY = 'vrm.activeOrg'\n// The user's mode choice, shared by every app on the origin's localStorage.\nconst MODE_STORAGE_KEY = 'vrm.activeMode'\nconst CORE_BASE_URL = 'https://app.politogyvrm.com'\n\nexport type ChromeIdentity = {\n userId: string\n userName: string | null\n email: string | null\n /** The APP's own role string (any casing) — feeds DevNotes admin gating. */\n role: string\n}\n\ntype ChromeState = {\n orgId: string | null\n setOrgId: (id: string) => void\n orgs: (NavOrgOption & { rawRole?: string | null })[]\n /**\n * The org list is still in flight. Consumers must render a skeleton rather\n * than fall back to the app's own workspace label, which is a DIFFERENT\n * account and flashes as the wrong organization.\n */\n orgsLoading: boolean\n navDisplay: NavDisplayMode\n setNavDisplay: (m: NavDisplayMode) => void\n}\n\nconst ChromeStateContext = createContext<ChromeState | null>(null)\n\nexport function useChromeState(): ChromeState | null {\n return useContext(ChromeStateContext)\n}\n\nexport type ControlledOrgState = {\n orgId: string | null\n onSwitch: (id: string) => void\n /** Override the org list (e.g. the app's own session-scoped list). */\n orgs?: (NavOrgOption & { rawRole?: string | null })[]\n}\n\nfunction ChromeStateProvider({\n controlledOrg,\n children,\n}: {\n controlledOrg?: ControlledOrgState\n children: ReactNode\n}) {\n const { orgs, loading: orgsFetching, error: orgsError } = useChromeOrgs()\n const chromeMe = useChromeMe()\n const chromeFetch = useNavChromeFetcher()\n const [orgId, setOrgIdState] = useState<string | null>(null)\n const [navDisplay, setNavDisplayState] = useState<NavDisplayMode>(NAV_DISPLAY_DEFAULT)\n const navReconciled = useRef(false)\n\n // Boot: restore persisted choices. The cross-app active org (URL `?org=` or\n // the `.politogyvrm.com` cookie) wins over host-only localStorage so every\n // app + subdomain resolves the SAME org instead of each defaulting to its own\n // earliest-joined org. Nav display follows the same cross-subdomain contract\n // as theme (cookie → localStorage → default) — it used to be localStorage\n // only, which is per-origin, so it reset on every app the user opened.\n useEffect(() => {\n try {\n const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY)\n if (storedOrg) setOrgIdState(storedOrg)\n } catch {\n /* storage unavailable */\n }\n setNavDisplayState(resolveNavDisplay())\n }, [])\n\n // Once per session: converge on the profile's saved choice (authoritative),\n // reusing the provider's single `me` fetch — the theme reconcile pattern.\n useEffect(() => {\n if (navReconciled.current || !chromeMe.me) return\n const pref = chromeMe.me.navDisplay\n if (!isNavDisplayPreference(pref)) return\n navReconciled.current = true\n setNavDisplayState(pref)\n writeNavDisplayCookie(pref)\n writeNavDisplayLocal(pref)\n }, [chromeMe])\n\n // Live sync: `storage` events for same-app tabs, cookie re-reads on\n // focus/visibility for tabs on OTHER app subdomains.\n useEffect(() => {\n const onStorage = (e: StorageEvent) => {\n if (e.key === NAV_DISPLAY_STORAGE_KEY && isNavDisplayPreference(e.newValue)) {\n setNavDisplayState(e.newValue)\n }\n }\n const onWake = () => {\n const cookie = readNavDisplayCookie()\n if (cookie) setNavDisplayState(cookie)\n }\n window.addEventListener('storage', onStorage)\n window.addEventListener('focus', onWake)\n document.addEventListener('visibilitychange', onWake)\n return () => {\n window.removeEventListener('storage', onStorage)\n window.removeEventListener('focus', onWake)\n document.removeEventListener('visibilitychange', onWake)\n }\n }, [])\n\n // Once orgs resolve: validate the stored org, default to the first.\n useEffect(() => {\n if (!orgs || orgs.length === 0) return\n setOrgIdState((prev) => (prev && orgs.some((o) => o.id === prev) ? prev : orgs[0].id))\n }, [orgs])\n\n const setOrgId = useCallback((id: string) => {\n setOrgIdState(id)\n try {\n localStorage.setItem(ORG_STORAGE_KEY, id)\n } catch {\n /* best effort */\n }\n // Remember the choice cross-subdomain so the next app opens on the same org.\n writeActiveOrgCookie(id)\n }, [])\n\n const setNavDisplay = useCallback(\n (m: NavDisplayMode) => {\n setNavDisplayState(m)\n writeNavDisplayLocal(m)\n // Cross-subdomain, so the next app opens on the same choice.\n writeNavDisplayCookie(m)\n // Best-effort durable persistence on the user's profile — a nav toggle\n // never errors, and the cookie already carries the choice meanwhile.\n chromeFetch?.('me/preferences', {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ navDisplay: m }),\n })?.catch(() => {})\n },\n [chromeFetch],\n )\n\n const value = useMemo<ChromeState>(() => {\n // CONTROLLED mode (Contacts): the APP owns the active org + switching —\n // its switch does a full session re-scope. The shared widgets read the\n // app's state so there is exactly one org truth (unification directive).\n if (controlledOrg) {\n return {\n orgId: controlledOrg.orgId,\n setOrgId: controlledOrg.onSwitch,\n orgs:\n controlledOrg.orgs ??\n (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n orgsLoading: false,\n navDisplay,\n setNavDisplay,\n }\n }\n return {\n orgId,\n setOrgId,\n orgs: (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n // Loading until the fetch settles. On error we stop loading so the frame\n // falls back to the app label rather than shimmering forever.\n orgsLoading: orgsFetching && !orgsError && !orgs,\n navDisplay,\n setNavDisplay,\n }\n }, [controlledOrg, orgId, setOrgId, orgs, orgsFetching, orgsError, navDisplay, setNavDisplay])\n\n // Keep the shared cookie + address-bar `?org=` in sync with the ACTIVE org —\n // whichever path owns it, controlled (Contacts) or uncontrolled (feature\n // apps) — so every app + tab resolves the same org and every URL is\n // bookmarkable/shareable with its org context. Idempotent; no-op on server.\n const activeOrgId = value.orgId\n useEffect(() => {\n if (!isOrgId(activeOrgId)) return\n writeActiveOrgCookie(activeOrgId as string)\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get(ACTIVE_ORG_QUERY_PARAM) !== activeOrgId) {\n url.searchParams.set(ACTIVE_ORG_QUERY_PARAM, activeOrgId as string)\n window.history.replaceState(window.history.state, '', url.toString())\n }\n } catch {\n /* SSR / no window */\n }\n }, [activeOrgId])\n\n return <ChromeStateContext.Provider value={value}>{children}</ChromeStateContext.Provider>\n}\n\nexport type ChromeDevNotesConfig = {\n /** Override the DevNotes API base (default: `${proxyBasePath}/devnotes`). */\n basePath?: string\n /** Client-side token supplier (default: empty — same-origin proxy injects). */\n getAuthToken?: () => Promise<string> | string\n /** Custom fetch (e.g. credentials:'include' against a cross-origin API). */\n fetch?: typeof globalThis.fetch\n /**\n * The app already mounts its own `<DevNotesProvider>` ABOVE this chrome, so\n * the chrome must not mount a second one (VRM core, whose provider carries\n * capabilities the generic client has no equivalent for — user stories, its\n * own role gating). The working SURFACE still comes from here, so the\n * launcher, the overlay and the task modal behave identically everywhere;\n * only the data adapter is the app's.\n */\n providedByApp?: boolean\n}\n\nfunction AppDevNotes({\n storagePrefix,\n proxyBasePath,\n devNotes,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath: string\n devNotes?: ChromeDevNotesConfig\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const adapter = useMemo(\n () =>\n createDevNotesClient({\n basePath: devNotes?.basePath ?? `${proxyBasePath}/devnotes`,\n // Default: the same-origin proxy attaches the session's Bearer.\n getAuthToken: devNotes?.getAuthToken ?? (async () => ''),\n ...(devNotes?.fetch ? { fetch: devNotes.fetch } : {}),\n }),\n [proxyBasePath, devNotes?.basePath, devNotes?.getAuthToken, devNotes?.fetch],\n )\n\n const user = useMemo(\n () => ({\n id: identity.userId,\n email: identity.email ?? '',\n fullName: identity.userName ?? undefined,\n }),\n [identity.userId, identity.email, identity.userName],\n )\n\n const config = useMemo<DevNotesConfig>(\n () => ({\n storagePrefix,\n role: identity.role.toLowerCase() === 'admin' ? 'admin' : 'reporter',\n disableAi: true,\n getPagePath: () => window.location.pathname,\n }),\n [storagePrefix, identity.role],\n )\n\n // The app brought its own provider — mount only the surface, so a second\n // DevNotesProvider never shadows the app's adapter.\n if (devNotes?.providedByApp) return <DevNotesSurface>{children}</DevNotesSurface>\n\n return (\n <DevNotesProvider adapter={adapter} user={user} config={config}>\n <DevNotesSurface>{children}</DevNotesSurface>\n </DevNotesProvider>\n )\n}\n\n// \"View All Tasks\" is triggered from the menu in the top bar but the modal has\n// to render at the chrome ROOT, or it would be trapped inside the top bar's\n// stacking/blur context. The menu publishes the request through this context.\nconst DevNotesTasksContext = createContext<{ ready: boolean; openTasks: () => void }>({\n ready: false,\n openTasks: () => {},\n})\n\n/**\n * The DevNotes working surface, mounted once at the chrome root.\n *\n * `<DevNotesMenu>` only TOGGLES creation mode — `<DevNotesOverlay>` is what\n * renders the click-to-place capture layer, the report dots and the report\n * form. The shell shipped the provider and the menu but never the overlay, so\n * on every app that used this chrome (forms / surveys / polls) \"Create Task\"\n * closed the menu and did nothing at all: the mode flipped on with nothing\n * listening (HITL bug report 2026-07-23). Contacts didn't hit it only because\n * it still had its own hand-rolled surface.\n */\nfunction DevNotesSurface({ children }: { children: ReactNode }) {\n const [tasksOpen, setTasksOpen] = useState(false)\n const openTasks = useCallback(() => setTasksOpen(true), [])\n const tasksCtx = useMemo(() => ({ ready: true, openTasks }), [openTasks])\n\n return (\n <DevNotesTasksContext.Provider value={tasksCtx}>\n {children}\n {/* Mounted only while open: the modal loads data on mount regardless of\n its `open` prop, so keeping it unmounted avoids fetching every user's\n tasks before they ask for them. */}\n {tasksOpen ? <DevNotesTaskListModal open onClose={() => setTasksOpen(false)} /> : null}\n <DevNotesOverlay />\n </DevNotesTasksContext.Provider>\n )\n}\n\n/**\n * The app-chrome root. Wrap the authenticated frame once:\n *\n * <PolitogyAppChrome storagePrefix=\"forms\" identity={{userId, userName, email, role}}>\n * <ShellFrame …/>\n * </PolitogyAppChrome>\n */\nexport function PolitogyAppChrome({\n storagePrefix,\n proxyBasePath = '/api/chrome',\n fetchJson: fetchJsonOverride,\n devNotes,\n org,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath?: string\n /**\n * Override the same-origin proxy fetcher for apps that talk to the chrome\n * API directly with their own auth (Contacts sends a client-side Bearer).\n * Paths stay relative ('orgs', 'me', …) — the override owns base + auth.\n */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for direct-auth apps (Contacts). */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state — the app owns active-org + switching (Contacts). */\n org?: ControlledOrgState\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const proxyFetchJson = useMemo<NavChromeFetcher>(\n () =>\n async (path, init) => {\n const res = await fetch(`${proxyBasePath}/${path}`, {\n method: init?.method ?? 'GET',\n headers: init?.headers,\n body: init?.body,\n })\n if (!res.ok) throw new Error(`chrome ${path}: ${res.status}`)\n if (res.status === 204) return null\n const text = await res.text()\n return text ? JSON.parse(text) : null\n },\n [proxyBasePath],\n )\n const fetchJson = fetchJsonOverride ?? proxyFetchJson\n\n return (\n <NavChromeProvider fetchJson={fetchJson}>\n <ChromeStateProvider controlledOrg={org}>\n <AppDevNotes\n storagePrefix={storagePrefix}\n proxyBasePath={proxyBasePath}\n devNotes={devNotes}\n identity={identity}\n >\n {children}\n </AppDevNotes>\n </ChromeStateProvider>\n </NavChromeProvider>\n )\n}\n\n/** The standard top-bar action cluster. The Alerts bell renders LAST so it\n * sits farthest right, immediately left of the My Account badge. */\nexport function ChromeTopBarActions() {\n const state = useChromeState()\n if (!state) return null\n return (\n <>\n <FuelAction orgId={state.orgId} />\n <NavDisplayAction value={state.navDisplay} onChange={state.setNavDisplay} />\n <TopBarWidgetSlot\n title=\"DevNotes\"\n description=\"Capture bugs and notes, filed straight to Forge.\"\n >\n <ChromeDevNotesMenu />\n </TopBarWidgetSlot>\n <SupportAction orgId={state.orgId} />\n <NotificationsAction orgId={state.orgId} />\n </>\n )\n}\n\n/**\n * The DevNotes launcher. Renders nothing until the surface above is mounted, so\n * it never touches DevNotes context before the provider exists, and forwards\n * \"View All Tasks\" to the root-level modal.\n */\nfunction ChromeDevNotesMenu() {\n const { ready, openTasks } = useContext(DevNotesTasksContext)\n if (!ready) return null\n return <DevNotesMenu position=\"top-right\" onViewTasks={openTasks} />\n}\n\n/** True when any core-org membership carries a super-admin tier. */\nexport function useIsSuperAdmin(): boolean {\n const state = useChromeState()\n return (state?.orgs ?? []).some((o) => /super/i.test(o.rawRole ?? ''))\n}\n\nconst FOOTER_LINKS: NavFooterLink[] = [\n { label: 'Privacy', href: `${CORE_BASE_URL}/privacy-policy`, external: true },\n { label: 'Terms', href: `${CORE_BASE_URL}/terms-and-conditions`, external: true },\n {\n label: 'Subscriptions',\n href: 'https://politogyvrm.com/subscription-cancellation-policy',\n external: true,\n },\n]\n\n/**\n * Sidebar footer: legal links for everyone; build metadata (env / commit /\n * version / date / author) ONLY for Super Administrators\n * (Reference/UI-Platform). Pass the app's build-time metadata in.\n */\nexport function useChromeFooter(meta: {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}): NavFooter {\n const isSuperAdmin = useIsSuperAdmin()\n return useMemo<NavFooter>(\n () => (isSuperAdmin ? { links: FOOTER_LINKS, ...meta } : { links: FOOTER_LINKS }),\n [isSuperAdmin, meta.envLabel, meta.version, meta.commit, meta.commitDate, meta.commitAuthor],\n )\n}\n\n// ---------------------------------------------------------------------------\n// Identity — resolved by the PACKAGE, from the chrome API.\n//\n// Apps used to pass a display name straight from their own session claims, and\n// forms passed `user.name ?? user.email` — so a user with no name set had their\n// EMAIL rendered as their name, and the chrome API's real profile name could\n// never win (HITL 2026-07-23). The chrome API is the authority; what the app\n// passes is a pre-hydration fallback only, and an email is never a name.\n// ---------------------------------------------------------------------------\n\nexport function useChromeIdentity(fallback?: {\n userName?: string | null\n email?: string | null\n}): ResolvedIdentity {\n const { me, loaded } = useChromeMe()\n return resolveIdentity({\n apiName: me?.userName,\n fallbackName: fallback?.userName,\n email: fallback?.email,\n avatarUrl: me?.avatarUrl,\n loaded,\n })\n}\n\n// Breadcrumbs live in ../lib/breadcrumbs (pure, dependency-free) and are\n// re-exported here so apps keep a single import surface.\nexport { deriveBreadcrumbs } from '../lib/breadcrumbs'\nexport { looksLikeEmail, resolveIdentity, type ResolvedIdentity } from '../lib/identity'\nimport { resolveIdentity, type ResolvedIdentity } from '../lib/identity'\n\n// ---------------------------------------------------------------------------\n// Breadcrumbs — derived by the PACKAGE from the nav tree + the current path.\n//\n// Every app hand-built its own trail, so they disagreed: forms rendered\n// \"Forms › Dashboard\" while polls rendered \"Polls\" under a \"Polls\" title\n// (\"Polls / Polls\"). The standard is Dashboard › Sub › Item (HITL 2026-07-23),\n// derived from the same nav tree the sidebar renders, so no app can drift.\n// ---------------------------------------------------------------------------\n\n/**\n * The My Account menu config — profile/org links into VRM core, real roles\n * from the user's core organizations, the signed-in account, and the shared\n * Light/System/Dark theme control (auth-derived, cross-app).\n */\nexport function useChromeUserMenu({\n userId,\n userName,\n email,\n}: {\n userId: string\n userName: string | null\n email: string | null\n}): NavUserMenu {\n const state = useChromeState()\n const theme = useThemePreference()\n const activeOrg = state?.orgs.find((o) => o.id === state.orgId) ?? null\n const orgSuffix = activeOrg ? `?org=${activeOrg.id}` : ''\n return {\n // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).\n myProfileHref: `${CORE_BASE_URL}/my-profile`,\n organizationSettingsHref: `${CORE_BASE_URL}/organization-settings${orgSuffix}`,\n roles: (state?.orgs ?? [])\n .filter((o) => o.rawRole || o.role)\n .map((o) => ({ role: (o.rawRole ?? o.role) as string, orgName: o.name })),\n accounts: [{ id: userId, name: userName, email, avatarUrl: null, active: true }],\n addAccountHref: `${CORE_BASE_URL}/my-profile#accounts`,\n theme: { value: theme.value, onChange: theme.setValue },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mode selector — entitlement-driven (HITL 2026-07-23: an org with multiple\n// active modes selects them from the left nav). Entitlements come from the\n// chrome API: GET me with the active org header → modes booleans\n// {relationship, campaign, petition, polling}. Selecting a mode navigates to\n// that mode's VRM-core dashboard carrying the active org.\n// ---------------------------------------------------------------------------\n\nconst MODE_DEFS: {\n id: string\n label: string\n icon: NavIcon\n /** Absolute destination, or a VRM-core path prefixed with CORE_BASE_URL. */\n href: string\n orgQuery: boolean\n}[] = [\n {\n id: 'relationship',\n label: 'Relationship Mode',\n icon: FiUsers,\n href: `${CORE_BASE_URL}/relationship/dashboard`,\n orgQuery: true,\n },\n {\n id: 'campaign',\n label: 'Campaign Mode',\n icon: FiBarChart2,\n href: `${CORE_BASE_URL}/campaign/dashboard`,\n orgQuery: true,\n },\n {\n // Petition Mode lives in its own standalone app on its own subdomain — the\n // petitions repo deploys production to petition-mode.politogyvrm.com.\n id: 'petition',\n label: 'Petition Mode',\n icon: FiFlag,\n href: 'https://petition-mode.politogyvrm.com/',\n orgQuery: true,\n },\n]\n\ntype ModeId = 'relationship' | 'campaign' | 'petition'\n\ntype UseChromeModeArg =\n | ModeId\n | {\n /** Pre-entitlement fallback for first paint (default 'relationship'). */\n fallbackModeId?: ModeId\n /**\n * Registry-pinned mode for a MODE-SCOPED app (e.g. the Campaign apps\n * a2p / phonebank / canvass / signs, from `POLITOGY_APPS[app].mode`). The\n * pill DISPLAYS this mode regardless of the cross-app selection, so a\n * campaign app never shows a Relationship pill. The switcher still appears\n * when the active org is entitled to more than one mode, so the user can\n * jump out to another mode's home.\n */\n pinnedModeId?: ModeId\n }\n\n/**\n * The active mode pill / selector.\n *\n * For an UNSCOPED app the current mode is the USER'S OWN SELECTION (persisted\n * cross-app in localStorage under `vrm.activeMode`), narrowed to what the active\n * org is entitled to — never a literal an app hardcodes. Apps used to call\n * `useChromeMode('relationship')`, which pinned the pill to Relationship even\n * for an org entitled to Campaign or Petition (HITL 2026-07-23); the string\n * argument now only supplies the pre-entitlement fallback for first paint.\n *\n * A MODE-SCOPED app passes `{ pinnedModeId }` so its pill displays that mode\n * (its own mode from the app registry) rather than the cross-app selection —\n * while a multi-mode org still gets the switcher to leave.\n */\nexport function useChromeMode(arg: UseChromeModeArg = {}): NavMode {\n const { fallbackModeId = 'relationship', pinnedModeId } =\n typeof arg === 'string' ? { fallbackModeId: arg, pinnedModeId: undefined } : arg\n const ctx = useContext(ChromeStateContext)\n const nav = useNavChromeFetcher()\n const orgId = ctx?.orgId ?? null\n const [entitled, setEntitled] = useState<Record<string, boolean> | null>(null)\n const [selectedModeId, setSelectedModeId] = useState<string | null>(null)\n\n // Restore the user's last mode choice (shared key across every app).\n useEffect(() => {\n try {\n const stored = localStorage.getItem(MODE_STORAGE_KEY)\n if (stored && MODE_DEFS.some((m) => m.id === stored)) setSelectedModeId(stored)\n } catch {\n /* storage unavailable */\n }\n }, [])\n\n useEffect(() => {\n if (!nav || !orgId) return\n let cancelled = false\n nav('me', { headers: { 'x-contacts-org': orgId } })\n .then((data) => {\n if (cancelled) return\n const modes = (data as { modes?: Record<string, boolean> | null })?.modes ?? null\n setEntitled(modes)\n })\n .catch(() => {\n /* entitlements unavailable — single-mode pill stands */\n })\n return () => {\n cancelled = true\n }\n }, [nav, orgId])\n\n const options = MODE_DEFS.filter((m) => entitled?.[m.id])\n // Resolution order: a registry pin (mode-scoped app) wins outright → then the\n // user's selection (if the org is still entitled to it) → the first entitled\n // mode → the app's fallback. Entitlements are async, so before they land the\n // pin/fallback holds the pill's shape (no layout shift).\n const pinned = pinnedModeId ? MODE_DEFS.find((m) => m.id === pinnedModeId) ?? null : null\n const selected = selectedModeId\n ? options.find((m) => m.id === selectedModeId) ?? null\n : null\n const current =\n pinned ??\n selected ??\n options[0] ??\n MODE_DEFS.find((m) => m.id === fallbackModeId) ??\n MODE_DEFS[0]\n const suffix = orgId ? `?org=${orgId}` : ''\n\n return {\n label: current.label,\n icon: current.icon,\n options:\n options.length > 1\n ? options.map((o) => ({ id: o.id, label: o.label, icon: o.icon }))\n : undefined,\n onSelect:\n options.length > 1\n ? (id: string) => {\n const def = MODE_DEFS.find((m) => m.id === id)\n if (!def) return\n // Remember the choice BEFORE navigating so the destination app —\n // and every app after it — opens in the mode the user picked.\n setSelectedModeId(id)\n try {\n localStorage.setItem(MODE_STORAGE_KEY, id)\n } catch {\n /* storage unavailable */\n }\n const sep = def.href.includes('?') ? '&' : '?'\n window.location.assign(\n def.orgQuery && suffix ? `${def.href}${sep}${suffix.slice(1)}` : def.href,\n )\n }\n : undefined,\n }\n}\n\n// The one-component app frame (see ./frame.tsx). Re-exported here so apps have\n// a single import surface: `@the-portland-company/shell/app-chrome`.\nexport {\n PolitogyAppFrame,\n POLITOGY_APPS,\n type PolitogyAppKey,\n type PolitogyAppMode,\n type PolitogyAppSession,\n type PolitogyBuildMeta,\n type PolitogyFrameData,\n} from './frame'\n"]}
@@ -0,0 +1,63 @@
1
+ // src/nav-preference.ts
2
+ var NAV_DISPLAY_STORAGE_KEY = "politogy_nav_display";
3
+ var NAV_DISPLAY_COOKIE = "politogy_nav_display";
4
+ var LEGACY_NAV_DISPLAY_KEY = "vrm.navDisplay";
5
+ var NAV_DISPLAY_DEFAULT = "both";
6
+ var VALID = ["menu", "tabs", "both"];
7
+ function isNavDisplayPreference(v) {
8
+ return typeof v === "string" && VALID.includes(v);
9
+ }
10
+ function readNavDisplayCookie() {
11
+ if (typeof document === "undefined") return null;
12
+ try {
13
+ const m = document.cookie.match(
14
+ new RegExp(`(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)`)
15
+ );
16
+ const v = m ? decodeURIComponent(m[1]) : null;
17
+ return isNavDisplayPreference(v) ? v : null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function writeNavDisplayCookie(pref) {
23
+ if (typeof document === "undefined" || typeof window === "undefined") return;
24
+ const host = window.location.hostname;
25
+ const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
26
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
27
+ document.cookie = `${NAV_DISPLAY_COOKIE}=${encodeURIComponent(
28
+ pref
29
+ )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
30
+ }
31
+ function readNavDisplayLocal() {
32
+ if (typeof localStorage === "undefined") return null;
33
+ try {
34
+ const current = localStorage.getItem(NAV_DISPLAY_STORAGE_KEY);
35
+ if (isNavDisplayPreference(current)) return current;
36
+ const legacy = localStorage.getItem(LEGACY_NAV_DISPLAY_KEY);
37
+ return isNavDisplayPreference(legacy) ? legacy : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function writeNavDisplayLocal(pref) {
43
+ if (typeof localStorage === "undefined") return;
44
+ try {
45
+ localStorage.setItem(NAV_DISPLAY_STORAGE_KEY, pref);
46
+ } catch {
47
+ }
48
+ }
49
+ function resolveNavDisplay() {
50
+ return readNavDisplayCookie() ?? readNavDisplayLocal() ?? NAV_DISPLAY_DEFAULT;
51
+ }
52
+ var NAV_DISPLAY_PREPAINT_SCRIPT = `(function(){try{
53
+ var KEY=${JSON.stringify(NAV_DISPLAY_STORAGE_KEY)};
54
+ var VALID=["menu","tabs","both"];
55
+ function fromCookie(){try{var m=document.cookie.match(/(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}
56
+ var cookie=fromCookie();
57
+ if(cookie&&VALID.indexOf(cookie)!==-1){var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
58
+ if(cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}}
59
+ }catch(e){}})();`;
60
+
61
+ export { LEGACY_NAV_DISPLAY_KEY, NAV_DISPLAY_COOKIE, NAV_DISPLAY_DEFAULT, NAV_DISPLAY_PREPAINT_SCRIPT, NAV_DISPLAY_STORAGE_KEY, isNavDisplayPreference, readNavDisplayCookie, readNavDisplayLocal, resolveNavDisplay, writeNavDisplayCookie, writeNavDisplayLocal };
62
+ //# sourceMappingURL=chunk-6UHJV2FH.js.map
63
+ //# sourceMappingURL=chunk-6UHJV2FH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nav-preference.ts"],"names":[],"mappings":";AAwBO,IAAM,uBAAA,GAA0B;AAChC,IAAM,kBAAA,GAAqB;AAG3B,IAAM,sBAAA,GAAyB;AAE/B,IAAM,mBAAA,GAA4C;AAEzD,IAAM,KAAA,GAAyC,CAAC,MAAA,EAAQ,MAAA,EAAQ,MAAM,CAAA;AAE/D,SAAS,uBAAuB,CAAA,EAAuC;AAC5E,EAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAa,KAAA,CAA4B,SAAS,CAAC,CAAA;AACzE;AAGO,SAAS,oBAAA,GAAoD;AAClE,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,IAAA;AAC5C,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,SAAS,MAAA,CAAO,KAAA;AAAA,MACxB,IAAI,MAAA,CAAO,CAAA,WAAA,EAAc,kBAAkB,CAAA,QAAA,CAAU;AAAA,KACvD;AACA,IAAA,MAAM,IAAI,CAAA,GAAI,kBAAA,CAAmB,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI,IAAA;AACzC,IAAA,OAAO,sBAAA,CAAuB,CAAC,CAAA,GAAI,CAAA,GAAI,IAAA;AAAA,EACzC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAMO,SAAS,sBAAsB,IAAA,EAAkC;AACtE,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,IAAe,OAAO,WAAW,WAAA,EAAa;AACtE,EAAA,MAAM,IAAA,GAAO,OAAO,QAAA,CAAS,QAAA;AAC7B,EAAA,MAAM,UAAA,GAAa,0BAAA,CAA2B,IAAA,CAAK,IAAI,IAAI,2BAAA,GAA8B,EAAA;AACzF,EAAA,MAAM,MAAA,GAAS,MAAA,CAAO,QAAA,CAAS,QAAA,KAAa,WAAW,UAAA,GAAa,EAAA;AACpE,EAAA,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG,kBAAkB,CAAA,CAAA,EAAI,kBAAA;AAAA,IACzC;AAAA,GACD,CAAA,wCAAA,EAA2C,UAAU,CAAA,EAAG,MAAM,CAAA,CAAA;AACjE;AAGO,SAAS,mBAAA,GAAmD;AACjE,EAAA,IAAI,OAAO,YAAA,KAAiB,WAAA,EAAa,OAAO,IAAA;AAChD,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,OAAA,CAAQ,uBAAuB,CAAA;AAC5D,IAAA,IAAI,sBAAA,CAAuB,OAAO,CAAA,EAAG,OAAO,OAAA;AAC5C,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,sBAAsB,CAAA;AAC1D,IAAA,OAAO,sBAAA,CAAuB,MAAM,CAAA,GAAI,MAAA,GAAS,IAAA;AAAA,EACnD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEO,SAAS,qBAAqB,IAAA,EAAkC;AACrE,EAAA,IAAI,OAAO,iBAAiB,WAAA,EAAa;AACzC,EAAA,IAAI;AACF,IAAA,YAAA,CAAa,OAAA,CAAQ,yBAAyB,IAAI,CAAA;AAAA,EACpD,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAGO,SAAS,iBAAA,GAA0C;AACxD,EAAA,OAAO,oBAAA,EAAqB,IAAK,mBAAA,EAAoB,IAAK,mBAAA;AAC5D;AAMO,IAAM,2BAAA,GAA8B,CAAA;AAAA,QAAA,EACjC,IAAA,CAAK,SAAA,CAAU,uBAAuB,CAAC,CAAA;AAAA;AAAA,kEAAA,EAEmB,kBAAkB,CAAA;AAAA;AAAA;AAAA;AAAA,gBAAA","file":"chunk-6UHJV2FH.js","sourcesContent":["// Universal navigation-display preference — the same cross-app contract as the\n// theme (see ./theme-preference, which this deliberately mirrors).\n//\n// The user's choice of how navigation renders (Menu / Tabs / Both) follows the\n// authenticated user across every Politogy app + subdomain. Three layers:\n// 1. VRM Core `profiles.nav_display_preference` — source of truth, persisted\n// via the chrome API (`PATCH me/preferences { navDisplay }`) and read back\n// from `GET me` (`navDisplay`).\n// 2. `.politogyvrm.com` cookie `politogy_nav_display` — the cross-subdomain\n// vehicle (app session cookies are host-only, and so is localStorage).\n// 3. localStorage — same-app fast reads and cross-tab `storage` events.\n//\n// Before this existed the preference lived ONLY in localStorage under\n// `vrm.navDisplay`, which is per-ORIGIN: every app subdomain kept its own copy\n// and a freshly opened app booted the default, so the setting appeared not to\n// hold when moving between apps. The legacy key is still read as a fallback so\n// an existing local choice survives the upgrade.\n//\n// Server-safe (no 'use client'): layouts may import NAV_DISPLAY_PREPAINT_SCRIPT.\n\nexport type NavDisplayPreference = 'menu' | 'tabs' | 'both'\n\n// One key for BOTH the localStorage store and the cross-subdomain cookie, so a\n// choice in one app is the same key in the next.\nexport const NAV_DISPLAY_STORAGE_KEY = 'politogy_nav_display'\nexport const NAV_DISPLAY_COOKIE = 'politogy_nav_display'\n\n/** Pre-0.37 host-only key. Read for migration; never written. */\nexport const LEGACY_NAV_DISPLAY_KEY = 'vrm.navDisplay'\n\nexport const NAV_DISPLAY_DEFAULT: NavDisplayPreference = 'both'\n\nconst VALID: readonly NavDisplayPreference[] = ['menu', 'tabs', 'both']\n\nexport function isNavDisplayPreference(v: unknown): v is NavDisplayPreference {\n return typeof v === 'string' && (VALID as readonly string[]).includes(v)\n}\n\n/** Read the shared cookie (browser only; null on the server or when unset). */\nexport function readNavDisplayCookie(): NavDisplayPreference | null {\n if (typeof document === 'undefined') return null\n try {\n const m = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)`),\n )\n const v = m ? decodeURIComponent(m[1]) : null\n return isNavDisplayPreference(v) ? v : null\n } catch {\n return null\n }\n}\n\n// Same host-aware scoping as the theme cookie: parent domain on\n// *.politogyvrm.com so every app subdomain reads it, host-only elsewhere\n// (localhost, *.pages.dev preview) because browsers silently drop a mismatched\n// Domain attribute. 1-year, SameSite=Lax, Secure on https.\nexport function writeNavDisplayCookie(pref: NavDisplayPreference): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return\n const host = window.location.hostname\n const domainAttr = /(^|\\.)politogyvrm\\.com$/i.test(host) ? '; Domain=.politogyvrm.com' : ''\n const secure = window.location.protocol === 'https:' ? '; Secure' : ''\n document.cookie = `${NAV_DISPLAY_COOKIE}=${encodeURIComponent(\n pref,\n )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`\n}\n\n/** Read the local store, falling back to the legacy host-only key. */\nexport function readNavDisplayLocal(): NavDisplayPreference | null {\n if (typeof localStorage === 'undefined') return null\n try {\n const current = localStorage.getItem(NAV_DISPLAY_STORAGE_KEY)\n if (isNavDisplayPreference(current)) return current\n const legacy = localStorage.getItem(LEGACY_NAV_DISPLAY_KEY)\n return isNavDisplayPreference(legacy) ? legacy : null\n } catch {\n return null\n }\n}\n\nexport function writeNavDisplayLocal(pref: NavDisplayPreference): void {\n if (typeof localStorage === 'undefined') return\n try {\n localStorage.setItem(NAV_DISPLAY_STORAGE_KEY, pref)\n } catch {\n /* storage unavailable */\n }\n}\n\n/** Boot precedence: cookie → localStorage (incl. legacy) → default. */\nexport function resolveNavDisplay(): NavDisplayPreference {\n return readNavDisplayCookie() ?? readNavDisplayLocal() ?? NAV_DISPLAY_DEFAULT\n}\n\n// Inline pre-paint script — optional, and the mirror of THEME_PREPAINT_SCRIPT.\n// Nav display has no paint-blocking visual (the frame is client-rendered), but\n// seeding localStorage from the cookie before hydration keeps the first render\n// on the synced value instead of the default. Self-contained and silent.\nexport const NAV_DISPLAY_PREPAINT_SCRIPT = `(function(){try{\nvar KEY=${JSON.stringify(NAV_DISPLAY_STORAGE_KEY)};\nvar VALID=[\"menu\",\"tabs\",\"both\"];\nfunction fromCookie(){try{var m=document.cookie.match(/(?:^|;\\\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}\nvar cookie=fromCookie();\nif(cookie&&VALID.indexOf(cookie)!==-1){var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}\nif(cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}}\n}catch(e){}})();`\n"]}
@@ -54,6 +54,12 @@ var CORE_APP_ORIGIN = "https://app.politogyvrm.com";
54
54
  var CORE_BILLING_PATH = "/organization-settings/subscription-billing/text-email";
55
55
  var CORE_BILLING_URL = `${CORE_APP_ORIGIN}${CORE_BILLING_PATH}`;
56
56
  var QUARANTINED_CONTACTS_URL = `${CORE_APP_ORIGIN}/relationship/contacts/quarantined`;
57
+ function resolveOrgLogoUrl(url) {
58
+ const raw = url?.trim();
59
+ if (!raw) return void 0;
60
+ if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(raw)) return raw;
61
+ return `${CORE_APP_ORIGIN}${raw.startsWith("/") ? "" : "/"}${raw}`;
62
+ }
57
63
  function topBarIconButtonClass(tone = "default") {
58
64
  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)]";
59
65
  return [
@@ -1492,6 +1498,7 @@ function NavSidebar({
1492
1498
  const activeFn = nav.isActive ?? isNavItemActive;
1493
1499
  const ModeIcon = mode?.icon;
1494
1500
  const orgBg = org?.brandColor || ORG_PILL_DEFAULT;
1501
+ const orgLogoSrc = resolveOrgLogoUrl(org?.logoUrl);
1495
1502
  const activeGroupKey = useMemo(() => {
1496
1503
  for (let i = 0; i < nav.items.length; i++) {
1497
1504
  const item = nav.items[i];
@@ -1559,7 +1566,7 @@ function NavSidebar({
1559
1566
  {
1560
1567
  style: { backgroundColor: orgBg },
1561
1568
  className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
1562
- children: org.logoUrl ? /* @__PURE__ */ jsx("img", { src: org.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
1569
+ children: orgLogoSrc ? /* @__PURE__ */ jsx("img", { src: orgLogoSrc, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
1563
1570
  }
1564
1571
  ),
1565
1572
  /* @__PURE__ */ jsx(
@@ -1594,7 +1601,14 @@ function NavSidebar({
1594
1601
  {
1595
1602
  style: { backgroundColor: o.brandColor || ORG_PILL_DEFAULT },
1596
1603
  className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-full text-[10px] font-bold text-white",
1597
- children: o.logoUrl ? /* @__PURE__ */ jsx("img", { src: o.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (o.name ?? "Org").slice(0, 2).toUpperCase()
1604
+ children: resolveOrgLogoUrl(o.logoUrl) ? /* @__PURE__ */ jsx(
1605
+ "img",
1606
+ {
1607
+ src: resolveOrgLogoUrl(o.logoUrl),
1608
+ alt: "",
1609
+ className: "h-full w-full object-cover"
1610
+ }
1611
+ ) : (o.name ?? "Org").slice(0, 2).toUpperCase()
1598
1612
  }
1599
1613
  ),
1600
1614
  /* @__PURE__ */ jsx("span", { title: o.name ?? o.id, className: "truncate", children: o.name ?? o.id.slice(0, 8) })
@@ -1616,7 +1630,7 @@ function NavSidebar({
1616
1630
  {
1617
1631
  style: { backgroundColor: orgBg },
1618
1632
  className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
1619
- children: org.logoUrl ? /* @__PURE__ */ jsx("img", { src: org.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
1633
+ children: orgLogoSrc ? /* @__PURE__ */ jsx("img", { src: orgLogoSrc, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
1620
1634
  }
1621
1635
  ),
1622
1636
  /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold", children: org.name ?? "No organizations" })
@@ -2490,6 +2504,6 @@ function NavAppShell({
2490
2504
  ] });
2491
2505
  }
2492
2506
 
2493
- export { CAMPAIGN_FEATURE_AVAILABILITY, CORE_APP_ORIGIN, CORE_BILLING_PATH, FuelAction, NavAppShell, NavBetaBanner, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SupportAction, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, standardPolitogyNav, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeFetcher, useThemePreference };
2494
- //# sourceMappingURL=chunk-LIGKGXFT.js.map
2495
- //# sourceMappingURL=chunk-LIGKGXFT.js.map
2507
+ export { CAMPAIGN_FEATURE_AVAILABILITY, CORE_APP_ORIGIN, CORE_BILLING_PATH, FuelAction, NavAppShell, NavBetaBanner, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SupportAction, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, resolveOrgLogoUrl, standardPolitogyNav, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeFetcher, useThemePreference };
2508
+ //# sourceMappingURL=chunk-CGSS2PDG.js.map
2509
+ //# sourceMappingURL=chunk-CGSS2PDG.js.map