@open-mercato/ui 0.6.6-develop.6465.1.019f0fb26f → 0.6.6-develop.6472.1.1673e7e66b

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 +7 -10
  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
@@ -97,6 +97,7 @@ import { dispatchBackendMutationError } from './injection/mutationEvents'
97
97
  import { VersionHistoryAction } from './version-history/VersionHistoryAction'
98
98
  import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
99
99
  import { cn } from '@open-mercato/shared/lib/utils'
100
+ import { createLogger } from '@open-mercato/shared/lib/logger'
100
101
  import { useInjectionDataWidgets } from './injection/useInjectionDataWidgets'
101
102
  import { CollapsibleGroup, type CollapsibleGroupHandle } from './crud/CollapsibleGroup'
102
103
  import { SortableGroupHandleProvider, type SortableGroupHandleProps } from './crud/SortableGroupHandle'
@@ -110,6 +111,8 @@ import { RichEditor, type RichEditorLabels } from '../primitives/rich-editor'
110
111
  import MarkdownField from './inputs/MarkdownField'
111
112
 
112
113
  // Stable empty options array to avoid creating a new [] every render
114
+ const logger = createLogger('ui').child({ component: 'CrudForm' })
115
+
113
116
  const EMPTY_OPTIONS: CrudFieldOption[] = []
114
117
  // Sentinel for the optional-Select clear affordance. Radix Select forbids
115
118
  // empty-string item values, so we use a stable non-empty token that maps to
@@ -1106,7 +1109,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1106
1109
  Object.entries(transformed as Record<string, unknown>).map(([key, value]) => [key, String(value)]),
1107
1110
  )
1108
1111
  } catch (err) {
1109
- console.error('[CrudForm] Error in transformValidation:', err)
1112
+ logger.error('Error in transformValidation', { err })
1110
1113
  return fieldErrors
1111
1114
  }
1112
1115
  },
@@ -1186,7 +1189,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1186
1189
  void triggerInjectionEvent('onAppEvent', valuesRef.current as TValues, injectionContextRef.current, {
1187
1190
  appEvent: customEvent.detail,
1188
1191
  }).catch((err) => {
1189
- console.error('[CrudForm] Error in onAppEvent:', err)
1192
+ logger.error('Error in onAppEvent', { err })
1190
1193
  })
1191
1194
  }
1192
1195
  window.addEventListener('om:event', handleEvent as EventListener)
@@ -1201,7 +1204,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1201
1204
  void triggerInjectionEvent('onVisibilityChange', valuesRef.current as TValues, injectionContextRef.current, {
1202
1205
  visible: document.visibilityState === 'visible',
1203
1206
  }).catch((err) => {
1204
- console.error('[CrudForm] Error in onVisibilityChange:', err)
1207
+ logger.error('Error in onVisibilityChange', { err })
1205
1208
  })
1206
1209
  }
1207
1210
  document.addEventListener('visibilitychange', emitVisibility)
@@ -1325,7 +1328,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1325
1328
  }
1326
1329
  injectionRequestHeaders = result.requestHeaders
1327
1330
  } catch (err) {
1328
- console.error('[CrudForm] Error in onBeforeDelete:', err)
1331
+ logger.error('Error in onBeforeDelete', { err })
1329
1332
  flash(t('ui.forms.flash.saveBlocked', 'Save blocked by validation'), 'error')
1330
1333
  return
1331
1334
  }
@@ -1336,7 +1339,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1336
1339
  try {
1337
1340
  await triggerInjectionEvent('onDelete', deletePayload, injectionContext)
1338
1341
  } catch (err) {
1339
- console.error('[CrudForm] Error in onDelete:', err)
1342
+ logger.error('Error in onDelete', { err })
1340
1343
  flash(t('ui.forms.flash.saveBlocked', 'Save blocked by validation'), 'error')
1341
1344
  return
1342
1345
  }
@@ -1359,7 +1362,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1359
1362
  try {
1360
1363
  await triggerInjectionEvent('onAfterDelete', deletePayload, injectionContext)
1361
1364
  } catch (err) {
1362
- console.error('[CrudForm] Error in onAfterDelete:', err)
1365
+ logger.error('Error in onAfterDelete', { err })
1363
1366
  }
1364
1367
  }
1365
1368
  try { flash(deleteSuccessMessage, 'success') } catch {}
@@ -1372,7 +1375,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
1372
1375
  try {
1373
1376
  await triggerInjectionEvent('onDeleteError', deletePayload, injectionContext, { error: err })
1374
1377
  } catch (hookError) {
1375
- console.error('[CrudForm] Error in onDeleteError:', hookError)
1378
+ logger.error('Error in onDeleteError', { err: hookError })
1376
1379
  }
1377
1380
  }
1378
1381
  try {
@@ -2000,7 +2003,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2000
2003
  const targetIndex = cloned.findIndex((group) => group.id === definition.group)
2001
2004
  const index = targetIndex >= 0 ? targetIndex : fallbackIndex
2002
2005
  if (targetIndex < 0 && process.env.NODE_ENV !== 'production') {
2003
- console.warn(`[CrudForm] Injected field "${definition.id}" targets group "${definition.group}" which does not exist. Appended to last group.`)
2006
+ logger.warn('Injected field targets a group that does not exist; appended to last group', { fieldId: definition.id, group: definition.group })
2004
2007
  }
2005
2008
  if (index < 0) continue
2006
2009
  const fieldEntries = cloned[index].fields ?? []
@@ -2380,7 +2383,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2380
2383
  flash(message.text, message.severity)
2381
2384
  }
2382
2385
  }).catch((err) => {
2383
- console.error('[CrudForm] Error in onFieldChange:', err)
2386
+ logger.error('Error in onFieldChange', { err })
2384
2387
  })
2385
2388
  }, [embedded, extendedInjectionEventsEnabled, flash, t, trackDirtyWhenEmbedded, translateValidationMessage, triggerInjectionEvent, updateEditedFieldMarker])
2386
2389
 
@@ -2501,7 +2504,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2501
2504
  dirtyBaselineValuesRef.current = { ...(transformed as Record<string, unknown>) }
2502
2505
  setValues(transformed as CrudFormValues<TValues>)
2503
2506
  } catch (err) {
2504
- console.error('[CrudForm] Error in transformDisplayData:', err)
2507
+ logger.error('Error in transformDisplayData', { err })
2505
2508
  }
2506
2509
  }
2507
2510
  void run()
@@ -2647,7 +2650,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2647
2650
  }
2648
2651
  if (Object.keys(requiredErrors).length) {
2649
2652
  if (process.env.NODE_ENV !== 'production') {
2650
- console.debug('[crud-form] Required field errors prevented submit', requiredErrors)
2653
+ logger.debug('Required field errors prevented submit', { requiredErrors })
2651
2654
  }
2652
2655
  const transformedErrors = await transformValidationErrors(requiredErrors)
2653
2656
  setErrors(transformedErrors)
@@ -2724,7 +2727,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2724
2727
  if (path) fieldErrors[path] = issue.message
2725
2728
  })
2726
2729
  if (process.env.NODE_ENV !== 'production') {
2727
- console.debug('[crud-form] Schema validation failed', res.error.issues)
2730
+ logger.debug('Schema validation failed', { issues: res.error.issues })
2728
2731
  }
2729
2732
  const transformedErrors = await transformValidationErrors(fieldErrors)
2730
2733
  setErrors(translateValidationErrors(transformedErrors))
@@ -2760,7 +2763,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2760
2763
  }
2761
2764
  }
2762
2765
  } catch (err) {
2763
- console.error('[CrudForm] Error in transformFormData:', err)
2766
+ logger.error('Error in transformFormData', { err })
2764
2767
  }
2765
2768
  }
2766
2769
 
@@ -2799,7 +2802,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2799
2802
  }
2800
2803
  injectionRequestHeaders = result.requestHeaders
2801
2804
  } catch (err) {
2802
- console.error('[CrudForm] Error in onBeforeSave:', err)
2805
+ logger.error('Error in onBeforeSave', { err })
2803
2806
  flash(t('ui.forms.flash.saveBlocked', 'Save blocked by validation'), 'error')
2804
2807
  setPending(false)
2805
2808
  return
@@ -2834,7 +2837,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2834
2837
  try {
2835
2838
  await triggerInjectionEvent('onSave', submitValues, injectionContext)
2836
2839
  } catch (err) {
2837
- console.error('[CrudForm] Error in onSave:', err)
2840
+ logger.error('Error in onSave', { err })
2838
2841
  flash(t('ui.forms.flash.saveBlocked', 'Save blocked by validation'), 'error')
2839
2842
  setPending(false)
2840
2843
  return
@@ -2861,7 +2864,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2861
2864
  try {
2862
2865
  await triggerInjectionEvent('onAfterSave', submitValues, injectionContext)
2863
2866
  } catch (err) {
2864
- console.error('[CrudForm] Error in onAfterSave:', err)
2867
+ logger.error('Error in onAfterSave', { err })
2865
2868
  }
2866
2869
  }
2867
2870
 
@@ -2903,7 +2906,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2903
2906
  const transformedErrors = await transformValidationErrors(combinedFieldErrors)
2904
2907
  setErrors(translateValidationErrors(transformedErrors))
2905
2908
  if (process.env.NODE_ENV !== 'production') {
2906
- console.debug('[crud-form] Submission failed with field errors', transformedErrors)
2909
+ logger.debug('Submission failed with field errors', { fieldErrors: transformedErrors })
2907
2910
  }
2908
2911
  }
2909
2912
 
@@ -92,6 +92,9 @@ import {
92
92
  useSortable,
93
93
  } from '@dnd-kit/sortable'
94
94
  import { CSS } from '@dnd-kit/utilities'
95
+ import { createLogger } from '@open-mercato/shared/lib/logger'
96
+
97
+ const logger = createLogger('ui').child({ component: 'DataTable' })
95
98
 
96
99
  let refreshScheduled = false
97
100
 
@@ -276,7 +279,7 @@ export type DataTableProps<T> = {
276
279
  * bridge. The bridge is provided for one minor version; legacy callers SHOULD
277
280
  * migrate to the tree shape — see the spec
278
281
  * `.ai/specs/implemented/2026-05-10-crm-list-filter-redesign.md` "Migration & Backward
279
- * Compatibility" section and `RELEASE_NOTES.md`.
282
+ * Compatibility" section and `UPGRADE_NOTES.md`.
280
283
  *
281
284
  * When the legacy flat shape is detected, DataTable converts it to a tree via
282
285
  * `flatToTree` for internal rendering and converts any user edits back via
@@ -393,7 +396,7 @@ function collectUniqueById<T extends { id: string }>(
393
396
  if (!entry.id) continue
394
397
  if (byId.has(entry.id)) {
395
398
  if (process.env.NODE_ENV !== 'production') {
396
- console.warn(`[UMES] Duplicate injected ${warningScope} id "${entry.id}" detected. Keeping the first entry.`)
399
+ logger.warn('Duplicate injected id detected; keeping the first entry', { scope: warningScope, id: entry.id })
397
400
  }
398
401
  continue
399
402
  }
@@ -1057,12 +1060,7 @@ export function DataTable<T>({
1057
1060
  }
1058
1061
  if (!legacyAdvancedFilterWarnedRef.current && process.env.NODE_ENV !== 'production') {
1059
1062
  legacyAdvancedFilterWarnedRef.current = true
1060
- // eslint-disable-next-line no-console
1061
- console.warn(
1062
- '[DataTable] `advancedFilter.value` was passed as the legacy `AdvancedFilterState` shape. ' +
1063
- 'This bridge will be removed in the next minor version — migrate to the tree shape ' +
1064
- '(`AdvancedFilterTree`, see `@open-mercato/shared/lib/query/advanced-filter-tree`).',
1065
- )
1063
+ logger.warn('advancedFilter.value was passed as the legacy AdvancedFilterState shape. This bridge will be removed in the next minor version — migrate to the tree shape (AdvancedFilterTree, see @open-mercato/shared/lib/query/advanced-filter-tree).')
1066
1064
  }
1067
1065
  const legacy = advancedFilterInput as Extract<typeof advancedFilterInput, { value: AdvancedFilterState }>
1068
1066
  return {
@@ -1775,8 +1773,7 @@ export function DataTable<T>({
1775
1773
  : {}),
1776
1774
  }
1777
1775
  if (process.env.NODE_ENV !== 'production') {
1778
- // eslint-disable-next-line no-console
1779
- console.debug('[DataTable] perspective payload', payload)
1776
+ logger.debug('Perspective payload', { payload })
1780
1777
  }
1781
1778
  const existing = input.perspectiveId
1782
1779
  ? perspectiveData?.perspectives.find((p) => p.id === input.perspectiveId) ?? null
@@ -4,6 +4,9 @@ import * as React from 'react'
4
4
  import { cn } from '@open-mercato/shared/lib/utils'
5
5
  import { Button } from '../primitives/button'
6
6
  import { Check, Copy, ChevronRight, ChevronDown } from 'lucide-react'
7
+ import { createLogger } from '@open-mercato/shared/lib/logger'
8
+
9
+ const logger = createLogger('ui')
7
10
 
8
11
  type JsonDisplayProps = {
9
12
  data: unknown
@@ -51,7 +54,7 @@ export function JsonDisplay({
51
54
  setCopied(true)
52
55
  setTimeout(() => setCopied(false), 2000)
53
56
  } catch (err) {
54
- console.error('Failed to copy to clipboard:', err)
57
+ logger.error('Failed to copy to clipboard', { err })
55
58
  }
56
59
  }, [jsonString])
57
60
 
@@ -238,7 +238,7 @@ describe('component replacement', () => {
238
238
  expect(screen.queryByText('low:picked')).toBeNull()
239
239
  expect(screen.getByText('high:picked')).toBeInTheDocument()
240
240
  expect(warnSpy).toHaveBeenCalledWith(
241
- `[UMES] Multiple replacements registered for "${componentId}". Highest-priority replacement is applied.`,
241
+ expect.stringContaining(`Multiple replacements registered; highest-priority replacement is applied component=useRegisteredComponent componentId=${componentId}`),
242
242
  )
243
243
  warnSpy.mockRestore()
244
244
  })
@@ -1,6 +1,9 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
3
  import { ConfirmDialog } from "./ConfirmDialog";
4
+ import { createLogger } from "@open-mercato/shared/lib/logger";
5
+
6
+ const logger = createLogger("ui");
4
7
 
5
8
  function DialogMountTracker({ trackerRef }: { trackerRef: React.MutableRefObject<boolean> }) {
6
9
  React.useEffect(() => {
@@ -82,9 +85,7 @@ export function useConfirmDialog(): UseConfirmDialogReturn {
82
85
  process.env.NODE_ENV === "development" &&
83
86
  !isDialogElementRenderedRef.current
84
87
  ) {
85
- console.warn(
86
- "useConfirmDialog: confirm() was called but ConfirmDialogElement is not rendered. Add {ConfirmDialogElement} to your JSX."
87
- );
88
+ logger.warn("useConfirmDialog: confirm() was called but ConfirmDialogElement is not rendered. Add {ConfirmDialogElement} to your JSX.");
88
89
  }
89
90
 
90
91
  // If dialog is already open or a previous interaction is still
@@ -14,6 +14,9 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
14
14
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
15
15
  import { InjectionSpot } from '../injection/InjectionSpot'
16
16
  import { WidgetDataBatchProvider } from './widgetData'
17
+ import { createLogger } from '@open-mercato/shared/lib/logger'
18
+
19
+ const logger = createLogger('ui').child({ component: 'DashboardScreen' })
17
20
 
18
21
  type DashboardWidgetSize = 'sm' | 'md' | 'lg'
19
22
 
@@ -147,7 +150,7 @@ export function DashboardScreen() {
147
150
  setSettingsId(null)
148
151
  }
149
152
  } catch (err) {
150
- console.error('Failed to load dashboard layout', err)
153
+ logger.error('Failed to load dashboard layout', { err })
151
154
  if (getDashboardWidgets().length === 0) {
152
155
  setHasRegisteredWidgets(false)
153
156
  setLayout([])
@@ -237,7 +240,7 @@ export function DashboardScreen() {
237
240
  if (!call.ok) throw new Error(`Failed with status ${call.status}`)
238
241
  setError(null)
239
242
  } catch (err) {
240
- console.error('Failed to save layout', err)
243
+ logger.error('Failed to save layout', { err })
241
244
  setError(t('dashboard.saveError'))
242
245
  } finally {
243
246
  adjustSaving(-1)
@@ -256,7 +259,7 @@ export function DashboardScreen() {
256
259
  if (!call.ok) throw new Error(`Failed with status ${call.status}`)
257
260
  setError(null)
258
261
  } catch (err) {
259
- console.error('Failed to update widget settings', err)
262
+ logger.error('Failed to update widget settings', { err })
260
263
  setError(t('dashboard.saveError'))
261
264
  } finally {
262
265
  adjustSaving(-1)
@@ -574,7 +577,7 @@ function DashboardWidgetCard({
574
577
  })
575
578
  .catch((err) => {
576
579
  if (cancelled) return
577
- console.error('Failed to load widget module', err)
580
+ logger.error('Failed to load widget module', { err })
578
581
  setLoadError(t('dashboard.widget.loadError'))
579
582
  setLoading(false)
580
583
  })
@@ -615,7 +618,7 @@ function DashboardWidgetCard({
615
618
  try {
616
619
  return module.hydrateSettings(raw)
617
620
  } catch (err) {
618
- console.warn('Failed to hydrate widget settings', err)
621
+ logger.warn('Failed to hydrate widget settings', { err })
619
622
  return raw
620
623
  }
621
624
  }
@@ -628,7 +631,7 @@ function DashboardWidgetCard({
628
631
  try {
629
632
  raw = module.dehydrateSettings(next as never)
630
633
  } catch (err) {
631
- console.warn('Failed to dehydrate widget settings', err)
634
+ logger.warn('Failed to dehydrate widget settings', { err })
632
635
  }
633
636
  }
634
637
  onSettingsChange(raw)
@@ -1,6 +1,9 @@
1
1
  import type { ModuleDashboardWidgetEntry } from '@open-mercato/shared/modules/registry'
2
2
  import type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
3
3
  import { applyDashboardWidgetOverridesToEntries } from '@open-mercato/shared/modules/overrides'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+
6
+ const logger = createLogger('ui')
4
7
 
5
8
  type Entry = ModuleDashboardWidgetEntry
6
9
  type LoadedWidgetModule = DashboardWidgetModule<any>
@@ -21,7 +24,7 @@ function resolveRegistrationWaiters(entries: Entry[]) {
21
24
 
22
25
  export function registerDashboardWidgets(entries: Entry[]) {
23
26
  if (_dashboardWidgetEntries !== null && process.env.NODE_ENV === 'development') {
24
- console.debug('[Bootstrap] Dashboard widgets re-registered (this may occur during HMR)')
27
+ logger.debug('Dashboard widgets re-registered (this may occur during HMR)')
25
28
  }
26
29
  _dashboardWidgetEntries = applyDashboardWidgetOverridesToEntries(entries)
27
30
  entriesPromise = Promise.resolve(_dashboardWidgetEntries)
@@ -31,6 +31,9 @@ import { cn } from '@open-mercato/shared/lib/utils'
31
31
  import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
32
32
  import { MarkdownPreview } from '../markdown'
33
33
  import { useRegisteredComponent } from '../injection/useRegisteredComponent'
34
+ import { createLogger } from '@open-mercato/shared/lib/logger'
35
+
36
+ const logger = createLogger('ui').child({ component: 'CustomDataSection' })
34
37
 
35
38
  const isTestEnv =
36
39
  typeof process !== 'undefined' &&
@@ -536,7 +539,7 @@ function CustomDataSectionImpl({
536
539
  displays[option.value] = { label: option.label, href }
537
540
  })
538
541
  } catch (error) {
539
- console.debug('[CustomDataSection] Failed to load remote options for field', field.id, error)
542
+ logger.debug('Failed to load remote options for field', { fieldId: field.id, err: error })
540
543
  }
541
544
  }
542
545
 
@@ -552,7 +555,7 @@ function CustomDataSectionImpl({
552
555
  const fetchedDisplays = await fetchRelationRecordDisplays(definition.optionsUrl!, relation, unresolvedIds, abortController.signal)
553
556
  Object.assign(displays, fetchedDisplays)
554
557
  } catch (error) {
555
- console.debug('[CustomDataSection] Failed to fetch relation record displays for field', field.id, error)
558
+ logger.debug('Failed to fetch relation record displays for field', { fieldId: field.id, err: error })
556
559
  unresolvedIds.forEach((relationId) => {
557
560
  if (!displays[relationId]) {
558
561
  displays[relationId] = {
@@ -20,6 +20,9 @@ import { format } from 'date-fns/format'
20
20
  import { LoadingMessage } from './LoadingMessage'
21
21
  import { mapCrudServerErrorToFormErrors } from '../utils/serverErrors'
22
22
  import { MarkdownPreview } from '../markdown'
23
+ import { createLogger } from '@open-mercato/shared/lib/logger'
24
+
25
+ const logger = createLogger('ui')
23
26
 
24
27
  function resolveInlineErrorMessage(err: unknown, fallbackMessage: string): string {
25
28
  const { message, fieldErrors } = mapCrudServerErrorToFormErrors(err)
@@ -791,7 +794,7 @@ export function InlineSelectEditor({
791
794
  setEditing(false)
792
795
  } catch (err) {
793
796
  const message = err instanceof Error ? err.message : t('ui.detail.inline.error', 'Failed to save value.')
794
- console.error(message, err)
797
+ logger.error(message, { err })
795
798
  } finally {
796
799
  setSaving(false)
797
800
  }
@@ -8,6 +8,9 @@ import { DataLoader } from '@open-mercato/ui/primitives/DataLoader'
8
8
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
9
9
  import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
10
10
  import { useRegisteredComponent } from '../injection/useRegisteredComponent'
11
+ import { createLogger } from '@open-mercato/shared/lib/logger'
12
+
13
+ const logger = createLogger('ui')
11
14
 
12
15
  export type TagOption = {
13
16
  id: string
@@ -135,7 +138,7 @@ function TagsSectionImpl({
135
138
  syncFetchedOptions(fetched)
136
139
  return fetched.map((tag) => tag.label)
137
140
  } catch (err) {
138
- console.error('tags.section.loadSuggestions', err)
141
+ logger.error('Failed to load tag suggestions', { err })
139
142
  return []
140
143
  }
141
144
  },
@@ -16,6 +16,9 @@ import {
16
16
  type LoadedInjectionWidget,
17
17
  } from '@open-mercato/shared/modules/widgets/injection-loader'
18
18
  import { getWidgetSharedState } from './WidgetSharedState'
19
+ import { createLogger } from '@open-mercato/shared/lib/logger'
20
+
21
+ const logger = createLogger('ui').child({ component: 'InjectionSpot' })
19
22
 
20
23
  export type InjectionSpotProps<TContext = unknown, TData = unknown> = {
21
24
  spotId: InjectionSpotId
@@ -125,14 +128,14 @@ export function useInjectionWidgets<TContext = unknown>(
125
128
  await widget.module.eventHandlers.onLoad(widgetContext)
126
129
  onEventRef.current?.('onLoad', widget.widgetId)
127
130
  } catch (err) {
128
- console.error(`[InjectionSpot] Error in onLoad for widget ${widget.widgetId}:`, err)
131
+ logger.error('Error in onLoad for widget', { widgetId: widget.widgetId, err })
129
132
  }
130
133
  }
131
134
  }
132
135
  }
133
136
  } catch (err) {
134
137
  if (!mounted) return
135
- console.error(`[InjectionSpot] Failed to load widgets for spot ${spotId}:`, err)
138
+ logger.error('Failed to load widgets for spot', { spotId, err })
136
139
  setError(err instanceof Error ? err.message : String(err))
137
140
  } finally {
138
141
  if (mounted) setLoading(false)
@@ -182,7 +185,7 @@ export function InjectionSpot<TContext = unknown, TData = unknown>({
182
185
  }
183
186
 
184
187
  if (effectiveError) {
185
- console.error(`[InjectionSpot] Error loading widgets for spot ${spotId}:`, effectiveError)
188
+ logger.error('Error loading widgets for spot', { spotId, err: effectiveError })
186
189
  return null
187
190
  }
188
191
 
@@ -241,7 +244,7 @@ export function useInjectionSpotEvents<TContext = unknown, TData = unknown>(spot
241
244
  }))
242
245
  )
243
246
  } catch (err) {
244
- console.error(`[useInjectionSpotEvents] Failed to load widgets for spot ${spotId}:`, err)
247
+ logger.error('Failed to load widgets for spot', { hook: 'useInjectionSpotEvents', spotId, err })
245
248
  }
246
249
  }
247
250
  load()
@@ -358,7 +361,7 @@ export function useInjectionSpotEvents<TContext = unknown, TData = unknown>(spot
358
361
  pipelineData = handlerResult as TData
359
362
  }
360
363
  } catch (err) {
361
- console.error(`[useInjectionSpotEvents] Error in ${event} for widget ${widget.widgetId}:`, err)
364
+ logger.error('Error in event handler for widget', { hook: 'useInjectionSpotEvents', event, widgetId: widget.widgetId, err })
362
365
  }
363
366
  }
364
367
  return { ok: true, data: pipelineData, applyToForm }
@@ -404,7 +407,7 @@ export function useInjectionSpotEvents<TContext = unknown, TData = unknown>(spot
404
407
  if (event === 'onBeforeSave') {
405
408
  const normalized = normalizeBeforeSave(result as WidgetBeforeSaveResult)
406
409
  if (!normalized.ok) {
407
- console.log(`[useInjectionSpotEvents] Widget ${widget.widgetId} prevented ${event}`)
410
+ logger.info('Widget prevented event', { hook: 'useInjectionSpotEvents', widgetId: widget.widgetId, event })
408
411
  return normalized
409
412
  }
410
413
  if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {
@@ -415,7 +418,7 @@ export function useInjectionSpotEvents<TContext = unknown, TData = unknown>(spot
415
418
  if (event === 'onBeforeDelete') {
416
419
  const normalized = normalizeBeforeDelete(result as WidgetBeforeDeleteResult)
417
420
  if (!normalized.ok) {
418
- console.log(`[useInjectionSpotEvents] Widget ${widget.widgetId} prevented ${event}`)
421
+ logger.info('Widget prevented event', { hook: 'useInjectionSpotEvents', widgetId: widget.widgetId, event })
419
422
  return normalized
420
423
  }
421
424
  if (normalized.requestHeaders && Object.keys(normalized.requestHeaders).length > 0) {
@@ -442,7 +445,7 @@ export function useInjectionSpotEvents<TContext = unknown, TData = unknown>(spot
442
445
  }
443
446
  }
444
447
  } catch (err) {
445
- console.error(`[useInjectionSpotEvents] Error in ${event} for widget ${widget.widgetId}:`, err)
448
+ logger.error('Error in event handler for widget', { hook: 'useInjectionSpotEvents', event, widgetId: widget.widgetId, err })
446
449
  if (event === 'onBeforeSave' || event === 'onBeforeDelete' || event === 'onBeforeNavigate') {
447
450
  const message =
448
451
  err instanceof Error
@@ -2,6 +2,9 @@
2
2
  import { useEffect, useRef } from 'react'
3
3
  import type { AppEventPayload } from '@open-mercato/shared/modules/widgets/injection'
4
4
  import { APP_EVENT_DOM_NAME } from './useAppEvent'
5
+ import { createLogger } from '@open-mercato/shared/lib/logger'
6
+
7
+ const logger = createLogger('ui').child({ component: 'EventBridge' })
5
8
 
6
9
  const SSE_ENDPOINT = '/api/events/stream'
7
10
  const HEARTBEAT_TIMEOUT = 45_000 // Expect heartbeat every 30s, allow 45s grace
@@ -60,7 +63,7 @@ export function useEventBridge(): void {
60
63
  function resetHeartbeatTimer() {
61
64
  if (heartbeatTimer.current) clearTimeout(heartbeatTimer.current)
62
65
  heartbeatTimer.current = setTimeout(() => {
63
- console.warn('[EventBridge] Heartbeat timeout — reconnecting')
66
+ logger.warn('Heartbeat timeout — reconnecting')
64
67
  disconnect()
65
68
  scheduleReconnect()
66
69
  }, HEARTBEAT_TIMEOUT)
@@ -6,6 +6,9 @@ import { GLOBAL_MUTATION_INJECTION_SPOT_ID, dispatchBackendMutationError } from
6
6
  import { withScopedApiRequestHeaders } from '../utils/apiCall'
7
7
  import { surfaceRecordConflict } from '../conflicts'
8
8
  import { useT } from '@open-mercato/shared/lib/i18n/context'
9
+ import { createLogger } from '@open-mercato/shared/lib/logger'
10
+
11
+ const logger = createLogger('ui').child({ component: 'useGuardedMutation' })
9
12
 
10
13
  type GuardedMutationContext = Record<string, unknown>
11
14
 
@@ -83,7 +86,7 @@ export function useGuardedMutation<TContext extends GuardedMutationContext>({
83
86
  try {
84
87
  await triggerEvent('onAfterSave', payload, context)
85
88
  } catch (error) {
86
- console.error('[useGuardedMutation] Error in onAfterSave injection event:', error)
89
+ logger.error('Error in onAfterSave injection event', { err: error })
87
90
  }
88
91
 
89
92
  return result
@@ -8,6 +8,9 @@ import {
8
8
  subscribeToInjectionRegistryChanges,
9
9
  type LoadedInjectionDataWidget,
10
10
  } from '@open-mercato/shared/modules/widgets/injection-loader'
11
+ import { createLogger } from '@open-mercato/shared/lib/logger'
12
+
13
+ const logger = createLogger('ui').child({ component: 'useInjectionDataWidgets' })
11
14
 
12
15
  export function useInjectionDataWidgets(spotId: InjectionSpotId): {
13
16
  widgets: LoadedInjectionDataWidget[]
@@ -40,7 +43,7 @@ export function useInjectionDataWidgets(spotId: InjectionSpotId): {
40
43
  setWidgets(loaded)
41
44
  } catch (loadError) {
42
45
  if (!mounted) return
43
- console.error(`[useInjectionDataWidgets] Failed to load widgets for spot ${spotId}:`, loadError)
46
+ logger.error('Failed to load widgets for spot', { spotId, err: loadError })
44
47
  setError(loadError instanceof Error ? loadError.message : String(loadError))
45
48
  setWidgets([])
46
49
  } finally {
@@ -4,6 +4,9 @@ import * as React from 'react'
4
4
  import type { ComponentType } from 'react'
5
5
  import { getComponentEntry, getComponentOverrides } from '@open-mercato/shared/modules/widgets/component-registry'
6
6
  import { useOverrideUserFeatures } from './ComponentOverrideProvider'
7
+ import { createLogger } from '@open-mercato/shared/lib/logger'
8
+
9
+ const logger = createLogger('ui').child({ component: 'useRegisteredComponent' })
7
10
 
8
11
  class ReplacementErrorBoundary extends React.Component<
9
12
  { fallback: React.ReactNode; onError: (error: unknown) => void; children: React.ReactNode },
@@ -38,7 +41,7 @@ export function useRegisteredComponent<TProps>(
38
41
  const original = (entry?.component as ComponentType<TProps> | undefined) ?? fallback ?? null
39
42
  if (!original) {
40
43
  if (process.env.NODE_ENV !== 'production' && !fallback) {
41
- console.warn(`[UMES] Component "${componentId}" is not registered.`)
44
+ logger.warn('Component is not registered', { componentId })
42
45
  }
43
46
  const Missing = () => null
44
47
  return Missing as ComponentType<TProps>
@@ -46,9 +49,7 @@ export function useRegisteredComponent<TProps>(
46
49
  const overrides = getComponentOverrides(componentId, userFeatures)
47
50
  const replacementOverrides = overrides.filter((override) => 'replacement' in override)
48
51
  if (process.env.NODE_ENV !== 'production' && replacementOverrides.length > 1) {
49
- console.warn(
50
- `[UMES] Multiple replacements registered for "${componentId}". Highest-priority replacement is applied.`,
51
- )
52
+ logger.warn('Multiple replacements registered; highest-priority replacement is applied', { componentId })
52
53
  }
53
54
 
54
55
  let replacement: ComponentType<TProps> | null = null
@@ -78,10 +79,7 @@ export function useRegisteredComponent<TProps>(
78
79
  ) {
79
80
  const validation = replacementOverride.propsSchema.safeParse(transformed)
80
81
  if (!validation.success) {
81
- console.error(
82
- `[UMES] Props schema validation failed for replacement "${componentId}" from module "${replacementOverride.metadata?.module ?? 'unknown'}"`,
83
- validation.error.format(),
84
- )
82
+ logger.error('Props schema validation failed for replacement', { componentId, module: replacementOverride.metadata?.module ?? 'unknown', issues: validation.error.format() })
85
83
  return Fallback
86
84
  }
87
85
  }
@@ -90,7 +88,7 @@ export function useRegisteredComponent<TProps>(
90
88
  fallback={Fallback}
91
89
  onError={(error) => {
92
90
  const replacementModule = overrides.find((override) => 'replacement' in override)?.metadata?.module ?? 'unknown'
93
- console.error(`[UMES] Component replacement failed for "${componentId}" from module "${replacementModule}"`, error)
91
+ logger.error('Component replacement failed', { componentId, module: replacementModule, err: error })
94
92
  }}
95
93
  >
96
94
  {React.createElement(wrapped as React.ComponentType<Record<string, unknown>>, transformed as Record<string, unknown>)}
@@ -1,6 +1,9 @@
1
1
  import type { ModuleInjectionWidgetEntry } from '@open-mercato/shared/modules/registry'
2
2
  import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'
3
3
  import { applyInjectionWidgetOverridesToEntries } from '@open-mercato/shared/modules/overrides'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+
6
+ const logger = createLogger('ui')
4
7
 
5
8
  type Entry = ModuleInjectionWidgetEntry
6
9
 
@@ -9,7 +12,7 @@ let _injectionWidgetEntries: Entry[] | null = null
9
12
 
10
13
  export function registerInjectionWidgets(entries: Entry[]) {
11
14
  if (_injectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {
12
- console.debug('[Bootstrap] Injection widgets re-registered (this may occur during HMR)')
15
+ logger.debug('Injection widgets re-registered (this may occur during HMR)')
13
16
  }
14
17
  _injectionWidgetEntries = applyInjectionWidgetOverridesToEntries(entries)
15
18
  }