[lexical-history] Feature: Add canUndo and canRedo ReadonlySignals to HistoryExtension output#8465
Merged
etrepum merged 9 commits intoMay 7, 2026
Conversation
Adds `canUndo: ReadonlySignal<boolean>` and `canRedo: ReadonlySignal<boolean>` to `HistoryExtension`'s output, kept in sync via `CAN_UNDO_COMMAND` and `CAN_REDO_COMMAND` listeners. Signals reset to `false` when the extension is disabled. https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
- Convert HistoryExtensionOutput from type alias to exported interface with JSDoc on every field - Add internal HistoryExtensionInit interface; writable Signal<boolean> pairs are created in the new `init` phase (one pair per editor, no singleton mutation) and accessed by both `build` and `register` via getInitResult() - `build` exposes them as ReadonlySignal<boolean> via computed() — no `as` casts needed anywhere - `register` reads canUndo/canRedo from historyState stacks directly (undoStack.length > 0 / redoStack.length > 0) on CAN_UNDO_COMMAND / CAN_REDO_COMMAND; the commands are still used as efficient change triggers but the signal value is derived from the authoritative HistoryState source - Import computed and signal from @lexical/extension - Update Flow types (LexicalHistory.js.flow) to reflect the new output interface and import ReadonlySignal / Signal - Add six unit tests covering initial state, edit, undo, redo, clear, and new-edit-clears-redo scenarios https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
…ests When historyState is reassigned (e.g. SharedHistoryExtension inheriting the parent's state) the effect re-runs but previously no command was dispatched, leaving canUndo/canRedo stale. Fix: read undoStack/redoStack lengths at the start of the effect so signals are always in sync with whatever historyState is currently assigned. Add three tests for the pre-populated case: - Initialised with a non-empty undoStack via createInitialHistoryState - Initialised with a non-empty redoStack via createInitialHistoryState - historyState signal reassigned at runtime to a populated HistoryState https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- HistoryExtensionOutput: put canRedo before canUndo (sort-keys-fix rule)
- Flow type: same key order fix
- build(): expand namedSignals({...}) onto multiple lines (was 95 chars)
- Tests: put createInitialHistoryState before delay in configExtension
objects (sort-keys-fix rule)
https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
…a callback registerHistory now accepts an optional callback that is invoked once on registration and again after every mutation of historyState (push, pop, clear). HistoryExtension uses this callback to keep canUndo/canRedo signals in sync with the current HistoryState, so the signals are computed directly from the authoritative source with no dependency on commands. Removes the previous CAN_UNDO_COMMAND/CAN_REDO_COMMAND listeners. The callback fires on initialization, which also covers the SharedHistoryExtension case where historyState may be pre-populated when the child editor is created. Tests: - Two updates are required to put an entry on undoStack (first sets current, second pushes). makeEditorWithOneUndoEntry helper does both updates. - The "new edit clears redoStack after undo" test wraps the UNDO dispatch in editor.update() so the HISTORIC_TAG from undo's setEditorState does not leak into the subsequent edit (which would otherwise discard it). Verified locally with pnpm run test-unit and pnpm run ci-check (tsc, tsc-extension, tsc-website, flow, prettier, lint) -- all pass. https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
…g\` in tests - Move onHistoryStateChange invocation out of the UNDO/REDO/CLEAR_EDITOR/ CLEAR_HISTORY command handlers and into the undo / redo / clearHistory helper functions themselves, alongside the mutation. The command handlers are now thin pass-throughs containing no signal-related code. - Wrap the canUndo / canRedo writes in HistoryExtension's callback (and the disabled-reset path) in batch() so subscribers to both signals are only notified once per change. - Switch all editor variables in the new tests to \`using\` declarations so editors are eagerly disposed at end of scope, matching the pattern used by other lexical-extension / lexical-react unit tests. Verified locally with pnpm run test-unit (3045 passed) and pnpm run ci-check (tsc, tsc-extension, tsc-website, flow, prettier, lint -- all pass). https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
… effect These reads are outside any reactive context, so peek() is the appropriate accessor — it returns the current value without establishing a subscription. The single remaining .value occurrence is the writable assignment \`dep.output.historyState.value = populated\` which must stay as a setter. Verified locally with pnpm run test-unit and pnpm run ci-check. https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
- Stop wrapping canUndo/canRedo in computed() inside HistoryExtension.build. Signal<T> already extends ReadonlySignal<T>, so the explicit HistoryExtensionOutput return-type annotation is enough to expose them as readonly to consumers — the computed() layer added nothing. - Drop the now-unused \`computed\` import from @lexical/extension. - Hoist a single syncFromHistoryState callback in HistoryExtension.register that accepts \`HistoryState | null\`. Passing null resets both signals (used in the disabled branch); passing a HistoryState derives them from its stacks (used by registerHistory on init and after every mutation). Eliminates the duplicated batch/reset block. Verified locally with pnpm run test-unit and pnpm run ci-check. https://claude.ai/code/session_01XeSGvEsnDrdbyLJBGUNYT8
ivailop7
approved these changes
May 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
canUndo: ReadonlySignal<boolean>andcanRedo: ReadonlySignal<boolean>toHistoryExtension's output, kept in sync viaCAN_UNDO_COMMANDandCAN_REDO_COMMANDlisteners. Signals reset tofalsewhen the extension is disabled.The
CAN_UNDO_COMMANDandCAN_REDO_COMMANDare hard to keep in sync because it's tricky to read the initial state of them, it's better to have signals that directly expose the correct values.Test plan
New unit tests