@flamingo-stack/openframe-frontend-core 0.0.512-1640.4220.1 → 0.0.512
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-HCKICFSZ.cjs → chunk-2WWDZR4D.cjs} +31 -31
- package/dist/{chunk-HCKICFSZ.cjs.map → chunk-2WWDZR4D.cjs.map} +1 -1
- package/dist/{chunk-FAPHWUEM.cjs → chunk-2XL7UK5Y.cjs} +5 -5
- package/dist/{chunk-FAPHWUEM.cjs.map → chunk-2XL7UK5Y.cjs.map} +1 -1
- package/dist/{chunk-XHGL7XHL.js → chunk-4NFDSMB5.js} +4 -4
- package/dist/{chunk-YJRJCJRK.cjs → chunk-5JBDT2MQ.cjs} +7 -7
- package/dist/{chunk-YJRJCJRK.cjs.map → chunk-5JBDT2MQ.cjs.map} +1 -1
- package/dist/{chunk-VL6RXIPW.cjs → chunk-67YDSTH7.cjs} +9 -9
- package/dist/{chunk-VL6RXIPW.cjs.map → chunk-67YDSTH7.cjs.map} +1 -1
- package/dist/{chunk-7UYW6C4P.js → chunk-6LMTBNLN.js} +2 -2
- package/dist/{chunk-QXFJU5I2.cjs → chunk-7VBEOGCR.cjs} +61 -61
- package/dist/{chunk-QXFJU5I2.cjs.map → chunk-7VBEOGCR.cjs.map} +1 -1
- package/dist/{chunk-WOM5RZ6T.cjs → chunk-AWLFJMOJ.cjs} +37 -37
- package/dist/{chunk-WOM5RZ6T.cjs.map → chunk-AWLFJMOJ.cjs.map} +1 -1
- package/dist/{chunk-SIRV5BCG.cjs → chunk-DQDPDUI3.cjs} +4 -4
- package/dist/{chunk-SIRV5BCG.cjs.map → chunk-DQDPDUI3.cjs.map} +1 -1
- package/dist/{chunk-Y5QFN4WH.cjs → chunk-F2YI5N7E.cjs} +13 -13
- package/dist/{chunk-Y5QFN4WH.cjs.map → chunk-F2YI5N7E.cjs.map} +1 -1
- package/dist/{chunk-IYIO2OVS.js → chunk-FI35LJ3L.js} +2 -2
- package/dist/{chunk-NZR2U5VK.cjs → chunk-GOFTC2HV.cjs} +444 -479
- package/dist/chunk-GOFTC2HV.cjs.map +1 -0
- package/dist/{chunk-6SVI63CC.js → chunk-HGZPH7S5.js} +2 -2
- package/dist/{chunk-7LHDZK7M.js → chunk-IHNUMRMU.js} +2 -2
- package/dist/{chunk-YLVVZVK6.js → chunk-IK2WRYVA.js} +4 -4
- package/dist/{chunk-XDOZN745.cjs → chunk-JIW5JI3E.cjs} +3 -3
- package/dist/{chunk-XDOZN745.cjs.map → chunk-JIW5JI3E.cjs.map} +1 -1
- package/dist/{chunk-CLEAYFOX.js → chunk-JKAX5K7C.js} +5 -5
- package/dist/{chunk-4BUHSV4D.js → chunk-JO3NPEK6.js} +2 -2
- package/dist/{chunk-ARTN2P3B.js → chunk-JOFGJYIT.js} +2 -2
- package/dist/{chunk-ZI3GAXFN.js → chunk-OT4RV6WY.js} +6 -6
- package/dist/{chunk-QUT2X2LX.cjs → chunk-RJXI25TJ.cjs} +26 -26
- package/dist/{chunk-QUT2X2LX.cjs.map → chunk-RJXI25TJ.cjs.map} +1 -1
- package/dist/{chunk-EXDW3LB5.cjs → chunk-SG25J6LV.cjs} +87 -87
- package/dist/{chunk-EXDW3LB5.cjs.map → chunk-SG25J6LV.cjs.map} +1 -1
- package/dist/{chunk-DDWLGE6D.js → chunk-V5WLIFKN.js} +325 -360
- package/dist/chunk-V5WLIFKN.js.map +1 -0
- package/dist/{chunk-OOYBU46B.cjs → chunk-VVXUUBML.cjs} +26 -26
- package/dist/{chunk-OOYBU46B.cjs.map → chunk-VVXUUBML.cjs.map} +1 -1
- package/dist/{chunk-Q4XWNC33.js → chunk-VZX3SSFL.js} +2 -2
- package/dist/{chunk-WHKVCEAB.cjs → chunk-WVZHCDB5.cjs} +11 -11
- package/dist/{chunk-WHKVCEAB.cjs.map → chunk-WVZHCDB5.cjs.map} +1 -1
- package/dist/{chunk-UMUNWABO.js → chunk-XG7FLAKQ.js} +2 -2
- package/dist/{chunk-EYUANCRA.js → chunk-ZIQMVAGA.js} +2 -2
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-chat-card-item.d.ts +3 -0
- package/dist/components/chat/hooks/use-chat-card-item.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/remark-strip-citations.d.ts.map +1 -1
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
- package/dist/components/chat/stream/message-mutations.d.ts +42 -5
- package/dist/components/chat/stream/message-mutations.d.ts.map +1 -1
- package/dist/components/chat/utils/message-segment-accumulator.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +132 -132
- package/dist/components/index.js +11 -11
- package/dist/components/meeting-scheduler/index.cjs +34 -34
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/chat/entity-cards/dispatch.tsx +92 -99
- package/src/components/chat/hooks/use-chat-card-item.ts +9 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +15 -15
- package/src/components/chat/remark-strip-citations.ts +7 -5
- package/src/components/chat/stream/__tests__/chat-stream-reducer-approval-batch.test.ts +18 -0
- package/src/components/chat/stream/chat-stream-reducer.ts +12 -3
- package/src/components/chat/stream/message-mutations.ts +153 -96
- package/src/components/chat/utils/message-segment-accumulator.ts +19 -27
- package/dist/chunk-DDWLGE6D.js.map +0 -1
- package/dist/chunk-NZR2U5VK.cjs.map +0 -1
- /package/dist/{chunk-XHGL7XHL.js.map → chunk-4NFDSMB5.js.map} +0 -0
- /package/dist/{chunk-7UYW6C4P.js.map → chunk-6LMTBNLN.js.map} +0 -0
- /package/dist/{chunk-IYIO2OVS.js.map → chunk-FI35LJ3L.js.map} +0 -0
- /package/dist/{chunk-6SVI63CC.js.map → chunk-HGZPH7S5.js.map} +0 -0
- /package/dist/{chunk-7LHDZK7M.js.map → chunk-IHNUMRMU.js.map} +0 -0
- /package/dist/{chunk-YLVVZVK6.js.map → chunk-IK2WRYVA.js.map} +0 -0
- /package/dist/{chunk-CLEAYFOX.js.map → chunk-JKAX5K7C.js.map} +0 -0
- /package/dist/{chunk-4BUHSV4D.js.map → chunk-JO3NPEK6.js.map} +0 -0
- /package/dist/{chunk-ARTN2P3B.js.map → chunk-JOFGJYIT.js.map} +0 -0
- /package/dist/{chunk-ZI3GAXFN.js.map → chunk-OT4RV6WY.js.map} +0 -0
- /package/dist/{chunk-Q4XWNC33.js.map → chunk-VZX3SSFL.js.map} +0 -0
- /package/dist/{chunk-UMUNWABO.js.map → chunk-XG7FLAKQ.js.map} +0 -0
- /package/dist/{chunk-EYUANCRA.js.map → chunk-ZIQMVAGA.js.map} +0 -0
|
@@ -1026,26 +1026,34 @@ const FINANCIAL_CARD_CONFIGS: Record<string, FinancialCardConfig> = {
|
|
|
1026
1026
|
balance_sheet: { label: 'Balance sheet', icon: () => <BankIcon size={24} /> },
|
|
1027
1027
|
cash_flow: { label: 'Cash flow', icon: () => <CoinsExchangeCurrencyIcon size={24} /> },
|
|
1028
1028
|
}
|
|
1029
|
-
|
|
1029
|
+
/** Expand a per-family config map into registry entries — ONE loop shell
|
|
1030
|
+
* for every card family (financial, github, program, roadmap); adding a
|
|
1031
|
+
* family is a config map + one `registryEntries(...)` call. */
|
|
1032
|
+
function registryEntries<C>(
|
|
1033
|
+
configs: Record<string, C>,
|
|
1034
|
+
build: (cfg: C) => ChatCardRegistryEntry,
|
|
1035
|
+
): Record<string, ChatCardRegistryEntry> {
|
|
1030
1036
|
const out: Record<string, ChatCardRegistryEntry> = {}
|
|
1031
|
-
for (const [docType, cfg] of Object.entries(
|
|
1032
|
-
out[docType] = {
|
|
1033
|
-
mode: 'no-fetch',
|
|
1034
|
-
label: cfg.label,
|
|
1035
|
-
bareInline: true,
|
|
1036
|
-
render: (chatRef, opts) => (
|
|
1037
|
-
<GenericFinancialChatCard
|
|
1038
|
-
chatRef={chatRef}
|
|
1039
|
-
icon={cfg.icon()}
|
|
1040
|
-
isNewTab={opts.isNewTab}
|
|
1041
|
-
discuss={opts.discuss}
|
|
1042
|
-
/>
|
|
1043
|
-
),
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1037
|
+
for (const [docType, cfg] of Object.entries(configs)) out[docType] = build(cfg)
|
|
1046
1038
|
return out
|
|
1047
1039
|
}
|
|
1048
1040
|
|
|
1041
|
+
function financialRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1042
|
+
return registryEntries(FINANCIAL_CARD_CONFIGS, (cfg) => ({
|
|
1043
|
+
mode: 'no-fetch',
|
|
1044
|
+
label: cfg.label,
|
|
1045
|
+
bareInline: true,
|
|
1046
|
+
render: (chatRef, opts) => (
|
|
1047
|
+
<GenericFinancialChatCard
|
|
1048
|
+
chatRef={chatRef}
|
|
1049
|
+
icon={cfg.icon()}
|
|
1050
|
+
isNewTab={opts.isNewTab}
|
|
1051
|
+
discuss={opts.discuss}
|
|
1052
|
+
/>
|
|
1053
|
+
),
|
|
1054
|
+
}))
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1049
1057
|
interface GitHubCardConfig {
|
|
1050
1058
|
label: string
|
|
1051
1059
|
kind: GitHubActivityKind
|
|
@@ -1059,19 +1067,15 @@ const GITHUB_CARD_CONFIGS: Record<string, GitHubCardConfig> = {
|
|
|
1059
1067
|
github_pr_review_public: { label: 'GitHub review (public)', kind: 'pr_review' },
|
|
1060
1068
|
}
|
|
1061
1069
|
function githubRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
),
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
return out
|
|
1070
|
+
return registryEntries(GITHUB_CARD_CONFIGS, (cfg) => ({
|
|
1071
|
+
mode: 'no-fetch',
|
|
1072
|
+
label: cfg.label,
|
|
1073
|
+
bareInline: true,
|
|
1074
|
+
render: (chatRef, opts) => (
|
|
1075
|
+
<GitHubChatCard chatRef={chatRef} kind={cfg.kind} isNewTab={opts.isNewTab}
|
|
1076
|
+
discuss={opts.discuss} />
|
|
1077
|
+
),
|
|
1078
|
+
}))
|
|
1075
1079
|
}
|
|
1076
1080
|
|
|
1077
1081
|
type ProgramConfigKey = 'podcast' | 'webinar' | 'event'
|
|
@@ -1086,28 +1090,24 @@ const PROGRAM_CARD_CONFIGS: Record<string, ProgramCardConfig> = {
|
|
|
1086
1090
|
event: { label: 'Event', configKey: 'event', contentRefType: 'event' },
|
|
1087
1091
|
}
|
|
1088
1092
|
function programRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
),
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
return out
|
|
1093
|
+
return registryEntries(PROGRAM_CARD_CONFIGS, (cfg) => ({
|
|
1094
|
+
mode: 'fetch',
|
|
1095
|
+
label: cfg.label,
|
|
1096
|
+
contentRefType: cfg.contentRefType,
|
|
1097
|
+
bareInline: true,
|
|
1098
|
+
skeleton: () => <ProgramCardSkeleton size="sm" />,
|
|
1099
|
+
render: (item, chatRef, opts) => (
|
|
1100
|
+
<ProgramChatCard
|
|
1101
|
+
item={item}
|
|
1102
|
+
chatRef={chatRef}
|
|
1103
|
+
isNewTab={opts.isNewTab}
|
|
1104
|
+
discuss={opts.discuss}
|
|
1105
|
+
configKey={cfg.configKey}
|
|
1106
|
+
label={cfg.configKey.charAt(0).toUpperCase() + cfg.configKey.slice(1)}
|
|
1107
|
+
ogPlaceholder={opts?.extras?.buildOgPlaceholderUrl?.(item?.title ?? '') ?? null}
|
|
1108
|
+
/>
|
|
1109
|
+
),
|
|
1110
|
+
}))
|
|
1111
1111
|
}
|
|
1112
1112
|
|
|
1113
1113
|
type RoadmapCardType = 'roadmap_item' | 'delivery_item' | 'internal_task'
|
|
@@ -1135,27 +1135,23 @@ const ROADMAP_CARD_CONFIGS: Record<string, RoadmapEntryConfig> = {
|
|
|
1135
1135
|
},
|
|
1136
1136
|
}
|
|
1137
1137
|
function roadmapRegistryEntries(): Record<string, ChatCardRegistryEntry> {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
),
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
return out
|
|
1138
|
+
return registryEntries(ROADMAP_CARD_CONFIGS, (cfg) => ({
|
|
1139
|
+
mode: 'fetch',
|
|
1140
|
+
label: cfg.label,
|
|
1141
|
+
contentRefType: cfg.contentRefType,
|
|
1142
|
+
bareInline: true,
|
|
1143
|
+
...(cfg.noComposedHref ? { noComposedHref: true } : {}),
|
|
1144
|
+
skeleton: () => <RoadmapCardSkeleton size="sm" />,
|
|
1145
|
+
render: (item, chatRef, opts) => (
|
|
1146
|
+
<RoadmapChatCard
|
|
1147
|
+
item={item}
|
|
1148
|
+
chatRef={chatRef}
|
|
1149
|
+
isNewTab={opts.isNewTab}
|
|
1150
|
+
discuss={opts.discuss}
|
|
1151
|
+
cardType={cfg.cardType}
|
|
1152
|
+
/>
|
|
1153
|
+
),
|
|
1154
|
+
}))
|
|
1159
1155
|
}
|
|
1160
1156
|
|
|
1161
1157
|
const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
|
|
@@ -1558,7 +1554,7 @@ export function ChatCardLoader({
|
|
|
1558
1554
|
// `contentRefType` is empty so the hook returns `isLoading=false` and
|
|
1559
1555
|
// `item=undefined`, which we ignore.
|
|
1560
1556
|
const fetchEntry = entry && entry.mode === 'fetch' ? entry : null
|
|
1561
|
-
const { item, isLoading, isError } = useChatCardItem<any>(
|
|
1557
|
+
const { item, isLoading, isError, isFetched } = useChatCardItem<any>(
|
|
1562
1558
|
fetchEntry?.contentRefType ?? '',
|
|
1563
1559
|
fetchEntry ? resolvedChatRef.id : '',
|
|
1564
1560
|
)
|
|
@@ -1668,6 +1664,23 @@ export function ChatCardLoader({
|
|
|
1668
1664
|
{children}
|
|
1669
1665
|
</ChatCardNavWrap>
|
|
1670
1666
|
)
|
|
1667
|
+
// ONE render tail for both modes — bare-inline cards skip the discuss
|
|
1668
|
+
// wrapper; everything else gets the ChatCardWithDiscuss chrome. Kept
|
|
1669
|
+
// here (not per-branch) so the two paths can never drift on the
|
|
1670
|
+
// discuss-menu wiring.
|
|
1671
|
+
const finish = (node: React.ReactNode) =>
|
|
1672
|
+
entry.bareInline ? (
|
|
1673
|
+
<>{navWrap(node)}</>
|
|
1674
|
+
) : (
|
|
1675
|
+
<ChatCardWithDiscuss
|
|
1676
|
+
chatRef={finalChatRef}
|
|
1677
|
+
onDiscuss={onDiscuss}
|
|
1678
|
+
onDisplay={onDisplay}
|
|
1679
|
+
displayAction={entry.displayAction}
|
|
1680
|
+
>
|
|
1681
|
+
{navWrap(node)}
|
|
1682
|
+
</ChatCardWithDiscuss>
|
|
1683
|
+
)
|
|
1671
1684
|
if (entry.mode === 'no-fetch') {
|
|
1672
1685
|
// Synthetic-ref gate. `chat-message-enhanced.tsx` builds a minimal
|
|
1673
1686
|
// `{ type, id, title: cardId, url: null }` ChatRef when the LLM
|
|
@@ -1690,19 +1703,7 @@ export function ChatCardLoader({
|
|
|
1690
1703
|
// Fetch-mode types already handle this gracefully: a synthetic
|
|
1691
1704
|
// id leads to a fetch miss → `!item` → null at line ~1034.
|
|
1692
1705
|
if (!finalChatRef.sourceRepo) return null
|
|
1693
|
-
|
|
1694
|
-
return navWrap(entry.render(finalChatRef, renderOpts))
|
|
1695
|
-
}
|
|
1696
|
-
return (
|
|
1697
|
-
<ChatCardWithDiscuss
|
|
1698
|
-
chatRef={finalChatRef}
|
|
1699
|
-
onDiscuss={onDiscuss}
|
|
1700
|
-
onDisplay={onDisplay}
|
|
1701
|
-
displayAction={entry.displayAction}
|
|
1702
|
-
>
|
|
1703
|
-
{navWrap(entry.render(finalChatRef, renderOpts))}
|
|
1704
|
-
</ChatCardWithDiscuss>
|
|
1705
|
-
)
|
|
1706
|
+
return finish(entry.render(finalChatRef, renderOpts))
|
|
1706
1707
|
}
|
|
1707
1708
|
if (isLoading) return <>{entry.skeleton()}</>
|
|
1708
1709
|
if (!item) {
|
|
@@ -1711,6 +1712,10 @@ export function ChatCardLoader({
|
|
|
1711
1712
|
// "deleted" claim. Only a SUCCESSFUL fetch that lacks the id
|
|
1712
1713
|
// reaches the tombstone below.
|
|
1713
1714
|
if (isError) return null
|
|
1715
|
+
// NEVER-FETCHED (query disabled — no list URL registered for this
|
|
1716
|
+
// type, or an empty id): no request was made, so absence of `item`
|
|
1717
|
+
// proves nothing. Render nothing rather than a false "deleted".
|
|
1718
|
+
if (!isFetched) return null
|
|
1714
1719
|
// FETCH MISS. Two distinct cases, split by ref provenance:
|
|
1715
1720
|
//
|
|
1716
1721
|
// - SERVER-BUILT ref (`sourceRepo` present — the entity provably
|
|
@@ -1738,19 +1743,7 @@ export function ChatCardLoader({
|
|
|
1738
1743
|
}
|
|
1739
1744
|
return null
|
|
1740
1745
|
}
|
|
1741
|
-
|
|
1742
|
-
return <>{navWrap(entry.render(item, finalChatRef, renderOpts))}</>
|
|
1743
|
-
}
|
|
1744
|
-
return (
|
|
1745
|
-
<ChatCardWithDiscuss
|
|
1746
|
-
chatRef={finalChatRef}
|
|
1747
|
-
onDiscuss={onDiscuss}
|
|
1748
|
-
onDisplay={onDisplay}
|
|
1749
|
-
displayAction={entry.displayAction}
|
|
1750
|
-
>
|
|
1751
|
-
{navWrap(entry.render(item, finalChatRef, renderOpts))}
|
|
1752
|
-
</ChatCardWithDiscuss>
|
|
1753
|
-
)
|
|
1746
|
+
return finish(entry.render(item, finalChatRef, renderOpts))
|
|
1754
1747
|
}
|
|
1755
1748
|
|
|
1756
1749
|
// =============================================================================
|
|
@@ -28,6 +28,9 @@ export interface UseChatCardItemResult<T = unknown> {
|
|
|
28
28
|
item: T | undefined
|
|
29
29
|
isLoading: boolean
|
|
30
30
|
isError: boolean
|
|
31
|
+
/** True only after a fetch actually completed — false for disabled
|
|
32
|
+
* queries (no list URL for the type / empty id). */
|
|
33
|
+
isFetched: boolean
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
// `extractItems` / `extractItemId` hoisted to `src/utils/extract-items.ts`
|
|
@@ -84,5 +87,11 @@ export function useChatCardItem<T = unknown>(
|
|
|
84
87
|
item: (query.data ?? undefined) as T | undefined,
|
|
85
88
|
isLoading: query.isLoading,
|
|
86
89
|
isError: query.isError,
|
|
90
|
+
// True only after the query actually completed a fetch. DISABLED
|
|
91
|
+
// queries (no list URL registered for the type, empty id) never
|
|
92
|
+
// fetch — `item` is undefined there as well, and callers must not
|
|
93
|
+
// read that as "fetched fine, entity absent" (tombstone gate in
|
|
94
|
+
// entity-cards/dispatch.tsx).
|
|
95
|
+
isFetched: query.isFetched,
|
|
87
96
|
}
|
|
88
97
|
}
|
|
@@ -254,21 +254,21 @@ export function useSseChatAdapter(
|
|
|
254
254
|
|
|
255
255
|
// Approve/reject pass the transport's boolean outcome through so batch
|
|
256
256
|
// approve-all loops can mark a FAILED row (expired proposal, network
|
|
257
|
-
// error) instead of leaving its execution loader spinning.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
// error) instead of leaving its execution loader spinning. ONE factory
|
|
258
|
+
// — the two handlers differ only in the action literal.
|
|
259
|
+
const cardResolve = useCallback(
|
|
260
|
+
(action: 'approve' | 'reject') =>
|
|
261
|
+
(reqId?: string): void | Promise<boolean> => {
|
|
262
|
+
if (!reqId) return
|
|
263
|
+
return sendMessageRef.current('', {
|
|
264
|
+
hidden: true,
|
|
265
|
+
approvalAction: { proposalId: reqId, action },
|
|
266
|
+
})
|
|
267
|
+
},
|
|
268
|
+
[],
|
|
269
|
+
)
|
|
270
|
+
const cardApprove = useMemo(() => cardResolve('approve'), [cardResolve])
|
|
271
|
+
const cardReject = useMemo(() => cardResolve('reject'), [cardResolve])
|
|
272
272
|
|
|
273
273
|
const createReducerOptions = useCallback(
|
|
274
274
|
(): ChatStreamReducerOptions => ({
|
|
@@ -27,11 +27,13 @@ export const remarkStripCitations: Plugin<[], Root> = () => {
|
|
|
27
27
|
return (tree: Root) => {
|
|
28
28
|
visit(tree, "text", (node: Text) => {
|
|
29
29
|
if (!node.value || !node.value.includes("[")) return
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (
|
|
30
|
+
const withoutCitations = node.value.replace(CITATION_REGEX, "$1")
|
|
31
|
+
// No citation matched → leave the node untouched, INCLUDING its
|
|
32
|
+
// whitespace (collapsing unconditionally would rewrite deliberate
|
|
33
|
+
// spacing in prose like `array[1] value`).
|
|
34
|
+
if (withoutCitations === node.value) return
|
|
35
|
+
// Collapse doubled spaces the removal can leave mid-sentence.
|
|
36
|
+
node.value = withoutCitations.replace(/ {2,}/g, " ")
|
|
35
37
|
})
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -175,4 +175,22 @@ describe('decoder — approval_batch frame mapping', () => {
|
|
|
175
175
|
expect(approval!.toolCalls?.[0].toolTitle).toContain('Fix Website Header')
|
|
176
176
|
expect(approval!.toolCalls?.[1].toolExecutionRequestId).toBe('prop-2')
|
|
177
177
|
})
|
|
178
|
+
|
|
179
|
+
it('a colliding anchor id (misbehaving server) still cannot pre-tick a row execution', async () => {
|
|
180
|
+
// Defense-in-depth pin: the click-time flip uses the STATUS-ONLY
|
|
181
|
+
// projection, so even a server that (incorrectly) reuses a row's
|
|
182
|
+
// proposal id as the batchId cannot mark that row done before its
|
|
183
|
+
// confirm actually resolves.
|
|
184
|
+
const onApprove = vi.fn().mockResolvedValue(undefined)
|
|
185
|
+
const r = createChatStreamReducer({ transport: 'sse', callbacks: { onApprove } })
|
|
186
|
+
r.beginSseSend({ text: 'delete both', assistantName: 'Mingo AI' })
|
|
187
|
+
r.apply({ ...batchEvent, requestId: 'prop-1' })
|
|
188
|
+
const batch = findBatch(r)
|
|
189
|
+
await batch.onApprove?.(batch.data.approvalRequestId)
|
|
190
|
+
const after = findBatch(r)
|
|
191
|
+
expect(after.status).toBe('approved')
|
|
192
|
+
// The flip itself wrote NO execution for prop-1 — its icon state
|
|
193
|
+
// comes only from the confirm result / approval-resolved event.
|
|
194
|
+
expect(after.data.executions ?? {}).toEqual({})
|
|
195
|
+
})
|
|
178
196
|
})
|
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
nextId,
|
|
96
96
|
projectApprovalResolutionToMessages,
|
|
97
97
|
projectBatchRowFailureToMessages,
|
|
98
|
+
projectBatchStatusToMessages,
|
|
98
99
|
updateTrailingAssistant,
|
|
99
100
|
upsertTrailingCompaction,
|
|
100
101
|
} from './message-mutations'
|
|
@@ -1562,9 +1563,11 @@ export function createChatStreamReducer(
|
|
|
1562
1563
|
const resolveBatch =
|
|
1563
1564
|
(status: 'approved' | 'rejected', confirm: typeof callbacks.onApprove) =>
|
|
1564
1565
|
async () => {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1566
|
+
// STATUS-ONLY projection (defense-in-depth): even a
|
|
1567
|
+
// batchId colliding with a row id cannot pre-tick an
|
|
1568
|
+
// execution here — rows resolve exclusively via their
|
|
1569
|
+
// own confirm results / approval-resolved events.
|
|
1570
|
+
setMessagesInternal(projectBatchStatusToMessages(messages, anchorId, status))
|
|
1568
1571
|
for (const id of ids) {
|
|
1569
1572
|
const ok = await confirm?.(id)
|
|
1570
1573
|
if (ok === false) {
|
|
@@ -1975,6 +1978,12 @@ export function createChatStreamReducer(
|
|
|
1975
1978
|
setMessagesInternal(
|
|
1976
1979
|
projectApprovalResolutionToMessages(messages, requestId, status, resolvedByName),
|
|
1977
1980
|
)
|
|
1981
|
+
// Status-map mirror — kept in lockstep with the other two flip
|
|
1982
|
+
// paths (SSE approval-resolved at ~1240, NATS at ~1950): the map
|
|
1983
|
+
// restores status when a replayed approval-request event
|
|
1984
|
+
// re-renders the card, and a flip applied through THIS path must
|
|
1985
|
+
// survive that replay the same way.
|
|
1986
|
+
mirrorApprovalStatus(requestId, status)
|
|
1978
1987
|
return segments
|
|
1979
1988
|
},
|
|
1980
1989
|
getPendingEscalated() {
|