@the-portland-company/shell 0.93.0 → 0.95.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/app-chrome.cjs +105 -46
  3. package/dist/app-chrome.cjs.map +1 -1
  4. package/dist/app-chrome.d.cts +2 -1
  5. package/dist/app-chrome.d.ts +2 -1
  6. package/dist/app-chrome.js +6 -50
  7. package/dist/app-chrome.js.map +1 -1
  8. package/dist/app-icons.cjs +9 -0
  9. package/dist/app-icons.cjs.map +1 -1
  10. package/dist/app-icons.d.cts +5 -3
  11. package/dist/app-icons.d.ts +5 -3
  12. package/dist/app-icons.js +9 -1
  13. package/dist/app-icons.js.map +1 -1
  14. package/dist/brand-favicon.cjs +87 -0
  15. package/dist/brand-favicon.cjs.map +1 -0
  16. package/dist/brand-favicon.d.cts +18 -0
  17. package/dist/brand-favicon.d.ts +18 -0
  18. package/dist/brand-favicon.js +4 -0
  19. package/dist/brand-favicon.js.map +1 -0
  20. package/dist/{chunk-EN5ANFMB.js → chunk-BA3GITRQ.js} +4 -4
  21. package/dist/{chunk-EN5ANFMB.js.map → chunk-BA3GITRQ.js.map} +1 -1
  22. package/dist/chunk-BMR5WAWH.js +111 -0
  23. package/dist/chunk-BMR5WAWH.js.map +1 -0
  24. package/dist/{chunk-Y66PVCZO.js → chunk-JQ53VZO5.js} +68 -3
  25. package/dist/chunk-JQ53VZO5.js.map +1 -0
  26. package/dist/{index-BAFJZuRA.d.cts → index-AIzWNzl-.d.cts} +22 -1
  27. package/dist/{index-DoSs3sQY.d.ts → index-Byjq0Whp.d.ts} +22 -1
  28. package/dist/index.cjs +147 -0
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +3 -2
  31. package/dist/index.d.ts +3 -2
  32. package/dist/index.js +3 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/native.cjs +148 -3
  35. package/dist/native.cjs.map +1 -1
  36. package/dist/native.d.cts +2 -1
  37. package/dist/native.d.ts +2 -1
  38. package/dist/native.js +4 -3
  39. package/package.json +6 -1
  40. package/dist/chunk-Y66PVCZO.js.map +0 -1
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/app-icons.ts
4
4
  var SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
5
+ var SPECS_FAVICON_URL = "https://specs.politogyvrm.com/api/favicon";
5
6
  var VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
6
7
  function pick(icons, ...keys) {
7
8
  if (!icons) return null;
@@ -36,6 +37,13 @@ function toMetadataIcons(data) {
36
37
  const icons = data?.icons;
37
38
  const favicon = pick(icons, "favicon-color-square", "favicon-color-round");
38
39
  const apple = pick(icons, "ios-color-square", "ios-color-round", "favicon-color-square") ?? favicon;
40
+ if (!favicon) {
41
+ return {
42
+ icon: [{ url: SPECS_FAVICON_URL }],
43
+ shortcut: [{ url: SPECS_FAVICON_URL }],
44
+ apple: [{ url: `${SPECS_FAVICON_URL}?kind=apple` }]
45
+ };
46
+ }
39
47
  const entry = (url) => url ? [{ url: withVersion(url, data) }] : [];
40
48
  return {
41
49
  icon: entry(favicon),
@@ -44,6 +52,7 @@ function toMetadataIcons(data) {
44
52
  };
45
53
  }
46
54
 
55
+ exports.SPECS_FAVICON_URL = SPECS_FAVICON_URL;
47
56
  exports.SPECS_ICONS_BASE = SPECS_ICONS_BASE;
48
57
  exports.VRM_FALLBACK_ICON = VRM_FALLBACK_ICON;
49
58
  exports.politogyAppIcons = politogyAppIcons;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/app-icons.ts"],"names":[],"mappings":";;;AAuBO,IAAM,gBAAA,GAAmB;AAOzB,IAAM,iBAAA,GACX;AAmBF,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,gBAAgB,IAAI,CAAA;AAAA,EAC7B;AACF;AASA,SAAS,WAAA,CAAY,KAAa,IAAA,EAA4C;AAC5E,EAAA,MAAM,GAAA,GACJ,OAAO,IAAA,EAAM,OAAA,KAAY,WACrB,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,GACnB,IAAA,EAAM,SAAA,GACJ,OAAO,IAAA,CAAK,KAAA,CAAM,KAAK,SAAS,CAAA,IAAK,EAAE,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,GAC1D,EAAA;AACR,EAAA,IAAI,CAAC,KAAK,OAAO,GAAA;AACjB,EAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAG,GAAA,CAAI,QAAA,CAAS,GAAG,CAAA,GAAI,GAAA,GAAM,GAAG,CAAA,EAAA,EAAK,GAAG,CAAA,CAAA;AACvD;AAGO,SAAS,gBAAgB,IAAA,EAA2D;AACzF,EAAA,MAAM,QAAQ,IAAA,EAAM,KAAA;AAGpB,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,EAAO,sBAAA,EAAwB,qBAAqB,CAAA;AACzE,EAAA,MAAM,QACJ,IAAA,CAAK,KAAA,EAAO,kBAAA,EAAoB,iBAAA,EAAmB,sBAAsB,CAAA,IAAK,OAAA;AAChF,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,KAAwB,GAAA,GAAM,CAAC,EAAE,GAAA,EAAK,WAAA,CAAY,GAAA,EAAK,IAAI,CAAA,EAAG,IAAI,EAAC;AAClF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAM,OAAO,CAAA;AAAA,IACnB,QAAA,EAAU,MAAM,OAAO,CAAA;AAAA,IACvB,KAAA,EAAO,MAAM,KAAK;AAAA,GACpB;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 platform favicon (Brand\n// `assets.favicon`) from the API, with no per-app defaulting logic.\n//\n// The shell itself carries NO icon image and never substitutes one Brand asset\n// for another (Brand guideline \"Logo standard: use the uploaded Brand assets\n// as-is\"). If the API cannot be reached, or returns no browser-tab icon, this\n// returns no icon entries at all rather than a baked-in stand-in.\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 * @deprecated No longer used and will be removed. It was a hardcoded copy of an\n * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy\n * of one Brand asset substituted for another, which the Brand forbids.\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 /** Empty when the Brand API supplied no browser-tab icon — never a stand-in. */\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 it\n * returns no icon entries, 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 toMetadataIcons(null)\n }\n}\n\n/**\n * Cache-buster: stamp the Brand revision onto an icon URL as `?v=<version>`\n * (falling back to the `updatedAt` timestamp). The stored filenames are already\n * content-hashed, but favicons are cached so aggressively by browsers that a new\n * Brand publish can still show stale; a version-keyed URL forces every publish to\n * be treated as a distinct resource. No-op for an unknown rev.\n */\nfunction withVersion(url: string, data: PolitogyIconsResponse | null): string {\n const rev =\n typeof data?.version === 'number'\n ? String(data.version)\n : data?.updatedAt\n ? String(Date.parse(data.updatedAt) || '').replace(/\\D/g, '')\n : ''\n if (!rev) return url\n return `${url}${url.includes('?') ? '&' : '?'}v=${rev}`\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 // The API has already resolved the chain down to Brand `assets.favicon`\n // (and `assets.appleIcon` for iOS). Nothing is substituted here.\n const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round')\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n const entry = (url: string | null) => (url ? [{ url: withVersion(url, data) }] : [])\n return {\n icon: entry(favicon),\n shortcut: entry(favicon),\n apple: entry(apple),\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/app-icons.ts"],"names":[],"mappings":"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","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 platform favicon (Brand\n// `assets.favicon`) from the API, with no per-app defaulting logic.\n//\n// The shell itself carries NO icon image and never substitutes one Brand asset\n// for another (Brand guideline \"Logo standard: use the uploaded Brand assets\n// as-is\"). If the API cannot be reached, or returns no browser-tab icon, this\n// returns SPECS_FAVICON_URL: Specs' public redirect to Brand `assets.favicon`,\n// which the BROWSER resolves. In production Cloudflare's bot check challenges\n// the server-side fetch below, so this is the usual path there. The link is in\n// the server HTML, so the browser never falls back to /favicon.ico.\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/** Public 302 to Brand `assets.favicon` (`?kind=apple` → `assets.appleIcon`). */\nexport const SPECS_FAVICON_URL = 'https://specs.politogyvrm.com/api/favicon'\n\n/**\n * @deprecated No longer used and will be removed. It was a hardcoded copy of an\n * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy\n * of one Brand asset substituted for another, which the Brand forbids.\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 /** Falls back to SPECS_FAVICON_URL when the Brand API supplied no browser-tab icon. */\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 it\n * returns the SPECS_FAVICON_URL redirect, 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 toMetadataIcons(null)\n }\n}\n\n/**\n * Cache-buster: stamp the Brand revision onto an icon URL as `?v=<version>`\n * (falling back to the `updatedAt` timestamp). The stored filenames are already\n * content-hashed, but favicons are cached so aggressively by browsers that a new\n * Brand publish can still show stale; a version-keyed URL forces every publish to\n * be treated as a distinct resource. No-op for an unknown rev.\n */\nfunction withVersion(url: string, data: PolitogyIconsResponse | null): string {\n const rev =\n typeof data?.version === 'number'\n ? String(data.version)\n : data?.updatedAt\n ? String(Date.parse(data.updatedAt) || '').replace(/\\D/g, '')\n : ''\n if (!rev) return url\n return `${url}${url.includes('?') ? '&' : '?'}v=${rev}`\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 // The API has already resolved the chain down to Brand `assets.favicon`\n // (and `assets.appleIcon` for iOS). Nothing is substituted here.\n const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round')\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n if (!favicon) {\n // No resolved icon (API unreachable, or nothing set): let the browser\n // follow Specs' redirect to the Brand favicon. Never a bundled image.\n return {\n icon: [{ url: SPECS_FAVICON_URL }],\n shortcut: [{ url: SPECS_FAVICON_URL }],\n apple: [{ url: `${SPECS_FAVICON_URL}?kind=apple` }],\n }\n }\n const entry = (url: string | null) => (url ? [{ url: withVersion(url, data) }] : [])\n return {\n icon: entry(favicon),\n shortcut: entry(favicon),\n apple: entry(apple),\n }\n}\n"]}
@@ -1,4 +1,6 @@
1
1
  declare const SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
2
+ /** Public 302 to Brand `assets.favicon` (`?kind=apple` → `assets.appleIcon`). */
3
+ declare const SPECS_FAVICON_URL = "https://specs.politogyvrm.com/api/favicon";
2
4
  /**
3
5
  * @deprecated No longer used and will be removed. It was a hardcoded copy of an
4
6
  * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy
@@ -17,7 +19,7 @@ type PolitogyIconsResponse = {
17
19
  };
18
20
  /** Next.js `metadata.icons` shape (a subset — url/rel entries). */
19
21
  type PolitogyMetadataIcons = {
20
- /** Empty when the Brand API supplied no browser-tab icon — never a stand-in. */
22
+ /** Falls back to SPECS_FAVICON_URL when the Brand API supplied no browser-tab icon. */
21
23
  icon: {
22
24
  url: string;
23
25
  }[];
@@ -33,7 +35,7 @@ type PolitogyMetadataIcons = {
33
35
  *
34
36
  * Cached for 60s with revalidation, matching the tokens contract — a Brand edit
35
37
  * shows up promptly without defeating caching. Never throws: on any failure it
36
- * returns no icon entries, so an app can call this unguarded in metadata.
38
+ * returns the SPECS_FAVICON_URL redirect, so an app can call this unguarded in metadata.
37
39
  */
38
40
  declare function politogyAppIcons(app: string, init?: {
39
41
  baseUrl?: string;
@@ -43,4 +45,4 @@ declare function politogyAppIcons(app: string, init?: {
43
45
  /** Pure shaping step — exported for tests and for callers with their own fetch. */
44
46
  declare function toMetadataIcons(data: PolitogyIconsResponse | null): PolitogyMetadataIcons;
45
47
 
46
- export { type PolitogyIconVariant, type PolitogyIconsResponse, type PolitogyMetadataIcons, SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
48
+ export { type PolitogyIconVariant, type PolitogyIconsResponse, type PolitogyMetadataIcons, SPECS_FAVICON_URL, SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
@@ -1,4 +1,6 @@
1
1
  declare const SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
2
+ /** Public 302 to Brand `assets.favicon` (`?kind=apple` → `assets.appleIcon`). */
3
+ declare const SPECS_FAVICON_URL = "https://specs.politogyvrm.com/api/favicon";
2
4
  /**
3
5
  * @deprecated No longer used and will be removed. It was a hardcoded copy of an
4
6
  * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy
@@ -17,7 +19,7 @@ type PolitogyIconsResponse = {
17
19
  };
18
20
  /** Next.js `metadata.icons` shape (a subset — url/rel entries). */
19
21
  type PolitogyMetadataIcons = {
20
- /** Empty when the Brand API supplied no browser-tab icon — never a stand-in. */
22
+ /** Falls back to SPECS_FAVICON_URL when the Brand API supplied no browser-tab icon. */
21
23
  icon: {
22
24
  url: string;
23
25
  }[];
@@ -33,7 +35,7 @@ type PolitogyMetadataIcons = {
33
35
  *
34
36
  * Cached for 60s with revalidation, matching the tokens contract — a Brand edit
35
37
  * shows up promptly without defeating caching. Never throws: on any failure it
36
- * returns no icon entries, so an app can call this unguarded in metadata.
38
+ * returns the SPECS_FAVICON_URL redirect, so an app can call this unguarded in metadata.
37
39
  */
38
40
  declare function politogyAppIcons(app: string, init?: {
39
41
  baseUrl?: string;
@@ -43,4 +45,4 @@ declare function politogyAppIcons(app: string, init?: {
43
45
  /** Pure shaping step — exported for tests and for callers with their own fetch. */
44
46
  declare function toMetadataIcons(data: PolitogyIconsResponse | null): PolitogyMetadataIcons;
45
47
 
46
- export { type PolitogyIconVariant, type PolitogyIconsResponse, type PolitogyMetadataIcons, SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
48
+ export { type PolitogyIconVariant, type PolitogyIconsResponse, type PolitogyMetadataIcons, SPECS_FAVICON_URL, SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
package/dist/app-icons.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/app-icons.ts
2
2
  var SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
3
+ var SPECS_FAVICON_URL = "https://specs.politogyvrm.com/api/favicon";
3
4
  var VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
4
5
  function pick(icons, ...keys) {
5
6
  if (!icons) return null;
@@ -34,6 +35,13 @@ function toMetadataIcons(data) {
34
35
  const icons = data?.icons;
35
36
  const favicon = pick(icons, "favicon-color-square", "favicon-color-round");
36
37
  const apple = pick(icons, "ios-color-square", "ios-color-round", "favicon-color-square") ?? favicon;
38
+ if (!favicon) {
39
+ return {
40
+ icon: [{ url: SPECS_FAVICON_URL }],
41
+ shortcut: [{ url: SPECS_FAVICON_URL }],
42
+ apple: [{ url: `${SPECS_FAVICON_URL}?kind=apple` }]
43
+ };
44
+ }
37
45
  const entry = (url) => url ? [{ url: withVersion(url, data) }] : [];
38
46
  return {
39
47
  icon: entry(favicon),
@@ -42,6 +50,6 @@ function toMetadataIcons(data) {
42
50
  };
43
51
  }
44
52
 
45
- export { SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
53
+ export { SPECS_FAVICON_URL, SPECS_ICONS_BASE, VRM_FALLBACK_ICON, politogyAppIcons, toMetadataIcons };
46
54
  //# sourceMappingURL=app-icons.js.map
47
55
  //# sourceMappingURL=app-icons.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/app-icons.ts"],"names":[],"mappings":";AAuBO,IAAM,gBAAA,GAAmB;AAOzB,IAAM,iBAAA,GACX;AAmBF,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,gBAAgB,IAAI,CAAA;AAAA,EAC7B;AACF;AASA,SAAS,WAAA,CAAY,KAAa,IAAA,EAA4C;AAC5E,EAAA,MAAM,GAAA,GACJ,OAAO,IAAA,EAAM,OAAA,KAAY,WACrB,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,GACnB,IAAA,EAAM,SAAA,GACJ,OAAO,IAAA,CAAK,KAAA,CAAM,KAAK,SAAS,CAAA,IAAK,EAAE,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,GAC1D,EAAA;AACR,EAAA,IAAI,CAAC,KAAK,OAAO,GAAA;AACjB,EAAA,OAAO,CAAA,EAAG,GAAG,CAAA,EAAG,GAAA,CAAI,QAAA,CAAS,GAAG,CAAA,GAAI,GAAA,GAAM,GAAG,CAAA,EAAA,EAAK,GAAG,CAAA,CAAA;AACvD;AAGO,SAAS,gBAAgB,IAAA,EAA2D;AACzF,EAAA,MAAM,QAAQ,IAAA,EAAM,KAAA;AAGpB,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,EAAO,sBAAA,EAAwB,qBAAqB,CAAA;AACzE,EAAA,MAAM,QACJ,IAAA,CAAK,KAAA,EAAO,kBAAA,EAAoB,iBAAA,EAAmB,sBAAsB,CAAA,IAAK,OAAA;AAChF,EAAA,MAAM,KAAA,GAAQ,CAAC,GAAA,KAAwB,GAAA,GAAM,CAAC,EAAE,GAAA,EAAK,WAAA,CAAY,GAAA,EAAK,IAAI,CAAA,EAAG,IAAI,EAAC;AAClF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAM,OAAO,CAAA;AAAA,IACnB,QAAA,EAAU,MAAM,OAAO,CAAA;AAAA,IACvB,KAAA,EAAO,MAAM,KAAK;AAAA,GACpB;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 platform favicon (Brand\n// `assets.favicon`) from the API, with no per-app defaulting logic.\n//\n// The shell itself carries NO icon image and never substitutes one Brand asset\n// for another (Brand guideline \"Logo standard: use the uploaded Brand assets\n// as-is\"). If the API cannot be reached, or returns no browser-tab icon, this\n// returns no icon entries at all rather than a baked-in stand-in.\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 * @deprecated No longer used and will be removed. It was a hardcoded copy of an\n * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy\n * of one Brand asset substituted for another, which the Brand forbids.\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 /** Empty when the Brand API supplied no browser-tab icon — never a stand-in. */\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 it\n * returns no icon entries, 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 toMetadataIcons(null)\n }\n}\n\n/**\n * Cache-buster: stamp the Brand revision onto an icon URL as `?v=<version>`\n * (falling back to the `updatedAt` timestamp). The stored filenames are already\n * content-hashed, but favicons are cached so aggressively by browsers that a new\n * Brand publish can still show stale; a version-keyed URL forces every publish to\n * be treated as a distinct resource. No-op for an unknown rev.\n */\nfunction withVersion(url: string, data: PolitogyIconsResponse | null): string {\n const rev =\n typeof data?.version === 'number'\n ? String(data.version)\n : data?.updatedAt\n ? String(Date.parse(data.updatedAt) || '').replace(/\\D/g, '')\n : ''\n if (!rev) return url\n return `${url}${url.includes('?') ? '&' : '?'}v=${rev}`\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 // The API has already resolved the chain down to Brand `assets.favicon`\n // (and `assets.appleIcon` for iOS). Nothing is substituted here.\n const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round')\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n const entry = (url: string | null) => (url ? [{ url: withVersion(url, data) }] : [])\n return {\n icon: entry(favicon),\n shortcut: entry(favicon),\n apple: entry(apple),\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/app-icons.ts"],"names":[],"mappings":"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","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 platform favicon (Brand\n// `assets.favicon`) from the API, with no per-app defaulting logic.\n//\n// The shell itself carries NO icon image and never substitutes one Brand asset\n// for another (Brand guideline \"Logo standard: use the uploaded Brand assets\n// as-is\"). If the API cannot be reached, or returns no browser-tab icon, this\n// returns SPECS_FAVICON_URL: Specs' public redirect to Brand `assets.favicon`,\n// which the BROWSER resolves. In production Cloudflare's bot check challenges\n// the server-side fetch below, so this is the usual path there. The link is in\n// the server HTML, so the browser never falls back to /favicon.ico.\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/** Public 302 to Brand `assets.favicon` (`?kind=apple` → `assets.appleIcon`). */\nexport const SPECS_FAVICON_URL = 'https://specs.politogyvrm.com/api/favicon'\n\n/**\n * @deprecated No longer used and will be removed. It was a hardcoded copy of an\n * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy\n * of one Brand asset substituted for another, which the Brand forbids.\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 /** Falls back to SPECS_FAVICON_URL when the Brand API supplied no browser-tab icon. */\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 it\n * returns the SPECS_FAVICON_URL redirect, 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 toMetadataIcons(null)\n }\n}\n\n/**\n * Cache-buster: stamp the Brand revision onto an icon URL as `?v=<version>`\n * (falling back to the `updatedAt` timestamp). The stored filenames are already\n * content-hashed, but favicons are cached so aggressively by browsers that a new\n * Brand publish can still show stale; a version-keyed URL forces every publish to\n * be treated as a distinct resource. No-op for an unknown rev.\n */\nfunction withVersion(url: string, data: PolitogyIconsResponse | null): string {\n const rev =\n typeof data?.version === 'number'\n ? String(data.version)\n : data?.updatedAt\n ? String(Date.parse(data.updatedAt) || '').replace(/\\D/g, '')\n : ''\n if (!rev) return url\n return `${url}${url.includes('?') ? '&' : '?'}v=${rev}`\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 // The API has already resolved the chain down to Brand `assets.favicon`\n // (and `assets.appleIcon` for iOS). Nothing is substituted here.\n const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round')\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n if (!favicon) {\n // No resolved icon (API unreachable, or nothing set): let the browser\n // follow Specs' redirect to the Brand favicon. Never a bundled image.\n return {\n icon: [{ url: SPECS_FAVICON_URL }],\n shortcut: [{ url: SPECS_FAVICON_URL }],\n apple: [{ url: `${SPECS_FAVICON_URL}?kind=apple` }],\n }\n }\n const entry = (url: string | null) => (url ? [{ url: withVersion(url, data) }] : [])\n return {\n icon: entry(favicon),\n shortcut: entry(favicon),\n apple: entry(apple),\n }\n}\n"]}
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+
6
+ function parseCssUrl(value) {
7
+ if (!value) return null;
8
+ const m = /^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(value.trim());
9
+ const url = m?.[2]?.trim();
10
+ return url && /^https:\/\//i.test(url) ? url : null;
11
+ }
12
+ function readBrandAsset(kind) {
13
+ if (typeof document === "undefined") return null;
14
+ try {
15
+ return parseCssUrl(
16
+ window.getComputedStyle(document.documentElement).getPropertyValue(`--asset-${kind}`)
17
+ );
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ // src/lib/brandFavicon.ts
24
+ var BRAND_FAVICON_LINK_ID = "politogy-brand-favicon";
25
+ var BRAND_APPLE_ICON_LINK_ID = "politogy-brand-apple-icon";
26
+ function upsertLink(id, rel, href) {
27
+ let link = document.getElementById(id);
28
+ if (!link) {
29
+ link = document.createElement("link");
30
+ link.id = id;
31
+ link.rel = rel;
32
+ }
33
+ if (link.getAttribute("href") !== href) link.href = href;
34
+ const icons = document.head.querySelectorAll(`link[rel~="${rel.split(" ")[0]}"]`);
35
+ if (icons[icons.length - 1] !== link) document.head.appendChild(link);
36
+ }
37
+ function applyBrandFavicon() {
38
+ if (typeof document === "undefined") return false;
39
+ const favicon = readBrandAsset("favicon");
40
+ if (!favicon) return false;
41
+ upsertLink(BRAND_FAVICON_LINK_ID, "icon", favicon);
42
+ const apple = readBrandAsset("apple-icon");
43
+ if (apple) upsertLink(BRAND_APPLE_ICON_LINK_ID, "apple-touch-icon", apple);
44
+ return true;
45
+ }
46
+ var RETRY_MS = [0, 250, 1e3, 3e3];
47
+ function watchBrandFavicon() {
48
+ if (typeof document === "undefined") return () => {
49
+ };
50
+ let done = false;
51
+ const apply = () => {
52
+ if (!done) applyBrandFavicon();
53
+ };
54
+ const sheets = /* @__PURE__ */ new Set();
55
+ const listen = () => {
56
+ document.querySelectorAll('link[rel="stylesheet"][href*="api/tokens"]').forEach((l) => {
57
+ if (sheets.has(l)) return;
58
+ sheets.add(l);
59
+ l.addEventListener("load", apply);
60
+ });
61
+ };
62
+ listen();
63
+ const timers = RETRY_MS.map((ms) => window.setTimeout(apply, ms));
64
+ const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(() => {
65
+ listen();
66
+ apply();
67
+ });
68
+ observer?.observe(document.head, { childList: true });
69
+ return () => {
70
+ done = true;
71
+ sheets.forEach((l) => l.removeEventListener("load", apply));
72
+ timers.forEach((t) => window.clearTimeout(t));
73
+ observer?.disconnect();
74
+ };
75
+ }
76
+ function BrandFavicon() {
77
+ react.useEffect(() => watchBrandFavicon(), []);
78
+ return null;
79
+ }
80
+
81
+ exports.BRAND_APPLE_ICON_LINK_ID = BRAND_APPLE_ICON_LINK_ID;
82
+ exports.BRAND_FAVICON_LINK_ID = BRAND_FAVICON_LINK_ID;
83
+ exports.BrandFavicon = BrandFavicon;
84
+ exports.applyBrandFavicon = applyBrandFavicon;
85
+ exports.watchBrandFavicon = watchBrandFavicon;
86
+ //# sourceMappingURL=brand-favicon.cjs.map
87
+ //# sourceMappingURL=brand-favicon.cjs.map
@@ -0,0 +1 @@
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'\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"]}
@@ -0,0 +1,18 @@
1
+ declare const BRAND_FAVICON_LINK_ID = "politogy-brand-favicon";
2
+ declare const BRAND_APPLE_ICON_LINK_ID = "politogy-brand-apple-icon";
3
+ /**
4
+ * Point the tab icon (and the iOS home-screen icon) at the Brand's uploaded
5
+ * assets. Returns true once the favicon was set; false while the token sheet
6
+ * has not defined `--asset-favicon` yet. Never draws a stand-in.
7
+ */
8
+ declare function applyBrandFavicon(): boolean;
9
+ /**
10
+ * Apply the Brand favicon now and keep it applied: on token sheet load, on a
11
+ * few retries, and whenever <head> changes (a client navigation can re-insert
12
+ * metadata icon links after ours). Returns a cleanup function.
13
+ */
14
+ declare function watchBrandFavicon(): () => void;
15
+ /** Renders nothing; sets the Brand favicon after mount. PolitogyAppFrame renders it. */
16
+ declare function BrandFavicon(): null;
17
+
18
+ export { BRAND_APPLE_ICON_LINK_ID, BRAND_FAVICON_LINK_ID, BrandFavicon, applyBrandFavicon, watchBrandFavicon };
@@ -0,0 +1,18 @@
1
+ declare const BRAND_FAVICON_LINK_ID = "politogy-brand-favicon";
2
+ declare const BRAND_APPLE_ICON_LINK_ID = "politogy-brand-apple-icon";
3
+ /**
4
+ * Point the tab icon (and the iOS home-screen icon) at the Brand's uploaded
5
+ * assets. Returns true once the favicon was set; false while the token sheet
6
+ * has not defined `--asset-favicon` yet. Never draws a stand-in.
7
+ */
8
+ declare function applyBrandFavicon(): boolean;
9
+ /**
10
+ * Apply the Brand favicon now and keep it applied: on token sheet load, on a
11
+ * few retries, and whenever <head> changes (a client navigation can re-insert
12
+ * metadata icon links after ours). Returns a cleanup function.
13
+ */
14
+ declare function watchBrandFavicon(): () => void;
15
+ /** Renders nothing; sets the Brand favicon after mount. PolitogyAppFrame renders it. */
16
+ declare function BrandFavicon(): null;
17
+
18
+ export { BRAND_APPLE_ICON_LINK_ID, BRAND_FAVICON_LINK_ID, BrandFavicon, applyBrandFavicon, watchBrandFavicon };
@@ -0,0 +1,4 @@
1
+ "use client";
2
+ export { BRAND_APPLE_ICON_LINK_ID, BRAND_FAVICON_LINK_ID, BrandFavicon, applyBrandFavicon, watchBrandFavicon } from './chunk-BMR5WAWH.js';
3
+ //# sourceMappingURL=brand-favicon.js.map
4
+ //# sourceMappingURL=brand-favicon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"brand-favicon.js"}
@@ -1,6 +1,6 @@
1
- import { PageHeaderView, BreadcrumbBarView, resolveOrgLogoUrl, topBarIconButtonClass, TopBarTooltip, TopBarPanelArrow, NavThemeControl, NAV_DISPLAY_OPTIONS, useTopBarIconSlotGlyph, resolveBrandGlyph, TOPBAR_GLYPH_SIZE, resolveTabGlyph, warnDeprecated, SpeculationRulesScript } from './chunk-Y66PVCZO.js';
2
- import { readRailPreference, writeRailPreference } from './chunk-7YVJ7HQG.js';
3
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-JQ53VZO5.js';
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';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -2026,5 +2026,5 @@ function NavAppShell({
2026
2026
  }
2027
2027
 
2028
2028
  export { BETA_BANNER_DISMISS_KEY, BreadcrumbBar, CAMPAIGN_FEATURE_AVAILABILITY, NavAppShell, NavBetaBanner, NavSidebar, NavTabStrip, NavTopBar, PageHeader, clearBetaBannerDismissal, envChipClasses, isNavItemActive, modeIdFromLabel, shortEnvLabel, shortUserName, standardNavIcons, standardPolitogyNav };
2029
- //# sourceMappingURL=chunk-EN5ANFMB.js.map
2030
- //# sourceMappingURL=chunk-EN5ANFMB.js.map
2029
+ //# sourceMappingURL=chunk-BA3GITRQ.js.map
2030
+ //# sourceMappingURL=chunk-BA3GITRQ.js.map