@open-mercato/ui 0.6.4-develop.3944.1.4100aa7fbe → 0.6.4-develop.3962.1.70f30e284c

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 (51) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/ai/AiChat.js +4 -2
  3. package/dist/ai/AiChat.js.map +2 -2
  4. package/dist/ai/AiChatSessions.js +30 -9
  5. package/dist/ai/AiChatSessions.js.map +2 -2
  6. package/dist/ai/AiDock.js +6 -1
  7. package/dist/ai/AiDock.js.map +2 -2
  8. package/dist/ai/conversation-store.js +2 -1
  9. package/dist/ai/conversation-store.js.map +2 -2
  10. package/dist/ai/useAiChat.js +15 -4
  11. package/dist/ai/useAiChat.js.map +2 -2
  12. package/dist/backend/charts/BarChart.js +19 -76
  13. package/dist/backend/charts/BarChart.js.map +2 -2
  14. package/dist/backend/charts/BarChartImpl.js +91 -0
  15. package/dist/backend/charts/BarChartImpl.js.map +7 -0
  16. package/dist/backend/charts/LineChart.js +18 -91
  17. package/dist/backend/charts/LineChart.js.map +2 -2
  18. package/dist/backend/charts/LineChartImpl.js +112 -0
  19. package/dist/backend/charts/LineChartImpl.js.map +7 -0
  20. package/dist/backend/charts/PieChart.js +17 -62
  21. package/dist/backend/charts/PieChart.js.map +2 -2
  22. package/dist/backend/charts/PieChartImpl.js +76 -0
  23. package/dist/backend/charts/PieChartImpl.js.map +7 -0
  24. package/dist/backend/dashboard/DashboardScreen.js +9 -1
  25. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  26. package/dist/backend/dashboard/widgetRegistry.js +18 -2
  27. package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
  28. package/dist/backend/injection/useInjectionDataWidgets.js +12 -2
  29. package/dist/backend/injection/useInjectionDataWidgets.js.map +2 -2
  30. package/package.json +3 -3
  31. package/src/ai/AiChat.tsx +10 -0
  32. package/src/ai/AiChatSessions.tsx +54 -9
  33. package/src/ai/AiDock.tsx +11 -0
  34. package/src/ai/__tests__/AiChat.conversation.test.tsx +97 -0
  35. package/src/ai/__tests__/AiChatSessions.test.tsx +228 -0
  36. package/src/ai/__tests__/AiDock.test.tsx +31 -1
  37. package/src/ai/__tests__/conversation-store.test.ts +74 -1
  38. package/src/ai/conversation-store.ts +18 -2
  39. package/src/ai/useAiChat.ts +35 -4
  40. package/src/backend/__tests__/lazy-heavy-libraries.test.ts +53 -0
  41. package/src/backend/charts/BarChart.tsx +21 -72
  42. package/src/backend/charts/BarChartImpl.tsx +99 -0
  43. package/src/backend/charts/LineChart.tsx +23 -85
  44. package/src/backend/charts/LineChartImpl.tsx +120 -0
  45. package/src/backend/charts/PieChart.tsx +19 -60
  46. package/src/backend/charts/PieChartImpl.tsx +82 -0
  47. package/src/backend/dashboard/DashboardScreen.tsx +9 -1
  48. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +45 -0
  49. package/src/backend/dashboard/__tests__/widgetRegistry.test.ts +60 -0
  50. package/src/backend/dashboard/widgetRegistry.ts +23 -5
  51. package/src/backend/injection/useInjectionDataWidgets.ts +18 -2
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import type { ModuleDashboardWidgetEntry } from '@open-mercato/shared/modules/registry'
6
+
7
+ function createEntry(loader = jest.fn().mockResolvedValue({ Widget: () => null })): ModuleDashboardWidgetEntry {
8
+ return {
9
+ moduleId: 'example',
10
+ key: 'example.dashboard.widget',
11
+ source: 'app',
12
+ loader,
13
+ }
14
+ }
15
+
16
+ describe('dashboard widget registry', () => {
17
+ beforeEach(() => {
18
+ jest.resetModules()
19
+ })
20
+
21
+ it('waits for client bootstrap registration before resolving widget modules', async () => {
22
+ const { loadDashboardWidgetModule, registerDashboardWidgets } = await import('../widgetRegistry')
23
+ const loader = jest.fn().mockResolvedValue({ Widget: () => null })
24
+ const pending = loadDashboardWidgetModule('example.dashboard.widget')
25
+ let resolved = false
26
+ pending.then(() => {
27
+ resolved = true
28
+ })
29
+
30
+ await Promise.resolve()
31
+ expect(resolved).toBe(false)
32
+ expect(loader).not.toHaveBeenCalled()
33
+
34
+ registerDashboardWidgets([createEntry(loader)])
35
+
36
+ await expect(pending).resolves.toEqual({ Widget: expect.any(Function) })
37
+ expect(loader).toHaveBeenCalledTimes(1)
38
+ })
39
+
40
+ it('uses the latest entries after dashboard widgets are re-registered', async () => {
41
+ const { loadDashboardWidgetModule, registerDashboardWidgets } = await import('../widgetRegistry')
42
+ const firstLoader = jest.fn().mockResolvedValue({ Widget: () => null })
43
+ const secondLoader = jest.fn().mockResolvedValue({ Widget: () => null })
44
+
45
+ registerDashboardWidgets([createEntry(firstLoader)])
46
+ await loadDashboardWidgetModule('example.dashboard.widget')
47
+
48
+ registerDashboardWidgets([
49
+ {
50
+ ...createEntry(secondLoader),
51
+ key: 'example.dashboard.other',
52
+ },
53
+ ])
54
+
55
+ await loadDashboardWidgetModule('example.dashboard.other')
56
+
57
+ expect(firstLoader).toHaveBeenCalledTimes(1)
58
+ expect(secondLoader).toHaveBeenCalledTimes(1)
59
+ })
60
+ })
@@ -3,15 +3,30 @@ import type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboa
3
3
  import { applyDashboardWidgetOverridesToEntries } from '@open-mercato/shared/modules/overrides'
4
4
 
5
5
  type Entry = ModuleDashboardWidgetEntry
6
+ type LoadedWidgetModule = DashboardWidgetModule<any>
7
+
8
+ const cache = new Map<string, Promise<LoadedWidgetModule>>()
6
9
 
7
10
  // Registration pattern for publishable packages
8
11
  let _dashboardWidgetEntries: Entry[] | null = null
12
+ let registrationWaiters: Array<(entries: Entry[]) => void> = []
13
+
14
+ function resolveRegistrationWaiters(entries: Entry[]) {
15
+ const waiters = registrationWaiters
16
+ registrationWaiters = []
17
+ for (const resolve of waiters) {
18
+ resolve(entries)
19
+ }
20
+ }
9
21
 
10
22
  export function registerDashboardWidgets(entries: Entry[]) {
11
23
  if (_dashboardWidgetEntries !== null && process.env.NODE_ENV === 'development') {
12
24
  console.debug('[Bootstrap] Dashboard widgets re-registered (this may occur during HMR)')
13
25
  }
14
26
  _dashboardWidgetEntries = applyDashboardWidgetOverridesToEntries(entries)
27
+ entriesPromise = Promise.resolve(_dashboardWidgetEntries)
28
+ cache.clear()
29
+ resolveRegistrationWaiters(_dashboardWidgetEntries)
15
30
  }
16
31
 
17
32
  export function getDashboardWidgets(): Entry[] {
@@ -28,8 +43,15 @@ export function getDashboardWidgets(): Entry[] {
28
43
  let entriesPromise: Promise<Entry[]> | null = null
29
44
 
30
45
  async function getEntries(): Promise<Entry[]> {
46
+ if (_dashboardWidgetEntries) {
47
+ return _dashboardWidgetEntries
48
+ }
31
49
  if (!entriesPromise) {
32
- const promise = Promise.resolve().then(() => getDashboardWidgets())
50
+ const promise = typeof window !== 'undefined'
51
+ ? new Promise<Entry[]>((resolve) => {
52
+ registrationWaiters.push(resolve)
53
+ })
54
+ : Promise.resolve().then(() => getDashboardWidgets())
33
55
  entriesPromise = promise.catch((err) => {
34
56
  // Clear cache on error so next call can retry after registration
35
57
  if (entriesPromise === promise) {
@@ -41,10 +63,6 @@ async function getEntries(): Promise<Entry[]> {
41
63
  return entriesPromise
42
64
  }
43
65
 
44
- type LoadedWidgetModule = DashboardWidgetModule<any>
45
-
46
- const cache = new Map<string, Promise<LoadedWidgetModule>>()
47
-
48
66
  async function findEntry(loaderKey: string): Promise<Entry | undefined> {
49
67
  const entries = await getEntries()
50
68
  return entries.find((entry) => entry.key === loaderKey)
@@ -2,7 +2,12 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import type { InjectionSpotId } from '@open-mercato/shared/modules/widgets/injection'
5
- import { loadInjectionDataWidgetsForSpot, type LoadedInjectionDataWidget } from '@open-mercato/shared/modules/widgets/injection-loader'
5
+ import {
6
+ loadInjectionDataWidgetsForSpot,
7
+ getInjectionRegistryVersion,
8
+ subscribeToInjectionRegistryChanges,
9
+ type LoadedInjectionDataWidget,
10
+ } from '@open-mercato/shared/modules/widgets/injection-loader'
6
11
 
7
12
  export function useInjectionDataWidgets(spotId: InjectionSpotId): {
8
13
  widgets: LoadedInjectionDataWidget[]
@@ -12,6 +17,17 @@ export function useInjectionDataWidgets(spotId: InjectionSpotId): {
12
17
  const [widgets, setWidgets] = React.useState<LoadedInjectionDataWidget[]>([])
13
18
  const [isLoading, setIsLoading] = React.useState(true)
14
19
  const [error, setError] = React.useState<string | null>(null)
20
+ // Re-load when the injection registry is (re-)populated. With the async
21
+ // ClientBootstrap, registration can land after this hook first runs; the
22
+ // version bump triggers a reload so injected menus/columns/fields appear
23
+ // without requiring an unrelated re-render.
24
+ const [registryVersion, setRegistryVersion] = React.useState(() => getInjectionRegistryVersion())
25
+
26
+ React.useEffect(() => {
27
+ return subscribeToInjectionRegistryChanges(() => {
28
+ setRegistryVersion(getInjectionRegistryVersion())
29
+ })
30
+ }, [])
15
31
 
16
32
  React.useEffect(() => {
17
33
  let mounted = true
@@ -35,7 +51,7 @@ export function useInjectionDataWidgets(spotId: InjectionSpotId): {
35
51
  return () => {
36
52
  mounted = false
37
53
  }
38
- }, [spotId])
54
+ }, [spotId, registryVersion])
39
55
 
40
56
  return { widgets, isLoading, error }
41
57
  }