@open-mercato/ui 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2

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 (148) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -1
  3. package/__integration__/TC-AI-UI-003-aichat-registry.spec.tsx +204 -0
  4. package/dist/ai/AiAssistantLauncher.js +596 -0
  5. package/dist/ai/AiAssistantLauncher.js.map +7 -0
  6. package/dist/ai/AiChat.js +1092 -0
  7. package/dist/ai/AiChat.js.map +7 -0
  8. package/dist/ai/AiChatSessions.js +297 -0
  9. package/dist/ai/AiChatSessions.js.map +7 -0
  10. package/dist/ai/AiDock.js +347 -0
  11. package/dist/ai/AiDock.js.map +7 -0
  12. package/dist/ai/AiMessageContent.js +369 -0
  13. package/dist/ai/AiMessageContent.js.map +7 -0
  14. package/dist/ai/ChatPaneTabs.js +251 -0
  15. package/dist/ai/ChatPaneTabs.js.map +7 -0
  16. package/dist/ai/index.js +115 -0
  17. package/dist/ai/index.js.map +7 -0
  18. package/dist/ai/parts/ConfirmationCard.js +211 -0
  19. package/dist/ai/parts/ConfirmationCard.js.map +7 -0
  20. package/dist/ai/parts/FieldDiffCard.js +119 -0
  21. package/dist/ai/parts/FieldDiffCard.js.map +7 -0
  22. package/dist/ai/parts/MutationPreviewCard.js +224 -0
  23. package/dist/ai/parts/MutationPreviewCard.js.map +7 -0
  24. package/dist/ai/parts/MutationResultCard.js +240 -0
  25. package/dist/ai/parts/MutationResultCard.js.map +7 -0
  26. package/dist/ai/parts/approval-cards-map.js +15 -0
  27. package/dist/ai/parts/approval-cards-map.js.map +7 -0
  28. package/dist/ai/parts/index.js +24 -0
  29. package/dist/ai/parts/index.js.map +7 -0
  30. package/dist/ai/parts/pending-action-api.js +60 -0
  31. package/dist/ai/parts/pending-action-api.js.map +7 -0
  32. package/dist/ai/parts/types.js +1 -0
  33. package/dist/ai/parts/types.js.map +7 -0
  34. package/dist/ai/parts/useAiPendingActionPolling.js +126 -0
  35. package/dist/ai/parts/useAiPendingActionPolling.js.map +7 -0
  36. package/dist/ai/records/ActivityCard.js +83 -0
  37. package/dist/ai/records/ActivityCard.js.map +7 -0
  38. package/dist/ai/records/CompanyCard.js +81 -0
  39. package/dist/ai/records/CompanyCard.js.map +7 -0
  40. package/dist/ai/records/DealCard.js +76 -0
  41. package/dist/ai/records/DealCard.js.map +7 -0
  42. package/dist/ai/records/PersonCard.js +68 -0
  43. package/dist/ai/records/PersonCard.js.map +7 -0
  44. package/dist/ai/records/ProductCard.js +68 -0
  45. package/dist/ai/records/ProductCard.js.map +7 -0
  46. package/dist/ai/records/RecordCard.js +29 -0
  47. package/dist/ai/records/RecordCard.js.map +7 -0
  48. package/dist/ai/records/RecordCardShell.js +103 -0
  49. package/dist/ai/records/RecordCardShell.js.map +7 -0
  50. package/dist/ai/records/index.js +31 -0
  51. package/dist/ai/records/index.js.map +7 -0
  52. package/dist/ai/records/registry.js +51 -0
  53. package/dist/ai/records/registry.js.map +7 -0
  54. package/dist/ai/records/types.js +1 -0
  55. package/dist/ai/records/types.js.map +7 -0
  56. package/dist/ai/ui-part-registry.js +112 -0
  57. package/dist/ai/ui-part-registry.js.map +7 -0
  58. package/dist/ai/ui-part-slots.js +14 -0
  59. package/dist/ai/ui-part-slots.js.map +7 -0
  60. package/dist/ai/ui-parts/pending-phase3-placeholder.js +35 -0
  61. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +7 -0
  62. package/dist/ai/upload-adapter.js +256 -0
  63. package/dist/ai/upload-adapter.js.map +7 -0
  64. package/dist/ai/useAiChat.js +549 -0
  65. package/dist/ai/useAiChat.js.map +7 -0
  66. package/dist/ai/useAiChatUpload.js +127 -0
  67. package/dist/ai/useAiChatUpload.js.map +7 -0
  68. package/dist/ai/useAiShortcuts.js +43 -0
  69. package/dist/ai/useAiShortcuts.js.map +7 -0
  70. package/dist/backend/AppShell.js +8 -4
  71. package/dist/backend/AppShell.js.map +2 -2
  72. package/dist/backend/BackendChromeProvider.js +2 -0
  73. package/dist/backend/BackendChromeProvider.js.map +2 -2
  74. package/dist/backend/DataTable.js +19 -2
  75. package/dist/backend/DataTable.js.map +2 -2
  76. package/dist/backend/FilterBar.js +19 -15
  77. package/dist/backend/FilterBar.js.map +2 -2
  78. package/dist/backend/dashboard/DashboardScreen.js +31 -3
  79. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  80. package/dist/backend/injection/spotIds.js +6 -0
  81. package/dist/backend/injection/spotIds.js.map +2 -2
  82. package/dist/backend/notifications/useNotificationEffect.js +38 -2
  83. package/dist/backend/notifications/useNotificationEffect.js.map +2 -2
  84. package/dist/index.js +1 -0
  85. package/dist/index.js.map +2 -2
  86. package/jest.config.cjs +7 -1
  87. package/jest.markdown-mock.tsx +7 -0
  88. package/package.json +10 -4
  89. package/src/ai/AiAssistantLauncher.tsx +805 -0
  90. package/src/ai/AiChat.tsx +1483 -0
  91. package/src/ai/AiChatSessions.tsx +429 -0
  92. package/src/ai/AiDock.tsx +505 -0
  93. package/src/ai/AiMessageContent.tsx +515 -0
  94. package/src/ai/ChatPaneTabs.tsx +310 -0
  95. package/src/ai/__tests__/AiChat.conversation.test.tsx +160 -0
  96. package/src/ai/__tests__/AiChat.debug.test.tsx +152 -0
  97. package/src/ai/__tests__/AiChat.registry.test.tsx +213 -0
  98. package/src/ai/__tests__/AiChat.test.tsx +257 -0
  99. package/src/ai/__tests__/AiDock.test.tsx +124 -0
  100. package/src/ai/__tests__/AiMessageContent.test.ts +111 -0
  101. package/src/ai/__tests__/ui-part-registry.test.ts +199 -0
  102. package/src/ai/__tests__/ui-part-slots.test.ts +43 -0
  103. package/src/ai/__tests__/upload-adapter.test.ts +213 -0
  104. package/src/ai/__tests__/useAiChatUpload.test.tsx +163 -0
  105. package/src/ai/__tests__/useAiShortcuts.test.tsx +100 -0
  106. package/src/ai/index.ts +125 -0
  107. package/src/ai/parts/ConfirmationCard.tsx +310 -0
  108. package/src/ai/parts/FieldDiffCard.tsx +173 -0
  109. package/src/ai/parts/MutationPreviewCard.tsx +302 -0
  110. package/src/ai/parts/MutationResultCard.tsx +360 -0
  111. package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +169 -0
  112. package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +74 -0
  113. package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +177 -0
  114. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +127 -0
  115. package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +151 -0
  116. package/src/ai/parts/approval-cards-map.ts +24 -0
  117. package/src/ai/parts/index.ts +27 -0
  118. package/src/ai/parts/pending-action-api.ts +123 -0
  119. package/src/ai/parts/types.ts +84 -0
  120. package/src/ai/parts/useAiPendingActionPolling.ts +210 -0
  121. package/src/ai/records/ActivityCard.tsx +102 -0
  122. package/src/ai/records/CompanyCard.tsx +89 -0
  123. package/src/ai/records/DealCard.tsx +85 -0
  124. package/src/ai/records/PersonCard.tsx +77 -0
  125. package/src/ai/records/ProductCard.tsx +83 -0
  126. package/src/ai/records/RecordCard.tsx +37 -0
  127. package/src/ai/records/RecordCardShell.tsx +169 -0
  128. package/src/ai/records/index.ts +30 -0
  129. package/src/ai/records/registry.tsx +80 -0
  130. package/src/ai/records/types.ts +90 -0
  131. package/src/ai/ui-part-registry.ts +233 -0
  132. package/src/ai/ui-part-slots.ts +32 -0
  133. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +50 -0
  134. package/src/ai/upload-adapter.ts +421 -0
  135. package/src/ai/useAiChat.ts +865 -0
  136. package/src/ai/useAiChatUpload.ts +180 -0
  137. package/src/ai/useAiShortcuts.ts +79 -0
  138. package/src/backend/AppShell.tsx +12 -5
  139. package/src/backend/BackendChromeProvider.tsx +2 -0
  140. package/src/backend/DataTable.tsx +20 -1
  141. package/src/backend/FilterBar.tsx +26 -13
  142. package/src/backend/__tests__/BackendChromeProvider.test.tsx +45 -0
  143. package/src/backend/dashboard/DashboardScreen.tsx +38 -3
  144. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +24 -1
  145. package/src/backend/injection/spotIds.ts +6 -0
  146. package/src/backend/notifications/__tests__/useNotificationEffect.test.tsx +77 -0
  147. package/src/backend/notifications/useNotificationEffect.ts +47 -2
  148. package/src/index.ts +1 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ai/parts/ConfirmationCard.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { AlertTriangle, Loader2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '../../primitives/alert'\nimport { Button } from '../../primitives/button'\nimport { Spinner } from '../../primitives/spinner'\nimport { useAiShortcuts } from '../useAiShortcuts'\nimport type { AiUiPartProps } from '../ui-part-registry'\nimport { cancelPendingAction } from './pending-action-api'\nimport { useAiPendingActionPolling } from './useAiPendingActionPolling'\nimport { MutationResultCard } from './MutationResultCard'\nimport type { AiPendingActionCardAction } from './types'\n\n/**\n * Confirmation / in-flight card rendered after the user clicks `Confirm` on\n * the preview card. Shows a spinner + the side-effects summary while the\n * server runs the re-check contract and executes the wrapped tool.\n *\n * The user can race the confirm by clicking Cancel \u2014 but only while the\n * server has not yet flipped the row to `executing`. The polling hook\n * drives the disable logic.\n *\n * Surfaces structured error envelopes from the confirm route: 412\n * `stale_version` (records changed since preview) and 412 `schema_drift`\n * (tool input schema changed) render targeted alerts with the specific\n * recovery copy. Keyboard: `Escape` triggers Cancel; `Cmd/Ctrl+Enter` is\n * intentionally inert (the user already confirmed).\n *\n * Terminal states flip this card into a {@link MutationResultCard} render\n * so the chat transcript does not need two separate cards for confirmed\n * vs pending.\n */\nexport interface ConfirmationCardPayload {\n sideEffectsSummary?: string | null\n pendingAction?: AiPendingActionCardAction\n confirmError?: {\n status: number\n code?: string\n message: string\n extra?: Record<string, unknown>\n }\n}\n\nexport interface ConfirmationCardProps extends AiUiPartProps {\n /** Optional override for tests \u2014 bypasses the polling fetch. */\n initialAction?: AiPendingActionCardAction\n /** Endpoint override (tests). */\n endpoint?: string\n /** Optional cancel handler override (tests). */\n onCancel?: () => Promise<void> | void\n}\n\nexport function ConfirmationCard(props: ConfirmationCardProps) {\n const t = useT()\n const pendingActionId = props.pendingActionId ?? ''\n const payload = (props.payload as ConfirmationCardPayload | undefined) ?? {}\n const injected = props.initialAction ?? payload.pendingAction ?? null\n\n const { action, status, refresh } = useAiPendingActionPolling({\n pendingActionId,\n endpoint: props.endpoint,\n disabled: !pendingActionId,\n })\n\n const effectiveAction = action ?? injected ?? null\n const effectiveStatus = effectiveAction?.status ?? status\n\n const [isCancelling, setIsCancelling] = React.useState(false)\n const [localError, setLocalError] = React.useState<{\n code?: string\n message: string\n extra?: Record<string, unknown>\n } | null>(\n payload.confirmError\n ? { code: payload.confirmError.code, message: payload.confirmError.message, extra: payload.confirmError.extra }\n : null,\n )\n\n const canCancel =\n !isCancelling &&\n (effectiveStatus === 'pending' || effectiveStatus === 'confirmed' || effectiveStatus == null)\n\n const handleCancel = React.useCallback(async () => {\n if (!canCancel) return\n if (props.onCancel) {\n await props.onCancel()\n return\n }\n if (!pendingActionId) return\n setIsCancelling(true)\n try {\n const result = await cancelPendingAction(pendingActionId, {\n endpoint: props.endpoint,\n })\n if (!result.ok) {\n setLocalError({\n code: result.error.code,\n message: result.error.message,\n extra: result.error.extra,\n })\n }\n await refresh()\n } finally {\n setIsCancelling(false)\n }\n }, [canCancel, pendingActionId, props, refresh])\n\n const { handleKeyDown } = useAiShortcuts({\n onCancel: () => {\n void handleCancel()\n },\n enabled: canCancel,\n })\n\n // Terminal states \u2014 hand off to the result card renderer. Also short-\n // circuit when the dispatcher has already populated an\n // `executionResult.error`: the row may not have transitioned out of\n // `executing` in the latest polling snapshot, but the handler error is\n // authoritative, and leaving the spinner up while a known error is\n // available is exactly the \"stalled at processing\" symptom the user\n // reported. Surface the error card immediately.\n const executionError = effectiveAction?.executionResult?.error\n if (\n effectiveStatus === 'confirmed' ||\n effectiveStatus === 'failed' ||\n effectiveStatus === 'cancelled' ||\n effectiveStatus === 'expired' ||\n executionError\n ) {\n return (\n <MutationResultCard\n componentId=\"mutation-result-card\"\n pendingActionId={pendingActionId}\n initialAction={effectiveAction ?? undefined}\n endpoint={props.endpoint}\n />\n )\n }\n\n const summary =\n effectiveAction?.sideEffectsSummary ??\n payload.sideEffectsSummary ??\n t(\n 'ai_assistant.chat.mutation_cards.confirmation.defaultSummary',\n 'Applying the requested changes...',\n )\n\n return (\n <section\n className=\"rounded-md border border-border bg-muted/30 p-4 text-sm outline-none\"\n tabIndex={0}\n onKeyDown={handleKeyDown}\n data-ai-confirmation-card\n data-ai-confirmation-status={effectiveStatus ?? 'pending'}\n aria-busy\n >\n <div className=\"flex items-start gap-3\">\n <Spinner size=\"sm\" className=\"mt-0.5\" />\n <div className=\"flex-1\">\n <h4 className=\"text-sm font-semibold\">\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.title',\n 'Applying action...',\n )}\n </h4>\n <p className=\"mt-1 text-sm text-muted-foreground\">{summary}</p>\n </div>\n </div>\n\n {localError ? (\n <ConfirmationErrorAlert error={localError} />\n ) : null}\n\n <div className=\"mt-3 flex items-center justify-end gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n void handleCancel()\n }}\n disabled={!canCancel}\n data-ai-confirmation-cancel\n >\n {isCancelling ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n ) : null}\n <span>\n {t('ai_assistant.chat.mutation_cards.confirmation.cancel', 'Cancel')}\n </span>\n </Button>\n </div>\n </section>\n )\n}\n\nfunction ConfirmationErrorAlert({\n error,\n}: {\n error: { code?: string; message: string; extra?: Record<string, unknown> }\n}) {\n const t = useT()\n const code = error.code ?? 'unknown'\n\n if (code === 'stale_version') {\n const failedRecords = Array.isArray(error.extra?.failedRecords)\n ? (error.extra?.failedRecords as Array<{ recordId?: string }>)\n : []\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"stale_version\"\n >\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.staleVersionTitle',\n 'Re-propose required',\n )}\n </AlertTitle>\n <AlertDescription>\n <p>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.staleVersionBody',\n 'One or more records changed since this preview was generated. Ask the assistant to re-propose the change.',\n )}\n </p>\n {failedRecords.length > 0 ? (\n <ul className=\"mt-2 list-disc pl-5 text-xs\">\n {failedRecords.map((record, idx) => (\n <li\n key={`${record.recordId ?? idx}`}\n data-ai-confirmation-stale-record={record.recordId ?? ''}\n >\n <span className=\"font-mono\">{record.recordId ?? '\u2014'}</span>\n </li>\n ))}\n </ul>\n ) : null}\n </AlertDescription>\n </Alert>\n )\n }\n\n if (code === 'schema_drift') {\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"schema_drift\"\n >\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.schemaDriftTitle',\n 'Schema changed',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.schemaDriftBody',\n 'The tool signature changed since this preview was generated. Ask the assistant to re-propose the change.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n if (code === 'invalid_status') {\n return (\n <Alert\n variant=\"warning\"\n className=\"mt-3\"\n data-ai-confirmation-error=\"invalid_status\"\n >\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.invalidStatusTitle',\n 'Action already resolved',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.confirmation.invalidStatusBody',\n 'This action has already been confirmed, cancelled, or executed.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <Alert variant=\"destructive\" className=\"mt-3\" data-ai-confirmation-error={code}>\n <AlertTriangle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t('ai_assistant.chat.mutation_cards.confirmation.errorTitle', 'Confirm failed')}\n </AlertTitle>\n <AlertDescription>\n <span className=\"mr-2 font-mono text-xs\">{code}</span>\n <span>{error.message}</span>\n </AlertDescription>\n </Alert>\n )\n}\n\nexport default ConfirmationCard\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Info } from "lucide-react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
+ import { Alert, AlertDescription } from "../../primitives/alert.js";
6
+ function formatValue(value) {
7
+ if (value == null) return "";
8
+ if (typeof value === "string") return value;
9
+ try {
10
+ return JSON.stringify(value);
11
+ } catch {
12
+ return String(value);
13
+ }
14
+ }
15
+ function DiffRow({ entry }) {
16
+ const before = formatValue(entry.before);
17
+ const after = formatValue(entry.after);
18
+ return /* @__PURE__ */ jsxs("tr", { className: "border-b border-border last:border-b-0", "data-ai-field-diff-row": true, children: [
19
+ /* @__PURE__ */ jsx("td", { className: "py-1.5 pr-4 text-xs font-mono text-muted-foreground align-top", children: entry.field }),
20
+ /* @__PURE__ */ jsx(
21
+ "td",
22
+ {
23
+ className: "py-1.5 pr-4 text-sm align-top text-status-warning-text",
24
+ "data-ai-field-diff-before": true,
25
+ children: /* @__PURE__ */ jsx("span", { className: "line-through break-all", children: before || "\u2014" })
26
+ }
27
+ ),
28
+ /* @__PURE__ */ jsx(
29
+ "td",
30
+ {
31
+ className: "py-1.5 text-sm align-top text-status-success-text",
32
+ "data-ai-field-diff-after": true,
33
+ children: /* @__PURE__ */ jsx("span", { className: "font-medium break-all", children: after || "\u2014" })
34
+ }
35
+ )
36
+ ] });
37
+ }
38
+ function DiffTable({ rows, fieldHeader, beforeHeader, afterHeader }) {
39
+ return /* @__PURE__ */ jsxs("table", { className: "w-full", "data-ai-field-diff-table": true, children: [
40
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border", children: [
41
+ /* @__PURE__ */ jsx("th", { className: "py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: fieldHeader }),
42
+ /* @__PURE__ */ jsx("th", { className: "py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: beforeHeader }),
43
+ /* @__PURE__ */ jsx("th", { className: "py-1 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: afterHeader })
44
+ ] }) }),
45
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((entry, idx) => /* @__PURE__ */ jsx(DiffRow, { entry }, `${entry.field}-${idx}`)) })
46
+ ] });
47
+ }
48
+ function FieldDiffCard({ fieldDiff, records }) {
49
+ const t = useT();
50
+ const fieldHeader = t(
51
+ "ai_assistant.chat.mutation_cards.diff.fieldHeader",
52
+ "Field"
53
+ );
54
+ const beforeHeader = t(
55
+ "ai_assistant.chat.mutation_cards.diff.beforeHeader",
56
+ "Before"
57
+ );
58
+ const afterHeader = t(
59
+ "ai_assistant.chat.mutation_cards.diff.afterHeader",
60
+ "After"
61
+ );
62
+ const batch = Array.isArray(records) && records.length > 0 ? records : null;
63
+ const flat = Array.isArray(fieldDiff) ? fieldDiff : [];
64
+ if (batch) {
65
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", "data-ai-field-diff-mode": "batch", children: batch.map((record) => /* @__PURE__ */ jsxs(
66
+ "section",
67
+ {
68
+ className: "rounded-md border border-border bg-background p-3",
69
+ "data-ai-field-diff-record": record.recordId,
70
+ children: [
71
+ /* @__PURE__ */ jsxs("header", { className: "mb-2 flex items-baseline justify-between gap-2", children: [
72
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: record.label }),
73
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-muted-foreground", children: record.entityType })
74
+ ] }),
75
+ record.fieldDiff.length > 0 ? /* @__PURE__ */ jsx(
76
+ DiffTable,
77
+ {
78
+ rows: record.fieldDiff,
79
+ fieldHeader,
80
+ beforeHeader,
81
+ afterHeader
82
+ }
83
+ ) : /* @__PURE__ */ jsxs(Alert, { variant: "info", children: [
84
+ /* @__PURE__ */ jsx(Info, { className: "size-4", "aria-hidden": true }),
85
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
86
+ "ai_assistant.chat.mutation_cards.diff.empty",
87
+ "No field changes for this record."
88
+ ) })
89
+ ] })
90
+ ]
91
+ },
92
+ record.recordId
93
+ )) });
94
+ }
95
+ if (flat.length === 0) {
96
+ return /* @__PURE__ */ jsxs(Alert, { variant: "info", "data-ai-field-diff-mode": "empty", children: [
97
+ /* @__PURE__ */ jsx(Info, { className: "size-4", "aria-hidden": true }),
98
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
99
+ "ai_assistant.chat.mutation_cards.diff.empty",
100
+ "No field changes for this record."
101
+ ) })
102
+ ] });
103
+ }
104
+ return /* @__PURE__ */ jsx("div", { "data-ai-field-diff-mode": "flat", children: /* @__PURE__ */ jsx(
105
+ DiffTable,
106
+ {
107
+ rows: flat,
108
+ fieldHeader,
109
+ beforeHeader,
110
+ afterHeader
111
+ }
112
+ ) });
113
+ }
114
+ var FieldDiffCard_default = FieldDiffCard;
115
+ export {
116
+ FieldDiffCard,
117
+ FieldDiffCard_default as default
118
+ };
119
+ //# sourceMappingURL=FieldDiffCard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ai/parts/FieldDiffCard.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Info } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription } from '../../primitives/alert'\nimport type {\n AiPendingActionCardFieldDiff,\n AiPendingActionCardRecordDiff,\n} from './types'\n\n/**\n * Presentational card rendering a `fieldDiff` list in compact three-column\n * form (field | before | after) with DS-compliant semantic-token colors.\n * Accepts either a flat `fieldDiff[]` (single-record preview) or grouped\n * `records[]` (batch preview) \u2014 when both are supplied, `records` wins,\n * matching the server-side `AiPendingAction` contract (spec \u00A78 rule 2).\n */\nexport interface FieldDiffCardProps {\n fieldDiff?: AiPendingActionCardFieldDiff[] | null\n records?: AiPendingActionCardRecordDiff[] | null\n /** Optional forwarded componentId for the registry renderer. */\n componentId?: string\n}\n\nfunction formatValue(value: unknown): string {\n if (value == null) return ''\n if (typeof value === 'string') return value\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\nfunction DiffRow({ entry }: { entry: AiPendingActionCardFieldDiff }) {\n const before = formatValue(entry.before)\n const after = formatValue(entry.after)\n return (\n <tr className=\"border-b border-border last:border-b-0\" data-ai-field-diff-row>\n <td className=\"py-1.5 pr-4 text-xs font-mono text-muted-foreground align-top\">\n {entry.field}\n </td>\n <td\n className=\"py-1.5 pr-4 text-sm align-top text-status-warning-text\"\n data-ai-field-diff-before\n >\n <span className=\"line-through break-all\">{before || '\u2014'}</span>\n </td>\n <td\n className=\"py-1.5 text-sm align-top text-status-success-text\"\n data-ai-field-diff-after\n >\n <span className=\"font-medium break-all\">{after || '\u2014'}</span>\n </td>\n </tr>\n )\n}\n\nfunction DiffTable({ rows, fieldHeader, beforeHeader, afterHeader }: {\n rows: AiPendingActionCardFieldDiff[]\n fieldHeader: string\n beforeHeader: string\n afterHeader: string\n}) {\n return (\n <table className=\"w-full\" data-ai-field-diff-table>\n <thead>\n <tr className=\"border-b border-border\">\n <th className=\"py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {fieldHeader}\n </th>\n <th className=\"py-1 pr-4 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {beforeHeader}\n </th>\n <th className=\"py-1 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {afterHeader}\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.map((entry, idx) => (\n <DiffRow key={`${entry.field}-${idx}`} entry={entry} />\n ))}\n </tbody>\n </table>\n )\n}\n\nexport function FieldDiffCard({ fieldDiff, records }: FieldDiffCardProps) {\n const t = useT()\n const fieldHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.fieldHeader',\n 'Field',\n )\n const beforeHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.beforeHeader',\n 'Before',\n )\n const afterHeader = t(\n 'ai_assistant.chat.mutation_cards.diff.afterHeader',\n 'After',\n )\n\n const batch = Array.isArray(records) && records.length > 0 ? records : null\n const flat = Array.isArray(fieldDiff) ? fieldDiff : []\n\n if (batch) {\n return (\n <div className=\"flex flex-col gap-3\" data-ai-field-diff-mode=\"batch\">\n {batch.map((record) => (\n <section\n key={record.recordId}\n className=\"rounded-md border border-border bg-background p-3\"\n data-ai-field-diff-record={record.recordId}\n >\n <header className=\"mb-2 flex items-baseline justify-between gap-2\">\n <h4 className=\"text-sm font-semibold\">{record.label}</h4>\n <span className=\"text-xs font-mono text-muted-foreground\">\n {record.entityType}\n </span>\n </header>\n {record.fieldDiff.length > 0 ? (\n <DiffTable\n rows={record.fieldDiff}\n fieldHeader={fieldHeader}\n beforeHeader={beforeHeader}\n afterHeader={afterHeader}\n />\n ) : (\n <Alert variant=\"info\">\n <Info className=\"size-4\" aria-hidden />\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.diff.empty',\n 'No field changes for this record.',\n )}\n </AlertDescription>\n </Alert>\n )}\n </section>\n ))}\n </div>\n )\n }\n\n if (flat.length === 0) {\n return (\n <Alert variant=\"info\" data-ai-field-diff-mode=\"empty\">\n <Info className=\"size-4\" aria-hidden />\n <AlertDescription>\n {t(\n 'ai_assistant.chat.mutation_cards.diff.empty',\n 'No field changes for this record.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div data-ai-field-diff-mode=\"flat\">\n <DiffTable\n rows={flat}\n fieldHeader={fieldHeader}\n beforeHeader={beforeHeader}\n afterHeader={afterHeader}\n />\n </div>\n )\n}\n\nexport default FieldDiffCard\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,224 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { ChevronDown, Eye, ShieldAlert } from "lucide-react";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { Button } from "../../primitives/button.js";
7
+ import { useAiShortcuts } from "../useAiShortcuts.js";
8
+ import { confirmPendingAction, cancelPendingAction } from "./pending-action-api.js";
9
+ import { useAiPendingActionPolling } from "./useAiPendingActionPolling.js";
10
+ import { FieldDiffCard } from "./FieldDiffCard.js";
11
+ import { ConfirmationCard } from "./ConfirmationCard.js";
12
+ import { MutationResultCard } from "./MutationResultCard.js";
13
+ function summarizeBatch(records) {
14
+ const labels = records.slice(0, 3).map((record) => record.label);
15
+ return { count: records.length, labels };
16
+ }
17
+ function MutationPreviewCard(props) {
18
+ const t = useT();
19
+ const pendingActionId = props.pendingActionId ?? "";
20
+ const payload = props.payload ?? {};
21
+ const injected = props.initialAction ?? payload.pendingAction ?? null;
22
+ const { action: polled, refresh } = useAiPendingActionPolling({
23
+ pendingActionId,
24
+ endpoint: props.endpoint,
25
+ disabled: !pendingActionId
26
+ });
27
+ const action = polled ?? injected;
28
+ const [expanded, setExpanded] = React.useState(false);
29
+ const [phase, setPhase] = React.useState("preview");
30
+ const [confirmError, setConfirmError] = React.useState(null);
31
+ const handleConfirm = React.useCallback(async () => {
32
+ if (!pendingActionId) return;
33
+ if (phase !== "preview") return;
34
+ setPhase("confirming");
35
+ setConfirmError(null);
36
+ const result = await confirmPendingAction(pendingActionId, {
37
+ endpoint: props.endpoint
38
+ });
39
+ if (!result.ok) {
40
+ setConfirmError(result.error);
41
+ setPhase("preview");
42
+ await refresh();
43
+ return;
44
+ }
45
+ const handlerError = result.data?.mutationResult?.error;
46
+ if (result.data?.ok === false || handlerError) {
47
+ const mappedCode = typeof handlerError?.code === "string" && handlerError.code.length > 0 ? handlerError.code : "execution_failed";
48
+ setConfirmError({
49
+ status: 200,
50
+ code: mappedCode,
51
+ message: handlerError?.message ?? t(
52
+ "ai_assistant.chat.mutation_cards.preview.handlerError",
53
+ "The mutation handler reported an error. Review the details and re-propose if needed."
54
+ )
55
+ });
56
+ }
57
+ await refresh();
58
+ }, [pendingActionId, phase, props.endpoint, refresh, t]);
59
+ const handleCancel = React.useCallback(async () => {
60
+ if (!pendingActionId) return;
61
+ const result = await cancelPendingAction(pendingActionId, {
62
+ endpoint: props.endpoint
63
+ });
64
+ if (!result.ok) {
65
+ setConfirmError(result.error);
66
+ }
67
+ await refresh();
68
+ }, [pendingActionId, props.endpoint, refresh]);
69
+ const currentStatus = action?.status ?? null;
70
+ const executionError = action?.executionResult?.error;
71
+ const isTerminal = currentStatus === "confirmed" || currentStatus === "failed" || currentStatus === "cancelled" || currentStatus === "expired" || Boolean(executionError);
72
+ const { handleKeyDown } = useAiShortcuts({
73
+ onSubmit: () => {
74
+ void handleConfirm();
75
+ },
76
+ onCancel: () => {
77
+ void handleCancel();
78
+ },
79
+ enabled: phase === "preview" && !isTerminal
80
+ });
81
+ if (isTerminal && action) {
82
+ return /* @__PURE__ */ jsx(
83
+ MutationResultCard,
84
+ {
85
+ componentId: "mutation-result-card",
86
+ pendingActionId,
87
+ initialAction: action,
88
+ endpoint: props.endpoint
89
+ }
90
+ );
91
+ }
92
+ if (phase === "confirming") {
93
+ return /* @__PURE__ */ jsx(
94
+ ConfirmationCard,
95
+ {
96
+ componentId: "confirmation-card",
97
+ pendingActionId,
98
+ initialAction: action ?? void 0,
99
+ endpoint: props.endpoint,
100
+ payload: {
101
+ sideEffectsSummary: action?.sideEffectsSummary ?? null,
102
+ pendingAction: action ?? void 0,
103
+ confirmError: confirmError ?? void 0
104
+ }
105
+ }
106
+ );
107
+ }
108
+ const batch = Array.isArray(action?.records) && action.records.length > 0 ? action.records : null;
109
+ const summary = batch ? summarizeBatch(batch) : null;
110
+ return /* @__PURE__ */ jsxs(
111
+ "section",
112
+ {
113
+ className: "rounded-md border border-border bg-background p-4 text-sm outline-none",
114
+ tabIndex: 0,
115
+ onKeyDown: handleKeyDown,
116
+ "data-ai-mutation-preview": true,
117
+ "data-ai-mutation-preview-mode": batch ? "batch" : "single",
118
+ children: [
119
+ /* @__PURE__ */ jsx("header", { className: "flex items-start justify-between gap-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
120
+ /* @__PURE__ */ jsx(ShieldAlert, { className: "mt-0.5 size-4 text-status-warning-icon", "aria-hidden": true }),
121
+ /* @__PURE__ */ jsxs("div", { children: [
122
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: t(
123
+ "ai_assistant.chat.mutation_cards.preview.title",
124
+ "Review proposed changes"
125
+ ) }),
126
+ action?.sideEffectsSummary ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: action.sideEffectsSummary }) : null
127
+ ] })
128
+ ] }) }),
129
+ /* @__PURE__ */ jsx("div", { className: "mt-3", "data-ai-mutation-preview-body": true, children: summary ? /* @__PURE__ */ jsxs(
130
+ "div",
131
+ {
132
+ className: "rounded-md border border-border bg-muted/30 p-3 text-sm",
133
+ "data-ai-mutation-preview-batch-summary": true,
134
+ children: [
135
+ /* @__PURE__ */ jsxs("p", { className: "font-medium", children: [
136
+ t(
137
+ "ai_assistant.chat.mutation_cards.preview.batchSummary",
138
+ "Batch update"
139
+ ),
140
+ ": ",
141
+ /* @__PURE__ */ jsx("span", { "data-ai-mutation-preview-count": true, children: summary.count }),
142
+ " ",
143
+ /* @__PURE__ */ jsx("span", { children: t(
144
+ "ai_assistant.chat.mutation_cards.preview.batchRecords",
145
+ "records"
146
+ ) })
147
+ ] }),
148
+ summary.labels.length > 0 ? /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [
149
+ summary.labels.join(", "),
150
+ summary.count > summary.labels.length ? ` +${summary.count - summary.labels.length}` : ""
151
+ ] }) : null
152
+ ]
153
+ }
154
+ ) : /* @__PURE__ */ jsx(FieldDiffCard, { fieldDiff: action?.fieldDiff ?? null }) }),
155
+ expanded ? /* @__PURE__ */ jsx("div", { className: "mt-3 rounded-md border border-border bg-muted/20 p-3", "data-ai-mutation-preview-details": true, children: /* @__PURE__ */ jsx(
156
+ FieldDiffCard,
157
+ {
158
+ fieldDiff: action?.fieldDiff ?? null,
159
+ records: action?.records ?? null
160
+ }
161
+ ) }) : null,
162
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between gap-2", children: [
163
+ /* @__PURE__ */ jsxs(
164
+ Button,
165
+ {
166
+ type: "button",
167
+ variant: "ghost",
168
+ size: "sm",
169
+ onClick: () => setExpanded((value) => !value),
170
+ "data-ai-mutation-preview-review": true,
171
+ children: [
172
+ /* @__PURE__ */ jsx(Eye, { className: "size-4", "aria-hidden": true }),
173
+ /* @__PURE__ */ jsx("span", { children: t(
174
+ "ai_assistant.chat.mutation_cards.preview.reviewDetails",
175
+ "Review details"
176
+ ) }),
177
+ /* @__PURE__ */ jsx(
178
+ ChevronDown,
179
+ {
180
+ className: `size-4 transition-transform ${expanded ? "rotate-180" : ""}`,
181
+ "aria-hidden": true
182
+ }
183
+ )
184
+ ]
185
+ }
186
+ ),
187
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
188
+ /* @__PURE__ */ jsx(
189
+ Button,
190
+ {
191
+ type: "button",
192
+ variant: "outline",
193
+ size: "sm",
194
+ onClick: () => {
195
+ void handleCancel();
196
+ },
197
+ "data-ai-mutation-preview-cancel": true,
198
+ children: t("ai_assistant.chat.mutation_cards.preview.cancel", "Cancel")
199
+ }
200
+ ),
201
+ /* @__PURE__ */ jsx(
202
+ Button,
203
+ {
204
+ type: "button",
205
+ size: "sm",
206
+ onClick: () => {
207
+ void handleConfirm();
208
+ },
209
+ "data-ai-mutation-preview-confirm": true,
210
+ children: t("ai_assistant.chat.mutation_cards.preview.confirm", "Confirm")
211
+ }
212
+ )
213
+ ] })
214
+ ] })
215
+ ]
216
+ }
217
+ );
218
+ }
219
+ var MutationPreviewCard_default = MutationPreviewCard;
220
+ export {
221
+ MutationPreviewCard,
222
+ MutationPreviewCard_default as default
223
+ };
224
+ //# sourceMappingURL=MutationPreviewCard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/ai/parts/MutationPreviewCard.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ChevronDown, Eye, ShieldAlert } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { useAiShortcuts } from '../useAiShortcuts'\nimport type { AiUiPartProps } from '../ui-part-registry'\nimport { confirmPendingAction, cancelPendingAction } from './pending-action-api'\nimport { useAiPendingActionPolling } from './useAiPendingActionPolling'\nimport { FieldDiffCard } from './FieldDiffCard'\nimport { ConfirmationCard } from './ConfirmationCard'\nimport { MutationResultCard } from './MutationResultCard'\nimport type { AiPendingActionCardAction } from './types'\n\n/**\n * Mutation-approval preview card. Rendered by the server-emitted\n * `mutation-preview-card` UI part (spec \u00A79.2).\n *\n * Responsibilities:\n * - Fetch the current pending-action row via the shared polling hook so the\n * card recovers state on page reload (reconnect behavior).\n * - Render the top-level `fieldDiff` OR a per-record `records[]` summary\n * with a drill-in link.\n * - Provide `Confirm` / `Cancel` / `Review Details` actions with shared\n * keyboard shortcuts (`Cmd/Ctrl+Enter` \u2192 confirm, `Escape` \u2192 cancel).\n * - Flip to the {@link ConfirmationCard} once the user confirms, and\n * further to the {@link MutationResultCard} once the row becomes\n * terminal.\n */\nexport interface MutationPreviewCardPayload {\n /** Optional server-serialized pending action snapshot for the initial render. */\n pendingAction?: AiPendingActionCardAction\n}\n\nexport interface MutationPreviewCardProps extends AiUiPartProps {\n /** Optional injected action for tests \u2014 bypasses the polling fetch. */\n initialAction?: AiPendingActionCardAction\n /** Endpoint base override for tests. */\n endpoint?: string\n}\n\nfunction summarizeBatch(\n records: NonNullable<AiPendingActionCardAction['records']>,\n): { count: number; labels: string[] } {\n const labels = records.slice(0, 3).map((record) => record.label)\n return { count: records.length, labels }\n}\n\nexport function MutationPreviewCard(props: MutationPreviewCardProps) {\n const t = useT()\n const pendingActionId = props.pendingActionId ?? ''\n const payload = (props.payload as MutationPreviewCardPayload | undefined) ?? {}\n const injected = props.initialAction ?? payload.pendingAction ?? null\n\n const { action: polled, refresh } = useAiPendingActionPolling({\n pendingActionId,\n endpoint: props.endpoint,\n disabled: !pendingActionId,\n })\n const action = polled ?? injected\n\n const [expanded, setExpanded] = React.useState(false)\n const [phase, setPhase] = React.useState<'preview' | 'confirming'>('preview')\n const [confirmError, setConfirmError] = React.useState<{\n status: number\n code?: string\n message: string\n extra?: Record<string, unknown>\n } | null>(null)\n\n const handleConfirm = React.useCallback(async () => {\n if (!pendingActionId) return\n if (phase !== 'preview') return\n setPhase('confirming')\n setConfirmError(null)\n const result = await confirmPendingAction(pendingActionId, {\n endpoint: props.endpoint,\n })\n if (!result.ok) {\n // Network / timeout / 4xx / 5xx \u2014 surface the envelope and rewind the\n // card to the preview phase so the operator can read the error,\n // edit the proposal upstream, or retry.\n setConfirmError(result.error)\n setPhase('preview')\n await refresh()\n return\n }\n // HTTP 200 path. The dispatcher returns `ok: false` AND a populated\n // `mutationResult.error` when the wrapped tool handler failed inside\n // the confirm route \u2014 the row is already in a terminal state but the\n // overall HTTP call succeeded. Treat that as a confirm error too so\n // the alert renders inline instead of leaving the card on the\n // generic \"applying\u2026\" spinner forever.\n const handlerError = result.data?.mutationResult?.error\n if (result.data?.ok === false || handlerError) {\n const mappedCode =\n typeof handlerError?.code === 'string' && handlerError.code.length > 0\n ? handlerError.code\n : 'execution_failed'\n setConfirmError({\n status: 200,\n code: mappedCode,\n message:\n handlerError?.message ??\n t(\n 'ai_assistant.chat.mutation_cards.preview.handlerError',\n 'The mutation handler reported an error. Review the details and re-propose if needed.',\n ),\n })\n }\n await refresh()\n }, [pendingActionId, phase, props.endpoint, refresh, t])\n\n const handleCancel = React.useCallback(async () => {\n if (!pendingActionId) return\n const result = await cancelPendingAction(pendingActionId, {\n endpoint: props.endpoint,\n })\n if (!result.ok) {\n setConfirmError(result.error)\n }\n await refresh()\n }, [pendingActionId, props.endpoint, refresh])\n\n const currentStatus = action?.status ?? null\n const executionError = action?.executionResult?.error\n // Treat any captured handler error as terminal too \u2014 the dispatcher\n // sometimes returns the action before the row has fully transitioned\n // out of `executing`, and we must never leave the spinner masking a\n // real failure. The MutationResultCard's failure path renders the\n // error envelope as long as `executionResult.error` is set.\n const isTerminal =\n currentStatus === 'confirmed' ||\n currentStatus === 'failed' ||\n currentStatus === 'cancelled' ||\n currentStatus === 'expired' ||\n Boolean(executionError)\n\n const { handleKeyDown } = useAiShortcuts({\n onSubmit: () => {\n void handleConfirm()\n },\n onCancel: () => {\n void handleCancel()\n },\n enabled: phase === 'preview' && !isTerminal,\n })\n\n // Terminal \u2014 short-circuit into the result card.\n if (isTerminal && action) {\n return (\n <MutationResultCard\n componentId=\"mutation-result-card\"\n pendingActionId={pendingActionId}\n initialAction={action}\n endpoint={props.endpoint}\n />\n )\n }\n\n // Confirming \u2014 flip to the spinner card. Propagate the confirmError so\n // the user sees the structured envelope even though the confirm call has\n // already resolved.\n if (phase === 'confirming') {\n return (\n <ConfirmationCard\n componentId=\"confirmation-card\"\n pendingActionId={pendingActionId}\n initialAction={action ?? undefined}\n endpoint={props.endpoint}\n payload={{\n sideEffectsSummary: action?.sideEffectsSummary ?? null,\n pendingAction: action ?? undefined,\n confirmError: confirmError ?? undefined,\n }}\n />\n )\n }\n\n const batch = Array.isArray(action?.records) && action!.records!.length > 0 ? action!.records! : null\n const summary = batch ? summarizeBatch(batch) : null\n\n return (\n <section\n className=\"rounded-md border border-border bg-background p-4 text-sm outline-none\"\n tabIndex={0}\n onKeyDown={handleKeyDown}\n data-ai-mutation-preview\n data-ai-mutation-preview-mode={batch ? 'batch' : 'single'}\n >\n <header className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-2\">\n <ShieldAlert className=\"mt-0.5 size-4 text-status-warning-icon\" aria-hidden />\n <div>\n <h4 className=\"text-sm font-semibold\">\n {t(\n 'ai_assistant.chat.mutation_cards.preview.title',\n 'Review proposed changes',\n )}\n </h4>\n {action?.sideEffectsSummary ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {action.sideEffectsSummary}\n </p>\n ) : null}\n </div>\n </div>\n </header>\n\n <div className=\"mt-3\" data-ai-mutation-preview-body>\n {summary ? (\n <div\n className=\"rounded-md border border-border bg-muted/30 p-3 text-sm\"\n data-ai-mutation-preview-batch-summary\n >\n <p className=\"font-medium\">\n {t(\n 'ai_assistant.chat.mutation_cards.preview.batchSummary',\n 'Batch update',\n )}\n {': '}\n <span data-ai-mutation-preview-count>{summary.count}</span>{' '}\n <span>\n {t(\n 'ai_assistant.chat.mutation_cards.preview.batchRecords',\n 'records',\n )}\n </span>\n </p>\n {summary.labels.length > 0 ? (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {summary.labels.join(', ')}\n {summary.count > summary.labels.length\n ? ` +${summary.count - summary.labels.length}`\n : ''}\n </p>\n ) : null}\n </div>\n ) : (\n <FieldDiffCard fieldDiff={action?.fieldDiff ?? null} />\n )}\n </div>\n\n {expanded ? (\n <div className=\"mt-3 rounded-md border border-border bg-muted/20 p-3\" data-ai-mutation-preview-details>\n <FieldDiffCard\n fieldDiff={action?.fieldDiff ?? null}\n records={action?.records ?? null}\n />\n </div>\n ) : null}\n\n <div className=\"mt-3 flex items-center justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setExpanded((value) => !value)}\n data-ai-mutation-preview-review\n >\n <Eye className=\"size-4\" aria-hidden />\n <span>\n {t(\n 'ai_assistant.chat.mutation_cards.preview.reviewDetails',\n 'Review details',\n )}\n </span>\n <ChevronDown\n className={`size-4 transition-transform ${expanded ? 'rotate-180' : ''}`}\n aria-hidden\n />\n </Button>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n void handleCancel()\n }}\n data-ai-mutation-preview-cancel\n >\n {t('ai_assistant.chat.mutation_cards.preview.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => {\n void handleConfirm()\n }}\n data-ai-mutation-preview-confirm\n >\n {t('ai_assistant.chat.mutation_cards.preview.confirm', 'Confirm')}\n </Button>\n </div>\n </div>\n </section>\n )\n}\n\nexport default MutationPreviewCard\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }