@the-portland-company/shell 0.68.0 → 0.69.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.
@@ -24,27 +24,23 @@ async function politogyAppIcons(app, init) {
24
24
  const data = await res.json();
25
25
  return toMetadataIcons(data);
26
26
  } catch {
27
- return {
28
- icon: [{ url: VRM_FALLBACK_ICON }],
29
- shortcut: [{ url: VRM_FALLBACK_ICON }],
30
- apple: [{ url: VRM_FALLBACK_ICON }]
31
- };
27
+ return toMetadataIcons(null);
32
28
  }
33
29
  }
34
30
  function withVersion(url, data) {
35
- if (url === VRM_FALLBACK_ICON) return url;
36
31
  const rev = typeof data?.version === "number" ? String(data.version) : data?.updatedAt ? String(Date.parse(data.updatedAt) || "").replace(/\D/g, "") : "";
37
32
  if (!rev) return url;
38
33
  return `${url}${url.includes("?") ? "&" : "?"}v=${rev}`;
39
34
  }
40
35
  function toMetadataIcons(data) {
41
36
  const icons = data?.icons;
42
- const favicon = pick(icons, "favicon-color-square", "favicon-color-round") ?? VRM_FALLBACK_ICON;
37
+ const favicon = pick(icons, "favicon-color-square", "favicon-color-round");
43
38
  const apple = pick(icons, "ios-color-square", "ios-color-round", "favicon-color-square") ?? favicon;
39
+ const entry = (url) => url ? [{ url: withVersion(url, data) }] : [];
44
40
  return {
45
- icon: [{ url: withVersion(favicon, data) }],
46
- shortcut: [{ url: withVersion(favicon, data) }],
47
- apple: [{ url: withVersion(apple, data) }]
41
+ icon: entry(favicon),
42
+ shortcut: entry(favicon),
43
+ apple: entry(apple)
48
44
  };
49
45
  }
50
46
 
@@ -1 +1 @@
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 VRM logo — which is exactly the\n// requested behavior (HITL 2026-07-23), with no per-app defaulting logic.\n//\n// Server-safe: no React, no client deps. Next apps call this from\n// `generateMetadata()` so the icon updates when Brand changes, without a\n// redeploy of the app.\n// ---------------------------------------------------------------------------\n\nexport const SPECS_ICONS_BASE = 'https://specs.politogyvrm.com/api/icons'\n\n/**\n * The VRM Core square logo — last-resort icon used only when the Brand API is\n * unreachable (the API's own fallback chain already ends here). Taken from the\n * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens\n * link keeps the live value authoritative everywhere else.\n */\nexport const VRM_FALLBACK_ICON =\n 'https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png'\n\nexport type PolitogyIconVariant = { url: string; fallback?: boolean }\n\nexport type PolitogyIconsResponse = {\n app: string\n version?: number\n updatedAt?: string\n icons: Record<string, PolitogyIconVariant>\n}\n\n/** Next.js `metadata.icons` shape (a subset — url/rel entries). */\nexport type PolitogyMetadataIcons = {\n icon: { url: string }[]\n shortcut?: { url: string }[]\n apple?: { url: string }[]\n}\n\nfunction pick(\n icons: Record<string, PolitogyIconVariant> | undefined,\n ...keys: string[]\n): string | null {\n if (!icons) return null\n for (const key of keys) {\n const hit = icons[key]?.url\n if (hit) return hit\n }\n return null\n}\n\n/**\n * Fetch the resolved icon set for an app and shape it for Next `metadata`.\n *\n * Cached for 60s with revalidation, matching the tokens contract — a Brand edit\n * shows up promptly without defeating caching. Never throws: on any failure the\n * VRM Core logo is returned, so an app can call this unguarded in metadata.\n */\nexport async function politogyAppIcons(\n app: string,\n init?: { baseUrl?: string; revalidateSeconds?: number; fetchImpl?: typeof globalThis.fetch },\n): Promise<PolitogyMetadataIcons> {\n const base = init?.baseUrl ?? SPECS_ICONS_BASE\n const revalidate = init?.revalidateSeconds ?? 60\n const doFetch = init?.fetchImpl ?? globalThis.fetch\n try {\n const res = await doFetch(`${base}/${encodeURIComponent(app)}`, {\n // `next.revalidate` is ignored by non-Next fetch implementations.\n next: { revalidate },\n } as RequestInit)\n if (!res.ok) throw new Error(`icons ${app}: ${res.status}`)\n const data = (await res.json()) as PolitogyIconsResponse\n return toMetadataIcons(data)\n } catch {\n return {\n icon: [{ url: VRM_FALLBACK_ICON }],\n shortcut: [{ url: VRM_FALLBACK_ICON }],\n apple: [{ url: VRM_FALLBACK_ICON }],\n }\n }\n}\n\n/**\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 the fallback logo / unknown rev.\n */\nfunction withVersion(url: string, data: PolitogyIconsResponse | null): string {\n if (url === VRM_FALLBACK_ICON) return url\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 const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round') ?? VRM_FALLBACK_ICON\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n return {\n icon: [{ url: withVersion(favicon, data) }],\n shortcut: [{ url: withVersion(favicon, data) }],\n apple: [{ url: withVersion(apple, data) }],\n }\n}\n"]}
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,9 +1,8 @@
1
1
  declare const SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
2
2
  /**
3
- * The VRM Core square logo — last-resort icon used only when the Brand API is
4
- * unreachable (the API's own fallback chain already ends here). Taken from the
5
- * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens
6
- * link keeps the live value authoritative everywhere else.
3
+ * @deprecated No longer used and will be removed. It was a hardcoded copy of an
4
+ * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy
5
+ * of one Brand asset substituted for another, which the Brand forbids.
7
6
  */
8
7
  declare const VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
9
8
  type PolitogyIconVariant = {
@@ -18,6 +17,7 @@ type PolitogyIconsResponse = {
18
17
  };
19
18
  /** Next.js `metadata.icons` shape (a subset — url/rel entries). */
20
19
  type PolitogyMetadataIcons = {
20
+ /** Empty when the Brand API supplied no browser-tab icon — never a stand-in. */
21
21
  icon: {
22
22
  url: string;
23
23
  }[];
@@ -32,8 +32,8 @@ type PolitogyMetadataIcons = {
32
32
  * Fetch the resolved icon set for an app and shape it for Next `metadata`.
33
33
  *
34
34
  * Cached for 60s with revalidation, matching the tokens contract — a Brand edit
35
- * shows up promptly without defeating caching. Never throws: on any failure the
36
- * VRM Core logo is returned, so an app can call this unguarded in metadata.
35
+ * 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.
37
37
  */
38
38
  declare function politogyAppIcons(app: string, init?: {
39
39
  baseUrl?: string;
@@ -1,9 +1,8 @@
1
1
  declare const SPECS_ICONS_BASE = "https://specs.politogyvrm.com/api/icons";
2
2
  /**
3
- * The VRM Core square logo — last-resort icon used only when the Brand API is
4
- * unreachable (the API's own fallback chain already ends here). Taken from the
5
- * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens
6
- * link keeps the live value authoritative everywhere else.
3
+ * @deprecated No longer used and will be removed. It was a hardcoded copy of an
4
+ * old `assets.logoSquare` upload, used as a stand-in favicon — a bundled copy
5
+ * of one Brand asset substituted for another, which the Brand forbids.
7
6
  */
8
7
  declare const VRM_FALLBACK_ICON = "https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png";
9
8
  type PolitogyIconVariant = {
@@ -18,6 +17,7 @@ type PolitogyIconsResponse = {
18
17
  };
19
18
  /** Next.js `metadata.icons` shape (a subset — url/rel entries). */
20
19
  type PolitogyMetadataIcons = {
20
+ /** Empty when the Brand API supplied no browser-tab icon — never a stand-in. */
21
21
  icon: {
22
22
  url: string;
23
23
  }[];
@@ -32,8 +32,8 @@ type PolitogyMetadataIcons = {
32
32
  * Fetch the resolved icon set for an app and shape it for Next `metadata`.
33
33
  *
34
34
  * Cached for 60s with revalidation, matching the tokens contract — a Brand edit
35
- * shows up promptly without defeating caching. Never throws: on any failure the
36
- * VRM Core logo is returned, so an app can call this unguarded in metadata.
35
+ * 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.
37
37
  */
38
38
  declare function politogyAppIcons(app: string, init?: {
39
39
  baseUrl?: string;
package/dist/app-icons.js CHANGED
@@ -22,27 +22,23 @@ async function politogyAppIcons(app, init) {
22
22
  const data = await res.json();
23
23
  return toMetadataIcons(data);
24
24
  } catch {
25
- return {
26
- icon: [{ url: VRM_FALLBACK_ICON }],
27
- shortcut: [{ url: VRM_FALLBACK_ICON }],
28
- apple: [{ url: VRM_FALLBACK_ICON }]
29
- };
25
+ return toMetadataIcons(null);
30
26
  }
31
27
  }
32
28
  function withVersion(url, data) {
33
- if (url === VRM_FALLBACK_ICON) return url;
34
29
  const rev = typeof data?.version === "number" ? String(data.version) : data?.updatedAt ? String(Date.parse(data.updatedAt) || "").replace(/\D/g, "") : "";
35
30
  if (!rev) return url;
36
31
  return `${url}${url.includes("?") ? "&" : "?"}v=${rev}`;
37
32
  }
38
33
  function toMetadataIcons(data) {
39
34
  const icons = data?.icons;
40
- const favicon = pick(icons, "favicon-color-square", "favicon-color-round") ?? VRM_FALLBACK_ICON;
35
+ const favicon = pick(icons, "favicon-color-square", "favicon-color-round");
41
36
  const apple = pick(icons, "ios-color-square", "ios-color-round", "favicon-color-square") ?? favicon;
37
+ const entry = (url) => url ? [{ url: withVersion(url, data) }] : [];
42
38
  return {
43
- icon: [{ url: withVersion(favicon, data) }],
44
- shortcut: [{ url: withVersion(favicon, data) }],
45
- apple: [{ url: withVersion(apple, data) }]
39
+ icon: entry(favicon),
40
+ shortcut: entry(favicon),
41
+ apple: entry(apple)
46
42
  };
47
43
  }
48
44
 
@@ -1 +1 @@
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 VRM logo — which is exactly the\n// requested behavior (HITL 2026-07-23), with no per-app defaulting logic.\n//\n// Server-safe: no React, no client deps. Next apps call this from\n// `generateMetadata()` so the icon updates when Brand changes, without a\n// redeploy of the app.\n// ---------------------------------------------------------------------------\n\nexport const SPECS_ICONS_BASE = 'https://specs.politogyvrm.com/api/icons'\n\n/**\n * The VRM Core square logo — last-resort icon used only when the Brand API is\n * unreachable (the API's own fallback chain already ends here). Taken from the\n * Brand `assets.logoSquare` token; it is a stable storage URL, and the tokens\n * link keeps the live value authoritative everywhere else.\n */\nexport const VRM_FALLBACK_ICON =\n 'https://lzhzqtzwlwdffxhjjrba.supabase.co/storage/v1/object/public/brand-assets/logo-square/logo-square-mrspraen.png'\n\nexport type PolitogyIconVariant = { url: string; fallback?: boolean }\n\nexport type PolitogyIconsResponse = {\n app: string\n version?: number\n updatedAt?: string\n icons: Record<string, PolitogyIconVariant>\n}\n\n/** Next.js `metadata.icons` shape (a subset — url/rel entries). */\nexport type PolitogyMetadataIcons = {\n icon: { url: string }[]\n shortcut?: { url: string }[]\n apple?: { url: string }[]\n}\n\nfunction pick(\n icons: Record<string, PolitogyIconVariant> | undefined,\n ...keys: string[]\n): string | null {\n if (!icons) return null\n for (const key of keys) {\n const hit = icons[key]?.url\n if (hit) return hit\n }\n return null\n}\n\n/**\n * Fetch the resolved icon set for an app and shape it for Next `metadata`.\n *\n * Cached for 60s with revalidation, matching the tokens contract — a Brand edit\n * shows up promptly without defeating caching. Never throws: on any failure the\n * VRM Core logo is returned, so an app can call this unguarded in metadata.\n */\nexport async function politogyAppIcons(\n app: string,\n init?: { baseUrl?: string; revalidateSeconds?: number; fetchImpl?: typeof globalThis.fetch },\n): Promise<PolitogyMetadataIcons> {\n const base = init?.baseUrl ?? SPECS_ICONS_BASE\n const revalidate = init?.revalidateSeconds ?? 60\n const doFetch = init?.fetchImpl ?? globalThis.fetch\n try {\n const res = await doFetch(`${base}/${encodeURIComponent(app)}`, {\n // `next.revalidate` is ignored by non-Next fetch implementations.\n next: { revalidate },\n } as RequestInit)\n if (!res.ok) throw new Error(`icons ${app}: ${res.status}`)\n const data = (await res.json()) as PolitogyIconsResponse\n return toMetadataIcons(data)\n } catch {\n return {\n icon: [{ url: VRM_FALLBACK_ICON }],\n shortcut: [{ url: VRM_FALLBACK_ICON }],\n apple: [{ url: VRM_FALLBACK_ICON }],\n }\n }\n}\n\n/**\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 the fallback logo / unknown rev.\n */\nfunction withVersion(url: string, data: PolitogyIconsResponse | null): string {\n if (url === VRM_FALLBACK_ICON) return url\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 const favicon = pick(icons, 'favicon-color-square', 'favicon-color-round') ?? VRM_FALLBACK_ICON\n const apple =\n pick(icons, 'ios-color-square', 'ios-color-round', 'favicon-color-square') ?? favicon\n return {\n icon: [{ url: withVersion(favicon, data) }],\n shortcut: [{ url: withVersion(favicon, data) }],\n apple: [{ url: withVersion(apple, data) }],\n }\n}\n"]}
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"]}
@@ -0,0 +1,26 @@
1
+ import clsx from 'clsx';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/components/FormColumn.tsx
5
+ function FormColumn({ children, className }) {
6
+ return /* @__PURE__ */ jsx("div", { "data-shell-form-column": "", className: clsx("w-full max-w-[960px]", className), children });
7
+ }
8
+
9
+ // src/lib/deprecation.ts
10
+ var warned = /* @__PURE__ */ new Set();
11
+ function isDev() {
12
+ try {
13
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ function warnDeprecated(key, message) {
19
+ if (!isDev() || warned.has(key)) return;
20
+ warned.add(key);
21
+ console.warn(`[@the-portland-company/shell] ${message}`);
22
+ }
23
+
24
+ export { FormColumn, warnDeprecated };
25
+ //# sourceMappingURL=chunk-LPSBSAF3.js.map
26
+ //# sourceMappingURL=chunk-LPSBSAF3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/FormColumn.tsx","../src/lib/deprecation.ts"],"names":[],"mappings":";;;;AAmBe,SAAR,UAAA,CAA4B,EAAE,QAAA,EAAU,SAAA,EAAU,EAAoB;AAC3E,EAAA,uBACE,GAAA,CAAC,SAAI,wBAAA,EAAuB,EAAA,EAAG,WAAW,IAAA,CAAK,sBAAA,EAAwB,SAAS,CAAA,EAC7E,QAAA,EACH,CAAA;AAEJ;;;ACvBA,IAAM,MAAA,uBAAa,GAAA,EAAY;AAE/B,SAAS,KAAA,GAAiB;AACxB,EAAA,IAAI;AACF,IAAA,OAAO,OAAO,OAAA,KAAY,WAAA,IAAe,OAAA,CAAQ,KAAK,QAAA,KAAa,YAAA;AAAA,EACrE,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAGO,SAAS,cAAA,CAAe,KAAa,OAAA,EAAuB;AACjE,EAAA,IAAI,CAAC,KAAA,EAAM,IAAK,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA,EAAG;AACjC,EAAA,MAAA,CAAO,IAAI,GAAG,CAAA;AACd,EAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,8BAAA,EAAiC,OAAO,CAAA,CAAE,CAAA;AACzD","file":"chunk-LPSBSAF3.js","sourcesContent":["import type { ReactNode } from 'react';\nimport clsx from 'clsx';\n\ntype FormColumnProps = {\n children: ReactNode;\n className?: string;\n};\n\n/**\n * The ONE constrained layout inside the frame's fluid `<main>` (Brand guideline\n * \"Content width is fluid\"): a single left-aligned column, 960px max, for form\n * and settings pages — single-record edit forms, Organization Settings tabs,\n * My Profile, activation and setup flows. Data surfaces (tables, dashboards,\n * maps) never use it; they take the full fluid width.\n *\n * It adds no gutter of its own — `<main>` already owns the 24px / 16px gutter —\n * and it is never centered. There is no width prop on purpose: a per-page\n * max-width is exactly what the guideline forbids.\n */\nexport default function FormColumn({ children, className }: FormColumnProps) {\n return (\n <div data-shell-form-column=\"\" className={clsx('w-full max-w-[960px]', className)}>\n {children}\n </div>\n );\n}\n","// One-time console warnings for retired shell API, in development only.\n\nconst warned = new Set<string>()\n\nfunction isDev(): boolean {\n try {\n return typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'\n } catch {\n return false\n }\n}\n\n/** Warn once per `key` in dev; silent in production builds. */\nexport function warnDeprecated(key: string, message: string): void {\n if (!isDev() || warned.has(key)) return\n warned.add(key)\n console.warn(`[@the-portland-company/shell] ${message}`)\n}\n\n/** Test hook: forget which warnings have fired. */\nexport function resetDeprecationWarnings(): void {\n warned.clear()\n}\n"]}