Skip to content

[lexical-y] Bug Fix: An undo that ends a restored run links its null to the items the store holds - #9696

Merged
zurfyx merged 4 commits into
mainfrom
zurfyx/y-undo-run-end-links
Nov 1, 2026
Merged

zurfyx merged 4 commits into
mainfrom
zurfyx/y-undo-run-end-links

Conversation

@zurfyx

@zurfyx zurfyx commented Nov 1, 2026

Copy link
Copy Markdown
Member

Description

Stacked on #9693 (it extends that PR's structure check).

An undo that brings back a run gives it an end of this client's own where a
closer ended it (closeRestoredRuns). It found where in one transaction and
wrote the null in a second one, keeping the item objects it had found. The
first transaction's scan splits items and its cleanup merges them back, so
the item to the null's left could be a piece no longer in the store. The
null was linked to that piece: the merged item in the store kept its old
right, so a walk of the list skipped the null. Only the undoing client's
document is affected (others integrate the null from its origin), and its
editor can stop matching its document: every pre-existing editor-vs-document
divergence a downstream long-session fuzz found starts with such a link.

Now:

  • closeRestoredRuns keeps ids, not items, across the two transactions, and
    resolves them in the store when it writes (splitting there as Yjs does);
    it writes nothing where the two are no longer next to each other.
  • The running undo's own items (isKeptByUndo) are looked up by id in the
    step's insertions instead of by the item objects an earlier transaction
    saw.
  • The structure check (misLinkedItems, run by expectConverged, the undo
    fuzz and the structure fuzz) also requires each neighbour to link back
    and to have the same map key.

Audit of other item references kept across a transaction: the format-closer
marks and the kept format items are format items, which Yjs never splits or
merges; the stored nodes an undo removes are nested types, likewise. Nothing
else outlives its transaction.

Test plan

Before

$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/UndoRunEndLinks.test.ts
AssertionError: after step 7: expected [ '795599280632996:0' ] to deeply equal []
AssertionError: after step 7: expected [ '2159088886642643:0' ] to deeply equal []
      Tests  2 failed (2)

After

$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/UndoRunEndLinks.test.ts
      Tests  2 passed (2)
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y
 Test Files  241 passed | 2 skipped (243)
      Tests  19231 passed | 304 expected fail | 130 skipped (19665)
$ LEXICAL_Y_GC=1 vitest --run --project unit packages/lexical-y
 Test Files  241 passed | 2 skipped (243)
      Tests  19228 passed | 307 expected fail | 130 skipped (19665)
$ pnpm run ci-check
exit 0

@vercel

vercel Bot commented Nov 1, 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 Nov 1, 2026 5:16am UTC
lexical-playground Ready Ready Preview Nov 1, 2026 5:16am UTC

Request Review

@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 Nov 1, 2026
@meta-code-review

meta-code-review Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 1, 2026, 5:32 AM UTC

@pkg-pr-new

pkg-pr-new Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
lexical

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

@lexical/a11y

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

@lexical/clipboard

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

@lexical/code

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

@lexical/code-core

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

@lexical/code-prism

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

@lexical/code-shiki

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

@lexical/compiler

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

@lexical/devtools-core

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

@lexical/dragon

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

@lexical/eslint-plugin

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

@lexical/extension

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

@lexical/fast-check

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

@lexical/file

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

@lexical/hashtag

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

@lexical/headless

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

@lexical/history

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

@lexical/html

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

@lexical/internal

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

@lexical/link

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

@lexical/list

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

@lexical/mark

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

@lexical/markdown

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

@lexical/mdast

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

@lexical/offset

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

@lexical/overflow

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

@lexical/plain-text

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

@lexical/react

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

@lexical/rich-text

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

@lexical/selection

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

@lexical/table

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

@lexical/tailwind

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

@lexical/text

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

@lexical/utils

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

@lexical/y

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

@lexical/yjs

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

commit: d7773fc

zurfyx added a commit that referenced this pull request Nov 1, 2026
…does not repeat what it stored

## Description

Stacked on #9696.

When a local write throws part way, the binding rewrites the document from
the editor and reports `@lexical/y/recovered-write`. That rewrite wrote a
changed line through the keystroke fast path (`$writeTextEdit`), which
writes the text edits Lexical recorded against the previous editor state.
Where the failed write had already stored such an edit, the rewrite stored
it again: the other client showed the inserted text twice, and the
author's editor no longer matched its own document. The rewrite now skips
the fast path and diffs each line against the document. The failure is not
reachable through a known path; it is measured by injecting a throw (the
second item a cursor writes) and affects every tree, including the commit
before #9679.

## Test plan

### Before

```
$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/RecoveredWriteRepeats.test.ts
AssertionError: expected 'paragraph("one INSERT INSERT alpha be…' to be 'paragraph("one INSERT alpha beta") / …' // Object.is equality
      Tests  2 failed (2)
```

### After

```
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/RecoveredWriteRepeats.test.ts
      Tests  2 passed (2)
$ LEXICAL_Y_GC=1 (same file)
      Tests  2 passed (2)
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 241 passed | 2 skipped (244)
      Tests  1 failed | 19232 passed | 304 expected fail | 130 skipped (19667)
$ LEXICAL_Y_GC=1 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 241 passed | 2 skipped (244)
      Tests  1 failed | 19229 passed | 307 expected fail | 130 skipped (19667)
$ pnpm run ci-check
exit 0
```

The one failure in each full run is a memory-loop test (Headless gc off,
DisposeMemory gc on) that exceeded its heap bound under load from other
runs on the machine; both pass alone.
zurfyx added a commit that referenced this pull request Nov 1, 2026
…does not repeat what it stored

## Description

Stacked on #9696.

When a local write throws part way, the binding rewrites the document from
the editor and reports `@lexical/y/recovered-write`. That rewrite wrote a
changed line through the keystroke fast path (`$writeTextEdit`), which
writes the text edits Lexical recorded against the previous editor state.
Where the failed write had already stored such an edit, the rewrite stored
it again: the other client showed the inserted text twice, and the
author's editor no longer matched its own document. The rewrite now skips
the fast path and diffs each line against the document. The failure is not
reachable through a known path; it is measured by injecting a throw (the
second item a cursor writes) and affects every tree, including the commit
before #9679.

## Test plan

### Before

```
$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/RecoveredWriteRepeats.test.ts
AssertionError: expected 'paragraph("one INSERT INSERT alpha be…' to be 'paragraph("one INSERT alpha beta") / …' // Object.is equality
      Tests  2 failed (2)
```

### After

```
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/RecoveredWriteRepeats.test.ts
      Tests  2 passed (2)
$ LEXICAL_Y_GC=1 (same file)
      Tests  2 passed (2)
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 241 passed | 2 skipped (244)
      Tests  1 failed | 19232 passed | 304 expected fail | 130 skipped (19667)
$ LEXICAL_Y_GC=1 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 241 passed | 2 skipped (244)
      Tests  1 failed | 19229 passed | 307 expected fail | 130 skipped (19667)
$ pnpm run ci-check
exit 0
```

The one failure in each full run is a memory-loop test (Headless gc off,
DisposeMemory gc on) that exceeded its heap bound under load from other
runs on the machine; both pass alone.
@zurfyx
zurfyx changed the base branch from zurfyx/y-run-end-own-container to main November 1, 2026 05:14
…to the items the store holds

## Description

Stacked on #9693 (it extends that PR's structure check).

An undo that brings back a run gives it an end of this client's own where a
closer ended it (`closeRestoredRuns`). It found where in one transaction and
wrote the null in a second one, keeping the item objects it had found. The
first transaction's scan splits items and its cleanup merges them back, so
the item to the null's left could be a piece no longer in the store. The
null was linked to that piece: the merged item in the store kept its old
right, so a walk of the list skipped the null. Only the undoing client's
document is affected (others integrate the null from its origin), and its
editor can stop matching its document: every pre-existing editor-vs-document
divergence a downstream long-session fuzz found starts with such a link.

Now:

- `closeRestoredRuns` keeps ids, not items, across the two transactions, and
  resolves them in the store when it writes (splitting there as Yjs does);
  it writes nothing where the two are no longer next to each other.
- The running undo's own items (`isKeptByUndo`) are looked up by id in the
  step's insertions instead of by the item objects an earlier transaction
  saw.
- The structure check (`misLinkedItems`, run by `expectConverged`, the undo
  fuzz and the structure fuzz) also requires each neighbour to link back
  and to have the same map key.

Audit of other item references kept across a transaction: the format-closer
marks and the kept format items are format items, which Yjs never splits or
merges; the stored nodes an undo removes are nested types, likewise. Nothing
else outlives its transaction.

## Test plan

### Before

```
$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/UndoRunEndLinks.test.ts
AssertionError: after step 7: expected [ '795599280632996:0' ] to deeply equal []
AssertionError: after step 7: expected [ '2159088886642643:0' ] to deeply equal []
      Tests  2 failed (2)
```

### After

```
$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/UndoRunEndLinks.test.ts
      Tests  2 passed (2)
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y
 Test Files  241 passed | 2 skipped (243)
      Tests  19231 passed | 304 expected fail | 130 skipped (19665)
$ LEXICAL_Y_GC=1 vitest --run --project unit packages/lexical-y
 Test Files  241 passed | 2 skipped (243)
      Tests  19228 passed | 307 expected fail | 130 skipped (19665)
$ pnpm run ci-check
exit 0
```
…o recover from

## Description

The harness's `expectConverged` accepted a recovered-write warning (a local change that could not be written, rewritten from the editor). A fix that only refused a bad write and let the recovery repair it would pass every converging test. It now fails on that warning, as it does on a self-check warning; no existing test converges after a recovery. Tests only.

## Test plan

### Before

A recovered write passed `expectConverged`.

### After

```
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 240 passed | 2 skipped (243)
      Tests  1 failed | 19230 passed | 304 expected fail | 130 skipped (19665)
$ LEXICAL_Y_GC=1 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 240 passed | 2 skipped (243)
      Tests  1 failed | 19227 passed | 307 expected fail | 130 skipped (19665)
$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/Headless.test.ts packages/lexical-y/src/__tests__/unit/DisposeMemory.test.ts   # gc off, then on
      Tests  33 passed (33)
      Tests  33 passed (33)
```

The one failure in each full run is a memory-loop test (Headless gc off, DisposeMemory gc on) that exceeded its heap bound with 30 workers and other load on the machine; both pass alone.
…D96 on what other clients hold

## Description

Review follow-ups to #9693, tests and docs only:

- `StructureFuzz.test.ts` records the editors' warnings and expects none after each step. With `checkLinkable`, a regression of the run-end rule is refused and recovered from instead of stored, so the structure check alone stays empty and the warning only surfaced as an unhandled error; now the failing seeds are named.
- D96, `Cursor.ts` (checkLinkable's comment) and `CrossContainerEnd.test.ts`: other clients take the stray item's parent from its origin and put it in the root's list, where it changes nothing; only the writer's document holds it wrong.

## Test plan

### Before

With the run-end rule's same-type filter removed, the fuzz reported 200 passed plus unhandled errors.

### After

```
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y/src/__tests__/unit/StructureFuzz.test.ts
      Tests  200 passed (200)
$ LEXICAL_Y_GC=1 (same file)
      Tests  200 passed (200)
$ (same-type filter in changeEnd removed) LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y/src/__tests__/unit/StructureFuzz.test.ts
      Tests  9 failed | 191 passed (200)
```
…ach of them

## Description

Review follow-up to this PR's run-end change: `closeRestoredRuns` resolves
each saved gap again before writing a null, and skipped the gap when its
two items were no longer next to each other. Where two keys need an end in
the same gap, the second key found the first key's new null between them
and skipped its own: A bolds and italicizes "word"; B removes both in one
step; A's bold end is gone while its italic end stays; B undoes; once A's
italic end goes too, italic ran on over " plain tail". The check now passes
over the nulls this loop wrote, and each null goes right before the gap's
right item, after those. No end is deduplicated by place, and none is
assigned to a write.

## Test plan

### Before

```
$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/UndoRunEndSharedGap.test.ts
      Tests  2 failed (2)
```

### After

```
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/{UndoRunEndSharedGap,UndoRunEndLinks}.test.ts
      Tests  4 passed (4)
$ LEXICAL_Y_GC=1 (same files)
      Tests  4 passed (4)
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 241 passed | 2 skipped (244)
      Tests  1 failed | 19232 passed | 304 expected fail | 130 skipped (19667)
$ LEXICAL_Y_GC=1 vitest --run --project unit packages/lexical-y
 Test Files  242 passed | 2 skipped (244)
      Tests  19230 passed | 307 expected fail | 130 skipped (19667)
$ tsc --noEmit
exit 0
```

The gc-off failure is `DisposeMemory.test.ts`'s heap bound under load from
other runs on the machine; it passes alone.

@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.

The change looks good. In closeRestoredRuns (packages/lexical-y/src/internal/Binding.ts, lines 5663–5789), the saved ids are now resolved in the store at write time, so the run-end null links to the item the store holds rather than to a split piece that the scan transaction's cleanup merged away. UndoRunEndLinks.test.ts fails at the undo step on the base implementation and passes here, with gc off and on. For a single end, the item written has the same origin, right origin and content as before, so other clients receive the same update.

Two optional, non-blocking suggestions:

  1. Pin the visible symptom in the regression test (packages/lexical-y/src/__tests__/unit/regressions/UndoRunEndLinks.test.ts, OPS at lines 63–72 and the assertion at line 182). The test checks the broken link only. Add two steps after the undo, {e: 0, k: 'undo'} twice (A undoes its bold, then its two comments). On the base, B's editor then shows comment c2 over the four paragraphs after the restored run, while A's editor, and a fresh read of B's own document, show it only on Xe 1. Asserting after those steps that the two editors' JSON match, or that each matches a fresh reader of its own document, keeps the test tied to what users see even if misLinkedItems changes. packages/lexical-y/AGENTS.md asks every bug fix to land with a regression test in src/__tests__/unit/regressions/; this would make that test cover the visible symptom too.
  2. The comment on undoing (Binding.ts, lines 5828–5829). isKeptByUndo only looks up format items, and Yjs never splits or merges those, as the PR description's audit notes. So top.inserts.hasId(it.id) gives the same answer as the previous Set<Item>. Using the IdSet is a fine simplification, but the reason the comment gives does not apply to these items. You could say the two are equivalent for format items instead.

Comment on lines +5828 to +5829
// By id: this scan's transaction splits items and merges them back, so
// the item objects it saw need not be those the undo meets.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit (optional): isKeptByUndo only looks up format items here, and Yjs never splits or merges those, so hasId(it.id) gives the same answer as the previous Set<Item>. The switch is fine, but this reason does not apply to those items. Maybe say the two are equivalent for format items instead.

}
sync();
for (const peer of peers) {
expect(misLinkedItems(peer.doc), `after step ${i}`).toEqual([]);

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: this checks the broken link only. If you append {e: 0, k: 'undo'} twice to OPS, then on the base B's editor shows comment c2 over the four paragraphs after the restored run, while A's editor shows it only on Xe 1. Asserting the two editors' JSON match after those steps would pin the user-visible symptom as well.

@zurfyx
zurfyx force-pushed the zurfyx/y-undo-run-end-links branch from 896ff92 to d7773fc Compare November 1, 2026 05:15
zurfyx added a commit that referenced this pull request Nov 1, 2026
…does not repeat what it stored

## Description

Stacked on #9696.

When a local write throws part way, the binding rewrites the document from
the editor and reports `@lexical/y/recovered-write`. That rewrite wrote a
changed line through the keystroke fast path (`$writeTextEdit`), which
writes the text edits Lexical recorded against the previous editor state.
Where the failed write had already stored such an edit, the rewrite stored
it again: the other client showed the inserted text twice, and the
author's editor no longer matched its own document. The rewrite now skips
the fast path and diffs each line against the document. The failure is not
reachable through a known path; it is measured by injecting a throw (the
second item a cursor writes) and affects every tree, including the commit
before #9679.

## Test plan

### Before

```
$ vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/RecoveredWriteRepeats.test.ts
AssertionError: expected 'paragraph("one INSERT INSERT alpha be…' to be 'paragraph("one INSERT alpha beta") / …' // Object.is equality
      Tests  2 failed (2)
```

### After

```
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y/src/__tests__/unit/regressions/RecoveredWriteRepeats.test.ts
      Tests  2 passed (2)
$ LEXICAL_Y_GC=1 (same file)
      Tests  2 passed (2)
$ LEXICAL_Y_GC=0 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 241 passed | 2 skipped (244)
      Tests  1 failed | 19232 passed | 304 expected fail | 130 skipped (19667)
$ LEXICAL_Y_GC=1 vitest --run --project unit packages/lexical-y
 Test Files  1 failed | 241 passed | 2 skipped (244)
      Tests  1 failed | 19229 passed | 307 expected fail | 130 skipped (19667)
$ pnpm run ci-check
exit 0
```

The one failure in each full run is a memory-loop test (Headless gc off,
DisposeMemory gc on) that exceeded its heap bound under load from other
runs on the machine; both pass alone.

@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. The id-based write in closeRestoredRuns fixes the broken link that UndoRunEndLinks.test.ts reproduces: the test fails on main at the undo step, with gc on and off, and passes here. UndoRunEndSharedGap.test.ts covers the new handling of two ends in one gap.

Two optional, non-blocking suggestions:

  1. Make the skipped gaps visible (packages/lexical-y/src/internal/Binding.ts, lines 5742-5772). The continue branches that skip a gap when its two items are no longer adjacent cannot fire today: the scan and the write run back to back, and the only changes between them are merges the recorded ids survive and the nulls this loop writes itself. If a later change does make one fire, the undo writes no end and the restored value runs on over the rest of the document with no signal. A dev-only warning on those branches would make that show up in the fuzzes.
  2. Verify: wording (.changeset/y-undo-run-end-links.md, line 7). CONTRIBUTING.md asks this paragraph to say what to test, but its last sentence is a structural check that people using @lexical/y cannot observe. A user-side check fits better, for example: after the undo, the undoing client's editor shows what a fresh load of its document shows.

Comment on lines +5759 to +5761
if (l !== left) {
continue;
}

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: this skip (and the ones at 5742-5749 and 5770-5772) cannot fire today: the scan and the write run back to back, and the only changes between them are merges the recorded ids survive and the nulls this loop writes itself. If a later change does make one fire, the undo writes no end and the restored value runs on with no signal. A dev-only warning here would make that show up in the fuzzes.


[fix] An undo that ends a restored run keeps the document's item list linked: the client that undid no longer holds an item a walk of its list skips, which could make its editor stop matching its document

Verify: two clients synced after every step; one deletes two characters of a line, the other comments the end of one line and part of another and bolds part of a third, the first types " new " and deletes part of it, removes the first comment and undoes that. Every item's neighbours link back to it on both clients.

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 nit: CONTRIBUTING.md asks Verify: to say what to test, but the last sentence is a structural check users cannot observe. Something like "after the undo, the undoing client's editor shows what a fresh load of its document shows" fits better.

@zurfyx
zurfyx added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 95cc4ad Nov 1, 2026
57 checks passed

This branch was successfully deployed

2 active deployments
Preview – lexical — d7773fcd Deployed Nov 1, 2026 by vercel[bot]
Preview – lexical-playground — d7773fcd Deployed Nov 1, 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