@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,233 @@
1
+ "use client"
2
+
3
+ import type { ComponentType } from 'react'
4
+ import {
5
+ RESERVED_AI_UI_PART_IDS,
6
+ isReservedAiUiPartId,
7
+ type ReservedAiUiPartId,
8
+ } from './ui-part-slots'
9
+ import { PendingPhase3Placeholder } from './ui-parts/pending-phase3-placeholder'
10
+ import { AI_MUTATION_APPROVAL_CARDS } from './parts/approval-cards-map'
11
+
12
+ export { RESERVED_AI_UI_PART_IDS, isReservedAiUiPartId }
13
+ export type { ReservedAiUiPartId }
14
+
15
+ export type AiUiPartComponentId = ReservedAiUiPartId | (string & {})
16
+
17
+ export type AiUiPartProps = {
18
+ /** Stable component id emitted by the server-side UI-part producer. */
19
+ componentId: AiUiPartComponentId
20
+ /** Arbitrary payload the server attached to this UI part. */
21
+ payload?: unknown
22
+ /** Optional pending-action id for mutation-approval cards (Phase 3). */
23
+ pendingActionId?: string
24
+ }
25
+
26
+ export type AiUiPartComponent<P = AiUiPartProps> = ComponentType<P>
27
+
28
+ export interface AiUiPartRegistryEntry {
29
+ componentId: string
30
+ reserved: boolean
31
+ }
32
+
33
+ /**
34
+ * A UI-part registry instance. Consumers typically use
35
+ * {@link defaultAiUiPartRegistry} for global registrations; tests and embedded
36
+ * pages that need isolation can construct a dedicated registry via
37
+ * {@link createAiUiPartRegistry} and pass it to `<AiChat registry={...} />`.
38
+ */
39
+ export interface AiUiPartRegistry {
40
+ register<P = AiUiPartProps>(
41
+ componentId: AiUiPartComponentId,
42
+ component: AiUiPartComponent<P>,
43
+ ): void
44
+ unregister(componentId: AiUiPartComponentId): void
45
+ resolve<P = AiUiPartProps>(
46
+ componentId: AiUiPartComponentId,
47
+ ): AiUiPartComponent<P> | null
48
+ has(componentId: AiUiPartComponentId): boolean
49
+ list(): AiUiPartRegistryEntry[]
50
+ clear(): void
51
+ }
52
+
53
+ type RegistryStore = Map<string, AiUiPartComponent<AiUiPartProps>>
54
+
55
+ export interface CreateAiUiPartRegistryOptions {
56
+ /**
57
+ * When true (default) the registry is pre-seeded with the shared
58
+ * `PendingPhase3Placeholder` for every id in {@link RESERVED_AI_UI_PART_IDS}.
59
+ * Pass `false` to get an empty registry — useful for tests that want to
60
+ * assert the registry is genuinely empty.
61
+ */
62
+ seedReservedPlaceholders?: boolean
63
+ /**
64
+ * When true, the registry is pre-seeded with the live Phase 3 mutation
65
+ * approval cards from `@open-mercato/ui/ai/parts` instead of the humane
66
+ * pending placeholder. The app-wide {@link defaultAiUiPartRegistry}
67
+ * opts in, so end users see the real cards without any bootstrap wiring;
68
+ * scoped registries created via {@link createAiUiPartRegistry} keep the
69
+ * placeholder by default so unit tests and embedded playground mounts
70
+ * stay deterministic and isolated.
71
+ */
72
+ seedLiveApprovalCards?: boolean
73
+ }
74
+
75
+ function seedReservedSlots(
76
+ store: RegistryStore,
77
+ options: { seedLive: boolean } = { seedLive: false },
78
+ ): void {
79
+ for (const reservedId of RESERVED_AI_UI_PART_IDS) {
80
+ if (store.has(reservedId)) continue
81
+ if (options.seedLive && AI_MUTATION_APPROVAL_CARDS[reservedId]) {
82
+ store.set(
83
+ reservedId,
84
+ AI_MUTATION_APPROVAL_CARDS[reservedId] as AiUiPartComponent<AiUiPartProps>,
85
+ )
86
+ continue
87
+ }
88
+ store.set(
89
+ reservedId,
90
+ PendingPhase3Placeholder as AiUiPartComponent<AiUiPartProps>,
91
+ )
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Create a fresh UI-part registry. By default the four Phase 3 reserved
97
+ * slot ids are pre-seeded with {@link PendingPhase3Placeholder} so consumers
98
+ * that forget to register the real cards see a humane "Phase 3 pending"
99
+ * state instead of the neutral debug chip.
100
+ */
101
+ export function createAiUiPartRegistry(
102
+ options?: CreateAiUiPartRegistryOptions,
103
+ ): AiUiPartRegistry {
104
+ const seedReserved = options?.seedReservedPlaceholders !== false
105
+ const seedLive = options?.seedLiveApprovalCards === true
106
+ const store: RegistryStore = new Map()
107
+ if (seedReserved) {
108
+ seedReservedSlots(store, { seedLive })
109
+ }
110
+
111
+ const registry: AiUiPartRegistry = {
112
+ register(componentId, component) {
113
+ if (!componentId) {
114
+ throw new Error('registerAiUiPart requires a non-empty componentId')
115
+ }
116
+ store.set(componentId, component as AiUiPartComponent<AiUiPartProps>)
117
+ },
118
+ unregister(componentId) {
119
+ store.delete(componentId)
120
+ },
121
+ resolve<P = AiUiPartProps>(componentId: AiUiPartComponentId) {
122
+ const found = store.get(componentId)
123
+ if (!found) return null
124
+ return found as unknown as AiUiPartComponent<P>
125
+ },
126
+ has(componentId) {
127
+ return store.has(componentId)
128
+ },
129
+ list() {
130
+ const entries: AiUiPartRegistryEntry[] = []
131
+ for (const componentId of store.keys()) {
132
+ entries.push({
133
+ componentId,
134
+ reserved: isReservedAiUiPartId(componentId),
135
+ })
136
+ }
137
+ return entries
138
+ },
139
+ clear() {
140
+ store.clear()
141
+ if (seedReserved) {
142
+ seedReservedSlots(store, { seedLive })
143
+ }
144
+ },
145
+ }
146
+ return registry
147
+ }
148
+
149
+ const REGISTRY_GLOBAL_KEY = '__openMercatoAiUiPartRegistry'
150
+
151
+ function getDefaultRegistry(): AiUiPartRegistry {
152
+ const scope = globalThis as typeof globalThis & {
153
+ [REGISTRY_GLOBAL_KEY]?: AiUiPartRegistry
154
+ }
155
+ if (!scope[REGISTRY_GLOBAL_KEY]) {
156
+ // Default registry seeds the LIVE mutation-approval cards (Step 5.10).
157
+ // Scoped registries created via `createAiUiPartRegistry()` still default
158
+ // to the humane placeholder so playground embeds + unit tests stay
159
+ // deterministic and isolated.
160
+ scope[REGISTRY_GLOBAL_KEY] = createAiUiPartRegistry({ seedLiveApprovalCards: true })
161
+ }
162
+ return scope[REGISTRY_GLOBAL_KEY]
163
+ }
164
+
165
+ /**
166
+ * The app-wide default UI-part registry. Seeded with Phase 3 placeholders on
167
+ * first access. Tests that need a clean registry should instantiate their own
168
+ * via {@link createAiUiPartRegistry} and pass it to `<AiChat registry={...}>`.
169
+ */
170
+ export const defaultAiUiPartRegistry: AiUiPartRegistry = new Proxy(
171
+ {} as AiUiPartRegistry,
172
+ {
173
+ get(_target, prop: keyof AiUiPartRegistry) {
174
+ const impl = getDefaultRegistry()
175
+ const value = impl[prop]
176
+ return typeof value === 'function' ? value.bind(impl) : value
177
+ },
178
+ },
179
+ )
180
+
181
+ /**
182
+ * Register a UI-part component on the module-global default registry. Legacy
183
+ * Step 4.1 API — preserved verbatim so existing callers keep working. Prefer
184
+ * {@link defaultAiUiPartRegistry} directly (or a scoped registry) in new code.
185
+ *
186
+ * Idempotent: re-registering overwrites the previous entry so hot reload and
187
+ * Phase 3 card replacement (which overwrites the seeded placeholder) stay
188
+ * deterministic.
189
+ */
190
+ export function registerAiUiPart<P = AiUiPartProps>(
191
+ componentId: AiUiPartComponentId,
192
+ component: AiUiPartComponent<P>,
193
+ ): void {
194
+ defaultAiUiPartRegistry.register(componentId, component)
195
+ }
196
+
197
+ /**
198
+ * Resolve a registered UI-part component on the module-global default
199
+ * registry. Returns `null` when no component has been registered — callers
200
+ * MUST handle the null case gracefully (the canonical consumer, `<AiChat>`,
201
+ * renders a neutral placeholder chip + logs a `console.warn`).
202
+ */
203
+ export function resolveAiUiPart<P = AiUiPartProps>(
204
+ componentId: AiUiPartComponentId,
205
+ ): AiUiPartComponent<P> | null {
206
+ return defaultAiUiPartRegistry.resolve<P>(componentId)
207
+ }
208
+
209
+ /**
210
+ * Remove a UI-part component registration from the module-global default
211
+ * registry. Primarily useful for tests to keep a clean slate between runs.
212
+ */
213
+ export function unregisterAiUiPart(componentId: AiUiPartComponentId): void {
214
+ defaultAiUiPartRegistry.unregister(componentId)
215
+ }
216
+
217
+ /**
218
+ * Clear every registration on the module-global default registry and re-seed
219
+ * the Phase 3 reserved placeholders. Test-only helper; production code must
220
+ * never invoke this.
221
+ */
222
+ export function resetAiUiPartRegistryForTests(): void {
223
+ defaultAiUiPartRegistry.clear()
224
+ }
225
+
226
+ /**
227
+ * Snapshot every registration on the module-global default registry. Used by
228
+ * debugging UIs (Step 4.6) to enumerate what's registered without mutating
229
+ * state.
230
+ */
231
+ export function listAiUiParts(): AiUiPartRegistryEntry[] {
232
+ return defaultAiUiPartRegistry.list()
233
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Reserved UI-part component ids for Phase 3 approval cards.
3
+ *
4
+ * These ids match the server-emitted UI parts the mutation-approval runtime
5
+ * will produce in Phase 3 Steps 5.6 and 5.10. They are listed here so the
6
+ * registry contract can be validated at compile time and the default registry
7
+ * ships with the same slot names the runtime will later emit.
8
+ *
9
+ * Hard rule: this tuple is FROZEN. Adding new reserved ids is additive, but
10
+ * renaming or removing any entry is a breaking change per the backward-
11
+ * compatibility contract (see `BACKWARD_COMPATIBILITY.md` §6 Widget injection
12
+ * spot IDs).
13
+ */
14
+ export const RESERVED_AI_UI_PART_IDS = Object.freeze([
15
+ 'mutation-preview-card',
16
+ 'field-diff-card',
17
+ 'confirmation-card',
18
+ 'mutation-result-card',
19
+ ] as const)
20
+
21
+ export type ReservedAiUiPartId = (typeof RESERVED_AI_UI_PART_IDS)[number]
22
+
23
+ /**
24
+ * Returns true when the given componentId is one of the Phase 3 reserved
25
+ * slot identifiers. Used by the registry to flag seeded placeholders and by
26
+ * debugging UIs (Step 4.6) to render reserved slots distinctly.
27
+ */
28
+ export function isReservedAiUiPartId(
29
+ componentId: string,
30
+ ): componentId is ReservedAiUiPartId {
31
+ return (RESERVED_AI_UI_PART_IDS as readonly string[]).includes(componentId)
32
+ }
@@ -0,0 +1,50 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { Info } from 'lucide-react'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+ import { Alert, AlertDescription, AlertTitle } from '../../primitives/alert'
7
+ import type { AiUiPartProps } from '../ui-part-registry'
8
+
9
+ /**
10
+ * Default placeholder for the four Phase 3 reserved slot ids.
11
+ *
12
+ * Consumers that forget to register the real mutation-approval cards still
13
+ * see a humane "Phase 3 pending" state instead of the neutral debug chip the
14
+ * `<AiChat>` fallback uses for genuinely unknown component ids. Uses the
15
+ * shared DS `Alert` primitive with `variant="info"` — no hardcoded colors.
16
+ *
17
+ * When Step 5.10 lands the real cards, app bootstrappers will call
18
+ * `registerAiUiPart('mutation-preview-card', MutationPreviewCard)` (etc.)
19
+ * which overwrites this placeholder. The unit tests in
20
+ * `__tests__/ui-part-registry.test.ts` pin that replacement behavior so the
21
+ * Phase 3 hand-off stays deterministic.
22
+ */
23
+ export function PendingPhase3Placeholder({ componentId }: AiUiPartProps) {
24
+ const t = useT()
25
+ return (
26
+ <Alert
27
+ variant="info"
28
+ data-ai-ui-part-pending-phase3={componentId}
29
+ >
30
+ <Info className="size-4" aria-hidden />
31
+ <AlertTitle>
32
+ {t(
33
+ 'ai_assistant.chat.pending_phase3.title',
34
+ 'Mutation approval card pending',
35
+ )}
36
+ </AlertTitle>
37
+ <AlertDescription>
38
+ <span>
39
+ {t(
40
+ 'ai_assistant.chat.pending_phase3.body',
41
+ 'This interactive card will land in Phase 3 of the unified AI framework.',
42
+ )}
43
+ </span>
44
+ <span className="ml-1 font-mono text-xs">{componentId}</span>
45
+ </AlertDescription>
46
+ </Alert>
47
+ )
48
+ }
49
+
50
+ export default PendingPhase3Placeholder