@the-portland-company/shell 0.97.0 → 0.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { S as StandardNavIconKey, o as NavIcon, N as NavItem, p as NavBreadcrumb, q as StandardNavMode, s as NavLinkComponent, t as NavChromeFetcher, u as NavMode, v as NavUserMenu, w as NavOrgOption, f as NotificationsAdapter, x as NavFooterLink, y as NavFooter, z as NavDisplayMode } from './index-BaUIPQpN.cjs';
3
+ import { S as StandardNavIconKey, t as NavIcon, N as NavItem, u as NavBreadcrumb, v as StandardNavMode, w as NavLinkComponent, x as NavChromeFetcher, y as NavMode, z as NavUserMenu, D as NavOrgOption, f as NotificationsAdapter, E as NavFooterLink, G as NavFooter, I as NavDisplayMode } from './index-CUPeC2Ji.cjs';
4
4
  import './theme-preference.cjs';
5
5
  import './brand-favicon.cjs';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { S as StandardNavIconKey, o as NavIcon, N as NavItem, p as NavBreadcrumb, q as StandardNavMode, s as NavLinkComponent, t as NavChromeFetcher, u as NavMode, v as NavUserMenu, w as NavOrgOption, f as NotificationsAdapter, x as NavFooterLink, y as NavFooter, z as NavDisplayMode } from './index-BEMGfLpI.js';
3
+ import { S as StandardNavIconKey, t as NavIcon, N as NavItem, u as NavBreadcrumb, v as StandardNavMode, w as NavLinkComponent, x as NavChromeFetcher, y as NavMode, z as NavUserMenu, D as NavOrgOption, f as NotificationsAdapter, E as NavFooterLink, G as NavFooter, I as NavDisplayMode } from './index-DAD6tSfV.js';
4
4
  import './theme-preference.js';
5
5
  import './brand-favicon.js';
6
6
 
@@ -1,13 +1,13 @@
1
1
  "use client";
2
2
  import { broadcastLogout, watchLogout } from './chunk-LL6LSJAH.js';
3
3
  import { writeUrlState } from './chunk-GVQKZI5X.js';
4
- import { clearBetaBannerDismissal, standardPolitogyNav, standardNavIcons, isNavItemActive, NavAppShell } from './chunk-WQR42AGC.js';
5
- export { standardNavIcons } from './chunk-WQR42AGC.js';
4
+ import { clearBetaBannerDismissal, standardPolitogyNav, standardNavIcons, isNavItemActive, NavAppShell } from './chunk-IVLXZFGV.js';
5
+ export { standardNavIcons } from './chunk-IVLXZFGV.js';
6
6
  import { orgIdTail, ORG_ID_QUERY_PARAM, settingsBaseUrl, authBaseUrl, readOrgSelectorFromUrl, matchOrgSelector, syncOrgIdInUrl, isOrgId, switchActiveOrg, ACTIVE_ORG_QUERY_PARAM, writeActiveOrgCookie, resolveActiveOrg, readActiveOrgCookie } from './chunk-5LO5W7W5.js';
7
- import { warnDeprecated, NavChromeProvider, FuelAction, NotificationsAction, OrganizationSettingsAction, MyProfileAction, useChromeMe, useNavChromeDedupedFetch, useChromeOrgs, useNavChromeFetcher } from './chunk-VAQNZD37.js';
7
+ import { warnDeprecated, NavChromeProvider, FuelAction, NotificationsAction, OrganizationSettingsAction, MyProfileAction, useChromeMe, useNavChromeDedupedFetch, useChromeOrgs, useNavChromeFetcher } from './chunk-WOOBPM4V.js';
8
8
  import './chunk-BHRVZPZ7.js';
9
9
  import { NAV_DISPLAY_DEFAULT, resolveNavDisplay, writeNavDisplayLocal, writeNavDisplayCookie, isNavDisplayPreference, NAV_DISPLAY_STORAGE_KEY, readNavDisplayCookie } from './chunk-7YVJ7HQG.js';
10
- import { useBrandAssets, BrandFavicon } from './chunk-BMR5WAWH.js';
10
+ import { useBrandAssets, BrandFavicon } from './chunk-Q5DTPY5N.js';
11
11
  import { createContext, useMemo, useRef, useEffect, useState, useCallback, useContext } from 'react';
12
12
  import { createDevNotesClient, DevNotesProvider, DevNotesTaskListModal, DevNotesOverlay } from '@the-portland-company/devnotes';
13
13
  import { LuContactRound, LuMegaphone, LuFilePenLine } from 'react-icons/lu';
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/brandAssets.ts","../src/lib/brandFavicon.ts"],"names":["useEffect"],"mappings":";;;;AAmBO,SAAS,YAAY,KAAA,EAAiD;AAC3E,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,CAAA,GAAI,gCAAA,CAAiC,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAC5D,EAAA,MAAM,GAAA,GAAM,CAAA,GAAI,CAAC,CAAA,EAAG,IAAA,EAAK;AACzB,EAAA,OAAO,GAAA,IAAO,cAAA,CAAe,IAAA,CAAK,GAAG,IAAI,GAAA,GAAM,IAAA;AACjD;AAGO,SAAS,eAAe,IAAA,EAAqC;AAClE,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,IAAA;AAC5C,EAAA,IAAI;AACF,IAAA,OAAO,WAAA;AAAA,MACL,MAAA,CAAO,iBAAiB,QAAA,CAAS,eAAe,EAAE,gBAAA,CAAiB,CAAA,QAAA,EAAW,IAAI,CAAA,CAAE;AAAA,KACtF;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;;;ACdO,IAAM,qBAAA,GAAwB;AAC9B,IAAM,wBAAA,GAA2B;AAExC,SAAS,UAAA,CAAW,EAAA,EAAY,GAAA,EAAa,IAAA,EAAoB;AAC/D,EAAA,IAAI,IAAA,GAAO,QAAA,CAAS,cAAA,CAAe,EAAE,CAAA;AACrC,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,IAAA,GAAO,QAAA,CAAS,cAAc,MAAM,CAAA;AACpC,IAAA,IAAA,CAAK,EAAA,GAAK,EAAA;AACV,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AAAA,EACb;AACA,EAAA,IAAI,KAAK,YAAA,CAAa,MAAM,CAAA,KAAM,IAAA,OAAW,IAAA,GAAO,IAAA;AAGpD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,IAAA,CAAK,gBAAA,CAAiB,CAAA,WAAA,EAAc,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAC,CAAA,EAAA,CAAI,CAAA;AAChF,EAAA,IAAI,KAAA,CAAM,MAAM,MAAA,GAAS,CAAC,MAAM,IAAA,EAAM,QAAA,CAAS,IAAA,CAAK,WAAA,CAAY,IAAI,CAAA;AACtE;AAOO,SAAS,iBAAA,GAA6B;AAC3C,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,KAAA;AAC5C,EAAA,MAAM,OAAA,GAAU,eAAe,SAAS,CAAA;AACxC,EAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AACrB,EAAA,UAAA,CAAW,qBAAA,EAAuB,QAAQ,OAAO,CAAA;AACjD,EAAA,MAAM,KAAA,GAAQ,eAAe,YAAY,CAAA;AACzC,EAAA,IAAI,KAAA,EAAO,UAAA,CAAW,wBAAA,EAA0B,kBAAA,EAAoB,KAAK,CAAA;AACzE,EAAA,OAAO,IAAA;AACT;AAIA,IAAM,QAAA,GAAW,CAAC,CAAA,EAAG,GAAA,EAAK,KAAM,GAAI,CAAA;AAO7B,SAAS,iBAAA,GAAgC;AAC9C,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,MAAM;AAAA,EAAC,CAAA;AACnD,EAAA,IAAI,IAAA,GAAO,KAAA;AACX,EAAA,MAAM,QAAQ,MAAM;AAClB,IAAA,IAAI,CAAC,MAAM,iBAAA,EAAkB;AAAA,EAC/B,CAAA;AAEA,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAqB;AACxC,EAAA,MAAM,SAAS,MAAM;AACnB,IAAA,QAAA,CACG,gBAAA,CAAkC,4CAA4C,CAAA,CAC9E,OAAA,CAAQ,CAAC,CAAA,KAAM;AACd,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,EAAG;AACnB,MAAA,MAAA,CAAO,IAAI,CAAC,CAAA;AACZ,MAAA,CAAA,CAAE,gBAAA,CAAiB,QAAQ,KAAK,CAAA;AAAA,IAClC,CAAC,CAAA;AAAA,EACL,CAAA;AACA,EAAA,MAAA,EAAO;AACP,EAAA,MAAM,MAAA,GAAS,SAAS,GAAA,CAAI,CAAC,OAAO,MAAA,CAAO,UAAA,CAAW,KAAA,EAAO,EAAE,CAAC,CAAA;AAChE,EAAA,MAAM,WACJ,OAAO,gBAAA,KAAqB,cACxB,IAAA,GACA,IAAI,iBAAiB,MAAM;AACzB,IAAA,MAAA,EAAO;AACP,IAAA,KAAA,EAAM;AAAA,EACR,CAAC,CAAA;AACP,EAAA,QAAA,EAAU,QAAQ,QAAA,CAAS,IAAA,EAAM,EAAE,SAAA,EAAW,MAAM,CAAA;AACpD,EAAA,OAAO,MAAM;AACX,IAAA,IAAA,GAAO,IAAA;AACP,IAAA,MAAA,CAAO,QAAQ,CAAC,CAAA,KAAM,EAAE,mBAAA,CAAoB,MAAA,EAAQ,KAAK,CAAC,CAAA;AAC1D,IAAA,MAAA,CAAO,QAAQ,CAAC,CAAA,KAAM,MAAA,CAAO,YAAA,CAAa,CAAC,CAAC,CAAA;AAC5C,IAAA,QAAA,EAAU,UAAA,EAAW;AAAA,EACvB,CAAA;AACF;AAGO,SAAS,YAAA,GAAqB;AACnC,EAAAA,eAAAA,CAAU,MAAM,iBAAA,EAAkB,EAAG,EAAE,CAAA;AACvC,EAAA,OAAO,IAAA;AACT","file":"brand-favicon.cjs","sourcesContent":["// Brand assets — logo URLs read at RUNTIME from the Brand token sheet.\n//\n// Brand guideline \"Logo standard: use the uploaded Brand assets as-is\": apps\n// (and the shell) never bundle their own copy of a logo; they read the URLs\n// from the Brand `assets` group. `/api/tokens?format=css` emits each set asset\n// as `--asset-<kebab>: url(\"…\")` on `:root`, and every app already loads that\n// sheet at runtime (see ../runtime-tokens.ts), so the rail logo is resolved\n// from the same live sheet as every other chrome value — a Brand upload swaps\n// the logo in every app without a deploy, and there is nothing to fall back to\n// that could drift.\n\n'use client'\n\nimport { useEffect, useState } from 'react'\n\n/** Brand `assets` keys the chrome renders, by their `--asset-<kebab>` name. */\nexport type BrandAssetKind = 'logo-horizontal' | 'logo-square' | 'favicon' | 'apple-icon'\n\n/** `url(\"https://…\")` / `url(https://…)` → the bare https URL, else null. */\nexport function parseCssUrl(value: string | null | undefined): string | null {\n if (!value) return null\n const m = /^url\\(\\s*(['\"]?)(.*?)\\1\\s*\\)$/i.exec(value.trim())\n const url = m?.[2]?.trim()\n return url && /^https:\\/\\//i.test(url) ? url : null\n}\n\n/** One Brand asset URL from the live token sheet, or null while it is unset. */\nexport function readBrandAsset(kind: BrandAssetKind): string | null {\n if (typeof document === 'undefined') return null\n try {\n return parseCssUrl(\n window.getComputedStyle(document.documentElement).getPropertyValue(`--asset-${kind}`),\n )\n } catch {\n return null\n }\n}\n\nexport type BrandAssets = {\n /** `assets.logoHorizontal` — the all-white lockup at the top of the rail. */\n logoHorizontal: string | null\n /** `assets.logoSquare` — the all-white vrm tile on the collapsed rail. */\n logoSquare: string | null\n}\n\nconst EMPTY: BrandAssets = { logoHorizontal: null, logoSquare: null }\n\n// The token sheet is a <link> the app appends at startup, so it may still be\n// in flight on first paint. Re-read when it loads, and a few times after, to\n// cover a link that is inserted after this effect ran.\nconst RETRY_MS = [0, 250, 1000, 3000]\n\n/**\n * The Brand logo URLs, resolved after mount (null on the server and until the\n * runtime token sheet has loaded). Callers reserve the logo's box and draw\n * nothing while a value is null — never a stand-in image.\n */\nexport function useBrandAssets(): BrandAssets {\n const [assets, setAssets] = useState<BrandAssets>(EMPTY)\n useEffect(() => {\n let done = false\n const read = () => {\n if (done) return\n const next = {\n logoHorizontal: readBrandAsset('logo-horizontal'),\n logoSquare: readBrandAsset('logo-square'),\n }\n setAssets((prev) =>\n prev.logoHorizontal === next.logoHorizontal && prev.logoSquare === next.logoSquare\n ? prev\n : next,\n )\n }\n const links = Array.from(\n document.querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"][href*=\"api/tokens\"]'),\n )\n links.forEach((l) => l.addEventListener('load', read))\n const timers = RETRY_MS.map((ms) => window.setTimeout(read, ms))\n read()\n return () => {\n done = true\n links.forEach((l) => l.removeEventListener('load', read))\n timers.forEach((t) => window.clearTimeout(t))\n }\n }, [])\n return assets\n}\n","// Browser-tab icon — Brand `assets.favicon` read at RUNTIME from the Brand\n// token sheet, the same way the rail logo is (./brandAssets).\n//\n// `politogyAppIcons()` (../app-icons) resolves the icon on the SERVER from\n// specs.politogyvrm.com/api/icons/<app>. In production that request comes from\n// a Worker, not a browser, and Cloudflare's bot check answers it with a\n// challenge page, so the metadata comes back with no icon and the tab shows the\n// browser default (or an app's leftover Next.js `favicon.ico` triangle). The\n// token sheet, by contrast, is loaded by the BROWSER in every app, and emits\n// `--asset-favicon` / `--asset-apple-icon` from Brand `assets.*`. Reading those\n// gives every app the Brand's VRM tile with no static file in any repo (Global\n// Rules → App Icons) and no extra request.\n//\n// The link carries a fixed id and is kept the LAST icon link in <head>, so a\n// stale icon from metadata or an old static file never wins over the Brand's.\n\n'use client'\n\nimport { useEffect } from 'react'\n\nimport { readBrandAsset } from './brandAssets'\n\nexport const BRAND_FAVICON_LINK_ID = 'politogy-brand-favicon'\nexport const BRAND_APPLE_ICON_LINK_ID = 'politogy-brand-apple-icon'\n\nfunction upsertLink(id: string, rel: string, href: string): void {\n let link = document.getElementById(id) as HTMLLinkElement | null\n if (!link) {\n link = document.createElement('link')\n link.id = id\n link.rel = rel\n }\n if (link.getAttribute('href') !== href) link.href = href\n // Last icon link in <head> wins. Re-appending an already-last node is a no-op\n // for the browser, so only move it when something was added after it.\n const icons = document.head.querySelectorAll(`link[rel~=\"${rel.split(' ')[0]}\"]`)\n if (icons[icons.length - 1] !== link) document.head.appendChild(link)\n}\n\n/**\n * Point the tab icon (and the iOS home-screen icon) at the Brand's uploaded\n * assets. Returns true once the favicon was set; false while the token sheet\n * has not defined `--asset-favicon` yet. Never draws a stand-in.\n */\nexport function applyBrandFavicon(): boolean {\n if (typeof document === 'undefined') return false\n const favicon = readBrandAsset('favicon')\n if (!favicon) return false\n upsertLink(BRAND_FAVICON_LINK_ID, 'icon', favicon)\n const apple = readBrandAsset('apple-icon')\n if (apple) upsertLink(BRAND_APPLE_ICON_LINK_ID, 'apple-touch-icon', apple)\n return true\n}\n\n// The token sheet is a <link> the app appends at startup, so it may still be in\n// flight on first paint. Re-read when it loads and a few times after.\nconst RETRY_MS = [0, 250, 1000, 3000]\n\n/**\n * Apply the Brand favicon now and keep it applied: on token sheet load, on a\n * few retries, and whenever <head> changes (a client navigation can re-insert\n * metadata icon links after ours). Returns a cleanup function.\n */\nexport function watchBrandFavicon(): () => void {\n if (typeof document === 'undefined') return () => {}\n let done = false\n const apply = () => {\n if (!done) applyBrandFavicon()\n }\n // A token sheet inserted after mount is picked up by the observer below.\n const sheets = new Set<HTMLLinkElement>()\n const listen = () => {\n document\n .querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"][href*=\"api/tokens\"]')\n .forEach((l) => {\n if (sheets.has(l)) return\n sheets.add(l)\n l.addEventListener('load', apply)\n })\n }\n listen()\n const timers = RETRY_MS.map((ms) => window.setTimeout(apply, ms))\n const observer =\n typeof MutationObserver === 'undefined'\n ? null\n : new MutationObserver(() => {\n listen()\n apply()\n })\n observer?.observe(document.head, { childList: true })\n return () => {\n done = true\n sheets.forEach((l) => l.removeEventListener('load', apply))\n timers.forEach((t) => window.clearTimeout(t))\n observer?.disconnect()\n }\n}\n\n/** Renders nothing; sets the Brand favicon after mount. PolitogyAppFrame renders it. */\nexport function BrandFavicon(): null {\n useEffect(() => watchBrandFavicon(), [])\n return null\n}\n"]}
1
+ {"version":3,"sources":["../src/lib/brandAssets.ts","../src/lib/brandFavicon.ts"],"names":["useEffect"],"mappings":"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","file":"brand-favicon.cjs","sourcesContent":["// Brand assets — logo URLs read at RUNTIME from the Brand token sheet.\n//\n// Brand guideline \"Logo standard: use the uploaded Brand assets as-is\": apps\n// (and the shell) never bundle their own copy of a logo; they read the URLs\n// from the Brand `assets` group. `/api/tokens?format=css` emits each set asset\n// as `--asset-<kebab>: url(\"…\")` on `:root`, and every app already loads that\n// sheet at runtime (see ../runtime-tokens.ts), so the rail logo is resolved\n// from the same live sheet as every other chrome value — a Brand upload swaps\n// the logo in every app without a deploy, and there is nothing to fall back to\n// that could drift.\n\n'use client'\n\nimport { useEffect, useState } from 'react'\n\n/** Brand `assets` keys the chrome renders, by their `--asset-<kebab>` name. */\nexport type BrandAssetKind = 'logo-horizontal' | 'logo-square' | 'favicon' | 'apple-icon' | 'verified-icon'\n\n/** `url(\"https://…\")` / `url(https://…)` → the bare https URL, else null. */\nexport function parseCssUrl(value: string | null | undefined): string | null {\n if (!value) return null\n const m = /^url\\(\\s*(['\"]?)(.*?)\\1\\s*\\)$/i.exec(value.trim())\n const url = m?.[2]?.trim()\n return url && /^https:\\/\\//i.test(url) ? url : null\n}\n\n/** One Brand asset URL from the live token sheet, or null while it is unset. */\nexport function readBrandAsset(kind: BrandAssetKind): string | null {\n if (typeof document === 'undefined') return null\n try {\n return parseCssUrl(\n window.getComputedStyle(document.documentElement).getPropertyValue(`--asset-${kind}`),\n )\n } catch {\n return null\n }\n}\n\nexport type BrandAssets = {\n /** `assets.logoHorizontal` — the all-white lockup at the top of the rail. */\n logoHorizontal: string | null\n /** `assets.logoSquare` — the all-white vrm tile on the collapsed rail. */\n logoSquare: string | null\n}\n\nconst EMPTY: BrandAssets = { logoHorizontal: null, logoSquare: null }\n\n// The token sheet is a <link> the app appends at startup, so it may still be\n// in flight on first paint. Re-read when it loads, and a few times after, to\n// cover a link that is inserted after this effect ran.\nconst RETRY_MS = [0, 250, 1000, 3000]\n\n/**\n * The Brand logo URLs, resolved after mount (null on the server and until the\n * runtime token sheet has loaded). Callers reserve the logo's box and draw\n * nothing while a value is null — never a stand-in image.\n */\nexport function useBrandAssets(): BrandAssets {\n const [assets, setAssets] = useState<BrandAssets>(EMPTY)\n useEffect(() => {\n let done = false\n const read = () => {\n if (done) return\n const next = {\n logoHorizontal: readBrandAsset('logo-horizontal'),\n logoSquare: readBrandAsset('logo-square'),\n }\n setAssets((prev) =>\n prev.logoHorizontal === next.logoHorizontal && prev.logoSquare === next.logoSquare\n ? prev\n : next,\n )\n }\n const links = Array.from(\n document.querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"][href*=\"api/tokens\"]'),\n )\n links.forEach((l) => l.addEventListener('load', read))\n const timers = RETRY_MS.map((ms) => window.setTimeout(read, ms))\n read()\n return () => {\n done = true\n links.forEach((l) => l.removeEventListener('load', read))\n timers.forEach((t) => window.clearTimeout(t))\n }\n }, [])\n return assets\n}\n\n/**\n * One Brand asset URL, resolved after mount the same way as the rail logos\n * (null on the server and until the runtime token sheet has loaded). Used by\n * the VRM Verified badge for `assets.verifiedIcon` (`--asset-verified-icon`).\n */\nexport function useBrandAsset(kind: BrandAssetKind): string | null {\n const [url, setUrl] = useState<string | null>(null)\n useEffect(() => {\n let done = false\n const read = () => {\n if (done) return\n const next = readBrandAsset(kind)\n setUrl((prev) => (prev === next ? prev : next))\n }\n const links = Array.from(\n document.querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"][href*=\"api/tokens\"]'),\n )\n links.forEach((l) => l.addEventListener('load', read))\n const timers = RETRY_MS.map((ms) => window.setTimeout(read, ms))\n read()\n return () => {\n done = true\n links.forEach((l) => l.removeEventListener('load', read))\n timers.forEach((t) => window.clearTimeout(t))\n }\n }, [kind])\n return url\n}\n","// Browser-tab icon — Brand `assets.favicon` read at RUNTIME from the Brand\n// token sheet, the same way the rail logo is (./brandAssets).\n//\n// `politogyAppIcons()` (../app-icons) resolves the icon on the SERVER from\n// specs.politogyvrm.com/api/icons/<app>. In production that request comes from\n// a Worker, not a browser, and Cloudflare's bot check answers it with a\n// challenge page, so the metadata comes back with no icon and the tab shows the\n// browser default (or an app's leftover Next.js `favicon.ico` triangle). The\n// token sheet, by contrast, is loaded by the BROWSER in every app, and emits\n// `--asset-favicon` / `--asset-apple-icon` from Brand `assets.*`. Reading those\n// gives every app the Brand's VRM tile with no static file in any repo (Global\n// Rules → App Icons) and no extra request.\n//\n// The link carries a fixed id and is kept the LAST icon link in <head>, so a\n// stale icon from metadata or an old static file never wins over the Brand's.\n\n'use client'\n\nimport { useEffect } from 'react'\n\nimport { readBrandAsset } from './brandAssets'\n\nexport const BRAND_FAVICON_LINK_ID = 'politogy-brand-favicon'\nexport const BRAND_APPLE_ICON_LINK_ID = 'politogy-brand-apple-icon'\n\nfunction upsertLink(id: string, rel: string, href: string): void {\n let link = document.getElementById(id) as HTMLLinkElement | null\n if (!link) {\n link = document.createElement('link')\n link.id = id\n link.rel = rel\n }\n if (link.getAttribute('href') !== href) link.href = href\n // Last icon link in <head> wins. Re-appending an already-last node is a no-op\n // for the browser, so only move it when something was added after it.\n const icons = document.head.querySelectorAll(`link[rel~=\"${rel.split(' ')[0]}\"]`)\n if (icons[icons.length - 1] !== link) document.head.appendChild(link)\n}\n\n/**\n * Point the tab icon (and the iOS home-screen icon) at the Brand's uploaded\n * assets. Returns true once the favicon was set; false while the token sheet\n * has not defined `--asset-favicon` yet. Never draws a stand-in.\n */\nexport function applyBrandFavicon(): boolean {\n if (typeof document === 'undefined') return false\n const favicon = readBrandAsset('favicon')\n if (!favicon) return false\n upsertLink(BRAND_FAVICON_LINK_ID, 'icon', favicon)\n const apple = readBrandAsset('apple-icon')\n if (apple) upsertLink(BRAND_APPLE_ICON_LINK_ID, 'apple-touch-icon', apple)\n return true\n}\n\n// The token sheet is a <link> the app appends at startup, so it may still be in\n// flight on first paint. Re-read when it loads and a few times after.\nconst RETRY_MS = [0, 250, 1000, 3000]\n\n/**\n * Apply the Brand favicon now and keep it applied: on token sheet load, on a\n * few retries, and whenever <head> changes (a client navigation can re-insert\n * metadata icon links after ours). Returns a cleanup function.\n */\nexport function watchBrandFavicon(): () => void {\n if (typeof document === 'undefined') return () => {}\n let done = false\n const apply = () => {\n if (!done) applyBrandFavicon()\n }\n // A token sheet inserted after mount is picked up by the observer below.\n const sheets = new Set<HTMLLinkElement>()\n const listen = () => {\n document\n .querySelectorAll<HTMLLinkElement>('link[rel=\"stylesheet\"][href*=\"api/tokens\"]')\n .forEach((l) => {\n if (sheets.has(l)) return\n sheets.add(l)\n l.addEventListener('load', apply)\n })\n }\n listen()\n const timers = RETRY_MS.map((ms) => window.setTimeout(apply, ms))\n const observer =\n typeof MutationObserver === 'undefined'\n ? null\n : new MutationObserver(() => {\n listen()\n apply()\n })\n observer?.observe(document.head, { childList: true })\n return () => {\n done = true\n sheets.forEach((l) => l.removeEventListener('load', apply))\n timers.forEach((t) => window.clearTimeout(t))\n observer?.disconnect()\n }\n}\n\n/** Renders nothing; sets the Brand favicon after mount. PolitogyAppFrame renders it. */\nexport function BrandFavicon(): null {\n useEffect(() => watchBrandFavicon(), [])\n return null\n}\n"]}
@@ -1,4 +1,4 @@
1
1
  "use client";
2
- export { BRAND_APPLE_ICON_LINK_ID, BRAND_FAVICON_LINK_ID, BrandFavicon, applyBrandFavicon, watchBrandFavicon } from './chunk-BMR5WAWH.js';
2
+ export { BRAND_APPLE_ICON_LINK_ID, BRAND_FAVICON_LINK_ID, BrandFavicon, applyBrandFavicon, watchBrandFavicon } from './chunk-Q5DTPY5N.js';
3
3
  //# sourceMappingURL=brand-favicon.js.map
4
4
  //# sourceMappingURL=brand-favicon.js.map
@@ -1,5 +1,5 @@
1
1
  import { orgIdTail, authBaseUrl } from './chunk-5LO5W7W5.js';
2
- import { PageHeaderView, BreadcrumbBarView, resolveOrgLogoUrl, topBarIconButtonClass, TopBarTooltip, TopBarPanelArrow, NavThemeControl, NAV_DISPLAY_OPTIONS, useTopBarIconSlotGlyph, resolveBrandGlyph, TOPBAR_GLYPH_SIZE, resolveTabGlyph, warnDeprecated, SpeculationRulesScript } from './chunk-VAQNZD37.js';
2
+ import { PageHeaderView, BreadcrumbBarView, resolveOrgLogoUrl, topBarIconButtonClass, TopBarTooltip, TopBarPanelArrow, NavThemeControl, NAV_DISPLAY_OPTIONS, useTopBarIconSlotGlyph, resolveBrandGlyph, TOPBAR_GLYPH_SIZE, resolveTabGlyph, warnDeprecated, SpeculationRulesScript } from './chunk-WOOBPM4V.js';
3
3
  import { readRailPreference, writeRailPreference } from './chunk-7YVJ7HQG.js';
4
4
  import { createContext, useRef, useEffect, useMemo, useState, Fragment as Fragment$1, useContext } from 'react';
5
5
  import clsx from 'clsx';
@@ -484,6 +484,33 @@ function shortEnvLabel(label) {
484
484
  if (l === "development") return "dev";
485
485
  return l;
486
486
  }
487
+ function buildLineParts(meta) {
488
+ const version = meta.version ? `v${meta.version.replace(/^v/, "")}` : null;
489
+ const parsed = meta.commitDate ? new Date(meta.commitDate) : null;
490
+ const date = parsed && !Number.isNaN(parsed.getTime()) ? parsed.toLocaleDateString(void 0, { month: "short", day: "numeric" }) : null;
491
+ const line = [version, date].filter(Boolean).join(" \xB7 ");
492
+ const detail = [
493
+ meta.envLabel ? shortEnvLabel(meta.envLabel) : null,
494
+ meta.commit ? meta.commit.slice(0, 12) : null,
495
+ version,
496
+ date,
497
+ meta.commitAuthor ? meta.commitAuthor.split(/\s+/)[0] : null
498
+ ].filter(Boolean).join(" | ");
499
+ return { line, detail };
500
+ }
501
+ function ShellBuildLine({ footer, hidden }) {
502
+ const { line, detail } = buildLineParts(footer);
503
+ if (!line && !detail) return null;
504
+ return /* @__PURE__ */ jsx(
505
+ "div",
506
+ {
507
+ "data-shell-meta": true,
508
+ title: detail,
509
+ className: `mt-1.5 truncate text-center font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[10px] leading-[1.2] text-[color:var(--sidebar-row-text,rgba(255,255,255,0.66))] ${hidden}`,
510
+ children: line || detail
511
+ }
512
+ );
513
+ }
487
514
  function envChipClasses(label) {
488
515
  const l = label.toLowerCase();
489
516
  if (l === "production" || l === "prod")
@@ -986,31 +1013,7 @@ function NavSidebar({
986
1013
  }
987
1014
  )
988
1015
  ] }, l.label)) }) : null,
989
- footer.envLabel || footer.commit || footer.version ? /* @__PURE__ */ jsxs(
990
- "div",
991
- {
992
- "data-shell-meta": true,
993
- className: `mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[11px] text-[color:var(--sidebar-meta-text,rgba(255,255,255,0.72))] ${rc("max-md:hidden")}`,
994
- children: [
995
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: [
996
- footer.envLabel ? shortEnvLabel(footer.envLabel) : null,
997
- footer.commit ? footer.commit.slice(-4) : null,
998
- footer.version ? `v${footer.version.replace(/^v/, "")}` : null
999
- ].filter((v) => Boolean(v)).map((v, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
1000
- i > 0 ? /* @__PURE__ */ jsx("span", { children: "|" }) : null,
1001
- /* @__PURE__ */ jsx("span", { children: v })
1002
- ] }, i)) }),
1003
- footer.commitDate || footer.commitAuthor ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1004
- footer.commitDate ? /* @__PURE__ */ jsx("span", { children: new Date(footer.commitDate).toLocaleDateString(void 0, {
1005
- month: "short",
1006
- day: "numeric"
1007
- }) }) : null,
1008
- footer.commitDate && footer.commitAuthor ? /* @__PURE__ */ jsx("span", { children: "|" }) : null,
1009
- footer.commitAuthor ? /* @__PURE__ */ jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null
1010
- ] }) : null
1011
- ]
1012
- }
1013
- ) : null
1016
+ footer.envLabel || footer.commit || footer.version ? /* @__PURE__ */ jsx(ShellBuildLine, { footer, hidden: rc("max-md:hidden") }) : null
1014
1017
  ] }) : null
1015
1018
  ]
1016
1019
  }
@@ -2030,6 +2033,6 @@ function NavAppShell({
2030
2033
  );
2031
2034
  }
2032
2035
 
2033
- export { BETA_BANNER_DISMISS_KEY, BreadcrumbBar, CAMPAIGN_FEATURE_AVAILABILITY, NavAppShell, NavBetaBanner, NavSidebar, NavTabStrip, NavTopBar, PageHeader, clearBetaBannerDismissal, envChipClasses, isNavItemActive, modeIdFromLabel, shortEnvLabel, shortUserName, standardNavIcons, standardPolitogyNav };
2034
- //# sourceMappingURL=chunk-WQR42AGC.js.map
2035
- //# sourceMappingURL=chunk-WQR42AGC.js.map
2036
+ export { BETA_BANNER_DISMISS_KEY, BreadcrumbBar, CAMPAIGN_FEATURE_AVAILABILITY, NavAppShell, NavBetaBanner, NavSidebar, NavTabStrip, NavTopBar, PageHeader, buildLineParts, clearBetaBannerDismissal, envChipClasses, isNavItemActive, modeIdFromLabel, shortEnvLabel, shortUserName, standardNavIcons, standardPolitogyNav };
2037
+ //# sourceMappingURL=chunk-IVLXZFGV.js.map
2038
+ //# sourceMappingURL=chunk-IVLXZFGV.js.map