@flamingo-stack/openframe-frontend-core 0.0.699 → 0.0.700

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 (75) hide show
  1. package/dist/{chunk-HVY6MU33.cjs → chunk-2CSYUMNM.cjs} +2 -2
  2. package/dist/{chunk-Q2QS562J.cjs → chunk-4H5Q6MQG.cjs} +44 -44
  3. package/dist/{chunk-ERC2PVUG.cjs → chunk-65YVUYWJ.cjs} +32 -32
  4. package/dist/{chunk-SUXB6YSP.js → chunk-6JG75WVO.js} +4 -4
  5. package/dist/{chunk-Z6R7GE5L.js → chunk-6JPQDYYY.js} +261 -232
  6. package/dist/{chunk-SCUPTMLG.cjs → chunk-7ZDTAABP.cjs} +7 -7
  7. package/dist/{chunk-UDJPRMLP.js → chunk-A5ZGKBN2.js} +1 -1
  8. package/dist/{chunk-U473T27Y.js → chunk-B7M6QEEM.js} +1 -1
  9. package/dist/{chunk-GRTLCRZA.cjs → chunk-BPS7JQCI.cjs} +2 -2
  10. package/dist/{chunk-YJNJINXN.cjs → chunk-BQ34PIFC.cjs} +3 -3
  11. package/dist/{chunk-GZLE67PF.cjs → chunk-BRQY2HKD.cjs} +24 -24
  12. package/dist/{chunk-4CZC5NFR.js → chunk-BZYMGI4L.js} +1 -1
  13. package/dist/{chunk-SNFL3B73.js → chunk-CUPJCGLB.js} +2 -2
  14. package/dist/{chunk-5RYXQ4TX.cjs → chunk-DG2I57UM.cjs} +21 -21
  15. package/dist/{chunk-WYLVP5OC.js → chunk-DJLZXJYB.js} +1 -1
  16. package/dist/{chunk-WTR2GHID.js → chunk-EONNPRAW.js} +2 -2
  17. package/dist/{chunk-36S7W4AD.js → chunk-GD4JALTR.js} +2 -2
  18. package/dist/{chunk-UM44DDPC.cjs → chunk-GFXZMFDX.cjs} +9 -9
  19. package/dist/{chunk-6THYLHRF.js → chunk-JPVDJB27.js} +4 -4
  20. package/dist/{chunk-6ARCZ2VH.js → chunk-MFORWVGD.js} +5 -5
  21. package/dist/{chunk-Z4W4XDDZ.js → chunk-N7DPIH57.js} +1 -1
  22. package/dist/{chunk-SWXXLZLZ.js → chunk-N7HMUQRV.js} +1 -1
  23. package/dist/{chunk-JN7IKMMJ.cjs → chunk-NIT257DV.cjs} +4 -4
  24. package/dist/{chunk-KXRUQCHZ.js → chunk-PNWIVND4.js} +3 -3
  25. package/dist/{chunk-EKPOIGYI.cjs → chunk-QDIX2ZRL.cjs} +313 -313
  26. package/dist/{chunk-ANOZ7Z4A.cjs → chunk-QPNR4N7N.cjs} +20 -20
  27. package/dist/{chunk-QTLRRPDO.cjs → chunk-QZGFJBG5.cjs} +70 -70
  28. package/dist/{chunk-2MVAILI4.cjs → chunk-RTGPIHVO.cjs} +334 -305
  29. package/dist/{chunk-LGEGOCU7.js → chunk-S4ADN25W.js} +2 -2
  30. package/dist/{chunk-IAMMZHYE.js → chunk-V5QNDOEL.js} +4 -4
  31. package/dist/{chunk-K7SNFTIX.cjs → chunk-YBC346FY.cjs} +12 -12
  32. package/dist/{chunk-RD3WWENS.cjs → chunk-ZSPHOHOW.cjs} +9 -9
  33. package/dist/components/case-studies/index.cjs +14 -14
  34. package/dist/components/case-studies/index.js +3 -3
  35. package/dist/components/chat/entity-cards/dispatch.d.ts +50 -1
  36. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  37. package/dist/components/chat/index.cjs +6 -2
  38. package/dist/components/chat/index.js +5 -1
  39. package/dist/components/chat/utils/card-destination.d.ts +48 -0
  40. package/dist/components/chat/utils/card-destination.d.ts.map +1 -0
  41. package/dist/components/chat/utils/index.d.ts +2 -1
  42. package/dist/components/chat/utils/index.d.ts.map +1 -1
  43. package/dist/components/chat/utils/resolve-fetched-card-href.d.ts +10 -0
  44. package/dist/components/chat/utils/resolve-fetched-card-href.d.ts.map +1 -1
  45. package/dist/components/contact/index.cjs +4 -4
  46. package/dist/components/contact/index.js +3 -3
  47. package/dist/components/docs/index.cjs +7 -7
  48. package/dist/components/docs/index.js +6 -6
  49. package/dist/components/embeds/index.cjs +4 -4
  50. package/dist/components/embeds/index.js +3 -3
  51. package/dist/components/faq/index.cjs +4 -4
  52. package/dist/components/faq/index.js +3 -3
  53. package/dist/components/features/index.cjs +3 -3
  54. package/dist/components/features/index.js +2 -2
  55. package/dist/components/help-center-pages/index.cjs +71 -71
  56. package/dist/components/help-center-pages/index.js +14 -14
  57. package/dist/components/index.cjs +45 -41
  58. package/dist/components/index.js +17 -13
  59. package/dist/components/meeting-scheduler/index.cjs +25 -25
  60. package/dist/components/meeting-scheduler/index.js +2 -2
  61. package/dist/components/navigation/index.cjs +6 -6
  62. package/dist/components/navigation/index.js +5 -5
  63. package/dist/components/onboarding-guides/index.cjs +6 -6
  64. package/dist/components/onboarding-guides/index.js +5 -5
  65. package/dist/components/tickets/index.cjs +7 -7
  66. package/dist/components/tickets/index.js +6 -6
  67. package/dist/components/ui/index.cjs +7 -3
  68. package/dist/components/ui/index.js +6 -2
  69. package/dist/index.cjs +11 -7
  70. package/dist/index.js +10 -6
  71. package/package.json +1 -1
  72. package/src/components/chat/entity-cards/dispatch.tsx +29 -73
  73. package/src/components/chat/utils/card-destination.ts +103 -0
  74. package/src/components/chat/utils/index.ts +13 -1
  75. package/src/components/chat/utils/resolve-fetched-card-href.ts +17 -0
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  SoftwareInfo,
10
10
  SoftwareSourceBadge,
11
11
  ToolBadge
12
- } from "./chunk-SWXXLZLZ.js";
12
+ } from "./chunk-N7HMUQRV.js";
13
13
  import {
14
14
  AppHeader,
15
15
  AppLayout,
@@ -29,14 +29,14 @@ import {
29
29
  SlidingSidebar,
30
30
  TicketAlertsButton,
31
31
  TopNavigation
32
- } from "./chunk-KXRUQCHZ.js";
32
+ } from "./chunk-PNWIVND4.js";
33
33
  import {
34
34
  MobileNavigationDropdown,
35
35
  MultiLevelNavigation,
36
36
  StickySectionNav,
37
37
  useSectionNavigation
38
- } from "./chunk-WYLVP5OC.js";
39
- import "./chunk-LGEGOCU7.js";
38
+ } from "./chunk-DJLZXJYB.js";
39
+ import "./chunk-S4ADN25W.js";
40
40
  import {
41
41
  AIEnrichButton,
42
42
  AIEnrichSection,
@@ -655,7 +655,7 @@ import {
655
655
  withFirstTouchAttribution,
656
656
  withWalkthroughOpenParam,
657
657
  writeDismissCookie
658
- } from "./chunk-U473T27Y.js";
658
+ } from "./chunk-B7M6QEEM.js";
659
659
  import "./chunk-LQYXMEYF.js";
660
660
  import "./chunk-4JPLYOS6.js";
661
661
  import {
@@ -1131,6 +1131,7 @@ import {
1131
1131
  processHistoricalMessages,
1132
1132
  processHistoricalMessagesWithErrors,
1133
1133
  ratioToCategory,
1134
+ readFetchedCardPath,
1134
1135
  readFetchedCardTitle,
1135
1136
  rebaseCaptionsUrl,
1136
1137
  recordCountLabel,
@@ -1139,6 +1140,7 @@ import {
1139
1140
  remarkMentionChips,
1140
1141
  renderChatInlineEntityCard,
1141
1142
  renderQuickActionIcon,
1143
+ resolveCardDestination,
1142
1144
  resolveExternalNavigation,
1143
1145
  resolveFetchedCardHref,
1144
1146
  resolveIcon,
@@ -1188,7 +1190,7 @@ import {
1188
1190
  useVideoOriginPreconnect,
1189
1191
  useVideoWarmup,
1190
1192
  usesCanonicalStatusStyle
1191
- } from "./chunk-Z6R7GE5L.js";
1193
+ } from "./chunk-6JPQDYYY.js";
1192
1194
  import {
1193
1195
  HEADING_EMOJI_RE,
1194
1196
  HUB_HEADER_OFFSET_PX,
@@ -3337,6 +3339,7 @@ export {
3337
3339
  progressColorByVariant,
3338
3340
  ratioToCategory,
3339
3341
  readDismissCookie,
3342
+ readFetchedCardPath,
3340
3343
  readFetchedCardTitle,
3341
3344
  readLeadingDecisionFrame,
3342
3345
  rebaseCaptionsUrl,
@@ -3354,6 +3357,7 @@ export {
3354
3357
  renderTicketStatusOption,
3355
3358
  rescueCompletionPct,
3356
3359
  resolutionToStatus,
3360
+ resolveCardDestination,
3357
3361
  resolveContentFetchPath,
3358
3362
  resolveExternalNavigation,
3359
3363
  resolveFetchedCardHref,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.699",
3
+ "version": "0.0.700",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,7 +29,8 @@
29
29
  * fetch-mode entries.
30
30
  */
31
31
 
32
- import React, { type ReactNode } from 'react';
32
+ import type React from 'react';
33
+ import type { ReactNode } from 'react';
33
34
  import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context';
34
35
  import Image from '../../../embed-shims/next-image';
35
36
  import { useRouter } from '../../../embed-shims/next-navigation';
@@ -106,15 +107,15 @@ import type { PrReviewState, GitHubActivityKind } from '../types/entities/github
106
107
  import { formatInvestorUpdatePeriod } from '../types/entities/investor-update';
107
108
  import type { BaseProgramItem, ProgramConfig } from '../types/entities/program-types';
108
109
  import { getStatusColorScheme } from '../utils/agent-status-message';
110
+ import { resolveCardDestination } from '../utils/card-destination';
109
111
  import { resolveHrefForRuntime } from '../utils/chat-nav-resolution';
110
- import { safeHref } from '../utils/compact-card-classes';
111
112
  import { executeNavigation } from '../utils/execute-navigation';
112
113
  import { clickupTaskUrl } from '../utils/external-app-urls';
113
114
  import { resolveIcon } from '../utils/icon-library';
114
115
  import { computeIsNewTab, buildAnchorProps } from '../utils/nav-anchor-props';
115
- import { resolveFetchedCardHref, pickFetchedCardHref, readFetchedCardTitle } from '../utils/resolve-fetched-card-href';
116
+ import { readFetchedCardTitle } from '../utils/resolve-fetched-card-href';
116
117
  import { getSourceLabel } from '../utils/source-icons';
117
- import { resolveSourceRowCTA, resolveSourceIcon, sourceRowCtxFromRuntime } from '../utils/source-row-cta';
118
+ import { resolveSourceIcon, sourceRowCtxFromRuntime } from '../utils/source-row-cta';
118
119
  import { BlockCard } from './block-card';
119
120
  import { BlogCardSkeleton } from './blog-card';
120
121
  import { CampaignCardAdminSkeleton } from './campaign-card-admin';
@@ -1629,7 +1630,8 @@ function roadmapRegistryEntries(): Record<string, ChatCardRegistryEntry> {
1629
1630
  }));
1630
1631
  }
1631
1632
 
1632
- const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
1633
+ /** Exported for the destination test, which walks every registered type. */
1634
+ export const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
1633
1635
  // ───────── ref-shaped types: hydrated via /api/chat/entity-refs ─────────
1634
1636
  ...githubRegistryEntries(),
1635
1637
  // Generic TOMBSTONE for entities a chat action deleted (ClickUp task
@@ -2002,28 +2004,7 @@ export function ChatCardLoader({
2002
2004
  extras,
2003
2005
  }: ChatCardLoaderProps) {
2004
2006
  const runtime = useRequiredChatRuntime();
2005
- const resolvedChatRef = React.useMemo<ChatRef>(() => {
2006
- const cta = resolveSourceRowCTA(
2007
- {
2008
- sourceRepo: chatRef.sourceRepo,
2009
- documentType: chatRef.type,
2010
- id: chatRef.id,
2011
- title: chatRef.title,
2012
- externalUrl: chatRef.url,
2013
- targetPlatform: chatRef.targetPlatform,
2014
- path: typeof chatRef.metadata?.path === 'string' ? chatRef.metadata.path : null,
2015
- },
2016
- sourceRowCtxFromRuntime(runtime, { baseRoute, chipBasePlatform }),
2017
- );
2018
- const finalHref = cta.href ? resolveHrefForRuntime(cta.href, runtime) : null;
2019
- return {
2020
- ...chatRef,
2021
- url: finalHref ?? chatRef.url,
2022
- targetPlatform: cta.targetPlatform ?? chatRef.targetPlatform ?? null,
2023
- };
2024
- }, [chatRef, runtime, baseRoute, chipBasePlatform]);
2025
-
2026
- const entry = CHAT_CARD_REGISTRY[resolvedChatRef.type];
2007
+ const entry = CHAT_CARD_REGISTRY[chatRef.type];
2027
2008
  // Hook order MUST be stable across renders — call the data hook
2028
2009
  // unconditionally regardless of entry mode. For non-fetch types the
2029
2010
  // `contentRefType` is empty so the hook returns `isLoading=false` and
@@ -2031,55 +2012,30 @@ export function ChatCardLoader({
2031
2012
  const fetchEntry = entry && entry.contentRefType ? entry : null;
2032
2013
  const { item, isLoading, isError, isFetched } = useChatCardItem<ChatCardItem>(
2033
2014
  fetchEntry?.contentRefType ?? '',
2034
- fetchEntry ? resolvedChatRef.id : '',
2015
+ fetchEntry ? chatRef.id : '',
2035
2016
  );
2036
2017
  if (!entry) return null;
2037
2018
 
2038
- // Apply the post-fetch URL fallback (the ref carried no `externalUrl`).
2039
- // We mutate `resolvedChatRef.url` BEFORE computing isNewTab so the
2040
- // wrapper's interceptor sees the destination the user will actually
2041
- // visit. `safeHref` blocks `javascript:` / `data:` payloads even
2042
- // though the registry callers compose hub-internal strings today.
2043
- //
2044
- // Two producers, ranked by `pickFetchedCardHref` (which owns the
2045
- // precedence and its rationale):
2046
- // 1. the registry's per-type `fallbackHref` — an explicit non-content
2047
- // destination (marketing campaign → `/admin/...`, or a ref-hydrated
2048
- // row's own `item.url`);
2049
- // 2. the host's `composeContentUrl` seam via `resolveFetchedCardHref` —
2050
- // the SAME resolver page cards go through. This is what makes cards
2051
- // clickable on every transport: the wire ships bare
2052
- // `[card://type:id]` markers with no metadata, so the ref reaches
2053
- // us with `url: null` and `resolveSourceRowCTA` has nothing to route.
2054
- // …except an EXPLICIT host override outranks both: the seam is asked
2055
- // even for `fallbackHref` / `noComposedHref` types now, and its answer is
2056
- // taken only when the host actually re-homed the type (never when the
2057
- // composer merely synthesized one) — the case those two flags guard against.
2058
- const composedHref =
2059
- fetchEntry?.contentRefType && !resolvedChatRef.url && item
2060
- ? resolveFetchedCardHref({
2061
- contentRefType: fetchEntry.contentRefType,
2062
- id: resolvedChatRef.id,
2063
- item,
2064
- composeContentUrl: runtime.composeContentUrl,
2065
- })
2066
- : null;
2067
- const hrefChoice =
2068
- fetchEntry && !resolvedChatRef.url && item
2069
- ? pickFetchedCardHref({
2070
- composed: composedHref,
2071
- itemHref: fetchEntry.fallbackHref?.(item) ?? null,
2072
- allowComposed: !fetchEntry.noComposedHref,
2073
- })
2074
- : null;
2075
- const hrefResolvedChatRef: ChatRef = hrefChoice
2076
- ? {
2077
- ...resolvedChatRef,
2078
- url: safeHref(hrefChoice.href),
2079
- // The `item` branch carries no platform of its own — keep the ref's.
2080
- targetPlatform: hrefChoice.targetPlatform ?? resolvedChatRef.targetPlatform ?? null,
2081
- }
2082
- : resolvedChatRef;
2019
+ // THE card's destination, decided in one place (`resolveCardDestination`):
2020
+ // the source chip's resolver with the row as far as it is known (the ref's
2021
+ // fields, plus the fetched row's doc path when a bare `[card://type:id]`
2022
+ // marker carried none), else the fetched row's own destination. Computed
2023
+ // BEFORE `isNewTab` so the click wrapper sees the page the user will visit.
2024
+ const destination = resolveCardDestination({
2025
+ chatRef,
2026
+ item: fetchEntry ? item : undefined,
2027
+ entry: fetchEntry,
2028
+ ctx: sourceRowCtxFromRuntime(runtime, { baseRoute, chipBasePlatform }),
2029
+ composeContentUrl: runtime.composeContentUrl,
2030
+ resolveHref: href => resolveHrefForRuntime(href, runtime),
2031
+ });
2032
+ const hrefResolvedChatRef: ChatRef = {
2033
+ ...chatRef,
2034
+ url: destination.url,
2035
+ targetPlatform: destination.targetPlatform,
2036
+ // The nav wrapper reads the path off the ref for in-app doc navigation.
2037
+ ...(destination.path ? { metadata: { ...(chatRef.metadata ?? {}), path: destination.path } } : {}),
2038
+ };
2083
2039
 
2084
2040
  // Title enrichment, same synthetic-ref gap as the href above: a Mingo
2085
2041
  // `[card://type:id]` marker produces `title: <id>` because the transport
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Where an inline chat card goes when clicked: THE one decision, pure.
3
+ *
4
+ * A card and the source chip for the same row must land on the same page. The
5
+ * chip always has its row (`externalUrl`, `path`); a card may arrive as a bare
6
+ * `[card://type:id]` marker and learn the rest only when its row loads. So the
7
+ * card asks the chip's resolver (`resolveSourceRowCTA`) with everything known
8
+ * at that moment, the marker's fields first and the fetched row's `path` when
9
+ * the marker had none, and falls back to the fetched row's own destination
10
+ * (`pickFetchedCardHref`) only when that resolver has nothing to route.
11
+ *
12
+ * `ChatCardLoader` calls this once per render; nothing else decides a card's
13
+ * href. No React, no DOM.
14
+ */
15
+
16
+ import type { ComposeContentUrl } from '../../../utils/content-href';
17
+ import type { ChatRef } from '../chat-ref.types';
18
+ import { safeHref } from './compact-card-classes';
19
+ import { pickFetchedCardHref, readFetchedCardPath, resolveFetchedCardHref } from './resolve-fetched-card-href';
20
+ import { resolveSourceRowCTA, type SourceRowContext } from './source-row-cta';
21
+
22
+ /** Which rule produced the destination (for tests and debugging). */
23
+ export type CardDestinationSource = 'row' | 'hostOverride' | 'item' | 'composed' | 'none';
24
+
25
+ export interface CardDestination {
26
+ url: string | null;
27
+ targetPlatform: string | null;
28
+ /** Doc-tree path, from the ref or the fetched row. The click router uses it for in-app doc navigation. */
29
+ path: string | null;
30
+ source: CardDestinationSource;
31
+ }
32
+
33
+ /** The routing fields of a card registry entry, generic over the row type the entry reads. */
34
+ export interface CardDestinationEntry<Item> {
35
+ contentRefType?: string;
36
+ fallbackHref?: (item: Item) => string | null;
37
+ noComposedHref?: boolean;
38
+ }
39
+
40
+ export interface CardDestinationInput<Item = unknown> {
41
+ /** The ref the card was mounted with: a full ref, or a bare marker (`url: null`, no metadata). */
42
+ chatRef: ChatRef;
43
+ /** The row the card fetched; undefined while loading and for ref-only types. */
44
+ item?: Item;
45
+ /** The registry entry's routing fields. */
46
+ entry?: CardDestinationEntry<Item> | null;
47
+ /** `sourceRowCtxFromRuntime(runtime, surface)`, the same context a source chip is resolved with. */
48
+ ctx: SourceRowContext;
49
+ /** The host's content-href seam (`runtime.composeContentUrl`). */
50
+ composeContentUrl?: ComposeContentUrl;
51
+ /** Embed-mode origin prefix (`resolveHrefForRuntime`), applied to a row-resolved href. */
52
+ resolveHref?: (href: string) => string;
53
+ }
54
+
55
+ export function resolveCardDestination<Item = unknown>({
56
+ chatRef,
57
+ item,
58
+ entry,
59
+ ctx,
60
+ composeContentUrl,
61
+ resolveHref = href => href,
62
+ }: CardDestinationInput<Item>): CardDestination {
63
+ const refPath = typeof chatRef.metadata?.path === 'string' && chatRef.metadata.path ? chatRef.metadata.path : null;
64
+ const path = refPath ?? (item ? readFetchedCardPath(item) : null);
65
+
66
+ // 1. The chip's resolver, with the row as far as it is known.
67
+ const cta = resolveSourceRowCTA(
68
+ {
69
+ sourceRepo: chatRef.sourceRepo,
70
+ documentType: chatRef.type,
71
+ id: chatRef.id,
72
+ title: chatRef.title,
73
+ externalUrl: chatRef.url,
74
+ targetPlatform: chatRef.targetPlatform,
75
+ path,
76
+ },
77
+ ctx,
78
+ );
79
+ const rowUrl = cta.href ? resolveHref(cta.href) : (chatRef.url ?? null);
80
+ const rowPlatform = cta.targetPlatform ?? chatRef.targetPlatform ?? null;
81
+ if (rowUrl) return { url: rowUrl, targetPlatform: rowPlatform, path, source: 'row' };
82
+
83
+ // 2. The fetched row's own destination: an explicit host override, else the
84
+ // registry's `fallbackHref`, else the host seam's composed href.
85
+ if (entry?.contentRefType && item) {
86
+ const choice = pickFetchedCardHref({
87
+ composed: resolveFetchedCardHref({
88
+ contentRefType: entry.contentRefType,
89
+ id: chatRef.id,
90
+ item,
91
+ composeContentUrl,
92
+ }),
93
+ itemHref: entry.fallbackHref?.(item) ?? null,
94
+ allowComposed: !entry.noComposedHref,
95
+ });
96
+ const url = choice ? (safeHref(choice.href) ?? null) : null;
97
+ if (choice && url) {
98
+ // The `item` branch carries no platform of its own: keep the ref's.
99
+ return { url, targetPlatform: choice.targetPlatform ?? rowPlatform, path, source: choice.source };
100
+ }
101
+ }
102
+ return { url: null, targetPlatform: rowPlatform, path, source: 'none' };
103
+ }
@@ -176,7 +176,19 @@ export {
176
176
 
177
177
  // Post-fetch enrichment for the minimal ChatRef descriptor a
178
178
  // `[card://type:id]` marker produces (the wire ships no card metadata).
179
- export { resolveFetchedCardHref, readFetchedCardTitle, type FetchedCardHrefInput } from './resolve-fetched-card-href';
179
+ export {
180
+ resolveCardDestination,
181
+ type CardDestination,
182
+ type CardDestinationEntry,
183
+ type CardDestinationInput,
184
+ type CardDestinationSource,
185
+ } from './card-destination';
186
+ export {
187
+ resolveFetchedCardHref,
188
+ readFetchedCardPath,
189
+ readFetchedCardTitle,
190
+ type FetchedCardHrefInput,
191
+ } from './resolve-fetched-card-href';
180
192
 
181
193
  // The ONE "Ask Mingo" prompt builder — shared by the SSE adapter, the NATS
182
194
  // adapter, and hosts that inject their own Mingo state (openframe-frontend).
@@ -70,6 +70,23 @@ export function readFetchedCardTitle(item: unknown): string | null {
70
70
  return null;
71
71
  }
72
72
 
73
+ /**
74
+ * Doc-tree path of a fetched row (`metadata.path`), or null.
75
+ *
76
+ * A doc card (`markdown`, `data_room_doc`) has no public url: its destination
77
+ * is its viewer plus this path, the route `resolveSourceRowCTA` composes for a
78
+ * source chip. A bare `[card://type:id]` marker carries no path, so the loader
79
+ * reads it from the row once it loads and resolves the card the way the chip
80
+ * was resolved.
81
+ */
82
+ export function readFetchedCardPath(item: unknown): string | null {
83
+ if (!item || typeof item !== 'object') return null;
84
+ const metadata = (item as { metadata?: unknown }).metadata;
85
+ if (!metadata || typeof metadata !== 'object') return null;
86
+ const path = (metadata as { path?: unknown }).path;
87
+ return typeof path === 'string' && path.trim() ? path.trim() : null;
88
+ }
89
+
73
90
  /** Read the row's platform-association array — the hub composer keys its
74
91
  * cross-platform topology off it; the embedder default ignores it. Hub
75
92
  * list-API rows carry the array under the registry arrayKey