@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
@@ -1,3 +1,3 @@
1
1
  Generated lucide registry with 134 icons -> /home/runner/work/open-mercato/open-mercato/packages/ui/src/backend/icons/lucideRegistry.generated.tsx
2
- [build:ui] found 277 entry points
2
+ [build:ui] found 310 entry points
3
3
  [build:ui] built successfully
package/AGENTS.md CHANGED
@@ -221,7 +221,8 @@ const leadTagMap: TagMap<'customer' | 'hot' | 'inactive' | 'renewal'> = {
221
221
 
222
222
  - Use `DataTable` as the default list view.
223
223
  - For wide list views where rightmost `rowActions` can scroll out of view, enable `stickyActionsColumn` on the host `DataTable`; keep it opt-in instead of making all actions columns sticky by default.
224
- - DataTable extension spots include: `data-table:<tableId>:columns`, `:row-actions`, `:bulk-actions`, `:filters` (in addition to `:header`/`:footer`).
224
+ - DataTable extension spots include: `data-table:<tableId>:columns`, `:row-actions`, `:bulk-actions`, `:filters`, `:search-trailing`, `:toolbar` (in addition to `:header`/`:footer`).
225
+ - `:search-trailing` renders inside `FilterBar`, immediately to the right of the search input on the same row. Reserve it for **compact triggers** (AI assistants, saved-view shortcuts, focus-mode toggles) — full-width / multi-action toolbars belong in `:toolbar` or `:header`. The slot is suppressed automatically when the host DataTable does not render a search input. Use `Button variant="outline"` (default size, h-9, `rounded-md`) with a single leading icon plus a short caption (e.g. `AI`) so the trigger matches the search input's `h-9` row height and the rest of the toolbar's rounded-rectangle button radius. Resolve the spot ID via `DataTableInjectionSpots.searchTrailing(tableId)` from `@open-mercato/ui/backend/injection/spotIds`.
225
226
  - Populate `columns` with explicit renderers and set `meta.truncate`/`meta.maxWidth` where truncation is needed.
226
227
  - For filters, use `FilterBar`/`FilterOverlay` with async option loaders; keep `pageSize` at or below 100.
227
228
  - Support exports using `buildCrudExportUrl` and pass `exportOptions` to `DataTable`.
@@ -0,0 +1,204 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ *
4
+ * TC-AI-UI-003: `<AiChat>` UI-part registry integration.
5
+ *
6
+ * Integration-level contract check for Step 4.3 (Phase 2 WS-A). Asserts that a
7
+ * host page rendering `<AiChat>` with a scoped registry resolves a registered
8
+ * custom component over the default Phase 3 placeholder — the same end-to-end
9
+ * code path that Phase 3 approval cards will exercise when Step 5.10 lands.
10
+ *
11
+ * Jest + React Testing Library was chosen over Playwright because
12
+ * `packages/ui` is a pure component package with no runnable Next.js route.
13
+ * The companion browser smoke for this change exercises the code path through
14
+ * a dev-only probe page under `/backend/config/ai-assistant/_dev-aichat-probe`
15
+ * (see `step-4.3-checks.md`). When Step 4.4 lands the real playground route,
16
+ * follow-up Playwright specs will move under
17
+ * `packages/ai-assistant/src/modules/ai_assistant/__integration__/`.
18
+ */
19
+
20
+ // Polyfill what jsdom lacks before any consumer module pulls in streams.
21
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
22
+ const nodeUtil = require('node:util') as typeof import('node:util')
23
+ if (typeof globalThis.TextEncoder === 'undefined') {
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ ;(globalThis as any).TextEncoder = nodeUtil.TextEncoder
26
+ }
27
+ if (typeof globalThis.TextDecoder === 'undefined') {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ ;(globalThis as any).TextDecoder =
30
+ nodeUtil.TextDecoder as unknown as typeof TextDecoder
31
+ }
32
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
33
+ const nodeStreamWeb = require('node:stream/web') as typeof import('node:stream/web')
34
+ if (
35
+ typeof (globalThis as unknown as { ReadableStream?: unknown }).ReadableStream ===
36
+ 'undefined'
37
+ ) {
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ ;(globalThis as any).ReadableStream = nodeStreamWeb.ReadableStream
40
+ }
41
+
42
+ import * as React from 'react'
43
+ import { screen } from '@testing-library/react'
44
+ import { renderWithProviders } from '@open-mercato/shared/lib/testing/renderWithProviders'
45
+
46
+ jest.mock('@open-mercato/ai-assistant/modules/ai_assistant/lib/agent-transport', () => ({
47
+ createAiAgentTransport: jest.fn(() => ({
48
+ sendMessages: jest.fn(),
49
+ reconnectToStream: jest.fn(),
50
+ })),
51
+ }))
52
+
53
+ jest.mock('@open-mercato/ui/backend/utils/api', () => ({
54
+ apiFetch: jest.fn(),
55
+ }))
56
+
57
+ import {
58
+ AiChat,
59
+ createAiUiPartRegistry,
60
+ defaultAiUiPartRegistry,
61
+ resetAiUiPartRegistryForTests,
62
+ } from '@open-mercato/ui/ai'
63
+
64
+ const dict = {
65
+ 'ai_assistant.chat.assistantRoleLabel': 'Assistant',
66
+ 'ai_assistant.chat.cancel': 'Cancel streaming response',
67
+ 'ai_assistant.chat.composerLabel': 'Message composer',
68
+ 'ai_assistant.chat.composerPlaceholder': 'Message the AI agent...',
69
+ 'ai_assistant.chat.debugPanelTitle': 'Debug panel',
70
+ 'ai_assistant.chat.emptyTranscript':
71
+ 'No messages yet. Ask the agent anything to get started.',
72
+ 'ai_assistant.chat.errorTitle': 'Agent dispatch failed',
73
+ 'ai_assistant.chat.regionLabel': 'AI chat',
74
+ 'ai_assistant.chat.send': 'Send message',
75
+ 'ai_assistant.chat.shortcutHint':
76
+ 'Press Cmd/Ctrl+Enter to send, Escape to cancel.',
77
+ 'ai_assistant.chat.thinking': 'Thinking...',
78
+ 'ai_assistant.chat.transcriptLabel': 'Chat transcript',
79
+ 'ai_assistant.chat.pending_phase3.body':
80
+ 'This interactive card will land in Phase 3 of the unified AI framework.',
81
+ 'ai_assistant.chat.pending_phase3.title': 'Mutation approval card pending',
82
+ 'ai_assistant.chat.uiPartPending': 'Pending UI part:',
83
+ 'ai_assistant.chat.userRoleLabel': 'You',
84
+ }
85
+
86
+ describe('TC-AI-UI-003: <AiChat> UI-part registry integration', () => {
87
+ beforeEach(() => {
88
+ resetAiUiPartRegistryForTests()
89
+ })
90
+
91
+ afterAll(() => {
92
+ resetAiUiPartRegistryForTests()
93
+ })
94
+
95
+ it('resolves a registered Phase 3 card over the default placeholder', () => {
96
+ function FakeApprovalCard({
97
+ componentId,
98
+ payload,
99
+ }: {
100
+ componentId: string
101
+ payload?: unknown
102
+ pendingActionId?: string
103
+ }) {
104
+ const payloadRecord = (payload as Record<string, unknown>) ?? {}
105
+ return (
106
+ <div data-testid="approval-card">
107
+ <span>Approval:{componentId}</span>
108
+ <span data-testid="approval-card-label">
109
+ {String(payloadRecord.label ?? '')}
110
+ </span>
111
+ </div>
112
+ )
113
+ }
114
+
115
+ const scoped = createAiUiPartRegistry()
116
+ scoped.register('mutation-preview-card', FakeApprovalCard)
117
+
118
+ renderWithProviders(
119
+ <AiChat
120
+ agent="customers.account_assistant"
121
+ registry={scoped}
122
+ uiParts={[
123
+ {
124
+ componentId: 'mutation-preview-card',
125
+ payload: { label: 'Update customer name' },
126
+ pendingActionId: 'act_123',
127
+ },
128
+ ]}
129
+ />,
130
+ { dict },
131
+ )
132
+
133
+ const card = screen.getByTestId('approval-card')
134
+ expect(card).toHaveTextContent('Approval:mutation-preview-card')
135
+ expect(screen.getByTestId('approval-card-label')).toHaveTextContent(
136
+ 'Update customer name',
137
+ )
138
+ expect(
139
+ document.querySelector(
140
+ '[data-ai-ui-part-pending-phase3="mutation-preview-card"]',
141
+ ),
142
+ ).toBeNull()
143
+ })
144
+
145
+ it('shows the Phase 3 placeholder when nothing is registered for a reserved id on a scoped registry', () => {
146
+ // Step 5.10 flipped the DEFAULT registry to live approval cards; scoped
147
+ // registries still seed placeholders by default so the pending-chip path
148
+ // is covered here with an explicit scoped registry.
149
+ const scoped = createAiUiPartRegistry()
150
+ renderWithProviders(
151
+ <AiChat
152
+ agent="customers.account_assistant"
153
+ registry={scoped}
154
+ uiParts={[{ componentId: 'confirmation-card' }]}
155
+ />,
156
+ { dict },
157
+ )
158
+
159
+ const placeholder = document.querySelector(
160
+ '[data-ai-ui-part-pending-phase3="confirmation-card"]',
161
+ )
162
+ expect(placeholder).not.toBeNull()
163
+ expect(placeholder?.textContent).toContain('Mutation approval card pending')
164
+ expect(placeholder?.textContent).toContain('confirmation-card')
165
+ })
166
+
167
+ it('keeps two <AiChat> instances isolated when each has its own scoped registry', () => {
168
+ function Card({ componentId }: { componentId: string }) {
169
+ return <div data-testid={`card:${componentId}`}>{componentId}</div>
170
+ }
171
+ function OtherCard({ componentId }: { componentId: string }) {
172
+ return (
173
+ <div data-testid={`other-card:${componentId}`}>other:{componentId}</div>
174
+ )
175
+ }
176
+
177
+ const a = createAiUiPartRegistry()
178
+ a.register('field-diff-card', Card)
179
+ const b = createAiUiPartRegistry()
180
+ b.register('field-diff-card', OtherCard)
181
+
182
+ renderWithProviders(
183
+ <div>
184
+ <AiChat
185
+ agent="customers.account_assistant"
186
+ registry={a}
187
+ uiParts={[{ componentId: 'field-diff-card' }]}
188
+ />
189
+ <AiChat
190
+ agent="customers.account_assistant"
191
+ registry={b}
192
+ uiParts={[{ componentId: 'field-diff-card' }]}
193
+ />
194
+ </div>,
195
+ { dict },
196
+ )
197
+
198
+ expect(screen.getByTestId('card:field-diff-card')).toBeInTheDocument()
199
+ expect(screen.getByTestId('other-card:field-diff-card')).toBeInTheDocument()
200
+ // Scoped registries must not write through to the default registry.
201
+ expect(defaultAiUiPartRegistry.resolve('field-diff-card')).not.toBe(Card)
202
+ expect(defaultAiUiPartRegistry.resolve('field-diff-card')).not.toBe(OtherCard)
203
+ })
204
+ })