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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -1
  3. package/__integration__/TC-AI-UI-003-aichat-registry.spec.tsx +204 -0
  4. package/dist/ai/AiAssistantLauncher.js +596 -0
  5. package/dist/ai/AiAssistantLauncher.js.map +7 -0
  6. package/dist/ai/AiChat.js +1092 -0
  7. package/dist/ai/AiChat.js.map +7 -0
  8. package/dist/ai/AiChatSessions.js +297 -0
  9. package/dist/ai/AiChatSessions.js.map +7 -0
  10. package/dist/ai/AiDock.js +347 -0
  11. package/dist/ai/AiDock.js.map +7 -0
  12. package/dist/ai/AiMessageContent.js +369 -0
  13. package/dist/ai/AiMessageContent.js.map +7 -0
  14. package/dist/ai/ChatPaneTabs.js +251 -0
  15. package/dist/ai/ChatPaneTabs.js.map +7 -0
  16. package/dist/ai/index.js +115 -0
  17. package/dist/ai/index.js.map +7 -0
  18. package/dist/ai/parts/ConfirmationCard.js +211 -0
  19. package/dist/ai/parts/ConfirmationCard.js.map +7 -0
  20. package/dist/ai/parts/FieldDiffCard.js +119 -0
  21. package/dist/ai/parts/FieldDiffCard.js.map +7 -0
  22. package/dist/ai/parts/MutationPreviewCard.js +224 -0
  23. package/dist/ai/parts/MutationPreviewCard.js.map +7 -0
  24. package/dist/ai/parts/MutationResultCard.js +240 -0
  25. package/dist/ai/parts/MutationResultCard.js.map +7 -0
  26. package/dist/ai/parts/approval-cards-map.js +15 -0
  27. package/dist/ai/parts/approval-cards-map.js.map +7 -0
  28. package/dist/ai/parts/index.js +24 -0
  29. package/dist/ai/parts/index.js.map +7 -0
  30. package/dist/ai/parts/pending-action-api.js +60 -0
  31. package/dist/ai/parts/pending-action-api.js.map +7 -0
  32. package/dist/ai/parts/types.js +1 -0
  33. package/dist/ai/parts/types.js.map +7 -0
  34. package/dist/ai/parts/useAiPendingActionPolling.js +126 -0
  35. package/dist/ai/parts/useAiPendingActionPolling.js.map +7 -0
  36. package/dist/ai/records/ActivityCard.js +83 -0
  37. package/dist/ai/records/ActivityCard.js.map +7 -0
  38. package/dist/ai/records/CompanyCard.js +81 -0
  39. package/dist/ai/records/CompanyCard.js.map +7 -0
  40. package/dist/ai/records/DealCard.js +76 -0
  41. package/dist/ai/records/DealCard.js.map +7 -0
  42. package/dist/ai/records/PersonCard.js +68 -0
  43. package/dist/ai/records/PersonCard.js.map +7 -0
  44. package/dist/ai/records/ProductCard.js +68 -0
  45. package/dist/ai/records/ProductCard.js.map +7 -0
  46. package/dist/ai/records/RecordCard.js +29 -0
  47. package/dist/ai/records/RecordCard.js.map +7 -0
  48. package/dist/ai/records/RecordCardShell.js +103 -0
  49. package/dist/ai/records/RecordCardShell.js.map +7 -0
  50. package/dist/ai/records/index.js +31 -0
  51. package/dist/ai/records/index.js.map +7 -0
  52. package/dist/ai/records/registry.js +51 -0
  53. package/dist/ai/records/registry.js.map +7 -0
  54. package/dist/ai/records/types.js +1 -0
  55. package/dist/ai/records/types.js.map +7 -0
  56. package/dist/ai/ui-part-registry.js +112 -0
  57. package/dist/ai/ui-part-registry.js.map +7 -0
  58. package/dist/ai/ui-part-slots.js +14 -0
  59. package/dist/ai/ui-part-slots.js.map +7 -0
  60. package/dist/ai/ui-parts/pending-phase3-placeholder.js +35 -0
  61. package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +7 -0
  62. package/dist/ai/upload-adapter.js +256 -0
  63. package/dist/ai/upload-adapter.js.map +7 -0
  64. package/dist/ai/useAiChat.js +549 -0
  65. package/dist/ai/useAiChat.js.map +7 -0
  66. package/dist/ai/useAiChatUpload.js +127 -0
  67. package/dist/ai/useAiChatUpload.js.map +7 -0
  68. package/dist/ai/useAiShortcuts.js +43 -0
  69. package/dist/ai/useAiShortcuts.js.map +7 -0
  70. package/dist/backend/AppShell.js +8 -4
  71. package/dist/backend/AppShell.js.map +2 -2
  72. package/dist/backend/BackendChromeProvider.js +2 -0
  73. package/dist/backend/BackendChromeProvider.js.map +2 -2
  74. package/dist/backend/DataTable.js +19 -2
  75. package/dist/backend/DataTable.js.map +2 -2
  76. package/dist/backend/FilterBar.js +19 -15
  77. package/dist/backend/FilterBar.js.map +2 -2
  78. package/dist/backend/dashboard/DashboardScreen.js +31 -3
  79. package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
  80. package/dist/backend/injection/spotIds.js +6 -0
  81. package/dist/backend/injection/spotIds.js.map +2 -2
  82. package/dist/backend/notifications/useNotificationEffect.js +38 -2
  83. package/dist/backend/notifications/useNotificationEffect.js.map +2 -2
  84. package/dist/index.js +1 -0
  85. package/dist/index.js.map +2 -2
  86. package/jest.config.cjs +7 -1
  87. package/jest.markdown-mock.tsx +7 -0
  88. package/package.json +10 -4
  89. package/src/ai/AiAssistantLauncher.tsx +805 -0
  90. package/src/ai/AiChat.tsx +1483 -0
  91. package/src/ai/AiChatSessions.tsx +429 -0
  92. package/src/ai/AiDock.tsx +505 -0
  93. package/src/ai/AiMessageContent.tsx +515 -0
  94. package/src/ai/ChatPaneTabs.tsx +310 -0
  95. package/src/ai/__tests__/AiChat.conversation.test.tsx +160 -0
  96. package/src/ai/__tests__/AiChat.debug.test.tsx +152 -0
  97. package/src/ai/__tests__/AiChat.registry.test.tsx +213 -0
  98. package/src/ai/__tests__/AiChat.test.tsx +257 -0
  99. package/src/ai/__tests__/AiDock.test.tsx +124 -0
  100. package/src/ai/__tests__/AiMessageContent.test.ts +111 -0
  101. package/src/ai/__tests__/ui-part-registry.test.ts +199 -0
  102. package/src/ai/__tests__/ui-part-slots.test.ts +43 -0
  103. package/src/ai/__tests__/upload-adapter.test.ts +213 -0
  104. package/src/ai/__tests__/useAiChatUpload.test.tsx +163 -0
  105. package/src/ai/__tests__/useAiShortcuts.test.tsx +100 -0
  106. package/src/ai/index.ts +125 -0
  107. package/src/ai/parts/ConfirmationCard.tsx +310 -0
  108. package/src/ai/parts/FieldDiffCard.tsx +173 -0
  109. package/src/ai/parts/MutationPreviewCard.tsx +302 -0
  110. package/src/ai/parts/MutationResultCard.tsx +360 -0
  111. package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +169 -0
  112. package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +74 -0
  113. package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +177 -0
  114. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +127 -0
  115. package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +151 -0
  116. package/src/ai/parts/approval-cards-map.ts +24 -0
  117. package/src/ai/parts/index.ts +27 -0
  118. package/src/ai/parts/pending-action-api.ts +123 -0
  119. package/src/ai/parts/types.ts +84 -0
  120. package/src/ai/parts/useAiPendingActionPolling.ts +210 -0
  121. package/src/ai/records/ActivityCard.tsx +102 -0
  122. package/src/ai/records/CompanyCard.tsx +89 -0
  123. package/src/ai/records/DealCard.tsx +85 -0
  124. package/src/ai/records/PersonCard.tsx +77 -0
  125. package/src/ai/records/ProductCard.tsx +83 -0
  126. package/src/ai/records/RecordCard.tsx +37 -0
  127. package/src/ai/records/RecordCardShell.tsx +169 -0
  128. package/src/ai/records/index.ts +30 -0
  129. package/src/ai/records/registry.tsx +80 -0
  130. package/src/ai/records/types.ts +90 -0
  131. package/src/ai/ui-part-registry.ts +233 -0
  132. package/src/ai/ui-part-slots.ts +32 -0
  133. package/src/ai/ui-parts/pending-phase3-placeholder.tsx +50 -0
  134. package/src/ai/upload-adapter.ts +421 -0
  135. package/src/ai/useAiChat.ts +865 -0
  136. package/src/ai/useAiChatUpload.ts +180 -0
  137. package/src/ai/useAiShortcuts.ts +79 -0
  138. package/src/backend/AppShell.tsx +12 -5
  139. package/src/backend/BackendChromeProvider.tsx +2 -0
  140. package/src/backend/DataTable.tsx +20 -1
  141. package/src/backend/FilterBar.tsx +26 -13
  142. package/src/backend/__tests__/BackendChromeProvider.test.tsx +45 -0
  143. package/src/backend/dashboard/DashboardScreen.tsx +38 -3
  144. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +24 -1
  145. package/src/backend/injection/spotIds.ts +6 -0
  146. package/src/backend/notifications/__tests__/useNotificationEffect.test.tsx +77 -0
  147. package/src/backend/notifications/useNotificationEffect.ts +47 -2
  148. package/src/index.ts +1 -0
@@ -0,0 +1,77 @@
1
+ import { act, renderHook } from '@testing-library/react'
2
+ import type { NotificationDto } from '@open-mercato/shared/modules/notifications/types'
3
+ import { APP_EVENT_DOM_NAME } from '../../injection/useAppEvent'
4
+ import {
5
+ __resetNotificationDispatcherForTests,
6
+ dispatchNotificationHandlers,
7
+ } from '../NotificationDispatcher'
8
+ import { useNotificationEffect } from '../useNotificationEffect'
9
+
10
+ function makeNotification(id: string, type: string): NotificationDto {
11
+ return {
12
+ id,
13
+ type,
14
+ title: `title-${id}`,
15
+ severity: 'info',
16
+ status: 'unread',
17
+ actions: [],
18
+ createdAt: new Date().toISOString(),
19
+ }
20
+ }
21
+
22
+ function dispatchNotificationCreated(notification: NotificationDto) {
23
+ window.dispatchEvent(
24
+ new CustomEvent(APP_EVENT_DOM_NAME, {
25
+ detail: {
26
+ id: 'notifications.notification.created',
27
+ payload: { notification },
28
+ timestamp: Date.now(),
29
+ organizationId: 'org-1',
30
+ },
31
+ }),
32
+ )
33
+ }
34
+
35
+ function runtime() {
36
+ return {
37
+ features: [],
38
+ currentPath: '/backend/umes-next-phases',
39
+ refreshNotifications: jest.fn(),
40
+ navigate: jest.fn(),
41
+ markAsRead: jest.fn(async () => {}),
42
+ dismiss: jest.fn(async () => {}),
43
+ }
44
+ }
45
+
46
+ describe('useNotificationEffect', () => {
47
+ beforeEach(() => {
48
+ __resetNotificationDispatcherForTests()
49
+ })
50
+
51
+ it('runs component-scoped effects from notification-created bridge events', () => {
52
+ const calls: string[] = []
53
+ const notification = makeNotification('n1', 'example.umes.actionable')
54
+
55
+ renderHook(() => useNotificationEffect('example.umes.actionable', (item) => calls.push(item.id)))
56
+
57
+ act(() => {
58
+ dispatchNotificationCreated(notification)
59
+ })
60
+
61
+ expect(calls).toEqual(['n1'])
62
+ })
63
+
64
+ it('dedupes when the dispatcher and bridge deliver the same notification', () => {
65
+ const calls: string[] = []
66
+ const notification = makeNotification('n1', 'example.umes.actionable')
67
+
68
+ renderHook(() => useNotificationEffect('example.umes.actionable', (item) => calls.push(item.id)))
69
+
70
+ act(() => {
71
+ dispatchNotificationHandlers([notification], runtime())
72
+ dispatchNotificationCreated(notification)
73
+ })
74
+
75
+ expect(calls).toEqual(['n1'])
76
+ })
77
+ })
@@ -2,16 +2,61 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import type { NotificationDto } from '@open-mercato/shared/modules/notifications/types'
5
+ import type { AppEventPayload } from '@open-mercato/shared/modules/widgets/injection'
6
+ import { useAppEvent } from '../injection/useAppEvent'
5
7
  import { subscribeNotificationEffects } from './NotificationDispatcher'
6
8
 
9
+ const NOTIFICATION_CREATED_EVENT = 'notifications.notification.created'
10
+ const MAX_SEEN_IDS = 200
11
+
12
+ function matchesType(pattern: string | string[], type: string): boolean {
13
+ const patterns = Array.isArray(pattern) ? pattern : [pattern]
14
+ return patterns.some((current) => {
15
+ if (current === '*') return true
16
+ if (current.endsWith('.*')) return type.startsWith(current.slice(0, -1))
17
+ return current === type
18
+ })
19
+ }
20
+
21
+ function readNotificationFromEvent(event: AppEventPayload): NotificationDto | null {
22
+ const payload = event.payload
23
+ if (!payload || typeof payload !== 'object') return null
24
+ const notification = (payload as { notification?: unknown }).notification
25
+ if (!notification || typeof notification !== 'object') return null
26
+ const candidate = notification as Partial<NotificationDto>
27
+ if (typeof candidate.id !== 'string' || typeof candidate.type !== 'string') return null
28
+ return candidate as NotificationDto
29
+ }
30
+
7
31
  export function useNotificationEffect(
8
32
  notificationType: string | string[],
9
33
  effect: (notification: NotificationDto) => void,
10
34
  deps: React.DependencyList = [],
11
35
  ) {
36
+ const seenIdsRef = React.useRef<string[]>([])
37
+
38
+ const handleNotification = React.useCallback((notification: NotificationDto) => {
39
+ if (!matchesType(notificationType, notification.type)) return
40
+ if (seenIdsRef.current.includes(notification.id)) return
41
+ seenIdsRef.current = [notification.id, ...seenIdsRef.current.filter((id) => id !== notification.id)]
42
+ .slice(0, MAX_SEEN_IDS)
43
+ effect(notification)
44
+ // eslint-disable-next-line react-hooks/exhaustive-deps
45
+ }, [notificationType, ...deps])
46
+
12
47
  React.useEffect(() => {
13
- const unsubscribe = subscribeNotificationEffects(notificationType, effect)
48
+ const unsubscribe = subscribeNotificationEffects(notificationType, handleNotification)
14
49
  return unsubscribe
15
50
  // eslint-disable-next-line react-hooks/exhaustive-deps
16
- }, [notificationType, ...deps])
51
+ }, [notificationType, handleNotification])
52
+
53
+ useAppEvent(
54
+ NOTIFICATION_CREATED_EVENT,
55
+ (event) => {
56
+ const notification = readNotificationFromEvent(event)
57
+ if (!notification) return
58
+ handleNotification(notification)
59
+ },
60
+ [handleNotification],
61
+ )
17
62
  }
package/src/index.ts CHANGED
@@ -47,3 +47,4 @@ export * from './primitives/Notice'
47
47
  export * from './primitives/ErrorNotice'
48
48
  export * from './primitives/dialog'
49
49
  export * from './primitives/progress'
50
+ export * from './ai'