@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +2 -1
- package/__integration__/TC-AI-UI-003-aichat-registry.spec.tsx +204 -0
- package/dist/ai/AiAssistantLauncher.js +596 -0
- package/dist/ai/AiAssistantLauncher.js.map +7 -0
- package/dist/ai/AiChat.js +1092 -0
- package/dist/ai/AiChat.js.map +7 -0
- package/dist/ai/AiChatSessions.js +297 -0
- package/dist/ai/AiChatSessions.js.map +7 -0
- package/dist/ai/AiDock.js +347 -0
- package/dist/ai/AiDock.js.map +7 -0
- package/dist/ai/AiMessageContent.js +369 -0
- package/dist/ai/AiMessageContent.js.map +7 -0
- package/dist/ai/ChatPaneTabs.js +251 -0
- package/dist/ai/ChatPaneTabs.js.map +7 -0
- package/dist/ai/index.js +115 -0
- package/dist/ai/index.js.map +7 -0
- package/dist/ai/parts/ConfirmationCard.js +211 -0
- package/dist/ai/parts/ConfirmationCard.js.map +7 -0
- package/dist/ai/parts/FieldDiffCard.js +119 -0
- package/dist/ai/parts/FieldDiffCard.js.map +7 -0
- package/dist/ai/parts/MutationPreviewCard.js +224 -0
- package/dist/ai/parts/MutationPreviewCard.js.map +7 -0
- package/dist/ai/parts/MutationResultCard.js +240 -0
- package/dist/ai/parts/MutationResultCard.js.map +7 -0
- package/dist/ai/parts/approval-cards-map.js +15 -0
- package/dist/ai/parts/approval-cards-map.js.map +7 -0
- package/dist/ai/parts/index.js +24 -0
- package/dist/ai/parts/index.js.map +7 -0
- package/dist/ai/parts/pending-action-api.js +60 -0
- package/dist/ai/parts/pending-action-api.js.map +7 -0
- package/dist/ai/parts/types.js +1 -0
- package/dist/ai/parts/types.js.map +7 -0
- package/dist/ai/parts/useAiPendingActionPolling.js +126 -0
- package/dist/ai/parts/useAiPendingActionPolling.js.map +7 -0
- package/dist/ai/records/ActivityCard.js +83 -0
- package/dist/ai/records/ActivityCard.js.map +7 -0
- package/dist/ai/records/CompanyCard.js +81 -0
- package/dist/ai/records/CompanyCard.js.map +7 -0
- package/dist/ai/records/DealCard.js +76 -0
- package/dist/ai/records/DealCard.js.map +7 -0
- package/dist/ai/records/PersonCard.js +68 -0
- package/dist/ai/records/PersonCard.js.map +7 -0
- package/dist/ai/records/ProductCard.js +68 -0
- package/dist/ai/records/ProductCard.js.map +7 -0
- package/dist/ai/records/RecordCard.js +29 -0
- package/dist/ai/records/RecordCard.js.map +7 -0
- package/dist/ai/records/RecordCardShell.js +103 -0
- package/dist/ai/records/RecordCardShell.js.map +7 -0
- package/dist/ai/records/index.js +31 -0
- package/dist/ai/records/index.js.map +7 -0
- package/dist/ai/records/registry.js +51 -0
- package/dist/ai/records/registry.js.map +7 -0
- package/dist/ai/records/types.js +1 -0
- package/dist/ai/records/types.js.map +7 -0
- package/dist/ai/ui-part-registry.js +112 -0
- package/dist/ai/ui-part-registry.js.map +7 -0
- package/dist/ai/ui-part-slots.js +14 -0
- package/dist/ai/ui-part-slots.js.map +7 -0
- package/dist/ai/ui-parts/pending-phase3-placeholder.js +35 -0
- package/dist/ai/ui-parts/pending-phase3-placeholder.js.map +7 -0
- package/dist/ai/upload-adapter.js +256 -0
- package/dist/ai/upload-adapter.js.map +7 -0
- package/dist/ai/useAiChat.js +549 -0
- package/dist/ai/useAiChat.js.map +7 -0
- package/dist/ai/useAiChatUpload.js +127 -0
- package/dist/ai/useAiChatUpload.js.map +7 -0
- package/dist/ai/useAiShortcuts.js +43 -0
- package/dist/ai/useAiShortcuts.js.map +7 -0
- package/dist/backend/AppShell.js +8 -4
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/BackendChromeProvider.js +2 -0
- package/dist/backend/BackendChromeProvider.js.map +2 -2
- package/dist/backend/DataTable.js +19 -2
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/FilterBar.js +19 -15
- package/dist/backend/FilterBar.js.map +2 -2
- package/dist/backend/dashboard/DashboardScreen.js +31 -3
- package/dist/backend/dashboard/DashboardScreen.js.map +2 -2
- package/dist/backend/injection/spotIds.js +6 -0
- package/dist/backend/injection/spotIds.js.map +2 -2
- package/dist/backend/notifications/useNotificationEffect.js +38 -2
- package/dist/backend/notifications/useNotificationEffect.js.map +2 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +2 -2
- package/jest.config.cjs +7 -1
- package/jest.markdown-mock.tsx +7 -0
- package/package.json +10 -4
- package/src/ai/AiAssistantLauncher.tsx +805 -0
- package/src/ai/AiChat.tsx +1483 -0
- package/src/ai/AiChatSessions.tsx +429 -0
- package/src/ai/AiDock.tsx +505 -0
- package/src/ai/AiMessageContent.tsx +515 -0
- package/src/ai/ChatPaneTabs.tsx +310 -0
- package/src/ai/__tests__/AiChat.conversation.test.tsx +160 -0
- package/src/ai/__tests__/AiChat.debug.test.tsx +152 -0
- package/src/ai/__tests__/AiChat.registry.test.tsx +213 -0
- package/src/ai/__tests__/AiChat.test.tsx +257 -0
- package/src/ai/__tests__/AiDock.test.tsx +124 -0
- package/src/ai/__tests__/AiMessageContent.test.ts +111 -0
- package/src/ai/__tests__/ui-part-registry.test.ts +199 -0
- package/src/ai/__tests__/ui-part-slots.test.ts +43 -0
- package/src/ai/__tests__/upload-adapter.test.ts +213 -0
- package/src/ai/__tests__/useAiChatUpload.test.tsx +163 -0
- package/src/ai/__tests__/useAiShortcuts.test.tsx +100 -0
- package/src/ai/index.ts +125 -0
- package/src/ai/parts/ConfirmationCard.tsx +310 -0
- package/src/ai/parts/FieldDiffCard.tsx +173 -0
- package/src/ai/parts/MutationPreviewCard.tsx +302 -0
- package/src/ai/parts/MutationResultCard.tsx +360 -0
- package/src/ai/parts/__tests__/ConfirmationCard.test.tsx +169 -0
- package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +74 -0
- package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +177 -0
- package/src/ai/parts/__tests__/MutationResultCard.test.tsx +127 -0
- package/src/ai/parts/__tests__/useAiPendingActionPolling.test.tsx +151 -0
- package/src/ai/parts/approval-cards-map.ts +24 -0
- package/src/ai/parts/index.ts +27 -0
- package/src/ai/parts/pending-action-api.ts +123 -0
- package/src/ai/parts/types.ts +84 -0
- package/src/ai/parts/useAiPendingActionPolling.ts +210 -0
- package/src/ai/records/ActivityCard.tsx +102 -0
- package/src/ai/records/CompanyCard.tsx +89 -0
- package/src/ai/records/DealCard.tsx +85 -0
- package/src/ai/records/PersonCard.tsx +77 -0
- package/src/ai/records/ProductCard.tsx +83 -0
- package/src/ai/records/RecordCard.tsx +37 -0
- package/src/ai/records/RecordCardShell.tsx +169 -0
- package/src/ai/records/index.ts +30 -0
- package/src/ai/records/registry.tsx +80 -0
- package/src/ai/records/types.ts +90 -0
- package/src/ai/ui-part-registry.ts +233 -0
- package/src/ai/ui-part-slots.ts +32 -0
- package/src/ai/ui-parts/pending-phase3-placeholder.tsx +50 -0
- package/src/ai/upload-adapter.ts +421 -0
- package/src/ai/useAiChat.ts +865 -0
- package/src/ai/useAiChatUpload.ts +180 -0
- package/src/ai/useAiShortcuts.ts +79 -0
- package/src/backend/AppShell.tsx +12 -5
- package/src/backend/BackendChromeProvider.tsx +2 -0
- package/src/backend/DataTable.tsx +20 -1
- package/src/backend/FilterBar.tsx +26 -13
- package/src/backend/__tests__/BackendChromeProvider.test.tsx +45 -0
- package/src/backend/dashboard/DashboardScreen.tsx +38 -3
- package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +24 -1
- package/src/backend/injection/spotIds.ts +6 -0
- package/src/backend/notifications/__tests__/useNotificationEffect.test.tsx +77 -0
- package/src/backend/notifications/useNotificationEffect.ts +47 -2
- package/src/index.ts +1 -0
package/.turbo/turbo-build.log
CHANGED
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
|
+
})
|