Skip to content

[lexical-mdast][lexical-y] Chore: Give slow tests a timeout that holds under parallel load, and measure the follow-up check's cost against a smaller document - #9576

Open
zurfyx wants to merge 3 commits into
mainfrom
audit/y-test-timeouts
Open

zurfyx wants to merge 3 commits into
mainfrom
audit/y-test-timeouts

Conversation

@zurfyx

@zurfyx zurfyx commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Description

Four tests take 2 to 4 seconds alone against vitest's default 5 second limit, so they time out when the whole suite runs in parallel: DisposeMemory, ServerScale's linear-time test, both MdastTextRuns tests and the first extensionFlowSubpaths test (which starts the TypeScript checker). Their times and measured values are the same on main, on main of a day earlier (0cd7448) and on the first @lexical/y commit, so the timeouts hide no slowdown and no leak. They now have a 30 second limit, as Headless's synchronous loop test already does.

FollowUpCheckCost asserted that the follow-up check costs under 5 ms per follow-up in a 10,000-block document. Alone it measures 2.1 to 2.7 ms, and it measured 5.06 ms with type checks running beside it. The property is that the check reads the part of the document a follow-up changed, after walking the container up to it (followUpRanges finds the follow-up's unit with units.indexOf, and formatsAt walks the container's items from its start), so its cost grows with the follow-up's position, not with the rest of the document. The test now builds a 10,000- and a 1,000-block document, and after 10 warm-up keystrokes types into the same block of both in turns (the last block of the smaller one, and its middle), comparing the per-check medians: the larger document's must stay under 3 * small + 0.1 ms. A correct check measures 1.07 to 1.35 times the smaller document's; a check that reads whole containers measures about 10.7 times, and fails.

Test plan

Before

$ vitest --run --project unit --maxWorkers=16 packages/lexical-y   # on main, next to other work
 FAIL  |unit| packages/lexical-y/src/__tests__/unit/DisposeMemory.test.ts > 40 editors bound, edited and disposed in a synchronous loop hold no document
Error: Test timed out in 5000ms.
      Tests  1 failed | 7482 passed | 46 expected fail | 4 skipped (7533)

Test durations alone on main, 10 runs each (median / max, against the 5,000 ms default): DisposeMemory 2,881 / 3,738 ms; ServerScale full-state updates 2,746 / 3,247 ms; MdastTextRuns per-run nesting 2,181 / 3,274 ms; extensionFlowSubpaths first test 2,297 / 2,686 ms. FollowUpCheckCost's check alone: 2.07 to 2.65 ms per follow-up at the end, 1.47 to 1.74 ms in the middle, against its 5 ms bound.

$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/DisposeMemory.test.ts packages/lexical-y/src/__tests__/unit/ServerScale.test.ts packages/lexical-mdast/src/__tests__/unit/MdastTextRuns.test.ts   # one run alone
 ✓  unit  packages/lexical-y/src/__tests__/unit/DisposeMemory.test.ts (1 test) 1648ms
   ✓ 40 editors bound, edited and disposed in a synchronous loop hold no document 1647ms
     ✓ full-state updates resolve in linear time 1743ms
     ✓ never round-trips worse than per-run nesting, for any format mix 1363ms
$ vitest --run --project scripts-unit scripts/__tests__/unit/extensionFlowSubpaths.test.ts
     ✓ declares all exports of @lexical/extension 1487ms

The first commit's FollowUpCheckCost, in this PR's Node 24 CI unit job (its bound was 5 * 0.481 + 0.5 = 2.905 ms):

the check 2.855 ms per follow-up, 0.481 ms in 1000 blocks

After

$ vitest --run --project unit --maxWorkers=12 packages/lexical-y packages/lexical-mdast
 Test Files  173 passed | 2 skipped (175)
      Tests  7768 passed | 46 expected fail | 4 skipped (7818)

$ vitest --run --project scripts-unit scripts/__tests__/unit/extensionFlowSubpaths.test.ts
      Tests  32 passed (32)

$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/FollowUpCheckCost.test.ts
FOLLOWUP_COST end: median check 0.812 ms in 10000 blocks, 0.623 ms in 1000 blocks (1.30x)
FOLLOWUP_COST middle: median check 0.692 ms in 10000 blocks, 0.591 ms in 1000 blocks (1.17x)
      Tests  2 passed (2)

$ # with followUpRanges changed to return null (every container read whole)
FOLLOWUP_COST end: median check 743.910 ms in 10000 blocks, 69.480 ms in 1000 blocks (10.71x)
FOLLOWUP_COST middle: median check 738.341 ms in 10000 blocks, 68.840 ms in 1000 blocks (10.73x)
AssertionError: expected 743.9102489999932 to be less than 208.54115800000872
AssertionError: expected 738.3414029999985 to be less than 206.62141899996786
      Tests  2 failed (2)

$ # 10 runs alone and 10 next to a 9-worker packages/lexical-y run, one 2-worker vitest each
this branch, alone:       10/10 pass; ratio 1.07-1.33x (bound 3x + 0.1 ms)
this branch, under load:  10/10 pass; ratio 1.07-1.35x
first commit, alone:      10/10 pass; smallest margin to 5 * small + 0.5: 2.02 ms
first commit, under load: 10/10 pass; smallest margin: 2.28 ms
main, alone:              10/10 pass; check 0.825-1.475 ms against 5 ms
main, under load:         10/10 pass; check 0.832-1.579 ms
(a many-core machine: that load was mild and did not reproduce the CI job's slow 10,000-block run)

$ pnpm run ci-check
Changeset check passed: 31 pending changeset(s) valid.
Found 0 errors
(exit 0)

…s under parallel load, and measure the follow-up check's cost against a smaller document

## Description

Four tests take 2 to 4 seconds alone against vitest's default 5 second limit, so they time out when the whole suite runs in parallel: `DisposeMemory`, `ServerScale`'s linear-time test, both `MdastTextRuns` tests and the first `extensionFlowSubpaths` test (which starts the TypeScript checker). Their times and measured values are the same on `main`, on `main` of a day earlier (0cd7448) and on the first `@lexical/y` commit, so the timeouts hide no slowdown and no leak. They now have a 30 second limit, as `Headless`'s synchronous loop test already does.

`FollowUpCheckCost` asserted that the follow-up check costs under 5 ms per follow-up in a 10,000-block document. Alone it measures 2.1 to 2.7 ms, and it measured 5.06 ms with type checks running beside it. The property is that the check reads the part of the document a follow-up changed, so the test now compares, in the same process, the check in 10,000 blocks with the check in 1,000 blocks: reading the whole document would cost about ten times as much. A check that reads whole containers fails it (1,160 ms against a bound of 547 ms).

## Test plan

### Before

```
$ vitest --run --project unit --maxWorkers=16 packages/lexical-y   # on main, next to other work
 FAIL  |unit| packages/lexical-y/src/__tests__/unit/DisposeMemory.test.ts > 40 editors bound, edited and disposed in a synchronous loop hold no document
Error: Test timed out in 5000ms.
      Tests  1 failed | 7482 passed | 46 expected fail | 4 skipped (7533)
```

Test durations alone on `main`, 10 runs each (median / max, against the 5,000 ms default): `DisposeMemory` 2,881 / 3,738 ms; `ServerScale` full-state updates 2,746 / 3,247 ms; `MdastTextRuns` per-run nesting 2,181 / 3,274 ms; `extensionFlowSubpaths` first test 2,297 / 2,686 ms. `FollowUpCheckCost`'s check alone: 2.07 to 2.65 ms per follow-up at the end, 1.47 to 1.74 ms in the middle, against its 5 ms bound.

### After

```
$ vitest --run --project unit --maxWorkers=12 packages/lexical-y packages/lexical-mdast
 Test Files  173 passed | 2 skipped (175)
      Tests  7768 passed | 46 expected fail | 4 skipped (7818)

$ vitest --run --project scripts-unit scripts/__tests__/unit/extensionFlowSubpaths.test.ts
      Tests  32 passed (32)

$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/FollowUpCheckCost.test.ts
FOLLOWUP_COST end: 60 follow-ups checked; median follow-up 23.45 ms without the check, 24.91 ms with it; the check 2.187 ms per follow-up, 0.746 ms in 1000 blocks
FOLLOWUP_COST middle: 60 follow-ups checked; median follow-up 23.68 ms without the check, 25.82 ms with it; the check 1.457 ms per follow-up, 0.726 ms in 1000 blocks
      Tests  2 passed (2)

$ # with YBinding#$checkFollowUp changed to read every container whole
FOLLOWUP_COST end: 60 follow-ups checked; median follow-up 28.82 ms without the check, 1168.80 ms with it; the check 1159.723 ms per follow-up, 109.382 ms in 1000 blocks
FOLLOWUP_COST middle: 60 follow-ups checked; median follow-up 22.02 ms without the check, 1152.41 ms with it; the check 1157.358 ms per follow-up, 108.534 ms in 1000 blocks
AssertionError: expected 1159.7230917000006 to be less than 547.4100489999983
AssertionError: expected 1157.3581318499996 to be less than 543.1716224166692
      Tests  2 failed (2)

$ pnpm run ci-check
Changeset check passed: 31 pending changeset(s) valid.
Found 0 errors
(exit 0)
```
@vercel

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lexical Ready Ready Preview Oct 30, 2026 5:38pm UTC
lexical-playground Ready Ready Preview Oct 30, 2026 5:38pm UTC

Request Review

@meta-code-review

meta-code-review Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 30, 2026, 2:15 PM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 30, 2026
@pkg-pr-new

pkg-pr-new Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
lexical

npm i https://pkg.pr.new/lexical@9576

@lexical/a11y

npm i https://pkg.pr.new/@lexical/a11y@9576

@lexical/clipboard

npm i https://pkg.pr.new/@lexical/clipboard@9576

@lexical/code

npm i https://pkg.pr.new/@lexical/code@9576

@lexical/code-core

npm i https://pkg.pr.new/@lexical/code-core@9576

@lexical/code-prism

npm i https://pkg.pr.new/@lexical/code-prism@9576

@lexical/code-shiki

npm i https://pkg.pr.new/@lexical/code-shiki@9576

@lexical/compiler

npm i https://pkg.pr.new/@lexical/compiler@9576

@lexical/devtools-core

npm i https://pkg.pr.new/@lexical/devtools-core@9576

@lexical/dragon

npm i https://pkg.pr.new/@lexical/dragon@9576

@lexical/eslint-plugin

npm i https://pkg.pr.new/@lexical/eslint-plugin@9576

@lexical/extension

npm i https://pkg.pr.new/@lexical/extension@9576

@lexical/fast-check

npm i https://pkg.pr.new/@lexical/fast-check@9576

@lexical/file

npm i https://pkg.pr.new/@lexical/file@9576

@lexical/hashtag

npm i https://pkg.pr.new/@lexical/hashtag@9576

@lexical/headless

npm i https://pkg.pr.new/@lexical/headless@9576

@lexical/history

npm i https://pkg.pr.new/@lexical/history@9576

@lexical/html

npm i https://pkg.pr.new/@lexical/html@9576

@lexical/internal

npm i https://pkg.pr.new/@lexical/internal@9576

@lexical/link

npm i https://pkg.pr.new/@lexical/link@9576

@lexical/list

npm i https://pkg.pr.new/@lexical/list@9576

@lexical/mark

npm i https://pkg.pr.new/@lexical/mark@9576

@lexical/markdown

npm i https://pkg.pr.new/@lexical/markdown@9576

@lexical/mdast

npm i https://pkg.pr.new/@lexical/mdast@9576

@lexical/offset

npm i https://pkg.pr.new/@lexical/offset@9576

@lexical/overflow

npm i https://pkg.pr.new/@lexical/overflow@9576

@lexical/plain-text

npm i https://pkg.pr.new/@lexical/plain-text@9576

@lexical/react

npm i https://pkg.pr.new/@lexical/react@9576

@lexical/rich-text

npm i https://pkg.pr.new/@lexical/rich-text@9576

@lexical/selection

npm i https://pkg.pr.new/@lexical/selection@9576

@lexical/table

npm i https://pkg.pr.new/@lexical/table@9576

@lexical/tailwind

npm i https://pkg.pr.new/@lexical/tailwind@9576

@lexical/text

npm i https://pkg.pr.new/@lexical/text@9576

@lexical/utils

npm i https://pkg.pr.new/@lexical/utils@9576

@lexical/y

npm i https://pkg.pr.new/@lexical/y@9576

@lexical/yjs

npm i https://pkg.pr.new/@lexical/yjs@9576

commit: 3c3a009

@meta-code-review meta-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for tracking these down. The 30 s limits look right: describe(..., {timeout: 30_000}, ...) reaches both MdastTextRuns tests, the numeric third argument covers DisposeMemory and ServerScale, the assertions behind them are unchanged, and this follows the existing precedent in Headless.test.ts.

FollowUpCheckCost: the new bound is tighter than the one it replaces (packages/lexical-y/src/__tests__/unit/FollowUpCheckCost.test.ts:155)

  • 5 * small.checkMs + 0.5 is below the old 5 ms whenever the 1,000-block check costs under 0.9 ms, which is the usual case: 0.48 and 0.77 ms in this PR's two CI unit jobs, 0.75 ms in the run in the description. It only loosens when load also slows the 1,000-block run.
  • In this PR's Node 24 unit job the end test printed the check 2.855 ms per follow-up, 0.481 ms in 1000 blocks, so it passed against 2.905 ms by 0.05 ms; the old bound had 2.1 ms to spare there.
  • The comment at lines 34–36 says the check "costs about the same in a document ten times longer", but it still grows with the follow-up's position: followUpRanges finds the unit with units.indexOf(unit) (packages/lexical-y/src/internal/Binding.ts:6371), and $followUpDifference walks the container's items from cs.y._start in formatsAt (packages/lexical-y/src/internal/SelfCheck.ts:857-873). The 10,000/1,000 ratio was 1.9× in the Node 22 job and 5.9× in the Node 24 job, while a check reading whole containers, the regression this guards, comes out at about 10.6× in the description's own run, so a factor of 5 sits close to the passing side.
  • off, on and small run one after another and checkMs is a mean (lines 133 and 143–145), so a slowdown that hits only the 10,000-block run lands on one side: in the Node 24 job that run's median follow-up was 22.08 ms with the check against 10.79 ms without it in the run before.

Suggested fix: build both editors first, run a few warm-up keystrokes, then alternate keystrokes between the two documents and compare per-check medians, like the warm-up in HeadlessScale.test.ts and the median comparison in RepeatedIdReads.test.ts. Both sides then see the same load and JIT state, so a burst or a cold first check no longer lands on one of them. Please also update the comment to say the check walks the container up to the follow-up.

Minor: AGENTS.md asks the Test plan to paste real output rather than describe it; the 5.06 ms FollowUpCheckCost failure and the other three timeouts appear only as prose under Before.

expect(small.stored).toBe(`renamed-${KEYS - 1}`);
// The check reads a few blocks, not the 10,000: ten times the blocks
// would cost about ten times as much.
expect(on.checkMs).toBeLessThan(5 * small.checkMs + 0.5);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This sits below the old < 5 whenever small.checkMs is under 0.9 ms, which is the usual case, and in this PR's Node 24 CI job it passed by 0.05 ms (2.855 against 5 * 0.481 + 0.5 = 2.905). Measuring the two documents interleaved after a warm-up and comparing per-check medians would give the factor headroom on both sides; see the review comment.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 0a8b7e9: both documents are built first, then after 10 warm-up keystrokes each keystroke goes to the same block of both, in turns, and the per-check medians are compared (3 * small + 0.1 ms). Measuring at the same block keeps the position-dependent walk equal on both sides, so a correct check measures 1.07 to 1.35 times the smaller document (20 runs alone and under load), while a check that reads every container whole (followUpRanges returning null) measures 10.7 times and fails. Numbers are in the description.

Comment on lines +34 to +36
// document. So the check costs about the same in a document ten times
// longer; measured in the same process, the comparison holds on a loaded
// machine, where an absolute bound in milliseconds does not.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The check isn't size-independent: followUpRanges uses units.indexOf(unit) (internal/Binding.ts:6371) and formatsAt walks the container's items from cs.y._start (internal/SelfCheck.ts:857-873), so its cost grows with the follow-up's position (1.9× on Node 22 and 5.9× on Node 24 in this PR's CI). Worth saying so here, since the bound's factor depends on it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 0a8b7e9: the comment now says the check walks the container up to the follow-up (followUpRanges uses units.indexOf, formatsAt walks from the container start), so its cost grows with the follow-up position, which is why both documents are measured at the same block.

…oth documents, in turns, by medians

## Description

`FollowUpCheckCost` compared the follow-up check's mean cost at the end (or middle) of a 10,000-block document with its cost at the end of a 1,000-block one, each measured in its own run, against `5 * small + 0.5`. That bound was tighter than the old 5 ms whenever the 1,000-block check took under 0.9 ms (the usual case), and it could not have headroom on both sides: the check walks the container up to the follow-up (`followUpRanges` finds its unit with `units.indexOf`, and `formatsAt` walks the container's items from its start), so its cost grows with the follow-up's position, by 1.9 to 5.9 times between those two positions in CI, close to the about 10 times a check reading whole containers costs.

Now both documents are built first, then, after 10 warm-up keystrokes, each keystroke (and its naming follow-up) goes to the same block of both, in turns: the last block of the 1,000-block document, and its middle. The per-check medians are compared: the 10,000-block one must stay under `3 * small + 0.1` ms. A correct check measures 1.07 to 1.35 times the smaller document's (only the rest of the document differs); one that reads whole containers measures about 10.7 times. The comment says what the check walks and why its cost grows with position.

## Test plan

```
pnpm exec vitest run --project unit packages/lexical-y/src/__tests__/unit/FollowUpCheckCost.test.ts
```

### Before

This PR's first commit, in its Node 24 CI unit job (bound `5 * 0.481 + 0.5 = 2.905` ms):

```
the check 2.855 ms per follow-up, 0.481 ms in 1000 blocks
```

The four slow tests alone (on this branch, with the same bodies as on main):

```
 ✓  unit  packages/lexical-y/src/__tests__/unit/DisposeMemory.test.ts (1 test) 1648ms
   ✓ 40 editors bound, edited and disposed in a synchronous loop hold no document 1647ms
     ✓ full-state updates resolve in linear time 1743ms
     ✓ never round-trips worse than per-run nesting, for any format mix 1363ms
     ✓ declares all exports of @lexical/extension 1487ms
```

### After

```
FOLLOWUP_COST end: median check 0.812 ms in 10000 blocks, 0.623 ms in 1000 blocks (1.30x)
FOLLOWUP_COST middle: median check 0.692 ms in 10000 blocks, 0.591 ms in 1000 blocks (1.17x)
      Tests  2 passed (2)
```

With `followUpRanges` changed to return null (every container read whole):

```
FOLLOWUP_COST end: median check 743.910 ms in 10000 blocks, 69.480 ms in 1000 blocks (10.71x)
FOLLOWUP_COST middle: median check 738.341 ms in 10000 blocks, 68.840 ms in 1000 blocks (10.73x)
AssertionError: expected 743.9102489999932 to be less than 208.54115800000872
AssertionError: expected 738.3414029999985 to be less than 206.62141899996786
      Tests  2 failed (2)
```

10 runs alone and 10 next to a 9-worker `packages/lexical-y` run, on one 2-worker vitest each:

```
this commit, alone:      10/10 pass; ratio 1.07-1.33x (bound 3x + 0.1 ms)
this commit, under load: 10/10 pass; ratio 1.07-1.35x
first commit, alone:      10/10 pass; smallest margin to 5 * small + 0.5: 2.02 ms
first commit, under load: 10/10 pass; smallest margin: 2.28 ms
main, alone:      10/10 pass; check 0.825-1.475 ms against 5 ms
main, under load: 10/10 pass; check 0.832-1.579 ms
```

(The machine has many cores, so that load was mild: it did not reproduce the CI job's slow 10,000-block run.)

@meta-code-review meta-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks, this looks good to me. The 30 s limits reach the tests they name (Vitest applies the describe-level {timeout: 30_000} to both MdastTextRuns tests), the assertions behind them are unchanged, and the warmed-up, interleaved median comparison in FollowUpCheckCost still fails a check that reads whole containers, at about 10.6x as the description reports.

One optional, non-blocking suggestion:

  • FollowUpCheckCost no longer guards cost that grows with the follow-up's position (packages/lexical-y/src/__tests__/unit/FollowUpCheckCost.test.ts:110-159). Both documents are typed at the same block, so a cost proportional to the position cancels out of b < 3 * m + 0.1. For example, starting every range at unit 0 in followUpRanges (found.push([0, j]) in packages/lexical-y/src/internal/Binding.ts) makes the check read every block before the follow-up: about 72 ms per follow-up at block 999 instead of under 1 ms, and both tests still pass at 1.00x, while the previous version of this test failed it (about 763 ms against 5 ms at block 9,999). A generous absolute ceiling for a third interleaved series at the last block of the 10,000-block document (say 50 ms, against the 2 to 3 ms the description measures there) would keep that coverage without bringing back the flakiness.

expect(on.checkMs).toBeLessThan(5);
// About the same in both; a check reading the whole container would
// cost about ten times as much in the larger document.
expect(b).toBeLessThan(3 * m + 0.1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional, non-blocking: because both documents are typed at the same block, any cost that grows with the follow-up's position is equal on both sides and cancels out here. Starting every range at unit 0 in followUpRanges (found.push([0, j])) makes the check cost about 72 ms per follow-up at block 999 instead of under 1 ms, and this assertion still passes at 1.00x; the previous absolute bound at block 9,999 failed it at about 763 ms. A generous ceiling for a series typed at the larger document's last block (say 50 ms, against the 2 to 3 ms the description measures there) would keep that coverage without the flakiness.

…ument's last block, against an absolute ceiling

## Description

Comparing the follow-up check at the same block of a 10,000- and a 1,000-block document cancels any cost that grows with the follow-up's position: a check that starts every range it reads at the container's first unit costs about 70 ms per follow-up at block 999 of both documents, and the ratio stays about 1. The test now also types at the last block of the 10,000-block document, in the same turns after the warm-up, and requires the median check there to stay under 20 ms (it measures about 1 ms). Each series renames its own block (`renamed-k`, `end-k`), so the block-id repair never sees two blocks with one id.

## Test plan

```
pnpm exec vitest run --project unit packages/lexical-y/src/__tests__/unit/FollowUpCheckCost.test.ts
```

### Before

With `followUpRanges` changed to start every range at unit 0, the first commit's test passes:

```
FOLLOWUP_COST end: median check 68.201 ms in 10000 blocks, 67.919 ms in 1000 blocks (1.00x)
FOLLOWUP_COST middle: median check 34.854 ms in 10000 blocks, 34.061 ms in 1000 blocks (1.02x)
```

This commit's test, with the same change:

```
FOLLOWUP_COST end: median check 68.201 ms in 10000 blocks, 67.919 ms in 1000 blocks (1.00x); 742.212 ms at block 9999
FOLLOWUP_COST middle: median check 34.854 ms in 10000 blocks, 34.061 ms in 1000 blocks (1.02x); 759.364 ms at block 9999
AssertionError: expected 742.2124700000022 to be less than 20
AssertionError: expected 759.3642030000046 to be less than 20
      Tests  2 failed (2)
```

### After

Three runs:

```
FOLLOWUP_COST end: median check 0.799 ms in 10000 blocks, 0.604 ms in 1000 blocks (1.32x); 1.004 ms at block 9999
FOLLOWUP_COST middle: median check 0.694 ms in 10000 blocks, 0.587 ms in 1000 blocks (1.18x); 0.896 ms at block 9999
      Tests  2 passed (2)
FOLLOWUP_COST end: median check 0.772 ms in 10000 blocks, 0.558 ms in 1000 blocks (1.38x); 0.952 ms at block 9999
FOLLOWUP_COST middle: median check 0.615 ms in 10000 blocks, 0.544 ms in 1000 blocks (1.13x); 0.921 ms at block 9999
      Tests  2 passed (2)
FOLLOWUP_COST end: median check 0.820 ms in 10000 blocks, 0.631 ms in 1000 blocks (1.30x); 1.001 ms at block 9999
FOLLOWUP_COST middle: median check 0.733 ms in 10000 blocks, 0.622 ms in 1000 blocks (1.18x); 1.224 ms at block 9999
      Tests  2 passed (2)
```

`pnpm run tsc`, `tsc-test`, `flow` (`Found 0 errors`), `prettier` (no files listed), `check-changesets` and `lint` pass, run one at a time.

This branch was successfully deployed

2 active deployments
Preview – lexical — 3c3a0098 Deployed Oct 30, 2026 by vercel[bot]
Preview – lexical-playground — 3c3a0098 Deployed Oct 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant