@open-mercato/ui 0.6.3-develop.3562.1.71049275ff → 0.6.3-develop.3574.1.f3c5dcf345

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.
@@ -1,14 +1,9 @@
1
1
  "use client";
2
2
  import { useEffect, useRef } from "react";
3
+ import { matchWildcardPattern } from "@open-mercato/shared/lib/patterns/wildcard";
3
4
  const APP_EVENT_DOM_NAME = "om:event";
4
5
  function matchesPattern(pattern, eventId) {
5
- if (pattern === "*") return true;
6
- if (!pattern.includes("*")) return pattern === eventId;
7
- const escapedPattern = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
8
- const regex = new RegExp(
9
- "^" + escapedPattern + "$"
10
- );
11
- return regex.test(eventId);
6
+ return matchWildcardPattern(eventId, pattern);
12
7
  }
13
8
  function useAppEvent(eventPattern, handler, deps = []) {
14
9
  const handlerRef = useRef(handler);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/injection/useAppEvent.ts"],
4
- "sourcesContent": ["\"use client\"\nimport { useEffect, useRef } from 'react'\nimport type { AppEventPayload } from '@open-mercato/shared/modules/widgets/injection'\n\n/**\n * DOM Event Bridge event name.\n * Server-side events with `clientBroadcast: true` are dispatched as\n * CustomEvents with this name on the window object.\n */\nexport const APP_EVENT_DOM_NAME = 'om:event'\n\n/**\n * Match an event ID against a wildcard pattern.\n *\n * Supports:\n * - Exact match: `'example.todo.created'`\n * - Wildcard suffix: `'example.todo.*'` matches `'example.todo.created'`, `'example.todo.updated'`\n * - Global wildcard: `'*'` matches everything\n *\n * @example\n * matchesPattern('example.todo.*', 'example.todo.created') // true\n * matchesPattern('example.todo.*', 'example.item.created') // false\n * matchesPattern('*', 'anything.here') // true\n */\nexport function matchesPattern(pattern: string, eventId: string): boolean {\n if (pattern === '*') return true\n if (!pattern.includes('*')) return pattern === eventId\n const escapedPattern = pattern\n .replace(/[|\\\\{}()[\\]^$+?.]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n const regex = new RegExp(\n '^' + escapedPattern + '$',\n )\n return regex.test(eventId)\n}\n\n/**\n * React hook that listens for app events delivered via the DOM Event Bridge.\n *\n * Events are dispatched as `om:event` CustomEvents on `window` by the event bridge\n * (see `eventBridge.ts`). This hook filters events by pattern and calls the handler.\n *\n * @param eventPattern - Pattern to match event IDs against (e.g., 'example.todo.*')\n * @param handler - Callback invoked when a matching event arrives\n * @param deps - Optional dependency array for the handler (defaults to [])\n *\n * @example\n * useAppEvent('example.todo.*', (event) => {\n * console.log('Todo event:', event.id, event.payload)\n * setRefreshKey(k => k + 1)\n * })\n */\nexport function useAppEvent(\n eventPattern: string,\n handler: (payload: AppEventPayload) => void,\n deps: unknown[] = [],\n): void {\n const handlerRef = useRef(handler)\n handlerRef.current = handler\n\n useEffect(() => {\n const listener = (e: Event) => {\n const detail = (e as CustomEvent<AppEventPayload>).detail\n if (!detail || typeof detail.id !== 'string') return\n if (matchesPattern(eventPattern, detail.id)) {\n handlerRef.current(detail)\n }\n }\n\n window.addEventListener(APP_EVENT_DOM_NAME, listener)\n return () => {\n window.removeEventListener(APP_EVENT_DOM_NAME, listener)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [eventPattern, ...deps])\n}\n"],
5
- "mappings": ";AACA,SAAS,WAAW,cAAc;AAQ3B,MAAM,qBAAqB;AAe3B,SAAS,eAAe,SAAiB,SAA0B;AACxE,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO,YAAY;AAC/C,QAAM,iBAAiB,QACpB,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,OAAO,IAAI;AACtB,QAAM,QAAQ,IAAI;AAAA,IAChB,MAAM,iBAAiB;AAAA,EACzB;AACA,SAAO,MAAM,KAAK,OAAO;AAC3B;AAkBO,SAAS,YACd,cACA,SACA,OAAkB,CAAC,GACb;AACN,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,YAAU,MAAM;AACd,UAAM,WAAW,CAAC,MAAa;AAC7B,YAAM,SAAU,EAAmC;AACnD,UAAI,CAAC,UAAU,OAAO,OAAO,OAAO,SAAU;AAC9C,UAAI,eAAe,cAAc,OAAO,EAAE,GAAG;AAC3C,mBAAW,QAAQ,MAAM;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO,iBAAiB,oBAAoB,QAAQ;AACpD,WAAO,MAAM;AACX,aAAO,oBAAoB,oBAAoB,QAAQ;AAAA,IACzD;AAAA,EAEF,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC;AAC5B;",
4
+ "sourcesContent": ["\"use client\"\nimport { useEffect, useRef } from 'react'\nimport type { AppEventPayload } from '@open-mercato/shared/modules/widgets/injection'\nimport { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\n\n/**\n * DOM Event Bridge event name.\n * Server-side events with `clientBroadcast: true` are dispatched as\n * CustomEvents with this name on the window object.\n */\nexport const APP_EVENT_DOM_NAME = 'om:event'\n\n/**\n * Match an event ID against a wildcard pattern.\n *\n * Supports:\n * - Exact match: `'example.todo.created'`\n * - Wildcard suffix: `'example.todo.*'` matches `'example.todo.created'`, `'example.todo.updated'`\n * - Global wildcard: `'*'` matches everything\n *\n * @example\n * matchesPattern('example.todo.*', 'example.todo.created') // true\n * matchesPattern('example.todo.*', 'example.item.created') // false\n * matchesPattern('*', 'anything.here') // true\n */\nexport function matchesPattern(pattern: string, eventId: string): boolean {\n return matchWildcardPattern(eventId, pattern)\n}\n\n/**\n * React hook that listens for app events delivered via the DOM Event Bridge.\n *\n * Events are dispatched as `om:event` CustomEvents on `window` by the event bridge\n * (see `eventBridge.ts`). This hook filters events by pattern and calls the handler.\n *\n * @param eventPattern - Pattern to match event IDs against (e.g., 'example.todo.*')\n * @param handler - Callback invoked when a matching event arrives\n * @param deps - Optional dependency array for the handler (defaults to [])\n *\n * @example\n * useAppEvent('example.todo.*', (event) => {\n * console.log('Todo event:', event.id, event.payload)\n * setRefreshKey(k => k + 1)\n * })\n */\nexport function useAppEvent(\n eventPattern: string,\n handler: (payload: AppEventPayload) => void,\n deps: unknown[] = [],\n): void {\n const handlerRef = useRef(handler)\n handlerRef.current = handler\n\n useEffect(() => {\n const listener = (e: Event) => {\n const detail = (e as CustomEvent<AppEventPayload>).detail\n if (!detail || typeof detail.id !== 'string') return\n if (matchesPattern(eventPattern, detail.id)) {\n handlerRef.current(detail)\n }\n }\n\n window.addEventListener(APP_EVENT_DOM_NAME, listener)\n return () => {\n window.removeEventListener(APP_EVENT_DOM_NAME, listener)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [eventPattern, ...deps])\n}\n"],
5
+ "mappings": ";AACA,SAAS,WAAW,cAAc;AAElC,SAAS,4BAA4B;AAO9B,MAAM,qBAAqB;AAe3B,SAAS,eAAe,SAAiB,SAA0B;AACxE,SAAO,qBAAqB,SAAS,OAAO;AAC9C;AAkBO,SAAS,YACd,cACA,SACA,OAAkB,CAAC,GACb;AACN,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,YAAU,MAAM;AACd,UAAM,WAAW,CAAC,MAAa;AAC7B,YAAM,SAAU,EAAmC;AACnD,UAAI,CAAC,UAAU,OAAO,OAAO,OAAO,SAAU;AAC9C,UAAI,eAAe,cAAc,OAAO,EAAE,GAAG;AAC3C,mBAAW,QAAQ,MAAM;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO,iBAAiB,oBAAoB,QAAQ;AACpD,WAAO,MAAM;AACX,aAAO,oBAAoB,oBAAoB,QAAQ;AAAA,IACzD;AAAA,EAEF,GAAG,CAAC,cAAc,GAAG,IAAI,CAAC;AAC5B;",
6
6
  "names": []
7
7
  }
package/jest.config.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  /** @type {import('jest').Config} */
2
2
  module.exports = {
3
3
  testEnvironment: 'jsdom',
4
+ testTimeout: 30000,
4
5
  watchman: false,
5
6
  rootDir: '.',
6
7
  maxWorkers: 4,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.3-develop.3562.1.71049275ff",
3
+ "version": "0.6.3-develop.3574.1.f3c5dcf345",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -141,13 +141,13 @@
141
141
  "remark-gfm": "^4.0.1"
142
142
  },
143
143
  "peerDependencies": {
144
- "@open-mercato/shared": "0.6.3-develop.3562.1.71049275ff",
144
+ "@open-mercato/shared": "0.6.3-develop.3574.1.f3c5dcf345",
145
145
  "react": ">=18.0.0",
146
146
  "react-dom": ">=18.0.0",
147
147
  "react-is": ">=18.0.0"
148
148
  },
149
149
  "devDependencies": {
150
- "@open-mercato/shared": "0.6.3-develop.3562.1.71049275ff",
150
+ "@open-mercato/shared": "0.6.3-develop.3574.1.f3c5dcf345",
151
151
  "@testing-library/dom": "^10.4.1",
152
152
  "@testing-library/jest-dom": "^6.9.1",
153
153
  "@testing-library/react": "^16.3.1",
@@ -54,5 +54,5 @@ describe('<AiAssistantLauncher>', () => {
54
54
 
55
55
  expect(await screen.findByRole('dialog', { name: 'AI assistants' })).toBeInTheDocument()
56
56
  expect(screen.getByText('Catalog Assistant')).toBeInTheDocument()
57
- })
57
+ }, 60_000)
58
58
  })
@@ -293,7 +293,7 @@ describe('<AiChat>', () => {
293
293
  expect(parsedUrl.searchParams.get('agent')).toBe('customers.deal_analyzer')
294
294
  expect(parsedUrl.searchParams.has('provider')).toBe(false)
295
295
  expect(parsedUrl.searchParams.has('model')).toBe(false)
296
- }, 15000)
296
+ }, 15_000)
297
297
 
298
298
  it('renders a compact footer before a constrained host is measured', async () => {
299
299
  const apiCallMock = apiCall as unknown as jest.Mock
@@ -600,7 +600,7 @@ describe('AppShell', () => {
600
600
 
601
601
  await waitFor(() => {
602
602
  expect(screen.getByText('Pay Links')).toBeInTheDocument()
603
- })
603
+ }, { timeout: 10_000 })
604
604
 
605
605
  const link = screen.getByRole('link', { name: 'Pay Links' })
606
606
  expect(link.querySelector('svg.lucide-ticket')).toBeTruthy()
@@ -55,6 +55,13 @@ jest.mock('../injection/mutationEvents', () => ({
55
55
  dispatchBackendMutationError: jest.fn(),
56
56
  }))
57
57
 
58
+ jest.mock('../injection/useGuardedMutation', () => ({
59
+ useGuardedMutation: () => ({
60
+ runMutation: async ({ operation }: { operation: () => Promise<unknown> }) => operation(),
61
+ retryLastMutation: jest.fn(async () => false),
62
+ }),
63
+ }))
64
+
58
65
  const fakeGroups = [
59
66
  {
60
67
  id: 'core',
@@ -218,7 +225,7 @@ describe('SidebarCustomizationEditor', () => {
218
225
  expect(screen.getByRole('dialog')).toBeInTheDocument()
219
226
 
220
227
  expect(screen.getAllByText(duplicateMessage)).toHaveLength(1)
221
- })
228
+ }, 15_000)
222
229
 
223
230
  it('clears the inline dialog error when the user edits the name', async () => {
224
231
  const duplicateMessage = 'A variant with this name already exists. Choose a different name.'
@@ -0,0 +1,16 @@
1
+ import { matchesPattern } from '../useAppEvent'
2
+
3
+ describe('matchesPattern', () => {
4
+ it('matches DOM event wildcard patterns without regex execution', () => {
5
+ expect(matchesPattern('*', 'customers.person.created')).toBe(true)
6
+ expect(matchesPattern('customers.person.*', 'customers.person.created')).toBe(true)
7
+ expect(matchesPattern('customers.*.created', 'customers.person.created')).toBe(true)
8
+ expect(matchesPattern('sales.*', 'customers.person.created')).toBe(false)
9
+ })
10
+
11
+ it('treats regex metacharacters as literal event characters', () => {
12
+ expect(matchesPattern('customers.(person).*', 'customers.(person).created')).toBe(true)
13
+ expect(matchesPattern('customers.+.*', 'customers.person.created')).toBe(false)
14
+ })
15
+ })
16
+
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
  import { useEffect, useRef } from 'react'
3
3
  import type { AppEventPayload } from '@open-mercato/shared/modules/widgets/injection'
4
+ import { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'
4
5
 
5
6
  /**
6
7
  * DOM Event Bridge event name.
@@ -23,15 +24,7 @@ export const APP_EVENT_DOM_NAME = 'om:event'
23
24
  * matchesPattern('*', 'anything.here') // true
24
25
  */
25
26
  export function matchesPattern(pattern: string, eventId: string): boolean {
26
- if (pattern === '*') return true
27
- if (!pattern.includes('*')) return pattern === eventId
28
- const escapedPattern = pattern
29
- .replace(/[|\\{}()[\]^$+?.]/g, '\\$&')
30
- .replace(/\*/g, '.*')
31
- const regex = new RegExp(
32
- '^' + escapedPattern + '$',
33
- )
34
- return regex.test(eventId)
27
+ return matchWildcardPattern(eventId, pattern)
35
28
  }
36
29
 
37
30
  /**