@the-portland-company/shell 0.35.4 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 0.36.0 — 2026-07-24
2
+
3
+ ### Added — cross-subdomain active-org + favicon cache-busting
4
+
5
+ `ChromeStateProvider` now seeds the active org from a shared cross-subdomain
6
+ signal (`active-org` module: URL `?org=<uuid>` → `.politogyvrm.com` cookie
7
+ `politogy_active_org`) before falling back to host-only localStorage, and stamps
8
+ `?org=<full-uuid>` into the address bar on every page. Previously each app +
9
+ subdomain restored from its own host-only localStorage and otherwise defaulted
10
+ to the earliest-joined org, so apps diverged onto different orgs and campaign
11
+ apps could land on a Relationship-only org and lock themselves. `setOrgId` now
12
+ writes the shared cookie so the last-used org follows the user across every app,
13
+ and cross-app/core links carry the FULL org uuid (not the lossy last-6 suffix),
14
+ the only collision-free selector for contacts-api membership matching.
15
+
16
+ `toMetadataIcons` stamps the Brand revision onto every favicon URL as
17
+ `?v=<version>` (falling back to `updatedAt`) so an aggressively-cached browser
18
+ picks up a new Brand publish even when the stored filename is unchanged.
19
+
1
20
  ## 0.32.0 — 2026-07-23
2
21
 
3
22
  ### Added — mode-aware navigation (Campaign Mode) with zero new chrome config
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ // src/active-org.ts
4
+ var ACTIVE_ORG_COOKIE = "politogy_active_org";
5
+ var ACTIVE_ORG_QUERY_PARAM = "org";
6
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7
+ function isOrgId(v) {
8
+ return typeof v === "string" && UUID_RE.test(v.trim());
9
+ }
10
+ function readActiveOrgCookie() {
11
+ if (typeof document === "undefined") return null;
12
+ try {
13
+ const m = document.cookie.match(
14
+ new RegExp(`(?:^|;\\s*)${ACTIVE_ORG_COOKIE}=([^;]+)`)
15
+ );
16
+ const v = m ? decodeURIComponent(m[1]) : null;
17
+ return isOrgId(v) ? v : null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function writeActiveOrgCookie(orgId) {
23
+ if (typeof document === "undefined" || typeof window === "undefined") return;
24
+ if (!isOrgId(orgId)) return;
25
+ const host = window.location.hostname;
26
+ const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
27
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
28
+ document.cookie = `${ACTIVE_ORG_COOKIE}=${encodeURIComponent(
29
+ orgId
30
+ )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
31
+ }
32
+ function readActiveOrgFromUrl(search) {
33
+ if (!search) return null;
34
+ try {
35
+ const qs = search.includes("?") ? search.slice(search.indexOf("?") + 1) : search;
36
+ const v = new URLSearchParams(qs).get(ACTIVE_ORG_QUERY_PARAM);
37
+ return isOrgId(v) ? v : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function resolveActiveOrg(search) {
43
+ const fromUrl = readActiveOrgFromUrl(
44
+ search ?? (typeof window !== "undefined" ? window.location.search : null)
45
+ );
46
+ if (fromUrl) return fromUrl;
47
+ return readActiveOrgCookie();
48
+ }
49
+ function activeOrgParam(orgId) {
50
+ return isOrgId(orgId) ? `${ACTIVE_ORG_QUERY_PARAM}=${orgId}` : "";
51
+ }
52
+ function withActiveOrgParam(url, orgId) {
53
+ if (!isOrgId(orgId)) return url;
54
+ try {
55
+ const base = typeof window !== "undefined" ? window.location.origin : "https://politogyvrm.com";
56
+ const u = new URL(url, base);
57
+ u.searchParams.set(ACTIVE_ORG_QUERY_PARAM, orgId);
58
+ return /^https?:\/\//i.test(url) ? u.toString() : `${u.pathname}${u.search}${u.hash}`;
59
+ } catch {
60
+ return url;
61
+ }
62
+ }
63
+
64
+ exports.ACTIVE_ORG_COOKIE = ACTIVE_ORG_COOKIE;
65
+ exports.ACTIVE_ORG_QUERY_PARAM = ACTIVE_ORG_QUERY_PARAM;
66
+ exports.activeOrgParam = activeOrgParam;
67
+ exports.isOrgId = isOrgId;
68
+ exports.readActiveOrgCookie = readActiveOrgCookie;
69
+ exports.readActiveOrgFromUrl = readActiveOrgFromUrl;
70
+ exports.resolveActiveOrg = resolveActiveOrg;
71
+ exports.withActiveOrgParam = withActiveOrgParam;
72
+ exports.writeActiveOrgCookie = writeActiveOrgCookie;
73
+ //# sourceMappingURL=active-org.cjs.map
74
+ //# sourceMappingURL=active-org.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/active-org.ts"],"names":[],"mappings":";;;AAqBO,IAAM,iBAAA,GAAoB;AAC1B,IAAM,sBAAA,GAAyB;AAItC,IAAM,OAAA,GAAU,iEAAA;AAET,SAAS,QAAQ,CAAA,EAAyB;AAC/C,EAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAY,QAAQ,IAAA,CAAK,CAAA,CAAE,MAAM,CAAA;AACvD;AAGO,SAAS,mBAAA,GAAqC;AACnD,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,iBAAiB,CAAA,QAAA,CAAU;AAAA,KACtD;AACA,IAAA,MAAM,IAAI,CAAA,GAAI,kBAAA,CAAmB,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI,IAAA;AACzC,IAAA,OAAO,OAAA,CAAQ,CAAC,CAAA,GAAI,CAAA,GAAI,IAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAMO,SAAS,qBAAqB,KAAA,EAAqB;AACxD,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,IAAe,OAAO,WAAW,WAAA,EAAa;AACtE,EAAA,IAAI,CAAC,OAAA,CAAQ,KAAK,CAAA,EAAG;AACrB,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,iBAAiB,CAAA,CAAA,EAAI,kBAAA;AAAA,IACxC;AAAA,GACD,CAAA,wCAAA,EAA2C,UAAU,CAAA,EAAG,MAAM,CAAA,CAAA;AACjE;AAMO,SAAS,qBAAqB,MAAA,EAAkD;AACrF,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,GAAK,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA,GAAI,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,GAAI,CAAC,CAAA,GAAI,MAAA;AAC1E,IAAA,MAAM,IAAI,IAAI,eAAA,CAAgB,EAAE,CAAA,CAAE,IAAI,sBAAsB,CAAA;AAC5D,IAAA,OAAO,OAAA,CAAQ,CAAC,CAAA,GAAI,CAAA,GAAI,IAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAUO,SAAS,iBAAiB,MAAA,EAAuC;AACtE,EAAA,MAAM,OAAA,GAAU,oBAAA;AAAA,IACd,WAAW,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,MAAA,GAAS,IAAA;AAAA,GACtE;AACA,EAAA,IAAI,SAAS,OAAO,OAAA;AACpB,EAAA,OAAO,mBAAA,EAAoB;AAC7B;AAGO,SAAS,eAAe,KAAA,EAA0C;AACvE,EAAA,OAAO,QAAQ,KAAK,CAAA,GAAI,GAAG,sBAAsB,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,GAAK,EAAA;AACjE;AAOO,SAAS,kBAAA,CAAmB,KAAa,KAAA,EAA0C;AACxF,EAAA,IAAI,CAAC,OAAA,CAAQ,KAAK,CAAA,EAAG,OAAO,GAAA;AAC5B,EAAA,IAAI;AAEF,IAAA,MAAM,OAAO,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,MAAA,GAAS,yBAAA;AACtE,IAAA,MAAM,CAAA,GAAI,IAAI,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AAC3B,IAAA,CAAA,CAAE,YAAA,CAAa,GAAA,CAAI,sBAAA,EAAwB,KAAe,CAAA;AAE1D,IAAA,OAAO,eAAA,CAAgB,IAAA,CAAK,GAAG,CAAA,GAAI,EAAE,QAAA,EAAS,GAAI,CAAA,EAAG,CAAA,CAAE,QAAQ,CAAA,EAAG,CAAA,CAAE,MAAM,CAAA,EAAG,EAAE,IAAI,CAAA,CAAA;AAAA,EACrF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,GAAA;AAAA,EACT;AACF","file":"active-org.cjs","sourcesContent":["// Universal active-organization selection — the ONE cross-app contract.\n//\n// A user belongs to many orgs; ONE active-org choice must follow them across\n// every Politogy app + subdomain so all apps resolve the SAME org (and campaign\n// apps don't land on a rel-only org and lock themselves). Layers cooperate,\n// mirroring the theme-preference contract:\n// 1. URL `?org=<uuid>` — the authoritative per-request selector. The shared\n// chrome stamps it onto every route so it is bookmarkable + shareable, and\n// each app forwards it to the chrome API as `x-contacts-org`.\n// 2. `.politogyvrm.com` cookie `politogy_active_org` — cross-subdomain vehicle\n// (auth session cookies are host-only) that remembers the LAST USED org so\n// a bare URL in the next app opens on the same org.\n//\n// The stored/most-authoritative value is the FULL org UUID: contacts-api\n// (auth/vrmMembership.selectRequestedMembership) matches the selector by EXACT\n// id first, then falls back to a short suffix that can COLLIDE across a user's\n// orgs — so the full UUID is the only collision-free selector.\n//\n// Server-safe (no 'use client'): constants/resolve helpers are importable from\n// server layouts; the cookie read/write helpers no-op on the server.\n\nexport const ACTIVE_ORG_COOKIE = 'politogy_active_org'\nexport const ACTIVE_ORG_QUERY_PARAM = 'org'\n\n// Loose UUID shape check — we only ever store ids that came from the user's own\n// authoritative membership list, so this just rejects obvious garbage.\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport function isOrgId(v: unknown): v is string {\n return typeof v === 'string' && UUID_RE.test(v.trim())\n}\n\n/** Read the shared active-org cookie (browser only; null on server or unset). */\nexport function readActiveOrgCookie(): string | null {\n if (typeof document === 'undefined') return null\n try {\n const m = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${ACTIVE_ORG_COOKIE}=([^;]+)`),\n )\n const v = m ? decodeURIComponent(m[1]) : null\n return isOrgId(v) ? v : null\n } catch {\n return null\n }\n}\n\n// Write the cross-subdomain cookie with the SAME scoping rules as the theme\n// cookie: on a *.politogyvrm.com host scope to the parent domain so every app\n// subdomain reads it; on any other host (localhost, *.pages.dev preview) fall\n// back to a host-only cookie. 1-year, SameSite=Lax, Secure on https.\nexport function writeActiveOrgCookie(orgId: string): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return\n if (!isOrgId(orgId)) 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 = `${ACTIVE_ORG_COOKIE}=${encodeURIComponent(\n orgId,\n )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`\n}\n\n/**\n * Read the `?org=` selector from a URL search string (full UUID only). Accepts a\n * `location.search`-style string or a full URL.\n */\nexport function readActiveOrgFromUrl(search: string | null | undefined): string | null {\n if (!search) return null\n try {\n const qs = search.includes('?') ? search.slice(search.indexOf('?') + 1) : search\n const v = new URLSearchParams(qs).get(ACTIVE_ORG_QUERY_PARAM)\n return isOrgId(v) ? v : null\n } catch {\n return null\n }\n}\n\n/**\n * Resolve the active org with the cross-app precedence:\n * 1. `?org=<uuid>` in the current URL (explicit per-request selector)\n * 2. the `.politogyvrm.com` cookie (last used, cross-subdomain)\n * 3. null → let the app fall back to the chrome-API default (earliest-joined)\n * Pass `search` on the server (from the request URL); it defaults to\n * `window.location.search` in the browser.\n */\nexport function resolveActiveOrg(search?: string | null): string | null {\n const fromUrl = readActiveOrgFromUrl(\n search ?? (typeof window !== 'undefined' ? window.location.search : null),\n )\n if (fromUrl) return fromUrl\n return readActiveOrgCookie()\n}\n\n/** The canonical `?org=` query fragment for a full org id (no leading `?`/`&`). */\nexport function activeOrgParam(orgId: string | null | undefined): string {\n return isOrgId(orgId) ? `${ACTIVE_ORG_QUERY_PARAM}=${orgId}` : ''\n}\n\n/**\n * Return `url` with `?org=<uuid>` set/replaced. Used by the shared chrome to\n * stamp the active org onto every route so it is always present in the address\n * bar. Leaves the URL unchanged when `orgId` is not a valid id.\n */\nexport function withActiveOrgParam(url: string, orgId: string | null | undefined): string {\n if (!isOrgId(orgId)) return url\n try {\n // Support both absolute and relative URLs.\n const base = typeof window !== 'undefined' ? window.location.origin : 'https://politogyvrm.com'\n const u = new URL(url, base)\n u.searchParams.set(ACTIVE_ORG_QUERY_PARAM, orgId as string)\n // Preserve relative form when the input was relative.\n return /^https?:\\/\\//i.test(url) ? u.toString() : `${u.pathname}${u.search}${u.hash}`\n } catch {\n return url\n }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ declare const ACTIVE_ORG_COOKIE = "politogy_active_org";
2
+ declare const ACTIVE_ORG_QUERY_PARAM = "org";
3
+ declare function isOrgId(v: unknown): v is string;
4
+ /** Read the shared active-org cookie (browser only; null on server or unset). */
5
+ declare function readActiveOrgCookie(): string | null;
6
+ declare function writeActiveOrgCookie(orgId: string): void;
7
+ /**
8
+ * Read the `?org=` selector from a URL search string (full UUID only). Accepts a
9
+ * `location.search`-style string or a full URL.
10
+ */
11
+ declare function readActiveOrgFromUrl(search: string | null | undefined): string | null;
12
+ /**
13
+ * Resolve the active org with the cross-app precedence:
14
+ * 1. `?org=<uuid>` in the current URL (explicit per-request selector)
15
+ * 2. the `.politogyvrm.com` cookie (last used, cross-subdomain)
16
+ * 3. null → let the app fall back to the chrome-API default (earliest-joined)
17
+ * Pass `search` on the server (from the request URL); it defaults to
18
+ * `window.location.search` in the browser.
19
+ */
20
+ declare function resolveActiveOrg(search?: string | null): string | null;
21
+ /** The canonical `?org=` query fragment for a full org id (no leading `?`/`&`). */
22
+ declare function activeOrgParam(orgId: string | null | undefined): string;
23
+ /**
24
+ * Return `url` with `?org=<uuid>` set/replaced. Used by the shared chrome to
25
+ * stamp the active org onto every route so it is always present in the address
26
+ * bar. Leaves the URL unchanged when `orgId` is not a valid id.
27
+ */
28
+ declare function withActiveOrgParam(url: string, orgId: string | null | undefined): string;
29
+
30
+ export { ACTIVE_ORG_COOKIE, ACTIVE_ORG_QUERY_PARAM, activeOrgParam, isOrgId, readActiveOrgCookie, readActiveOrgFromUrl, resolveActiveOrg, withActiveOrgParam, writeActiveOrgCookie };
@@ -0,0 +1,30 @@
1
+ declare const ACTIVE_ORG_COOKIE = "politogy_active_org";
2
+ declare const ACTIVE_ORG_QUERY_PARAM = "org";
3
+ declare function isOrgId(v: unknown): v is string;
4
+ /** Read the shared active-org cookie (browser only; null on server or unset). */
5
+ declare function readActiveOrgCookie(): string | null;
6
+ declare function writeActiveOrgCookie(orgId: string): void;
7
+ /**
8
+ * Read the `?org=` selector from a URL search string (full UUID only). Accepts a
9
+ * `location.search`-style string or a full URL.
10
+ */
11
+ declare function readActiveOrgFromUrl(search: string | null | undefined): string | null;
12
+ /**
13
+ * Resolve the active org with the cross-app precedence:
14
+ * 1. `?org=<uuid>` in the current URL (explicit per-request selector)
15
+ * 2. the `.politogyvrm.com` cookie (last used, cross-subdomain)
16
+ * 3. null → let the app fall back to the chrome-API default (earliest-joined)
17
+ * Pass `search` on the server (from the request URL); it defaults to
18
+ * `window.location.search` in the browser.
19
+ */
20
+ declare function resolveActiveOrg(search?: string | null): string | null;
21
+ /** The canonical `?org=` query fragment for a full org id (no leading `?`/`&`). */
22
+ declare function activeOrgParam(orgId: string | null | undefined): string;
23
+ /**
24
+ * Return `url` with `?org=<uuid>` set/replaced. Used by the shared chrome to
25
+ * stamp the active org onto every route so it is always present in the address
26
+ * bar. Leaves the URL unchanged when `orgId` is not a valid id.
27
+ */
28
+ declare function withActiveOrgParam(url: string, orgId: string | null | undefined): string;
29
+
30
+ export { ACTIVE_ORG_COOKIE, ACTIVE_ORG_QUERY_PARAM, activeOrgParam, isOrgId, readActiveOrgCookie, readActiveOrgFromUrl, resolveActiveOrg, withActiveOrgParam, writeActiveOrgCookie };
@@ -0,0 +1,3 @@
1
+ export { ACTIVE_ORG_COOKIE, ACTIVE_ORG_QUERY_PARAM, activeOrgParam, isOrgId, readActiveOrgCookie, readActiveOrgFromUrl, resolveActiveOrg, withActiveOrgParam, writeActiveOrgCookie } from './chunk-W2HITMV5.js';
2
+ //# sourceMappingURL=active-org.js.map
3
+ //# sourceMappingURL=active-org.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"active-org.js"}
@@ -1397,7 +1397,7 @@ function standardPolitogyNav(opts) {
1397
1397
  href: abs(c.path),
1398
1398
  children: c.children ? leaves(c.children) : void 0
1399
1399
  }));
1400
- const orgParam = opts.activeOrgId ? `org=${opts.activeOrgId.slice(-6)}` : "";
1400
+ const orgParam = opts.activeOrgId ? `org=${opts.activeOrgId}` : "";
1401
1401
  const withOrg = (url) => orgParam ? `${url}${url.includes("?") ? "&" : "?"}${orgParam}` : url;
1402
1402
  const buildRelationship = () => {
1403
1403
  const contacts = opts.contactsItem ? {
@@ -2154,116 +2154,118 @@ function NavTopBar({
2154
2154
  }
2155
2155
  ) })
2156
2156
  ] }),
2157
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 z-30 mt-2 w-72 rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: [
2157
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 z-30 mt-2 flex max-h-[calc(100vh-5rem)] w-72 flex-col overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: [
2158
2158
  /* @__PURE__ */ jsxRuntime.jsx(TopBarPanelArrow, {}),
2159
- userMenu?.myProfileHref ? /* @__PURE__ */ jsxRuntime.jsxs(
2160
- "a",
2161
- {
2162
- href: safeHref(userMenu.myProfileHref),
2163
- className: "flex w-full items-center gap-2.5 px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
2164
- children: [
2165
- /* @__PURE__ */ jsxRuntime.jsx(ProfileIcon, {}),
2166
- "My Profile"
2167
- ]
2168
- }
2169
- ) : null,
2170
- userMenu?.organizationSettingsHref ? /* @__PURE__ */ jsxRuntime.jsxs(
2171
- "a",
2172
- {
2173
- href: safeHref(userMenu.organizationSettingsHref),
2174
- className: "flex w-full items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
2175
- children: [
2176
- /* @__PURE__ */ jsxRuntime.jsx(BriefcaseIcon, {}),
2177
- "Organization Settings"
2178
- ]
2179
- }
2180
- ) : null,
2181
- userMenu?.roles && userMenu.roles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
2182
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Roles & Organizations" }),
2183
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1.5", children: userMenu.roles.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex flex-wrap items-center gap-1.5 text-sm", children: [
2184
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-md bg-[color:var(--panel)] px-2 py-0.5 text-xs font-bold uppercase tracking-wide text-[color:var(--foreground)]", children: r.role }),
2185
- r.orgName ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[color:var(--muted)]", children: [
2186
- "in ",
2187
- r.orgName
2188
- ] }) : null
2189
- ] }, `${r.role}-${i}`)) })
2190
- ] }) : null,
2191
- userMenu?.accounts && userMenu.accounts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
2192
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Accounts" }),
2193
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1", children: userMenu.accounts.map((a) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2.5", children: [
2194
- /* @__PURE__ */ jsxRuntime.jsxs(
2195
- "button",
2159
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain", children: [
2160
+ userMenu?.myProfileHref ? /* @__PURE__ */ jsxRuntime.jsxs(
2161
+ "a",
2162
+ {
2163
+ href: safeHref(userMenu.myProfileHref),
2164
+ className: "flex w-full items-center gap-2.5 px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
2165
+ children: [
2166
+ /* @__PURE__ */ jsxRuntime.jsx(ProfileIcon, {}),
2167
+ "My Profile"
2168
+ ]
2169
+ }
2170
+ ) : null,
2171
+ userMenu?.organizationSettingsHref ? /* @__PURE__ */ jsxRuntime.jsxs(
2172
+ "a",
2173
+ {
2174
+ href: safeHref(userMenu.organizationSettingsHref),
2175
+ className: "flex w-full items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
2176
+ children: [
2177
+ /* @__PURE__ */ jsxRuntime.jsx(BriefcaseIcon, {}),
2178
+ "Organization Settings"
2179
+ ]
2180
+ }
2181
+ ) : null,
2182
+ userMenu?.roles && userMenu.roles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
2183
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Roles & Organizations" }),
2184
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1.5", children: userMenu.roles.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex flex-wrap items-center gap-1.5 text-sm", children: [
2185
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-md bg-[color:var(--panel)] px-2 py-0.5 text-xs font-bold uppercase tracking-wide text-[color:var(--foreground)]", children: r.role }),
2186
+ r.orgName ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[color:var(--muted)]", children: [
2187
+ "in ",
2188
+ r.orgName
2189
+ ] }) : null
2190
+ ] }, `${r.role}-${i}`)) })
2191
+ ] }) : null,
2192
+ userMenu?.accounts && userMenu.accounts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-[color:var(--line)] px-4 py-3", children: [
2193
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Accounts" }),
2194
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-1", children: userMenu.accounts.map((a) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2.5", children: [
2195
+ /* @__PURE__ */ jsxRuntime.jsxs(
2196
+ "button",
2197
+ {
2198
+ type: "button",
2199
+ onClick: () => {
2200
+ if (!a.active) userMenu.onSwitchAccount?.(a.id);
2201
+ },
2202
+ className: "flex min-w-0 flex-1 items-center gap-2.5 rounded-lg px-1 py-1 text-left hover:bg-[color:var(--panel)]",
2203
+ children: [
2204
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-[11px] font-bold text-white", children: a.avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: a.avatarUrl, alt: "", className: "h-full w-full object-cover" }) : (a.name ?? a.email ?? "?").slice(0, 2).toUpperCase() }),
2205
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col leading-tight", children: [
2206
+ /* @__PURE__ */ jsxRuntime.jsx(
2207
+ "span",
2208
+ {
2209
+ className: [
2210
+ "truncate text-sm font-semibold",
2211
+ a.active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)]"
2212
+ ].join(" "),
2213
+ children: a.name ?? a.email ?? a.id.slice(0, 8)
2214
+ }
2215
+ ),
2216
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-[color:var(--muted)]", children: a.active ? "Active" : a.email ?? "" })
2217
+ ] })
2218
+ ]
2219
+ }
2220
+ ),
2221
+ !a.active && userMenu.onRemoveAccount ? /* @__PURE__ */ jsxRuntime.jsx(
2222
+ "button",
2223
+ {
2224
+ type: "button",
2225
+ "aria-label": `Remove ${a.name ?? a.email ?? "account"}`,
2226
+ onClick: () => userMenu.onRemoveAccount?.(a.id),
2227
+ className: "shrink-0 rounded p-1 text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
2228
+ children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {})
2229
+ }
2230
+ ) : null
2231
+ ] }, a.id)) }),
2232
+ userMenu.addAccountHref || userMenu.onAddAccount ? userMenu.addAccountHref ? /* @__PURE__ */ jsxRuntime.jsxs(
2233
+ "a",
2196
2234
  {
2197
- type: "button",
2198
- onClick: () => {
2199
- if (!a.active) userMenu.onSwitchAccount?.(a.id);
2200
- },
2201
- className: "flex min-w-0 flex-1 items-center gap-2.5 rounded-lg px-1 py-1 text-left hover:bg-[color:var(--panel)]",
2235
+ href: safeHref(userMenu.addAccountHref),
2236
+ className: "mt-1.5 flex items-center gap-2.5 rounded-lg px-1 py-1.5 text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
2202
2237
  children: [
2203
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-[11px] font-bold text-white", children: a.avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: a.avatarUrl, alt: "", className: "h-full w-full object-cover" }) : (a.name ?? a.email ?? "?").slice(0, 2).toUpperCase() }),
2204
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col leading-tight", children: [
2205
- /* @__PURE__ */ jsxRuntime.jsx(
2206
- "span",
2207
- {
2208
- className: [
2209
- "truncate text-sm font-semibold",
2210
- a.active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)]"
2211
- ].join(" "),
2212
- children: a.name ?? a.email ?? a.id.slice(0, 8)
2213
- }
2214
- ),
2215
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-[color:var(--muted)]", children: a.active ? "Active" : a.email ?? "" })
2216
- ] })
2238
+ /* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
2239
+ "Add Account"
2217
2240
  ]
2218
2241
  }
2219
- ),
2220
- !a.active && userMenu.onRemoveAccount ? /* @__PURE__ */ jsxRuntime.jsx(
2242
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
2221
2243
  "button",
2222
2244
  {
2223
2245
  type: "button",
2224
- "aria-label": `Remove ${a.name ?? a.email ?? "account"}`,
2225
- onClick: () => userMenu.onRemoveAccount?.(a.id),
2226
- className: "shrink-0 rounded p-1 text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
2227
- children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {})
2246
+ onClick: userMenu.onAddAccount,
2247
+ className: "mt-1.5 flex w-full items-center gap-2.5 rounded-lg px-1 py-1.5 text-left text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
2248
+ children: [
2249
+ /* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
2250
+ "Add Account"
2251
+ ]
2228
2252
  }
2229
2253
  ) : null
2230
- ] }, a.id)) }),
2231
- userMenu.addAccountHref || userMenu.onAddAccount ? userMenu.addAccountHref ? /* @__PURE__ */ jsxRuntime.jsxs(
2232
- "a",
2233
- {
2234
- href: safeHref(userMenu.addAccountHref),
2235
- className: "mt-1.5 flex items-center gap-2.5 rounded-lg px-1 py-1.5 text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
2236
- children: [
2237
- /* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
2238
- "Add Account"
2239
- ]
2240
- }
2241
- ) : /* @__PURE__ */ jsxRuntime.jsxs(
2242
- "button",
2254
+ ] }) : null,
2255
+ userMenu?.theme ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-[color:var(--line)]", children: /* @__PURE__ */ jsxRuntime.jsx(
2256
+ NavThemeControl,
2243
2257
  {
2244
- type: "button",
2245
- onClick: userMenu.onAddAccount,
2246
- className: "mt-1.5 flex w-full items-center gap-2.5 rounded-lg px-1 py-1.5 text-left text-sm font-semibold text-[color:var(--color-race-red)] hover:bg-[color:var(--panel)]",
2247
- children: [
2248
- /* @__PURE__ */ jsxRuntime.jsx(AddUserIcon, {}),
2249
- "Add Account"
2250
- ]
2258
+ value: userMenu.theme.value,
2259
+ onChange: userMenu.theme.onChange
2251
2260
  }
2252
- ) : null
2253
- ] }) : null,
2254
- userMenu?.theme ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-[color:var(--line)]", children: /* @__PURE__ */ jsxRuntime.jsx(
2255
- NavThemeControl,
2256
- {
2257
- value: userMenu.theme.value,
2258
- onChange: userMenu.theme.onChange
2259
- }
2260
- ) }) : null,
2261
+ ) }) : null
2262
+ ] }),
2261
2263
  onSignOut ? /* @__PURE__ */ jsxRuntime.jsxs(
2262
2264
  "button",
2263
2265
  {
2264
2266
  type: "button",
2265
2267
  onClick: onSignOut,
2266
- className: "flex w-full items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
2268
+ className: "flex w-full shrink-0 items-center gap-2.5 border-t border-[color:var(--line)] px-4 py-2.5 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
2267
2269
  children: [
2268
2270
  /* @__PURE__ */ jsxRuntime.jsx(SignOutIcon, {}),
2269
2271
  "Sign out"
@@ -2486,6 +2488,53 @@ function NavAppShell({
2486
2488
  ] });
2487
2489
  }
2488
2490
 
2491
+ // src/active-org.ts
2492
+ var ACTIVE_ORG_COOKIE = "politogy_active_org";
2493
+ var ACTIVE_ORG_QUERY_PARAM = "org";
2494
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2495
+ function isOrgId(v) {
2496
+ return typeof v === "string" && UUID_RE.test(v.trim());
2497
+ }
2498
+ function readActiveOrgCookie() {
2499
+ if (typeof document === "undefined") return null;
2500
+ try {
2501
+ const m = document.cookie.match(
2502
+ new RegExp(`(?:^|;\\s*)${ACTIVE_ORG_COOKIE}=([^;]+)`)
2503
+ );
2504
+ const v = m ? decodeURIComponent(m[1]) : null;
2505
+ return isOrgId(v) ? v : null;
2506
+ } catch {
2507
+ return null;
2508
+ }
2509
+ }
2510
+ function writeActiveOrgCookie(orgId) {
2511
+ if (typeof document === "undefined" || typeof window === "undefined") return;
2512
+ if (!isOrgId(orgId)) return;
2513
+ const host = window.location.hostname;
2514
+ const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
2515
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
2516
+ document.cookie = `${ACTIVE_ORG_COOKIE}=${encodeURIComponent(
2517
+ orgId
2518
+ )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
2519
+ }
2520
+ function readActiveOrgFromUrl(search) {
2521
+ if (!search) return null;
2522
+ try {
2523
+ const qs = search.includes("?") ? search.slice(search.indexOf("?") + 1) : search;
2524
+ const v = new URLSearchParams(qs).get(ACTIVE_ORG_QUERY_PARAM);
2525
+ return isOrgId(v) ? v : null;
2526
+ } catch {
2527
+ return null;
2528
+ }
2529
+ }
2530
+ function resolveActiveOrg(search) {
2531
+ const fromUrl = readActiveOrgFromUrl(
2532
+ (typeof window !== "undefined" ? window.location.search : null)
2533
+ );
2534
+ if (fromUrl) return fromUrl;
2535
+ return readActiveOrgCookie();
2536
+ }
2537
+
2489
2538
  // src/lib/breadcrumbs.ts
2490
2539
  function deriveBreadcrumbs(items, pathname, opts) {
2491
2540
  const search = opts?.search ?? (typeof window !== "undefined" ? window.location.search : "");
@@ -2555,6 +2604,89 @@ function resolveIdentity(input) {
2555
2604
  loaded: input.loaded ?? false
2556
2605
  };
2557
2606
  }
2607
+
2608
+ // src/logout-signal.ts
2609
+ var LOGOUT_COOKIE = "politogy_logout_at";
2610
+ var LOGOUT_CHANNEL = "politogy-logout";
2611
+ var POLL_MS = 1e3;
2612
+ function parseTimestamp(value) {
2613
+ if (!value) return null;
2614
+ const n = Number(value);
2615
+ return Number.isFinite(n) && n > 0 ? n : null;
2616
+ }
2617
+ function isSessionRevoked(logoutCookieValue, authTime) {
2618
+ const loggedOutAt = parseTimestamp(logoutCookieValue);
2619
+ if (loggedOutAt === null) return false;
2620
+ if (authTime == null || !Number.isFinite(authTime) || authTime <= 0) return true;
2621
+ const authMs = authTime < 1e12 ? authTime * 1e3 : authTime;
2622
+ return authMs < loggedOutAt;
2623
+ }
2624
+ function readLogoutCookie() {
2625
+ if (typeof document === "undefined") return null;
2626
+ try {
2627
+ const m = document.cookie.match(new RegExp(`(?:^|;\\s*)${LOGOUT_COOKIE}=([^;]+)`));
2628
+ return parseTimestamp(m ? decodeURIComponent(m[1]) : null);
2629
+ } catch {
2630
+ return null;
2631
+ }
2632
+ }
2633
+ function broadcastLogout(at = Date.now()) {
2634
+ if (typeof document === "undefined" || typeof window === "undefined") return;
2635
+ const host = window.location.hostname;
2636
+ const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
2637
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
2638
+ document.cookie = `${LOGOUT_COOKIE}=${at}; Path=/; Max-Age=2592000; SameSite=Lax${domainAttr}${secure}`;
2639
+ try {
2640
+ new BroadcastChannel(LOGOUT_CHANNEL).postMessage(at);
2641
+ } catch {
2642
+ }
2643
+ }
2644
+ function watchLogout(onLogout, options = {}) {
2645
+ if (typeof document === "undefined" || typeof window === "undefined") {
2646
+ return () => {
2647
+ };
2648
+ }
2649
+ const { authTime = null, intervalMs = POLL_MS } = options;
2650
+ const baseline = readLogoutCookie() ?? 0;
2651
+ let done = false;
2652
+ const fire = (at) => {
2653
+ if (done) return;
2654
+ if (at <= baseline) return;
2655
+ if (authTime != null && !isSessionRevoked(String(at), authTime)) return;
2656
+ done = true;
2657
+ cleanup();
2658
+ onLogout();
2659
+ };
2660
+ const check = () => {
2661
+ const at = readLogoutCookie();
2662
+ if (at !== null) fire(at);
2663
+ };
2664
+ let channel = null;
2665
+ try {
2666
+ channel = new BroadcastChannel(LOGOUT_CHANNEL);
2667
+ channel.onmessage = (event) => {
2668
+ const at = typeof event.data === "number" ? event.data : readLogoutCookie();
2669
+ if (at !== null) fire(at);
2670
+ };
2671
+ } catch {
2672
+ channel = null;
2673
+ }
2674
+ const timer = window.setInterval(check, intervalMs);
2675
+ window.addEventListener("visibilitychange", check);
2676
+ window.addEventListener("focus", check);
2677
+ window.addEventListener("pageshow", check);
2678
+ function cleanup() {
2679
+ window.clearInterval(timer);
2680
+ window.removeEventListener("visibilitychange", check);
2681
+ window.removeEventListener("focus", check);
2682
+ window.removeEventListener("pageshow", check);
2683
+ try {
2684
+ channel?.close();
2685
+ } catch {
2686
+ }
2687
+ }
2688
+ return cleanup;
2689
+ }
2558
2690
  var CORE_APP = "vrm";
2559
2691
  var POLITOGY_APPS = {
2560
2692
  vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
@@ -2631,6 +2763,28 @@ function FrameBody({
2631
2763
  }) {
2632
2764
  const meta = POLITOGY_APPS[app];
2633
2765
  const chrome = useChromeState();
2766
+ const signOutRef = react.useRef(onSignOut);
2767
+ signOutRef.current = onSignOut;
2768
+ const signingOutRef = react.useRef(false);
2769
+ const handleSignOut = react.useCallback(() => {
2770
+ signingOutRef.current = true;
2771
+ broadcastLogout();
2772
+ signOutRef.current();
2773
+ }, []);
2774
+ react.useEffect(
2775
+ () => (
2776
+ // A tab that learns of a sign-out elsewhere ends its OWN session properly
2777
+ // (clear cookie, RP-initiated logout at the IdP) rather than merely
2778
+ // navigating away — otherwise its session cookie would outlive the
2779
+ // sign-out and a later visit would silently resume it. No re-broadcast:
2780
+ // the signal that woke us is already on record for everyone.
2781
+ watchLogout(() => {
2782
+ if (signingOutRef.current) return;
2783
+ signOutRef.current();
2784
+ })
2785
+ ),
2786
+ []
2787
+ );
2634
2788
  const core = app === CORE_APP ? data?.core : void 0;
2635
2789
  const identity = useChromeIdentity({ userName: session.userName, email: session.email });
2636
2790
  const derivedUserMenu = useChromeUserMenu({
@@ -2713,7 +2867,7 @@ function FrameBody({
2713
2867
  role: session.role
2714
2868
  },
2715
2869
  userMenu,
2716
- onSignOut,
2870
+ onSignOut: handleSignOut,
2717
2871
  // The action cluster is IDENTICAL everywhere — core included. Page
2718
2872
  // actions sit BEFORE it, never inside it, so the Alerts bell keeps its
2719
2873
  // required position: farthest right, immediately before My Account.
@@ -2772,7 +2926,7 @@ function ChromeStateProvider({
2772
2926
  const [navDisplay, setNavDisplayState] = react.useState("both");
2773
2927
  react.useEffect(() => {
2774
2928
  try {
2775
- const storedOrg = localStorage.getItem(ORG_STORAGE_KEY);
2929
+ const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY);
2776
2930
  if (storedOrg) setOrgIdState(storedOrg);
2777
2931
  const storedNav = localStorage.getItem(NAV_DISPLAY_KEY);
2778
2932
  if (storedNav === "menu" || storedNav === "tabs" || storedNav === "both") {
@@ -2791,6 +2945,7 @@ function ChromeStateProvider({
2791
2945
  localStorage.setItem(ORG_STORAGE_KEY, id);
2792
2946
  } catch {
2793
2947
  }
2948
+ writeActiveOrgCookie(id);
2794
2949
  }, []);
2795
2950
  const setNavDisplay = react.useCallback((m) => {
2796
2951
  setNavDisplayState(m);
@@ -2835,6 +2990,19 @@ function ChromeStateProvider({
2835
2990
  setNavDisplay
2836
2991
  };
2837
2992
  }, [controlledOrg, orgId, setOrgId, orgs, orgsFetching, orgsError, navDisplay, setNavDisplay]);
2993
+ const activeOrgId = value.orgId;
2994
+ react.useEffect(() => {
2995
+ if (!isOrgId(activeOrgId)) return;
2996
+ writeActiveOrgCookie(activeOrgId);
2997
+ try {
2998
+ const url = new URL(window.location.href);
2999
+ if (url.searchParams.get(ACTIVE_ORG_QUERY_PARAM) !== activeOrgId) {
3000
+ url.searchParams.set(ACTIVE_ORG_QUERY_PARAM, activeOrgId);
3001
+ window.history.replaceState(window.history.state, "", url.toString());
3002
+ }
3003
+ } catch {
3004
+ }
3005
+ }, [activeOrgId]);
2838
3006
  return /* @__PURE__ */ jsxRuntime.jsx(ChromeStateContext.Provider, { value, children });
2839
3007
  }
2840
3008
  function AppDevNotes({
@@ -2984,7 +3152,7 @@ function useChromeUserMenu({
2984
3152
  const state = useChromeState();
2985
3153
  const theme = useThemePreference();
2986
3154
  const activeOrg = state?.orgs.find((o) => o.id === state.orgId) ?? null;
2987
- const orgSuffix = activeOrg ? `?org=${activeOrg.id.slice(-6)}` : "";
3155
+ const orgSuffix = activeOrg ? `?org=${activeOrg.id}` : "";
2988
3156
  return {
2989
3157
  // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).
2990
3158
  myProfileHref: `${CORE_BASE_URL}/my-profile`,
@@ -3051,7 +3219,7 @@ function useChromeMode(arg = {}) {
3051
3219
  const pinned = pinnedModeId ? MODE_DEFS.find((m) => m.id === pinnedModeId) ?? null : null;
3052
3220
  const selected = selectedModeId ? options.find((m) => m.id === selectedModeId) ?? null : null;
3053
3221
  const current = pinned ?? selected ?? options[0] ?? MODE_DEFS.find((m) => m.id === fallbackModeId) ?? MODE_DEFS[0];
3054
- const suffix = orgId ? `?org=${orgId.slice(-6)}` : "";
3222
+ const suffix = orgId ? `?org=${orgId}` : "";
3055
3223
  return {
3056
3224
  label: current.label,
3057
3225
  icon: current.icon,