@open-mercato/ui 0.6.6-develop.6465.1.019f0fb26f → 0.6.6-develop.6471.1.9ab5bdd79a

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 (81) hide show
  1. package/dist/ai/AiChat.js +3 -3
  2. package/dist/ai/AiChat.js.map +2 -2
  3. package/dist/backend/CrudForm.js +19 -17
  4. package/dist/backend/CrudForm.js.map +2 -2
  5. package/dist/backend/DataTable.js +5 -5
  6. package/dist/backend/DataTable.js.map +2 -2
  7. package/dist/backend/JsonDisplay.js +3 -1
  8. package/dist/backend/JsonDisplay.js.map +2 -2
  9. package/dist/backend/confirm-dialog/useConfirmDialog.js +3 -3
  10. package/dist/backend/confirm-dialog/useConfirmDialog.js.map +2 -2
  11. package/dist/backend/dashboard/DashboardScreen.js +8 -6
  12. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  13. package/dist/backend/dashboard/widgetRegistry.js +3 -1
  14. package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
  15. package/dist/backend/detail/CustomDataSection.js +4 -2
  16. package/dist/backend/detail/CustomDataSection.js.map +2 -2
  17. package/dist/backend/detail/InlineEditors.js +3 -1
  18. package/dist/backend/detail/InlineEditors.js.map +2 -2
  19. package/dist/backend/detail/TagsSection.js +3 -1
  20. package/dist/backend/detail/TagsSection.js.map +2 -2
  21. package/dist/backend/injection/InjectionSpot.js +10 -8
  22. package/dist/backend/injection/InjectionSpot.js.map +2 -2
  23. package/dist/backend/injection/eventBridge.js +3 -1
  24. package/dist/backend/injection/eventBridge.js.map +2 -2
  25. package/dist/backend/injection/useGuardedMutation.js +3 -1
  26. package/dist/backend/injection/useGuardedMutation.js.map +2 -2
  27. package/dist/backend/injection/useInjectionDataWidgets.js +3 -1
  28. package/dist/backend/injection/useInjectionDataWidgets.js.map +2 -2
  29. package/dist/backend/injection/useRegisteredComponent.js +6 -9
  30. package/dist/backend/injection/useRegisteredComponent.js.map +2 -2
  31. package/dist/backend/injection/widgetRegistry.js +3 -1
  32. package/dist/backend/injection/widgetRegistry.js.map +2 -2
  33. package/dist/backend/inputs/LookupSelect.js +3 -1
  34. package/dist/backend/inputs/LookupSelect.js.map +2 -2
  35. package/dist/backend/notifications/NotificationDispatcher.js +4 -2
  36. package/dist/backend/notifications/NotificationDispatcher.js.map +2 -2
  37. package/dist/backend/sidebar/SidebarCustomizationEditor.js +7 -5
  38. package/dist/backend/sidebar/SidebarCustomizationEditor.js.map +2 -2
  39. package/dist/backend/utils/api.js +3 -1
  40. package/dist/backend/utils/api.js.map +2 -2
  41. package/dist/backend/utils/bulkDelete.js +4 -2
  42. package/dist/backend/utils/bulkDelete.js.map +2 -2
  43. package/dist/backend/version-history/VersionHistoryPanel.js +4 -2
  44. package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
  45. package/dist/portal/hooks/usePortalDashboardWidgets.js +3 -1
  46. package/dist/portal/hooks/usePortalDashboardWidgets.js.map +2 -2
  47. package/dist/portal/hooks/usePortalEventBridge.js +3 -1
  48. package/dist/portal/hooks/usePortalEventBridge.js.map +2 -2
  49. package/dist/primitives/Notice.js +3 -3
  50. package/dist/primitives/Notice.js.map +2 -2
  51. package/dist/theme/ThemeProvider.js +4 -2
  52. package/dist/theme/ThemeProvider.js.map +2 -2
  53. package/package.json +3 -3
  54. package/src/ai/AiChat.tsx +4 -4
  55. package/src/backend/CrudForm.tsx +20 -17
  56. package/src/backend/DataTable.tsx +6 -9
  57. package/src/backend/JsonDisplay.tsx +4 -1
  58. package/src/backend/__tests__/component-replacement.test.tsx +1 -1
  59. package/src/backend/confirm-dialog/useConfirmDialog.tsx +4 -3
  60. package/src/backend/dashboard/DashboardScreen.tsx +9 -6
  61. package/src/backend/dashboard/widgetRegistry.ts +4 -1
  62. package/src/backend/detail/CustomDataSection.tsx +5 -2
  63. package/src/backend/detail/InlineEditors.tsx +4 -1
  64. package/src/backend/detail/TagsSection.tsx +4 -1
  65. package/src/backend/injection/InjectionSpot.tsx +11 -8
  66. package/src/backend/injection/eventBridge.ts +4 -1
  67. package/src/backend/injection/useGuardedMutation.ts +4 -1
  68. package/src/backend/injection/useInjectionDataWidgets.ts +4 -1
  69. package/src/backend/injection/useRegisteredComponent.tsx +7 -9
  70. package/src/backend/injection/widgetRegistry.ts +4 -1
  71. package/src/backend/inputs/LookupSelect.tsx +4 -1
  72. package/src/backend/notifications/NotificationDispatcher.ts +5 -2
  73. package/src/backend/sidebar/SidebarCustomizationEditor.tsx +8 -5
  74. package/src/backend/utils/__tests__/bulkDelete.test.ts +29 -23
  75. package/src/backend/utils/api.ts +4 -2
  76. package/src/backend/utils/bulkDelete.ts +5 -2
  77. package/src/backend/version-history/VersionHistoryPanel.tsx +5 -2
  78. package/src/portal/hooks/usePortalDashboardWidgets.ts +4 -1
  79. package/src/portal/hooks/usePortalEventBridge.ts +4 -1
  80. package/src/primitives/Notice.tsx +4 -4
  81. package/src/theme/ThemeProvider.tsx +5 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/detail/TagsSection.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Pencil, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\n\nexport type TagOption = {\n id: string\n label: string\n color?: string | null\n}\n\nexport type TagsSectionLabels = {\n loading: string\n placeholder: string\n empty: string\n loadError: string\n createError: string\n updateError: string\n labelRequired: string\n saveShortcut: string\n cancelShortcut: string\n edit?: string\n cancel?: string\n success?: string\n saving?: string\n autoSaveHint?: string\n}\n\nexport type TagsSectionController = {\n flush: () => Promise<void>\n}\n\nexport type TagsSectionProps = {\n title: string\n tags: TagOption[]\n onChange?: (next: TagOption[]) => void\n isSubmitting?: boolean\n canEdit?: boolean\n autoSave?: boolean\n controllerRef?: React.MutableRefObject<TagsSectionController | null>\n loadOptions: (query?: string) => Promise<TagOption[]>\n createTag: (label: string) => Promise<TagOption>\n onSave: (params: {\n next: TagOption[]\n added: TagOption[]\n removed: TagOption[]\n }) => Promise<void>\n labels: TagsSectionLabels\n}\n\nfunction normalizeTagLabels(labels: string[]): string[] {\n return Array.from(\n new Set(\n labels\n .map((label) => label.trim().toLowerCase())\n .filter((label) => label.length > 0),\n ),\n ).sort((a, b) => a.localeCompare(b))\n}\n\nfunction buildTagLabelKey(labels: string[]): string {\n return normalizeTagLabels(labels).join('\\0')\n}\n\nfunction TagsSectionImpl({\n title,\n tags,\n onChange,\n isSubmitting = false,\n canEdit = true,\n autoSave = false,\n loadOptions,\n createTag,\n onSave,\n labels,\n controllerRef,\n}: TagsSectionProps) {\n const [editing, setEditing] = React.useState(autoSave)\n const [draft, setDraft] = React.useState<string[]>([])\n const [saving, setSaving] = React.useState(false)\n const [, setOptions] = React.useState<Map<string, TagOption>>(() => new Map())\n const [loadingOptions, setLoadingOptions] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const draftRef = React.useRef<string[]>([])\n const savedTagsRef = React.useRef<TagOption[]>(tags)\n const optionsRef = React.useRef<Map<string, TagOption>>(new Map())\n const saveTaskRef = React.useRef<Promise<void> | null>(null)\n React.useEffect(() => {\n savedTagsRef.current = tags\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of tags) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [tags])\n\n React.useEffect(() => {\n draftRef.current = draft\n }, [draft])\n\n const syncFetchedOptions = React.useCallback((fetched: TagOption[]) => {\n if (!fetched.length) return\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of fetched) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [])\n\n const hasPendingDraftChanges = React.useCallback(() => {\n if (!autoSave || !autoSaveUserEditedRef.current) return false\n const draftLabels = normalizeTagLabels(draftRef.current)\n const savedLabels = normalizeTagLabels(savedTagsRef.current.map((tag) => tag.label))\n if (draftLabels.length !== savedLabels.length) return true\n return draftLabels.some((label, index) => label !== savedLabels[index])\n }, [autoSave])\n\n const loadSuggestions = React.useCallback(\n async (query?: string) => {\n try {\n const fetched = await loadOptions(query)\n syncFetchedOptions(fetched)\n return fetched.map((tag) => tag.label)\n } catch (err) {\n console.error('tags.section.loadSuggestions', err)\n return []\n }\n },\n [loadOptions, syncFetchedOptions],\n )\n\n const startEditing = React.useCallback(async () => {\n if (editing || isSubmitting || !canEdit) return\n setError(null)\n setDraft(tags.map((tag) => tag.label))\n setEditing(true)\n setLoadingOptions(true)\n try {\n const fetched = await loadOptions()\n syncFetchedOptions(fetched)\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.loadError\n setError(message)\n flash(message, 'error')\n } finally {\n setLoadingOptions(false)\n }\n }, [canEdit, editing, isSubmitting, labels.loadError, loadOptions, syncFetchedOptions, tags])\n\n const cancelEditing = React.useCallback(() => {\n setEditing(false)\n setDraft([])\n setError(null)\n }, [])\n\n const ensureTagOption = React.useCallback(\n async (label: string): Promise<TagOption> => {\n const normalized = label.trim()\n if (!normalized.length) {\n throw new Error(labels.labelRequired)\n }\n const existing = optionsRef.current.get(normalized.toLowerCase())\n if (existing) return existing\n try {\n const created = await createTag(normalized)\n setOptions((prev) => {\n const next = new Map(prev)\n next.set(created.label.toLowerCase(), created)\n optionsRef.current = next\n return next\n })\n return created\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.createError\n throw new Error(message)\n }\n },\n [createTag, labels.createError, labels.labelRequired],\n )\n\n const handleSave = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !hasPendingDraftChanges()) return\n }\n if (autoSave && !hasPendingDraftChanges()) {\n autoSaveUserEditedRef.current = false\n return\n }\n\n const trimmed = draftRef.current.map((label) => label.trim()).filter((label) => label.length > 0)\n const uniqueLabels = Array.from(new Set(trimmed.map((label) => label.toLowerCase())))\n\n let shouldContinueAutoSave = false\n\n const task = (async () => {\n const currentTags = savedTagsRef.current\n const currentIds = new Set(currentTags.map((tag) => tag.id))\n const submittedDraftKey = buildTagLabelKey(trimmed)\n\n setSaving(true)\n setError(null)\n try {\n const finalTagOptions = await Promise.all(\n uniqueLabels.map((normalized) => {\n const existing = optionsRef.current.get(normalized)\n if (existing) return existing\n const matchingLabel = trimmed.find((label) => label.toLowerCase() === normalized) ?? normalized\n return ensureTagOption(matchingLabel)\n }),\n )\n\n const finalIds = new Set(finalTagOptions.map((tag) => tag.id))\n const added = finalTagOptions.filter((tag) => !currentIds.has(tag.id))\n const removed = currentTags.filter((tag) => !finalIds.has(tag.id))\n\n if (added.length > 0 || removed.length > 0) {\n await onSave({ next: finalTagOptions, added, removed })\n }\n\n savedTagsRef.current = finalTagOptions\n onChange?.(finalTagOptions)\n if (autoSave) {\n const latestDraftKey = buildTagLabelKey(draftRef.current)\n if (latestDraftKey === submittedDraftKey) {\n autoSaveUserEditedRef.current = false\n setDraft(finalTagOptions.map((tag) => tag.label))\n } else {\n shouldContinueAutoSave = true\n }\n } else {\n setEditing(false)\n setDraft([])\n }\n if (labels.success && (added.length > 0 || removed.length > 0)) flash(labels.success, 'success')\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.updateError\n setError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n })()\n\n saveTaskRef.current = task\n try {\n await task\n } finally {\n saveTaskRef.current = null\n }\n if (shouldContinueAutoSave) {\n void handleSave()\n }\n }, [autoSave, ensureTagOption, hasPendingDraftChanges, labels.success, labels.updateError, onChange, onSave])\n\n const activeTags = editing ? draft : tags.map((tag) => tag.label)\n\n const handleEditingKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (!editing) return\n if (event.key === 'Escape') {\n event.preventDefault()\n cancelEditing()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (saving || isSubmitting) return\n void handleSave()\n }\n },\n [cancelEditing, editing, handleSave, isSubmitting, saving],\n )\n\n const autoSaveInitializedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!autoSave || autoSaveInitializedRef.current) return\n autoSaveInitializedRef.current = true\n setEditing(true)\n setDraft(tags.map((tag) => tag.label))\n let cancelled = false\n loadOptions().then((fetched) => {\n if (cancelled) return\n syncFetchedOptions(fetched)\n }).catch(() => {})\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoSave])\n\n const autoSaveUserEditedRef = React.useRef(false)\n const originalOnChange = React.useCallback(\n (values: string[]) => {\n autoSaveUserEditedRef.current = true\n setDraft(values)\n },\n [],\n )\n\n React.useEffect(() => {\n if (!autoSave || !editing || !autoSaveUserEditedRef.current) return\n if (!hasPendingDraftChanges()) return\n void handleSave()\n }, [autoSave, draft, editing, handleSave, hasPendingDraftChanges])\n\n const flush = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n }\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n await handleSave()\n }, [autoSave, editing, handleSave, hasPendingDraftChanges])\n\n React.useEffect(() => {\n if (!controllerRef) return\n controllerRef.current = { flush }\n return () => {\n if (controllerRef.current?.flush === flush) {\n controllerRef.current = null\n }\n }\n }, [controllerRef, flush])\n\n const disableInteraction = isSubmitting || !canEdit\n const autoSaveStatusLabel = saving\n ? (labels.saving ?? 'Saving\u2026')\n : (labels.autoSaveHint ?? null)\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between group\">\n <h2 className=\"text-sm font-semibold\">\n {title}\n </h2>\n {autoSave ? (\n autoSaveStatusLabel ? <p className=\"text-xs text-muted-foreground\">{autoSaveStatusLabel}</p> : null\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={editing ? cancelEditing : startEditing}\n disabled={disableInteraction || saving}\n className={\n editing\n ? 'opacity-100 transition-opacity duration-150'\n : 'opacity-100 md:opacity-0 transition-opacity duration-150 md:group-hover:opacity-100 focus-visible:opacity-100'\n }\n >\n {editing ? <X className=\"h-4 w-4\" /> : <Pencil className=\"h-4 w-4\" />}\n <span className=\"sr-only\">\n {editing ? labels.cancel ?? 'Cancel' : labels.edit ?? 'Edit'}\n </span>\n </Button>\n )}\n </div>\n\n {editing ? (\n <div className=\"rounded-lg border bg-card p-4 space-y-3\">\n <DataLoader\n isLoading={loadingOptions}\n loadingMessage={labels.loading}\n spinnerSize=\"sm\"\n >\n <div className=\"space-y-3\" onKeyDown={handleEditingKeyDown}>\n <TagsInput\n value={activeTags}\n onChange={autoSave ? originalOnChange : (values) => setDraft(values)}\n placeholder={labels.placeholder}\n loadSuggestions={loadSuggestions}\n autoFocus={!autoSave}\n />\n {error ? <p className=\"text-xs text-red-600\">{error}</p> : null}\n {autoSave ? null : (\n <div className=\"flex items-center gap-2 mt-3 mb-2\">\n <Button type=\"button\" size=\"sm\" onClick={handleSave} disabled={saving || isSubmitting}>\n {saving ? (\n <span className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-background border-t-primary\" />\n ) : null}\n {labels.saveShortcut}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={cancelEditing}\n disabled={saving || isSubmitting}\n >\n {labels.cancelShortcut}\n </Button>\n </div>\n )}\n </div>\n </DataLoader>\n </div>\n ) : (\n <div\n className=\"group/tags relative rounded-lg border bg-muted/30 p-4 transition-colors hover:border-primary/40 focus-visible:border-primary focus-visible:outline-none\"\n role={disableInteraction ? undefined : 'button'}\n tabIndex={disableInteraction ? -1 : 0}\n onClick={disableInteraction ? undefined : startEditing}\n onKeyDown={(event) => {\n if (disableInteraction) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n void startEditing()\n }\n }}\n >\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-3 top-3 text-muted-foreground opacity-0 transition-opacity duration-150 group-hover/tags:opacity-100 group-focus-within/tags:opacity-100\"\n >\n <Pencil className=\"h-4 w-4\" />\n </span>\n {tags.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {labels.empty}\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {tags.map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium\"\n style={tag.color ? { borderColor: tag.color, color: tag.color } : undefined}\n >\n {tag.label}\n </span>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n\nexport function TagsSection(props: TagsSectionProps) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'TagsSection')\n const Resolved = useRegisteredComponent<TagsSectionProps>(\n handle,\n TagsSectionImpl as React.ComponentType<TagsSectionProps>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n\nexport default TagsSection\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Pencil, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '../injection/useRegisteredComponent'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui')\n\nexport type TagOption = {\n id: string\n label: string\n color?: string | null\n}\n\nexport type TagsSectionLabels = {\n loading: string\n placeholder: string\n empty: string\n loadError: string\n createError: string\n updateError: string\n labelRequired: string\n saveShortcut: string\n cancelShortcut: string\n edit?: string\n cancel?: string\n success?: string\n saving?: string\n autoSaveHint?: string\n}\n\nexport type TagsSectionController = {\n flush: () => Promise<void>\n}\n\nexport type TagsSectionProps = {\n title: string\n tags: TagOption[]\n onChange?: (next: TagOption[]) => void\n isSubmitting?: boolean\n canEdit?: boolean\n autoSave?: boolean\n controllerRef?: React.MutableRefObject<TagsSectionController | null>\n loadOptions: (query?: string) => Promise<TagOption[]>\n createTag: (label: string) => Promise<TagOption>\n onSave: (params: {\n next: TagOption[]\n added: TagOption[]\n removed: TagOption[]\n }) => Promise<void>\n labels: TagsSectionLabels\n}\n\nfunction normalizeTagLabels(labels: string[]): string[] {\n return Array.from(\n new Set(\n labels\n .map((label) => label.trim().toLowerCase())\n .filter((label) => label.length > 0),\n ),\n ).sort((a, b) => a.localeCompare(b))\n}\n\nfunction buildTagLabelKey(labels: string[]): string {\n return normalizeTagLabels(labels).join('\\0')\n}\n\nfunction TagsSectionImpl({\n title,\n tags,\n onChange,\n isSubmitting = false,\n canEdit = true,\n autoSave = false,\n loadOptions,\n createTag,\n onSave,\n labels,\n controllerRef,\n}: TagsSectionProps) {\n const [editing, setEditing] = React.useState(autoSave)\n const [draft, setDraft] = React.useState<string[]>([])\n const [saving, setSaving] = React.useState(false)\n const [, setOptions] = React.useState<Map<string, TagOption>>(() => new Map())\n const [loadingOptions, setLoadingOptions] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const draftRef = React.useRef<string[]>([])\n const savedTagsRef = React.useRef<TagOption[]>(tags)\n const optionsRef = React.useRef<Map<string, TagOption>>(new Map())\n const saveTaskRef = React.useRef<Promise<void> | null>(null)\n React.useEffect(() => {\n savedTagsRef.current = tags\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of tags) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [tags])\n\n React.useEffect(() => {\n draftRef.current = draft\n }, [draft])\n\n const syncFetchedOptions = React.useCallback((fetched: TagOption[]) => {\n if (!fetched.length) return\n setOptions((prev) => {\n const next = new Map(prev)\n for (const tag of fetched) {\n next.set(tag.label.toLowerCase(), tag)\n }\n optionsRef.current = next\n return next\n })\n }, [])\n\n const hasPendingDraftChanges = React.useCallback(() => {\n if (!autoSave || !autoSaveUserEditedRef.current) return false\n const draftLabels = normalizeTagLabels(draftRef.current)\n const savedLabels = normalizeTagLabels(savedTagsRef.current.map((tag) => tag.label))\n if (draftLabels.length !== savedLabels.length) return true\n return draftLabels.some((label, index) => label !== savedLabels[index])\n }, [autoSave])\n\n const loadSuggestions = React.useCallback(\n async (query?: string) => {\n try {\n const fetched = await loadOptions(query)\n syncFetchedOptions(fetched)\n return fetched.map((tag) => tag.label)\n } catch (err) {\n logger.error('Failed to load tag suggestions', { err })\n return []\n }\n },\n [loadOptions, syncFetchedOptions],\n )\n\n const startEditing = React.useCallback(async () => {\n if (editing || isSubmitting || !canEdit) return\n setError(null)\n setDraft(tags.map((tag) => tag.label))\n setEditing(true)\n setLoadingOptions(true)\n try {\n const fetched = await loadOptions()\n syncFetchedOptions(fetched)\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.loadError\n setError(message)\n flash(message, 'error')\n } finally {\n setLoadingOptions(false)\n }\n }, [canEdit, editing, isSubmitting, labels.loadError, loadOptions, syncFetchedOptions, tags])\n\n const cancelEditing = React.useCallback(() => {\n setEditing(false)\n setDraft([])\n setError(null)\n }, [])\n\n const ensureTagOption = React.useCallback(\n async (label: string): Promise<TagOption> => {\n const normalized = label.trim()\n if (!normalized.length) {\n throw new Error(labels.labelRequired)\n }\n const existing = optionsRef.current.get(normalized.toLowerCase())\n if (existing) return existing\n try {\n const created = await createTag(normalized)\n setOptions((prev) => {\n const next = new Map(prev)\n next.set(created.label.toLowerCase(), created)\n optionsRef.current = next\n return next\n })\n return created\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.createError\n throw new Error(message)\n }\n },\n [createTag, labels.createError, labels.labelRequired],\n )\n\n const handleSave = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !hasPendingDraftChanges()) return\n }\n if (autoSave && !hasPendingDraftChanges()) {\n autoSaveUserEditedRef.current = false\n return\n }\n\n const trimmed = draftRef.current.map((label) => label.trim()).filter((label) => label.length > 0)\n const uniqueLabels = Array.from(new Set(trimmed.map((label) => label.toLowerCase())))\n\n let shouldContinueAutoSave = false\n\n const task = (async () => {\n const currentTags = savedTagsRef.current\n const currentIds = new Set(currentTags.map((tag) => tag.id))\n const submittedDraftKey = buildTagLabelKey(trimmed)\n\n setSaving(true)\n setError(null)\n try {\n const finalTagOptions = await Promise.all(\n uniqueLabels.map((normalized) => {\n const existing = optionsRef.current.get(normalized)\n if (existing) return existing\n const matchingLabel = trimmed.find((label) => label.toLowerCase() === normalized) ?? normalized\n return ensureTagOption(matchingLabel)\n }),\n )\n\n const finalIds = new Set(finalTagOptions.map((tag) => tag.id))\n const added = finalTagOptions.filter((tag) => !currentIds.has(tag.id))\n const removed = currentTags.filter((tag) => !finalIds.has(tag.id))\n\n if (added.length > 0 || removed.length > 0) {\n await onSave({ next: finalTagOptions, added, removed })\n }\n\n savedTagsRef.current = finalTagOptions\n onChange?.(finalTagOptions)\n if (autoSave) {\n const latestDraftKey = buildTagLabelKey(draftRef.current)\n if (latestDraftKey === submittedDraftKey) {\n autoSaveUserEditedRef.current = false\n setDraft(finalTagOptions.map((tag) => tag.label))\n } else {\n shouldContinueAutoSave = true\n }\n } else {\n setEditing(false)\n setDraft([])\n }\n if (labels.success && (added.length > 0 || removed.length > 0)) flash(labels.success, 'success')\n } catch (err) {\n const message = err instanceof Error ? err.message : labels.updateError\n setError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n })()\n\n saveTaskRef.current = task\n try {\n await task\n } finally {\n saveTaskRef.current = null\n }\n if (shouldContinueAutoSave) {\n void handleSave()\n }\n }, [autoSave, ensureTagOption, hasPendingDraftChanges, labels.success, labels.updateError, onChange, onSave])\n\n const activeTags = editing ? draft : tags.map((tag) => tag.label)\n\n const handleEditingKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (!editing) return\n if (event.key === 'Escape') {\n event.preventDefault()\n cancelEditing()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (saving || isSubmitting) return\n void handleSave()\n }\n },\n [cancelEditing, editing, handleSave, isSubmitting, saving],\n )\n\n const autoSaveInitializedRef = React.useRef(false)\n\n React.useEffect(() => {\n if (!autoSave || autoSaveInitializedRef.current) return\n autoSaveInitializedRef.current = true\n setEditing(true)\n setDraft(tags.map((tag) => tag.label))\n let cancelled = false\n loadOptions().then((fetched) => {\n if (cancelled) return\n syncFetchedOptions(fetched)\n }).catch(() => {})\n return () => { cancelled = true }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoSave])\n\n const autoSaveUserEditedRef = React.useRef(false)\n const originalOnChange = React.useCallback(\n (values: string[]) => {\n autoSaveUserEditedRef.current = true\n setDraft(values)\n },\n [],\n )\n\n React.useEffect(() => {\n if (!autoSave || !editing || !autoSaveUserEditedRef.current) return\n if (!hasPendingDraftChanges()) return\n void handleSave()\n }, [autoSave, draft, editing, handleSave, hasPendingDraftChanges])\n\n const flush = React.useCallback(async () => {\n if (saveTaskRef.current) {\n await saveTaskRef.current\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n }\n if (!autoSave || !editing || !hasPendingDraftChanges()) return\n await handleSave()\n }, [autoSave, editing, handleSave, hasPendingDraftChanges])\n\n React.useEffect(() => {\n if (!controllerRef) return\n controllerRef.current = { flush }\n return () => {\n if (controllerRef.current?.flush === flush) {\n controllerRef.current = null\n }\n }\n }, [controllerRef, flush])\n\n const disableInteraction = isSubmitting || !canEdit\n const autoSaveStatusLabel = saving\n ? (labels.saving ?? 'Saving\u2026')\n : (labels.autoSaveHint ?? null)\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between group\">\n <h2 className=\"text-sm font-semibold\">\n {title}\n </h2>\n {autoSave ? (\n autoSaveStatusLabel ? <p className=\"text-xs text-muted-foreground\">{autoSaveStatusLabel}</p> : null\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={editing ? cancelEditing : startEditing}\n disabled={disableInteraction || saving}\n className={\n editing\n ? 'opacity-100 transition-opacity duration-150'\n : 'opacity-100 md:opacity-0 transition-opacity duration-150 md:group-hover:opacity-100 focus-visible:opacity-100'\n }\n >\n {editing ? <X className=\"h-4 w-4\" /> : <Pencil className=\"h-4 w-4\" />}\n <span className=\"sr-only\">\n {editing ? labels.cancel ?? 'Cancel' : labels.edit ?? 'Edit'}\n </span>\n </Button>\n )}\n </div>\n\n {editing ? (\n <div className=\"rounded-lg border bg-card p-4 space-y-3\">\n <DataLoader\n isLoading={loadingOptions}\n loadingMessage={labels.loading}\n spinnerSize=\"sm\"\n >\n <div className=\"space-y-3\" onKeyDown={handleEditingKeyDown}>\n <TagsInput\n value={activeTags}\n onChange={autoSave ? originalOnChange : (values) => setDraft(values)}\n placeholder={labels.placeholder}\n loadSuggestions={loadSuggestions}\n autoFocus={!autoSave}\n />\n {error ? <p className=\"text-xs text-red-600\">{error}</p> : null}\n {autoSave ? null : (\n <div className=\"flex items-center gap-2 mt-3 mb-2\">\n <Button type=\"button\" size=\"sm\" onClick={handleSave} disabled={saving || isSubmitting}>\n {saving ? (\n <span className=\"mr-2 h-4 w-4 animate-spin rounded-full border border-background border-t-primary\" />\n ) : null}\n {labels.saveShortcut}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={cancelEditing}\n disabled={saving || isSubmitting}\n >\n {labels.cancelShortcut}\n </Button>\n </div>\n )}\n </div>\n </DataLoader>\n </div>\n ) : (\n <div\n className=\"group/tags relative rounded-lg border bg-muted/30 p-4 transition-colors hover:border-primary/40 focus-visible:border-primary focus-visible:outline-none\"\n role={disableInteraction ? undefined : 'button'}\n tabIndex={disableInteraction ? -1 : 0}\n onClick={disableInteraction ? undefined : startEditing}\n onKeyDown={(event) => {\n if (disableInteraction) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n void startEditing()\n }\n }}\n >\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute right-3 top-3 text-muted-foreground opacity-0 transition-opacity duration-150 group-hover/tags:opacity-100 group-focus-within/tags:opacity-100\"\n >\n <Pencil className=\"h-4 w-4\" />\n </span>\n {tags.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {labels.empty}\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {tags.map((tag) => (\n <span\n key={tag.id}\n className=\"inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium\"\n style={tag.color ? { borderColor: tag.color, color: tag.color } : undefined}\n >\n {tag.label}\n </span>\n ))}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n\nexport function TagsSection(props: TagsSectionProps) {\n const handle = ComponentReplacementHandles.section('ui.detail', 'TagsSection')\n const Resolved = useRegisteredComponent<TagsSectionProps>(\n handle,\n TagsSectionImpl as React.ComponentType<TagsSectionProps>,\n )\n\n return (\n <div data-component-handle={handle}>\n <Resolved {...props} />\n </div>\n )\n}\n\nexport default TagsSection\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,8 @@ import {
7
7
  subscribeToInjectionRegistryChanges
8
8
  } from "@open-mercato/shared/modules/widgets/injection-loader";
9
9
  import { getWidgetSharedState } from "./WidgetSharedState.js";
10
+ import { createLogger } from "@open-mercato/shared/lib/logger";
11
+ const logger = createLogger("ui").child({ component: "InjectionSpot" });
10
12
  const TRANSFORMER_EVENTS = /* @__PURE__ */ new Set([
11
13
  "transformFormData",
12
14
  "transformDisplayData",
@@ -75,14 +77,14 @@ function useInjectionWidgets(spotId, options) {
75
77
  await widget.module.eventHandlers.onLoad(widgetContext);
76
78
  onEventRef.current?.("onLoad", widget.widgetId);
77
79
  } catch (err) {
78
- console.error(`[InjectionSpot] Error in onLoad for widget ${widget.widgetId}:`, err);
80
+ logger.error("Error in onLoad for widget", { widgetId: widget.widgetId, err });
79
81
  }
80
82
  }
81
83
  }
82
84
  }
83
85
  } catch (err) {
84
86
  if (!mounted) return;
85
- console.error(`[InjectionSpot] Failed to load widgets for spot ${spotId}:`, err);
87
+ logger.error("Failed to load widgets for spot", { spotId, err });
86
88
  setError(err instanceof Error ? err.message : String(err));
87
89
  } finally {
88
90
  if (mounted) setLoading(false);
@@ -126,7 +128,7 @@ function InjectionSpot({
126
128
  return null;
127
129
  }
128
130
  if (effectiveError) {
129
- console.error(`[InjectionSpot] Error loading widgets for spot ${spotId}:`, effectiveError);
131
+ logger.error("Error loading widgets for spot", { spotId, err: effectiveError });
130
132
  return null;
131
133
  }
132
134
  if (effectiveWidgets.length === 0) {
@@ -174,7 +176,7 @@ function useInjectionSpotEvents(spotId, prefetchedWidgets) {
174
176
  }))
175
177
  );
176
178
  } catch (err) {
177
- console.error(`[useInjectionSpotEvents] Failed to load widgets for spot ${spotId}:`, err);
179
+ logger.error("Failed to load widgets for spot", { hook: "useInjectionSpotEvents", spotId, err });
178
180
  }
179
181
  };
180
182
  load();
@@ -237,7 +239,7 @@ function useInjectionSpotEvents(spotId, prefetchedWidgets) {
237
239
  pipelineData = handlerResult;
238
240
  }
239
241
  } catch (err) {
240
- console.error(`[useInjectionSpotEvents] Error in ${event} for widget ${widget.widgetId}:`, err);
242
+ logger.error("Error in event handler for widget", { hook: "useInjectionSpotEvents", event, widgetId: widget.widgetId, err });
241
243
  }
242
244
  }
243
245
  return { ok: true, data: pipelineData, applyToForm };
@@ -267,7 +269,7 @@ function useInjectionSpotEvents(spotId, prefetchedWidgets) {
267
269
  if (event === "onBeforeSave") {
268
270
  const normalized = normalizeBeforeSave(result);
269
271
  if (!normalized.ok) {
270
- console.log(`[useInjectionSpotEvents] Widget ${widget.widgetId} prevented ${event}`);
272
+ logger.info("Widget prevented event", { hook: "useInjectionSpotEvents", widgetId: widget.widgetId, event });
271
273
  return normalized;
272
274
  }
273
275
  if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {
@@ -278,7 +280,7 @@ function useInjectionSpotEvents(spotId, prefetchedWidgets) {
278
280
  if (event === "onBeforeDelete") {
279
281
  const normalized = normalizeBeforeDelete(result);
280
282
  if (!normalized.ok) {
281
- console.log(`[useInjectionSpotEvents] Widget ${widget.widgetId} prevented ${event}`);
283
+ logger.info("Widget prevented event", { hook: "useInjectionSpotEvents", widgetId: widget.widgetId, event });
282
284
  return normalized;
283
285
  }
284
286
  if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {
@@ -305,7 +307,7 @@ function useInjectionSpotEvents(spotId, prefetchedWidgets) {
305
307
  }
306
308
  }
307
309
  } catch (err) {
308
- console.error(`[useInjectionSpotEvents] Error in ${event} for widget ${widget.widgetId}:`, err);
310
+ logger.error("Error in event handler for widget", { hook: "useInjectionSpotEvents", event, widgetId: widget.widgetId, err });
309
311
  if (event === "onBeforeSave" || event === "onBeforeDelete" || event === "onBeforeNavigate") {
310
312
  const message = err instanceof Error ? err.message || "Validation blocked" : typeof err === "string" ? err : void 0;
311
313
  return { ok: false, message };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/InjectionSpot.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport type {\n InjectionSpotId,\n InjectionWidgetModule,\n WidgetInjectionEventHandlers,\n WidgetBeforeDeleteResult,\n WidgetBeforeSaveResult,\n FieldChangeResult,\n NavigateGuardResult,\n} from '@open-mercato/shared/modules/widgets/injection'\nimport {\n getInjectionRegistryVersion,\n loadInjectionWidgetsForSpot,\n subscribeToInjectionRegistryChanges,\n type LoadedInjectionWidget,\n} from '@open-mercato/shared/modules/widgets/injection-loader'\nimport { getWidgetSharedState } from './WidgetSharedState'\n\nexport type InjectionSpotProps<TContext = unknown, TData = unknown> = {\n spotId: InjectionSpotId\n context: TContext\n data?: TData\n onDataChange?: (data: TData) => void\n disabled?: boolean\n onEvent?: (\n event: keyof WidgetInjectionEventHandlers<TContext, TData>,\n widgetId: string,\n ) => void\n widgetsOverride?: LoadedWidget[]\n}\n\n/**\n * Transformer events use pipeline dispatch: output of widget N becomes input of widget N+1.\n */\nconst TRANSFORMER_EVENTS = new Set<string>([\n 'transformFormData',\n 'transformDisplayData',\n 'transformValidation',\n])\n\ntype LoadedWidget = {\n widgetId: string\n module: InjectionWidgetModule<any, any>\n moduleId: string\n key: string\n placement?: LoadedInjectionWidget['placement']\n}\n\nexport type LoadedInjectionSpotWidget = LoadedWidget\n\nfunction injectSharedStateIntoContext<TContext>(context: TContext, moduleId: string): TContext {\n const sharedState = getWidgetSharedState(moduleId)\n if (typeof context === 'object' && context !== null && !Array.isArray(context)) {\n return {\n ...(context as Record<string, unknown>),\n sharedState,\n } as TContext\n }\n return {\n value: context,\n sharedState,\n } as TContext\n}\n\nexport function useInjectionWidgets<TContext = unknown>(\n spotId: InjectionSpotId | null | undefined,\n options?: {\n context?: TContext\n triggerOnLoad?: boolean\n onEvent?: (event: 'onLoad', widgetId: string) => void\n }\n) {\n const [widgets, setWidgets] = React.useState<LoadedWidget[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [registryVersion, setRegistryVersion] = React.useState(() => getInjectionRegistryVersion())\n const loadedRef = React.useRef(false)\n const contextRef = React.useRef(options?.context)\n const triggerOnLoadRef = React.useRef(options?.triggerOnLoad)\n const onEventRef = React.useRef(options?.onEvent)\n\n React.useEffect(() => {\n contextRef.current = options?.context\n triggerOnLoadRef.current = options?.triggerOnLoad\n onEventRef.current = options?.onEvent\n })\n\n React.useEffect(() => {\n return subscribeToInjectionRegistryChanges(() => {\n setRegistryVersion(getInjectionRegistryVersion())\n })\n }, [])\n\n React.useEffect(() => {\n if (!spotId) {\n setWidgets([])\n setLoading(false)\n setError(null)\n return\n }\n let mounted = true\n const load = async () => {\n try {\n setLoading(true)\n setError(null)\n const loaded = await loadInjectionWidgetsForSpot(spotId)\n if (!mounted) return\n const widgetList: LoadedWidget[] = loaded.map((w) => ({\n widgetId: w.metadata.id,\n module: w,\n moduleId: w.moduleId,\n key: w.key,\n placement: w.placement,\n }))\n setWidgets(widgetList)\n\n // Trigger onLoad for all widgets\n if (!loadedRef.current && triggerOnLoadRef.current) {\n loadedRef.current = true\n for (const widget of widgetList) {\n if (widget.module.eventHandlers?.onLoad) {\n try {\n const widgetContext = injectSharedStateIntoContext(contextRef.current as TContext, widget.moduleId)\n await widget.module.eventHandlers.onLoad(widgetContext)\n onEventRef.current?.('onLoad', widget.widgetId)\n } catch (err) {\n console.error(`[InjectionSpot] Error in onLoad for widget ${widget.widgetId}:`, err)\n }\n }\n }\n }\n } catch (err) {\n if (!mounted) return\n console.error(`[InjectionSpot] Failed to load widgets for spot ${spotId}:`, err)\n setError(err instanceof Error ? err.message : String(err))\n } finally {\n if (mounted) setLoading(false)\n }\n }\n load()\n return () => {\n mounted = false\n }\n // context/triggerOnLoad/onEvent are read from refs so only a real registry-version bump reloads the spot\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [spotId, registryVersion])\n\n return { widgets, loading, error }\n}\n\nexport function InjectionSpot<TContext = unknown, TData = unknown>({\n spotId,\n context,\n data,\n onDataChange,\n disabled,\n onEvent,\n widgetsOverride,\n}: InjectionSpotProps<TContext, TData>) {\n const useSpotId = widgetsOverride ? null : spotId\n const onEventRef = React.useRef(onEvent)\n React.useEffect(() => {\n onEventRef.current = onEvent\n })\n const hasOnEvent = Boolean(onEvent)\n const stableOnEvent = React.useMemo(\n () => (hasOnEvent ? (event: 'onLoad', id: string) => onEventRef.current?.(event, id) : undefined),\n [hasOnEvent],\n )\n const { widgets, loading, error } = useInjectionWidgets<TContext>(useSpotId, {\n context,\n triggerOnLoad: !widgetsOverride,\n onEvent: stableOnEvent,\n })\n const effectiveWidgets = widgetsOverride ?? widgets\n const effectiveLoading = widgetsOverride ? false : loading\n const effectiveError = widgetsOverride ? null : error\n\n if (effectiveLoading && effectiveWidgets.length === 0) {\n return null\n }\n\n if (effectiveError) {\n console.error(`[InjectionSpot] Error loading widgets for spot ${spotId}:`, effectiveError)\n return null\n }\n\n if (effectiveWidgets.length === 0) {\n return null\n }\n\n return (\n <>\n {effectiveWidgets.map((widget) => {\n const { Widget } = widget.module\n return (\n <Widget\n key={widget.widgetId}\n context={injectSharedStateIntoContext(context, widget.moduleId)}\n data={data}\n onDataChange={onDataChange}\n disabled={disabled}\n />\n )\n })}\n </>\n )\n}\n\n/**\n * Hook to trigger injection widget events imperatively\n */\nexport function useInjectionSpotEvents<TContext = unknown, TData = unknown>(spotId: InjectionSpotId, prefetchedWidgets?: LoadedWidget[]) {\n const [widgets, setWidgets] = React.useState<LoadedWidget[]>([])\n const [registryVersion, setRegistryVersion] = React.useState(() => getInjectionRegistryVersion())\n\n React.useEffect(() => {\n return subscribeToInjectionRegistryChanges(() => {\n setRegistryVersion(getInjectionRegistryVersion())\n })\n }, [])\n\n React.useEffect(() => {\n if (prefetchedWidgets && prefetchedWidgets.length) {\n setWidgets(prefetchedWidgets)\n return\n }\n let mounted = true\n const load = async () => {\n try {\n const loaded = await loadInjectionWidgetsForSpot(spotId)\n if (!mounted) return\n setWidgets(\n loaded.map((w) => ({\n widgetId: w.metadata.id,\n module: w,\n moduleId: w.moduleId,\n key: w.key,\n placement: w.placement,\n }))\n )\n } catch (err) {\n console.error(`[useInjectionSpotEvents] Failed to load widgets for spot ${spotId}:`, err)\n }\n }\n load()\n return () => {\n mounted = false\n }\n }, [spotId, prefetchedWidgets, registryVersion])\n\n const triggerEvent = React.useCallback(\n async (\n event: keyof WidgetInjectionEventHandlers<TContext, TData>,\n data: TData,\n context: TContext,\n meta?: {\n error?: unknown\n fieldId?: string\n fieldValue?: unknown\n originalData?: TData\n target?: unknown\n visible?: boolean\n appEvent?: unknown\n }\n ): Promise<{\n ok: boolean\n message?: string\n fieldErrors?: Record<string, string>\n requestHeaders?: Record<string, string>\n details?: unknown\n data?: TData\n applyToForm?: boolean\n fieldChange?: {\n value?: unknown\n sideEffects?: Record<string, unknown>\n messages?: Array<{ text: string; severity: 'info' | 'warning' | 'error' }>\n }\n }> => {\n const normalizeBeforeSave = (\n result: WidgetBeforeSaveResult,\n ): { ok: boolean; message?: string; fieldErrors?: Record<string, string>; requestHeaders?: Record<string, string>; details?: unknown } => {\n if (result === false) return { ok: false }\n if (result === true || typeof result === 'undefined') return { ok: true }\n if (result && typeof result === 'object') {\n const ok = typeof result.ok === 'boolean' ? result.ok : true\n const message = typeof result.message === 'string' ? result.message : undefined\n const fieldErrors =\n result.fieldErrors && typeof result.fieldErrors === 'object'\n ? Object.fromEntries(\n Object.entries(result.fieldErrors).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n const requestHeaders =\n result.requestHeaders && typeof result.requestHeaders === 'object'\n ? Object.fromEntries(\n Object.entries(result.requestHeaders).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n return { ok, message, fieldErrors, requestHeaders, details: result.details }\n }\n return { ok: true }\n }\n\n const normalizeBeforeDelete = (\n result: WidgetBeforeDeleteResult,\n ): { ok: boolean; message?: string; fieldErrors?: Record<string, string>; requestHeaders?: Record<string, string>; details?: unknown } => {\n if (result === false) return { ok: false }\n if (result === true || typeof result === 'undefined') return { ok: true }\n if (result && typeof result === 'object') {\n const ok = typeof result.ok === 'boolean' ? result.ok : true\n const message = typeof result.message === 'string' ? result.message : undefined\n const fieldErrors =\n result.fieldErrors && typeof result.fieldErrors === 'object'\n ? Object.fromEntries(\n Object.entries(result.fieldErrors).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n const requestHeaders =\n result.requestHeaders && typeof result.requestHeaders === 'object'\n ? Object.fromEntries(\n Object.entries(result.requestHeaders).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n return { ok, message, fieldErrors, requestHeaders, details: result.details }\n }\n return { ok: true }\n }\n\n // --- Transformer events: pipeline dispatch ---\n // Output of widget N becomes input of widget N+1\n if (TRANSFORMER_EVENTS.has(event)) {\n let pipelineData = data\n let applyToForm = false\n for (const widget of widgets) {\n const handler = widget.module.eventHandlers?.[event]\n if (!handler) continue\n try {\n const widgetContext = injectSharedStateIntoContext(context, widget.moduleId)\n let handlerResult: unknown\n if (event === 'transformValidation') {\n handlerResult = await (handler as any)(pipelineData, meta?.originalData ?? data, widgetContext)\n } else {\n handlerResult = await (handler as any)(pipelineData, widgetContext)\n }\n if (\n event === 'transformFormData' &&\n handlerResult !== null &&\n typeof handlerResult === 'object' &&\n 'applyToForm' in handlerResult &&\n (handlerResult as { applyToForm: unknown }).applyToForm === true &&\n 'data' in handlerResult\n ) {\n pipelineData = (handlerResult as { data: TData }).data\n applyToForm = true\n } else {\n pipelineData = handlerResult as TData\n }\n } catch (err) {\n console.error(`[useInjectionSpotEvents] Error in ${event} for widget ${widget.widgetId}:`, err)\n }\n }\n return { ok: true, data: pipelineData, applyToForm }\n }\n\n // --- Action events: sequential dispatch ---\n const mergedRequestHeaders: Record<string, string> = {}\n let hasRequestHeaders = false\n let fieldValue = meta?.fieldValue\n let fieldSideEffects: Record<string, unknown> | undefined\n let fieldMessages: Array<{ text: string; severity: 'info' | 'warning' | 'error' }> | undefined\n\n for (const widget of widgets) {\n const eventHandlers = widget.module.eventHandlers\n // Check operation filter \u2014 skip widget if current operation is filtered out\n const operationFilter = eventHandlers?.filter?.operations\n if (operationFilter) {\n const currentOperation = (context as Record<string, unknown>)?.operation as string | undefined\n if (currentOperation && !operationFilter.includes(currentOperation as 'create' | 'update' | 'delete')) {\n continue\n }\n }\n let handler = eventHandlers?.[event]\n // Delete-to-save fallback chain\n if (!handler && event === 'onBeforeDelete') handler = eventHandlers?.onBeforeSave as typeof handler\n if (!handler && event === 'onDelete') handler = eventHandlers?.onSave as typeof handler\n if (!handler && event === 'onAfterDelete') handler = eventHandlers?.onAfterSave as typeof handler\n if (handler) {\n try {\n const widgetContext = injectSharedStateIntoContext(context, widget.moduleId)\n const result =\n event === 'onDeleteError'\n ? await (handler as any)(data, widgetContext, meta?.error)\n : event === 'onFieldChange'\n ? await (handler as any)(meta?.fieldId, fieldValue, data, widgetContext)\n : event === 'onBeforeNavigate'\n ? await (handler as any)(meta?.target, widgetContext)\n : event === 'onVisibilityChange'\n ? await (handler as any)(meta?.visible, widgetContext)\n : event === 'onAppEvent'\n ? await (handler as any)(meta?.appEvent, widgetContext)\n : await (handler as any)(data, widgetContext)\n if (event === 'onBeforeSave') {\n const normalized = normalizeBeforeSave(result as WidgetBeforeSaveResult)\n if (!normalized.ok) {\n console.log(`[useInjectionSpotEvents] Widget ${widget.widgetId} prevented ${event}`)\n return normalized\n }\n if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {\n Object.assign(mergedRequestHeaders, normalized.requestHeaders)\n hasRequestHeaders = true\n }\n }\n if (event === 'onBeforeDelete') {\n const normalized = normalizeBeforeDelete(result as WidgetBeforeDeleteResult)\n if (!normalized.ok) {\n console.log(`[useInjectionSpotEvents] Widget ${widget.widgetId} prevented ${event}`)\n return normalized\n }\n if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {\n Object.assign(mergedRequestHeaders, normalized.requestHeaders)\n hasRequestHeaders = true\n }\n }\n if (event === 'onBeforeNavigate') {\n const navResult = result as NavigateGuardResult | undefined\n if (navResult && navResult.ok === false) {\n return { ok: false, message: navResult.message }\n }\n }\n if (event === 'onFieldChange') {\n const changeResult = result as FieldChangeResult | void\n if (changeResult?.value !== undefined) {\n fieldValue = changeResult.value\n }\n if (changeResult?.sideEffects && typeof changeResult.sideEffects === 'object') {\n fieldSideEffects = { ...(fieldSideEffects ?? {}), ...changeResult.sideEffects }\n }\n if (changeResult?.message?.text) {\n fieldMessages = [...(fieldMessages ?? []), changeResult.message]\n }\n }\n } catch (err) {\n console.error(`[useInjectionSpotEvents] Error in ${event} for widget ${widget.widgetId}:`, err)\n if (event === 'onBeforeSave' || event === 'onBeforeDelete' || event === 'onBeforeNavigate') {\n const message =\n err instanceof Error\n ? err.message || 'Validation blocked'\n : typeof err === 'string'\n ? err\n : undefined\n return { ok: false, message }\n }\n }\n }\n }\n if ((event === 'onBeforeSave' || event === 'onBeforeDelete') && hasRequestHeaders) {\n return { ok: true, requestHeaders: mergedRequestHeaders }\n }\n if (event === 'onFieldChange') {\n return {\n ok: true,\n fieldChange: {\n value: fieldValue,\n sideEffects: fieldSideEffects,\n messages: fieldMessages,\n },\n }\n }\n return { ok: true }\n },\n [widgets]\n )\n\n return { triggerEvent, widgets }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport type {\n InjectionSpotId,\n InjectionWidgetModule,\n WidgetInjectionEventHandlers,\n WidgetBeforeDeleteResult,\n WidgetBeforeSaveResult,\n FieldChangeResult,\n NavigateGuardResult,\n} from '@open-mercato/shared/modules/widgets/injection'\nimport {\n getInjectionRegistryVersion,\n loadInjectionWidgetsForSpot,\n subscribeToInjectionRegistryChanges,\n type LoadedInjectionWidget,\n} from '@open-mercato/shared/modules/widgets/injection-loader'\nimport { getWidgetSharedState } from './WidgetSharedState'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'InjectionSpot' })\n\nexport type InjectionSpotProps<TContext = unknown, TData = unknown> = {\n spotId: InjectionSpotId\n context: TContext\n data?: TData\n onDataChange?: (data: TData) => void\n disabled?: boolean\n onEvent?: (\n event: keyof WidgetInjectionEventHandlers<TContext, TData>,\n widgetId: string,\n ) => void\n widgetsOverride?: LoadedWidget[]\n}\n\n/**\n * Transformer events use pipeline dispatch: output of widget N becomes input of widget N+1.\n */\nconst TRANSFORMER_EVENTS = new Set<string>([\n 'transformFormData',\n 'transformDisplayData',\n 'transformValidation',\n])\n\ntype LoadedWidget = {\n widgetId: string\n module: InjectionWidgetModule<any, any>\n moduleId: string\n key: string\n placement?: LoadedInjectionWidget['placement']\n}\n\nexport type LoadedInjectionSpotWidget = LoadedWidget\n\nfunction injectSharedStateIntoContext<TContext>(context: TContext, moduleId: string): TContext {\n const sharedState = getWidgetSharedState(moduleId)\n if (typeof context === 'object' && context !== null && !Array.isArray(context)) {\n return {\n ...(context as Record<string, unknown>),\n sharedState,\n } as TContext\n }\n return {\n value: context,\n sharedState,\n } as TContext\n}\n\nexport function useInjectionWidgets<TContext = unknown>(\n spotId: InjectionSpotId | null | undefined,\n options?: {\n context?: TContext\n triggerOnLoad?: boolean\n onEvent?: (event: 'onLoad', widgetId: string) => void\n }\n) {\n const [widgets, setWidgets] = React.useState<LoadedWidget[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [registryVersion, setRegistryVersion] = React.useState(() => getInjectionRegistryVersion())\n const loadedRef = React.useRef(false)\n const contextRef = React.useRef(options?.context)\n const triggerOnLoadRef = React.useRef(options?.triggerOnLoad)\n const onEventRef = React.useRef(options?.onEvent)\n\n React.useEffect(() => {\n contextRef.current = options?.context\n triggerOnLoadRef.current = options?.triggerOnLoad\n onEventRef.current = options?.onEvent\n })\n\n React.useEffect(() => {\n return subscribeToInjectionRegistryChanges(() => {\n setRegistryVersion(getInjectionRegistryVersion())\n })\n }, [])\n\n React.useEffect(() => {\n if (!spotId) {\n setWidgets([])\n setLoading(false)\n setError(null)\n return\n }\n let mounted = true\n const load = async () => {\n try {\n setLoading(true)\n setError(null)\n const loaded = await loadInjectionWidgetsForSpot(spotId)\n if (!mounted) return\n const widgetList: LoadedWidget[] = loaded.map((w) => ({\n widgetId: w.metadata.id,\n module: w,\n moduleId: w.moduleId,\n key: w.key,\n placement: w.placement,\n }))\n setWidgets(widgetList)\n\n // Trigger onLoad for all widgets\n if (!loadedRef.current && triggerOnLoadRef.current) {\n loadedRef.current = true\n for (const widget of widgetList) {\n if (widget.module.eventHandlers?.onLoad) {\n try {\n const widgetContext = injectSharedStateIntoContext(contextRef.current as TContext, widget.moduleId)\n await widget.module.eventHandlers.onLoad(widgetContext)\n onEventRef.current?.('onLoad', widget.widgetId)\n } catch (err) {\n logger.error('Error in onLoad for widget', { widgetId: widget.widgetId, err })\n }\n }\n }\n }\n } catch (err) {\n if (!mounted) return\n logger.error('Failed to load widgets for spot', { spotId, err })\n setError(err instanceof Error ? err.message : String(err))\n } finally {\n if (mounted) setLoading(false)\n }\n }\n load()\n return () => {\n mounted = false\n }\n // context/triggerOnLoad/onEvent are read from refs so only a real registry-version bump reloads the spot\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [spotId, registryVersion])\n\n return { widgets, loading, error }\n}\n\nexport function InjectionSpot<TContext = unknown, TData = unknown>({\n spotId,\n context,\n data,\n onDataChange,\n disabled,\n onEvent,\n widgetsOverride,\n}: InjectionSpotProps<TContext, TData>) {\n const useSpotId = widgetsOverride ? null : spotId\n const onEventRef = React.useRef(onEvent)\n React.useEffect(() => {\n onEventRef.current = onEvent\n })\n const hasOnEvent = Boolean(onEvent)\n const stableOnEvent = React.useMemo(\n () => (hasOnEvent ? (event: 'onLoad', id: string) => onEventRef.current?.(event, id) : undefined),\n [hasOnEvent],\n )\n const { widgets, loading, error } = useInjectionWidgets<TContext>(useSpotId, {\n context,\n triggerOnLoad: !widgetsOverride,\n onEvent: stableOnEvent,\n })\n const effectiveWidgets = widgetsOverride ?? widgets\n const effectiveLoading = widgetsOverride ? false : loading\n const effectiveError = widgetsOverride ? null : error\n\n if (effectiveLoading && effectiveWidgets.length === 0) {\n return null\n }\n\n if (effectiveError) {\n logger.error('Error loading widgets for spot', { spotId, err: effectiveError })\n return null\n }\n\n if (effectiveWidgets.length === 0) {\n return null\n }\n\n return (\n <>\n {effectiveWidgets.map((widget) => {\n const { Widget } = widget.module\n return (\n <Widget\n key={widget.widgetId}\n context={injectSharedStateIntoContext(context, widget.moduleId)}\n data={data}\n onDataChange={onDataChange}\n disabled={disabled}\n />\n )\n })}\n </>\n )\n}\n\n/**\n * Hook to trigger injection widget events imperatively\n */\nexport function useInjectionSpotEvents<TContext = unknown, TData = unknown>(spotId: InjectionSpotId, prefetchedWidgets?: LoadedWidget[]) {\n const [widgets, setWidgets] = React.useState<LoadedWidget[]>([])\n const [registryVersion, setRegistryVersion] = React.useState(() => getInjectionRegistryVersion())\n\n React.useEffect(() => {\n return subscribeToInjectionRegistryChanges(() => {\n setRegistryVersion(getInjectionRegistryVersion())\n })\n }, [])\n\n React.useEffect(() => {\n if (prefetchedWidgets && prefetchedWidgets.length) {\n setWidgets(prefetchedWidgets)\n return\n }\n let mounted = true\n const load = async () => {\n try {\n const loaded = await loadInjectionWidgetsForSpot(spotId)\n if (!mounted) return\n setWidgets(\n loaded.map((w) => ({\n widgetId: w.metadata.id,\n module: w,\n moduleId: w.moduleId,\n key: w.key,\n placement: w.placement,\n }))\n )\n } catch (err) {\n logger.error('Failed to load widgets for spot', { hook: 'useInjectionSpotEvents', spotId, err })\n }\n }\n load()\n return () => {\n mounted = false\n }\n }, [spotId, prefetchedWidgets, registryVersion])\n\n const triggerEvent = React.useCallback(\n async (\n event: keyof WidgetInjectionEventHandlers<TContext, TData>,\n data: TData,\n context: TContext,\n meta?: {\n error?: unknown\n fieldId?: string\n fieldValue?: unknown\n originalData?: TData\n target?: unknown\n visible?: boolean\n appEvent?: unknown\n }\n ): Promise<{\n ok: boolean\n message?: string\n fieldErrors?: Record<string, string>\n requestHeaders?: Record<string, string>\n details?: unknown\n data?: TData\n applyToForm?: boolean\n fieldChange?: {\n value?: unknown\n sideEffects?: Record<string, unknown>\n messages?: Array<{ text: string; severity: 'info' | 'warning' | 'error' }>\n }\n }> => {\n const normalizeBeforeSave = (\n result: WidgetBeforeSaveResult,\n ): { ok: boolean; message?: string; fieldErrors?: Record<string, string>; requestHeaders?: Record<string, string>; details?: unknown } => {\n if (result === false) return { ok: false }\n if (result === true || typeof result === 'undefined') return { ok: true }\n if (result && typeof result === 'object') {\n const ok = typeof result.ok === 'boolean' ? result.ok : true\n const message = typeof result.message === 'string' ? result.message : undefined\n const fieldErrors =\n result.fieldErrors && typeof result.fieldErrors === 'object'\n ? Object.fromEntries(\n Object.entries(result.fieldErrors).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n const requestHeaders =\n result.requestHeaders && typeof result.requestHeaders === 'object'\n ? Object.fromEntries(\n Object.entries(result.requestHeaders).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n return { ok, message, fieldErrors, requestHeaders, details: result.details }\n }\n return { ok: true }\n }\n\n const normalizeBeforeDelete = (\n result: WidgetBeforeDeleteResult,\n ): { ok: boolean; message?: string; fieldErrors?: Record<string, string>; requestHeaders?: Record<string, string>; details?: unknown } => {\n if (result === false) return { ok: false }\n if (result === true || typeof result === 'undefined') return { ok: true }\n if (result && typeof result === 'object') {\n const ok = typeof result.ok === 'boolean' ? result.ok : true\n const message = typeof result.message === 'string' ? result.message : undefined\n const fieldErrors =\n result.fieldErrors && typeof result.fieldErrors === 'object'\n ? Object.fromEntries(\n Object.entries(result.fieldErrors).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n const requestHeaders =\n result.requestHeaders && typeof result.requestHeaders === 'object'\n ? Object.fromEntries(\n Object.entries(result.requestHeaders).map(([key, value]) => [key, String(value)]),\n )\n : undefined\n return { ok, message, fieldErrors, requestHeaders, details: result.details }\n }\n return { ok: true }\n }\n\n // --- Transformer events: pipeline dispatch ---\n // Output of widget N becomes input of widget N+1\n if (TRANSFORMER_EVENTS.has(event)) {\n let pipelineData = data\n let applyToForm = false\n for (const widget of widgets) {\n const handler = widget.module.eventHandlers?.[event]\n if (!handler) continue\n try {\n const widgetContext = injectSharedStateIntoContext(context, widget.moduleId)\n let handlerResult: unknown\n if (event === 'transformValidation') {\n handlerResult = await (handler as any)(pipelineData, meta?.originalData ?? data, widgetContext)\n } else {\n handlerResult = await (handler as any)(pipelineData, widgetContext)\n }\n if (\n event === 'transformFormData' &&\n handlerResult !== null &&\n typeof handlerResult === 'object' &&\n 'applyToForm' in handlerResult &&\n (handlerResult as { applyToForm: unknown }).applyToForm === true &&\n 'data' in handlerResult\n ) {\n pipelineData = (handlerResult as { data: TData }).data\n applyToForm = true\n } else {\n pipelineData = handlerResult as TData\n }\n } catch (err) {\n logger.error('Error in event handler for widget', { hook: 'useInjectionSpotEvents', event, widgetId: widget.widgetId, err })\n }\n }\n return { ok: true, data: pipelineData, applyToForm }\n }\n\n // --- Action events: sequential dispatch ---\n const mergedRequestHeaders: Record<string, string> = {}\n let hasRequestHeaders = false\n let fieldValue = meta?.fieldValue\n let fieldSideEffects: Record<string, unknown> | undefined\n let fieldMessages: Array<{ text: string; severity: 'info' | 'warning' | 'error' }> | undefined\n\n for (const widget of widgets) {\n const eventHandlers = widget.module.eventHandlers\n // Check operation filter \u2014 skip widget if current operation is filtered out\n const operationFilter = eventHandlers?.filter?.operations\n if (operationFilter) {\n const currentOperation = (context as Record<string, unknown>)?.operation as string | undefined\n if (currentOperation && !operationFilter.includes(currentOperation as 'create' | 'update' | 'delete')) {\n continue\n }\n }\n let handler = eventHandlers?.[event]\n // Delete-to-save fallback chain\n if (!handler && event === 'onBeforeDelete') handler = eventHandlers?.onBeforeSave as typeof handler\n if (!handler && event === 'onDelete') handler = eventHandlers?.onSave as typeof handler\n if (!handler && event === 'onAfterDelete') handler = eventHandlers?.onAfterSave as typeof handler\n if (handler) {\n try {\n const widgetContext = injectSharedStateIntoContext(context, widget.moduleId)\n const result =\n event === 'onDeleteError'\n ? await (handler as any)(data, widgetContext, meta?.error)\n : event === 'onFieldChange'\n ? await (handler as any)(meta?.fieldId, fieldValue, data, widgetContext)\n : event === 'onBeforeNavigate'\n ? await (handler as any)(meta?.target, widgetContext)\n : event === 'onVisibilityChange'\n ? await (handler as any)(meta?.visible, widgetContext)\n : event === 'onAppEvent'\n ? await (handler as any)(meta?.appEvent, widgetContext)\n : await (handler as any)(data, widgetContext)\n if (event === 'onBeforeSave') {\n const normalized = normalizeBeforeSave(result as WidgetBeforeSaveResult)\n if (!normalized.ok) {\n logger.info('Widget prevented event', { hook: 'useInjectionSpotEvents', widgetId: widget.widgetId, event })\n return normalized\n }\n if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {\n Object.assign(mergedRequestHeaders, normalized.requestHeaders)\n hasRequestHeaders = true\n }\n }\n if (event === 'onBeforeDelete') {\n const normalized = normalizeBeforeDelete(result as WidgetBeforeDeleteResult)\n if (!normalized.ok) {\n logger.info('Widget prevented event', { hook: 'useInjectionSpotEvents', widgetId: widget.widgetId, event })\n return normalized\n }\n if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {\n Object.assign(mergedRequestHeaders, normalized.requestHeaders)\n hasRequestHeaders = true\n }\n }\n if (event === 'onBeforeNavigate') {\n const navResult = result as NavigateGuardResult | undefined\n if (navResult && navResult.ok === false) {\n return { ok: false, message: navResult.message }\n }\n }\n if (event === 'onFieldChange') {\n const changeResult = result as FieldChangeResult | void\n if (changeResult?.value !== undefined) {\n fieldValue = changeResult.value\n }\n if (changeResult?.sideEffects && typeof changeResult.sideEffects === 'object') {\n fieldSideEffects = { ...(fieldSideEffects ?? {}), ...changeResult.sideEffects }\n }\n if (changeResult?.message?.text) {\n fieldMessages = [...(fieldMessages ?? []), changeResult.message]\n }\n }\n } catch (err) {\n logger.error('Error in event handler for widget', { hook: 'useInjectionSpotEvents', event, widgetId: widget.widgetId, err })\n if (event === 'onBeforeSave' || event === 'onBeforeDelete' || event === 'onBeforeNavigate') {\n const message =\n err instanceof Error\n ? err.message || 'Validation blocked'\n : typeof err === 'string'\n ? err\n : undefined\n return { ok: false, message }\n }\n }\n }\n }\n if ((event === 'onBeforeSave' || event === 'onBeforeDelete') && hasRequestHeaders) {\n return { ok: true, requestHeaders: mergedRequestHeaders }\n }\n if (event === 'onFieldChange') {\n return {\n ok: true,\n fieldChange: {\n value: fieldValue,\n sideEffects: fieldSideEffects,\n messages: fieldMessages,\n },\n }\n }\n return { ok: true }\n },\n [widgets]\n )\n\n return { triggerEvent, widgets }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
  import { useEffect, useRef } from "react";
3
3
  import { APP_EVENT_DOM_NAME } from "./useAppEvent.js";
4
+ import { createLogger } from "@open-mercato/shared/lib/logger";
5
+ const logger = createLogger("ui").child({ component: "EventBridge" });
4
6
  const SSE_ENDPOINT = "/api/events/stream";
5
7
  const HEARTBEAT_TIMEOUT = 45e3;
6
8
  const RECONNECT_BASE_MS = 1e3;
@@ -37,7 +39,7 @@ function useEventBridge() {
37
39
  function resetHeartbeatTimer() {
38
40
  if (heartbeatTimer.current) clearTimeout(heartbeatTimer.current);
39
41
  heartbeatTimer.current = setTimeout(() => {
40
- console.warn("[EventBridge] Heartbeat timeout \u2014 reconnecting");
42
+ logger.warn("Heartbeat timeout \u2014 reconnecting");
41
43
  disconnect();
42
44
  scheduleReconnect();
43
45
  }, HEARTBEAT_TIMEOUT);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/eventBridge.ts"],
4
- "sourcesContent": ["\"use client\"\nimport { useEffect, useRef } from 'react'\nimport type { AppEventPayload } from '@open-mercato/shared/modules/widgets/injection'\nimport { APP_EVENT_DOM_NAME } from './useAppEvent'\n\nconst SSE_ENDPOINT = '/api/events/stream'\nconst HEARTBEAT_TIMEOUT = 45_000 // Expect heartbeat every 30s, allow 45s grace\nconst RECONNECT_BASE_MS = 1_000\nconst RECONNECT_MAX_MS = 30_000\nconst DEDUP_WINDOW_MS = 500\nconst BRIDGE_RECONNECTED_EVENT_ID = 'om:bridge:reconnected'\n\ntype EventBridgeReadyWindow = { __omEventBridgeReady?: boolean }\n\nfunction setEventBridgeReady(ready: boolean): void {\n if (typeof window === 'undefined') return\n ;(window as unknown as EventBridgeReadyWindow).__omEventBridgeReady = ready\n}\n\n/**\n * React hook that establishes a singleton SSE connection to the event bridge.\n *\n * Mount once in the app shell/layout. Receives server-side events with\n * `clientBroadcast: true` and dispatches them as `om:event` CustomEvents\n * on the window object for consumption by `useAppEvent`.\n *\n * Features:\n * - Auto-reconnect with exponential backoff\n * - Deduplication within 500ms window\n * - Heartbeat-based liveness detection\n * - Singleton connection per browser tab\n */\nexport function useEventBridge(): void {\n const sourceRef = useRef<EventSource | null>(null)\n const reconnectAttempts = useRef(0)\n const reconnectTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const heartbeatTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const recentEvents = useRef<Map<string, number>>(new Map())\n const hasEverConnected = useRef(false)\n const reconnectPending = useRef(false)\n\n useEffect(() => {\n let mounted = true\n\n function isDuplicate(eventPayload: AppEventPayload): boolean {\n const key = `${eventPayload.id}:${JSON.stringify(eventPayload.payload ?? {})}`\n const lastSeen = recentEvents.current.get(key)\n if (lastSeen && Date.now() - lastSeen < DEDUP_WINDOW_MS) return true\n recentEvents.current.set(key, Date.now())\n // Prune old entries\n if (recentEvents.current.size > 100) {\n const now = Date.now()\n for (const [k, v] of recentEvents.current) {\n if (now - v > DEDUP_WINDOW_MS * 2) recentEvents.current.delete(k)\n }\n }\n return false\n }\n\n function resetHeartbeatTimer() {\n if (heartbeatTimer.current) clearTimeout(heartbeatTimer.current)\n heartbeatTimer.current = setTimeout(() => {\n console.warn('[EventBridge] Heartbeat timeout \u2014 reconnecting')\n disconnect()\n scheduleReconnect()\n }, HEARTBEAT_TIMEOUT)\n }\n\n function connect() {\n if (!mounted) return\n if (sourceRef.current) return\n\n try {\n const source = new EventSource(SSE_ENDPOINT, { withCredentials: true })\n sourceRef.current = source\n\n source.onopen = () => {\n const shouldEmitReconnect = hasEverConnected.current && reconnectPending.current\n hasEverConnected.current = true\n reconnectPending.current = false\n reconnectAttempts.current = 0\n setEventBridgeReady(true)\n resetHeartbeatTimer()\n if (shouldEmitReconnect) {\n window.dispatchEvent(\n new CustomEvent(APP_EVENT_DOM_NAME, {\n detail: {\n id: BRIDGE_RECONNECTED_EVENT_ID,\n payload: {},\n timestamp: Date.now(),\n organizationId: '',\n } satisfies AppEventPayload,\n }),\n )\n }\n }\n\n source.onmessage = (event) => {\n resetHeartbeatTimer()\n if (!event.data || event.data === ':heartbeat') return\n\n try {\n const parsed = JSON.parse(event.data) as AppEventPayload\n if (!parsed.id || typeof parsed.id !== 'string') return\n\n if (isDuplicate(parsed)) return\n\n window.dispatchEvent(\n new CustomEvent(APP_EVENT_DOM_NAME, { detail: parsed }),\n )\n } catch {\n // Ignore malformed events\n }\n }\n\n source.onerror = () => {\n if (hasEverConnected.current) {\n reconnectPending.current = true\n }\n disconnect()\n if (mounted) scheduleReconnect()\n }\n } catch {\n if (hasEverConnected.current) {\n reconnectPending.current = true\n }\n if (mounted) scheduleReconnect()\n }\n }\n\n function disconnect() {\n setEventBridgeReady(false)\n if (sourceRef.current) {\n sourceRef.current.close()\n sourceRef.current = null\n }\n if (heartbeatTimer.current) {\n clearTimeout(heartbeatTimer.current)\n heartbeatTimer.current = null\n }\n }\n\n function scheduleReconnect() {\n if (reconnectTimer.current) return\n const delay = Math.min(\n RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts.current),\n RECONNECT_MAX_MS,\n )\n reconnectAttempts.current++\n reconnectTimer.current = setTimeout(() => {\n reconnectTimer.current = null\n connect()\n }, delay)\n }\n\n connect()\n\n return () => {\n mounted = false\n disconnect()\n if (reconnectTimer.current) {\n clearTimeout(reconnectTimer.current)\n reconnectTimer.current = null\n }\n }\n }, [])\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useEffect, useRef } from 'react'\nimport type { AppEventPayload } from '@open-mercato/shared/modules/widgets/injection'\nimport { APP_EVENT_DOM_NAME } from './useAppEvent'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'EventBridge' })\n\nconst SSE_ENDPOINT = '/api/events/stream'\nconst HEARTBEAT_TIMEOUT = 45_000 // Expect heartbeat every 30s, allow 45s grace\nconst RECONNECT_BASE_MS = 1_000\nconst RECONNECT_MAX_MS = 30_000\nconst DEDUP_WINDOW_MS = 500\nconst BRIDGE_RECONNECTED_EVENT_ID = 'om:bridge:reconnected'\n\ntype EventBridgeReadyWindow = { __omEventBridgeReady?: boolean }\n\nfunction setEventBridgeReady(ready: boolean): void {\n if (typeof window === 'undefined') return\n ;(window as unknown as EventBridgeReadyWindow).__omEventBridgeReady = ready\n}\n\n/**\n * React hook that establishes a singleton SSE connection to the event bridge.\n *\n * Mount once in the app shell/layout. Receives server-side events with\n * `clientBroadcast: true` and dispatches them as `om:event` CustomEvents\n * on the window object for consumption by `useAppEvent`.\n *\n * Features:\n * - Auto-reconnect with exponential backoff\n * - Deduplication within 500ms window\n * - Heartbeat-based liveness detection\n * - Singleton connection per browser tab\n */\nexport function useEventBridge(): void {\n const sourceRef = useRef<EventSource | null>(null)\n const reconnectAttempts = useRef(0)\n const reconnectTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const heartbeatTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n const recentEvents = useRef<Map<string, number>>(new Map())\n const hasEverConnected = useRef(false)\n const reconnectPending = useRef(false)\n\n useEffect(() => {\n let mounted = true\n\n function isDuplicate(eventPayload: AppEventPayload): boolean {\n const key = `${eventPayload.id}:${JSON.stringify(eventPayload.payload ?? {})}`\n const lastSeen = recentEvents.current.get(key)\n if (lastSeen && Date.now() - lastSeen < DEDUP_WINDOW_MS) return true\n recentEvents.current.set(key, Date.now())\n // Prune old entries\n if (recentEvents.current.size > 100) {\n const now = Date.now()\n for (const [k, v] of recentEvents.current) {\n if (now - v > DEDUP_WINDOW_MS * 2) recentEvents.current.delete(k)\n }\n }\n return false\n }\n\n function resetHeartbeatTimer() {\n if (heartbeatTimer.current) clearTimeout(heartbeatTimer.current)\n heartbeatTimer.current = setTimeout(() => {\n logger.warn('Heartbeat timeout \u2014 reconnecting')\n disconnect()\n scheduleReconnect()\n }, HEARTBEAT_TIMEOUT)\n }\n\n function connect() {\n if (!mounted) return\n if (sourceRef.current) return\n\n try {\n const source = new EventSource(SSE_ENDPOINT, { withCredentials: true })\n sourceRef.current = source\n\n source.onopen = () => {\n const shouldEmitReconnect = hasEverConnected.current && reconnectPending.current\n hasEverConnected.current = true\n reconnectPending.current = false\n reconnectAttempts.current = 0\n setEventBridgeReady(true)\n resetHeartbeatTimer()\n if (shouldEmitReconnect) {\n window.dispatchEvent(\n new CustomEvent(APP_EVENT_DOM_NAME, {\n detail: {\n id: BRIDGE_RECONNECTED_EVENT_ID,\n payload: {},\n timestamp: Date.now(),\n organizationId: '',\n } satisfies AppEventPayload,\n }),\n )\n }\n }\n\n source.onmessage = (event) => {\n resetHeartbeatTimer()\n if (!event.data || event.data === ':heartbeat') return\n\n try {\n const parsed = JSON.parse(event.data) as AppEventPayload\n if (!parsed.id || typeof parsed.id !== 'string') return\n\n if (isDuplicate(parsed)) return\n\n window.dispatchEvent(\n new CustomEvent(APP_EVENT_DOM_NAME, { detail: parsed }),\n )\n } catch {\n // Ignore malformed events\n }\n }\n\n source.onerror = () => {\n if (hasEverConnected.current) {\n reconnectPending.current = true\n }\n disconnect()\n if (mounted) scheduleReconnect()\n }\n } catch {\n if (hasEverConnected.current) {\n reconnectPending.current = true\n }\n if (mounted) scheduleReconnect()\n }\n }\n\n function disconnect() {\n setEventBridgeReady(false)\n if (sourceRef.current) {\n sourceRef.current.close()\n sourceRef.current = null\n }\n if (heartbeatTimer.current) {\n clearTimeout(heartbeatTimer.current)\n heartbeatTimer.current = null\n }\n }\n\n function scheduleReconnect() {\n if (reconnectTimer.current) return\n const delay = Math.min(\n RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts.current),\n RECONNECT_MAX_MS,\n )\n reconnectAttempts.current++\n reconnectTimer.current = setTimeout(() => {\n reconnectTimer.current = null\n connect()\n }, delay)\n }\n\n connect()\n\n return () => {\n mounted = false\n disconnect()\n if (reconnectTimer.current) {\n clearTimeout(reconnectTimer.current)\n reconnectTimer.current = null\n }\n }\n }, [])\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,8 @@ import { GLOBAL_MUTATION_INJECTION_SPOT_ID, dispatchBackendMutationError } from
5
5
  import { withScopedApiRequestHeaders } from "../utils/apiCall.js";
6
6
  import { surfaceRecordConflict } from "../conflicts/index.js";
7
7
  import { useT } from "@open-mercato/shared/lib/i18n/context";
8
+ import { createLogger } from "@open-mercato/shared/lib/logger";
9
+ const logger = createLogger("ui").child({ component: "useGuardedMutation" });
8
10
  function useGuardedMutation({
9
11
  contextId,
10
12
  blockedMessage = "Save blocked by validation",
@@ -45,7 +47,7 @@ function useGuardedMutation({
45
47
  try {
46
48
  await triggerEvent("onAfterSave", payload, context);
47
49
  } catch (error) {
48
- console.error("[useGuardedMutation] Error in onAfterSave injection event:", error);
50
+ logger.error("Error in onAfterSave injection event", { err: error });
49
51
  }
50
52
  return result;
51
53
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/useGuardedMutation.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useInjectionSpotEvents } from './InjectionSpot'\nimport { GLOBAL_MUTATION_INJECTION_SPOT_ID, dispatchBackendMutationError } from './mutationEvents'\nimport { withScopedApiRequestHeaders } from '../utils/apiCall'\nimport { surfaceRecordConflict } from '../conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype GuardedMutationContext = Record<string, unknown>\n\ntype GuardedMutationRunInput<TResult, TContext extends GuardedMutationContext> = {\n operation: () => Promise<TResult>\n context: TContext\n mutationPayload?: Record<string, unknown>\n}\n\ntype UseGuardedMutationOptions = {\n contextId: string\n blockedMessage?: string\n spotId?: string\n}\n\ntype StoredMutation<TContext extends GuardedMutationContext> = {\n operation: () => Promise<unknown>\n context: TContext\n mutationPayload: Record<string, unknown>\n}\n\nexport function useGuardedMutation<TContext extends GuardedMutationContext>({\n contextId,\n blockedMessage = 'Save blocked by validation',\n spotId = GLOBAL_MUTATION_INJECTION_SPOT_ID,\n}: UseGuardedMutationOptions) {\n const t = useT()\n const { triggerEvent } = useInjectionSpotEvents<TContext, Record<string, unknown>>(spotId)\n const lastMutationRef = React.useRef<StoredMutation<TContext> | null>(null)\n\n const emitMutationSaveError = React.useCallback((error: unknown) => {\n dispatchBackendMutationError({\n contextId,\n formId: contextId,\n error,\n })\n // Default UX for OSS optimistic-lock conflicts (spec\n // .ai/specs/implemented/2026-05-25-oss-optimistic-locking.md \u00A73.6): when the\n // server returns 409 with `code: 'optimistic_lock_conflict'`,\n // surface the conflict on the unified, persistent, error-styled\n // RecordConflictBanner (rendered in AppShell) instead of a transient\n // toast. Callers that have their own handler still see the dispatched\n // event above and can suppress/override by catching the error first.\n try {\n surfaceRecordConflict(error, t)\n } catch {\n // ignore conflict-bar dispatch failures\n }\n }, [contextId, t])\n\n const runMutation = React.useCallback(async <TResult,>({\n operation,\n context,\n mutationPayload,\n }: GuardedMutationRunInput<TResult, TContext>): Promise<TResult> => {\n const payload = mutationPayload ?? {}\n lastMutationRef.current = {\n operation: operation as () => Promise<unknown>,\n context,\n mutationPayload: payload,\n }\n\n const beforeSave = await triggerEvent('onBeforeSave', payload, context)\n if (!beforeSave.ok) {\n emitMutationSaveError(beforeSave.details ?? beforeSave)\n throw new Error(beforeSave.message || blockedMessage)\n }\n\n try {\n const result =\n beforeSave.requestHeaders && Object.keys(beforeSave.requestHeaders).length > 0\n ? await withScopedApiRequestHeaders(beforeSave.requestHeaders, operation)\n : await operation()\n\n try {\n await triggerEvent('onAfterSave', payload, context)\n } catch (error) {\n console.error('[useGuardedMutation] Error in onAfterSave injection event:', error)\n }\n\n return result\n } catch (error) {\n emitMutationSaveError(error)\n throw error\n }\n }, [blockedMessage, emitMutationSaveError, triggerEvent])\n\n const retryLastMutation = React.useCallback(async (): Promise<boolean> => {\n const lastMutation = lastMutationRef.current\n if (!lastMutation) return false\n\n try {\n await runMutation({\n operation: lastMutation.operation,\n context: lastMutation.context,\n mutationPayload: lastMutation.mutationPayload,\n })\n return true\n } catch {\n return false\n }\n }, [runMutation])\n\n return {\n runMutation,\n retryLastMutation,\n }\n}\n"],
5
- "mappings": ";AAEA,YAAY,WAAW;AACvB,SAAS,8BAA8B;AACvC,SAAS,mCAAmC,oCAAoC;AAChF,SAAS,mCAAmC;AAC5C,SAAS,6BAA6B;AACtC,SAAS,YAAY;AAsBd,SAAS,mBAA4D;AAAA,EAC1E;AAAA,EACA,iBAAiB;AAAA,EACjB,SAAS;AACX,GAA8B;AAC5B,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,aAAa,IAAI,uBAA0D,MAAM;AACzF,QAAM,kBAAkB,MAAM,OAAwC,IAAI;AAE1E,QAAM,wBAAwB,MAAM,YAAY,CAAC,UAAmB;AAClE,iCAA6B;AAAA,MAC3B;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAQD,QAAI;AACF,4BAAsB,OAAO,CAAC;AAAA,IAChC,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,WAAW,CAAC,CAAC;AAEjB,QAAM,cAAc,MAAM,YAAY,OAAiB;AAAA,IACrD;AAAA,IACA;AAAA,IACA;AAAA,EACF,MAAoE;AAClE,UAAM,UAAU,mBAAmB,CAAC;AACpC,oBAAgB,UAAU;AAAA,MACxB;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,IACnB;AAEA,UAAM,aAAa,MAAM,aAAa,gBAAgB,SAAS,OAAO;AACtE,QAAI,CAAC,WAAW,IAAI;AAClB,4BAAsB,WAAW,WAAW,UAAU;AACtD,YAAM,IAAI,MAAM,WAAW,WAAW,cAAc;AAAA,IACtD;AAEA,QAAI;AACF,YAAM,SACJ,WAAW,kBAAkB,OAAO,KAAK,WAAW,cAAc,EAAE,SAAS,IACzE,MAAM,4BAA4B,WAAW,gBAAgB,SAAS,IACtE,MAAM,UAAU;AAEtB,UAAI;AACF,cAAM,aAAa,eAAe,SAAS,OAAO;AAAA,MACpD,SAAS,OAAO;AACd,gBAAQ,MAAM,8DAA8D,KAAK;AAAA,MACnF;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,4BAAsB,KAAK;AAC3B,YAAM;AAAA,IACR;AAAA,EACF,GAAG,CAAC,gBAAgB,uBAAuB,YAAY,CAAC;AAExD,QAAM,oBAAoB,MAAM,YAAY,YAA8B;AACxE,UAAM,eAAe,gBAAgB;AACrC,QAAI,CAAC,aAAc,QAAO;AAE1B,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,aAAa;AAAA,QACxB,SAAS,aAAa;AAAA,QACtB,iBAAiB,aAAa;AAAA,MAChC,CAAC;AACD,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useInjectionSpotEvents } from './InjectionSpot'\nimport { GLOBAL_MUTATION_INJECTION_SPOT_ID, dispatchBackendMutationError } from './mutationEvents'\nimport { withScopedApiRequestHeaders } from '../utils/apiCall'\nimport { surfaceRecordConflict } from '../conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'useGuardedMutation' })\n\ntype GuardedMutationContext = Record<string, unknown>\n\ntype GuardedMutationRunInput<TResult, TContext extends GuardedMutationContext> = {\n operation: () => Promise<TResult>\n context: TContext\n mutationPayload?: Record<string, unknown>\n}\n\ntype UseGuardedMutationOptions = {\n contextId: string\n blockedMessage?: string\n spotId?: string\n}\n\ntype StoredMutation<TContext extends GuardedMutationContext> = {\n operation: () => Promise<unknown>\n context: TContext\n mutationPayload: Record<string, unknown>\n}\n\nexport function useGuardedMutation<TContext extends GuardedMutationContext>({\n contextId,\n blockedMessage = 'Save blocked by validation',\n spotId = GLOBAL_MUTATION_INJECTION_SPOT_ID,\n}: UseGuardedMutationOptions) {\n const t = useT()\n const { triggerEvent } = useInjectionSpotEvents<TContext, Record<string, unknown>>(spotId)\n const lastMutationRef = React.useRef<StoredMutation<TContext> | null>(null)\n\n const emitMutationSaveError = React.useCallback((error: unknown) => {\n dispatchBackendMutationError({\n contextId,\n formId: contextId,\n error,\n })\n // Default UX for OSS optimistic-lock conflicts (spec\n // .ai/specs/implemented/2026-05-25-oss-optimistic-locking.md \u00A73.6): when the\n // server returns 409 with `code: 'optimistic_lock_conflict'`,\n // surface the conflict on the unified, persistent, error-styled\n // RecordConflictBanner (rendered in AppShell) instead of a transient\n // toast. Callers that have their own handler still see the dispatched\n // event above and can suppress/override by catching the error first.\n try {\n surfaceRecordConflict(error, t)\n } catch {\n // ignore conflict-bar dispatch failures\n }\n }, [contextId, t])\n\n const runMutation = React.useCallback(async <TResult,>({\n operation,\n context,\n mutationPayload,\n }: GuardedMutationRunInput<TResult, TContext>): Promise<TResult> => {\n const payload = mutationPayload ?? {}\n lastMutationRef.current = {\n operation: operation as () => Promise<unknown>,\n context,\n mutationPayload: payload,\n }\n\n const beforeSave = await triggerEvent('onBeforeSave', payload, context)\n if (!beforeSave.ok) {\n emitMutationSaveError(beforeSave.details ?? beforeSave)\n throw new Error(beforeSave.message || blockedMessage)\n }\n\n try {\n const result =\n beforeSave.requestHeaders && Object.keys(beforeSave.requestHeaders).length > 0\n ? await withScopedApiRequestHeaders(beforeSave.requestHeaders, operation)\n : await operation()\n\n try {\n await triggerEvent('onAfterSave', payload, context)\n } catch (error) {\n logger.error('Error in onAfterSave injection event', { err: error })\n }\n\n return result\n } catch (error) {\n emitMutationSaveError(error)\n throw error\n }\n }, [blockedMessage, emitMutationSaveError, triggerEvent])\n\n const retryLastMutation = React.useCallback(async (): Promise<boolean> => {\n const lastMutation = lastMutationRef.current\n if (!lastMutation) return false\n\n try {\n await runMutation({\n operation: lastMutation.operation,\n context: lastMutation.context,\n mutationPayload: lastMutation.mutationPayload,\n })\n return true\n } catch {\n return false\n }\n }, [runMutation])\n\n return {\n runMutation,\n retryLastMutation,\n }\n}\n"],
5
+ "mappings": ";AAEA,YAAY,WAAW;AACvB,SAAS,8BAA8B;AACvC,SAAS,mCAAmC,oCAAoC;AAChF,SAAS,mCAAmC;AAC5C,SAAS,6BAA6B;AACtC,SAAS,YAAY;AACrB,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,IAAI,EAAE,MAAM,EAAE,WAAW,qBAAqB,CAAC;AAsBpE,SAAS,mBAA4D;AAAA,EAC1E;AAAA,EACA,iBAAiB;AAAA,EACjB,SAAS;AACX,GAA8B;AAC5B,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,aAAa,IAAI,uBAA0D,MAAM;AACzF,QAAM,kBAAkB,MAAM,OAAwC,IAAI;AAE1E,QAAM,wBAAwB,MAAM,YAAY,CAAC,UAAmB;AAClE,iCAA6B;AAAA,MAC3B;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAQD,QAAI;AACF,4BAAsB,OAAO,CAAC;AAAA,IAChC,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,WAAW,CAAC,CAAC;AAEjB,QAAM,cAAc,MAAM,YAAY,OAAiB;AAAA,IACrD;AAAA,IACA;AAAA,IACA;AAAA,EACF,MAAoE;AAClE,UAAM,UAAU,mBAAmB,CAAC;AACpC,oBAAgB,UAAU;AAAA,MACxB;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,IACnB;AAEA,UAAM,aAAa,MAAM,aAAa,gBAAgB,SAAS,OAAO;AACtE,QAAI,CAAC,WAAW,IAAI;AAClB,4BAAsB,WAAW,WAAW,UAAU;AACtD,YAAM,IAAI,MAAM,WAAW,WAAW,cAAc;AAAA,IACtD;AAEA,QAAI;AACF,YAAM,SACJ,WAAW,kBAAkB,OAAO,KAAK,WAAW,cAAc,EAAE,SAAS,IACzE,MAAM,4BAA4B,WAAW,gBAAgB,SAAS,IACtE,MAAM,UAAU;AAEtB,UAAI;AACF,cAAM,aAAa,eAAe,SAAS,OAAO;AAAA,MACpD,SAAS,OAAO;AACd,eAAO,MAAM,wCAAwC,EAAE,KAAK,MAAM,CAAC;AAAA,MACrE;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,4BAAsB,KAAK;AAC3B,YAAM;AAAA,IACR;AAAA,EACF,GAAG,CAAC,gBAAgB,uBAAuB,YAAY,CAAC;AAExD,QAAM,oBAAoB,MAAM,YAAY,YAA8B;AACxE,UAAM,eAAe,gBAAgB;AACrC,QAAI,CAAC,aAAc,QAAO;AAE1B,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,aAAa;AAAA,QACxB,SAAS,aAAa;AAAA,QACtB,iBAAiB,aAAa;AAAA,MAChC,CAAC;AACD,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAEhB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,8 @@ import {
5
5
  getInjectionRegistryVersion,
6
6
  subscribeToInjectionRegistryChanges
7
7
  } from "@open-mercato/shared/modules/widgets/injection-loader";
8
+ import { createLogger } from "@open-mercato/shared/lib/logger";
9
+ const logger = createLogger("ui").child({ component: "useInjectionDataWidgets" });
8
10
  function useInjectionDataWidgets(spotId) {
9
11
  const [widgets, setWidgets] = React.useState([]);
10
12
  const [isLoading, setIsLoading] = React.useState(true);
@@ -26,7 +28,7 @@ function useInjectionDataWidgets(spotId) {
26
28
  setWidgets(loaded);
27
29
  } catch (loadError) {
28
30
  if (!mounted) return;
29
- console.error(`[useInjectionDataWidgets] Failed to load widgets for spot ${spotId}:`, loadError);
31
+ logger.error("Failed to load widgets for spot", { spotId, err: loadError });
30
32
  setError(loadError instanceof Error ? loadError.message : String(loadError));
31
33
  setWidgets([]);
32
34
  } finally {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/useInjectionDataWidgets.ts"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { InjectionSpotId } from '@open-mercato/shared/modules/widgets/injection'\nimport {\n loadInjectionDataWidgetsForSpot,\n getInjectionRegistryVersion,\n subscribeToInjectionRegistryChanges,\n type LoadedInjectionDataWidget,\n} from '@open-mercato/shared/modules/widgets/injection-loader'\n\nexport function useInjectionDataWidgets(spotId: InjectionSpotId): {\n widgets: LoadedInjectionDataWidget[]\n isLoading: boolean\n error: string | null\n} {\n const [widgets, setWidgets] = React.useState<LoadedInjectionDataWidget[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n // Re-load when the injection registry is (re-)populated. With the async\n // ClientBootstrap, registration can land after this hook first runs; the\n // version bump triggers a reload so injected menus/columns/fields appear\n // without requiring an unrelated re-render.\n const [registryVersion, setRegistryVersion] = React.useState(() => getInjectionRegistryVersion())\n\n React.useEffect(() => {\n return subscribeToInjectionRegistryChanges(() => {\n setRegistryVersion(getInjectionRegistryVersion())\n })\n }, [])\n\n React.useEffect(() => {\n let mounted = true\n const load = async () => {\n try {\n setIsLoading(true)\n setError(null)\n const loaded = await loadInjectionDataWidgetsForSpot(spotId)\n if (!mounted) return\n setWidgets(loaded)\n } catch (loadError) {\n if (!mounted) return\n console.error(`[useInjectionDataWidgets] Failed to load widgets for spot ${spotId}:`, loadError)\n setError(loadError instanceof Error ? loadError.message : String(loadError))\n setWidgets([])\n } finally {\n if (mounted) setIsLoading(false)\n }\n }\n void load()\n return () => {\n mounted = false\n }\n }, [spotId, registryVersion])\n\n return { widgets, isLoading, error }\n}\n"],
5
- "mappings": ";AAEA,YAAY,WAAW;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEA,SAAS,wBAAwB,QAItC;AACA,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAsC,CAAC,CAAC;AAC5E,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAK5D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,MAAM,4BAA4B,CAAC;AAEhG,QAAM,UAAU,MAAM;AACpB,WAAO,oCAAoC,MAAM;AAC/C,yBAAmB,4BAA4B,CAAC;AAAA,IAClD,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU;AACd,UAAM,OAAO,YAAY;AACvB,UAAI;AACF,qBAAa,IAAI;AACjB,iBAAS,IAAI;AACb,cAAM,SAAS,MAAM,gCAAgC,MAAM;AAC3D,YAAI,CAAC,QAAS;AACd,mBAAW,MAAM;AAAA,MACnB,SAAS,WAAW;AAClB,YAAI,CAAC,QAAS;AACd,gBAAQ,MAAM,6DAA6D,MAAM,KAAK,SAAS;AAC/F,iBAAS,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS,CAAC;AAC3E,mBAAW,CAAC,CAAC;AAAA,MACf,UAAE;AACA,YAAI,QAAS,cAAa,KAAK;AAAA,MACjC;AAAA,IACF;AACA,SAAK,KAAK;AACV,WAAO,MAAM;AACX,gBAAU;AAAA,IACZ;AAAA,EACF,GAAG,CAAC,QAAQ,eAAe,CAAC;AAE5B,SAAO,EAAE,SAAS,WAAW,MAAM;AACrC;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { InjectionSpotId } from '@open-mercato/shared/modules/widgets/injection'\nimport {\n loadInjectionDataWidgetsForSpot,\n getInjectionRegistryVersion,\n subscribeToInjectionRegistryChanges,\n type LoadedInjectionDataWidget,\n} from '@open-mercato/shared/modules/widgets/injection-loader'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('ui').child({ component: 'useInjectionDataWidgets' })\n\nexport function useInjectionDataWidgets(spotId: InjectionSpotId): {\n widgets: LoadedInjectionDataWidget[]\n isLoading: boolean\n error: string | null\n} {\n const [widgets, setWidgets] = React.useState<LoadedInjectionDataWidget[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n // Re-load when the injection registry is (re-)populated. With the async\n // ClientBootstrap, registration can land after this hook first runs; the\n // version bump triggers a reload so injected menus/columns/fields appear\n // without requiring an unrelated re-render.\n const [registryVersion, setRegistryVersion] = React.useState(() => getInjectionRegistryVersion())\n\n React.useEffect(() => {\n return subscribeToInjectionRegistryChanges(() => {\n setRegistryVersion(getInjectionRegistryVersion())\n })\n }, [])\n\n React.useEffect(() => {\n let mounted = true\n const load = async () => {\n try {\n setIsLoading(true)\n setError(null)\n const loaded = await loadInjectionDataWidgetsForSpot(spotId)\n if (!mounted) return\n setWidgets(loaded)\n } catch (loadError) {\n if (!mounted) return\n logger.error('Failed to load widgets for spot', { spotId, err: loadError })\n setError(loadError instanceof Error ? loadError.message : String(loadError))\n setWidgets([])\n } finally {\n if (mounted) setIsLoading(false)\n }\n }\n void load()\n return () => {\n mounted = false\n }\n }, [spotId, registryVersion])\n\n return { widgets, isLoading, error }\n}\n"],
5
+ "mappings": ";AAEA,YAAY,WAAW;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,IAAI,EAAE,MAAM,EAAE,WAAW,0BAA0B,CAAC;AAEzE,SAAS,wBAAwB,QAItC;AACA,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAsC,CAAC,CAAC;AAC5E,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAK5D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,MAAM,4BAA4B,CAAC;AAEhG,QAAM,UAAU,MAAM;AACpB,WAAO,oCAAoC,MAAM;AAC/C,yBAAmB,4BAA4B,CAAC;AAAA,IAClD,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU;AACd,UAAM,OAAO,YAAY;AACvB,UAAI;AACF,qBAAa,IAAI;AACjB,iBAAS,IAAI;AACb,cAAM,SAAS,MAAM,gCAAgC,MAAM;AAC3D,YAAI,CAAC,QAAS;AACd,mBAAW,MAAM;AAAA,MACnB,SAAS,WAAW;AAClB,YAAI,CAAC,QAAS;AACd,eAAO,MAAM,mCAAmC,EAAE,QAAQ,KAAK,UAAU,CAAC;AAC1E,iBAAS,qBAAqB,QAAQ,UAAU,UAAU,OAAO,SAAS,CAAC;AAC3E,mBAAW,CAAC,CAAC;AAAA,MACf,UAAE;AACA,YAAI,QAAS,cAAa,KAAK;AAAA,MACjC;AAAA,IACF;AACA,SAAK,KAAK;AACV,WAAO,MAAM;AACX,gBAAU;AAAA,IACZ;AAAA,EACF,GAAG,CAAC,QAAQ,eAAe,CAAC;AAE5B,SAAO,EAAE,SAAS,WAAW,MAAM;AACrC;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,8 @@ import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { getComponentEntry, getComponentOverrides } from "@open-mercato/shared/modules/widgets/component-registry";
5
5
  import { useOverrideUserFeatures } from "./ComponentOverrideProvider.js";
6
+ import { createLogger } from "@open-mercato/shared/lib/logger";
7
+ const logger = createLogger("ui").child({ component: "useRegisteredComponent" });
6
8
  class ReplacementErrorBoundary extends React.Component {
7
9
  constructor(props) {
8
10
  super(props);
@@ -26,7 +28,7 @@ function useRegisteredComponent(componentId, fallback) {
26
28
  const original = entry?.component ?? fallback ?? null;
27
29
  if (!original) {
28
30
  if (process.env.NODE_ENV !== "production" && !fallback) {
29
- console.warn(`[UMES] Component "${componentId}" is not registered.`);
31
+ logger.warn("Component is not registered", { componentId });
30
32
  }
31
33
  const Missing = () => null;
32
34
  return Missing;
@@ -34,9 +36,7 @@ function useRegisteredComponent(componentId, fallback) {
34
36
  const overrides = getComponentOverrides(componentId, userFeatures);
35
37
  const replacementOverrides = overrides.filter((override) => "replacement" in override);
36
38
  if (process.env.NODE_ENV !== "production" && replacementOverrides.length > 1) {
37
- console.warn(
38
- `[UMES] Multiple replacements registered for "${componentId}". Highest-priority replacement is applied.`
39
- );
39
+ logger.warn("Multiple replacements registered; highest-priority replacement is applied", { componentId });
40
40
  }
41
41
  let replacement = null;
42
42
  let replacementOverride = null;
@@ -58,10 +58,7 @@ function useRegisteredComponent(componentId, fallback) {
58
58
  if (process.env.NODE_ENV !== "production" && replacementOverride && "replacement" in replacementOverride) {
59
59
  const validation = replacementOverride.propsSchema.safeParse(transformed);
60
60
  if (!validation.success) {
61
- console.error(
62
- `[UMES] Props schema validation failed for replacement "${componentId}" from module "${replacementOverride.metadata?.module ?? "unknown"}"`,
63
- validation.error.format()
64
- );
61
+ logger.error("Props schema validation failed for replacement", { componentId, module: replacementOverride.metadata?.module ?? "unknown", issues: validation.error.format() });
65
62
  return Fallback;
66
63
  }
67
64
  }
@@ -71,7 +68,7 @@ function useRegisteredComponent(componentId, fallback) {
71
68
  fallback: Fallback,
72
69
  onError: (error) => {
73
70
  const replacementModule = overrides.find((override) => "replacement" in override)?.metadata?.module ?? "unknown";
74
- console.error(`[UMES] Component replacement failed for "${componentId}" from module "${replacementModule}"`, error);
71
+ logger.error("Component replacement failed", { componentId, module: replacementModule, err: error });
75
72
  },
76
73
  children: React.createElement(wrapped, transformed)
77
74
  }