@open-mercato/ui 0.9.1-develop.7574.1.837288eec1 → 0.10.1-develop.7576.1.21d5b50435

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 (93) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/ai/parts/MutationResultCard.js +1 -14
  3. package/dist/ai/parts/MutationResultCard.js.map +2 -2
  4. package/dist/backend/CrudForm.js +52 -96
  5. package/dist/backend/CrudForm.js.map +2 -2
  6. package/dist/backend/DataTable.js +14 -40
  7. package/dist/backend/DataTable.js.map +2 -2
  8. package/dist/backend/FlashMessages.js +1 -25
  9. package/dist/backend/FlashMessages.js.map +2 -2
  10. package/dist/backend/columns/ColumnChooserPanel.js +3 -4
  11. package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
  12. package/dist/backend/confirm-dialog/ConfirmDialog.js +0 -11
  13. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
  14. package/dist/backend/detail/InlineEditors.js +6 -17
  15. package/dist/backend/detail/InlineEditors.js.map +2 -2
  16. package/dist/backend/filters/FilterFieldPicker.js +2 -3
  17. package/dist/backend/filters/FilterFieldPicker.js.map +2 -2
  18. package/dist/backend/forms/ActionsDropdown.js +2 -52
  19. package/dist/backend/forms/ActionsDropdown.js.map +2 -2
  20. package/dist/backend/inputs/PhoneNumberField.js +5 -17
  21. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  22. package/dist/backend/inputs/TagsInput.js +1 -4
  23. package/dist/backend/inputs/TagsInput.js.map +2 -2
  24. package/dist/backend/inputs/index.js +0 -2
  25. package/dist/backend/inputs/index.js.map +2 -2
  26. package/dist/backend/messages/useMessageCompose.js +10 -29
  27. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  28. package/dist/backend/notifications/NotificationDispatcher.js +0 -5
  29. package/dist/backend/notifications/NotificationDispatcher.js.map +2 -2
  30. package/dist/backend/notifications/useNotificationActions.js +2 -13
  31. package/dist/backend/notifications/useNotificationActions.js.map +2 -2
  32. package/dist/backend/utils/api.js +4 -25
  33. package/dist/backend/utils/api.js.map +2 -2
  34. package/dist/backend/version-history/VersionHistoryPanel.js +2 -18
  35. package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
  36. package/dist/theme/QueryProvider.js +1 -7
  37. package/dist/theme/QueryProvider.js.map +2 -2
  38. package/package.json +4 -4
  39. package/src/ai/parts/MutationResultCard.tsx +1 -30
  40. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +0 -48
  41. package/src/backend/CrudForm.tsx +60 -121
  42. package/src/backend/DataTable.tsx +14 -54
  43. package/src/backend/FlashMessages.tsx +1 -34
  44. package/src/backend/__tests__/ActionsDropdown.test.tsx +0 -105
  45. package/src/backend/__tests__/CrudForm.fieldInjection.test.tsx +4 -85
  46. package/src/backend/__tests__/FlashMessages.test.tsx +0 -65
  47. package/src/backend/__tests__/VersionHistoryPanel.test.tsx +1 -81
  48. package/src/backend/columns/ColumnChooserPanel.tsx +3 -4
  49. package/src/backend/confirm-dialog/ConfirmDialog.tsx +0 -16
  50. package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +0 -29
  51. package/src/backend/detail/InlineEditors.tsx +6 -28
  52. package/src/backend/detail/__tests__/InlineEditors.test.tsx +6 -121
  53. package/src/backend/filters/FilterFieldPicker.tsx +2 -3
  54. package/src/backend/forms/ActionsDropdown.tsx +2 -71
  55. package/src/backend/inputs/PhoneNumberField.tsx +11 -17
  56. package/src/backend/inputs/TagsInput.tsx +0 -4
  57. package/src/backend/inputs/__tests__/TagsInput.test.tsx +0 -41
  58. package/src/backend/inputs/index.ts +0 -2
  59. package/src/backend/messages/__tests__/useMessageCompose.test.tsx +1 -106
  60. package/src/backend/messages/message-composer.types.ts +0 -2
  61. package/src/backend/messages/useMessageCompose.ts +13 -34
  62. package/src/backend/notifications/NotificationDispatcher.ts +0 -5
  63. package/src/backend/notifications/__tests__/useNotificationActions.test.tsx +1 -86
  64. package/src/backend/notifications/useNotificationActions.ts +4 -19
  65. package/src/backend/utils/__tests__/api.test.ts +0 -67
  66. package/src/backend/utils/api.ts +4 -33
  67. package/src/backend/version-history/VersionHistoryPanel.tsx +2 -24
  68. package/src/theme/QueryProvider.tsx +1 -7
  69. package/dist/backend/crud/injectedFieldGroups.js +0 -50
  70. package/dist/backend/crud/injectedFieldGroups.js.map +0 -7
  71. package/dist/backend/inputs/LookupPickers.js +0 -149
  72. package/dist/backend/inputs/LookupPickers.js.map +0 -7
  73. package/dist/backend/inputs/lookupSources.js +0 -84
  74. package/dist/backend/inputs/lookupSources.js.map +0 -7
  75. package/dist/backend/utils/groupLabels.js +0 -16
  76. package/dist/backend/utils/groupLabels.js.map +0 -7
  77. package/src/backend/__tests__/CrudForm.disabledFields.test.tsx +0 -57
  78. package/src/backend/__tests__/CrudForm.embeddedExtraActions.test.tsx +0 -114
  79. package/src/backend/__tests__/CrudForm.fieldChangeRenders.test.tsx +0 -127
  80. package/src/backend/__tests__/DataTable.injectedColumnPlacement.test.tsx +0 -146
  81. package/src/backend/__tests__/next-dynamic-specifier.test.ts +0 -23
  82. package/src/backend/columns/__tests__/ColumnChooserPanel.groupLabels.test.tsx +0 -72
  83. package/src/backend/crud/__tests__/injectedFieldGroups.test.ts +0 -90
  84. package/src/backend/crud/injectedFieldGroups.ts +0 -75
  85. package/src/backend/filters/__tests__/FilterFieldPicker.groupLabels.test.tsx +0 -77
  86. package/src/backend/inputs/LookupPickers.tsx +0 -181
  87. package/src/backend/inputs/__tests__/LookupPickers.test.tsx +0 -120
  88. package/src/backend/inputs/__tests__/PhoneNumberField.renders.test.tsx +0 -35
  89. package/src/backend/inputs/__tests__/lookupSources.test.ts +0 -87
  90. package/src/backend/inputs/lookupSources.ts +0 -114
  91. package/src/backend/notifications/__tests__/NotificationDispatcher.navigate.test.ts +0 -76
  92. package/src/backend/utils/groupLabels.ts +0 -26
  93. package/src/theme/__tests__/QueryProvider.i18n.test.tsx +0 -40
@@ -1,3 +1,3 @@
1
1
  Generated lucide registry with 182 icons -> /home/runner/work/open-mercato/open-mercato/packages/ui/src/backend/icons/lucideRegistry.generated.tsx
2
- [build:ui] found 456 entry points
2
+ [build:ui] found 452 entry points
3
3
  [build:ui] built successfully
@@ -14,10 +14,7 @@ function isSuccessStatus(status) {
14
14
  return status === "confirmed";
15
15
  }
16
16
  function isFailureStatus(status) {
17
- return status === "failed" || status === "expired";
18
- }
19
- function isCancelledStatus(status) {
20
- return status === "cancelled";
17
+ return status === "failed" || status === "cancelled" || status === "expired";
21
18
  }
22
19
  function MutationResultCard(props) {
23
20
  const t = useT();
@@ -89,16 +86,6 @@ function MutationResultCard(props) {
89
86
  ] })
90
87
  ] });
91
88
  }
92
- if (isCancelledStatus(status)) {
93
- const reason = result?.error?.message?.trim() || null;
94
- return /* @__PURE__ */ jsxs(Alert, { status: "information", "data-ai-mutation-result": "cancelled", children: [
95
- /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.chat.mutation_cards.result.cancelledTitle", "Proposal cancelled") }),
96
- /* @__PURE__ */ jsx("div", { className: "text-sm leading-relaxed", children: /* @__PURE__ */ jsx("p", { children: reason ?? t(
97
- "ai_assistant.chat.mutation_cards.result.cancelledBody",
98
- "You cancelled this proposed change. No changes were made."
99
- ) }) })
100
- ] });
101
- }
102
89
  if (isFailureStatus(status) || result?.error) {
103
90
  const code = result?.error?.code ?? status ?? "failed";
104
91
  const message = result?.error?.message ?? t(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/ai/parts/MutationResultCard.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Wand2 } 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 type { AiUiPartProps } from '../ui-part-registry'\nimport { useAiPendingActionPolling } from './useAiPendingActionPolling'\nimport type { AiPendingActionCardAction, AiPendingActionCardStatus } from './types'\n\n/** Custom DOM event the failure card dispatches when the operator clicks\n * \"Fix with AI\". `<AiChat>` listens for this and sends a follow-up user\n * message asking the agent to diagnose and retry the failed call. */\nexport const AI_CHAT_FIX_REQUEST_EVENT = 'om-ai-chat-fix-request'\n\nexport interface AiChatFixRequestDetail {\n message: string\n toolName?: string\n pendingActionId?: string\n}\n\nfunction dispatchFixRequest(detail: AiChatFixRequestDetail): void {\n if (typeof window === 'undefined') return\n window.dispatchEvent(new CustomEvent<AiChatFixRequestDetail>(AI_CHAT_FIX_REQUEST_EVENT, { detail }))\n}\n\n/**\n * Terminal-state card that renders the `executionResult` of a pending\n * action. Success \u2192 `Alert variant=\"success\"` with a record link; partial\n * success (batch `failedRecords[]`) \u2192 `variant=\"warning\"` with the list;\n * failure \u2192 `variant=\"destructive\"` with the error code + message.\n *\n * Reads the pending action via the shared polling hook so page reloads\n * still recover state. The hook short-circuits once the row is terminal\n * (spec's reconnect behavior), which is always the case for this card.\n */\nexport interface MutationResultCardPayload {\n /** Server-serialized pending action snapshot (optional \u2014 the hook refetches). */\n pendingAction?: AiPendingActionCardAction\n /** Optional link target for the success record. */\n recordHref?: string\n}\n\nexport interface MutationResultCardProps extends AiUiPartProps {\n /** Optional injected action for tests \u2014 bypasses the polling fetch. */\n initialAction?: AiPendingActionCardAction\n /** Poll endpoint override for tests. */\n endpoint?: string\n}\n\nfunction isSuccessStatus(status: AiPendingActionCardStatus | null): boolean {\n return status === 'confirmed'\n}\n\nfunction isFailureStatus(status: AiPendingActionCardStatus | null): boolean {\n return status === 'failed' || status === 'expired'\n}\n\nfunction isCancelledStatus(status: AiPendingActionCardStatus | null): boolean {\n return status === 'cancelled'\n}\n\nexport function MutationResultCard(props: MutationResultCardProps) {\n const t = useT()\n const pendingActionId = props.pendingActionId ?? ''\n const payload = (props.payload as MutationResultCardPayload | undefined) ?? {}\n const injected = props.initialAction ?? payload.pendingAction ?? null\n\n const { action: polled } = useAiPendingActionPolling({\n pendingActionId,\n endpoint: props.endpoint,\n disabled: !pendingActionId || Boolean(injected),\n })\n const action = injected ?? polled\n const status = action?.status ?? null\n const failedRecords = action?.failedRecords ?? null\n const result = action?.executionResult ?? null\n\n if (!action) {\n return null\n }\n\n if (isSuccessStatus(status) && failedRecords && failedRecords.length > 0) {\n return (\n <Alert status=\"warning\" data-ai-mutation-result=\"partial\">\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.result.partialTitle',\n 'Action applied with failures',\n )}\n </AlertTitle>\n <div className=\"text-sm leading-relaxed\">\n <p>\n {t(\n 'ai_assistant.chat.mutation_cards.result.partialBody',\n 'Some records could not be updated.',\n )}\n </p>\n <ul\n className=\"mt-2 list-disc space-y-1 pl-5 text-xs\"\n data-ai-mutation-failed-records\n >\n {failedRecords.map((record) => (\n <li key={record.recordId} data-ai-mutation-failed-record={record.recordId}>\n <span className=\"font-mono\">{record.recordId}</span>\n <span className=\"mx-1 text-muted-foreground\">\u2022</span>\n <span className=\"font-mono\">{record.error.code}</span>\n <span className=\"mx-1 text-muted-foreground\">\u2014</span>\n <span>{record.error.message}</span>\n </li>\n ))}\n </ul>\n </div>\n </Alert>\n )\n }\n\n if (isSuccessStatus(status)) {\n const recordId = result?.recordId ?? action.targetRecordId ?? null\n const href = payload.recordHref ?? null\n return (\n <Alert status=\"success\" data-ai-mutation-result=\"success\">\n <AlertTitle>\n {t('ai_assistant.chat.mutation_cards.result.successTitle', 'Action applied')}\n </AlertTitle>\n <div className=\"text-sm leading-relaxed\">\n <p>\n {t(\n 'ai_assistant.chat.mutation_cards.result.successBody',\n 'The requested change was saved successfully.',\n )}\n </p>\n {recordId && href ? (\n <p className=\"mt-1 text-xs\">\n <a\n className=\"text-primary underline\"\n href={href}\n data-ai-mutation-result-link\n >\n {t(\n 'ai_assistant.chat.mutation_cards.result.viewRecord',\n 'View record',\n )}\n </a>\n </p>\n ) : null}\n </div>\n </Alert>\n )\n }\n\n // A deliberate cancellation is not a failure: no destructive styling, no\n // internal error code, and no \"Fix with AI\" (which would just re-propose\n // the change the operator just rejected). Show the operator's own\n // cancellation reason verbatim when they gave one, otherwise a neutral\n // localized default.\n if (isCancelledStatus(status)) {\n const reason = result?.error?.message?.trim() || null\n return (\n <Alert status=\"information\" data-ai-mutation-result=\"cancelled\">\n <AlertTitle>\n {t('ai_assistant.chat.mutation_cards.result.cancelledTitle', 'Proposal cancelled')}\n </AlertTitle>\n <div className=\"text-sm leading-relaxed\">\n <p>\n {reason ??\n t(\n 'ai_assistant.chat.mutation_cards.result.cancelledBody',\n 'You cancelled this proposed change. No changes were made.',\n )}\n </p>\n </div>\n </Alert>\n )\n }\n\n // Render the failure alert when the action is in a failure status OR when\n // the dispatcher already captured an `executionResult.error` (the row may\n // not yet have transitioned out of `executing` in the polling snapshot,\n // but the handler error is authoritative \u2014 surface it immediately so the\n // operator never sees a stuck \"applying\u2026\" state silently masking a real\n // failure).\n if (isFailureStatus(status) || result?.error) {\n const code = result?.error?.code ?? status ?? 'failed'\n const message =\n result?.error?.message ??\n t(\n 'ai_assistant.chat.mutation_cards.result.failureBody',\n 'The mutation could not be applied.',\n )\n const errorObj = result?.error\n const errorDetails = errorObj?.details\n const errorInput = errorObj?.input\n const errorName = errorObj?.name\n const onFixWithAi = () => {\n // Build a structured prompt that gives the agent enough context to\n // diagnose and retry without copy/paste from the operator. Keeping\n // it explicit (\"retry with corrected arguments\") nudges the model\n // away from re-issuing the same args (which would just hit the\n // same error). The repository's idempotency check only dedupes\n // active `pending` rows, so a fresh prepareMutation call after a\n // terminal failure always produces a new pending action \u2014 the\n // retry is never silently collapsed.\n //\n // The prompt now embeds the full structured failure context the\n // server captured (Zod issues / fieldErrors, original arguments,\n // failedRecords for batch tools, error name + cause). Without this\n // the operator routinely saw \"Invalid input\" with no field path \u2014\n // the model literally could not fix what it could not see.\n const promptLines: string[] = [\n `The previous call to tool \"${action.toolName}\" failed.`,\n `Error: ${code} \u2014 ${message}.`,\n ]\n if (errorName && errorName !== 'Error') {\n promptLines.push(`Error class: ${errorName}.`)\n }\n if (action.targetEntityType || action.targetRecordId) {\n promptLines.push(\n `Target: ${action.targetEntityType ?? '?'}${action.targetRecordId ? ' / ' + action.targetRecordId : ''}.`,\n )\n }\n\n // Field-level validation issues (Zod, custom). Render as a bulleted\n // list of `path: message` so the model can locate the offender by\n // schema path instead of guessing from a generic message.\n const issues = errorDetails?.issues ?? []\n if (Array.isArray(issues) && issues.length > 0) {\n promptLines.push('', 'Validation issues:')\n for (const issue of issues) {\n const path = Array.isArray(issue?.path) && issue.path.length > 0\n ? issue.path.join('.')\n : '(root)'\n const msg = issue?.message ?? '(no message)'\n const codeHint = issue?.code ? ` [${issue.code}]` : ''\n const expHint =\n issue?.expected || issue?.received\n ? ` (expected ${issue?.expected ?? '?'}, got ${issue?.received ?? '?'})`\n : ''\n promptLines.push(`- ${path}: ${msg}${codeHint}${expHint}`)\n }\n } else if (errorDetails?.fieldErrors && typeof errorDetails.fieldErrors === 'object') {\n const entries = Object.entries(errorDetails.fieldErrors)\n if (entries.length > 0) {\n promptLines.push('', 'Field errors:')\n for (const [path, msgs] of entries) {\n const list = Array.isArray(msgs) ? msgs.join('; ') : String(msgs)\n promptLines.push(`- ${path}: ${list}`)\n }\n }\n }\n\n // Echo the arguments the handler was invoked with so the model can\n // see exactly what it sent and change at least one parameter on\n // retry. JSON-stringified inline for compactness; non-serializable\n // values are dropped on the server side already.\n if (errorInput !== undefined) {\n try {\n const json = JSON.stringify(errorInput, null, 2)\n if (json && json !== '{}' && json.length <= 4000) {\n promptLines.push('', 'Arguments you sent:', '```json', json, '```')\n } else if (json && json.length > 4000) {\n promptLines.push(\n '',\n 'Arguments you sent (truncated):',\n '```json',\n json.slice(0, 4000) + '\\n\u2026 [truncated]',\n '```',\n )\n }\n } catch {\n // ignore\n }\n }\n\n // Surface root-cause when the handler nested another error inside.\n if (errorDetails?.cause !== undefined) {\n try {\n const causeJson = JSON.stringify(errorDetails.cause, null, 2)\n if (causeJson && causeJson !== '{}' && causeJson.length <= 1500) {\n promptLines.push('', 'Underlying cause:', '```json', causeJson, '```')\n }\n } catch {\n // ignore\n }\n }\n\n // Per-record failures from batch tools (Step 5.14). These usually\n // carry the most actionable information for partial-success cases.\n if (failedRecords && failedRecords.length > 0) {\n promptLines.push('', 'Records that failed:')\n for (const rec of failedRecords) {\n promptLines.push(\n `- ${rec.recordId} \u2192 ${rec.error.code}: ${rec.error.message}`,\n )\n }\n }\n\n promptLines.push(\n '',\n 'Diagnose what went wrong using the validation issues / cause / arguments above, correct the arguments, and call the tool again. If the failure indicates missing prerequisites (e.g. a deal needs a linked person/company before commenting), tell me what to fix on the platform side instead of retrying blindly. Do not repeat the exact same arguments \u2014 you must change at least one parameter or stop and explain.',\n )\n dispatchFixRequest({\n message: promptLines.join('\\n'),\n toolName: action.toolName,\n pendingActionId: action.id,\n })\n }\n const visibleIssues = Array.isArray(errorDetails?.issues)\n ? errorDetails!.issues!.filter((entry) => entry && (entry.message || entry.path))\n : []\n return (\n <Alert status=\"error\" data-ai-mutation-result=\"failure\">\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.result.failureTitle',\n 'Action failed',\n )}\n </AlertTitle>\n <div className=\"text-sm leading-relaxed\">\n <div>\n <span className=\"mr-2 font-mono text-xs\" data-ai-mutation-result-code>\n {code}\n </span>\n <span>{message}</span>\n </div>\n {visibleIssues.length > 0 ? (\n <ul\n className=\"mt-2 list-disc space-y-0.5 pl-5 text-xs\"\n data-ai-mutation-result-issues\n >\n {visibleIssues.map((issue, index) => {\n const path =\n Array.isArray(issue?.path) && issue.path.length > 0\n ? issue.path.join('.')\n : null\n return (\n <li key={index}>\n {path ? (\n <span className=\"font-mono\">{path}</span>\n ) : null}\n {path && issue?.message ? <span className=\"mx-1\">\u2014</span> : null}\n {issue?.message ? <span>{issue.message}</span> : null}\n </li>\n )\n })}\n </ul>\n ) : null}\n <div className=\"mt-2 flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={onFixWithAi}\n data-ai-mutation-result-fix\n >\n <Wand2 className=\"size-4\" aria-hidden />\n <span>\n {t(\n 'ai_assistant.chat.mutation_cards.result.fixWithAi',\n 'Fix with AI',\n )}\n </span>\n </Button>\n </div>\n </div>\n </Alert>\n )\n }\n\n return null\n}\n\nexport default MutationResultCard\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Wand2 } 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 type { AiUiPartProps } from '../ui-part-registry'\nimport { useAiPendingActionPolling } from './useAiPendingActionPolling'\nimport type { AiPendingActionCardAction, AiPendingActionCardStatus } from './types'\n\n/** Custom DOM event the failure card dispatches when the operator clicks\n * \"Fix with AI\". `<AiChat>` listens for this and sends a follow-up user\n * message asking the agent to diagnose and retry the failed call. */\nexport const AI_CHAT_FIX_REQUEST_EVENT = 'om-ai-chat-fix-request'\n\nexport interface AiChatFixRequestDetail {\n message: string\n toolName?: string\n pendingActionId?: string\n}\n\nfunction dispatchFixRequest(detail: AiChatFixRequestDetail): void {\n if (typeof window === 'undefined') return\n window.dispatchEvent(new CustomEvent<AiChatFixRequestDetail>(AI_CHAT_FIX_REQUEST_EVENT, { detail }))\n}\n\n/**\n * Terminal-state card that renders the `executionResult` of a pending\n * action. Success \u2192 `Alert variant=\"success\"` with a record link; partial\n * success (batch `failedRecords[]`) \u2192 `variant=\"warning\"` with the list;\n * failure \u2192 `variant=\"destructive\"` with the error code + message.\n *\n * Reads the pending action via the shared polling hook so page reloads\n * still recover state. The hook short-circuits once the row is terminal\n * (spec's reconnect behavior), which is always the case for this card.\n */\nexport interface MutationResultCardPayload {\n /** Server-serialized pending action snapshot (optional \u2014 the hook refetches). */\n pendingAction?: AiPendingActionCardAction\n /** Optional link target for the success record. */\n recordHref?: string\n}\n\nexport interface MutationResultCardProps extends AiUiPartProps {\n /** Optional injected action for tests \u2014 bypasses the polling fetch. */\n initialAction?: AiPendingActionCardAction\n /** Poll endpoint override for tests. */\n endpoint?: string\n}\n\nfunction isSuccessStatus(status: AiPendingActionCardStatus | null): boolean {\n return status === 'confirmed'\n}\n\nfunction isFailureStatus(status: AiPendingActionCardStatus | null): boolean {\n return status === 'failed' || status === 'cancelled' || status === 'expired'\n}\n\nexport function MutationResultCard(props: MutationResultCardProps) {\n const t = useT()\n const pendingActionId = props.pendingActionId ?? ''\n const payload = (props.payload as MutationResultCardPayload | undefined) ?? {}\n const injected = props.initialAction ?? payload.pendingAction ?? null\n\n const { action: polled } = useAiPendingActionPolling({\n pendingActionId,\n endpoint: props.endpoint,\n disabled: !pendingActionId || Boolean(injected),\n })\n const action = injected ?? polled\n const status = action?.status ?? null\n const failedRecords = action?.failedRecords ?? null\n const result = action?.executionResult ?? null\n\n if (!action) {\n return null\n }\n\n if (isSuccessStatus(status) && failedRecords && failedRecords.length > 0) {\n return (\n <Alert status=\"warning\" data-ai-mutation-result=\"partial\">\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.result.partialTitle',\n 'Action applied with failures',\n )}\n </AlertTitle>\n <div className=\"text-sm leading-relaxed\">\n <p>\n {t(\n 'ai_assistant.chat.mutation_cards.result.partialBody',\n 'Some records could not be updated.',\n )}\n </p>\n <ul\n className=\"mt-2 list-disc space-y-1 pl-5 text-xs\"\n data-ai-mutation-failed-records\n >\n {failedRecords.map((record) => (\n <li key={record.recordId} data-ai-mutation-failed-record={record.recordId}>\n <span className=\"font-mono\">{record.recordId}</span>\n <span className=\"mx-1 text-muted-foreground\">\u2022</span>\n <span className=\"font-mono\">{record.error.code}</span>\n <span className=\"mx-1 text-muted-foreground\">\u2014</span>\n <span>{record.error.message}</span>\n </li>\n ))}\n </ul>\n </div>\n </Alert>\n )\n }\n\n if (isSuccessStatus(status)) {\n const recordId = result?.recordId ?? action.targetRecordId ?? null\n const href = payload.recordHref ?? null\n return (\n <Alert status=\"success\" data-ai-mutation-result=\"success\">\n <AlertTitle>\n {t('ai_assistant.chat.mutation_cards.result.successTitle', 'Action applied')}\n </AlertTitle>\n <div className=\"text-sm leading-relaxed\">\n <p>\n {t(\n 'ai_assistant.chat.mutation_cards.result.successBody',\n 'The requested change was saved successfully.',\n )}\n </p>\n {recordId && href ? (\n <p className=\"mt-1 text-xs\">\n <a\n className=\"text-primary underline\"\n href={href}\n data-ai-mutation-result-link\n >\n {t(\n 'ai_assistant.chat.mutation_cards.result.viewRecord',\n 'View record',\n )}\n </a>\n </p>\n ) : null}\n </div>\n </Alert>\n )\n }\n\n // Render the failure alert when the action is in a failure status OR when\n // the dispatcher already captured an `executionResult.error` (the row may\n // not yet have transitioned out of `executing` in the polling snapshot,\n // but the handler error is authoritative \u2014 surface it immediately so the\n // operator never sees a stuck \"applying\u2026\" state silently masking a real\n // failure).\n if (isFailureStatus(status) || result?.error) {\n const code = result?.error?.code ?? status ?? 'failed'\n const message =\n result?.error?.message ??\n t(\n 'ai_assistant.chat.mutation_cards.result.failureBody',\n 'The mutation could not be applied.',\n )\n const errorObj = result?.error\n const errorDetails = errorObj?.details\n const errorInput = errorObj?.input\n const errorName = errorObj?.name\n const onFixWithAi = () => {\n // Build a structured prompt that gives the agent enough context to\n // diagnose and retry without copy/paste from the operator. Keeping\n // it explicit (\"retry with corrected arguments\") nudges the model\n // away from re-issuing the same args (which would just hit the\n // same error). The repository's idempotency check only dedupes\n // active `pending` rows, so a fresh prepareMutation call after a\n // terminal failure always produces a new pending action \u2014 the\n // retry is never silently collapsed.\n //\n // The prompt now embeds the full structured failure context the\n // server captured (Zod issues / fieldErrors, original arguments,\n // failedRecords for batch tools, error name + cause). Without this\n // the operator routinely saw \"Invalid input\" with no field path \u2014\n // the model literally could not fix what it could not see.\n const promptLines: string[] = [\n `The previous call to tool \"${action.toolName}\" failed.`,\n `Error: ${code} \u2014 ${message}.`,\n ]\n if (errorName && errorName !== 'Error') {\n promptLines.push(`Error class: ${errorName}.`)\n }\n if (action.targetEntityType || action.targetRecordId) {\n promptLines.push(\n `Target: ${action.targetEntityType ?? '?'}${action.targetRecordId ? ' / ' + action.targetRecordId : ''}.`,\n )\n }\n\n // Field-level validation issues (Zod, custom). Render as a bulleted\n // list of `path: message` so the model can locate the offender by\n // schema path instead of guessing from a generic message.\n const issues = errorDetails?.issues ?? []\n if (Array.isArray(issues) && issues.length > 0) {\n promptLines.push('', 'Validation issues:')\n for (const issue of issues) {\n const path = Array.isArray(issue?.path) && issue.path.length > 0\n ? issue.path.join('.')\n : '(root)'\n const msg = issue?.message ?? '(no message)'\n const codeHint = issue?.code ? ` [${issue.code}]` : ''\n const expHint =\n issue?.expected || issue?.received\n ? ` (expected ${issue?.expected ?? '?'}, got ${issue?.received ?? '?'})`\n : ''\n promptLines.push(`- ${path}: ${msg}${codeHint}${expHint}`)\n }\n } else if (errorDetails?.fieldErrors && typeof errorDetails.fieldErrors === 'object') {\n const entries = Object.entries(errorDetails.fieldErrors)\n if (entries.length > 0) {\n promptLines.push('', 'Field errors:')\n for (const [path, msgs] of entries) {\n const list = Array.isArray(msgs) ? msgs.join('; ') : String(msgs)\n promptLines.push(`- ${path}: ${list}`)\n }\n }\n }\n\n // Echo the arguments the handler was invoked with so the model can\n // see exactly what it sent and change at least one parameter on\n // retry. JSON-stringified inline for compactness; non-serializable\n // values are dropped on the server side already.\n if (errorInput !== undefined) {\n try {\n const json = JSON.stringify(errorInput, null, 2)\n if (json && json !== '{}' && json.length <= 4000) {\n promptLines.push('', 'Arguments you sent:', '```json', json, '```')\n } else if (json && json.length > 4000) {\n promptLines.push(\n '',\n 'Arguments you sent (truncated):',\n '```json',\n json.slice(0, 4000) + '\\n\u2026 [truncated]',\n '```',\n )\n }\n } catch {\n // ignore\n }\n }\n\n // Surface root-cause when the handler nested another error inside.\n if (errorDetails?.cause !== undefined) {\n try {\n const causeJson = JSON.stringify(errorDetails.cause, null, 2)\n if (causeJson && causeJson !== '{}' && causeJson.length <= 1500) {\n promptLines.push('', 'Underlying cause:', '```json', causeJson, '```')\n }\n } catch {\n // ignore\n }\n }\n\n // Per-record failures from batch tools (Step 5.14). These usually\n // carry the most actionable information for partial-success cases.\n if (failedRecords && failedRecords.length > 0) {\n promptLines.push('', 'Records that failed:')\n for (const rec of failedRecords) {\n promptLines.push(\n `- ${rec.recordId} \u2192 ${rec.error.code}: ${rec.error.message}`,\n )\n }\n }\n\n promptLines.push(\n '',\n 'Diagnose what went wrong using the validation issues / cause / arguments above, correct the arguments, and call the tool again. If the failure indicates missing prerequisites (e.g. a deal needs a linked person/company before commenting), tell me what to fix on the platform side instead of retrying blindly. Do not repeat the exact same arguments \u2014 you must change at least one parameter or stop and explain.',\n )\n dispatchFixRequest({\n message: promptLines.join('\\n'),\n toolName: action.toolName,\n pendingActionId: action.id,\n })\n }\n const visibleIssues = Array.isArray(errorDetails?.issues)\n ? errorDetails!.issues!.filter((entry) => entry && (entry.message || entry.path))\n : []\n return (\n <Alert status=\"error\" data-ai-mutation-result=\"failure\">\n <AlertTitle>\n {t(\n 'ai_assistant.chat.mutation_cards.result.failureTitle',\n 'Action failed',\n )}\n </AlertTitle>\n <div className=\"text-sm leading-relaxed\">\n <div>\n <span className=\"mr-2 font-mono text-xs\" data-ai-mutation-result-code>\n {code}\n </span>\n <span>{message}</span>\n </div>\n {visibleIssues.length > 0 ? (\n <ul\n className=\"mt-2 list-disc space-y-0.5 pl-5 text-xs\"\n data-ai-mutation-result-issues\n >\n {visibleIssues.map((issue, index) => {\n const path =\n Array.isArray(issue?.path) && issue.path.length > 0\n ? issue.path.join('.')\n : null\n return (\n <li key={index}>\n {path ? (\n <span className=\"font-mono\">{path}</span>\n ) : null}\n {path && issue?.message ? <span className=\"mx-1\">\u2014</span> : null}\n {issue?.message ? <span>{issue.message}</span> : null}\n </li>\n )\n })}\n </ul>\n ) : null}\n <div className=\"mt-2 flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={onFixWithAi}\n data-ai-mutation-result-fix\n >\n <Wand2 className=\"size-4\" aria-hidden />\n <span>\n {t(\n 'ai_assistant.chat.mutation_cards.result.fixWithAi',\n 'Fix with AI',\n )}\n </span>\n </Button>\n </div>\n </div>\n </Alert>\n )\n }\n\n return null\n}\n\nexport default MutationResultCard\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -98,7 +98,7 @@ import { CollapsibleGroup } from "./crud/CollapsibleGroup.js";
98
98
  import { SortableGroupHandleProvider } from "./crud/SortableGroupHandle.js";
99
99
  import { useGroupOrder } from "./crud/useGroupOrder.js";
100
100
  import { InjectedField } from "./injection/InjectedField.js";
101
- import { placeInjectedFieldsInGroups } from "./crud/injectedFieldGroups.js";
101
+ import { insertByInjectionPlacement } from "@open-mercato/shared/modules/widgets/injection-position";
102
102
  import { evaluateInjectedVisibility } from "./injection/visibility-utils.js";
103
103
  import { ComponentReplacementHandles } from "@open-mercato/shared/modules/widgets/component-registry";
104
104
  import { crudFormExtensionSpotId, extensionSpotChildId } from "@open-mercato/shared/modules/widgets/extension-points";
@@ -173,21 +173,6 @@ function buildResolvedEntityIdsKey(entityId, entityIds) {
173
173
  }
174
174
  return list.join("\0");
175
175
  }
176
- function renderNoInjectedFieldWidget() {
177
- return null;
178
- }
179
- async function skipInjectedFieldDeleteEvent() {
180
- }
181
- const INJECTED_FIELD_DELETE_EVENT_DEFAULTS = {
182
- onBeforeDelete: skipInjectedFieldDeleteEvent,
183
- onDelete: skipInjectedFieldDeleteEvent,
184
- onAfterDelete: skipInjectedFieldDeleteEvent
185
- };
186
- function injectionWidgetPriority(widget) {
187
- const placementPriority = widget.placement?.priority;
188
- if (typeof placementPriority === "number") return placementPriority;
189
- return typeof widget.module.metadata.priority === "number" ? widget.module.metadata.priority : 0;
190
- }
191
176
  function mergeByKey(primary, legacy, keyOf) {
192
177
  if (!legacy.length) return primary;
193
178
  const seen = new Set(primary.map(keyOf));
@@ -568,13 +553,7 @@ function CrudForm({
568
553
  const navigationConfirmPendingRef = React.useRef(false);
569
554
  const submitNavigationBypassRef = React.useRef(false);
570
555
  const submitNavigationBypassTimeoutRef = React.useRef(null);
571
- const [hasUnsavedChanges, setHasUnsavedChangesState] = React.useState(false);
572
- const hasUnsavedChangesRef = React.useRef(false);
573
- const setHasUnsavedChanges = React.useCallback((next) => {
574
- if (hasUnsavedChangesRef.current === next) return;
575
- hasUnsavedChangesRef.current = next;
576
- setHasUnsavedChangesState(next);
577
- }, []);
556
+ const [hasUnsavedChanges, setHasUnsavedChanges] = React.useState(false);
578
557
  const popStateRollbackRef = React.useRef(false);
579
558
  const shouldBypassUnsavedChangesGuardRef = React.useRef(shouldBypassUnsavedChangesGuard);
580
559
  React.useEffect(() => {
@@ -596,7 +575,7 @@ function CrudForm({
596
575
  const dirty = currentSnapshot !== snapshot;
597
576
  isDirtyRef.current = dirty;
598
577
  setHasUnsavedChanges(dirty);
599
- }, [embedded, trackDirtyWhenEmbedded, values, setHasUnsavedChanges]);
578
+ }, [embedded, trackDirtyWhenEmbedded, values]);
600
579
  React.useEffect(() => {
601
580
  onDirtyChange?.(hasUnsavedChanges);
602
581
  }, [hasUnsavedChanges, onDirtyChange]);
@@ -640,7 +619,7 @@ function CrudForm({
640
619
  everEditedFieldIdsRef.current.clear();
641
620
  isDirtyRef.current = false;
642
621
  setHasUnsavedChanges(false);
643
- }, [setHasUnsavedChanges]);
622
+ }, []);
644
623
  const confirmUnsavedChanges = React.useCallback(async () => {
645
624
  if (!isDirtyRef.current || typeof window === "undefined") return true;
646
625
  if (navigationPromptBypassRef.current || submitNavigationBypassRef.current) return true;
@@ -746,7 +725,7 @@ function CrudForm({
746
725
  window.history.pushState = originalPushState;
747
726
  window.history.replaceState = originalReplaceState;
748
727
  };
749
- }, [allowNextNavigation, clearDirtyState, confirmUnsavedChanges, embedded, hasUnsavedChanges, router, trackDirtyWhenEmbedded, setHasUnsavedChanges]);
728
+ }, [allowNextNavigation, clearDirtyState, confirmUnsavedChanges, embedded, hasUnsavedChanges, router, trackDirtyWhenEmbedded]);
750
729
  const { widgets: primaryInjectionWidgets } = useInjectionWidgets(resolvedInjectionSpotId, {
751
730
  context: injectionContext,
752
731
  triggerOnLoad: true
@@ -769,26 +748,7 @@ function CrudForm({
769
748
  () => mergeByKey(primaryFieldWidgets, legacyFieldWidgets, (w) => w.metadata.id),
770
749
  [primaryFieldWidgets, legacyFieldWidgets]
771
750
  );
772
- const injectionEventWidgets = React.useMemo(() => {
773
- const fieldEventWidgets = [];
774
- for (const widget of injectedFieldWidgets) {
775
- if (!("fields" in widget) || !widget.eventHandlers) continue;
776
- fieldEventWidgets.push({
777
- widgetId: widget.metadata.id,
778
- module: {
779
- metadata: widget.metadata,
780
- eventHandlers: { ...INJECTED_FIELD_DELETE_EVENT_DEFAULTS, ...widget.eventHandlers },
781
- Widget: renderNoInjectedFieldWidget
782
- },
783
- moduleId: widget.moduleId,
784
- key: widget.key,
785
- placement: widget.placement
786
- });
787
- }
788
- if (!fieldEventWidgets.length) return injectionWidgets;
789
- return mergeByKey(injectionWidgets, fieldEventWidgets, (widget) => widget.widgetId).map((widget, index) => ({ widget, index })).sort((left, right) => injectionWidgetPriority(right.widget) - injectionWidgetPriority(left.widget) || left.index - right.index).map((entry) => entry.widget);
790
- }, [injectionWidgets, injectedFieldWidgets]);
791
- const { triggerEvent: triggerInjectionEvent } = useInjectionSpotEvents(resolvedInjectionSpotId ?? "", injectionEventWidgets);
751
+ const { triggerEvent: triggerInjectionEvent } = useInjectionSpotEvents(resolvedInjectionSpotId ?? "", injectionWidgets);
792
752
  const extendedInjectionEventsEnabled = CRUDFORM_EXTENDED_EVENTS_ENABLED && Boolean(resolvedInjectionSpotId);
793
753
  const widgetRequiredFieldIds = React.useMemo(() => {
794
754
  const ids = /* @__PURE__ */ new Set();
@@ -1155,13 +1115,6 @@ function CrudForm({
1155
1115
  headerInjectionAction,
1156
1116
  extraActions
1157
1117
  ] }) : void 0;
1158
- const renderFooterActions = !hideFooterActions && !formReadOnly;
1159
- const embeddedExtraActions = renderFooterActions ? void 0 : extraActions;
1160
- const embeddedHeaderActions = versionHistoryEnabled || headerInjectionAction || embeddedExtraActions ? /* @__PURE__ */ jsxs(Fragment, { children: [
1161
- versionHistoryEnabled ? versionHistoryAction : null,
1162
- headerInjectionAction,
1163
- embeddedExtraActions
1164
- ] }) : void 0;
1165
1118
  React.useEffect(() => {
1166
1119
  let cancelled = false;
1167
1120
  async function load() {
@@ -1394,24 +1347,6 @@ function CrudForm({
1394
1347
  }
1395
1348
  return definitions;
1396
1349
  }, [injectedFieldWidgets]);
1397
- const injectedFieldPlacements = React.useMemo(() => {
1398
- const placements = /* @__PURE__ */ new Map();
1399
- for (const widget of injectedFieldWidgets) {
1400
- if (!("fields" in widget)) continue;
1401
- for (const field of widget.fields ?? []) {
1402
- placements.set(field.id, widget.placement);
1403
- }
1404
- }
1405
- return placements;
1406
- }, [injectedFieldWidgets]);
1407
- const groupsWithInjectedFields = React.useMemo(() => {
1408
- if (!groups || groups.length === 0 || injectedFieldDefinitions.length === 0) return groups;
1409
- return placeInjectedFieldsInGroups(groups, injectedFieldDefinitions, injectedFieldPlacements, (definition) => {
1410
- if (process.env.NODE_ENV !== "production") {
1411
- logger.warn("Injected field targets a group that does not exist; appended to the last plain-field group", { fieldId: definition.id, group: definition.group });
1412
- }
1413
- });
1414
- }, [groups, injectedFieldDefinitions, injectedFieldPlacements]);
1415
1350
  const injectedFieldContext = React.useMemo(() => {
1416
1351
  const recordValues = values;
1417
1352
  const organizationId = typeof recordValues.organizationId === "string" ? recordValues.organizationId : null;
@@ -1458,15 +1393,22 @@ function CrudForm({
1458
1393
  }
1459
1394
  return (group.fields ?? []).map((entry) => typeof entry === "string" ? entry : entry.id);
1460
1395
  };
1396
+ const declaredGroups = groups ?? [];
1461
1397
  const hidden = /* @__PURE__ */ new Set();
1462
1398
  const visible = /* @__PURE__ */ new Set();
1463
- for (const group of groupsWithInjectedFields ?? []) {
1399
+ for (const group of declaredGroups) {
1464
1400
  const target = hiddenGroupIdSet.has(group.id) ? hidden : visible;
1465
1401
  for (const fieldId of groupFieldIds(group)) target.add(fieldId);
1466
1402
  }
1403
+ const declaredGroupIds = new Set(declaredGroups.map((group) => group.id));
1404
+ const fallbackGroupId = declaredGroups[declaredGroups.length - 1]?.id;
1405
+ for (const definition of injectedFieldDefinitions) {
1406
+ const targetGroupId = definition.group && declaredGroupIds.has(definition.group) ? definition.group : fallbackGroupId;
1407
+ if (targetGroupId && hiddenGroupIdSet.has(targetGroupId)) hidden.add(definition.id);
1408
+ }
1467
1409
  for (const fieldId of visible) hidden.delete(fieldId);
1468
1410
  return hidden;
1469
- }, [cfFields, groupsWithInjectedFields, hiddenGroupIdSet, placedCustomFieldIds]);
1411
+ }, [cfFields, groups, hiddenGroupIdSet, injectedFieldDefinitions, placedCustomFieldIds]);
1470
1412
  const hiddenInjectedFieldIds = React.useMemo(() => {
1471
1413
  const hidden = /* @__PURE__ */ new Set();
1472
1414
  for (const definition of injectedFieldDefinitions) {
@@ -1673,6 +1615,31 @@ function CrudForm({
1673
1615
  pairs.sort((a, b) => b.priority - a.priority);
1674
1616
  return pairs.map((p) => p.group);
1675
1617
  }, [injectionWidgets, injectionContext, pending, setValues, values]);
1618
+ const groupsWithInjectedFields = React.useMemo(() => {
1619
+ if (!groups || groups.length === 0 || injectedFieldDefinitions.length === 0) return groups;
1620
+ const cloned = groups.map((group) => ({ ...group, fields: [...group.fields ?? []] }));
1621
+ const fallbackIndex = cloned.length - 1;
1622
+ for (const definition of injectedFieldDefinitions) {
1623
+ const targetIndex = cloned.findIndex((group) => group.id === definition.group);
1624
+ const index = targetIndex >= 0 ? targetIndex : fallbackIndex;
1625
+ if (targetIndex < 0 && process.env.NODE_ENV !== "production") {
1626
+ logger.warn("Injected field targets a group that does not exist; appended to last group", { fieldId: definition.id, group: definition.group });
1627
+ }
1628
+ if (index < 0) continue;
1629
+ const fieldEntries = cloned[index].fields ?? [];
1630
+ const alreadyPresent = fieldEntries.some((entry) => {
1631
+ const entryId = typeof entry === "string" ? entry : entry.id;
1632
+ return entryId === definition.id;
1633
+ });
1634
+ cloned[index].fields = alreadyPresent ? fieldEntries : insertByInjectionPlacement(
1635
+ fieldEntries,
1636
+ definition.id,
1637
+ definition.placement,
1638
+ (entry) => typeof entry === "string" ? entry : entry.id
1639
+ );
1640
+ }
1641
+ return cloned;
1642
+ }, [groups, injectedFieldDefinitions]);
1676
1643
  const shouldAutoGroup = (!groupsWithInjectedFields || groupsWithInjectedFields.length === 0) && injectionGroupCards.length > 0;
1677
1644
  const declaredGroupsForLayout = React.useMemo(() => {
1678
1645
  const baseGroups = groupsWithInjectedFields && groupsWithInjectedFields.length ? groupsWithInjectedFields : [];
@@ -1997,18 +1964,14 @@ function CrudForm({
1997
1964
  if (activeDispatch?.eventId === dispatchToken.eventId && activeDispatch.formGeneration === dispatchToken.formGeneration) {
1998
1965
  activeFieldChangeDispatchRef.current = null;
1999
1966
  }
2000
- if (fieldChangeDispatcherMountedRef.current && fieldChangeFormGenerationRef.current === dispatchToken.formGeneration && pendingFieldChangeEventsRef.current.length > 0) {
1967
+ if (fieldChangeDispatcherMountedRef.current && fieldChangeFormGenerationRef.current === dispatchToken.formGeneration) {
2001
1968
  setFieldChangeDispatchVersion((version) => version + 1);
2002
1969
  }
2003
1970
  });
2004
1971
  }, [extendedInjectionEventsEnabled, fieldChangeDispatchVersion, triggerInjectionEvent, values]);
2005
- const validateFieldOnBlurRef = React.useRef(validateFieldOnBlur);
2006
- React.useLayoutEffect(() => {
2007
- validateFieldOnBlurRef.current = validateFieldOnBlur;
2008
- }, [validateFieldOnBlur]);
2009
1972
  const onBlurRequest = React.useCallback((fieldId) => {
2010
- void validateFieldOnBlurRef.current(fieldId);
2011
- }, []);
1973
+ void validateFieldOnBlur(fieldId);
1974
+ }, [validateFieldOnBlur]);
2012
1975
  const handleFieldsetSelectionChange = React.useCallback(
2013
1976
  (entityId2, nextCode) => {
2014
1977
  if (!nextCode) return;
@@ -2990,7 +2953,7 @@ function CrudForm({
2990
2953
  submit: formReadOnly ? void 0 : { formId, pending, label: resolvedSubmitLabel, pendingLabel: savingLabel, icon: submitIcon }
2991
2954
  }
2992
2955
  }
2993
- ) : embeddedHeaderActions ? /* @__PURE__ */ jsx("div", { className: "flex justify-end gap-2 mb-2", children: embeddedHeaderActions }) : null,
2956
+ ) : headerExtraActions ? /* @__PURE__ */ jsx("div", { className: "flex justify-end gap-2 mb-2", children: headerExtraActions }) : null,
2994
2957
  contentHeader,
2995
2958
  /* @__PURE__ */ jsx(
2996
2959
  DataLoader,
@@ -3023,7 +2986,7 @@ function CrudForm({
3023
2986
  }
3024
2987
  ),
3025
2988
  formError && !Object.keys(errors).length ? /* @__PURE__ */ jsx("div", { className: "text-sm text-status-error-text", children: formError }) : null,
3026
- renderFooterActions ? /* @__PURE__ */ jsx(
2989
+ hideFooterActions || formReadOnly ? null : /* @__PURE__ */ jsx(
3027
2990
  FormFooter,
3028
2991
  {
3029
2992
  embedded,
@@ -3039,7 +3002,7 @@ function CrudForm({
3039
3002
  submit: { pending, label: resolvedSubmitLabel, pendingLabel: savingLabel, icon: submitIcon }
3040
3003
  }
3041
3004
  }
3042
- ) : null
3005
+ )
3043
3006
  ] })
3044
3007
  )
3045
3008
  }
@@ -3068,7 +3031,7 @@ function CrudForm({
3068
3031
  submit: formReadOnly ? void 0 : { formId, pending, label: resolvedSubmitLabel, pendingLabel: savingLabel, icon: submitIcon }
3069
3032
  }
3070
3033
  }
3071
- ) : embeddedHeaderActions ? /* @__PURE__ */ jsx("div", { className: "flex justify-end gap-2 mb-2", children: embeddedHeaderActions }) : null,
3034
+ ) : headerExtraActions ? /* @__PURE__ */ jsx("div", { className: "flex justify-end gap-2 mb-2", children: headerExtraActions }) : null,
3072
3035
  contentHeader,
3073
3036
  /* @__PURE__ */ jsx(
3074
3037
  DataLoader,
@@ -3122,7 +3085,7 @@ function CrudForm({
3122
3085
  );
3123
3086
  }) }),
3124
3087
  formError && !Object.keys(errors).length ? /* @__PURE__ */ jsx("div", { className: "text-sm text-status-error-text", children: formError }) : null,
3125
- renderFooterActions ? /* @__PURE__ */ jsx(
3088
+ hideFooterActions || formReadOnly ? null : /* @__PURE__ */ jsx(
3126
3089
  FormFooter,
3127
3090
  {
3128
3091
  embedded,
@@ -3138,7 +3101,7 @@ function CrudForm({
3138
3101
  submit: { pending, label: resolvedSubmitLabel, pendingLabel: savingLabel, icon: submitIcon }
3139
3102
  }
3140
3103
  }
3141
- ) : null
3104
+ )
3142
3105
  ]
3143
3106
  }
3144
3107
  ) })
@@ -3293,9 +3256,7 @@ function NumberInput({
3293
3256
  onChange,
3294
3257
  placeholder,
3295
3258
  autoFocus,
3296
- onSubmit,
3297
- disabled,
3298
- readOnly
3259
+ onSubmit
3299
3260
  }) {
3300
3261
  const locale = useOptionalLocale();
3301
3262
  const serializedValue = value !== void 0 && value !== null ? String(value) : "";
@@ -3363,8 +3324,6 @@ function NumberInput({
3363
3324
  onFocus: handleFocus,
3364
3325
  onBlur: handleBlur,
3365
3326
  autoFocus,
3366
- disabled,
3367
- readOnly,
3368
3327
  "data-crud-focus-target": ""
3369
3328
  }
3370
3329
  )
@@ -3690,9 +3649,7 @@ const FieldControl = React.memo(
3690
3649
  placeholder,
3691
3650
  onChange: fieldSetValue,
3692
3651
  autoFocus: autoFocusField,
3693
- onSubmit: onSubmitRequest,
3694
- disabled,
3695
- readOnly
3652
+ onSubmit: onSubmitRequest
3696
3653
  }
3697
3654
  ),
3698
3655
  field.type === "date" && /* @__PURE__ */ jsx(
@@ -3792,8 +3749,7 @@ const FieldControl = React.memo(
3792
3749
  autoFocus: autoFocusField,
3793
3750
  suppressInitialSuggestionsOnFocus: autoFocusField,
3794
3751
  suggestions: options.map((opt) => ({ value: opt.value, label: opt.label })),
3795
- loadSuggestions: loadFieldSuggestions,
3796
- disabled
3752
+ loadSuggestions: loadFieldSuggestions
3797
3753
  }
3798
3754
  ),
3799
3755
  field.type === "combobox" && /* @__PURE__ */ jsx(