@qoretechnologies/reqraft 0.10.27-pr.74.gc5f6ca3 → 0.10.27

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 (64) hide show
  1. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  2. package/dist/components/form/engine/CompactRow.js +8 -1
  3. package/dist/components/form/engine/CompactRow.js.map +1 -1
  4. package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
  5. package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
  6. package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
  7. package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
  8. package/dist/components/searchFilterBar/index.d.ts +2 -0
  9. package/dist/components/searchFilterBar/index.d.ts.map +1 -0
  10. package/dist/components/searchFilterBar/index.js +18 -0
  11. package/dist/components/searchFilterBar/index.js.map +1 -0
  12. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +9 -1
  13. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -1
  14. package/dist/components/supportTicket/InterfaceReferenceTags.js +2 -2
  15. package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -1
  16. package/dist/components/supportTicket/ReferencePicker.d.ts +52 -6
  17. package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -1
  18. package/dist/components/supportTicket/ReferencePicker.js +240 -31
  19. package/dist/components/supportTicket/ReferencePicker.js.map +1 -1
  20. package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
  21. package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
  22. package/dist/components/supportTicket/attachmentAccess.js +132 -0
  23. package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
  24. package/dist/components/supportTicket/index.d.ts +2 -0
  25. package/dist/components/supportTicket/index.d.ts.map +1 -1
  26. package/dist/components/supportTicket/index.js +2 -0
  27. package/dist/components/supportTicket/index.js.map +1 -1
  28. package/dist/components/supportTicket/meta.d.ts +8 -0
  29. package/dist/components/supportTicket/meta.d.ts.map +1 -1
  30. package/dist/components/supportTicket/meta.js +37 -1
  31. package/dist/components/supportTicket/meta.js.map +1 -1
  32. package/dist/components/supportTicket/ticketModel.d.ts +102 -0
  33. package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
  34. package/dist/components/supportTicket/ticketModel.js +3 -0
  35. package/dist/components/supportTicket/ticketModel.js.map +1 -0
  36. package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -1
  37. package/dist/components/ticketReferences/TicketReferences.js +109 -78
  38. package/dist/components/ticketReferences/TicketReferences.js.map +1 -1
  39. package/dist/components/ticketThread/TicketThread.d.ts.map +1 -1
  40. package/dist/components/ticketThread/TicketThread.js +19 -2
  41. package/dist/components/ticketThread/TicketThread.js.map +1 -1
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -1
  45. package/dist/index.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/components/form/engine/CompactRow.tsx +7 -0
  48. package/src/components/form/engine/FormEngine.stories.tsx +90 -0
  49. package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
  50. package/src/components/searchFilterBar/index.ts +1 -0
  51. package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +37 -0
  52. package/src/components/supportTicket/InterfaceReferenceTags.tsx +11 -0
  53. package/src/components/supportTicket/ReferencePicker.stories.tsx +257 -34
  54. package/src/components/supportTicket/ReferencePicker.tsx +421 -95
  55. package/src/components/supportTicket/TicketConversation.stories.tsx +0 -1
  56. package/src/components/supportTicket/TicketReplyBox.stories.tsx +0 -4
  57. package/src/components/supportTicket/attachmentAccess.ts +97 -0
  58. package/src/components/supportTicket/index.ts +2 -0
  59. package/src/components/supportTicket/meta.ts +40 -0
  60. package/src/components/supportTicket/ticketModel.ts +131 -0
  61. package/src/components/ticketReferences/TicketReferences.tsx +124 -145
  62. package/src/components/ticketThread/TicketThread.stories.tsx +24 -2
  63. package/src/components/ticketThread/TicketThread.tsx +23 -3
  64. package/src/index.tsx +3 -1
@@ -1,17 +1,19 @@
1
1
  import { ReqoreButton, ReqoreUIProvider } from '@qoretechnologies/reqore';
2
2
  import { StoryObj } from '@storybook/react-vite';
3
- import { useState } from 'react';
3
+ import { ComponentProps, useState } from 'react';
4
4
  import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
5
5
  import { StoryMeta } from '../../types';
6
6
  import { InterfaceReferenceTags } from './InterfaceReferenceTags';
7
7
  import { IInterfaceReference } from './meta';
8
- import { ReferencePicker } from './ReferencePicker';
8
+ import { ReferencePicker, TReferencePickerItem } from './ReferencePicker';
9
9
  import { TicketReplyBox } from './TicketReplyBox';
10
10
 
11
11
  /**
12
- * The two-pane reference browser (master-detail: kinds on the left, that kind's
13
- * interfaces on the right) with a single search bar that scopes both panes. Rendered
14
- * **on top** of the composer and toggled by its "Reference interfaces" action.
12
+ * The interface browser — kinds on one side, the selected kind's interfaces on the
13
+ * other, one search bar scoping both. Rendered **on top** of the composer and toggled
14
+ * by its "Reference interfaces" action. Search dims non-matching kinds rather than
15
+ * removing them, picked interfaces stay in the list as selected rows, and the two panes
16
+ * collapse into one column (with a modal on phones) when there isn't width for both.
15
17
  */
16
18
  const meta = {
17
19
  component: ReferencePicker,
@@ -22,11 +24,13 @@ type Story = StoryObj<typeof meta>;
22
24
 
23
25
  const ACCENT = { main: 'custom1' } as const;
24
26
 
27
+ /* The kind ids a host passes — wire values, not labels. `qog` is the customer-facing
28
+ * kind for what the instance still lists as `fsm`; hosts map that at the fetch
29
+ * boundary, so `fsm` never reaches the picker. */
25
30
  const KINDS = [
26
31
  'workflow',
27
32
  'service',
28
33
  'job',
29
- 'fsm',
30
34
  'connection',
31
35
  'mapper',
32
36
  'class',
@@ -38,17 +42,26 @@ const KINDS = [
38
42
  ];
39
43
 
40
44
  /* Stands in for the host's fetch: in the IDE these come off the customer's live
41
- * instance one kind at a time. */
42
- const CATALOGUE: Record<string, string[]> = {
45
+ * instance one kind at a time. Services carry the richer item shape — a description
46
+ * and a badge — with `partner-gateway` left as a bare string so the plain row and the
47
+ * mixed list both stay on screen. */
48
+ const CATALOGUE: Record<string, TReferencePickerItem[]> = {
43
49
  workflow: ['order-sync:1.2', 'invoice-export:2.0', 'partner-recon:1.1'],
44
- service: ['notifier', 'audit-logger', 'partner-gateway'],
50
+ service: [
51
+ {
52
+ name: 'notifier',
53
+ description: 'Fans order events out to email and Slack',
54
+ badge: '3 running',
55
+ },
56
+ { name: 'audit-logger', description: 'Writes every state change to the audit table' },
57
+ 'partner-gateway',
58
+ ],
45
59
  job: ['nightly-recon', 'cleanup-temp', 'sla-report'],
46
- fsm: ['onboarding', 'escalation'],
47
60
  connection: ['sftp-partner', 'pgsql-omq', 'salesforce-prod'],
48
61
  mapper: ['csv-to-order', 'order-to-invoice'],
49
62
  class: ['OrderUtils', 'PartnerApi'],
50
63
  'value-map': ['country-codes', 'currency'],
51
- qog: ['telegram-intake', 'stripe-billing'],
64
+ qog: ['telegram-intake', 'stripe-billing', 'onboarding', 'escalation'],
52
65
  'ai-collection': ['kb-support'],
53
66
  'ai-endpoint': ['gpt-router'],
54
67
  'ai-guardrail': ['pii-filter'],
@@ -64,11 +77,24 @@ const darkTheme = (Story: () => JSX.Element) => (
64
77
 
65
78
  /** Drives the picker the way a host does: owns the selected kind, resolves that
66
79
  * kind's interfaces, and accumulates what gets picked. */
67
- const Hosted = ({ withComposer }: { withComposer?: boolean }) => {
68
- const [kind, setKind] = useState<string>('workflow');
80
+ const Hosted = ({
81
+ withComposer,
82
+ initialKind = 'workflow',
83
+ ...pickerProps
84
+ }: { withComposer?: boolean; initialKind?: string } & Partial<
85
+ ComponentProps<typeof ReferencePicker>
86
+ >) => {
87
+ const [kind, setKind] = useState<string>(initialKind);
69
88
  const [picked, setPicked] = useState<IInterfaceReference[]>([]);
70
89
  const [open, setOpen] = useState<boolean>(true);
71
90
 
91
+ const isPicked = (reference: IInterfaceReference) =>
92
+ picked.some(
93
+ (existing) =>
94
+ existing.interface_kind === reference.interface_kind &&
95
+ existing.interface_name === reference.interface_name
96
+ );
97
+
72
98
  const picker = (
73
99
  <ReferencePicker
74
100
  kinds={KINDS}
@@ -76,7 +102,21 @@ const Hosted = ({ withComposer }: { withComposer?: boolean }) => {
76
102
  onKindChange={setKind}
77
103
  items={CATALOGUE[kind] ?? []}
78
104
  picked={picked}
79
- onAdd={(reference) => setPicked((current) => [...current, reference])}
105
+ onAdd={(reference) =>
106
+ setPicked((current) => (isPicked(reference) ? current : [...current, reference]))
107
+ }
108
+ onRemove={(reference) =>
109
+ setPicked((current) =>
110
+ current.filter(
111
+ (existing) =>
112
+ !(
113
+ existing.interface_kind === reference.interface_kind &&
114
+ existing.interface_name === reference.interface_name
115
+ )
116
+ )
117
+ )
118
+ }
119
+ {...pickerProps}
80
120
  />
81
121
  );
82
122
 
@@ -92,7 +132,24 @@ const Hosted = ({ withComposer }: { withComposer?: boolean }) => {
92
132
  placeholder='Write a reply…'
93
133
  aboveInput={
94
134
  picked.length ? (
95
- <InterfaceReferenceTags size='tiny' customTheme={ACCENT} references={picked} />
135
+ /* Composer chips are the references you're about to send, so they carry
136
+ their own remove — and read a size up from the thread's, where they're
137
+ a record rather than a control. */
138
+ <InterfaceReferenceTags
139
+ customTheme={ACCENT}
140
+ references={picked}
141
+ onRemove={(reference) =>
142
+ setPicked((current) =>
143
+ current.filter(
144
+ (existing) =>
145
+ !(
146
+ existing.interface_kind === reference.interface_kind &&
147
+ existing.interface_name === reference.interface_name
148
+ )
149
+ )
150
+ )
151
+ }
152
+ />
96
153
  ) : undefined
97
154
  }
98
155
  footerActions={
@@ -123,14 +180,13 @@ const option = (canvas: ReturnType<typeof within>, name: string) =>
123
180
  const missingOption = (canvas: ReturnType<typeof within>, name: string) =>
124
181
  canvas.queryByRole('button', { name });
125
182
 
126
- /** Picking an interface adds it as a reference — and it stops being offered, so the
127
- * same thing can't be referenced twice. */
183
+ /** Picking an interface adds it as a reference and leaves the row in place, selected. */
128
184
  export const Default: Story = {
129
185
  parameters: {
130
186
  docs: {
131
187
  description: {
132
188
  story:
133
- "Renders the two-pane reference browser and, on picking an interface, adds it as a reference and drops it from the pane so the same thing can't be referenced twice.",
189
+ 'Renders the two-pane reference browser. Picking an interface adds it as a reference and leaves the row in the list, selected and ticked, rather than removing it.',
134
190
  },
135
191
  },
136
192
  },
@@ -139,13 +195,104 @@ export const Default: Story = {
139
195
  async play({ canvasElement }) {
140
196
  const canvas = within(canvasElement);
141
197
  await userEvent.click(await canvas.findByRole('button', { name: 'order-sync:1.2' }));
142
- // it moves out of the pane once picked
143
- await waitFor(() => expect(missingOption(canvas, 'order-sync:1.2')).not.toBeInTheDocument());
198
+ // it stays in the pane, now selected
199
+ await waitFor(() =>
200
+ expect(option(canvas, 'order-sync:1.2')).toHaveClass('reqore-reference-picker-picked')
201
+ );
144
202
  // its siblings are untouched
145
203
  await expect(option(canvas, 'invoice-export:2.0')).toBeInTheDocument();
146
204
  },
147
205
  };
148
206
 
207
+ /** A picked interface can be un-picked from the list it was picked in. */
208
+ export const DeselectsAPickedInterface: Story = {
209
+ parameters: {
210
+ docs: {
211
+ description: {
212
+ story:
213
+ 'Renders the browser and picks an interface, then clicks the same row again — the reference comes back off without the user having to click away from the picker.',
214
+ },
215
+ },
216
+ },
217
+ decorators: [darkTheme],
218
+ render: () => <Hosted />,
219
+ async play({ canvasElement }) {
220
+ const canvas = within(canvasElement);
221
+ const row = await canvas.findByRole('button', { name: 'invoice-export:2.0' });
222
+
223
+ await userEvent.click(row);
224
+ await waitFor(() => expect(row).toHaveClass('reqore-reference-picker-picked'));
225
+
226
+ await userEvent.click(row);
227
+ await waitFor(() => expect(row).not.toHaveClass('reqore-reference-picker-picked'));
228
+ },
229
+ };
230
+
231
+ /**
232
+ * Interfaces can arrive as bare names or as objects carrying a description and a
233
+ * badge — a name alone often isn't enough to tell two of them apart. A picked row
234
+ * tints rather than filling solid, so the ones still up for choosing stay the loudest
235
+ * things in the list.
236
+ */
237
+ export const WithDescriptionsAndBadges: Story = {
238
+ parameters: {
239
+ docs: {
240
+ description: {
241
+ story:
242
+ 'Renders a kind whose interfaces carry descriptions and badges alongside one that has neither. Picking a row tints it rather than filling it solid, keeping the unpicked rows the most prominent.',
243
+ },
244
+ },
245
+ },
246
+ decorators: [darkTheme],
247
+ render: () => <Hosted initialKind='service' />,
248
+ async play({ canvasElement }) {
249
+ const canvas = within(canvasElement);
250
+ await expect(
251
+ await canvas.findByText('Fans order events out to email and Slack')
252
+ ).toBeInTheDocument();
253
+ await expect(canvas.getByText('3 running')).toBeInTheDocument();
254
+
255
+ // a bare-string item in the same list renders as a plain row
256
+ await expect(option(canvas, 'partner-gateway')).toBeInTheDocument();
257
+
258
+ // picking keeps the description and badge in place
259
+ await userEvent.click(canvas.getByRole('button', { name: /audit-logger/ }));
260
+ await waitFor(() =>
261
+ expect(canvas.getByRole('button', { name: /audit-logger/ })).toHaveClass(
262
+ 'reqore-reference-picker-picked'
263
+ )
264
+ );
265
+ await expect(
266
+ canvas.getByText('Writes every state change to the audit table')
267
+ ).toBeInTheDocument();
268
+ },
269
+ };
270
+
271
+ /** A description is searchable text too, so the search reads it alongside the name. */
272
+ export const SearchMatchesDescriptions: Story = {
273
+ parameters: {
274
+ docs: {
275
+ description: {
276
+ story:
277
+ "Renders a kind with described interfaces and searches for a word that appears only in a description — the matching row survives even though its name doesn't contain the query.",
278
+ },
279
+ },
280
+ },
281
+ decorators: [darkTheme],
282
+ render: () => <Hosted initialKind='service' />,
283
+ async play({ canvasElement }) {
284
+ const canvas = within(canvasElement);
285
+ const search = canvasElement.querySelector(
286
+ 'input[placeholder*="Search types"]'
287
+ ) as HTMLInputElement;
288
+
289
+ // "Slack" is only in the notifier's description
290
+ await userEvent.type(search, 'slack');
291
+ await waitFor(() => expect(missingOption(canvas, 'partner-gateway')).not.toBeInTheDocument());
292
+ await expect(canvas.getByRole('button', { name: /notifier/ })).toBeInTheDocument();
293
+ },
294
+ };
295
+
149
296
  /** Selecting a kind on the left swaps the right pane to that kind's interfaces. */
150
297
  export const SwitchesKind: Story = {
151
298
  parameters: {
@@ -161,19 +308,23 @@ export const SwitchesKind: Story = {
161
308
  async play({ canvasElement }) {
162
309
  const canvas = within(canvasElement);
163
310
  await expect(option(canvas, 'order-sync:1.2')).toBeInTheDocument();
164
- await userEvent.click(option(canvas, 'connection'));
311
+ await userEvent.click(option(canvas, 'Connections'));
165
312
  await waitFor(() => expect(option(canvas, 'sftp-partner')).toBeInTheDocument());
166
313
  await expect(missingOption(canvas, 'order-sync:1.2')).not.toBeInTheDocument();
167
314
  },
168
315
  };
169
316
 
170
- /** One search scopes both panes: the kind list and the selected kind's interfaces. */
171
- export const SearchScopesBothPanes: Story = {
317
+ /**
318
+ * One search scopes both panes: non-matching kinds drop off the left so the matches
319
+ * are the whole list, and the selected kind stays on — dimmed — even when the query
320
+ * doesn't match it, so the interfaces on the right always have a visible owner.
321
+ */
322
+ export const SearchKeepsTheSelectedKind: Story = {
172
323
  parameters: {
173
324
  docs: {
174
325
  description: {
175
326
  story:
176
- "Renders the picker and shows its single search box scoping both panes at once — filtering the selected kind's interfaces and the kind list together.",
327
+ "Renders the picker and types a search. Non-matching kinds drop off the left pane, but the selected kind stays on as a dimmed row so the interfaces still listed on the right can be told apart from the kind they belong to.",
177
328
  },
178
329
  },
179
330
  },
@@ -182,7 +333,7 @@ export const SearchScopesBothPanes: Story = {
182
333
  async play({ canvasElement }) {
183
334
  const canvas = within(canvasElement);
184
335
  const search = canvasElement.querySelector(
185
- 'input[placeholder*="Search kinds"]'
336
+ 'input[placeholder*="Search types"]'
186
337
  ) as HTMLInputElement;
187
338
 
188
339
  await userEvent.type(search, 'sync');
@@ -191,14 +342,84 @@ export const SearchScopesBothPanes: Story = {
191
342
  expect(missingOption(canvas, 'invoice-export:2.0')).not.toBeInTheDocument()
192
343
  );
193
344
  await expect(option(canvas, 'order-sync:1.2')).toBeInTheDocument();
194
- // left pane: no kind is called "sync", so the kind list empties out
195
- await expect(canvas.getByText('No kinds')).toBeInTheDocument();
345
+ // left pane: nothing is called "sync", so only the selected kind is left — dimmed,
346
+ // which is what tells you the surviving interface is a workflow
347
+ await expect(option(canvas, 'Workflows')).toHaveClass('reqore-reference-picker-dimmed');
348
+ await expect(missingOption(canvas, 'Services')).not.toBeInTheDocument();
196
349
 
197
- // a kind name matches: that kind stays, and every other kind drops away
350
+ // a kind name matches: it joins the list at full strength, next to the still-dimmed
351
+ // selected kind
198
352
  await userEvent.clear(search);
199
353
  await userEvent.type(search, 'connect');
200
- await waitFor(() => expect(option(canvas, 'connection')).toBeInTheDocument());
201
- await expect(missingOption(canvas, 'workflow')).not.toBeInTheDocument();
354
+ await waitFor(() =>
355
+ expect(option(canvas, 'Connections')).not.toHaveClass(
356
+ 'reqore-reference-picker-dimmed'
357
+ )
358
+ );
359
+ await expect(option(canvas, 'Workflows')).toHaveClass('reqore-reference-picker-dimmed');
360
+ await expect(missingOption(canvas, 'Jobs')).not.toBeInTheDocument();
361
+ },
362
+ };
363
+
364
+ /**
365
+ * Narrow container: the panes collapse into one column and the selected kind's
366
+ * interfaces render directly under its row. The width measured is the picker's own,
367
+ * not the viewport's — this canvas is 320px wide on a desktop-sized screen, which is
368
+ * exactly the case a viewport media query gets wrong.
369
+ */
370
+ export const StacksInANarrowContainer: Story = {
371
+ parameters: {
372
+ docs: {
373
+ description: {
374
+ story:
375
+ "Renders the picker in a 320px-wide container on a full-size screen: it collapses to one column on its own measured width, with the selected kind's interfaces indented directly beneath its row instead of in a second pane.",
376
+ },
377
+ },
378
+ },
379
+ decorators: [darkTheme],
380
+ render: () => (
381
+ <div style={{ width: 320 }}>
382
+ <Hosted />
383
+ </div>
384
+ ),
385
+ async play({ canvasElement }) {
386
+ const canvas = within(canvasElement);
387
+ const services = option(canvas, 'Services');
388
+
389
+ /* Stacked is a claim about ORDER, not just presence: the workflows sit between
390
+ their own kind row and the next one. Side by side, every kind would come
391
+ first and the interfaces after — so the comparison tells the two apart
392
+ without reaching for a marker class. */
393
+ await waitFor(() => {
394
+ const workflow = option(canvas, 'order-sync:1.2');
395
+ expect(
396
+ workflow.compareDocumentPosition(services) & Node.DOCUMENT_POSITION_FOLLOWING
397
+ ).toBeTruthy();
398
+ });
399
+ await expect(option(canvas, 'Workflows')).toBeInTheDocument();
400
+ },
401
+ };
402
+
403
+ /** On a phone the picker lifts into a modal — inline it would own the whole screen. */
404
+ export const OnAPhone: Story = {
405
+ parameters: {
406
+ // capture this one at phone size; the modal portals to the viewport, so a
407
+ // narrow story wrapper wouldn't constrain it
408
+ qlip: { viewport: { width: 390, height: 844 } },
409
+ docs: {
410
+ description: {
411
+ story:
412
+ 'Renders the phone presentation: the picker lifts out of the composer into a full-width modal with a close action, stacked into a single column.',
413
+ },
414
+ },
415
+ },
416
+ decorators: [darkTheme],
417
+ render: () => <Hosted presentation='modal' layout='stacked' onClose={fn()} />,
418
+ async play() {
419
+ // the modal portals out of the story canvas, so query the document
420
+ const canvas = within(document.body);
421
+ await expect(await canvas.findByText('Reference interfaces')).toBeInTheDocument();
422
+ await expect(await canvas.findByRole('button', { name: 'order-sync:1.2' })).toBeInTheDocument();
202
423
  },
203
424
  };
204
425
 
@@ -226,7 +447,7 @@ export const Loading: Story = {
226
447
  async play({ canvasElement }) {
227
448
  const canvas = within(canvasElement);
228
449
  await expect(canvas.getByText('Loading workflows…')).toBeInTheDocument();
229
- await expect(canvas.queryByText('No matching workflow')).not.toBeInTheDocument();
450
+ await expect(canvas.queryByText(/No workflows/)).not.toBeInTheDocument();
230
451
  },
231
452
  };
232
453
 
@@ -261,7 +482,7 @@ export const LoadFailed: Story = {
261
482
  const retry = await canvas.findByRole('button', { name: /Couldn't load connections/ });
262
483
  await expect(retry).toBeInTheDocument();
263
484
  // the failure replaces the list rather than sitting alongside an empty one
264
- await expect(canvas.queryByText('No matching connection')).not.toBeInTheDocument();
485
+ await expect(canvas.queryByText(/No connections/)).not.toBeInTheDocument();
265
486
  },
266
487
  };
267
488
 
@@ -281,8 +502,10 @@ export const AboveTheComposer: Story = {
281
502
  async play({ canvasElement }) {
282
503
  const canvas = within(canvasElement);
283
504
  await userEvent.click(await canvas.findByRole('button', { name: 'order-sync:1.2' }));
284
- // the pick leaves the pane and surfaces as a chip on the composer
285
- await waitFor(() => expect(missingOption(canvas, 'order-sync:1.2')).not.toBeInTheDocument());
505
+ // the pick stays selected in the pane and surfaces as a chip on the composer
506
+ await waitFor(() =>
507
+ expect(option(canvas, 'order-sync:1.2')).toHaveClass('reqore-reference-picker-picked')
508
+ );
286
509
  await expect(canvas.getAllByText('order-sync:1.2').length).toBeGreaterThan(0);
287
510
 
288
511
  const toggle = canvasElement.querySelector('.references-toggle') as HTMLElement;