@flamingo-stack/openframe-frontend-core 0.0.299 → 0.0.300

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 (157) hide show
  1. package/dist/chunk-2FI3USTC.js +43 -0
  2. package/dist/chunk-2FI3USTC.js.map +1 -0
  3. package/dist/{chunk-54KNMC2R.cjs → chunk-2ZHDP22R.cjs} +3 -3
  4. package/dist/{chunk-54KNMC2R.cjs.map → chunk-2ZHDP22R.cjs.map} +1 -1
  5. package/dist/{chunk-X26HXH3L.cjs → chunk-5VVNE6I5.cjs} +7 -7
  6. package/dist/{chunk-X26HXH3L.cjs.map → chunk-5VVNE6I5.cjs.map} +1 -1
  7. package/dist/{chunk-57HYFVTZ.cjs → chunk-735DLFS4.cjs} +29 -29
  8. package/dist/{chunk-57HYFVTZ.cjs.map → chunk-735DLFS4.cjs.map} +1 -1
  9. package/dist/{chunk-RY62M66O.js → chunk-7EMOBUB2.js} +2 -2
  10. package/dist/{chunk-WDWGFUT2.js → chunk-APWW3GPU.js} +6 -6
  11. package/dist/{chunk-5MLYCLOI.js → chunk-CZ2EJKPA.js} +5 -9
  12. package/dist/chunk-CZ2EJKPA.js.map +1 -0
  13. package/dist/{chunk-Q77BDAR7.cjs → chunk-E2XQ5AX7.cjs} +36 -36
  14. package/dist/{chunk-Q77BDAR7.cjs.map → chunk-E2XQ5AX7.cjs.map} +1 -1
  15. package/dist/{chunk-56X3EFTG.cjs → chunk-FM7OPH5J.cjs} +19 -19
  16. package/dist/{chunk-56X3EFTG.cjs.map → chunk-FM7OPH5J.cjs.map} +1 -1
  17. package/dist/{chunk-WMSTJAZT.cjs → chunk-G56GYN7Z.cjs} +47 -15
  18. package/dist/chunk-G56GYN7Z.cjs.map +1 -0
  19. package/dist/{chunk-PQEQUMSS.cjs → chunk-JCU4YVFY.cjs} +16 -16
  20. package/dist/{chunk-PQEQUMSS.cjs.map → chunk-JCU4YVFY.cjs.map} +1 -1
  21. package/dist/{chunk-2QG57XOJ.js → chunk-JQ2EYXWR.js} +42 -10
  22. package/dist/chunk-JQ2EYXWR.js.map +1 -0
  23. package/dist/{chunk-PI4WSYQV.js → chunk-JQLC2FVM.js} +2 -2
  24. package/dist/{chunk-XG2KGQ76.cjs → chunk-K2LINTWC.cjs} +40 -40
  25. package/dist/{chunk-XG2KGQ76.cjs.map → chunk-K2LINTWC.cjs.map} +1 -1
  26. package/dist/{chunk-DVHQGII5.js → chunk-K3F3AXCC.js} +4 -4
  27. package/dist/{chunk-N4DEX22O.js → chunk-KNX4OEU5.js} +2 -2
  28. package/dist/{chunk-BL6XZ2XD.cjs → chunk-LXXZDZGG.cjs} +616 -620
  29. package/dist/chunk-LXXZDZGG.cjs.map +1 -0
  30. package/dist/{chunk-PLNR6BHN.cjs → chunk-MFZP6ZJ5.cjs} +7 -7
  31. package/dist/{chunk-PLNR6BHN.cjs.map → chunk-MFZP6ZJ5.cjs.map} +1 -1
  32. package/dist/chunk-N2DVKXN4.cjs +43 -0
  33. package/dist/chunk-N2DVKXN4.cjs.map +1 -0
  34. package/dist/{chunk-B2IN2IND.js → chunk-OZW6GJKN.js} +4 -4
  35. package/dist/{chunk-NEVMYN4G.js → chunk-QSPEFQSN.js} +3 -3
  36. package/dist/{chunk-QE5CNWPF.js → chunk-SYNOZPQC.js} +3 -3
  37. package/dist/{chunk-67WXHSCX.cjs → chunk-XTQFETF6.cjs} +13 -13
  38. package/dist/{chunk-67WXHSCX.cjs.map → chunk-XTQFETF6.cjs.map} +1 -1
  39. package/dist/{chunk-XKIO5K5N.js → chunk-ZLLGC2RZ.js} +3 -3
  40. package/dist/components/case-studies/index.cjs +10 -11
  41. package/dist/components/case-studies/index.cjs.map +1 -1
  42. package/dist/components/case-studies/index.js +4 -5
  43. package/dist/components/case-studies/index.js.map +1 -1
  44. package/dist/components/chat/entity-cards/admin-content-card.d.ts +1 -1
  45. package/dist/components/chat/entity-cards/blog-card.d.ts +2 -2
  46. package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -1
  47. package/dist/components/chat/entity-cards/blog-image-placeholder.d.ts +1 -1
  48. package/dist/components/chat/entity-cards/case-study-card.d.ts +1 -1
  49. package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +4 -3
  50. package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -1
  51. package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts +4 -3
  52. package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts.map +1 -1
  53. package/dist/components/chat/entity-cards/what-i-shipped-card.d.ts +1 -1
  54. package/dist/components/chat/index.cjs +4 -5
  55. package/dist/components/chat/index.cjs.map +1 -1
  56. package/dist/components/chat/index.js +3 -4
  57. package/dist/components/contact/index.cjs +5 -6
  58. package/dist/components/contact/index.cjs.map +1 -1
  59. package/dist/components/contact/index.js +4 -5
  60. package/dist/components/docs/index.cjs +7 -8
  61. package/dist/components/docs/index.cjs.map +1 -1
  62. package/dist/components/docs/index.js +6 -7
  63. package/dist/components/embeds/index.cjs +5 -6
  64. package/dist/components/embeds/index.cjs.map +1 -1
  65. package/dist/components/embeds/index.js +4 -5
  66. package/dist/components/embeds/og-link-preview.d.ts +2 -2
  67. package/dist/components/faq/index.cjs +6 -7
  68. package/dist/components/faq/index.cjs.map +1 -1
  69. package/dist/components/faq/index.js +5 -6
  70. package/dist/components/features/index.cjs +4 -5
  71. package/dist/components/features/index.cjs.map +1 -1
  72. package/dist/components/features/index.js +3 -4
  73. package/dist/components/index.cjs +193 -195
  74. package/dist/components/index.cjs.map +1 -1
  75. package/dist/components/index.js +12 -14
  76. package/dist/components/index.js.map +1 -1
  77. package/dist/components/navigation/index.cjs +4 -5
  78. package/dist/components/navigation/index.cjs.map +1 -1
  79. package/dist/components/navigation/index.js +3 -4
  80. package/dist/components/onboarding-guides/index.cjs +44 -41
  81. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  82. package/dist/components/onboarding-guides/index.js +11 -8
  83. package/dist/components/onboarding-guides/index.js.map +1 -1
  84. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  85. package/dist/components/related-content/index.cjs +6 -7
  86. package/dist/components/related-content/index.cjs.map +1 -1
  87. package/dist/components/related-content/index.js +5 -6
  88. package/dist/components/tickets/index.cjs +75 -76
  89. package/dist/components/tickets/index.cjs.map +1 -1
  90. package/dist/components/tickets/index.js +5 -6
  91. package/dist/components/tickets/index.js.map +1 -1
  92. package/dist/components/ui/index.cjs +4 -5
  93. package/dist/components/ui/index.cjs.map +1 -1
  94. package/dist/components/ui/index.js +3 -4
  95. package/dist/contexts/chat-runtime-context.d.ts +17 -13
  96. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  97. package/dist/contexts/index.cjs +2 -4
  98. package/dist/contexts/index.cjs.map +1 -1
  99. package/dist/contexts/index.js +3 -5
  100. package/dist/contexts/index.js.map +1 -1
  101. package/dist/hooks/index.cjs +3 -3
  102. package/dist/hooks/index.cjs.map +1 -1
  103. package/dist/hooks/index.d.ts +1 -1
  104. package/dist/hooks/index.d.ts.map +1 -1
  105. package/dist/hooks/index.js +4 -4
  106. package/dist/hooks/use-og-placeholder-url.d.ts +27 -0
  107. package/dist/hooks/use-og-placeholder-url.d.ts.map +1 -0
  108. package/dist/index.cjs +6 -5
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.js +7 -6
  111. package/dist/utils/index.cjs +26 -0
  112. package/dist/utils/index.cjs.map +1 -1
  113. package/dist/utils/index.d.ts +1 -0
  114. package/dist/utils/index.d.ts.map +1 -1
  115. package/dist/utils/index.js +26 -1
  116. package/dist/utils/index.js.map +1 -1
  117. package/dist/utils/og-placeholder.d.ts +59 -0
  118. package/dist/utils/og-placeholder.d.ts.map +1 -0
  119. package/package.json +1 -1
  120. package/src/components/chat/entity-cards/admin-content-card.tsx +1 -1
  121. package/src/components/chat/entity-cards/blog-card.tsx +2 -2
  122. package/src/components/chat/entity-cards/blog-image-placeholder.tsx +1 -1
  123. package/src/components/chat/entity-cards/case-study-card.tsx +1 -1
  124. package/src/components/chat/entity-cards/onboarding-guide-card.tsx +4 -3
  125. package/src/components/chat/entity-cards/use-entity-card-placeholder.ts +13 -26
  126. package/src/components/chat/entity-cards/what-i-shipped-card.tsx +1 -1
  127. package/src/components/embeds/og-link-preview.tsx +2 -2
  128. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +4 -2
  129. package/src/contexts/chat-runtime-context.tsx +17 -13
  130. package/src/hooks/index.ts +2 -2
  131. package/src/hooks/use-og-placeholder-url.ts +46 -0
  132. package/src/utils/index.ts +4 -0
  133. package/src/utils/og-placeholder.ts +105 -0
  134. package/dist/chunk-27APPAJN.cjs +0 -24
  135. package/dist/chunk-27APPAJN.cjs.map +0 -1
  136. package/dist/chunk-2QG57XOJ.js.map +0 -1
  137. package/dist/chunk-5MLYCLOI.js.map +0 -1
  138. package/dist/chunk-BL6XZ2XD.cjs.map +0 -1
  139. package/dist/chunk-IZ7JSBFP.js +0 -24
  140. package/dist/chunk-IZ7JSBFP.js.map +0 -1
  141. package/dist/chunk-L6PSSIUQ.cjs +0 -24
  142. package/dist/chunk-L6PSSIUQ.cjs.map +0 -1
  143. package/dist/chunk-MJNXIEV2.js +0 -24
  144. package/dist/chunk-MJNXIEV2.js.map +0 -1
  145. package/dist/chunk-WMSTJAZT.cjs.map +0 -1
  146. package/dist/hooks/use-og-placeholder.d.ts +0 -31
  147. package/dist/hooks/use-og-placeholder.d.ts.map +0 -1
  148. package/src/hooks/use-og-placeholder.ts +0 -45
  149. /package/dist/{chunk-RY62M66O.js.map → chunk-7EMOBUB2.js.map} +0 -0
  150. /package/dist/{chunk-WDWGFUT2.js.map → chunk-APWW3GPU.js.map} +0 -0
  151. /package/dist/{chunk-PI4WSYQV.js.map → chunk-JQLC2FVM.js.map} +0 -0
  152. /package/dist/{chunk-DVHQGII5.js.map → chunk-K3F3AXCC.js.map} +0 -0
  153. /package/dist/{chunk-N4DEX22O.js.map → chunk-KNX4OEU5.js.map} +0 -0
  154. /package/dist/{chunk-B2IN2IND.js.map → chunk-OZW6GJKN.js.map} +0 -0
  155. /package/dist/{chunk-NEVMYN4G.js.map → chunk-QSPEFQSN.js.map} +0 -0
  156. /package/dist/{chunk-QE5CNWPF.js.map → chunk-SYNOZPQC.js.map} +0 -0
  157. /package/dist/{chunk-XKIO5K5N.js.map → chunk-ZLLGC2RZ.js.map} +0 -0
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Branded OG-placeholder URL construction — the DEFAULT cover-image fallback
3
+ * for entity cards (onboarding guides, blog/case-study/release/etc.) that have
4
+ * no image.
5
+ *
6
+ * ALL of the logic lives here, in the lib. A consumer hands over its runtime
7
+ * `endpoints` object and NOTHING else — base resolution AND the `?title=…`
8
+ * concatenation happen inside `buildOgPlaceholderUrl`. For the entity-card +
9
+ * onboarding-detail surfaces this is the single entry point — no card builds an
10
+ * og-placeholder URL itself (that was the bug this replaced: each embedder
11
+ * wired a per-surface callback that concatenated the URL, and a host that
12
+ * forgot it rendered a blank slot with no request). (Other server-side OG paths
13
+ * such as the hub's blog og:image generator construct their own URLs and are
14
+ * out of this module's scope.)
15
+ *
16
+ * The base API URL is taken from the endpoints the host already configures:
17
+ * 1. explicit `endpoints.ogPlaceholderUrl`
18
+ * 2. derived from the sibling `endpoints.imageProxyUrlPrefix` (same API base)
19
+ * 3. same-origin relative `/api/og-placeholder`
20
+ * The base may already carry pre-existing query params; they're preserved and
21
+ * `title` (+ dimensions) are layered on top. Per-platform brand colors are NOT
22
+ * baked into this URL — the `/api/og-placeholder` route resolves them
23
+ * server-side from the platform. Most hosts leave `ogPlaceholderUrl` unset and
24
+ * let the base derive from `imageProxyUrlPrefix`.
25
+ */
26
+ /** The slice of `ChatRuntime.endpoints` this module needs. */
27
+ export interface OgPlaceholderEndpoints {
28
+ /** Explicit base URL for the og-placeholder route. May already carry query
29
+ * params — they're preserved. Per-platform colors are NOT baked here; the
30
+ * route resolves them server-side from the platform. */
31
+ ogPlaceholderUrl?: string;
32
+ /** Sibling image route under the SAME API base. When `ogPlaceholderUrl` is
33
+ * unset, the base is derived from this by swapping the trailing
34
+ * `/image-proxy` segment for `/og-placeholder` — so a host that already
35
+ * proxies images gets the placeholder for free, with zero extra wiring. */
36
+ imageProxyUrlPrefix?: string;
37
+ }
38
+ export interface BuildOgPlaceholderOptions {
39
+ /** Site name shown under the title. Skipped when empty. */
40
+ site?: string;
41
+ /** `'wide'` (1200×630, the route default — no `w`/`h` emitted) or
42
+ * `'square'` (1024×1024, for compact 56×56 chat-inline slots). */
43
+ aspect?: 'wide' | 'square';
44
+ /** Explicit pixel overrides (win over `aspect`). */
45
+ width?: number;
46
+ height?: number;
47
+ }
48
+ /**
49
+ * Build the branded og-placeholder image URL from the host's `endpoints` + a
50
+ * title. This is the single entry point for entity-card + onboarding-detail
51
+ * cover fallbacks: it resolves the route base from `endpoints` AND concatenates
52
+ * `title` (+ dimensions), so those consumers never construct a URL themselves.
53
+ *
54
+ * Pure string construction — SSR- and browser-safe. Always returns a usable
55
+ * URL (relative default at worst), so a missing/unknown image degrades
56
+ * gracefully via the `<img onError>` recovery in the card components.
57
+ */
58
+ export declare function buildOgPlaceholderUrl(endpoints: OgPlaceholderEndpoints | null | undefined, title: string, options?: BuildOgPlaceholderOptions): string;
59
+ //# sourceMappingURL=og-placeholder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"og-placeholder.d.ts","sourceRoot":"","sources":["../../src/utils/og-placeholder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,sBAAsB;IACrC;;6DAEyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;gFAG4E;IAC5E,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;uEACmE;IACnE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;IAC1B,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAsBD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EACpD,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,yBAA8B,GACtC,MAAM,CAoBR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.299",
3
+ "version": "0.0.300",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@ interface AdminContentCardProps {
17
17
  /** Cover image URL */
18
18
  imageUrl?: string | null
19
19
  /** Pre-computed placeholder URL used when `imageUrl` is missing or
20
- * fails to load. Hub callers pass `useOgPlaceholder(title)`; embedders
20
+ * fails to load. Hub callers pass `useOgPlaceholderUrl({ title })`; embedders
21
21
  * supply their own URL or leave null for a plain background fallback. */
22
22
  placeholderUrl?: string | null
23
23
  /** Alt text / fallback title */
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Image-fallback chain:
16
16
  * `post.featured_image` → `placeholderUrl` (caller passes
17
- * `useOgPlaceholder(...)` from the hub OR any pre-resolved URL) →
17
+ * `useOgPlaceholderUrl(...)` from the hub OR any pre-resolved URL) →
18
18
  * `bg-ods-bg` (via the slot's background).
19
19
  */
20
20
 
@@ -53,7 +53,7 @@ export interface BlogCardProps {
53
53
  * pure-presentation contract. */
54
54
  targetPlatform?: string | null
55
55
  /** Placeholder URL when `post.featured_image` is missing. Caller
56
- * resolves via `useOgPlaceholder` (hub) or a static asset. */
56
+ * resolves via `useOgPlaceholderUrl` (hub) or a static asset. */
57
57
  placeholderUrl?: string | null
58
58
  size?: 'default' | 'sm'
59
59
  className?: string
@@ -3,7 +3,7 @@
3
3
  import React from 'react'
4
4
 
5
5
  interface BlogImagePlaceholderProps {
6
- /** Cover-image URL. The hub passes a `useOgPlaceholder(title, category)`
6
+ /** Cover-image URL. The hub passes a `useOgPlaceholderUrl({ title, siteName })`
7
7
  * result; embedders pass their own pre-resolved URL. When null, the
8
8
  * component renders nothing. */
9
9
  imageUrl: string | null
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Image-fallback chain:
11
11
  * `study.featured_image` → `placeholderUrl` (caller passes
12
- * `useOgPlaceholder(...)`) → `bg-ods-bg`.
12
+ * `useOgPlaceholderUrl(...)`) → `bg-ods-bg`.
13
13
  */
14
14
 
15
15
  import React from 'react'
@@ -11,9 +11,10 @@
11
11
  *
12
12
  * Link semantics: the card derives `target`/`rel` from `ChatRuntime.navigation
13
13
  * .decideNewTab` (hub-wired via `HubRuntimeProvider`) and the placeholder
14
- * image from `ChatRuntime.resolvePlaceholderUrl`. Explicit `target` / `rel`
15
- * / `placeholderUrl` props always WIN chat dispatch and tests can
16
- * pre-resolve. No runtime mounted same-tab + no placeholder.
14
+ * image from the runtime's base API URL (`endpoints.ogPlaceholderUrl`, via
15
+ * `useEntityCardPlaceholder`). Explicit `target` / `rel` / `placeholderUrl`
16
+ * props always WIN chat dispatch and tests can pre-resolve. No runtime
17
+ * mounted → same-tab + same-origin relative placeholder.
17
18
  */
18
19
 
19
20
  import React from 'react'
@@ -1,50 +1,37 @@
1
1
  'use client'
2
2
 
3
3
  /**
4
- * Shared OG-placeholder resolver for every entity card.
4
+ * Entity-card OG-placeholder resolver.
5
5
  *
6
- * Pure-presentation cards historically required callers to
7
- * pre-compute `placeholderUrl` via a hub-side `useOgPlaceholder`
8
- * wrapper that injected the hub's `buildOgPlaceholderUrl` (resolves
9
- * CSS-var ODS colors to hex via the static map).
10
- *
11
- * This hook moves that resolution INTO the card via the
12
- * `ChatRuntime.resolvePlaceholderUrl` callback. Embedders that don't
13
- * wire the callback get no placeholder (the card's empty-state path
14
- * activates) — same fallback semantics as before.
15
- *
16
- * Backwards compat: explicit `placeholderUrl` prop ALWAYS wins over
17
- * runtime-derived value. Callers that pre-resolve (chat dispatch,
18
- * tests) are unaffected.
6
+ * A thin wrapper over the one og-placeholder hook (`useOgPlaceholderUrl`) that
7
+ * adds a single concern: an explicit `placeholderUrl` prop (incl. `null`) wins
8
+ * over the runtime-derived URL chat dispatch + tests pre-resolve through it.
9
+ * All URL construction lives in `useOgPlaceholderUrl` / `buildOgPlaceholderUrl`,
10
+ * so this surface shares one memo + one code path with every other consumer.
19
11
  */
20
12
 
21
- import { useChatRuntime } from '../../../contexts/chat-runtime-context'
22
- import { useOgPlaceholder } from '../../../hooks/use-og-placeholder'
13
+ import { useOgPlaceholderUrl } from '../../../hooks/use-og-placeholder-url'
23
14
 
24
15
  export interface UseEntityCardPlaceholderArgs {
25
16
  /** Entity title — used as the placeholder label. */
26
17
  title: string | undefined | null
27
- /** Explicit override. When set, runtime resolver is skipped. */
18
+ /** Explicit override. When set (including `null`), the runtime default is
19
+ * skipped. */
28
20
  placeholderUrl?: string | null
29
21
  /** Site name shown under the title. Optional. */
30
22
  siteName?: string
31
- /** Output aspect ratio. `'wide'` (default) for catalog cards,
32
- * `'square'` for compact chat-inline cards. */
23
+ /** Output aspect ratio. `'wide'` (default) for catalog cards, `'square'`
24
+ * for compact chat-inline cards. */
33
25
  aspect?: 'wide' | 'square'
34
26
  }
35
27
 
36
- const NO_OP_BUILDER = () => ''
37
-
38
28
  export function useEntityCardPlaceholder({
39
29
  title,
40
30
  placeholderUrl,
41
31
  siteName = '',
42
32
  aspect = 'wide',
43
33
  }: UseEntityCardPlaceholderArgs): string | null {
44
- const runtime = useChatRuntime()
45
- const builder = runtime?.resolvePlaceholderUrl ?? NO_OP_BUILDER
46
- const enabled = placeholderUrl === undefined && !!runtime?.resolvePlaceholderUrl
47
- const derived = useOgPlaceholder(builder, title, siteName, enabled, aspect)
48
- // Explicit prop (including explicit null) wins; `undefined` falls back to derived.
34
+ const derived = useOgPlaceholderUrl({ title, siteName, aspect })
35
+ // Explicit prop (including explicit null) wins; `undefined` → derived default.
49
36
  return placeholderUrl !== undefined ? placeholderUrl : derived
50
37
  }
@@ -17,7 +17,7 @@ export interface WhatIShippedCardData {
17
17
 
18
18
  export interface WhatIShippedCardProps {
19
19
  entry: WhatIShippedCardData
20
- /** OG fallback cover. Caller computes it (hub: `useOgPlaceholder`; related
20
+ /** OG fallback cover. Caller computes it (hub: `useOgPlaceholderUrl`; related
21
21
  * rail: `extras.buildOgPlaceholderUrl`). */
22
22
  placeholderUrl?: string | null
23
23
  /** Owner action row (dashboard). Omit for a read-only card. */
@@ -65,8 +65,8 @@ export class OGLinkErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoun
65
65
 
66
66
  /**
67
67
  * Builds a placeholder image URL when the scrape returns no image. Hub passes
68
- * its own `buildOgPlaceholderUrl` (which resolves CSS-var ODS colors against
69
- * the platform's brand palette + hits `/api/og-placeholder`); other embedders
68
+ * its own `buildOgPlaceholderUrl` (which hits `/api/og-placeholder?…&platform=`;
69
+ * the route resolves the platform's brand colors server-side); other embedders
70
70
  * can omit the prop to disable the placeholder entirely.
71
71
  *
72
72
  * Receives the post-scrape `title` and `siteName` so the placeholder can echo
@@ -39,6 +39,7 @@ import { useChatRuntime } from '../../contexts/chat-runtime-context'
39
39
  import type { OnboardingGuide } from '../chat/types/entities/onboarding-guide'
40
40
  import type { VideoTeaser } from '../../types/video-processing'
41
41
  import { resolveContentHref } from '../../utils/content-href'
42
+ import { buildOgPlaceholderUrl } from '../../utils/og-placeholder'
42
43
  import { useSelfFetch } from '../../hooks/use-self-fetch'
43
44
 
44
45
  export interface OnboardingGuideDetailViewProps {
@@ -133,8 +134,9 @@ export function OnboardingGuideDetailView({
133
134
  guide.main_video_thumbnail ||
134
135
  guide.featured_image ||
135
136
  guide.og_image_url ||
136
- runtime?.resolvePlaceholderUrl?.(guide.title, { aspect: 'wide' }) ||
137
- undefined
137
+ // `buildOgPlaceholderUrl` always returns a usable string (relative route at
138
+ // worst), so it's the terminal fallback — no trailing `|| undefined` needed.
139
+ buildOgPlaceholderUrl(runtime?.endpoints, guide.title, { aspect: 'wide' })
138
140
 
139
141
  const defaultRenderRelatedCard = (g: OnboardingGuide) => {
140
142
  const cta = resolveContentHref(runtime?.composeContentUrl, {
@@ -117,6 +117,23 @@ export interface ChatRuntime {
117
117
  * leave it unset. Matches the `skipDomains` parameter of
118
118
  * `getProxiedImageUrl`. */
119
119
  imageProxySkipDomains?: string[]
120
+ /** Optional base URL for the branded og-placeholder image route — the
121
+ * DEFAULT cover-image fallback for entity cards with no image. The lib
122
+ * appends `?title=…` (+ `w`/`h` for square slots) itself, so this is
123
+ * the base, NOT a full URL: relative (`/api/og-placeholder`) for same-
124
+ * origin hosts, or the proxied path (`/content/api/og-placeholder`) for
125
+ * cross-origin embedders. May carry baked-in query params (preserved when
126
+ * the lib layers `title`/dimensions on top) — but per-platform brand
127
+ * colors are NO LONGER baked here; the `/api/og-placeholder` route
128
+ * resolves them server-side from the platform. Most hosts leave this unset
129
+ * and let the lib derive the base from `imageProxyUrlPrefix`.
130
+ *
131
+ * OPTIONAL — when unset the lib derives the base from the sibling
132
+ * `imageProxyUrlPrefix` (same API base, route name swapped), then falls
133
+ * back to the relative `/api/og-placeholder`. So an embedder that already
134
+ * proxies images needs NO og-placeholder wiring. See
135
+ * `resolveOgPlaceholderBase` / `buildOgPlaceholderUrl` in `../utils`. */
136
+ ogPlaceholderUrl?: string
120
137
  /** Supabase storage origin (e.g. `https://xyz.supabase.co`) — used
121
138
  * by `useVideoWarmup` to scope the `<link rel="preload" as="video">`
122
139
  * hint to MP4s the deployment actually hosts. Hub wires it via
@@ -158,19 +175,6 @@ export interface ChatRuntime {
158
175
  * same-origin/same-platform → same tab, else new tab. */
159
176
  decideNewTab?: (args: { href: string; targetPlatform?: string | null }) => boolean
160
177
  }
161
- /** Optional OG placeholder URL builder. Returns a branded
162
- * `/api/og-placeholder?...` URL for the given title. Hub wires this
163
- * to its `buildOgPlaceholderUrl` (resolves CSS-var ODS colors to
164
- * hex via the static map). Embedders can wire any equivalent that
165
- * hits their own placeholder route — or omit, in which case entity
166
- * cards fall back to no placeholder.
167
- *
168
- * Pure synchronous function — NOT a hook. Callers wrap with
169
- * `useMemo`/`useOgPlaceholder` for memoization. */
170
- resolvePlaceholderUrl?: (
171
- title: string,
172
- options?: { site?: string; aspect?: 'wide' | 'square' },
173
- ) => string
174
178
  /** Optional content-URL composer. Returns the platform-aware href +
175
179
  * target-platform tuple for a content entity. Hub wires this to its
176
180
  * `buildContentURL(type, slug, extractPrimaryPlatform(platforms))`
@@ -25,8 +25,8 @@ export * from './use-near-viewport'
25
25
  // server-safe utils bundle.
26
26
  export * from './use-access-code-integration'
27
27
 
28
- // OG placeholder URL builder hook (requires host-supplied URL builder)
29
- export * from './use-og-placeholder'
28
+ // THE og-placeholder URL hook (endpoints-driven; no injected builder)
29
+ export * from './use-og-placeholder-url'
30
30
 
31
31
  // Deep-link "scroll to URL hash" after data loads. Pairs with URL
32
32
  // composers that emit `?<filter>=<id>#<prefix>-<id>` — the filter
@@ -0,0 +1,46 @@
1
+ 'use client'
2
+
3
+ import { useMemo } from 'react'
4
+
5
+ import { useChatRuntime } from '../contexts/chat-runtime-context'
6
+ import { buildOgPlaceholderUrl } from '../utils/og-placeholder'
7
+
8
+ /**
9
+ * Resolve a branded og-placeholder image URL for a title, driven entirely by
10
+ * the runtime `endpoints` (no injected builder).
11
+ *
12
+ * THE one og-placeholder hook. It reads the host's `endpoints` from
13
+ * `ChatRuntime` and hands them to `buildOgPlaceholderUrl`, which resolves the
14
+ * route base (explicit `ogPlaceholderUrl` → derived from `imageProxyUrlPrefix`
15
+ * → same-origin `/api/og-placeholder`) and appends `?title=…`. Per-platform
16
+ * brand colors are resolved SERVER-SIDE by the route — nothing is baked here.
17
+ *
18
+ * Replaces the old builder-injection `useOgPlaceholder(buildUrl, …)`: callers
19
+ * no longer pass a URL builder. `useEntityCardPlaceholder` delegates here too,
20
+ * so every surface shares one memo + one code path.
21
+ */
22
+ export interface UseOgPlaceholderUrlArgs {
23
+ /** Text to display on the placeholder. */
24
+ title: string | undefined | null
25
+ /** Site name shown below the title (optional). */
26
+ siteName?: string
27
+ /** `'wide'` (1200×630 social-card; default) or `'square'` (1024×1024 — for
28
+ * compact chat-inline slots so `object-cover` doesn't crop the title off). */
29
+ aspect?: 'wide' | 'square'
30
+ /** When `false`, returns `null` instead of a URL. */
31
+ enabled?: boolean
32
+ }
33
+
34
+ export function useOgPlaceholderUrl({
35
+ title,
36
+ siteName = '',
37
+ aspect = 'wide',
38
+ enabled = true,
39
+ }: UseOgPlaceholderUrlArgs): string | null {
40
+ const endpoints = useChatRuntime()?.endpoints
41
+
42
+ return useMemo(() => {
43
+ if (!enabled || !title) return null
44
+ return buildOgPlaceholderUrl(endpoints, title, { site: siteName || undefined, aspect })
45
+ }, [endpoints, title, siteName, aspect, enabled])
46
+ }
@@ -41,6 +41,10 @@ export * from './date-formatters'
41
41
  // rich card metadata): badge-color mapping + cover-image fallback resolution.
42
42
  export * from './release-badge'
43
43
  export * from './release-cover'
44
+ // OG-placeholder fallback image — the lib-owned DEFAULT URL builder (replaces
45
+ // the old per-embedder `ChatRuntime.resolvePlaceholderUrl` callback). Driven by
46
+ // the runtime's base API URL; embedders override via `endpoints.ogPlaceholderUrl`.
47
+ export * from './og-placeholder'
44
48
  // Dev-center URL param keys — the ONE source for the `?search=` / `?status=` / … keys the
45
49
  // chrome registry writes and the list views read; re-exported so embedders (and the hub's
46
50
  // dev-section-url helper) build deep-links with the same keys instead of a bare literal.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Branded OG-placeholder URL construction — the DEFAULT cover-image fallback
3
+ * for entity cards (onboarding guides, blog/case-study/release/etc.) that have
4
+ * no image.
5
+ *
6
+ * ALL of the logic lives here, in the lib. A consumer hands over its runtime
7
+ * `endpoints` object and NOTHING else — base resolution AND the `?title=…`
8
+ * concatenation happen inside `buildOgPlaceholderUrl`. For the entity-card +
9
+ * onboarding-detail surfaces this is the single entry point — no card builds an
10
+ * og-placeholder URL itself (that was the bug this replaced: each embedder
11
+ * wired a per-surface callback that concatenated the URL, and a host that
12
+ * forgot it rendered a blank slot with no request). (Other server-side OG paths
13
+ * such as the hub's blog og:image generator construct their own URLs and are
14
+ * out of this module's scope.)
15
+ *
16
+ * The base API URL is taken from the endpoints the host already configures:
17
+ * 1. explicit `endpoints.ogPlaceholderUrl`
18
+ * 2. derived from the sibling `endpoints.imageProxyUrlPrefix` (same API base)
19
+ * 3. same-origin relative `/api/og-placeholder`
20
+ * The base may already carry pre-existing query params; they're preserved and
21
+ * `title` (+ dimensions) are layered on top. Per-platform brand colors are NOT
22
+ * baked into this URL — the `/api/og-placeholder` route resolves them
23
+ * server-side from the platform. Most hosts leave `ogPlaceholderUrl` unset and
24
+ * let the base derive from `imageProxyUrlPrefix`.
25
+ */
26
+
27
+ /** The slice of `ChatRuntime.endpoints` this module needs. */
28
+ export interface OgPlaceholderEndpoints {
29
+ /** Explicit base URL for the og-placeholder route. May already carry query
30
+ * params — they're preserved. Per-platform colors are NOT baked here; the
31
+ * route resolves them server-side from the platform. */
32
+ ogPlaceholderUrl?: string
33
+ /** Sibling image route under the SAME API base. When `ogPlaceholderUrl` is
34
+ * unset, the base is derived from this by swapping the trailing
35
+ * `/image-proxy` segment for `/og-placeholder` — so a host that already
36
+ * proxies images gets the placeholder for free, with zero extra wiring. */
37
+ imageProxyUrlPrefix?: string
38
+ }
39
+
40
+ export interface BuildOgPlaceholderOptions {
41
+ /** Site name shown under the title. Skipped when empty. */
42
+ site?: string
43
+ /** `'wide'` (1200×630, the route default — no `w`/`h` emitted) or
44
+ * `'square'` (1024×1024, for compact 56×56 chat-inline slots). */
45
+ aspect?: 'wide' | 'square'
46
+ /** Explicit pixel overrides (win over `aspect`). */
47
+ width?: number
48
+ height?: number
49
+ }
50
+
51
+ /** Same-origin default — for hosts that serve the route themselves (the hub).
52
+ * Cross-origin embedders override via `ogPlaceholderUrl` or inherit it from
53
+ * `imageProxyUrlPrefix`. */
54
+ const DEFAULT_OG_PLACEHOLDER_PATH = '/api/og-placeholder'
55
+
56
+ /** Resolve the og-placeholder route base from the host's endpoints.
57
+ * Internal — callers go through `buildOgPlaceholderUrl(endpoints, …)`. */
58
+ function resolveOgPlaceholderBase(endpoints?: OgPlaceholderEndpoints | null): string {
59
+ if (endpoints?.ogPlaceholderUrl) return endpoints.ogPlaceholderUrl
60
+ const imageProxy = endpoints?.imageProxyUrlPrefix
61
+ if (imageProxy) {
62
+ // `/image-proxy` and `/og-placeholder` are sibling API routes under one
63
+ // base. Anchor to a path-segment boundary so we only rewrite the route
64
+ // name, never an incidental substring.
65
+ const derived = imageProxy.replace(/\/image-proxy(?=$|[?/])/, '/og-placeholder')
66
+ if (derived !== imageProxy) return derived
67
+ }
68
+ return DEFAULT_OG_PLACEHOLDER_PATH
69
+ }
70
+
71
+ /**
72
+ * Build the branded og-placeholder image URL from the host's `endpoints` + a
73
+ * title. This is the single entry point for entity-card + onboarding-detail
74
+ * cover fallbacks: it resolves the route base from `endpoints` AND concatenates
75
+ * `title` (+ dimensions), so those consumers never construct a URL themselves.
76
+ *
77
+ * Pure string construction — SSR- and browser-safe. Always returns a usable
78
+ * URL (relative default at worst), so a missing/unknown image degrades
79
+ * gracefully via the `<img onError>` recovery in the card components.
80
+ */
81
+ export function buildOgPlaceholderUrl(
82
+ endpoints: OgPlaceholderEndpoints | null | undefined,
83
+ title: string,
84
+ options: BuildOgPlaceholderOptions = {},
85
+ ): string {
86
+ const base = resolveOgPlaceholderBase(endpoints)
87
+ const qIndex = base.indexOf('?')
88
+ const path = qIndex === -1 ? base : base.slice(0, qIndex)
89
+ const params = new URLSearchParams(qIndex === -1 ? '' : base.slice(qIndex + 1))
90
+
91
+ params.set('title', title)
92
+ if (options.site) params.set('site', options.site)
93
+
94
+ // Square aspect → request a 1024×1024 image so `object-cover` doesn't crop
95
+ // the title off in compact slots. Wide leaves dimensions to the route
96
+ // default (1200×630). Explicit width/height always win.
97
+ const width = options.width ?? (options.aspect === 'square' ? 1024 : undefined)
98
+ const height = options.height ?? (options.aspect === 'square' ? 1024 : undefined)
99
+ // `Number.isFinite` does not coerce, so it already rejects `undefined` — no
100
+ // separate `typeof === 'number'` guard needed.
101
+ if (Number.isFinite(width)) params.set('w', String(width))
102
+ if (Number.isFinite(height)) params.set('h', String(height))
103
+
104
+ return `${path}?${params.toString()}`
105
+ }
@@ -1,24 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
2
-
3
- // src/contexts/endpoints-runtime-context.tsx
4
- var _react = require('react');
5
- var EndpointsRuntimeContext = _react.createContext.call(void 0, null);
6
- function useEndpointsRuntime() {
7
- return _react.useContext.call(void 0, EndpointsRuntimeContext);
8
- }
9
- function useRequiredEndpointsRuntime() {
10
- const v = _react.useContext.call(void 0, EndpointsRuntimeContext);
11
- if (!v) {
12
- throw new Error(
13
- "[endpoints-runtime] hook called outside an <EndpointsRuntimeContext.Provider>. Hub: mount <HubRuntimeProvider> in your providers tree. Embedded app: mount your own provider with proxied URLs at the tree root. Tests/Storybook: wrap render() in <EndpointsRuntimeContext.Provider value={mocked}>."
14
- );
15
- }
16
- return v;
17
- }
18
-
19
-
20
-
21
-
22
-
23
- exports.EndpointsRuntimeContext = EndpointsRuntimeContext; exports.useEndpointsRuntime = useEndpointsRuntime; exports.useRequiredEndpointsRuntime = useRequiredEndpointsRuntime;
24
- //# sourceMappingURL=chunk-27APPAJN.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-27APPAJN.cjs","../src/contexts/endpoints-runtime-context.tsx"],"names":[],"mappings":"AAAA,qFAAY;AACZ;AACA;ACqBA,8BAA0C;AAenC,IAAM,wBAAA,EAA0B,kCAAA,IAA2C,CAAA;AAO3E,SAAS,mBAAA,CAAA,EAA+C;AAC7D,EAAA,OAAO,+BAAA,uBAAkC,CAAA;AAC3C;AASO,SAAS,2BAAA,CAAA,EAAgD;AAC9D,EAAA,MAAM,EAAA,EAAI,+BAAA,uBAAkC,CAAA;AAC5C,EAAA,GAAA,CAAI,CAAC,CAAA,EAAG;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,IAIF,CAAA;AAAA,EACF;AACA,EAAA,OAAO,CAAA;AACT;ADlDA;AACA;AACE;AACA;AACA;AACF,gLAAC","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-27APPAJN.cjs","sourcesContent":[null,"'use client'\n\n/**\n * Endpoints runtime — sibling of ChatRuntime. Carries the API path\n * literals consumed by oss-lib components/hooks/utils so a host\n * application can override them (e.g. when running behind a reverse\n * proxy as `user1.openframe.ai` → `/api/mingo-guide/*`).\n *\n * The hub mounts `<HubRuntimeProvider>` at root with the\n * canonical hub paths; an embedded app mounts its own provider with\n * remapped paths. The pattern mirrors ChatRuntimeContext exactly:\n *\n * - `useEndpointsRuntime()` returns null when no provider is mounted.\n * For optional consumers that should gracefully no-op without one.\n * - `useRequiredEndpointsRuntime()` throws on missing provider — for\n * hooks/components that cannot function without endpoints.\n *\n * IMPORTANT for embedders: memoize the value passed to\n * `<EndpointsRuntimeContext.Provider value={...}>` (e.g. React.useMemo).\n * Reference changes invalidate downstream effect dependency arrays and\n * trigger unnecessary re-fetches.\n */\n\nimport { createContext, useContext } from 'react'\n\nexport interface EndpointsRuntime {\n /** GET active announcement (used by `<AnnouncementBar>` polling). */\n announcementsUrl: string\n accessCode: {\n /** POST validate access code. */\n validateUrl: string\n /** POST consume / redeem access code after registration. */\n consumeUrl: string\n }\n /** POST contact-form submission. */\n contactUrl: string\n}\n\nexport const EndpointsRuntimeContext = createContext<EndpointsRuntime | null>(null)\n\n/**\n * Optional read — returns null when no provider is mounted. Use for\n * surfaces that should silently skip the fetch (e.g. announcement\n * polling on a page rendered outside the provider tree).\n */\nexport function useEndpointsRuntime(): EndpointsRuntime | null {\n return useContext(EndpointsRuntimeContext)\n}\n\n/**\n * Strict variant — throws on missing provider. Use for consumers that\n * cannot function without an endpoint (form submission, code\n * validation). In tests/Storybook, wrap with the hub's\n * `<HubRuntimeProvider>` or a stub\n * `<EndpointsRuntimeContext.Provider value={mockedEndpoints}>`.\n */\nexport function useRequiredEndpointsRuntime(): EndpointsRuntime {\n const v = useContext(EndpointsRuntimeContext)\n if (!v) {\n throw new Error(\n '[endpoints-runtime] hook called outside an <EndpointsRuntimeContext.Provider>. ' +\n 'Hub: mount <HubRuntimeProvider> in your providers tree. ' +\n 'Embedded app: mount your own provider with proxied URLs at the tree root. ' +\n 'Tests/Storybook: wrap render() in <EndpointsRuntimeContext.Provider value={mocked}>.',\n )\n }\n return v\n}\n"]}