Skip to content

perf(richtext-lexical): 3-15x less main thread blocking via centralized toolbar state#15832

Open
AlessioGr wants to merge 12 commits intomainfrom
perf/lexical
Open

perf(richtext-lexical): 3-15x less main thread blocking via centralized toolbar state#15832
AlessioGr wants to merge 12 commits intomainfrom
perf/lexical

Conversation

@AlessioGr
Copy link
Member

@AlessioGr AlessioGr commented Mar 4, 2026

Replaces per-component editor.registerUpdateListener subscriptions in toolbar buttons and dropdowns with a single centralized useToolbarStates hook that computes all active/enabled states in one pass. Previously every keystroke triggered 15+ independent React state updates across toolbar items - now it triggers one.

ToolbarButton and ToolbarDropdown are now stateless presentational components. The dropdown icon/label derivation that previously used a useState/useEffect/onActiveChange callback loop is replaced with a direct useMemo from groupState.activeItems.

Also adds a dedicated benchmark suite (test/lexical/benchmarks/) with CPU-throttled typing for tracking performance.

Benchmark Results (6x CPU throttle, 5 iterations)

Empty Editor

Metric Before After Change
Mean AvgLag 10.05ms 11.09ms ~same
Long Tasks 68.4 4.2 -94%
Long Task Duration 4315ms 277ms 15x less

With 30 Blocks

Metric Before After Change
Mean AvgLag 15.96ms 14.88ms ~same
Long Tasks 26.6 2.8 -89%
Long Task Duration 2838ms 844ms 3.4x less
Raw Results

Before:

=== CPU Throttled (6x) — Empty Editor (5 iterations) ===
    Iter  AvgLag  P95Lag  Mut/Key LongTasks LongTaskMs
       1   11.52   16.30     1.01     76.00    4818.00
       2    9.73   13.40     1.01     67.00    4253.00
       3    9.51   13.10     1.01     70.00    4309.00
       4    9.61   13.40     1.01     66.00    4159.00
       5    9.88   13.30     1.01     63.00    4036.00
  ────────────────────────────────────────────────────
    Mean   10.05   13.90     1.01     68.40    4315.00
  StdDev    0.83    1.35     0.00      4.93     299.61
     Min    9.51   13.10     1.01     63.00    4036.00
     Max   11.52   16.30     1.01     76.00    4818.00
  ✓  1 [chromium] › test/lexical/benchmarks/perf.spec.ts:200:3 › Lexical Performance Benchmarks › CPU throttled typing - empty editor (6x slowdown) (1.0m)

=== CPU Throttled (6x) — With 30 Blocks (5 iterations) ===
    Iter  AvgLag  P95Lag  Mut/Key LongTasks LongTaskMs
       1   15.38   19.60    12.41     17.00    2915.00
       2   12.94   15.70     8.61     22.00    2792.00
       3   23.35   64.20     4.81     45.00    3823.00
       4   14.23   16.30     4.81     19.00    2019.00
       5   13.92   16.00     4.81     30.00    2640.00
  ────────────────────────────────────────────────────
    Mean   15.96   26.36     7.09     26.60    2837.80
  StdDev    4.22   21.21     3.40     11.41     649.66
     Min   12.94   15.70     4.81     17.00    2019.00
     Max   23.35   64.20    12.41     45.00    3823.00
  ✓  2 [chromium] › test/lexical/benchmarks/perf.spec.ts:225:3 › Lexical Performance Benchmarks › CPU throttled typing - with 30 blocks (6x slowdown) (1.1m)

After:

=== CPU Throttled (6x) — Empty Editor (5 iterations) ===
    Iter  AvgLag  P95Lag  Mut/Key LongTasks LongTaskMs
       1   10.98   24.90     1.01      8.00     513.00
       2   11.00   13.90     1.01      2.00     145.00
       3   11.28   27.70     1.01      3.00     194.00
       4   11.97   28.30     1.01      5.00     305.00
       5   10.20   11.70     1.01      3.00     231.00
  ────────────────────────────────────────────────────
    Mean   11.09   21.30     1.01      4.20     277.60
  StdDev    0.64    7.90     0.00      2.39     143.97
     Min   10.20   11.70     1.01      2.00     145.00
     Max   11.97   28.30     1.01      8.00     513.00
  ✓  1 [chromium] › test/lexical/benchmarks/perf.spec.ts:200:3 › Lexical Performance Benchmarks › CPU throttled typing - empty editor (6x slowdown) (48.5s)

=== CPU Throttled (6x) — With 30 Blocks (5 iterations) ===
    Iter  AvgLag  P95Lag  Mut/Key LongTasks LongTaskMs
       1   15.17   32.30     4.81      3.00     955.00
       2   14.93   30.50     4.81      2.00     810.00
       3   14.13   29.80     4.81      3.00     896.00
       4   15.38   29.50     4.81      3.00     686.00
       5   14.78   30.30     4.81      3.00     871.00
  ────────────────────────────────────────────────────
    Mean   14.88   30.48     4.81      2.80     843.60
  StdDev    0.48    1.09     0.00      0.45     102.31
     Min   14.13   29.50     4.81      2.00     686.00
     Max   15.38   32.30     4.81      3.00     955.00
  ✓  2 [chromium] › test/lexical/benchmarks/perf.spec.ts:225:3 › Lexical Performance Benchmarks › CPU throttled typing - with 30 blocks (6x slowdown) (1.1m)

Main thread blocking dropped 3x with 30 blocks in the editor and 15x on an empty editor. I ran this test multiple times, and the results for both before and after were consistently in a similar range.

In practice, typing in the editor should feel smoother, especially on slower devices or in documents with lots of blocks and fields.

* Maximum number of active items allowed. This is a performance optimization to prevent
* unnecessary item active checks when the maximum number of active items is reached.
*/
maxActiveItems?: number
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This functionality still exists, it was just moved to useToolbarStates

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we're exporting this component, so it's a breaking change.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 959.88 KB 🆕 Added
packages/payload/meta_index.json esbuild/index.js 1.32 MB 🆕 Added
packages/payload/meta_shared.json esbuild/exports/shared.js 190.59 KB 🆕 Added
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 280.32 KB 🆕 Added
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.18 MB 🆕 Added
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 16.32 KB 🆕 Added
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████▌ }}}$ 82.0%, 783.61 KB
dist/views/Version ${{\color{Goldenrod}{ █▎ }}}$ 5.4%, 51.49 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 21.37 KB
dist/views/Document ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 16.59 KB
dist/views/List ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 11.38 KB
dist/views/Root ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 9.03 KB
dist/views/Versions ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.17 KB
dist/views/API ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.08 KB
dist/elements/Nav ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 5.96 KB
dist/views/Account ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 5.55 KB
dist/elements/DocumentHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 4.81 KB
dist/views/Login ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 4.40 KB
dist/views/ForgotPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 3.09 KB
dist/layouts/Root ${{\color{Goldenrod}{ }}}$ 0.3%, 2.91 KB
dist/views/CreateFirstUser ${{\color{Goldenrod}{ }}}$ 0.3%, 2.81 KB
dist/templates/Default ${{\color{Goldenrod}{ }}}$ 0.3%, 2.63 KB
dist/views/BrowseByFolder ${{\color{Goldenrod}{ }}}$ 0.3%, 2.61 KB
dist/views/CollectionFolders ${{\color{Goldenrod}{ }}}$ 0.3%, 2.44 KB
dist/views/ResetPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 2.40 KB
dist/views/Logout ${{\color{Goldenrod}{ }}}$ 0.2%, 1.94 KB
(other) ${{\color{Goldenrod}{ ████▌ }}}$ 18.0%, 171.60 KB

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████▉ }}}$ 67.6%, 887.23 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.3%, 43.59 KB
dist/collections/operations ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 39.90 KB
dist/versions/migrations ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 18.50 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 15.74 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.17 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.15 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.90 KB
dist/queues/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.80 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.54 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.87 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.35 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.69 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.67 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.54 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.83 KB
dist/collections/endpoints ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.23 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 5.80 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 5.50 KB
dist/auth/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 5.42 KB
(other) ${{\color{Goldenrod}{ ████████ }}}$ 32.4%, 424.50 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████████████▊ }}}$ 79.4%, 148.51 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 10.54 KB
dist/config/orderable ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 2.54 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.42 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.28 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 1.04 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 943 B
dist/folders/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 916 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.4%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.4%, 713 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 651 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.3%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.3%, 561 B
dist/auth/sessions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 525 B
dist/fields/getFieldPaths.js ${{\color{Goldenrod}{ }}}$ 0.3%, 485 B
dist/errors/APIError.js ${{\color{Goldenrod}{ }}}$ 0.2%, 438 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.2%, 423 B
(other) ${{\color{Goldenrod}{ █████▏ }}}$ 20.6%, 38.43 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▏ }}}$ 12.8%, 35.38 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.5%, 32.00 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▏ }}}$ 8.8%, 24.36 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▏ }}}$ 8.6%, 23.70 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▋ }}}$ 6.9%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.6%, 18.25 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▍ }}}$ 5.8%, 16.04 KB
dist/features/upload ${{\color{Goldenrod}{ █▎ }}}$ 5.0%, 13.77 KB
dist/features/textState ${{\color{Goldenrod}{ █ }}}$ 4.0%, 11.08 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.3%, 9.03 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 8.49 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 7.39 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 7.15 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 7.05 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.08 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.00 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.17 KB
dist/lexical/theme ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.62 KB
dist/features/indent ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.50 KB
(other) ${{\color{Goldenrod}{ █████████████████████▊ }}}$ 87.2%, 241.69 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████▎ }}}$ 49.4%, 579.02 KB
dist/elements/FolderView ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 29.37 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 27.63 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 17.27 KB
dist/views/Edit ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 17.17 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.78 KB
dist/elements/Table ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.77 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.31 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 14.24 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 13.89 KB
dist/elements/QueryPresets ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 10.36 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 9.06 KB
dist/providers/Folders ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.47 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.38 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.38 KB
dist/elements/ListHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.99 KB
dist/fields/Array ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.73 KB
dist/views/CollectionFolder ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.50 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.35 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.33 KB
(other) ${{\color{Goldenrod}{ ████████████▋ }}}$ 50.6%, 592.90 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ █████ }}}$ 20.0%, 3.12 KB
../../node_modules ${{\color{Goldenrod}{ ████▎ }}}$ 17.0%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▍ }}}$ 9.8%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ ██▏ }}}$ 8.5%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▍ }}}$ 5.5%, 862 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █▎ }}}$ 5.2%, 814 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █▏ }}}$ 4.9%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █▏ }}}$ 4.8%, 756 B
dist/elements/Translation ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 399 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 339 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 329 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 301 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 159 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 152 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 147 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 146 B
(other) ${{\color{Goldenrod}{ ████████████████████ }}}$ 80.0%, 12.51 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces richtext-lexical toolbar rendering overhead by centralizing toolbar active/enabled state computation into a single hook, and adds a Playwright-based benchmark suite to track editor typing performance under CPU throttling.

Changes:

  • Introduces useToolbarStates to compute all toolbar group/item states from a single Lexical update subscription.
  • Refactors fixed + inline toolbars to pass centralized state into stateless ToolbarButton / ToolbarDropdown.
  • Adds a Lexical benchmark collection + seeded doc and a new Playwright benchmark spec.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/runE2E.ts Removes --start-memory-db from spawned dev args.
test/playwright.config.ts Expands Playwright testMatch to include *perf.spec.ts.
test/lexical/slugs.ts Adds lexicalBenchmarkSlug.
test/lexical/seed.ts Seeds a benchmark Lexical doc containing 30 blocks.
test/lexical/payload-types.ts Updates generated types for the new benchmark collection.
test/lexical/collections/LexicalBenchmark/index.ts Adds a new LexicalBenchmark collection config with Blocks + FixedToolbar features.
test/lexical/collections/Lexical/e2e/main/e2e.spec.ts Makes internal link option selection more robust by filtering by text.
test/lexical/benchmarks/perf.spec.ts Adds CPU-throttled typing benchmark tests + summary output.
test/lexical/baseConfig.ts Registers LexicalBenchmark in the lexical test config.
packages/richtext-lexical/src/features/toolbars/shared/useToolbarStates.ts New centralized state hook for toolbar items/groups.
packages/richtext-lexical/src/features/toolbars/shared/ToolbarDropdown/index.tsx Refactors dropdown to be state-less and consume groupState.
packages/richtext-lexical/src/features/toolbars/shared/ToolbarButton/index.tsx Refactors button to be state-less and consume active/enabled.
packages/richtext-lexical/src/features/toolbars/inline/client/Toolbar/index.tsx Wires inline toolbar to useToolbarStates.
packages/richtext-lexical/src/features/toolbars/fixed/client/Toolbar/index.tsx Wires fixed toolbar to useToolbarStates.
Comments suppressed due to low confidence (1)

test/lexical/benchmarks/perf.spec.ts:111

  • p95Lag calculation uses sorted[Math.floor(lags.length * 0.95)], which is off by one for typical percentile definitions (and can skew the reported p95, especially for small sample sizes). Consider using an index based on (lags.length - 1) * 0.95 (or Math.ceil(lags.length * 0.95) - 1) to better approximate the 95th percentile.
  const sorted = [...lags].sort((a, b) => a - b)
  const avgLag = lags.length > 0 ? lags.reduce((a, b) => a + b, 0) / lags.length : 0
  const p95Lag = lags.length > 0 ? (sorted[Math.floor(lags.length * 0.95)] ?? Math.max(...lags)) : 0


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 82 to 99
export const ToolbarDropdown = ({
anchorElem,
classNames,
editor,
group,
groupState,
Icon,
itemsContainerClassNames,
label,
maxActiveItems,
onActiveChange,
}: {
anchorElem: HTMLElement
classNames?: string[]
editor: LexicalEditor
group: ToolbarDropdownGroup
groupState: ToolbarGroupState
Icon?: React.FC
itemsContainerClassNames?: string[]
label?: string
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolbarDropdown now requires a groupState prop and removes maxActiveItems / onActiveChange. Because ToolbarDropdown is exported from the package client exports, this is a breaking API change for consumers that render dropdowns directly. Consider making groupState optional (with an internal fallback to compute state), or introducing a new internal-only component and keeping the exported component’s previous props supported.

Copilot uses AI. Check for mistakes.
// Look for test files in the "test" directory, relative to this configuration file
testDir: '',
testMatch: '*e2e.spec.ts',
testMatch: ['*e2e.spec.ts', '*perf.spec.ts'],
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including *perf.spec.ts in the default testMatch means the CPU-throttled benchmark suite will run as part of every E2E run/CI run. These tests are intentionally long and performance-sensitive, so this change can significantly increase CI duration and introduce noise/flakiness. Consider moving benchmarks under a separate Playwright config/project, or gating them behind an env var / --grep so they only run when explicitly requested.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've already validated it. I would actually remove the benchmark. No point in merging it into main. What do you think @AlessioGr?

Comment on lines +14 to +23
active,
children,
editor,
enabled,
item,
}: {
active: boolean
children: React.JSX.Element
editor: LexicalEditor
enabled: boolean
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolbarButton now requires active and enabled props. Since this component is exported from the package client exports, this is a breaking public API change for any consumers rendering ToolbarButton directly. Consider keeping backward compatibility by making these props optional with safe defaults (e.g. active=false, enabled=true) and/or providing a fallback state calculation when they aren’t supplied, or stop exporting this internal component.

Suggested change
active,
children,
editor,
enabled,
item,
}: {
active: boolean
children: React.JSX.Element
editor: LexicalEditor
enabled: boolean
active = false,
children,
editor,
enabled = true,
item,
}: {
active?: boolean
children: React.JSX.Element
editor: LexicalEditor
enabled?: boolean

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Similar to my other comment

Copy link
Contributor

@GermanJablo GermanJablo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost good to go. I would remove the benchmark and avoid the breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants