@the-portland-company/shell 0.24.0 → 0.26.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 +1 @@
1
- {"version":3,"sources":["../src/app-chrome/index.tsx"],"names":[],"mappings":"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","file":"app-chrome.js","sourcesContent":["// ---------------------------------------------------------------------------\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 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 FiMessageSquare,\n FiSend,\n FiSettings,\n FiShare2,\n FiUpload,\n FiUser,\n FiUsers,\n} from 'react-icons/fi'\nimport { useNavChromeFetcher } from '../native/index'\nimport type { StandardNavIconKey } from '../native/standardNav'\nimport type { NavIcon, 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}\n\nconst ORG_STORAGE_KEY = 'vrm.activeOrg'\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 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 } = useChromeOrgs()\n const [orgId, setOrgIdState] = useState<string | null>(null)\n const [navDisplay, setNavDisplayState] = useState<NavDisplayMode>('both')\n\n // Boot: restore persisted choices.\n useEffect(() => {\n try {\n const storedOrg = 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 }, [])\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 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 navDisplay,\n setNavDisplay,\n }\n }, [controlledOrg, orgId, setOrgId, orgs, navDisplay, setNavDisplay])\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\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 return (\n <DevNotesProvider adapter={adapter} user={user} config={config}>\n {children}\n </DevNotesProvider>\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 <DevNotesMenu />\n </TopBarWidgetSlot>\n <SupportAction />\n <NotificationsAction orgId={state.orgId} />\n </>\n )\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 * 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.slice(-6)}` : ''\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 (HITL 2026-07-23).\n id: 'petition',\n label: 'Petition Mode',\n icon: FiFlag,\n href: 'https://petitions.politogyvrm.com/',\n orgQuery: true,\n },\n]\n\nexport function useChromeMode(currentModeId: 'relationship' | 'campaign' | 'petition' = 'relationship'): NavMode {\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\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 current = MODE_DEFS.find((m) => m.id === currentModeId) ?? MODE_DEFS[0]\n const options = MODE_DEFS.filter((m) => entitled?.[m.id])\n const suffix = orgId ? `?org=${orgId.slice(-6)}` : ''\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 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"]}
1
+ {"version":3,"sources":["../src/lib/breadcrumbs.ts","../src/lib/identity.ts","../src/app-chrome/frame.tsx","../src/app-chrome/index.tsx"],"names":["trail","useMemo","jsx"],"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 { useMemo, type ReactNode } from 'react'\n\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 { NavChromeFetcher, NavItem } from '../native/index'\nimport { deriveBreadcrumbs } from '../lib/breadcrumbs'\n\n/** Every app that renders the shared chrome. Keyed by its subdomain. */\nexport type PolitogyAppKey = 'vrm' | 'contacts' | 'forms' | 'surveys' | 'polls' | 'petitions'\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 }\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: { title: 'Petitions', origin: 'https://petitions.politogyvrm.com', storagePrefix: 'petitions' },\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\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 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 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 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 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 buildMeta?: PolitogyBuildMeta\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n const chrome = useChromeState()\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 userMenu = useChromeUserMenu({\n userId: session.userId,\n userName: identity.userName,\n email: identity.email,\n })\n const footer = useChromeFooter(buildMeta ?? {})\n // Entitlement + user-selection driven. No app passes a mode.\n const mode = useChromeMode()\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 const navItems = 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 }),\n [data?.contactsItem],\n )\n\n // Dashboard › Sub › Item, derived from the same tree the sidebar renders.\n const breadcrumbs = useMemo(\n () => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),\n [navItems, pathname, search, meta.origin],\n )\n\n return (\n <NavAppShell\n sidebar={{\n pathname,\n brand: {\n logoSrc: '/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 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 : { name: session.accountName, id: session.accountId },\n mode,\n footer,\n linkComponent,\n }}\n betaBanner={{}}\n topBar={{\n 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,\n actions: <ChromeTopBarActions />,\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 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 FiMessageSquare,\n FiSend,\n FiSettings,\n FiShare2,\n FiUpload,\n FiUser,\n FiUsers,\n} from 'react-icons/fi'\nimport { useChromeMe, useNavChromeFetcher } from '../native/index'\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}\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 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 } = useChromeOrgs()\n const [orgId, setOrgIdState] = useState<string | null>(null)\n const [navDisplay, setNavDisplayState] = useState<NavDisplayMode>('both')\n\n // Boot: restore persisted choices.\n useEffect(() => {\n try {\n const storedOrg = 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 }, [])\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 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 navDisplay,\n setNavDisplay,\n }\n }, [controlledOrg, orgId, setOrgId, orgs, navDisplay, setNavDisplay])\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\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 return (\n <DevNotesProvider adapter={adapter} user={user} config={config}>\n {children}\n </DevNotesProvider>\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 <DevNotesMenu />\n </TopBarWidgetSlot>\n <SupportAction />\n <NotificationsAction orgId={state.orgId} />\n </>\n )\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.slice(-6)}` : ''\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 (HITL 2026-07-23).\n id: 'petition',\n label: 'Petition Mode',\n icon: FiFlag,\n href: 'https://petitions.politogyvrm.com/',\n orgQuery: true,\n },\n]\n\n/**\n * The active mode pill / selector.\n *\n * The current mode is the USER'S OWN SELECTION (persisted cross-app in\n * localStorage under `vrm.activeMode`), narrowed to what the active org is\n * 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 optional\n * argument now only supplies the pre-entitlement fallback for first paint.\n */\nexport function useChromeMode(\n fallbackModeId: 'relationship' | 'campaign' | 'petition' = 'relationship',\n): NavMode {\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: the user's selection (if the org is still entitled to it)\n // → the first entitled mode → the app's fallback. Entitlements are async, so\n // before they land the fallback holds the pill's shape (no layout shift).\n const selected = selectedModeId\n ? options.find((m) => m.id === selectedModeId) ?? null\n : null\n const current =\n selected ??\n options[0] ??\n MODE_DEFS.find((m) => m.id === fallbackModeId) ??\n MODE_DEFS[0]\n const suffix = orgId ? `?org=${orgId.slice(-6)}` : ''\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 PolitogyAppSession,\n type PolitogyBuildMeta,\n type PolitogyFrameData,\n} from './frame'\n"]}
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ // src/app-icons.ts
4
+ var SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
5
+ var VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
6
+ function pick(icons, ...keys) {
7
+ if (!icons) return null;
8
+ for (const key of keys) {
9
+ const hit = icons[key]?.url;
10
+ if (hit) return hit;
11
+ }
12
+ return null;
13
+ }
14
+ async function politogyAppIcons(app, init) {
15
+ const base = init?.baseUrl ?? SPECS_ICONS_BASE;
16
+ const revalidate = init?.revalidateSeconds ?? 60;
17
+ const doFetch = init?.fetchImpl ?? globalThis.fetch;
18
+ try {
19
+ const res = await doFetch(`${base}/${encodeURIComponent(app)}`, {
20
+ // `next.revalidate` is ignored by non-Next fetch implementations.
21
+ next: { revalidate }
22
+ });
23
+ if (!res.ok) throw new Error(`icons ${app}: ${res.status}`);
24
+ const data = await res.json();
25
+ return toMetadataIcons(data);
26
+ } catch {
27
+ return {
28
+ icon: [{ url: VRM_FALLBACK_ICON }],
29
+ shortcut: [{ url: VRM_FALLBACK_ICON }],
30
+ apple: [{ url: VRM_FALLBACK_ICON }]
31
+ };
32
+ }
33
+ }
34
+ function toMetadataIcons(data) {
35
+ const icons = data?.icons;
36
+ const favicon = pick(icons, "favicon-color-square", "favicon-color-round") ?? VRM_FALLBACK_ICON;
37
+ const apple = pick(icons, "ios-color-square", "ios-color-round", "favicon-color-square") ?? favicon;
38
+ return {
39
+ icon: [{ url: favicon }],
40
+ shortcut: [{ url: favicon }],
41
+ apple: [{ url: apple }]
42
+ };
43
+ }
44
+
45
+ exports.SPECS_ICONS_BASE = SPECS_ICONS_BASE;
46
+ exports.VRM_FALLBACK_ICON = VRM_FALLBACK_ICON;
47
+ exports.politogyAppIcons = politogyAppIcons;
48
+ exports.toMetadataIcons = toMetadataIcons;
49
+ //# sourceMappingURL=app-icons.cjs.map
50
+ //# sourceMappingURL=app-icons.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app-icons.ts"],"names":[],"mappings":";;;AAkBO,IAAM,gBAAA,GAAmB;AAQzB,IAAM,iBAAA,GACX;AAkBF,SAAS,IAAA,CACP,UACG,IAAA,EACY;AACf,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,GAAG,CAAA,EAAG,GAAA;AACxB,IAAA,IAAI,KAAK,OAAO,GAAA;AAAA,EAClB;AACA,EAAA,OAAO,IAAA;AACT;AASA,eAAsB,gBAAA,CACpB,KACA,IAAA,EACgC;AAChC,EAAA,MAAM,IAAA,GAAO,MAAM,OAAA,IAAW,gBAAA;AAC9B,EAAA,MAAM,UAAA,GAAa,MAAM,iBAAA,IAAqB,EAAA;AAC9C,EAAA,MAAM,OAAA,GAAU,IAAA,EAAM,SAAA,IAAa,UAAA,CAAW,KAAA;AAC9C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,kBAAA,CAAmB,GAAG,CAAC,CAAA,CAAA,EAAI;AAAA;AAAA,MAE9D,IAAA,EAAM,EAAE,UAAA;AAAW,KACL,CAAA;AAChB,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,MAAA,EAAS,GAAG,CAAA,EAAA,EAAK,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC1D,IAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,IAAA,OAAO,gBAAgB,IAAI,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,CAAC,EAAE,GAAA,EAAK,mBAAmB,CAAA;AAAA,MACjC,QAAA,EAAU,CAAC,EAAE,GAAA,EAAK,mBAAmB,CAAA;AAAA,MACrC,KAAA,EAAO,CAAC,EAAE,GAAA,EAAK,mBAAmB;AAAA,KACpC;AAAA,EACF;AACF;AAGO,SAAS,gBAAgB,IAAA,EAA2D;AACzF,EAAA,MAAM,QAAQ,IAAA,EAAM,KAAA;AACpB,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,EAAO,sBAAA,EAAwB,qBAAqB,CAAA,IAAK,iBAAA;AAC9E,EAAA,MAAM,QACJ,IAAA,CAAK,KAAA,EAAO,kBAAA,EAAoB,iBAAA,EAAmB,sBAAsB,CAAA,IAAK,OAAA;AAChF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,CAAC,EAAE,GAAA,EAAK,SAAS,CAAA;AAAA,IACvB,QAAA,EAAU,CAAC,EAAE,GAAA,EAAK,SAAS,CAAA;AAAA,IAC3B,KAAA,EAAO,CAAC,EAAE,GAAA,EAAK,OAAO;AAAA,GACxB;AACF","file":"app-icons.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// App icons — favicons and platform icons resolved from the Brand API at\n// RUNTIME, never hand-dropped as static files in an app repo (Global Rules →\n// \"App Icons — Favicons & Browser Icons\").\n//\n// `GET https://specs.politogyvrm.com/api/icons/<app>` returns the FULLY\n// RESOLVED variant grid: the server already walks the fallback chain (exact\n// variant → same-platform color/square → color fallback → the app's base\n// favicon → the global platform icon → the VRM Core logo), and marks each entry\n// `fallback: true` when it is inherited rather than app-specific. So an app that\n// has never uploaded its own icon still gets the VRM logo — which is exactly the\n// requested behavior (HITL 2026-07-23), with no per-app defaulting logic.\n//\n// Server-safe: no React, no client deps. Next apps call this from\n// `generateMetadata()` so the icon updates when Brand changes, without a\n// redeploy of the app.\n// ---------------------------------------------------------------------------\n\nexport const SPECS_ICONS_BASE = 'https://specs.politogyvrm.com/api/icons'\n\n/**\n * The VRM Core square logo — last-resort icon used only when the Brand API is\n * unreachable (the API's own fallback chain already ends here). Taken from the\n * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens\n * link keeps the live value authoritative everywhere else.\n */\nexport const VRM_FALLBACK_ICON =\n 'https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png'\n\nexport type PolitogyIconVariant = { url: string; fallback?: boolean }\n\nexport type PolitogyIconsResponse = {\n app: string\n version?: number\n updatedAt?: string\n icons: Record<string, PolitogyIconVariant>\n}\n\n/** Next.js `metadata.icons` shape (a subset — url/rel entries). */\nexport type PolitogyMetadataIcons = {\n icon: { url: string }[]\n shortcut?: { url: string }[]\n apple?: { url: string }[]\n}\n\nfunction pick(\n icons: Record<string, PolitogyIconVariant> | undefined,\n ...keys: string[]\n): string | null {\n if (!icons) return null\n for (const key of keys) {\n const hit = icons[key]?.url\n if (hit) return hit\n }\n return null\n}\n\n/**\n * Fetch the resolved icon set for an app and shape it for Next `metadata`.\n *\n * Cached for 60s with revalidation, matching the tokens contract — a Brand edit\n * shows up promptly without defeating caching. Never throws: on any failure the\n * VRM Core logo is returned, so an app can call this unguarded in metadata.\n */\nexport async function politogyAppIcons(\n app: string,\n init?: { baseUrl?: string; revalidateSeconds?: number; fetchImpl?: typeof globalThis.fetch },\n): Promise<PolitogyMetadataIcons> {\n const base = init?.baseUrl ?? SPECS_ICONS_BASE\n const revalidate = init?.revalidateSeconds ?? 60\n const doFetch = init?.fetchImpl ?? globalThis.fetch\n try {\n const res = await doFetch(`${base}/${encodeURIComponent(app)}`, {\n // `next.revalidate` is ignored by non-Next fetch implementations.\n next: { revalidate },\n } as RequestInit)\n if (!res.ok) throw new Error(`icons ${app}: ${res.status}`)\n const data = (await res.json()) as PolitogyIconsResponse\n return toMetadataIcons(data)\n } catch {\n return {\n icon: [{ url: VRM_FALLBACK_ICON }],\n shortcut: [{ url: VRM_FALLBACK_ICON }],\n apple: [{ url: VRM_FALLBACK_ICON }],\n }\n }\n}\n\n/** Pure shaping step — exported for tests and for callers with their own fetch. */\nexport function toMetadataIcons(data: PolitogyIconsResponse | null): PolitogyMetadataIcons {\n const icons = data?.icons\n const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round') ?? VRM_FALLBACK_ICON\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n return {\n icon: [{ url: favicon }],\n shortcut: [{ url: favicon }],\n apple: [{ url: apple }],\n }\n}\n"]}
@@ -0,0 +1,46 @@
1
+ declare const SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
2
+ /**
3
+ * The VRM Core square logo — last-resort icon used only when the Brand API is
4
+ * unreachable (the API's own fallback chain already ends here). Taken from the
5
+ * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens
6
+ * link keeps the live value authoritative everywhere else.
7
+ */
8
+ declare const VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
9
+ type PolitogyIconVariant = {
10
+ url: string;
11
+ fallback?: boolean;
12
+ };
13
+ type PolitogyIconsResponse = {
14
+ app: string;
15
+ version?: number;
16
+ updatedAt?: string;
17
+ icons: Record<string, PolitogyIconVariant>;
18
+ };
19
+ /** Next.js `metadata.icons` shape (a subset — url/rel entries). */
20
+ type PolitogyMetadataIcons = {
21
+ icon: {
22
+ url: string;
23
+ }[];
24
+ shortcut?: {
25
+ url: string;
26
+ }[];
27
+ apple?: {
28
+ url: string;
29
+ }[];
30
+ };
31
+ /**
32
+ * Fetch the resolved icon set for an app and shape it for Next `metadata`.
33
+ *
34
+ * Cached for 60s with revalidation, matching the tokens contract — a Brand edit
35
+ * shows up promptly without defeating caching. Never throws: on any failure the
36
+ * VRM Core logo is returned, so an app can call this unguarded in metadata.
37
+ */
38
+ declare function politogyAppIcons(app: string, init?: {
39
+ baseUrl?: string;
40
+ revalidateSeconds?: number;
41
+ fetchImpl?: typeof globalThis.fetch;
42
+ }): Promise<PolitogyMetadataIcons>;
43
+ /** Pure shaping step — exported for tests and for callers with their own fetch. */
44
+ declare function toMetadataIcons(data: PolitogyIconsResponse | null): PolitogyMetadataIcons;
45
+
46
+ export { type PolitogyIconVariant, type PolitogyIconsResponse, type PolitogyMetadataIcons, SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
@@ -0,0 +1,46 @@
1
+ declare const SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
2
+ /**
3
+ * The VRM Core square logo — last-resort icon used only when the Brand API is
4
+ * unreachable (the API's own fallback chain already ends here). Taken from the
5
+ * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens
6
+ * link keeps the live value authoritative everywhere else.
7
+ */
8
+ declare const VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
9
+ type PolitogyIconVariant = {
10
+ url: string;
11
+ fallback?: boolean;
12
+ };
13
+ type PolitogyIconsResponse = {
14
+ app: string;
15
+ version?: number;
16
+ updatedAt?: string;
17
+ icons: Record<string, PolitogyIconVariant>;
18
+ };
19
+ /** Next.js `metadata.icons` shape (a subset — url/rel entries). */
20
+ type PolitogyMetadataIcons = {
21
+ icon: {
22
+ url: string;
23
+ }[];
24
+ shortcut?: {
25
+ url: string;
26
+ }[];
27
+ apple?: {
28
+ url: string;
29
+ }[];
30
+ };
31
+ /**
32
+ * Fetch the resolved icon set for an app and shape it for Next `metadata`.
33
+ *
34
+ * Cached for 60s with revalidation, matching the tokens contract — a Brand edit
35
+ * shows up promptly without defeating caching. Never throws: on any failure the
36
+ * VRM Core logo is returned, so an app can call this unguarded in metadata.
37
+ */
38
+ declare function politogyAppIcons(app: string, init?: {
39
+ baseUrl?: string;
40
+ revalidateSeconds?: number;
41
+ fetchImpl?: typeof globalThis.fetch;
42
+ }): Promise<PolitogyMetadataIcons>;
43
+ /** Pure shaping step — exported for tests and for callers with their own fetch. */
44
+ declare function toMetadataIcons(data: PolitogyIconsResponse | null): PolitogyMetadataIcons;
45
+
46
+ export { type PolitogyIconVariant, type PolitogyIconsResponse, type PolitogyMetadataIcons, SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
@@ -0,0 +1,45 @@
1
+ // src/app-icons.ts
2
+ var SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
3
+ var VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
4
+ function pick(icons, ...keys) {
5
+ if (!icons) return null;
6
+ for (const key of keys) {
7
+ const hit = icons[key]?.url;
8
+ if (hit) return hit;
9
+ }
10
+ return null;
11
+ }
12
+ async function politogyAppIcons(app, init) {
13
+ const base = init?.baseUrl ?? SPECS_ICONS_BASE;
14
+ const revalidate = init?.revalidateSeconds ?? 60;
15
+ const doFetch = init?.fetchImpl ?? globalThis.fetch;
16
+ try {
17
+ const res = await doFetch(`${base}/${encodeURIComponent(app)}`, {
18
+ // `next.revalidate` is ignored by non-Next fetch implementations.
19
+ next: { revalidate }
20
+ });
21
+ if (!res.ok) throw new Error(`icons ${app}: ${res.status}`);
22
+ const data = await res.json();
23
+ return toMetadataIcons(data);
24
+ } catch {
25
+ return {
26
+ icon: [{ url: VRM_FALLBACK_ICON }],
27
+ shortcut: [{ url: VRM_FALLBACK_ICON }],
28
+ apple: [{ url: VRM_FALLBACK_ICON }]
29
+ };
30
+ }
31
+ }
32
+ function toMetadataIcons(data) {
33
+ const icons = data?.icons;
34
+ const favicon = pick(icons, "favicon-color-square", "favicon-color-round") ?? VRM_FALLBACK_ICON;
35
+ const apple = pick(icons, "ios-color-square", "ios-color-round", "favicon-color-square") ?? favicon;
36
+ return {
37
+ icon: [{ url: favicon }],
38
+ shortcut: [{ url: favicon }],
39
+ apple: [{ url: apple }]
40
+ };
41
+ }
42
+
43
+ export { SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
44
+ //# sourceMappingURL=app-icons.js.map
45
+ //# sourceMappingURL=app-icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/app-icons.ts"],"names":[],"mappings":";AAkBO,IAAM,gBAAA,GAAmB;AAQzB,IAAM,iBAAA,GACX;AAkBF,SAAS,IAAA,CACP,UACG,IAAA,EACY;AACf,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,GAAG,CAAA,EAAG,GAAA;AACxB,IAAA,IAAI,KAAK,OAAO,GAAA;AAAA,EAClB;AACA,EAAA,OAAO,IAAA;AACT;AASA,eAAsB,gBAAA,CACpB,KACA,IAAA,EACgC;AAChC,EAAA,MAAM,IAAA,GAAO,MAAM,OAAA,IAAW,gBAAA;AAC9B,EAAA,MAAM,UAAA,GAAa,MAAM,iBAAA,IAAqB,EAAA;AAC9C,EAAA,MAAM,OAAA,GAAU,IAAA,EAAM,SAAA,IAAa,UAAA,CAAW,KAAA;AAC9C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,kBAAA,CAAmB,GAAG,CAAC,CAAA,CAAA,EAAI;AAAA;AAAA,MAE9D,IAAA,EAAM,EAAE,UAAA;AAAW,KACL,CAAA;AAChB,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,MAAA,EAAS,GAAG,CAAA,EAAA,EAAK,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC1D,IAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,IAAA,OAAO,gBAAgB,IAAI,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,CAAC,EAAE,GAAA,EAAK,mBAAmB,CAAA;AAAA,MACjC,QAAA,EAAU,CAAC,EAAE,GAAA,EAAK,mBAAmB,CAAA;AAAA,MACrC,KAAA,EAAO,CAAC,EAAE,GAAA,EAAK,mBAAmB;AAAA,KACpC;AAAA,EACF;AACF;AAGO,SAAS,gBAAgB,IAAA,EAA2D;AACzF,EAAA,MAAM,QAAQ,IAAA,EAAM,KAAA;AACpB,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,EAAO,sBAAA,EAAwB,qBAAqB,CAAA,IAAK,iBAAA;AAC9E,EAAA,MAAM,QACJ,IAAA,CAAK,KAAA,EAAO,kBAAA,EAAoB,iBAAA,EAAmB,sBAAsB,CAAA,IAAK,OAAA;AAChF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,CAAC,EAAE,GAAA,EAAK,SAAS,CAAA;AAAA,IACvB,QAAA,EAAU,CAAC,EAAE,GAAA,EAAK,SAAS,CAAA;AAAA,IAC3B,KAAA,EAAO,CAAC,EAAE,GAAA,EAAK,OAAO;AAAA,GACxB;AACF","file":"app-icons.js","sourcesContent":["// ---------------------------------------------------------------------------\n// App icons — favicons and platform icons resolved from the Brand API at\n// RUNTIME, never hand-dropped as static files in an app repo (Global Rules →\n// \"App Icons — Favicons & Browser Icons\").\n//\n// `GET https://specs.politogyvrm.com/api/icons/<app>` returns the FULLY\n// RESOLVED variant grid: the server already walks the fallback chain (exact\n// variant → same-platform color/square → color fallback → the app's base\n// favicon → the global platform icon → the VRM Core logo), and marks each entry\n// `fallback: true` when it is inherited rather than app-specific. So an app that\n// has never uploaded its own icon still gets the VRM logo — which is exactly the\n// requested behavior (HITL 2026-07-23), with no per-app defaulting logic.\n//\n// Server-safe: no React, no client deps. Next apps call this from\n// `generateMetadata()` so the icon updates when Brand changes, without a\n// redeploy of the app.\n// ---------------------------------------------------------------------------\n\nexport const SPECS_ICONS_BASE = 'https://specs.politogyvrm.com/api/icons'\n\n/**\n * The VRM Core square logo — last-resort icon used only when the Brand API is\n * unreachable (the API's own fallback chain already ends here). Taken from the\n * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens\n * link keeps the live value authoritative everywhere else.\n */\nexport const VRM_FALLBACK_ICON =\n 'https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png'\n\nexport type PolitogyIconVariant = { url: string; fallback?: boolean }\n\nexport type PolitogyIconsResponse = {\n app: string\n version?: number\n updatedAt?: string\n icons: Record<string, PolitogyIconVariant>\n}\n\n/** Next.js `metadata.icons` shape (a subset — url/rel entries). */\nexport type PolitogyMetadataIcons = {\n icon: { url: string }[]\n shortcut?: { url: string }[]\n apple?: { url: string }[]\n}\n\nfunction pick(\n icons: Record<string, PolitogyIconVariant> | undefined,\n ...keys: string[]\n): string | null {\n if (!icons) return null\n for (const key of keys) {\n const hit = icons[key]?.url\n if (hit) return hit\n }\n return null\n}\n\n/**\n * Fetch the resolved icon set for an app and shape it for Next `metadata`.\n *\n * Cached for 60s with revalidation, matching the tokens contract — a Brand edit\n * shows up promptly without defeating caching. Never throws: on any failure the\n * VRM Core logo is returned, so an app can call this unguarded in metadata.\n */\nexport async function politogyAppIcons(\n app: string,\n init?: { baseUrl?: string; revalidateSeconds?: number; fetchImpl?: typeof globalThis.fetch },\n): Promise<PolitogyMetadataIcons> {\n const base = init?.baseUrl ?? SPECS_ICONS_BASE\n const revalidate = init?.revalidateSeconds ?? 60\n const doFetch = init?.fetchImpl ?? globalThis.fetch\n try {\n const res = await doFetch(`${base}/${encodeURIComponent(app)}`, {\n // `next.revalidate` is ignored by non-Next fetch implementations.\n next: { revalidate },\n } as RequestInit)\n if (!res.ok) throw new Error(`icons ${app}: ${res.status}`)\n const data = (await res.json()) as PolitogyIconsResponse\n return toMetadataIcons(data)\n } catch {\n return {\n icon: [{ url: VRM_FALLBACK_ICON }],\n shortcut: [{ url: VRM_FALLBACK_ICON }],\n apple: [{ url: VRM_FALLBACK_ICON }],\n }\n }\n}\n\n/** Pure shaping step — exported for tests and for callers with their own fetch. */\nexport function toMetadataIcons(data: PolitogyIconsResponse | null): PolitogyMetadataIcons {\n const icons = data?.icons\n const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round') ?? VRM_FALLBACK_ICON\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n return {\n icon: [{ url: favicon }],\n shortcut: [{ url: favicon }],\n apple: [{ url: apple }],\n }\n}\n"]}
package/dist/chrome.css CHANGED
@@ -144,13 +144,6 @@
144
144
  --tab-gap: 8px;
145
145
  --tab-radius: 12px;
146
146
 
147
- /* Beta banner — a semantic amber NOTICE strip (not a chrome surface) that
148
- sits directly below the top bar. Kept amber in both themes; dark tunes to
149
- a dark-amber tint that sits on the neutral-black chrome (never blue). */
150
- --beta-banner-bg: #FDF6DF;
151
- --beta-banner-border: #EFE3B0;
152
- --beta-banner-text: #92720E;
153
-
154
147
  /* Sidebar (nav chrome) */
155
148
  --sidebar-width: 240px;
156
149
  --sidebar-bg: #FFFFFF;
@@ -163,7 +156,7 @@
163
156
  --sidebar-item-gap: 2px;
164
157
  --sidebar-text: #2F3136;
165
158
  --sidebar-text-muted: #929599;
166
- --sidebar-hover-bg: #ECEEEF;
159
+ --sidebar-hover-bg: #2F3136;
167
160
  --sidebar-active-text: #FFFFFF;
168
161
  --sidebar-scrollbar-thumb: #CBD5E0;
169
162
 
@@ -258,12 +251,31 @@
258
251
  --muted: var(--topbar-breadcrumb-color);
259
252
  --panel: var(--topbar-bg);
260
253
  --panel-elevated: var(--surface-1);
261
- /* The left bar's own surface. Separate from --panel-elevated (which also
262
- backs hover states and dropdowns) so dark can put the bars and the raised
263
- surfaces on different steps of the neutral ramp. */
264
- --nav-surface: var(--surface-1);
265
254
  --line: var(--sidebar-border);
266
255
  --shadow-soft: var(--shadow-lg);
256
+
257
+ /* App-surface aliases — the names feature apps' own CSS uses for
258
+ page/panel/border/text/accent. Emitted HERE so no app has to
259
+ declare them: every app used to define its own fallback palette
260
+ in globals.css and they drifted (surveys' accent was #C8102E
261
+ where every other app was #E63946; --tab-content-bg resolved
262
+ #1F1F1F on forms and #161616 on polls). Apps must consume these,
263
+ never redeclare them (HITL 2026-07-23). Both spellings of the
264
+ surface pair are emitted because apps historically used both. */
265
+ --color-bg: var(--surface-2, var(--color-layer-1));
266
+ --color-surface: var(--surface-1, var(--color-layer-3));
267
+ --color-surface-2: var(--color-layer-2);
268
+ --color-panel: var(--color-surface);
269
+ --color-panel-2: var(--color-surface-2);
270
+ --color-border: var(--line);
271
+ --color-text: var(--foreground);
272
+ --color-text-muted: var(--muted);
273
+ --color-muted: var(--muted);
274
+ --color-accent: var(--color-race-red);
275
+ --color-accent-hover: var(--color-race-red-600);
276
+ --color-accent-contrast: var(--color-white);
277
+ --skeleton-base: var(--color-surface-2);
278
+ --skeleton-shine: var(--color-layer-1);
267
279
  }
268
280
  .dark {
269
281
  --background: 222.2 84% 4.9%;
@@ -285,137 +297,22 @@
285
297
  --border: 217.2 32.6% 17.5%;
286
298
  --input: 217.2 32.6% 17.5%;
287
299
  --ring: 212.7 26.8% 83.9%;
288
- /* Native chrome semantic layer — dark values.
289
- Global Rules: dark chrome is NEUTRAL BLACK, never blue/navy/slate. These
290
- were ShadCN slate (hsl(222.2 84% 4.9%) = #020817, hsl(217.2 32.6% 17.5%)
291
- = #1E293B) — the exact palette the rule names as forbidden — which made
292
- the top bar and left bar read as navy. The ramp below is the hue-free one
293
- from Reference/Global-Rules: page #0A0A0A → bars #161616 → raised #1F1F1F
294
- → hover #2B2B2B → borders #333333, with #A3A3A3 muted / #E8E8E8 body text.
295
- The ShadCN HSL channel tokens above stay as-is — they are the separate
296
- darkMode group and are Brand-API-controlled. */
297
- --background: #0A0A0A;
298
- --foreground: #E8E8E8;
299
- --muted: #A3A3A3;
300
- --panel: #161616;
301
- --nav-surface: #161616;
302
- --panel-elevated: #1F1F1F;
303
- --line: #333333;
300
+ --background: hsl(222.2 84% 4.9%);
301
+ --foreground: hsl(210 40% 98%);
302
+ --muted: hsl(215 20.2% 65.1%);
303
+ --panel: hsl(222.2 84% 4.9%);
304
+ --panel-elevated: hsl(217.2 32.6% 17.5%);
305
+ --line: hsl(217.2 32.6% 17.5%);
304
306
  --shadow-soft: 0 4px 16px rgba(0, 0, 0, 0.5);
305
- /* Beta notice — dark-amber tint that reads on the neutral-black chrome. */
306
- --beta-banner-bg: #2E2710;
307
- --beta-banner-border: #4A3F1C;
308
- --beta-banner-text: #E0C56E;
309
- }
310
-
311
- /* ---------------------------------------------------------------------------
312
- Top-bar widget slot — normalizes third-party launchers (e.g. DevNotes) to
313
- the universal top-bar icon treatment (Global Rules: every icon the exact
314
- same size, every badge the exact same style; no slot ships its own look).
315
- Wrap the widget in `.topbar-widget-slot` (shell exports <TopBarWidgetSlot>).
316
- --------------------------------------------------------------------------- */
317
- .topbar-widget-slot > button,
318
- .topbar-widget-slot > div > button:first-of-type,
319
- .topbar-widget-slot > span > button:first-of-type {
320
- width: var(--topbar-icon-button-size, 32px);
321
- height: var(--topbar-icon-button-size, 32px);
322
- display: inline-flex;
323
- align-items: center;
324
- justify-content: center;
325
- border: 1px solid var(--line);
326
- border-radius: 9999px;
327
- background: transparent;
328
- color: var(--topbar-icon-color, var(--muted));
329
- opacity: 1;
330
- position: relative;
331
- transition: background-color 0.15s ease, color 0.15s ease;
332
- }
333
- .topbar-widget-slot > button:hover,
334
- .topbar-widget-slot > div > button:first-of-type:hover,
335
- .topbar-widget-slot > span > button:first-of-type:hover {
336
- background: var(--topbar-icon-hover-bg, transparent);
337
- color: var(--topbar-icon-hover-color, var(--foreground));
338
- }
339
- .topbar-widget-slot button svg {
340
- width: var(--topbar-icon-size, 20px);
341
- height: var(--topbar-icon-size, 20px);
342
- opacity: 1;
343
- }
344
- /* Match <TopBarCountBadge>: same offset, size, fill and type as every count. */
345
- .topbar-widget-slot button [class*="rounded-full"][class*="bg-"],
346
- .topbar-widget-slot button .dn-badge {
347
- position: absolute;
348
- top: -4px;
349
- right: -4px;
350
- height: 1rem;
351
- min-width: 1rem;
352
- display: inline-flex;
353
- align-items: center;
354
- justify-content: center;
355
- padding: 0 0.25rem;
356
- border-radius: 9999px;
357
- background: var(--color-race-red, #e63946);
358
- color: #fff;
359
- font-size: 10px;
360
- font-weight: 400;
361
- line-height: 1;
362
- }
363
-
364
- /* DevNotes (and any widget-slot popover) — dark-theme inheritance. The
365
- package's compiled sheet hardcodes light utility colors (bg-white,
366
- text-gray-*, border-gray-*), so its dropdown rendered light-on-dark. Remap
367
- those inside the slot to the chrome tokens under `.dark`. */
368
- .dark .topbar-widget-slot [class*="bg-white"] {
369
- background-color: var(--panel-elevated);
370
- }
371
- .dark .topbar-widget-slot [class*="text-gray-9"],
372
- .dark .topbar-widget-slot [class*="text-gray-8"],
373
- .dark .topbar-widget-slot [class*="text-gray-7"],
374
- .dark .topbar-widget-slot [class*="text-slate-9"],
375
- .dark .topbar-widget-slot [class*="text-slate-8"] {
376
- color: var(--foreground);
377
- }
378
- .dark .topbar-widget-slot [class*="text-gray-6"],
379
- .dark .topbar-widget-slot [class*="text-gray-5"],
380
- .dark .topbar-widget-slot [class*="text-gray-4"],
381
- .dark .topbar-widget-slot [class*="text-slate-5"] {
382
- color: var(--muted);
383
- }
384
- .dark .topbar-widget-slot [class*="border-gray-"],
385
- .dark .topbar-widget-slot [class*="border-slate-"] {
386
- border-color: var(--line);
387
- }
388
- .dark .topbar-widget-slot [class*="bg-gray-50"],
389
- .dark .topbar-widget-slot [class*="bg-gray-100"],
390
- .dark .topbar-widget-slot [class*="bg-slate-50"],
391
- .dark .topbar-widget-slot [class*="bg-slate-100"] {
392
- background-color: var(--panel);
393
- }
394
- .dark .topbar-widget-slot [class*="hover:bg-gray-"]:hover,
395
- .dark .topbar-widget-slot [class*="hover:bg-slate-"]:hover {
396
- background-color: var(--panel);
397
- }
398
-
399
- /* Badge anchor: DevNotes nests its count inside an inner `relative` span that
400
- wraps only the glyph, so identical offsets anchored to the GLYPH, not the
401
- 32px circle like every other slot. Neutralize inner positioning so the
402
- badge anchors to the (relative) button — one universal badge placement. */
403
- .topbar-widget-slot button > span,
404
- .topbar-widget-slot > div > button:first-of-type > span {
405
- position: static;
406
- }
407
-
408
- /* The dark-theme text remap above targets the widget POPOVER content; the
409
- LAUNCHER glyph must keep the universal icon color (it was picking up the
410
- foreground remap and rendering white/full-opacity vs its siblings). More
411
- specific than the [class*=] remaps, declared after them. */
412
- .dark .topbar-widget-slot > button,
413
- .dark .topbar-widget-slot > div > button:first-of-type,
414
- .dark .topbar-widget-slot > span > button:first-of-type {
415
- color: var(--topbar-icon-color, var(--muted));
416
- }
417
- .dark .topbar-widget-slot > button:hover,
418
- .dark .topbar-widget-slot > div > button:first-of-type:hover,
419
- .dark .topbar-widget-slot > span > button:first-of-type:hover {
420
- color: var(--topbar-icon-hover-color, var(--foreground));
307
+ --color-bg: #0A0A0A;
308
+ --color-surface: #161616;
309
+ --color-surface-2: #1F1F1F;
310
+ --color-panel: #161616;
311
+ --color-panel-2: #1F1F1F;
312
+ --color-border: #333333;
313
+ --color-text: #E8E8E8;
314
+ --color-text-muted: #A3A3A3;
315
+ --color-muted: #A3A3A3;
316
+ --skeleton-base: #212121;
317
+ --skeleton-shine: #2B2B2B;
421
318
  }