diff --git a/packages/benchmarks/.gitignore b/packages/benchmarks/.gitignore
index 24cefbf..d61b80a 100644
--- a/packages/benchmarks/.gitignore
+++ b/packages/benchmarks/.gitignore
@@ -2,3 +2,5 @@
traces/
# Benchmark JSON results and reports (generated by bench:json / bench:report)
results/
+# Reassure performance test output (generated by perf / perf:baseline)
+.reassure/
diff --git a/packages/benchmarks/package.json b/packages/benchmarks/package.json
index 7ec9791..ef4910b 100644
--- a/packages/benchmarks/package.json
+++ b/packages/benchmarks/package.json
@@ -6,9 +6,13 @@
"type": "module",
"scripts": {
"bench": "vitest bench --run",
- "bench:browser": "vitest bench --config vitest.config.browser.ts --run"
+ "bench:browser": "vitest bench --config vitest.config.browser.ts --run",
+ "perf": "rm -f .reassure/current.perf && vitest --run --config vitest.config.perf.ts",
+ "perf:baseline": "rm -f .reassure/baseline.perf && REASSURE_OUTPUT_FILE=.reassure/baseline.perf vitest --run --config vitest.config.perf.ts",
+ "perf:compare": "node scripts/perf-compare.mjs"
},
"devDependencies": {
+ "@callstack/reassure-compare": "^1.4.1",
"@codspeed/vitest-plugin": "^5.2.0",
"@omniview/base-ui": "workspace:*",
"@omniview/editors": "workspace:*",
@@ -22,6 +26,7 @@
"jsdom": "^25.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "reassure": "^1.4.1",
"typescript": "^5.9.2",
"vite": "^7.3.1",
"vitest": "^4.0.0"
diff --git a/packages/benchmarks/scripts/perf-compare.mjs b/packages/benchmarks/scripts/perf-compare.mjs
new file mode 100644
index 0000000..204dbf9
--- /dev/null
+++ b/packages/benchmarks/scripts/perf-compare.mjs
@@ -0,0 +1,16 @@
+/**
+ * Compare Reassure performance results between baseline and current runs.
+ *
+ * Usage:
+ * pnpm perf:baseline # run on base branch, writes .reassure/baseline.perf
+ * pnpm perf # run on current branch, writes .reassure/current.perf
+ * pnpm perf:compare # compare and output report
+ */
+import { compare } from '@callstack/reassure-compare';
+
+await compare({
+ baselineFile: '.reassure/baseline.perf',
+ currentFile: '.reassure/current.perf',
+ outputFile: '.reassure/output.json',
+ outputFormat: 'all',
+});
diff --git a/packages/benchmarks/src/__perf__/base-ui/Accordion.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Accordion.perf-test.tsx
new file mode 100644
index 0000000..3aa335b
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/base-ui/Accordion.perf-test.tsx
@@ -0,0 +1,49 @@
+import { test } from 'vitest';
+import { measureRenders } from 'reassure';
+import { fireEvent, screen } from '@testing-library/react';
+import { Accordion } from '@omniview/base-ui';
+import { ThemeWrapper } from '../utils/theme-wrapper';
+
+function FiveItemAccordion() {
+ return (
+
+ {Array.from({ length: 5 }, (_, i) => (
+
+ Content for section {i}
+
+ ))}
+
+ );
+}
+
+test('Accordion: mount 5 items', async () => {
+ await measureRenders(, { wrapper: ThemeWrapper });
+});
+
+/**
+ * Expanding one section should NOT re-render sibling sections.
+ * This catches shared-context over-notification patterns.
+ */
+test('Accordion: expand one section', async () => {
+ await measureRenders(, {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ fireEvent.click(screen.getByText('Section 2'));
+ },
+ });
+});
+
+/**
+ * Expanding then collapsing — tests the full toggle cycle.
+ * Render count should be similar to a single expand.
+ */
+test('Accordion: expand then collapse', async () => {
+ await measureRenders(, {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ const trigger = screen.getByText('Section 2');
+ fireEvent.click(trigger);
+ fireEvent.click(trigger);
+ },
+ });
+});
diff --git a/packages/benchmarks/src/__perf__/base-ui/Button.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Button.perf-test.tsx
new file mode 100644
index 0000000..a33554c
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/base-ui/Button.perf-test.tsx
@@ -0,0 +1,22 @@
+import { test } from 'vitest';
+import { measureRenders } from 'reassure';
+import { Button } from '@omniview/base-ui';
+import { ThemeWrapper } from '../utils/theme-wrapper';
+
+/**
+ * Button is the simplest interactive component — its render count
+ * serves as the baseline for all other components.
+ * Expected: 1 render on mount, 0 issues.
+ */
+test('Button: mount', async () => {
+ await measureRenders(, { wrapper: ThemeWrapper });
+});
+
+test('Button: mount with decorators', async () => {
+ await measureRenders(
+ ,
+ { wrapper: ThemeWrapper },
+ );
+});
diff --git a/packages/benchmarks/src/__perf__/base-ui/Checkbox.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Checkbox.perf-test.tsx
new file mode 100644
index 0000000..5f3c972
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/base-ui/Checkbox.perf-test.tsx
@@ -0,0 +1,49 @@
+import { test } from 'vitest';
+import { measureRenders } from 'reassure';
+import { fireEvent, screen } from '@testing-library/react';
+import { Checkbox } from '@omniview/base-ui';
+import { ThemeWrapper } from '../utils/theme-wrapper';
+
+test('Checkbox: mount', async () => {
+ await measureRenders(
+ Accept terms,
+ { wrapper: ThemeWrapper },
+ );
+});
+
+test('Checkbox: toggle checked', async () => {
+ await measureRenders(
+ Accept terms,
+ {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ const checkbox = screen.getByRole('checkbox');
+ fireEvent.click(checkbox);
+ },
+ },
+ );
+});
+
+/**
+ * Critical scenario: toggling one checkbox in a list.
+ * If memoization is missing, ALL checkboxes re-render on a single toggle.
+ */
+test('Checkbox: toggle one in list of 20', async () => {
+ const items = Array.from({ length: 20 }, (_, i) => `Option ${i}`);
+ await measureRenders(
+
+ {items.map((label) => (
+
+ {label}
+
+ ))}
+
,
+ {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ const checkboxes = screen.getAllByRole('checkbox');
+ fireEvent.click(checkboxes[0]!);
+ },
+ },
+ );
+});
diff --git a/packages/benchmarks/src/__perf__/base-ui/Dialog.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Dialog.perf-test.tsx
new file mode 100644
index 0000000..30533e6
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/base-ui/Dialog.perf-test.tsx
@@ -0,0 +1,59 @@
+import { useState } from 'react';
+import { test } from 'vitest';
+import { measureRenders } from 'reassure';
+import { fireEvent, screen } from '@testing-library/react';
+import { Dialog, Button } from '@omniview/base-ui';
+import { ThemeWrapper } from '../utils/theme-wrapper';
+
+function DialogWithTrigger() {
+ const [open, setOpen] = useState(false);
+ return (
+ <>
+
+
+ >
+ );
+}
+
+/**
+ * Mount cost includes the trigger but NOT the dialog body (open=false).
+ * Render count should be minimal — just the trigger + null dialog.
+ */
+test('Dialog: mount (closed)', async () => {
+ await measureRenders(, { wrapper: ThemeWrapper });
+});
+
+/**
+ * Opening the dialog triggers portal mount, backdrop, and content render.
+ * High render counts here signal excessive setState chains in useEffect.
+ */
+test('Dialog: open', async () => {
+ await measureRenders(, {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ fireEvent.click(screen.getByText('Open Dialog'));
+ },
+ });
+});
+
+/**
+ * Full open/close cycle. Render count should be ~2x the open-only count.
+ * Significantly more signals cleanup-related re-renders.
+ */
+test('Dialog: open then close', async () => {
+ await measureRenders(, {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ fireEvent.click(screen.getByText('Open Dialog'));
+ fireEvent.click(screen.getByText('Cancel'));
+ },
+ });
+});
diff --git a/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx
new file mode 100644
index 0000000..5867d66
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/base-ui/Input.perf-test.tsx
@@ -0,0 +1,63 @@
+import { test } from 'vitest';
+import { type ChangeEvent, useState } from 'react';
+import { measureRenders } from 'reassure';
+import { fireEvent, screen } from '@testing-library/react';
+import { Input, TextField } from '@omniview/base-ui';
+import { ThemeWrapper } from '../utils/theme-wrapper';
+
+test('Input: mount', async () => {
+ await measureRenders(
+
+ Username
+
+ ,
+ { wrapper: ThemeWrapper },
+ );
+});
+
+test('TextField: mount', async () => {
+ await measureRenders(
+
+ Email
+
+ ,
+ { wrapper: ThemeWrapper },
+ );
+});
+
+/**
+ * Controlled input wrapper so each fireEvent.change triggers a state update
+ * and re-render, simulating real per-keystroke behavior.
+ */
+function ControlledInput() {
+ const [value, setValue] = useState('');
+ return (
+
+ Username
+ ) => setValue(e.target.value)}
+ />
+
+ );
+}
+
+/**
+ * Typing in an input field — each keystroke should cause minimal re-renders.
+ * High counts here signal uncontrolled→controlled mismatches or expensive
+ * parent re-renders from onChange.
+ */
+test('Input: type 10 characters', async () => {
+ await measureRenders(, {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ const input = screen.getByTestId('input');
+ const chars = 'helloworld';
+ for (let i = 0; i < chars.length; i++) {
+ fireEvent.change(input, { target: { value: chars.slice(0, i + 1) } });
+ }
+ },
+ });
+});
diff --git a/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx
new file mode 100644
index 0000000..ad1471d
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/base-ui/Select.perf-test.tsx
@@ -0,0 +1,67 @@
+import { test } from 'vitest';
+import { measureRenders } from 'reassure';
+import { fireEvent, screen } from '@testing-library/react';
+import { Select } from '@omniview/base-ui';
+import { makeOptions } from '../../utils/factories';
+import { ThemeWrapper } from '../utils/theme-wrapper';
+
+const options = makeOptions(20);
+
+test('Select: mount with 20 options', async () => {
+ await measureRenders(
+ ,
+ { wrapper: ThemeWrapper },
+ );
+});
+
+/**
+ * Opening a Select triggers portal mount, positioner layout, and popup render.
+ * High render counts here signal excessive context notifications.
+ */
+test('Select: open dropdown', async () => {
+ await measureRenders(
+ ,
+ {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ const trigger = screen.getByRole('combobox');
+ fireEvent.click(trigger);
+ await screen.findByRole('listbox');
+ },
+ },
+ );
+});
diff --git a/packages/benchmarks/src/__perf__/base-ui/Tabs.perf-test.tsx b/packages/benchmarks/src/__perf__/base-ui/Tabs.perf-test.tsx
new file mode 100644
index 0000000..cbc8725
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/base-ui/Tabs.perf-test.tsx
@@ -0,0 +1,54 @@
+import { test } from 'vitest';
+import { measureRenders } from 'reassure';
+import { fireEvent, screen } from '@testing-library/react';
+import { Tabs } from '@omniview/base-ui';
+import { ThemeWrapper } from '../utils/theme-wrapper';
+
+test('Tabs: mount with 5 tabs', async () => {
+ await measureRenders(
+
+
+ {Array.from({ length: 5 }, (_, i) => (
+
+ Tab {i}
+
+ ))}
+
+ {Array.from({ length: 5 }, (_, i) => (
+
+ Content for tab {i}
+
+ ))}
+ ,
+ { wrapper: ThemeWrapper },
+ );
+});
+
+/**
+ * Switching tabs should ideally only re-render the old and new panels,
+ * not the entire tree. High render counts here signal over-rendering.
+ */
+test('Tabs: switch active tab', async () => {
+ await measureRenders(
+
+
+ {Array.from({ length: 5 }, (_, i) => (
+
+ Tab {i}
+
+ ))}
+
+ {Array.from({ length: 5 }, (_, i) => (
+
+ Content for tab {i}
+
+ ))}
+ ,
+ {
+ wrapper: ThemeWrapper,
+ scenario: async () => {
+ fireEvent.click(screen.getByText('Tab 3'));
+ },
+ },
+ );
+});
diff --git a/packages/benchmarks/src/__perf__/utils/theme-wrapper.tsx b/packages/benchmarks/src/__perf__/utils/theme-wrapper.tsx
new file mode 100644
index 0000000..fc9e35b
--- /dev/null
+++ b/packages/benchmarks/src/__perf__/utils/theme-wrapper.tsx
@@ -0,0 +1,10 @@
+import type { ReactElement } from 'react';
+import { ThemeProvider } from '@omniview/base-ui';
+
+/**
+ * Wrapper for Reassure tests — provides the ThemeProvider context
+ * required by all @omniview components.
+ */
+export function ThemeWrapper({ children }: { children: ReactElement }) {
+ return {children};
+}
diff --git a/packages/benchmarks/src/base-ui/Accordion.bench.tsx b/packages/benchmarks/src/base-ui/Accordion.bench.tsx
new file mode 100644
index 0000000..4ed6296
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Accordion.bench.tsx
@@ -0,0 +1,34 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Accordion } from '@omniview/base-ui';
+
+describe('Accordion', () => {
+ benchRender(
+ 'mount with 3 items',
+ () => (
+
+ Content A
+ Content B
+ Content C
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'expand toggle',
+ {
+ initialProps: { expanded: [] as string[] },
+ updatedProps: { expanded: ['a'] as string[] },
+ },
+ (props) => (
+
+ Content A
+ Content B
+ Content C
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/ActionList.bench.tsx b/packages/benchmarks/src/base-ui/ActionList.bench.tsx
new file mode 100644
index 0000000..fd337b6
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ActionList.bench.tsx
@@ -0,0 +1,38 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ActionList } from '@omniview/base-ui';
+
+describe('ActionList', () => {
+ benchRender(
+ 'mount with items',
+ () => (
+
+
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'disabled toggle',
+ {
+ initialProps: { disabled: false },
+ updatedProps: { disabled: true },
+ },
+ (props) => (
+
+
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx
new file mode 100644
index 0000000..e49a80e
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/AlertDialog.bench.tsx
@@ -0,0 +1,44 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { AlertDialog } from '@omniview/base-ui';
+
+describe('AlertDialog', () => {
+ benchRender(
+ 'mount (open)',
+ () => (
+
+
+
+
+ Confirm
+ Are you sure?
+ Cancel
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'open toggle',
+ {
+ initialProps: { open: true },
+ updatedProps: { open: false },
+ },
+ (props) => (
+
+
+
+
+ Confirm
+ Are you sure?
+ Cancel
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/AppShell.bench.tsx b/packages/benchmarks/src/base-ui/AppShell.bench.tsx
new file mode 100644
index 0000000..60a8156
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/AppShell.bench.tsx
@@ -0,0 +1,34 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { AppShell } from '@omniview/base-ui';
+
+describe('AppShell', () => {
+ benchRender(
+ 'mount with Header/Sidebar/Content',
+ () => (
+
+ Header
+ Sidebar
+ Content
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'sidebar visibility toggle',
+ {
+ initialProps: { sidebarCollapsed: false },
+ updatedProps: { sidebarCollapsed: true },
+ },
+ (props) => (
+
+ Header
+ Sidebar
+ Content
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx b/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx
new file mode 100644
index 0000000..f9b4f44
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Autocomplete.bench.tsx
@@ -0,0 +1,53 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeOptions, type Option } from '../utils/factories';
+import { Autocomplete } from '@omniview/base-ui';
+
+const options100 = makeOptions(100);
+const options200 = makeOptions(200);
+const options10 = makeOptions(10);
+
+function AutocompleteBench({ options }: { options: Option[] }) {
+ return (
+
+
+
+
+
+
+
+ {(item: Option) => (
+
+ {item.label}
+
+ )}
+
+
+
+
+
+ );
+}
+
+describe('Autocomplete', () => {
+ benchRender(
+ 'mount with 100 options',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'options change (100 → 200)',
+ { initialProps: { options: options100 }, updatedProps: { options: options200 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 100 instances (10 options each)',
+ 100,
+ (i) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Banner.bench.tsx b/packages/benchmarks/src/base-ui/Banner.bench.tsx
new file mode 100644
index 0000000..4a480db
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Banner.bench.tsx
@@ -0,0 +1,39 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Banner } from '@omniview/base-ui';
+
+describe('Banner', () => {
+ benchRender(
+ 'mount',
+ () => (
+
+ Notice
+ This is a banner message.
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'variant change',
+ {
+ initialProps: { variant: 'soft' as const },
+ updatedProps: { variant: 'outline' as const },
+ },
+ (props) => (
+
+ Notice
+ This is a banner message.
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 50', 50, (i) => (
+
+ Notice {i}
+ Message {i}
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/BasicList.bench.tsx b/packages/benchmarks/src/base-ui/BasicList.bench.tsx
new file mode 100644
index 0000000..f33e372
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/BasicList.bench.tsx
@@ -0,0 +1,43 @@
+import { describe } from 'vitest';
+import { BasicList } from '@omniview/base-ui';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeRows, type Row } from '../utils/factories';
+
+// Pre-generate data
+const rows200 = makeRows(200);
+const rows300 = makeRows(300);
+
+// ---------------------------------------------------------------------------
+// Wrapper
+// ---------------------------------------------------------------------------
+
+function BasicListBench({ data }: { data: Row[] }) {
+ return (
+
+
+ {data.map((row) => (
+
+ {row.name}
+ {row.status}
+
+ ))}
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Benchmarks
+// ---------------------------------------------------------------------------
+
+describe('BasicList', () => {
+ benchRender('mount 200 items', () => , TIER_1_OPTIONS);
+
+ benchRerender(
+ 'data change (200 -> 300 items)',
+ { initialProps: { data: rows200 }, updatedProps: { data: rows300 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Box.bench.tsx b/packages/benchmarks/src/base-ui/Box.bench.tsx
index 3294ad9..a694ffa 100644
--- a/packages/benchmarks/src/base-ui/Box.bench.tsx
+++ b/packages/benchmarks/src/base-ui/Box.bench.tsx
@@ -1,11 +1,10 @@
import { describe } from 'vitest';
import { benchRender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
import { Box } from '@omniview/base-ui';
describe('Box', () => {
- benchRender('mount div', () => Content);
-
- benchRender('mount section', () => Content);
-
- benchMountMany('mount 1000', 1000, (i) => Item {i});
+ benchRender('mount div', () => Content, TIER_2_OPTIONS);
+ benchRender('mount section', () => Content, TIER_2_OPTIONS);
+ benchMountMany('mount 1000', 1000, (i) => Item {i}, TIER_2_OPTIONS);
});
diff --git a/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx b/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx
new file mode 100644
index 0000000..c25600a
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Breadcrumbs.bench.tsx
@@ -0,0 +1,51 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Breadcrumbs } from '@omniview/base-ui';
+
+function threeItems() {
+ return (
+ <>
+ Home
+ Docs
+ Current
+ >
+ );
+}
+
+function fourItems() {
+ return (
+ <>
+ Home
+ Docs
+ API
+ Current
+ >
+ );
+}
+
+describe('Breadcrumbs', () => {
+ benchRender(
+ 'mount with 3 items',
+ () => {threeItems()},
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'items change',
+ {
+ initialProps: { children: threeItems() },
+ updatedProps: { children: fourItems() },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 50', 50, (i) => (
+
+ Home
+ Section
+ Page {i}
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Button.bench.tsx b/packages/benchmarks/src/base-ui/Button.bench.tsx
index 36c78fb..6874c1a 100644
--- a/packages/benchmarks/src/base-ui/Button.bench.tsx
+++ b/packages/benchmarks/src/base-ui/Button.bench.tsx
@@ -1,21 +1,20 @@
import { describe } from 'vitest';
import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
import { Button } from '@omniview/base-ui';
describe('Button', () => {
- benchRender('mount', () => );
-
+ benchRender('mount', () => , TIER_2_OPTIONS);
benchRender('mount with decorators', () => (
- ));
-
+ ), TIER_2_OPTIONS);
benchRerender(
'variant change',
{ initialProps: { variant: 'solid' as const }, updatedProps: { variant: 'outline' as const } },
(props) => ,
+ TIER_2_OPTIONS,
);
-
- benchMountMany('mount 1000', 1000, (i) => );
+ benchMountMany('mount 1000', 1000, (i) => , TIER_2_OPTIONS);
});
diff --git a/packages/benchmarks/src/base-ui/ButtonGroup.bench.tsx b/packages/benchmarks/src/base-ui/ButtonGroup.bench.tsx
new file mode 100644
index 0000000..abba5ca
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ButtonGroup.bench.tsx
@@ -0,0 +1,34 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ButtonGroup } from '@omniview/base-ui';
+
+describe('ButtonGroup', () => {
+ benchRender(
+ 'mount with 3 buttons',
+ () => (
+
+ One
+ Two
+ Three
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'variant change',
+ {
+ initialProps: { variant: 'soft' as const },
+ updatedProps: { variant: 'outline' as const },
+ },
+ (props) => (
+
+ One
+ Two
+ Three
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Card.bench.tsx b/packages/benchmarks/src/base-ui/Card.bench.tsx
new file mode 100644
index 0000000..5b8b8cc
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Card.bench.tsx
@@ -0,0 +1,43 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Card } from '@omniview/base-ui';
+
+describe('Card', () => {
+ benchRender(
+ 'mount with Header/Title/Body',
+ () => (
+
+
+ Title
+
+ Body content
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'variant change',
+ {
+ initialProps: { variant: 'soft' as const },
+ updatedProps: { variant: 'outline' as const },
+ },
+ (props) => (
+
+
+ Title
+
+ Body content
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => (
+
+ Card {i}
+ Content {i}
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Checkbox.bench.tsx b/packages/benchmarks/src/base-ui/Checkbox.bench.tsx
index 5786a9a..486d6aa 100644
--- a/packages/benchmarks/src/base-ui/Checkbox.bench.tsx
+++ b/packages/benchmarks/src/base-ui/Checkbox.bench.tsx
@@ -1,15 +1,15 @@
import { describe } from 'vitest';
import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
import { Checkbox } from '@omniview/base-ui';
describe('Checkbox', () => {
- benchRender('mount', () => );
-
+ benchRender('mount', () => , TIER_2_OPTIONS);
benchRerender(
'checked toggle',
{ initialProps: { checked: false }, updatedProps: { checked: true } },
(props) => ,
+ TIER_2_OPTIONS,
);
-
- benchMountMany('mount 1000', 1000, (i) => );
+ benchMountMany('mount 200', 200, (i) => , TIER_2_OPTIONS);
});
diff --git a/packages/benchmarks/src/base-ui/CheckboxGroup.bench.tsx b/packages/benchmarks/src/base-ui/CheckboxGroup.bench.tsx
new file mode 100644
index 0000000..1f12b05
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/CheckboxGroup.bench.tsx
@@ -0,0 +1,27 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { CheckboxGroup } from '@omniview/base-ui';
+
+describe('CheckboxGroup', () => {
+ benchRender('mount with 3 items', () => (
+
+ Option A
+ Option B
+ Option C
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'value change',
+ { initialProps: { value: ['a'] as string[] }, updatedProps: { value: ['a', 'b'] as string[] } },
+ (props) => (
+
+ Option A
+ Option B
+ Option C
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Chip.bench.tsx b/packages/benchmarks/src/base-ui/Chip.bench.tsx
new file mode 100644
index 0000000..c3459aa
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Chip.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Chip } from '@omniview/base-ui';
+
+describe('Chip', () => {
+ benchRender(
+ 'mount',
+ () => Label,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'variant change',
+ {
+ initialProps: { variant: 'soft' as const },
+ updatedProps: { variant: 'outline' as const },
+ },
+ (props) => Label,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 200', 200, (i) => Tag {i}, TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx b/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx
new file mode 100644
index 0000000..b8f6113
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ClipboardText.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ClipboardText } from '@omniview/base-ui';
+
+describe('ClipboardText', () => {
+ benchRender(
+ 'mount',
+ () => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'value change',
+ {
+ initialProps: { value: 'kubectl get pods' },
+ updatedProps: { value: 'kubectl get nodes' },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => , TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/CodeBlock.bench.tsx b/packages/benchmarks/src/base-ui/CodeBlock.bench.tsx
new file mode 100644
index 0000000..9d3fce3
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/CodeBlock.bench.tsx
@@ -0,0 +1,25 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { CodeBlock } from '@omniview/base-ui';
+
+const codeA = 'const x = 1;\nconsole.log(x);';
+const codeB = 'const y = 2;\nconsole.log(y);';
+
+describe('CodeBlock', () => {
+ benchRender(
+ 'mount',
+ () => {codeA},
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'code change',
+ {
+ initialProps: { children: codeA },
+ updatedProps: { children: codeB },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Collapsible.bench.tsx b/packages/benchmarks/src/base-ui/Collapsible.bench.tsx
new file mode 100644
index 0000000..6d652dd
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Collapsible.bench.tsx
@@ -0,0 +1,39 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@omniview/base-ui';
+
+describe('Collapsible', () => {
+ benchRender(
+ 'mount',
+ () => (
+
+ Toggle
+ Collapsible content here
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'open toggle',
+ {
+ initialProps: { open: false },
+ updatedProps: { open: true },
+ },
+ (props) => (
+
+ Toggle
+ Collapsible content here
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => (
+
+ Section {i}
+ Content {i}
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Combobox.bench.tsx b/packages/benchmarks/src/base-ui/Combobox.bench.tsx
new file mode 100644
index 0000000..b94f8e3
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Combobox.bench.tsx
@@ -0,0 +1,58 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeOptions, type Option } from '../utils/factories';
+import { Combobox } from '@omniview/base-ui';
+
+const options100 = makeOptions(100);
+const options200 = makeOptions(200);
+const options10 = makeOptions(10);
+
+function ComboboxBench({ options }: { options: Option[] }) {
+ return (
+ item.label}
+ itemToStringValue={(item: Option) => item.value}
+ defaultOpen
+ >
+
+
+
+
+
+
+ {(item: Option) => (
+
+ {item.label}
+
+ )}
+
+
+
+
+
+ );
+}
+
+describe('Combobox', () => {
+ benchRender(
+ 'mount with 100 options',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'options change (100 → 200)',
+ { initialProps: { options: options100 }, updatedProps: { options: options200 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 100 instances (10 options each)',
+ 100,
+ (i) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/CommandList.bench.tsx b/packages/benchmarks/src/base-ui/CommandList.bench.tsx
new file mode 100644
index 0000000..dbba346
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/CommandList.bench.tsx
@@ -0,0 +1,54 @@
+import { describe } from 'vitest';
+import { CommandList } from '@omniview/base-ui';
+import type { CommandItemMeta } from '@omniview/base-ui';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeCommandItems, type CommandItem } from '../utils/factories';
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+const itemKey = (item: CommandItem) => item.id;
+const getTextValue = (item: CommandItem) => item.label;
+const renderItem = (item: CommandItem, meta: CommandItemMeta) => (
+
+ {item.label}
+
+);
+
+// Pre-generate data
+const items200 = makeCommandItems(200);
+const items300 = makeCommandItems(300);
+
+// ---------------------------------------------------------------------------
+// Wrapper
+// ---------------------------------------------------------------------------
+
+function CommandListBench({ items }: { items: CommandItem[] }) {
+ return (
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Benchmarks
+// ---------------------------------------------------------------------------
+
+describe('CommandList', () => {
+ benchRender('mount 200 items', () => , TIER_1_OPTIONS);
+
+ benchRerender(
+ 'data change (200 -> 300 items)',
+ { initialProps: { items: items200 }, updatedProps: { items: items300 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx b/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx
new file mode 100644
index 0000000..9420a2d
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ConfirmButton.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ConfirmButton } from '@omniview/base-ui';
+
+const noop = () => {};
+
+describe('ConfirmButton', () => {
+ benchRender(
+ 'mount',
+ () => Delete,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'disabled toggle',
+ {
+ initialProps: { disabled: false, onConfirm: noop, children: 'Delete' },
+ updatedProps: { disabled: true, onConfirm: noop, children: 'Delete' },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/ContextMenu.bench.tsx b/packages/benchmarks/src/base-ui/ContextMenu.bench.tsx
new file mode 100644
index 0000000..3b1e528
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ContextMenu.bench.tsx
@@ -0,0 +1,75 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { ContextMenu } from '@omniview/base-ui';
+
+describe('ContextMenu', () => {
+ benchRender(
+ 'mount with items',
+ () => (
+
+
+ Right-click me
+
+
+
+
+ Cut
+ Copy
+ Paste
+
+ Delete
+
+
+
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'disabled toggle on item',
+ {
+ initialProps: { itemDisabled: false },
+ updatedProps: { itemDisabled: true },
+ },
+ (props) => (
+
+
+ Right-click me
+
+
+
+
+ Cut
+ Copy
+ Paste
+
+
+
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 50 context menus',
+ 50,
+ (i) => (
+
+
+ Trigger {i}
+
+
+
+
+ Action A
+ Action B
+
+
+
+
+ ),
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/DataTable.bench.tsx b/packages/benchmarks/src/base-ui/DataTable.bench.tsx
index a3a3a87..e294402 100644
--- a/packages/benchmarks/src/base-ui/DataTable.bench.tsx
+++ b/packages/benchmarks/src/base-ui/DataTable.bench.tsx
@@ -6,14 +6,9 @@ import {
type ColumnDef,
} from '@tanstack/react-table';
import { DataTable } from '@omniview/base-ui';
-import { benchRender } from '../utils/bench-render';
-
-interface Row {
- id: number;
- name: string;
- status: string;
- value: number;
-}
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeRows, type Row } from '../utils/factories';
const columnHelper = createColumnHelper();
@@ -24,23 +19,9 @@ const columns: ColumnDef[] = [
columnHelper.accessor('value', { header: 'Value' }),
];
-function generateRows(count: number): Row[] {
- return Array.from({ length: count }, (_, i) => ({
- id: i,
- name: `Row ${i}`,
- status: i % 3 === 0 ? 'active' : i % 3 === 1 ? 'pending' : 'inactive',
- value: (i * 7 + 13) % 1000,
- }));
-}
-
-// Pre-generate data so row construction isn't measured
-const rows100 = generateRows(100);
-const rows1000 = generateRows(1000);
+const rows100 = makeRows(100);
+const rows200 = makeRows(200);
-/**
- * Wrapper component that calls useReactTable internally and renders
- * DataTable with its compound children.
- */
function DataTableBench({ data }: { data: Row[] }) {
const table = useReactTable({
data,
@@ -59,6 +40,12 @@ function DataTableBench({ data }: { data: Row[] }) {
}
describe('DataTable', () => {
- benchRender('mount 100 rows', () => );
- benchRender('mount 1000 rows', () => );
+ benchRender('mount 100 rows', () => , TIER_1_OPTIONS);
+
+ benchRerender(
+ 'data change (100 → 200 rows)',
+ { initialProps: { data: rows100 }, updatedProps: { data: rows200 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
});
diff --git a/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx b/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx
new file mode 100644
index 0000000..9cf5f97
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/DescriptionList.bench.tsx
@@ -0,0 +1,43 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { DescriptionList } from '@omniview/base-ui';
+
+function threeItems() {
+ return (
+ <>
+ Widget
+ Active
+ 1.0.0
+ >
+ );
+}
+
+function fourItems() {
+ return (
+ <>
+ Widget
+ Active
+ 1.0.0
+ US-East
+ >
+ );
+}
+
+describe('DescriptionList', () => {
+ benchRender(
+ 'mount with 3 items',
+ () => {threeItems()},
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'items change',
+ {
+ initialProps: { children: threeItems() },
+ updatedProps: { children: fourItems() },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Dialog.bench.tsx b/packages/benchmarks/src/base-ui/Dialog.bench.tsx
new file mode 100644
index 0000000..25f5c36
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Dialog.bench.tsx
@@ -0,0 +1,53 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { Dialog } from '@omniview/base-ui';
+
+const noop = () => {};
+
+describe('Dialog', () => {
+ benchRender(
+ 'mount (open)',
+ () => (
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'open/close toggle',
+ {
+ initialProps: { open: true },
+ updatedProps: { open: false },
+ },
+ (props) => (
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 50 dialogs',
+ 50,
+ (i) => (
+
+ ),
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/DockLayout.bench.tsx b/packages/benchmarks/src/base-ui/DockLayout.bench.tsx
new file mode 100644
index 0000000..ef4dd34
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/DockLayout.bench.tsx
@@ -0,0 +1,105 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { DockLayout, type DockNode } from '@omniview/base-ui';
+
+function makeTwoPanel(): DockNode {
+ return {
+ type: 'split',
+ direction: 'horizontal',
+ children: [
+ {
+ type: 'leaf',
+ id: 'left',
+ tabs: [
+ { id: 'tab-1', title: 'File A', content: Content A
},
+ { id: 'tab-2', title: 'File B', content: Content B
},
+ ],
+ activeTab: 'tab-1',
+ },
+ {
+ type: 'leaf',
+ id: 'right',
+ tabs: [
+ { id: 'tab-3', title: 'File C', content: Content C
},
+ ],
+ activeTab: 'tab-3',
+ },
+ ],
+ sizes: [1, 1],
+ };
+}
+
+function makeThreePanelVertical(): DockNode {
+ return {
+ type: 'split',
+ direction: 'vertical',
+ children: [
+ {
+ type: 'leaf',
+ id: 'top',
+ tabs: [{ id: 'tab-a', title: 'Top', content: Top
}],
+ activeTab: 'tab-a',
+ },
+ {
+ type: 'leaf',
+ id: 'middle',
+ tabs: [{ id: 'tab-b', title: 'Middle', content: Middle
}],
+ activeTab: 'tab-b',
+ },
+ {
+ type: 'leaf',
+ id: 'bottom',
+ tabs: [{ id: 'tab-c', title: 'Bottom', content: Bottom
}],
+ activeTab: 'tab-c',
+ },
+ ],
+ sizes: [1, 1, 1],
+ };
+}
+
+describe('DockLayout', () => {
+ benchRender(
+ 'mount 2-panel layout',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'layout change (2-panel → 3-panel vertical)',
+ {
+ initialProps: { layout: makeTwoPanel() },
+ updatedProps: { layout: makeThreePanelVertical() },
+ },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 20 dock layouts',
+ 20,
+ (i) => {
+ const layout: DockNode = {
+ type: 'split',
+ direction: 'horizontal',
+ children: [
+ {
+ type: 'leaf',
+ id: `left-${i}`,
+ tabs: [{ id: `t-${i}-a`, title: `Tab A ${i}`, content: A{i}
}],
+ activeTab: `t-${i}-a`,
+ },
+ {
+ type: 'leaf',
+ id: `right-${i}`,
+ tabs: [{ id: `t-${i}-b`, title: `Tab B ${i}`, content: B{i}
}],
+ activeTab: `t-${i}-b`,
+ },
+ ],
+ sizes: [1, 1],
+ };
+ return ;
+ },
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Drawer.bench.tsx b/packages/benchmarks/src/base-ui/Drawer.bench.tsx
new file mode 100644
index 0000000..4edeffe
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Drawer.bench.tsx
@@ -0,0 +1,43 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { Drawer } from '@omniview/base-ui';
+
+const noop = () => {};
+
+describe('Drawer', () => {
+ benchRender(
+ 'mount (open)',
+ () => (
+
+ Drawer content
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'open/close toggle',
+ {
+ initialProps: { open: true },
+ updatedProps: { open: false },
+ },
+ (props) => (
+
+ Drawer content
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 50 drawers',
+ 50,
+ (i) => (
+
+ Content {i}
+
+ ),
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/EditableList.bench.tsx b/packages/benchmarks/src/base-ui/EditableList.bench.tsx
new file mode 100644
index 0000000..ed50815
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/EditableList.bench.tsx
@@ -0,0 +1,57 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { EditableList } from '@omniview/base-ui';
+
+const noop = () => {};
+
+function threeItems() {
+ return (
+ <>
+
+ Item A
+
+
+
+
+
+ Item B
+
+
+
+
+
+ Item C
+
+
+
+
+ >
+ );
+}
+
+describe('EditableList', () => {
+ benchRender(
+ 'mount with items',
+ () => (
+
+ {threeItems()}
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'editable toggle',
+ {
+ initialProps: { editable: true },
+ updatedProps: { editable: false },
+ },
+ (props) => (
+
+ {threeItems()}
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx b/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx
new file mode 100644
index 0000000..fdfff59
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/EditorTabs.bench.tsx
@@ -0,0 +1,36 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { EditorTabs } from '@omniview/base-ui';
+import { makeEditorTabs } from '../utils/factories';
+
+const tabs20 = makeEditorTabs(20);
+const tabs40 = makeEditorTabs(40);
+
+describe('EditorTabs', () => {
+ benchRender(
+ 'mount 20 tabs',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'tabs change (20 → 40)',
+ {
+ initialProps: { tabs: tabs20 },
+ updatedProps: { tabs: tabs40 },
+ },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 30 tab bars (5 tabs each)',
+ 30,
+ (i) => {
+ const tabs = makeEditorTabs(5, `bar${i}-`);
+ return ;
+ },
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/EmptyState.bench.tsx b/packages/benchmarks/src/base-ui/EmptyState.bench.tsx
new file mode 100644
index 0000000..8814de8
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/EmptyState.bench.tsx
@@ -0,0 +1,22 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { EmptyState } from '@omniview/base-ui';
+
+describe('EmptyState', () => {
+ benchRender(
+ 'mount',
+ () => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'title change',
+ {
+ initialProps: { title: 'No results' },
+ updatedProps: { title: 'Nothing found' },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/FilterBar.bench.tsx b/packages/benchmarks/src/base-ui/FilterBar.bench.tsx
new file mode 100644
index 0000000..5474e45
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/FilterBar.bench.tsx
@@ -0,0 +1,44 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeTags } from '../utils/factories';
+import { FilterBar } from '@omniview/base-ui';
+
+const filters10 = makeTags(10);
+const filters20 = makeTags(20);
+const filters5 = makeTags(5);
+
+function FilterBarBench({ filters }: { filters: string[] }) {
+ return (
+
+ {filters.map((filter) => (
+ {}}>
+ {filter}
+
+ ))}
+
+
+ );
+}
+
+describe('FilterBar', () => {
+ benchRender(
+ 'mount with 10 filters',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'filters change (10 → 20)',
+ { initialProps: { filters: filters10 }, updatedProps: { filters: filters20 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 100 instances (5 filters each)',
+ 100,
+ (i) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/FindBar.bench.tsx b/packages/benchmarks/src/base-ui/FindBar.bench.tsx
new file mode 100644
index 0000000..c14e45f
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/FindBar.bench.tsx
@@ -0,0 +1,22 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { FindBar } from '@omniview/base-ui';
+
+describe('FindBar', () => {
+ benchRender(
+ 'mount',
+ () => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'value change',
+ {
+ initialProps: { open: true as const, query: '' },
+ updatedProps: { open: true as const, query: 'search term' },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/FormField.bench.tsx b/packages/benchmarks/src/base-ui/FormField.bench.tsx
new file mode 100644
index 0000000..83d9d1a
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/FormField.bench.tsx
@@ -0,0 +1,32 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { FormField } from '@omniview/base-ui';
+
+describe('FormField', () => {
+ benchRender('mount', () => (
+
+
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'error toggle',
+ {
+ initialProps: { error: undefined as string | undefined },
+ updatedProps: { error: 'This field is required' },
+ },
+ (props) => (
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => (
+
+
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/IconButton.bench.tsx b/packages/benchmarks/src/base-ui/IconButton.bench.tsx
new file mode 100644
index 0000000..113384f
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/IconButton.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { IconButton } from '@omniview/base-ui';
+
+describe('IconButton', () => {
+ benchRender(
+ 'mount',
+ () => E,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'variant change',
+ {
+ initialProps: { variant: 'soft' as const, 'aria-label': 'Edit' },
+ updatedProps: { variant: 'outline' as const, 'aria-label': 'Edit' },
+ },
+ (props) => E,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 200', 200, (i) => E, TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Input.bench.tsx b/packages/benchmarks/src/base-ui/Input.bench.tsx
new file mode 100644
index 0000000..b8a3663
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Input.bench.tsx
@@ -0,0 +1,32 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Input } from '@omniview/base-ui';
+
+describe('Input', () => {
+ benchRender('mount', () => (
+
+ Label
+
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'disabled toggle',
+ { initialProps: { disabled: false }, updatedProps: { disabled: true } },
+ (props) => (
+
+ Label
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => (
+
+ Field {i}
+
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/List.bench.tsx b/packages/benchmarks/src/base-ui/List.bench.tsx
new file mode 100644
index 0000000..b5d2122
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/List.bench.tsx
@@ -0,0 +1,40 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { List } from '@omniview/base-ui';
+import type { ReactNode } from 'react';
+
+const threeItems = (
+ <>
+ Alpha
+ Beta
+ Gamma
+ >
+);
+
+const fourItems = (
+ <>
+ Alpha
+ Beta
+ Gamma
+ Delta
+ >
+);
+
+describe('List', () => {
+ benchRender(
+ 'mount with 3 items',
+ () => {threeItems}
,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'items change',
+ {
+ initialProps: { children: threeItems as ReactNode },
+ updatedProps: { children: fourItems as ReactNode },
+ },
+ (props) =>
,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Menu.bench.tsx b/packages/benchmarks/src/base-ui/Menu.bench.tsx
new file mode 100644
index 0000000..163edd5
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Menu.bench.tsx
@@ -0,0 +1,48 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Menu } from '@omniview/base-ui';
+
+describe('Menu', () => {
+ benchRender(
+ 'mount (closed)',
+ () => (
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'open toggle',
+ {
+ initialProps: { open: false },
+ updatedProps: { open: true },
+ },
+ (props) => (
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Meter.bench.tsx b/packages/benchmarks/src/base-ui/Meter.bench.tsx
new file mode 100644
index 0000000..ac0830a
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Meter.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Meter } from '@omniview/base-ui';
+
+describe('Meter', () => {
+ benchRender(
+ 'mount',
+ () => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'value change',
+ {
+ initialProps: { value: 60, label: 'CPU Usage' as const },
+ updatedProps: { value: 85, label: 'CPU Usage' as const },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => , TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx b/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx
new file mode 100644
index 0000000..404ce2e
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/MultiSelect.bench.tsx
@@ -0,0 +1,40 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeOptions, type Option } from '../utils/factories';
+import { MultiSelect } from '@omniview/base-ui';
+
+const options100 = makeOptions(100);
+const noSelection: Option[] = [];
+const selected20 = options100.slice(0, 20);
+
+function MultiSelectBench({ options, value }: { options: Option[]; value?: Option[] }) {
+ return (
+
+ items={options}
+ value={value}
+ getItemLabel={(item) => item.label}
+ getItemValue={(item) => item.value}
+ placeholder="Select options"
+ />
+ );
+}
+
+describe('MultiSelect', () => {
+ benchRender(
+ 'mount with 100 options',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'selection change (0 → 20 selected)',
+ {
+ initialProps: { options: options100, value: noSelection },
+ updatedProps: { options: options100, value: selected20 },
+ },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+});
diff --git a/packages/benchmarks/src/base-ui/NavList.bench.tsx b/packages/benchmarks/src/base-ui/NavList.bench.tsx
new file mode 100644
index 0000000..eed4e65
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/NavList.bench.tsx
@@ -0,0 +1,46 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { NavList } from '@omniview/base-ui';
+
+describe('NavList', () => {
+ benchRender(
+ 'mount with items',
+ () => (
+
+
+ Home
+
+
+ Settings
+
+
+ Profile
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'active item change',
+ {
+ initialProps: { activeKey: 'home' },
+ updatedProps: { activeKey: 'settings' },
+ },
+ (props) => (
+
+
+ Home
+
+
+ Settings
+
+
+ Profile
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/NumberInput.bench.tsx b/packages/benchmarks/src/base-ui/NumberInput.bench.tsx
new file mode 100644
index 0000000..bc4994d
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/NumberInput.bench.tsx
@@ -0,0 +1,44 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { NumberInput } from '@omniview/base-ui';
+
+describe('NumberInput', () => {
+ benchRender('mount', () => (
+
+ Quantity
+
+
+
+
+
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'disabled toggle',
+ { initialProps: { disabled: false }, updatedProps: { disabled: true } },
+ (props) => (
+
+ Quantity
+
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 50', 50, (i) => (
+
+ Qty {i}
+
+
+
+
+
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Pagination.bench.tsx b/packages/benchmarks/src/base-ui/Pagination.bench.tsx
new file mode 100644
index 0000000..6fde980
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Pagination.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Pagination } from '@omniview/base-ui';
+
+const noop = () => {};
+
+describe('Pagination', () => {
+ benchRender(
+ 'mount',
+ () => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'page change',
+ {
+ initialProps: { page: 1 },
+ updatedProps: { page: 5 },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Popover.bench.tsx b/packages/benchmarks/src/base-ui/Popover.bench.tsx
new file mode 100644
index 0000000..8cf374d
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Popover.bench.tsx
@@ -0,0 +1,48 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { Popover } from '@omniview/base-ui';
+
+describe('Popover', () => {
+ benchRender(
+ 'mount (open)',
+ () => (
+
+ Open
+
+
+
+ Info
+ Popover content here.
+ Close
+
+
+
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'open/close toggle',
+ {
+ initialProps: { open: true },
+ updatedProps: { open: false },
+ },
+ (props) => (
+
+ Open
+
+
+
+ Info
+ Content
+
+
+
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+});
diff --git a/packages/benchmarks/src/base-ui/Progress.bench.tsx b/packages/benchmarks/src/base-ui/Progress.bench.tsx
new file mode 100644
index 0000000..d96ea5d
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Progress.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Progress } from '@omniview/base-ui';
+
+describe('Progress', () => {
+ benchRender(
+ 'mount',
+ () => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'value change',
+ {
+ initialProps: { value: 50 },
+ updatedProps: { value: 80 },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 200', 200, (i) => , TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Radio.bench.tsx b/packages/benchmarks/src/base-ui/Radio.bench.tsx
new file mode 100644
index 0000000..761abfe
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Radio.bench.tsx
@@ -0,0 +1,21 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Radio } from '@omniview/base-ui';
+
+describe('Radio', () => {
+ benchRender('mount', () => (
+ Option 1
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'disabled toggle',
+ { initialProps: { disabled: false }, updatedProps: { disabled: true } },
+ (props) => (
+ Option 1
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 200', 200, (i) => Option {i}, TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/RadioGroup.bench.tsx b/packages/benchmarks/src/base-ui/RadioGroup.bench.tsx
new file mode 100644
index 0000000..3775199
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/RadioGroup.bench.tsx
@@ -0,0 +1,27 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { RadioGroup } from '@omniview/base-ui';
+
+describe('RadioGroup', () => {
+ benchRender('mount with 3 items', () => (
+
+ Option A
+ Option B
+ Option C
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'value change',
+ { initialProps: { value: 'a' }, updatedProps: { value: 'b' } },
+ (props) => (
+
+ Option A
+ Option B
+ Option C
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/ResizableSplitPane.bench.tsx b/packages/benchmarks/src/base-ui/ResizableSplitPane.bench.tsx
new file mode 100644
index 0000000..26b35a7
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ResizableSplitPane.bench.tsx
@@ -0,0 +1,41 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { ResizableSplitPane, type SplitDirection } from '@omniview/base-ui';
+
+describe('ResizableSplitPane', () => {
+ benchRender(
+ 'mount horizontal',
+ () => (
+
+ {[Left
, Right
]}
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'direction change (horizontal → vertical)',
+ {
+ initialProps: { direction: 'horizontal' as SplitDirection },
+ updatedProps: { direction: 'vertical' as SplitDirection },
+ },
+ (props) => (
+
+ {[Pane 1
, Pane 2
]}
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 30 split panes',
+ 30,
+ (i) => (
+
+ {[Left {i}
, Right {i}
]}
+
+ ),
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/RowList.bench.tsx b/packages/benchmarks/src/base-ui/RowList.bench.tsx
new file mode 100644
index 0000000..ee89a35
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/RowList.bench.tsx
@@ -0,0 +1,58 @@
+import { describe } from 'vitest';
+import { RowList } from '@omniview/base-ui';
+import type { ColumnDef } from '@omniview/base-ui';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeRows, type Row } from '../utils/factories';
+
+// ---------------------------------------------------------------------------
+// Column definitions
+// ---------------------------------------------------------------------------
+
+const columns: ColumnDef[] = [
+ { id: 'id', header: 'ID', width: '60px' },
+ { id: 'name', header: 'Name', width: '1fr' },
+ { id: 'status', header: 'Status', width: '100px' },
+ { id: 'value', header: 'Value', width: '80px', align: 'end' },
+];
+
+// Pre-generate data
+const rows200 = makeRows(200);
+const rows300 = makeRows(300);
+
+// ---------------------------------------------------------------------------
+// Wrapper
+// ---------------------------------------------------------------------------
+
+function RowListBench({ data }: { data: Row[] }) {
+ return (
+
+
+
+ {data.map((row) => (
+
+ {row.id}
+ {row.name}
+ {row.status}
+ {row.value}
+
+ ))}
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Benchmarks
+// ---------------------------------------------------------------------------
+
+describe('RowList', () => {
+ benchRender('mount 200 rows', () => , TIER_1_OPTIONS);
+
+ benchRerender(
+ 'data change (200 -> 300 rows)',
+ { initialProps: { data: rows200 }, updatedProps: { data: rows300 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/ScrollArea.bench.tsx b/packages/benchmarks/src/base-ui/ScrollArea.bench.tsx
new file mode 100644
index 0000000..480f429
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ScrollArea.bench.tsx
@@ -0,0 +1,30 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ScrollArea } from '@omniview/base-ui';
+import type { ReactNode } from 'react';
+
+const contentA = Scrollable content block A
;
+const contentB = Scrollable content block B with extra text
;
+
+describe('ScrollArea', () => {
+ benchRender(
+ 'mount',
+ () => (
+
+ {contentA}
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'content change',
+ {
+ initialProps: { children: contentA as ReactNode },
+ updatedProps: { children: contentB as ReactNode },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/SearchInput.bench.tsx b/packages/benchmarks/src/base-ui/SearchInput.bench.tsx
new file mode 100644
index 0000000..1d999ef
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/SearchInput.bench.tsx
@@ -0,0 +1,21 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { SearchInput } from '@omniview/base-ui';
+
+const noop = () => {};
+
+describe('SearchInput', () => {
+ benchRender('mount', () => (
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'value change',
+ { initialProps: { value: '' }, updatedProps: { value: 'search query' } },
+ (props) => (
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/SegmentedControl.bench.tsx b/packages/benchmarks/src/base-ui/SegmentedControl.bench.tsx
new file mode 100644
index 0000000..4a648f3
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/SegmentedControl.bench.tsx
@@ -0,0 +1,34 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { SegmentedControl } from '@omniview/base-ui';
+
+describe('SegmentedControl', () => {
+ benchRender(
+ 'mount with 3 items',
+ () => (
+
+ Option A
+ Option B
+ Option C
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'value change',
+ {
+ initialProps: { value: 'a' as string },
+ updatedProps: { value: 'c' as string },
+ },
+ (props) => (
+
+ Option A
+ Option B
+ Option C
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Select.bench.tsx b/packages/benchmarks/src/base-ui/Select.bench.tsx
new file mode 100644
index 0000000..d4e26ba
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Select.bench.tsx
@@ -0,0 +1,75 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { makeOptions } from '../utils/factories';
+import { Select } from '@omniview/base-ui';
+
+const options = makeOptions(20);
+
+describe('Select', () => {
+ benchRender('mount with 20 options', () => (
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'disabled toggle',
+ { initialProps: { disabled: false }, updatedProps: { disabled: true } },
+ (props) => (
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 50', 50, (i) => (
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/SelectableList.bench.tsx b/packages/benchmarks/src/base-ui/SelectableList.bench.tsx
new file mode 100644
index 0000000..0e9bcab
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/SelectableList.bench.tsx
@@ -0,0 +1,60 @@
+import { describe } from 'vitest';
+import { SelectableList } from '@omniview/base-ui';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeRows, type Row } from '../utils/factories';
+
+// Pre-generate data
+const rows200 = makeRows(200);
+
+// Pre-compute selection sets
+const noSelection = new Set();
+const fiftySelected = new Set(Array.from({ length: 50 }, (_, i) => i));
+
+// ---------------------------------------------------------------------------
+// Wrapper
+// ---------------------------------------------------------------------------
+
+function SelectableListBench({
+ data,
+ selectedKeys,
+}: {
+ data: Row[];
+ selectedKeys?: ReadonlySet;
+}) {
+ return (
+
+
+ {data.map((row) => (
+
+
+ {row.name}
+ {row.status}
+
+ ))}
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Benchmarks
+// ---------------------------------------------------------------------------
+
+describe('SelectableList', () => {
+ benchRender(
+ 'mount 200 items',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'selection change (0 -> 50 selected)',
+ {
+ initialProps: { data: rows200, selectedKeys: noSelection },
+ updatedProps: { data: rows200, selectedKeys: fiftySelected },
+ },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Sheet.bench.tsx b/packages/benchmarks/src/base-ui/Sheet.bench.tsx
new file mode 100644
index 0000000..bd0cd18
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Sheet.bench.tsx
@@ -0,0 +1,42 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { Sheet } from '@omniview/base-ui';
+import type { SurfaceElevation } from '@omniview/base-ui';
+
+describe('Sheet', () => {
+ benchRender(
+ 'mount',
+ () => (
+
+ Sheet content
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'elevation change',
+ {
+ initialProps: { elevation: 0 as SurfaceElevation },
+ updatedProps: { elevation: 3 as SurfaceElevation },
+ },
+ (props) => (
+
+ Sheet content
+
+ ),
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 50 sheets',
+ 50,
+ (i) => (
+
+ Content {i}
+
+ ),
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Slider.bench.tsx b/packages/benchmarks/src/base-ui/Slider.bench.tsx
new file mode 100644
index 0000000..81c62f5
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Slider.bench.tsx
@@ -0,0 +1,33 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Slider } from '@omniview/base-ui';
+
+describe('Slider', () => {
+ benchRender('mount', () => (
+
+
+
+
+
+
+
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'value change',
+ { initialProps: { value: 25 }, updatedProps: { value: 75 } },
+ (props) => (
+
+
+
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Spinner.bench.tsx b/packages/benchmarks/src/base-ui/Spinner.bench.tsx
new file mode 100644
index 0000000..b7a51b6
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Spinner.bench.tsx
@@ -0,0 +1,24 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Spinner } from '@omniview/base-ui';
+
+describe('Spinner', () => {
+ benchRender(
+ 'mount',
+ () => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'size change',
+ {
+ initialProps: { size: 'md' as const },
+ updatedProps: { size: 'lg' as const },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 200', 200, (i) => , TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/SplitButton.bench.tsx b/packages/benchmarks/src/base-ui/SplitButton.bench.tsx
new file mode 100644
index 0000000..71b3e40
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/SplitButton.bench.tsx
@@ -0,0 +1,36 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { SplitButton } from '@omniview/base-ui';
+
+describe('SplitButton', () => {
+ benchRender(
+ 'mount',
+ () => (
+
+ Save
+
+ Option 1
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'disabled toggle',
+ {
+ initialProps: { disabled: false },
+ updatedProps: { disabled: true },
+ },
+ (props) => (
+
+ Save
+
+ Option 1
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/StatRow.bench.tsx b/packages/benchmarks/src/base-ui/StatRow.bench.tsx
new file mode 100644
index 0000000..bea684c
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/StatRow.bench.tsx
@@ -0,0 +1,46 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { StatRow } from '@omniview/base-ui';
+import type { ReactNode } from 'react';
+
+const itemsA = (
+ <>
+ CPU: 45%
+ Mem: 2.1 GB
+ Pods: 12
+ >
+);
+
+const itemsB = (
+ <>
+ CPU: 72%
+ Mem: 3.4 GB
+ Pods: 18
+ >
+);
+
+describe('StatRow', () => {
+ benchRender(
+ 'mount',
+ () => {itemsA},
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'value change',
+ {
+ initialProps: { children: itemsA as ReactNode },
+ updatedProps: { children: itemsB as ReactNode },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => (
+
+ CPU: {i}%
+ Mem: {i} GB
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/StatusBar.bench.tsx b/packages/benchmarks/src/base-ui/StatusBar.bench.tsx
new file mode 100644
index 0000000..a2e3183
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/StatusBar.bench.tsx
@@ -0,0 +1,39 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { StatusBar } from '@omniview/base-ui';
+import type { ReactNode } from 'react';
+
+const contentA = (
+
+ main
+
+ UTF-8
+
+);
+
+const contentB = (
+
+ develop
+
+ UTF-16
+
+);
+
+describe('StatusBar', () => {
+ benchRender(
+ 'mount',
+ () => {contentA},
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'content change',
+ {
+ initialProps: { children: contentA as ReactNode },
+ updatedProps: { children: contentB as ReactNode },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Stepper.bench.tsx b/packages/benchmarks/src/base-ui/Stepper.bench.tsx
new file mode 100644
index 0000000..d00a1f1
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Stepper.bench.tsx
@@ -0,0 +1,34 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Stepper } from '@omniview/base-ui';
+
+describe('Stepper', () => {
+ benchRender(
+ 'mount with 3 steps',
+ () => (
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'active step change',
+ {
+ initialProps: { activeStep: 0 as number },
+ updatedProps: { activeStep: 2 as number },
+ },
+ (props) => (
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Switch.bench.tsx b/packages/benchmarks/src/base-ui/Switch.bench.tsx
new file mode 100644
index 0000000..8670c97
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Switch.bench.tsx
@@ -0,0 +1,21 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Switch } from '@omniview/base-ui';
+
+describe('Switch', () => {
+ benchRender('mount', () => (
+ Enable notifications
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'checked toggle',
+ { initialProps: { checked: false }, updatedProps: { checked: true } },
+ (props) => (
+ Enable notifications
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 200', 200, (i) => Setting {i}, TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Table.bench.tsx b/packages/benchmarks/src/base-ui/Table.bench.tsx
new file mode 100644
index 0000000..6ad570a
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Table.bench.tsx
@@ -0,0 +1,71 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Table } from '@omniview/base-ui';
+import type { ReactNode } from 'react';
+
+const rowsA = (
+
+
+ Alice
+ 42
+
+
+ Bob
+ 37
+
+
+ Charlie
+ 29
+
+
+);
+
+const rowsB = (
+
+
+ Diana
+ 55
+
+
+ Eve
+ 31
+
+
+ Frank
+ 48
+
+
+);
+
+const header = (
+
+
+ Name
+ Age
+
+
+);
+
+describe('Table', () => {
+ benchRender(
+ 'mount with rows',
+ () => (
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'rows change',
+ {
+ initialProps: { children: <>{header}{rowsA}> as ReactNode },
+ updatedProps: { children: <>{header}{rowsB}> as ReactNode },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Tabs.bench.tsx b/packages/benchmarks/src/base-ui/Tabs.bench.tsx
new file mode 100644
index 0000000..0505482
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Tabs.bench.tsx
@@ -0,0 +1,44 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Tabs } from '@omniview/base-ui';
+
+describe('Tabs', () => {
+ benchRender(
+ 'mount with 3 tabs',
+ () => (
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+ Panel 1
+ Panel 2
+ Panel 3
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'active tab change',
+ {
+ initialProps: { value: 0 as number },
+ updatedProps: { value: 2 as number },
+ },
+ (props) => (
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+ Panel 1
+ Panel 2
+ Panel 3
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/TagInput.bench.tsx b/packages/benchmarks/src/base-ui/TagInput.bench.tsx
new file mode 100644
index 0000000..0d32e3e
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/TagInput.bench.tsx
@@ -0,0 +1,37 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+import { makeTags } from '../utils/factories';
+import { TagInput } from '@omniview/base-ui';
+
+const tags10 = makeTags(10);
+const tags20 = makeTags(20);
+const tags5 = makeTags(5);
+
+const noop = () => {};
+
+function TagInputBench({ tags }: { tags: string[] }) {
+ return ;
+}
+
+describe('TagInput', () => {
+ benchRender(
+ 'mount with 10 tags',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'tags change (10 → 20)',
+ { initialProps: { tags: tags10 }, updatedProps: { tags: tags20 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchMountMany(
+ 'mount 100 instances (5 tags each)',
+ 100,
+ (i) => ,
+ TIER_1_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/TextArea.bench.tsx b/packages/benchmarks/src/base-ui/TextArea.bench.tsx
new file mode 100644
index 0000000..4aca204
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/TextArea.bench.tsx
@@ -0,0 +1,32 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { TextArea } from '@omniview/base-ui';
+
+describe('TextArea', () => {
+ benchRender('mount', () => (
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'disabled toggle',
+ { initialProps: { disabled: false }, updatedProps: { disabled: true } },
+ (props) => (
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 50', 50, (i) => (
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/TextField.bench.tsx b/packages/benchmarks/src/base-ui/TextField.bench.tsx
new file mode 100644
index 0000000..755f0d5
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/TextField.bench.tsx
@@ -0,0 +1,32 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { TextField } from '@omniview/base-ui';
+
+describe('TextField', () => {
+ benchRender('mount', () => (
+
+ Label
+
+
+ ), TIER_2_OPTIONS);
+
+ benchRerender(
+ 'disabled toggle',
+ { initialProps: { disabled: false }, updatedProps: { disabled: true } },
+ (props) => (
+
+ Label
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => (
+
+ Field {i}
+
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/Timeline.bench.tsx b/packages/benchmarks/src/base-ui/Timeline.bench.tsx
new file mode 100644
index 0000000..aa7d59a
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Timeline.bench.tsx
@@ -0,0 +1,43 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Timeline } from '@omniview/base-ui';
+
+function threeItems() {
+ return (
+ <>
+ Deployed v1.0
+ Health check passed
+ Rollout complete
+ >
+ );
+}
+
+function fourItems() {
+ return (
+ <>
+ Deployed v1.0
+ Health check passed
+ Rollout complete
+ Metrics stabilized
+ >
+ );
+}
+
+describe('Timeline', () => {
+ benchRender(
+ 'mount with 3 items',
+ () => {threeItems()},
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'items change',
+ {
+ initialProps: { children: threeItems() },
+ updatedProps: { children: fourItems() },
+ },
+ (props) => ,
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Toast.bench.tsx b/packages/benchmarks/src/base-ui/Toast.bench.tsx
new file mode 100644
index 0000000..ae654a7
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Toast.bench.tsx
@@ -0,0 +1,30 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ToastProvider } from '@omniview/base-ui';
+
+describe('ToastProvider', () => {
+ benchRender(
+ 'mount provider',
+ () => (
+
+ App content
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'position change',
+ {
+ initialProps: { position: 'bottom-right' as const },
+ updatedProps: { position: 'top-left' as const },
+ },
+ (props) => (
+
+ App content
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx b/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx
new file mode 100644
index 0000000..bd2491c
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ToggleButton.bench.tsx
@@ -0,0 +1,28 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ToggleButton } from '@omniview/base-ui';
+
+describe('ToggleButton', () => {
+ benchRender(
+ 'mount',
+ () => Bold,
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'pressed toggle',
+ {
+ initialProps: { pressed: false },
+ updatedProps: { pressed: true },
+ },
+ (props) => (
+
+ Bold
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 200', 200, (i) => Option {i}, TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/ToggleButtonGroup.bench.tsx b/packages/benchmarks/src/base-ui/ToggleButtonGroup.bench.tsx
new file mode 100644
index 0000000..d0b51bd
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/ToggleButtonGroup.bench.tsx
@@ -0,0 +1,34 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { ToggleButtonGroup } from '@omniview/base-ui';
+
+describe('ToggleButtonGroup', () => {
+ benchRender(
+ 'mount with 3 items',
+ () => (
+
+ Bold
+ Italic
+ Underline
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'value change',
+ {
+ initialProps: { value: ['bold'] as string[] },
+ updatedProps: { value: ['italic'] as string[] },
+ },
+ (props) => (
+
+ Bold
+ Italic
+ Underline
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Toolbar.bench.tsx b/packages/benchmarks/src/base-ui/Toolbar.bench.tsx
new file mode 100644
index 0000000..e99a69b
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Toolbar.bench.tsx
@@ -0,0 +1,38 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Toolbar, Button } from '@omniview/base-ui';
+
+describe('Toolbar', () => {
+ benchRender(
+ 'mount with buttons',
+ () => (
+
+
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'disabled toggle',
+ {
+ initialProps: { 'aria-disabled': undefined },
+ updatedProps: { 'aria-disabled': 'true' },
+ },
+ (props) => (
+
+
+
+
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+});
diff --git a/packages/benchmarks/src/base-ui/Tooltip.bench.tsx b/packages/benchmarks/src/base-ui/Tooltip.bench.tsx
new file mode 100644
index 0000000..7afabd2
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/Tooltip.bench.tsx
@@ -0,0 +1,51 @@
+import { describe } from 'vitest';
+import { benchRender, benchRerender, benchMountMany } from '../utils/bench-render';
+import { TIER_2_OPTIONS } from '../utils/bench-options';
+import { Tooltip } from '@omniview/base-ui';
+
+describe('Tooltip', () => {
+ benchRender(
+ 'mount',
+ () => (
+
+ Hover me
+
+
+ Tooltip content
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchRerender(
+ 'open toggle',
+ {
+ initialProps: { open: false },
+ updatedProps: { open: true },
+ },
+ (props) => (
+
+ Hover me
+
+
+ Tooltip content
+
+
+
+ ),
+ TIER_2_OPTIONS,
+ );
+
+ benchMountMany('mount 100', 100, (i) => (
+
+ Item {i}
+
+
+ Tooltip {i}
+
+
+
+ ), TIER_2_OPTIONS);
+});
diff --git a/packages/benchmarks/src/base-ui/TreeList.bench.tsx b/packages/benchmarks/src/base-ui/TreeList.bench.tsx
new file mode 100644
index 0000000..0d781d4
--- /dev/null
+++ b/packages/benchmarks/src/base-ui/TreeList.bench.tsx
@@ -0,0 +1,91 @@
+import { describe } from 'vitest';
+import { TreeList } from '@omniview/base-ui';
+import type { TreeNodeMeta } from '@omniview/base-ui';
+import { benchRender, benchRerender } from '../utils/bench-render';
+import { TIER_1_OPTIONS } from '../utils/bench-options';
+
+// ---------------------------------------------------------------------------
+// Data factories
+// ---------------------------------------------------------------------------
+
+interface TreeItem {
+ id: string;
+ label: string;
+ children?: TreeItem[];
+}
+
+function makeFlatItems(count: number): TreeItem[] {
+ return Array.from({ length: count }, (_, i) => ({
+ id: `item-${i}`,
+ label: `Item ${i}`,
+ }));
+}
+
+function makeNestedItems(parents: number, childrenPerParent: number): TreeItem[] {
+ return Array.from({ length: parents }, (_, i) => ({
+ id: `parent-${i}`,
+ label: `Parent ${i}`,
+ children: Array.from({ length: childrenPerParent }, (_, j) => ({
+ id: `parent-${i}-child-${j}`,
+ label: `Child ${i}-${j}`,
+ })),
+ }));
+}
+
+const itemKey = (item: TreeItem) => item.id;
+const getChildren = (item: TreeItem) => item.children ?? [];
+const isBranch = (item: TreeItem) => (item.children?.length ?? 0) > 0;
+const getTextValue = (item: TreeItem) => item.label;
+const renderItem = (item: TreeItem, node: TreeNodeMeta) => (
+
+
+ {node.isBranch && }
+ {item.label}
+
+);
+
+// Pre-generate data
+const flat500 = makeFlatItems(500);
+const nested50x2 = makeNestedItems(50, 2);
+const flat100 = makeFlatItems(100);
+const flat150 = makeFlatItems(150);
+// ---------------------------------------------------------------------------
+// Wrapper
+// ---------------------------------------------------------------------------
+
+function TreeListBench({ items }: { items: TreeItem[] }) {
+ return (
+
+
+
+ );
+}
+
+// ---------------------------------------------------------------------------
+// Benchmarks
+// ---------------------------------------------------------------------------
+
+describe('TreeList', () => {
+ benchRender('mount 500 flat nodes', () => , TIER_1_OPTIONS);
+
+ benchRender(
+ 'mount nested (50 parents x 2 children)',
+ () => ,
+ TIER_1_OPTIONS,
+ );
+
+ benchRerender(
+ 'data change (100 -> 150 nodes)',
+ { initialProps: { items: flat100 }, updatedProps: { items: flat150 } },
+ (props) => ,
+ TIER_1_OPTIONS,
+ );
+
+});
diff --git a/packages/benchmarks/src/setup-perf.ts b/packages/benchmarks/src/setup-perf.ts
new file mode 100644
index 0000000..472da93
--- /dev/null
+++ b/packages/benchmarks/src/setup-perf.ts
@@ -0,0 +1,20 @@
+import { configure } from 'reassure';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+// Ensure .reassure directory exists
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const reassureDir = path.resolve(__dirname, '../.reassure');
+if (!fs.existsSync(reassureDir)) {
+ fs.mkdirSync(reassureDir, { recursive: true });
+}
+
+// Tell Reassure to use @testing-library/react explicitly
+configure({
+ testingLibrary: 'react',
+ runs: 20,
+ warmupRuns: 3,
+});
+
diff --git a/packages/benchmarks/src/setup.ts b/packages/benchmarks/src/setup.ts
index bb02c60..cdca5e9 100644
--- a/packages/benchmarks/src/setup.ts
+++ b/packages/benchmarks/src/setup.ts
@@ -1 +1,62 @@
import '@testing-library/jest-dom/vitest';
+
+// ── Browser API stubs for jsdom ──
+// Components like ResizableSplitPane, DockLayout, ScrollArea depend on these.
+
+if (typeof globalThis.ResizeObserver === 'undefined') {
+ globalThis.ResizeObserver = class ResizeObserver {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+ } as unknown as typeof ResizeObserver;
+}
+
+if (typeof window !== 'undefined' && typeof window.matchMedia !== 'function') {
+ window.matchMedia = (query: string) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: () => {},
+ removeListener: () => {},
+ addEventListener: () => {},
+ removeEventListener: () => {},
+ dispatchEvent: () => false,
+ }) as MediaQueryList;
+}
+
+// @base-ui/react uses PointerEvent in click handlers (checkbox, button, etc.)
+if (typeof globalThis.PointerEvent === 'undefined') {
+ globalThis.PointerEvent = class PointerEvent extends MouseEvent {
+ readonly pointerId: number;
+ readonly width: number;
+ readonly height: number;
+ readonly pressure: number;
+ readonly tiltX: number;
+ readonly tiltY: number;
+ readonly pointerType: string;
+ readonly isPrimary: boolean;
+ constructor(type: string, params: PointerEventInit = {}) {
+ super(type, params);
+ this.pointerId = params.pointerId ?? 0;
+ this.width = params.width ?? 1;
+ this.height = params.height ?? 1;
+ this.pressure = params.pressure ?? 0;
+ this.tiltX = params.tiltX ?? 0;
+ this.tiltY = params.tiltY ?? 0;
+ this.pointerType = params.pointerType ?? '';
+ this.isPrimary = params.isPrimary ?? false;
+ }
+ } as unknown as typeof PointerEvent;
+}
+
+if (typeof globalThis.IntersectionObserver === 'undefined') {
+ globalThis.IntersectionObserver = class IntersectionObserver {
+ readonly root = null;
+ readonly rootMargin = '0px';
+ readonly thresholds = [0];
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+ takeRecords() { return []; }
+ } as unknown as typeof IntersectionObserver;
+}
diff --git a/packages/benchmarks/src/utils/bench-options.ts b/packages/benchmarks/src/utils/bench-options.ts
index 71f9374..a0fa345 100644
--- a/packages/benchmarks/src/utils/bench-options.ts
+++ b/packages/benchmarks/src/utils/bench-options.ts
@@ -13,3 +13,15 @@ const DEFAULT_OPTIONS: BenchRenderOptions = {
export function resolveOptions(options?: BenchRenderOptions): BenchRenderOptions {
return { ...DEFAULT_OPTIONS, ...options };
}
+
+/** Tier 1: deep benchmarks (mount + rerender + mountMany). */
+export const TIER_1_OPTIONS: BenchRenderOptions = {
+ iterations: 30,
+ warmupIterations: 3,
+};
+
+/** Tier 2: light benchmarks (mount + rerender only). */
+export const TIER_2_OPTIONS: BenchRenderOptions = {
+ iterations: 20,
+ warmupIterations: 2,
+};
diff --git a/packages/benchmarks/src/utils/factories.ts b/packages/benchmarks/src/utils/factories.ts
new file mode 100644
index 0000000..6a5fada
--- /dev/null
+++ b/packages/benchmarks/src/utils/factories.ts
@@ -0,0 +1,152 @@
+/**
+ * Shared data factories for benchmark tests.
+ * Pre-generate data outside the benchmark loop so construction cost isn't measured.
+ */
+
+// ── Row data (DataTable, BasicList, SelectableList, RowList) ──
+
+export interface Row {
+ id: number;
+ name: string;
+ status: string;
+ value: number;
+}
+
+export function makeRows(count: number): Row[] {
+ return Array.from({ length: count }, (_, i) => ({
+ id: i,
+ name: `Row ${i}`,
+ status: i % 3 === 0 ? 'active' : i % 3 === 1 ? 'pending' : 'inactive',
+ value: (i * 7 + 13) % 1000,
+ }));
+}
+
+// ── Tree node data (TreeList) ──
+
+export interface TreeNode {
+ id: string;
+ label: string;
+ children?: TreeNode[];
+}
+
+export function makeTreeNodes(count: number, depth: number = 2): TreeNode[] {
+ let created = 0;
+ const roots: TreeNode[] = [];
+ const queue: { node: TreeNode; depth: number }[] = [];
+
+ // Create root-level nodes
+ while (created < count) {
+ const node: TreeNode = {
+ id: `node-${created}`,
+ label: `Node ${created}`,
+ };
+ created++;
+ roots.push(node);
+ if (depth > 0) queue.push({ node, depth: 1 });
+ }
+
+ // Breadth-first: attach children until budget exhausted
+ while (queue.length > 0 && created < count) {
+ const { node, depth: d } = queue.shift()!;
+ const children: TreeNode[] = [];
+ const childCount = Math.min(2, count - created);
+ for (let i = 0; i < childCount; i++) {
+ const child: TreeNode = {
+ id: `node-${created}`,
+ label: `Node ${created}`,
+ };
+ created++;
+ children.push(child);
+ if (d < depth) queue.push({ node: child, depth: d + 1 });
+ }
+ node.children = children;
+ }
+
+ return roots;
+}
+
+// ── Option data (Select, Autocomplete, Combobox, MultiSelect) ──
+
+export interface Option {
+ value: string;
+ label: string;
+}
+
+export function makeOptions(count: number): Option[] {
+ return Array.from({ length: count }, (_, i) => ({
+ value: `option-${i}`,
+ label: `Option ${i}`,
+ }));
+}
+
+// ── Column definitions (DataTable, RowList) ──
+
+export interface Column {
+ id: string;
+ header: string;
+ accessorKey: keyof T;
+}
+
+export function makeColumns(): Column[] {
+ return [
+ { id: 'id', header: 'ID', accessorKey: 'id' },
+ { id: 'name', header: 'Name', accessorKey: 'name' },
+ { id: 'status', header: 'Status', accessorKey: 'status' },
+ { id: 'value', header: 'Value', accessorKey: 'value' },
+ ];
+}
+
+// ── Tag data (TagInput, FilterBar) ──
+
+export function makeTags(count: number): string[] {
+ return Array.from({ length: count }, (_, i) => `tag-${i}`);
+}
+
+// ── Command items (CommandList) ──
+
+export interface CommandItem {
+ id: string;
+ label: string;
+ group?: string;
+ keywords?: string[];
+}
+
+export function makeCommandItems(count: number): CommandItem[] {
+ const groups = ['File', 'Edit', 'View', 'Navigate', 'Run'];
+ return Array.from({ length: count }, (_, i) => ({
+ id: `cmd-${i}`,
+ label: `Command ${i}`,
+ group: groups[i % groups.length],
+ keywords: [`keyword-${i}`],
+ }));
+}
+
+// ── Tab data (EditorTabs) ──
+
+export interface TabItem {
+ id: string;
+ label: string;
+ closable?: boolean;
+}
+
+export function makeTabs(count: number): TabItem[] {
+ return Array.from({ length: count }, (_, i) => ({
+ id: `tab-${i}`,
+ label: `Tab ${i}`,
+ closable: true,
+ }));
+}
+
+export interface EditorTabItem {
+ id: string;
+ title: string;
+ closable?: boolean;
+}
+
+export function makeEditorTabs(count: number, prefix = ''): EditorTabItem[] {
+ return Array.from({ length: count }, (_, i) => ({
+ id: `${prefix}tab-${i}`,
+ title: `File ${prefix}${i}.ts`,
+ closable: true,
+ }));
+}
diff --git a/packages/benchmarks/vitest.config.perf.ts b/packages/benchmarks/vitest.config.perf.ts
new file mode 100644
index 0000000..446294d
--- /dev/null
+++ b/packages/benchmarks/vitest.config.perf.ts
@@ -0,0 +1,64 @@
+import { defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+/**
+ * Vitest config for Reassure performance tests.
+ *
+ * These are regular test() calls (not bench()) that use Reassure's measureRenders
+ * to track render counts and durations per interaction scenario. The output goes
+ * to .reassure/current.perf (or baseline.perf) for cross-branch comparison.
+ *
+ * V8 flags match Reassure's recommendations: disable JIT for stable measurements.
+ *
+ * Note: async factory + cast mirrors vitest.config.ts — needed because Vitest's
+ * InlineConfig type doesn't include `forks` even though the runtime supports it.
+ */
+export default defineConfig(async () => {
+ return {
+ plugins: [
+ react({
+ babel: {
+ plugins: [['babel-plugin-react-compiler', {}]],
+ },
+ }),
+ ],
+ resolve: {
+ alias: [
+ { find: '@omniview/base-ui', replacement: path.resolve(__dirname, '../base-ui/src/index.ts') },
+ { find: '@omniview/editors', replacement: path.resolve(__dirname, '../editors/src/index.ts') },
+ {
+ find: /^react-syntax-highlighter(\/.*)?$/,
+ replacement: path.resolve(__dirname, 'src/stubs/react-syntax-highlighter.ts'),
+ },
+ ],
+ },
+ test: {
+ include: ['src/**/*.perf-test.{ts,tsx}'],
+ environment: 'jsdom',
+ // Reassure's writeTestStats uses expect.getState().currentTestName
+ globals: true,
+ setupFiles: ['./src/setup.ts', './src/setup-perf.ts'],
+ pool: 'forks',
+ fileParallelism: false,
+ // Reassure recommends --no-turbofan --no-sparkplug for stable measurements.
+ // --expose-gc lets Reassure force GC between runs to reduce noise.
+ forks: {
+ execArgv: [
+ '--expose-gc',
+ '--no-turbofan',
+ '--no-sparkplug',
+ '--hash-seed=1',
+ '--random-seed=1',
+ '--max-old-space-size=4096',
+ ],
+ },
+ // Reassure tests are slow (20 runs + warmup per test). Generous timeout.
+ testTimeout: 60_000,
+ },
+ };
+}) as ReturnType;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 55a968b..2ccac93 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -208,6 +208,9 @@ importers:
packages/benchmarks:
devDependencies:
+ '@callstack/reassure-compare':
+ specifier: ^1.4.1
+ version: 1.4.1
'@codspeed/vitest-plugin':
specifier: ^5.2.0
version: 5.2.0(tinybench@2.9.0)(vite@7.3.1(@types/node@22.19.15)(yaml@2.8.2))(vitest@4.0.18(@types/node@22.19.15)(jsdom@25.0.1)(yaml@2.8.2))
@@ -247,6 +250,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.2.4(react@19.2.4)
+ reassure:
+ specifier: ^1.4.1
+ version: 1.4.1(react@19.2.4)
typescript:
specifier: ^5.9.2
version: 5.9.3
@@ -473,6 +479,24 @@ packages:
'@types/react':
optional: true
+ '@callstack/reassure-cli@1.4.1':
+ resolution: {integrity: sha512-NKJi/WoLiDkn3FnYJhOk+FhWsdvrytrAfHRqexrCeyVfwpgbM+laPWto2+EKbfKdrHWBqWgbBOWyFoMiOAXcVg==}
+ hasBin: true
+
+ '@callstack/reassure-compare@1.4.1':
+ resolution: {integrity: sha512-qJgASbKlBWA37XSN5b/uVAvc524dd9s3grumCKabI2GHkA5w5G6WWI11DnJPzjBnoZ/oIgNSrOm2sm6UMEvsVQ==}
+
+ '@callstack/reassure-danger@1.4.1':
+ resolution: {integrity: sha512-TTfliolOt8Tfq4yu8vdj4lRdki5Vuvwxj22K2+xpAjxiF2rT4BL6gwM3YIPVUwuQMN3kQ8d8TuGJ4Wlc3zpAUA==}
+
+ '@callstack/reassure-logger@1.4.1':
+ resolution: {integrity: sha512-2uB6OBk0/IdSXUpdTsMcN40d3ly5xWWPRjs0G4zEr37tyHB0lmJVuQIR5elUjIs+fTuK48PVuqW/4+Yce7WlFA==}
+
+ '@callstack/reassure-measure@1.4.1':
+ resolution: {integrity: sha512-EPUKuMgzz0bccf/nn+6A5PhunZS/K3h0rpvhdOsPzGl3abjROoPi7bQN/ipURsLwle+OonqLCigcOK/CnndLEg==}
+ peerDependencies:
+ react: '>=18.0.0'
+
'@codspeed/core@5.2.0':
resolution: {integrity: sha512-CmDhpWjcOJg2iBOQ/BmBnSBq8qxlM3r4h8uvYDkoUaba+EKRT3T73BZtKuml/48jZMsB+4/FG2UbTBinDWtuvw==}
@@ -764,6 +788,10 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@jest/schemas@30.0.5':
+ resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
'@joshwooding/vite-plugin-react-docgen-typescript@0.6.4':
resolution: {integrity: sha512-6PyZBYKnnVNqOSB0YFly+62R7dmov8segT27A+RVTBVd4iAE6kbW9QBJGlyR2yG4D4ohzhZSTIu7BK1UTtmFFA==}
peerDependencies:
@@ -789,6 +817,12 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+ '@kwsites/file-exists@1.1.1':
+ resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==}
+
+ '@kwsites/promise-deferred@1.1.1':
+ resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==}
+
'@mdx-js/react@3.1.1':
resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==}
peerDependencies:
@@ -975,6 +1009,9 @@ packages:
'@rushstack/ts-command-line@5.3.3':
resolution: {integrity: sha512-c+ltdcvC7ym+10lhwR/vWiOhsrm/bP3By2VsFcs5qTKv+6tTmxgbVrtJ5NdNjANiV5TcmOZgUN+5KYQ4llsvEw==}
+ '@sinclair/typebox@0.34.48':
+ resolution: {integrity: sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==}
+
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -1537,6 +1574,10 @@ packages:
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
engines: {node: '>= 16'}
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1554,6 +1595,9 @@ packages:
compare-versions@6.1.1:
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
+ complex.js@2.4.3:
+ resolution: {integrity: sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -1677,6 +1721,9 @@ packages:
electron-to-chromium@1.5.307:
resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==}
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
empathic@2.0.0:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
engines: {node: '>=14'}
@@ -1733,6 +1780,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
+ escape-latex@1.2.0:
+ resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
+
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -1857,6 +1907,10 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
+ find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -1893,6 +1947,9 @@ packages:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
+ fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+
fs-extra@11.3.4:
resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
engines: {node: '>=14.14'}
@@ -1920,6 +1977,10 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -2058,6 +2119,11 @@ packages:
resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
engines: {node: '>=8'}
+ import-local@3.2.0:
+ resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
+ engines: {node: '>=8'}
+ hasBin: true
+
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@@ -2127,6 +2193,10 @@ packages:
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
engines: {node: '>= 0.4'}
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
is-generator-function@1.1.2:
resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
engines: {node: '>= 0.4'}
@@ -2212,6 +2282,9 @@ packages:
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
engines: {node: '>= 0.4'}
+ javascript-natural-sort@0.7.1:
+ resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
+
jju@1.4.0:
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
@@ -2277,6 +2350,10 @@ packages:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'}
+ locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -2332,6 +2409,11 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
+ mathjs@15.1.1:
+ resolution: {integrity: sha512-rM668DTtpSzMVoh/cKAllyQVEbBApM5g//IMGD8vD7YlrIz9ITRr3SrdhjaDxcBNTdyETWwPebj2unZyHD7ZdA==}
+ engines: {node: '>= 18'}
+ hasBin: true
+
mdast-util-find-and-replace@3.0.2:
resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
@@ -2591,6 +2673,10 @@ packages:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -2599,6 +2685,10 @@ packages:
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
@@ -2607,6 +2697,10 @@ packages:
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -2653,6 +2747,10 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
+ pkg-dir@4.2.0:
+ resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
+ engines: {node: '>=8'}
+
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
@@ -2680,6 +2778,10 @@ packages:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+ pretty-format@30.3.0:
+ resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
@@ -2725,6 +2827,9 @@ packages:
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
react-markdown@9.1.0:
resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==}
peerDependencies:
@@ -2745,6 +2850,10 @@ packages:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
+ reassure@1.4.1:
+ resolution: {integrity: sha512-EeW23/ci4mGHsv4WDSK9jTzzi5yCu3Gdf3GgugKvi5NrqxCiVubiTlcz8bjfpipSFEUK+mpDtlhyJI/6Kmkgjg==}
+ hasBin: true
+
recast@0.23.11:
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
engines: {node: '>= 4'}
@@ -2782,6 +2891,10 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -2789,10 +2902,18 @@ packages:
reselect@5.1.1:
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+ resolve-cwd@3.0.0:
+ resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
+ engines: {node: '>=8'}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
resolve@1.22.11:
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
engines: {node: '>= 0.4'}
@@ -2840,6 +2961,9 @@ packages:
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+ seedrandom@3.0.5:
+ resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
+
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -2893,6 +3017,9 @@ packages:
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+ simple-git@3.33.0:
+ resolution: {integrity: sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==}
+
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -2930,6 +3057,10 @@ packages:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
string.prototype.matchall@4.0.12:
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
engines: {node: '>= 0.4'}
@@ -2952,6 +3083,10 @@ packages:
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
@@ -2992,6 +3127,9 @@ packages:
tabbable@6.4.0:
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
+ tiny-emitter@2.1.0:
+ resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
+
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -3049,6 +3187,14 @@ packages:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'}
+ ts-markdown-builder@0.5.0:
+ resolution: {integrity: sha512-/2pzAFjGwk5fUR8ftFMhCOm/zmoqtpYWK6209j3YPfgWF2I+eYY6PKpR0mBJte6s8McnkQ/T92fV+IJg6bdxyw==}
+ engines: {node: '>= 18.0.0'}
+
+ ts-regex-builder@1.8.2:
+ resolution: {integrity: sha512-Y8HovHFheDKm/jgLIWSO8o81xA/I9O5AGc3/vNG1sVSskatOifr3SQzAsatBXGLjL3nYhQif1MpwQRS5GF8ADg==}
+ engines: {node: '>= 18.0.0'}
+
tsconfig-paths@4.2.0:
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
engines: {node: '>=6'}
@@ -3076,6 +3222,10 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
+ typed-function@4.2.2:
+ resolution: {integrity: sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==}
+ engines: {node: '>= 18'}
+
typescript-eslint@8.57.0:
resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3308,6 +3458,10 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
ws@8.19.0:
resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
engines: {node: '>=10.0.0'}
@@ -3331,6 +3485,10 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -3342,6 +3500,14 @@ packages:
engines: {node: '>= 14.6'}
hasBin: true
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -3350,6 +3516,9 @@ packages:
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
engines: {node: '>=12.20'}
+ zod@4.3.6:
+ resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -3503,6 +3672,36 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.14
+ '@callstack/reassure-cli@1.4.1':
+ dependencies:
+ '@callstack/reassure-compare': 1.4.1
+ '@callstack/reassure-logger': 1.4.1
+ chalk: 4.1.2
+ simple-git: 3.33.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@callstack/reassure-compare@1.4.1':
+ dependencies:
+ '@callstack/reassure-logger': 1.4.1
+ ts-markdown-builder: 0.5.0
+ ts-regex-builder: 1.8.2
+ zod: 4.3.6
+
+ '@callstack/reassure-danger@1.4.1': {}
+
+ '@callstack/reassure-logger@1.4.1':
+ dependencies:
+ chalk: 4.1.2
+
+ '@callstack/reassure-measure@1.4.1(react@19.2.4)':
+ dependencies:
+ '@callstack/reassure-logger': 1.4.1
+ mathjs: 15.1.1
+ pretty-format: 30.3.0
+ react: 19.2.4
+
'@codspeed/core@5.2.0':
dependencies:
axios: 1.13.6
@@ -3725,6 +3924,10 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
+ '@jest/schemas@30.0.5':
+ dependencies:
+ '@sinclair/typebox': 0.34.48
+
'@joshwooding/vite-plugin-react-docgen-typescript@0.6.4(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.15)(yaml@2.8.2))':
dependencies:
glob: 13.0.6
@@ -3752,6 +3955,14 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
+ '@kwsites/file-exists@1.1.1':
+ dependencies:
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@kwsites/promise-deferred@1.1.1': {}
+
'@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4)':
dependencies:
'@types/mdx': 2.0.13
@@ -3918,6 +4129,8 @@ snapshots:
transitivePeerDependencies:
- '@types/node'
+ '@sinclair/typebox@0.34.48': {}
+
'@standard-schema/spec@1.1.0': {}
'@storybook/addon-a11y@10.2.17(storybook@10.2.17(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))':
@@ -4586,6 +4799,12 @@ snapshots:
check-error@2.1.3: {}
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -4600,6 +4819,8 @@ snapshots:
compare-versions@6.1.1: {}
+ complex.js@2.4.3: {}
+
concat-map@0.0.1: {}
confbox@0.1.8: {}
@@ -4713,6 +4934,8 @@ snapshots:
electron-to-chromium@1.5.307: {}
+ emoji-regex@8.0.0: {}
+
empathic@2.0.0: {}
entities@6.0.1: {}
@@ -4854,6 +5077,8 @@ snapshots:
escalade@3.2.0: {}
+ escape-latex@1.2.0: {}
+
escape-string-regexp@4.0.0: {}
escape-string-regexp@5.0.0: {}
@@ -4992,6 +5217,11 @@ snapshots:
dependencies:
flat-cache: 4.0.1
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -5025,6 +5255,8 @@ snapshots:
format@0.2.2: {}
+ fraction.js@5.3.4: {}
+
fs-extra@11.3.4:
dependencies:
graceful-fs: 4.2.11
@@ -5051,6 +5283,8 @@ snapshots:
gensync@1.0.0-beta.2: {}
+ get-caller-file@2.0.5: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -5242,6 +5476,11 @@ snapshots:
import-lazy@4.0.0: {}
+ import-local@3.2.0:
+ dependencies:
+ pkg-dir: 4.2.0
+ resolve-cwd: 3.0.0
+
imurmurhash@0.1.4: {}
indent-string@4.0.0: {}
@@ -5311,6 +5550,8 @@ snapshots:
dependencies:
call-bound: 1.0.4
+ is-fullwidth-code-point@3.0.0: {}
+
is-generator-function@1.1.2:
dependencies:
call-bound: 1.0.4
@@ -5398,6 +5639,8 @@ snapshots:
has-symbols: 1.1.0
set-function-name: 2.0.2
+ javascript-natural-sort@0.7.1: {}
+
jju@1.4.0: {}
js-tokens@4.0.0: {}
@@ -5478,6 +5721,10 @@ snapshots:
pkg-types: 2.3.0
quansync: 0.2.11
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
@@ -5525,6 +5772,18 @@ snapshots:
math-intrinsics@1.1.0: {}
+ mathjs@15.1.1:
+ dependencies:
+ '@babel/runtime': 7.28.6
+ complex.js: 2.4.3
+ decimal.js: 10.6.0
+ escape-latex: 1.2.0
+ fraction.js: 5.3.4
+ javascript-natural-sort: 0.7.1
+ seedrandom: 3.0.5
+ tiny-emitter: 2.1.0
+ typed-function: 4.2.2
+
mdast-util-find-and-replace@3.0.2:
dependencies:
'@types/mdast': 4.0.4
@@ -6018,6 +6277,10 @@ snapshots:
object-keys: 1.1.1
safe-push-apply: 1.0.0
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -6026,6 +6289,10 @@ snapshots:
dependencies:
yocto-queue: 1.2.2
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
@@ -6034,6 +6301,8 @@ snapshots:
dependencies:
p-limit: 4.0.0
+ p-try@2.2.0: {}
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -6075,6 +6344,10 @@ snapshots:
picomatch@4.0.3: {}
+ pkg-dir@4.2.0:
+ dependencies:
+ find-up: 4.1.0
+
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
@@ -6105,6 +6378,12 @@ snapshots:
ansi-styles: 5.2.0
react-is: 17.0.2
+ pretty-format@30.3.0:
+ dependencies:
+ '@jest/schemas': 30.0.5
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
prismjs@1.30.0: {}
prop-types@15.8.1:
@@ -6153,6 +6432,8 @@ snapshots:
react-is@17.0.2: {}
+ react-is@18.3.1: {}
+
react-markdown@9.1.0(@types/react@19.2.14)(react@19.2.4):
dependencies:
'@types/hast': 3.0.4
@@ -6185,6 +6466,17 @@ snapshots:
react@19.2.4: {}
+ reassure@1.4.1(react@19.2.4):
+ dependencies:
+ '@callstack/reassure-cli': 1.4.1
+ '@callstack/reassure-compare': 1.4.1
+ '@callstack/reassure-danger': 1.4.1
+ '@callstack/reassure-measure': 1.4.1(react@19.2.4)
+ import-local: 3.2.0
+ transitivePeerDependencies:
+ - react
+ - supports-color
+
recast@0.23.11:
dependencies:
ast-types: 0.16.1
@@ -6270,12 +6562,20 @@ snapshots:
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
+ require-directory@2.1.1: {}
+
require-from-string@2.0.2: {}
reselect@5.1.1: {}
+ resolve-cwd@3.0.0:
+ dependencies:
+ resolve-from: 5.0.0
+
resolve-from@4.0.0: {}
+ resolve-from@5.0.0: {}
+
resolve@1.22.11:
dependencies:
is-core-module: 2.16.1
@@ -6355,6 +6655,8 @@ snapshots:
scheduler@0.27.0: {}
+ seedrandom@3.0.5: {}
+
semver@6.3.1: {}
semver@7.5.4:
@@ -6421,6 +6723,14 @@ snapshots:
siginfo@2.0.0: {}
+ simple-git@3.33.0:
+ dependencies:
+ '@kwsites/file-exists': 1.1.1
+ '@kwsites/promise-deferred': 1.1.1
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
source-map-js@1.2.1: {}
source-map@0.6.1: {}
@@ -6463,6 +6773,12 @@ snapshots:
string-argv@0.3.2: {}
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
string.prototype.matchall@4.0.12:
dependencies:
call-bind: 1.0.8
@@ -6512,6 +6828,10 @@ snapshots:
character-entities-html4: 2.1.0
character-entities-legacy: 3.0.0
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
strip-bom@3.0.0: {}
strip-indent@3.0.0:
@@ -6544,6 +6864,8 @@ snapshots:
tabbable@6.4.0: {}
+ tiny-emitter@2.1.0: {}
+
tiny-invariant@1.3.3: {}
tinybench@2.9.0: {}
@@ -6585,6 +6907,10 @@ snapshots:
ts-dedent@2.2.0: {}
+ ts-markdown-builder@0.5.0: {}
+
+ ts-regex-builder@1.8.2: {}
+
tsconfig-paths@4.2.0:
dependencies:
json5: 2.2.3
@@ -6630,6 +6956,8 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
+ typed-function@4.2.2: {}
+
typescript-eslint@8.57.0(eslint@9.39.4)(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)
@@ -6883,6 +7211,12 @@ snapshots:
word-wrap@1.2.5: {}
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
ws@8.19.0: {}
wsl-utils@0.1.0:
@@ -6893,14 +7227,30 @@ snapshots:
xmlchars@2.2.0: {}
+ y18n@5.0.8: {}
+
yallist@3.1.1: {}
yallist@4.0.0: {}
yaml@2.8.2: {}
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
yocto-queue@0.1.0: {}
yocto-queue@1.2.2: {}
+ zod@4.3.6: {}
+
zwitch@2.0.4: {}