chore(frontend): upgrade Lexical 0.40 → 0.46#4922
Conversation
Bump lexical core + all @lexical/* packages from ^0.40.0 to ^0.46.0
across oss, ee, and the @agenta/{ui,entities,entity-ui} packages
(plus the root @lexical/eslint-plugin).
The only code change required across the editor (10 custom nodes, 12
extensions, plugins, markdown transformers) is one mechanical fix for
the 0.46 removal of unsafe type parameters on node-traversal methods:
TableCellResizerPlugin used tableRow.getChildren<TableCellNode>(), now
getChildren() as TableCellNode[].
Editing across a fenced code block with mixed { / {{ braces while
{{tokens}} exist could crash the editor with React "Maximum update
depth exceeded". Pre-existing (reproduces on 0.40), surfaced during
lexical upgrade testing.
Root cause: TokenTypeaheadPlugin called React setState (setAnchor/
setInputQuery) synchronously inside a Lexical registerUpdateListener. A
burst of commits drove setState → re-render → commit past React's depth
limit. A contributing factor was TokenPlugin.$transformNode scheduling
a nested editor.update() from inside a node transform to reposition the
caret, spawning extra commits.
Fix:
- TokenTypeaheadPlugin: compute the anchor synchronously but apply React
state in a coalesced queueMicrotask, identity-stable, never inside the
commit — no commit burst can exceed React's update-depth limit.
- TokenPlugin: reposition the caret synchronously via navigateCursor
instead of a nested editor.update() (behaviorally identical).
Verified: crash recipe + heavy stress no longer crash; auto-close and
manual-close caret behavior unchanged.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAll ChangesLexical 0.46 Upgrade
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f318c75c-11c1-4cd3-8133-ab21eefe776f
⛔ Files ignored due to path filters (1)
web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
web/ee/package.jsonweb/oss/package.jsonweb/package.jsonweb/packages/agenta-entities/package.jsonweb/packages/agenta-entity-ui/package.jsonweb/packages/agenta-ui/package.jsonweb/packages/agenta-ui/src/Editor/plugins/markdown/TableCellResizerPlugin.tsxweb/packages/agenta-ui/src/Editor/plugins/token/TokenPlugin.tsxweb/packages/agenta-ui/src/Editor/plugins/token/TokenTypeaheadPlugin.tsx
…nnel Addresses a review finding: after selecting a suggestion, the deferred microtask could re-anchor and reopen the menu right after selectOption closed it (the listener re-anchors on the caret position selectOption sets, and the deferred flush ran after the synchronous setAnchor(null)). Unify all anchor/query writers (update listener, selection, escape, click-outside) behind a single scheduleTypeahead → flushTypeahead channel. The last write before the microtask wins, so selectOption's close — which runs after the listener — wins by construction. Keeps the React update out of the synchronous Lexical commit (crash guard) and removes the need for ad-hoc suppression flags.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 413b1ac1-433c-4a62-93ba-5978444e5f49
📒 Files selected for processing (1)
web/packages/agenta-ui/src/Editor/plugins/token/TokenTypeaheadPlugin.tsx
The lifted flushTypeahead used a component-scoped mountedRef (init true, set false on unmount) that was never reset in effect setup. Under React StrictMode's mount → cleanup → remount cycle it stayed false, so the flush bailed permanently in dev and the typeahead menu could stop opening. On React 19 a useState setter after unmount is a harmless no-op, so the guard was unnecessary machinery — removed it rather than patching the reset, eliminating the StrictMode footgun.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 71eb4c64-31b5-48f8-a1fe-e9854a911001
📒 Files selected for processing (1)
web/packages/agenta-ui/src/Editor/plugins/token/TokenTypeaheadPlugin.tsx
| // No mounted-guard needed: on React 19 a useState setter after unmount is a | ||
| // harmless no-op, and the microtask is one-shot. A mounted ref here would | ||
| // also have to be reset in effect setup to survive StrictMode's remount. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify the React versions declared across web package manifests.
rg -n '"react"\s*:\s*"[^"]+"|"react-dom"\s*:\s*"[^"]+"' web -g package.jsonRepository: Agenta-AI/agenta
Length of output: 1128
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== web/packages/agenta-ui/package.json =="
sed -n '1,140p' web/packages/agenta-ui/package.json
echo
echo "== workspace react declarations =="
rg -n '"react"\s*:\s*"[^"]+"|"react-dom"\s*:\s*"[^"]+"' web -g package.jsonRepository: Agenta-AI/agenta
Length of output: 5484
Use a version-neutral explanation here. This package supports React >=18.0.0, so the note shouldn’t hinge on “React 19”; keep the StrictMode/microtask rationale, but drop the version-specific claim.
@agenta/ui supports React >=18.0.0, so the rationale shouldn't pin to React 19. setState-after-unmount is a no-op on the supported range; keep the StrictMode/microtask reasoning without the version-specific claim.
What
Upgrades Lexical from
^0.40.0to^0.46.0(core + all@lexical/*packages) across
oss,ee, and@agenta/{ui,entities,entity-ui}, andfixes a pre-existing token-typeahead crash uncovered while testing it.
Two commits, independently reviewable:
chore: the upgrade — version bumps + lockfile + one mechanical code fix.fix: token typeahead infinite-loop — pre-existing bug (reproduces on 0.40), bundled because it surfaced during upgrade QA.The upgrade (0.41 → 0.46 reviewed)
The editor is not vanilla Lexical — it's built on the experimental
extension system (
defineExtension/configExtension/LexicalExtensionComposer),with 10 custom nodes, 12 extensions, custom markdown transformers, and a
token system. Every breaking change across 0.41–0.46 was mapped to our usage.
Only one code change was required:
tableRow.getChildren<TableCellNode>()→getChildren() as TableCellNode[](0.46 removed unsafe type params on node-traversal methods).
Confirmed not affecting us (evidenced): Prism extraction (0.42 — we use
@lexical/code-shiki+ our own prism tokenizer), shiki externalization(0.43 —
shikialready a direct dep),OffsetView/$createChildrenArray(0.44),
toggleLink/insertList/removeListremovals (0.46), and the$getNearestNodeFromDOMNoderoot-return change (we only pass cell elements).Verification
@agenta/ui,@agenta/entity-ui,@agenta/entities— tsc + lint clean.osstsc — zero new Lexical-related errors.code, tokens + variable propagation, table cell/row resize — all correct.
The bundled fix (token typeahead loop)
Editing across a fenced code block with mixed
{/{{braces while{{tokens}}exist could crash the editor (React "Maximum update depthexceeded"). Pre-existing — reproduces identically on 0.40, so it does not
block the upgrade, but it's a real crash worth fixing.
TokenTypeaheadPlugincalled ReactsetStatesynchronouslyinside a Lexical
registerUpdateListener; a burst of commits drovesetState → re-render → commit past React's depth limit.
TokenPlugin'snode transform also scheduled a nested
editor.update()to move the caret.queueMicrotask(never synchronously inside the commit), and reposition the caret
synchronously via
navigateCursorinstead of a nested update.Verification
The previously-deterministic crash recipe and heavy-stress variants no longer
crash; caret behavior is unchanged (auto-close
{{+name keeps caret inside;manual
{{name}}+text lands after the token).Test plan