@marimo-team/frontend 0.23.4-dev1 → 0.23.4-dev11

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 (37) hide show
  1. package/dist/assets/JsonOutput-D5TiQGJ4.js +49 -0
  2. package/dist/assets/{add-connection-dialog-gd2yD9Qo.js → add-connection-dialog-Dg6K--Uw.js} +1 -1
  3. package/dist/assets/{agent-panel-TetMUXWP.js → agent-panel-oPuvVsjs.js} +1 -1
  4. package/dist/assets/{cell-editor-C6vZfKyj.js → cell-editor-CdyZleud.js} +3 -3
  5. package/dist/assets/{column-preview-_5uYiy74.js → column-preview-B1KZjh-X.js} +1 -1
  6. package/dist/assets/command-CXDOCJwp.js +1 -0
  7. package/dist/assets/{command-palette-DfotChyr.js → command-palette-C7AbtCWz.js} +1 -1
  8. package/dist/assets/{dependency-graph-panel-Yk1Ch_Gj.js → dependency-graph-panel-k1WQZ4m-.js} +1 -1
  9. package/dist/assets/{edit-page-C-LIP5uT.js → edit-page-CbGDoOay.js} +3 -3
  10. package/dist/assets/{file-explorer-panel-Dlwspu2Y.js → file-explorer-panel-Cjlf3QNa.js} +2 -2
  11. package/dist/assets/{form-Dr-_EXr8.js → form-D1abghOq.js} +1 -1
  12. package/dist/assets/{hooks-CNPv5U-N.js → hooks-4gSLiehn.js} +1 -1
  13. package/dist/assets/{index-BFivsT-R.js → index-rjcv10Dv.js} +8 -8
  14. package/dist/assets/{layout-DDh3d8oW.js → layout-C4u-1iTo.js} +3 -3
  15. package/dist/assets/loro_wasm_bg-Bu4Ofw1K.js +2 -0
  16. package/dist/assets/{loro_wasm_bg-DXuHQ2hl.js → loro_wasm_bg-DWJCS9pK.js} +1 -1
  17. package/dist/assets/{panels-CbRenLjk.js → panels-CpvbcuTn.js} +1 -1
  18. package/dist/assets/{reveal-component-CLjsFHmT.js → reveal-component-C2X5QJAC.js} +1 -1
  19. package/dist/assets/{run-page-C1Wd8dr8.js → run-page-BYdI3kiE.js} +1 -1
  20. package/dist/assets/{scratchpad-panel-DpqODhZ3.js → scratchpad-panel-BP6-rZV6.js} +1 -1
  21. package/dist/assets/{session-panel-BvCIuxgU.js → session-panel-YGK2D21A.js} +1 -1
  22. package/dist/assets/{slide-form-CXqvfwGg.js → slide-form-BE8M6oJo.js} +1 -1
  23. package/dist/assets/{snippets-panel-COBTGJcr.js → snippets-panel-CnN632Ki.js} +1 -1
  24. package/dist/assets/{state-LyvUXW6A.js → state-CoUA-8Ay.js} +1 -1
  25. package/dist/assets/{useDependencyPanelTab-DFZNQ3a8.js → useDependencyPanelTab-8WaMRWbo.js} +1 -1
  26. package/dist/assets/useNotebookActions-bCZiRDS-.js +1 -0
  27. package/dist/index.html +6 -6
  28. package/package.json +4 -4
  29. package/src/components/data-table/__tests__/column-header.test.ts +63 -0
  30. package/src/components/data-table/column-header.tsx +50 -159
  31. package/src/components/data-table/filter-by-values-picker.tsx +7 -4
  32. package/src/components/editor/actions/pair-with-agent-modal.tsx +1 -1
  33. package/src/components/editor/actions/useNotebookActions.tsx +1 -0
  34. package/dist/assets/JsonOutput-BVAcY5xS.js +0 -49
  35. package/dist/assets/command-CzIDL1VI.js +0 -1
  36. package/dist/assets/loro_wasm_bg-Ds40eH8K.js +0 -2
  37. package/dist/assets/useNotebookActions-F5fGMe9N.js +0 -1
package/dist/index.html CHANGED
@@ -66,7 +66,7 @@
66
66
  <marimo-server-token data-token="{{ server_token }}" hidden></marimo-server-token>
67
67
  <!-- /TODO -->
68
68
  <title>{{ title }}</title>
69
- <script type="module" crossorigin src="./assets/index-BFivsT-R.js"></script>
69
+ <script type="module" crossorigin src="./assets/index-rjcv10Dv.js"></script>
70
70
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-D2MJg03u.js">
71
71
  <link rel="modulepreload" crossorigin href="./assets/chunk-LvLJmgfZ.js">
72
72
  <link rel="modulepreload" crossorigin href="./assets/react-Bj1aDYRI.js">
@@ -170,7 +170,7 @@
170
170
  <link rel="modulepreload" crossorigin href="./assets/micromark-factory-space-WzovnJik.js">
171
171
  <link rel="modulepreload" crossorigin href="./assets/chunk-5FQGJX7Z-B4RvNAGm.js">
172
172
  <link rel="modulepreload" crossorigin href="./assets/markdown-renderer-CFrgiLqu.js">
173
- <link rel="modulepreload" crossorigin href="./assets/command-CzIDL1VI.js">
173
+ <link rel="modulepreload" crossorigin href="./assets/command-CXDOCJwp.js">
174
174
  <link rel="modulepreload" crossorigin href="./assets/popover-UExmgBsf.js">
175
175
  <link rel="modulepreload" crossorigin href="./assets/errors-CZb6hI2x.js">
176
176
  <link rel="modulepreload" crossorigin href="./assets/download-Iyng0xCz.js">
@@ -191,7 +191,7 @@
191
191
  <link rel="modulepreload" crossorigin href="./assets/message-circle-CWm2KnSx.js">
192
192
  <link rel="modulepreload" crossorigin href="./assets/trash-2-D280Xiwg.js">
193
193
  <link rel="modulepreload" crossorigin href="./assets/react-resizable-panels.browser.esm-Bcm5njwd.js">
194
- <link rel="modulepreload" crossorigin href="./assets/JsonOutput-BVAcY5xS.js">
194
+ <link rel="modulepreload" crossorigin href="./assets/JsonOutput-D5TiQGJ4.js">
195
195
  <link rel="modulepreload" crossorigin href="./assets/chart-no-axes-column-nqk474t8.js">
196
196
  <link rel="modulepreload" crossorigin href="./assets/square-function-D1dlJvD8.js">
197
197
  <link rel="modulepreload" crossorigin href="./assets/spec-Cq8FVoTf.js">
@@ -199,7 +199,7 @@
199
199
  <link rel="modulepreload" crossorigin href="./assets/refresh-cw-DHwG4Mac.js">
200
200
  <link rel="modulepreload" crossorigin href="./assets/tree-actions-oMCx6WNc.js">
201
201
  <link rel="modulepreload" crossorigin href="./assets/components-FepcpFGL.js">
202
- <link rel="modulepreload" crossorigin href="./assets/column-preview-_5uYiy74.js">
202
+ <link rel="modulepreload" crossorigin href="./assets/column-preview-B1KZjh-X.js">
203
203
  <link rel="modulepreload" crossorigin href="./assets/icons-BRopQwI3.js">
204
204
  <link rel="modulepreload" crossorigin href="./assets/floating-outline-CSNGDMAm.js">
205
205
  <link rel="modulepreload" crossorigin href="./assets/useAddCell-De9xD63_.js">
@@ -220,12 +220,12 @@
220
220
  <link rel="modulepreload" crossorigin href="./assets/memoize-Tp7rARFe.js">
221
221
  <link rel="modulepreload" crossorigin href="./assets/get-C-qh_et5.js">
222
222
  <link rel="modulepreload" crossorigin href="./assets/_baseSet-CxV9N1bc.js">
223
- <link rel="modulepreload" crossorigin href="./assets/state-LyvUXW6A.js">
223
+ <link rel="modulepreload" crossorigin href="./assets/state-CoUA-8Ay.js">
224
224
  <link rel="modulepreload" crossorigin href="./assets/label-DTR8T0AE.js">
225
225
  <link rel="modulepreload" crossorigin href="./assets/textarea-Dd0InTQJ.js">
226
226
  <link rel="modulepreload" crossorigin href="./assets/radio-group-BtBoRbGH.js">
227
227
  <link rel="modulepreload" crossorigin href="./assets/refresh-ccw-C-n2VFP5.js">
228
- <link rel="modulepreload" crossorigin href="./assets/form-Dr-_EXr8.js">
228
+ <link rel="modulepreload" crossorigin href="./assets/form-D1abghOq.js">
229
229
  <link rel="modulepreload" crossorigin href="./assets/renderShortcut-CkNNAheg.js">
230
230
  <link rel="modulepreload" crossorigin href="./assets/field-B8kJgr2A.js">
231
231
  <link rel="modulepreload" crossorigin href="./assets/RSPContexts-BeHIgT4C.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/frontend",
3
- "version": "0.23.4-dev1",
3
+ "version": "0.23.4-dev11",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -209,7 +209,7 @@
209
209
  "@types/react": "^19.2.10",
210
210
  "@types/react-dom": "^19.2.3",
211
211
  "@types/timestring": "^6.0.5",
212
- "@vitejs/plugin-react": "^5.1.4",
212
+ "@vitejs/plugin-react": "^5.2.0",
213
213
  "babel-plugin-react-compiler": "19.1.0-rc.3",
214
214
  "blob-polyfill": "^7.0.20220408",
215
215
  "cross-env": "^7.0.3",
@@ -226,11 +226,11 @@
226
226
  "storybook": "^10.2.12",
227
227
  "stylelint": "^16.26.1",
228
228
  "stylelint-config-standard": "^36.0.1",
229
- "tailwindcss": "^4.2.0",
229
+ "tailwindcss": "^4.2.2",
230
230
  "vega-typings": "^2.1.0",
231
231
  "vite": "npm:rolldown-vite@7.3.1",
232
232
  "vite-plugin-top-level-await": "^1.6.0",
233
- "vite-plugin-wasm": "^3.5.0",
233
+ "vite-plugin-wasm": "^3.6.0",
234
234
  "vitest": "^3.2.4"
235
235
  }
236
236
  }
@@ -0,0 +1,63 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { describe, expect, it } from "vitest";
4
+ import { seedFromFilter } from "../column-header";
5
+ import { Filter } from "../filters";
6
+
7
+ describe("seedFromFilter", () => {
8
+ it("returns empty defaults when there is no filter", () => {
9
+ expect(seedFromFilter(undefined)).toEqual({
10
+ values: [],
11
+ operator: "in",
12
+ });
13
+ });
14
+
15
+ it("seeds values from an `in` select filter", () => {
16
+ const filter = Filter.select({
17
+ options: ["Flying", "Ground"],
18
+ operator: "in",
19
+ });
20
+ expect(seedFromFilter(filter)).toEqual({
21
+ values: ["Flying", "Ground"],
22
+ operator: "in",
23
+ });
24
+ });
25
+
26
+ it("preserves `not_in` so reapplying does not silently flip to `in`", () => {
27
+ const filter = Filter.select({
28
+ options: ["Fire"],
29
+ operator: "not_in",
30
+ });
31
+ expect(seedFromFilter(filter)).toEqual({
32
+ values: ["Fire"],
33
+ operator: "not_in",
34
+ });
35
+ });
36
+
37
+ it("returns a fresh array (callers may mutate without affecting the filter)", () => {
38
+ const options = ["a", "b"];
39
+ const filter = Filter.select({ options, operator: "in" });
40
+ const seeded = seedFromFilter(filter);
41
+ seeded.values.push("c");
42
+ expect(options).toEqual(["a", "b"]);
43
+ });
44
+
45
+ it("ignores non-select filters and falls back to defaults", () => {
46
+ expect(
47
+ seedFromFilter(Filter.text({ text: "abc", operator: "equals" })),
48
+ ).toEqual({
49
+ values: [],
50
+ operator: "in",
51
+ });
52
+ expect(seedFromFilter(Filter.number({ min: 0, max: 10 }))).toEqual({
53
+ values: [],
54
+ operator: "in",
55
+ });
56
+ expect(
57
+ seedFromFilter(Filter.boolean({ value: true, operator: "is_true" })),
58
+ ).toEqual({
59
+ values: [],
60
+ operator: "in",
61
+ });
62
+ });
63
+ });
@@ -9,7 +9,7 @@ import {
9
9
  TextIcon,
10
10
  XIcon,
11
11
  } from "lucide-react";
12
- import { useMemo, useRef, useState } from "react";
12
+ import { useRef, useState } from "react";
13
13
  import { useLocale } from "react-aria";
14
14
  import {
15
15
  DropdownMenu,
@@ -22,24 +22,12 @@ import {
22
22
  DropdownMenuSubTrigger,
23
23
  DropdownMenuTrigger,
24
24
  } from "@/components/ui/dropdown-menu";
25
- import { useAsyncData } from "@/hooks/useAsyncData";
26
- import { ErrorBanner } from "@/plugins/impl/common/error-banner";
27
25
  import type { CalculateTopKRows } from "@/plugins/impl/DataTablePlugin";
28
26
  import type { OperatorType } from "@/plugins/impl/data-frames/utils/operators";
29
27
  import { logNever } from "@/utils/assertNever";
30
28
  import { cn } from "@/utils/cn";
31
- import { Logger } from "@/utils/Logger";
32
29
  import { capitalize } from "@/utils/strings";
33
- import { Spinner } from "../icons/spinner";
34
30
  import { Button } from "../ui/button";
35
- import { Checkbox } from "../ui/checkbox";
36
- import {
37
- Command,
38
- CommandEmpty,
39
- CommandInput,
40
- CommandItem,
41
- CommandList,
42
- } from "../ui/command";
43
31
  import { DraggablePopover } from "../ui/draggable-popover";
44
32
  import { Input } from "../ui/input";
45
33
  import { NumberField } from "../ui/number-field";
@@ -52,8 +40,12 @@ import {
52
40
  SelectTrigger,
53
41
  SelectValue,
54
42
  } from "../ui/select";
55
- import { type ColumnFilterForType, Filter } from "./filters";
56
- import { SentinelCell } from "./sentinel-cell";
43
+ import { FilterByValuesList } from "./filter-by-values-picker";
44
+ import {
45
+ type ColumnFilterForType,
46
+ type ColumnFilterValue,
47
+ Filter,
48
+ } from "./filters";
57
49
  import {
58
50
  ClearFilterMenuItem,
59
51
  FilterButtons,
@@ -66,9 +58,6 @@ import {
66
58
  renderSortFilterIcon,
67
59
  renderSorts,
68
60
  } from "./header-items";
69
- import { detectSentinel, stringifyUnknownValue } from "./utils";
70
-
71
- const TOP_K_ROWS = 30;
72
61
 
73
62
  interface DataTableColumnHeaderProps<
74
63
  TData,
@@ -534,6 +523,26 @@ const TextFilter = <TData, TValue>({
534
523
  );
535
524
  };
536
525
 
526
+ /**
527
+ * Seed the filter-by-values picker from a column's existing filter value.
528
+ *
529
+ * Reopening the picker should reflect what's already applied. Only `select`
530
+ * filters carry checkbox-style values; other filter shapes (number, text,
531
+ * etc.) seed an empty list.
532
+ */
533
+ export function seedFromFilter(value: ColumnFilterValue | undefined): {
534
+ values: unknown[];
535
+ operator: Extract<OperatorType, "in" | "not_in">;
536
+ } {
537
+ if (value && "type" in value && value.type === "select") {
538
+ return {
539
+ values: [...value.options],
540
+ operator: value.operator === "not_in" ? "not_in" : "in",
541
+ };
542
+ }
543
+ return { values: [], operator: "in" };
544
+ }
545
+
537
546
  const PopoverFilterByValues = <TData, TValue>({
538
547
  setIsFilterValueOpen,
539
548
  calculateTopKRows,
@@ -543,69 +552,13 @@ const PopoverFilterByValues = <TData, TValue>({
543
552
  calculateTopKRows?: CalculateTopKRows;
544
553
  column: Column<TData, TValue>;
545
554
  }) => {
546
- const [chosenValues, setChosenValues] = useState<Set<unknown>>(new Set());
547
- const [query, setQuery] = useState<string>("");
548
-
549
- const { data, isPending, error } = useAsyncData(async () => {
550
- if (!calculateTopKRows) {
551
- return null;
552
- }
553
- const res = await calculateTopKRows({ column: column.id, k: TOP_K_ROWS });
554
- return res.data;
555
- }, []);
556
-
557
- const filteredData = useMemo(() => {
558
- if (!data) {
559
- return [];
560
- }
561
-
562
- try {
563
- return data.filter(([value, _count]) => {
564
- // Check if value exists and can be converted to string
565
- // Keep null values for filtering
566
- return value === undefined
567
- ? false
568
- : String(value).toLowerCase().includes(query.toLowerCase());
569
- });
570
- } catch (error_) {
571
- Logger.error("Error filtering data", error_);
572
- return [];
573
- }
574
- }, [data, query]);
575
-
576
- let dataTable: React.ReactNode;
577
-
578
- if (isPending) {
579
- dataTable = <Spinner size="medium" className="mx-auto mt-12 mb-10" />;
580
- }
581
-
582
- if (error) {
583
- dataTable = <ErrorBanner error={error} className="my-10 mx-4" />;
584
- }
585
-
586
- const handleToggle = (value: unknown) => {
587
- setChosenValues((prev) => {
588
- const checked = prev.has(value);
589
- const newSet = new Set(prev);
590
- if (checked) {
591
- newSet.delete(value);
592
- } else {
593
- newSet.add(value);
594
- }
595
- return newSet;
596
- });
597
- };
555
+ const seed = seedFromFilter(
556
+ column.getFilterValue() as ColumnFilterValue | undefined,
557
+ );
598
558
 
599
- const handleToggleAll = (checked: boolean) => {
600
- if (!data) {
601
- return;
602
- }
603
- if (checked) {
604
- setChosenValues(new Set(filteredData.map(([value]) => value)));
605
- } else {
606
- setChosenValues(new Set());
607
- }
608
- };
559
+ const [chosenValues, setChosenValues] = useState<Set<unknown>>(
560
+ () => new Set(seed.values),
561
+ );
609
562
 
610
563
  const handleApply = () => {
611
564
  if (chosenValues.size === 0) {
@@ -613,87 +566,13 @@ const PopoverFilterByValues = <TData, TValue>({
613
566
  return;
614
567
  }
615
568
  column.setFilterValue(
616
- Filter.select({ options: [...chosenValues], operator: "in" }),
569
+ Filter.select({
570
+ options: [...chosenValues],
571
+ operator: seed.operator,
572
+ }),
617
573
  );
618
574
  };
619
575
 
620
- if (data) {
621
- const allChecked = chosenValues.size === filteredData.length;
622
-
623
- dataTable = (
624
- <>
625
- <Command className="text-sm outline-hidden" shouldFilter={false}>
626
- <CommandInput
627
- placeholder={`Search among the top ${data.length} values`}
628
- autoFocus={true}
629
- onValueChange={(value) => setQuery(value.trim())}
630
- />
631
- <CommandEmpty>No results found.</CommandEmpty>
632
- <CommandList className="border-b">
633
- {filteredData.length > 0 && (
634
- <CommandItem
635
- value="__select-all__"
636
- className="border-b rounded-none px-3"
637
- onSelect={() => handleToggleAll(!allChecked)}
638
- >
639
- <Checkbox
640
- checked={chosenValues.size === filteredData.length}
641
- aria-label="Select all"
642
- className="mr-3 h-3.5 w-3.5"
643
- />
644
- <span className="font-bold flex-1">{column.id}</span>
645
- <span className="font-bold">Count</span>
646
- </CommandItem>
647
- )}
648
- {filteredData.map(([value, count], rowIndex) => {
649
- const isSelected = chosenValues.has(value);
650
- const valueString = stringifyUnknownValue({ value });
651
- const sentinel = detectSentinel(
652
- value,
653
- column.columnDef.meta?.dataType,
654
- );
655
-
656
- return (
657
- <CommandItem
658
- key={rowIndex}
659
- value={valueString}
660
- className="not-last:border-b rounded-none px-3"
661
- onSelect={() => handleToggle(value)}
662
- >
663
- <Checkbox
664
- checked={isSelected}
665
- aria-label="Select row"
666
- className="mr-3 h-3.5 w-3.5"
667
- />
668
- <span className="flex-1 overflow-hidden max-h-20 line-clamp-3">
669
- {sentinel ? (
670
- <SentinelCell sentinel={sentinel} />
671
- ) : (
672
- valueString
673
- )}
674
- </span>
675
- <span className="ml-3">{count}</span>
676
- </CommandItem>
677
- );
678
- })}
679
- </CommandList>
680
- {filteredData.length === TOP_K_ROWS && (
681
- <span className="text-xs text-muted-foreground mt-1.5 text-center">
682
- Only showing the top {TOP_K_ROWS} values
683
- </span>
684
- )}
685
- </Command>
686
- <FilterButtons
687
- onApply={handleApply}
688
- onClear={() => {
689
- setChosenValues(new Set());
690
- }}
691
- clearButtonDisabled={chosenValues.size === 0}
692
- />
693
- </>
694
- );
695
- }
696
-
697
576
  return (
698
577
  <DraggablePopover
699
578
  open={true}
@@ -709,7 +588,19 @@ const PopoverFilterByValues = <TData, TValue>({
709
588
  <XIcon className="h-4 w-4" />
710
589
  </Button>
711
590
  </PopoverClose>
712
- <div className="flex flex-col gap-1.5 py-2">{dataTable}</div>
591
+ <div className="flex flex-col gap-1.5 py-2">
592
+ <FilterByValuesList
593
+ column={column}
594
+ calculateTopKRows={calculateTopKRows}
595
+ chosenValues={chosenValues}
596
+ onChange={(values) => setChosenValues(new Set(values))}
597
+ />
598
+ <FilterButtons
599
+ onApply={handleApply}
600
+ onClear={() => setChosenValues(new Set())}
601
+ clearButtonDisabled={chosenValues.size === 0}
602
+ />
603
+ </div>
713
604
  </DraggablePopover>
714
605
  );
715
606
  };
@@ -74,7 +74,7 @@ export const FilterByValuesPicker = <TData, TValue>({
74
74
  </Button>
75
75
  </PopoverTrigger>
76
76
  <PopoverContent className="w-80 p-0">
77
- <PickerBody
77
+ <FilterByValuesList
78
78
  column={column}
79
79
  calculateTopKRows={calculateTopKRows}
80
80
  chosenValues={chosenValuesSet}
@@ -85,19 +85,22 @@ export const FilterByValuesPicker = <TData, TValue>({
85
85
  );
86
86
  };
87
87
 
88
- interface PickerBodyProps<TData, TValue> {
88
+ interface FilterByValuesListProps<TData, TValue> {
89
89
  column: Column<TData, TValue>;
90
90
  calculateTopKRows?: CalculateTopKRows;
91
91
  chosenValues: Set<unknown>;
92
92
  onChange: (values: unknown[]) => void;
93
93
  }
94
94
 
95
- const PickerBody = <TData, TValue>({
95
+ /**
96
+ * Search + checkbox list that powers the "filter by values" picker.
97
+ */
98
+ export const FilterByValuesList = <TData, TValue>({
96
99
  column,
97
100
  calculateTopKRows,
98
101
  chosenValues,
99
102
  onChange,
100
- }: PickerBodyProps<TData, TValue>) => {
103
+ }: FilterByValuesListProps<TData, TValue>) => {
101
104
  const [query, setQuery] = useState<string>("");
102
105
 
103
106
  const { data, isPending, error } = useAsyncData(async () => {
@@ -37,7 +37,7 @@ function getPromptCommand(
37
37
  case "codex":
38
38
  return `codex "$(${base} --codex)"`;
39
39
  case "opencode":
40
- return `opencode "$(${base} --opencode)"`;
40
+ return `opencode --prompt "$(${base} --opencode)"`;
41
41
  default:
42
42
  assertNever(agent);
43
43
  }
@@ -348,6 +348,7 @@ export function useNotebookActions() {
348
348
  {
349
349
  icon: <SparklesIcon size={14} strokeWidth={1.5} />,
350
350
  label: "Pair with an agent",
351
+ hidden: isWasm(),
351
352
  handle: async () => {
352
353
  openModal(<PairWithAgentModal onClose={closeModal} />);
353
354
  },