@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.
- package/dist/chunk-2FI3USTC.js +43 -0
- package/dist/chunk-2FI3USTC.js.map +1 -0
- package/dist/{chunk-54KNMC2R.cjs → chunk-2ZHDP22R.cjs} +3 -3
- package/dist/{chunk-54KNMC2R.cjs.map → chunk-2ZHDP22R.cjs.map} +1 -1
- package/dist/{chunk-X26HXH3L.cjs → chunk-5VVNE6I5.cjs} +7 -7
- package/dist/{chunk-X26HXH3L.cjs.map → chunk-5VVNE6I5.cjs.map} +1 -1
- package/dist/{chunk-57HYFVTZ.cjs → chunk-735DLFS4.cjs} +29 -29
- package/dist/{chunk-57HYFVTZ.cjs.map → chunk-735DLFS4.cjs.map} +1 -1
- package/dist/{chunk-RY62M66O.js → chunk-7EMOBUB2.js} +2 -2
- package/dist/{chunk-WDWGFUT2.js → chunk-APWW3GPU.js} +6 -6
- package/dist/{chunk-5MLYCLOI.js → chunk-CZ2EJKPA.js} +5 -9
- package/dist/chunk-CZ2EJKPA.js.map +1 -0
- package/dist/{chunk-Q77BDAR7.cjs → chunk-E2XQ5AX7.cjs} +36 -36
- package/dist/{chunk-Q77BDAR7.cjs.map → chunk-E2XQ5AX7.cjs.map} +1 -1
- package/dist/{chunk-56X3EFTG.cjs → chunk-FM7OPH5J.cjs} +19 -19
- package/dist/{chunk-56X3EFTG.cjs.map → chunk-FM7OPH5J.cjs.map} +1 -1
- package/dist/{chunk-WMSTJAZT.cjs → chunk-G56GYN7Z.cjs} +47 -15
- package/dist/chunk-G56GYN7Z.cjs.map +1 -0
- package/dist/{chunk-PQEQUMSS.cjs → chunk-JCU4YVFY.cjs} +16 -16
- package/dist/{chunk-PQEQUMSS.cjs.map → chunk-JCU4YVFY.cjs.map} +1 -1
- package/dist/{chunk-2QG57XOJ.js → chunk-JQ2EYXWR.js} +42 -10
- package/dist/chunk-JQ2EYXWR.js.map +1 -0
- package/dist/{chunk-PI4WSYQV.js → chunk-JQLC2FVM.js} +2 -2
- package/dist/{chunk-XG2KGQ76.cjs → chunk-K2LINTWC.cjs} +40 -40
- package/dist/{chunk-XG2KGQ76.cjs.map → chunk-K2LINTWC.cjs.map} +1 -1
- package/dist/{chunk-DVHQGII5.js → chunk-K3F3AXCC.js} +4 -4
- package/dist/{chunk-N4DEX22O.js → chunk-KNX4OEU5.js} +2 -2
- package/dist/{chunk-BL6XZ2XD.cjs → chunk-LXXZDZGG.cjs} +616 -620
- package/dist/chunk-LXXZDZGG.cjs.map +1 -0
- package/dist/{chunk-PLNR6BHN.cjs → chunk-MFZP6ZJ5.cjs} +7 -7
- package/dist/{chunk-PLNR6BHN.cjs.map → chunk-MFZP6ZJ5.cjs.map} +1 -1
- package/dist/chunk-N2DVKXN4.cjs +43 -0
- package/dist/chunk-N2DVKXN4.cjs.map +1 -0
- package/dist/{chunk-B2IN2IND.js → chunk-OZW6GJKN.js} +4 -4
- package/dist/{chunk-NEVMYN4G.js → chunk-QSPEFQSN.js} +3 -3
- package/dist/{chunk-QE5CNWPF.js → chunk-SYNOZPQC.js} +3 -3
- package/dist/{chunk-67WXHSCX.cjs → chunk-XTQFETF6.cjs} +13 -13
- package/dist/{chunk-67WXHSCX.cjs.map → chunk-XTQFETF6.cjs.map} +1 -1
- package/dist/{chunk-XKIO5K5N.js → chunk-ZLLGC2RZ.js} +3 -3
- package/dist/components/case-studies/index.cjs +10 -11
- package/dist/components/case-studies/index.cjs.map +1 -1
- package/dist/components/case-studies/index.js +4 -5
- package/dist/components/case-studies/index.js.map +1 -1
- package/dist/components/chat/entity-cards/admin-content-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/blog-card.d.ts +2 -2
- package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/blog-image-placeholder.d.ts +1 -1
- package/dist/components/chat/entity-cards/case-study-card.d.ts +1 -1
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +4 -3
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts +4 -3
- package/dist/components/chat/entity-cards/use-entity-card-placeholder.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/what-i-shipped-card.d.ts +1 -1
- package/dist/components/chat/index.cjs +4 -5
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +3 -4
- package/dist/components/contact/index.cjs +5 -6
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +4 -5
- package/dist/components/docs/index.cjs +7 -8
- package/dist/components/docs/index.cjs.map +1 -1
- package/dist/components/docs/index.js +6 -7
- package/dist/components/embeds/index.cjs +5 -6
- package/dist/components/embeds/index.cjs.map +1 -1
- package/dist/components/embeds/index.js +4 -5
- package/dist/components/embeds/og-link-preview.d.ts +2 -2
- package/dist/components/faq/index.cjs +6 -7
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.js +5 -6
- package/dist/components/features/index.cjs +4 -5
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +3 -4
- package/dist/components/index.cjs +193 -195
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +12 -14
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +4 -5
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +3 -4
- package/dist/components/onboarding-guides/index.cjs +44 -41
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +11 -8
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +6 -7
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +5 -6
- package/dist/components/tickets/index.cjs +75 -76
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +5 -6
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/index.cjs +4 -5
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +3 -4
- package/dist/contexts/chat-runtime-context.d.ts +17 -13
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +2 -4
- package/dist/contexts/index.cjs.map +1 -1
- package/dist/contexts/index.js +3 -5
- package/dist/contexts/index.js.map +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -4
- package/dist/hooks/use-og-placeholder-url.d.ts +27 -0
- package/dist/hooks/use-og-placeholder-url.d.ts.map +1 -0
- package/dist/index.cjs +6 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -6
- package/dist/utils/index.cjs +26 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +26 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/og-placeholder.d.ts +59 -0
- package/dist/utils/og-placeholder.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/chat/entity-cards/admin-content-card.tsx +1 -1
- package/src/components/chat/entity-cards/blog-card.tsx +2 -2
- package/src/components/chat/entity-cards/blog-image-placeholder.tsx +1 -1
- package/src/components/chat/entity-cards/case-study-card.tsx +1 -1
- package/src/components/chat/entity-cards/onboarding-guide-card.tsx +4 -3
- package/src/components/chat/entity-cards/use-entity-card-placeholder.ts +13 -26
- package/src/components/chat/entity-cards/what-i-shipped-card.tsx +1 -1
- package/src/components/embeds/og-link-preview.tsx +2 -2
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +4 -2
- package/src/contexts/chat-runtime-context.tsx +17 -13
- package/src/hooks/index.ts +2 -2
- package/src/hooks/use-og-placeholder-url.ts +46 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/og-placeholder.ts +105 -0
- package/dist/chunk-27APPAJN.cjs +0 -24
- package/dist/chunk-27APPAJN.cjs.map +0 -1
- package/dist/chunk-2QG57XOJ.js.map +0 -1
- package/dist/chunk-5MLYCLOI.js.map +0 -1
- package/dist/chunk-BL6XZ2XD.cjs.map +0 -1
- package/dist/chunk-IZ7JSBFP.js +0 -24
- package/dist/chunk-IZ7JSBFP.js.map +0 -1
- package/dist/chunk-L6PSSIUQ.cjs +0 -24
- package/dist/chunk-L6PSSIUQ.cjs.map +0 -1
- package/dist/chunk-MJNXIEV2.js +0 -24
- package/dist/chunk-MJNXIEV2.js.map +0 -1
- package/dist/chunk-WMSTJAZT.cjs.map +0 -1
- package/dist/hooks/use-og-placeholder.d.ts +0 -31
- package/dist/hooks/use-og-placeholder.d.ts.map +0 -1
- package/src/hooks/use-og-placeholder.ts +0 -45
- /package/dist/{chunk-RY62M66O.js.map → chunk-7EMOBUB2.js.map} +0 -0
- /package/dist/{chunk-WDWGFUT2.js.map → chunk-APWW3GPU.js.map} +0 -0
- /package/dist/{chunk-PI4WSYQV.js.map → chunk-JQLC2FVM.js.map} +0 -0
- /package/dist/{chunk-DVHQGII5.js.map → chunk-K3F3AXCC.js.map} +0 -0
- /package/dist/{chunk-N4DEX22O.js.map → chunk-KNX4OEU5.js.map} +0 -0
- /package/dist/{chunk-B2IN2IND.js.map → chunk-OZW6GJKN.js.map} +0 -0
- /package/dist/{chunk-NEVMYN4G.js.map → chunk-QSPEFQSN.js.map} +0 -0
- /package/dist/{chunk-QE5CNWPF.js.map → chunk-SYNOZPQC.js.map} +0 -0
- /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
|
@@ -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 `
|
|
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
|
-
* `
|
|
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 `
|
|
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 `
|
|
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
|
|
@@ -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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
*
|
|
4
|
+
* Entity-card OG-placeholder resolver.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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 {
|
|
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
|
|
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
|
-
*
|
|
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
|
|
45
|
-
|
|
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: `
|
|
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
|
|
69
|
-
* the platform's brand
|
|
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
|
-
|
|
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))`
|
package/src/hooks/index.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/dist/chunk-27APPAJN.cjs
DELETED
|
@@ -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"]}
|