Skip some elements in history

I’m gonna explain step by step what I’m doing to make this easier to understand (I changed my approach removing the filtering and adding meta to every transaction I’m performing to be easier to track, but I still have the same problem).

Here is the workflow when the user performs an interaction using AI prompting (for the example, I used “translate in french”) :

Step 1 : I remove the user selection in the editor

editor.chain()
            .deleteRange({ from: fromPosition, to: toPosition })
            .focus()
            .setTextSelection(from)
            .focus()
            .run();

Step 2 : I insert my component “regenComponent” (the function retryFC is a way to restart the stream if it failed for one reason or another).

function retryFC() {
            editor.state.doc.descendants((node, pos) => {
                if (node.type.name === 'regenComponent') {
                    editor.chain().focus().setMeta("addToHistory", false).command(({ tr }) => {
                        tr.setNodeMarkup(pos, undefined, {
                            ...node.attrs,
                            isError: false,
                            isFinished: false,
                            errorMessage: false
                        });
                        return true;
                    }).run();
                }
            });
            handleStreamResponse()
        }

editor.chain().focus().setMeta("addToHistory", false)
            .insertContentAt(fromPosition, {
                type: 'regenComponent',
                attrs: {
                    content: '',
                    before: html,
                    from: from,
                    to: to,
                    isStart: isSelectionFromStart,
                    isEnd: isSelectionToEnd,
                    isSingleNode: isSingleNode,
                    listTag: listTag,
                    isFirstListItem: isFirstListItem,
                    onRetry: () => retryFC()
                }
            }).run();

Step 3 : I call the stream function that will feed my regenComponent

const handleStreamResponse = async () => {
            let accumulatedContent = '';

            const streamCallback = (chunk: string) => {
                accumulatedContent += chunk.replaceAll("\n", "");

                editor.state.doc.descendants((node, pos) => {
                    if (node.type.name === 'regenComponent') {
                        editor.chain().setMeta("addToHistory", false).focus().command(({ tr }) => {
                            tr.setNodeMarkup(pos, undefined, {
                                ...node.attrs,
                                content: node.attrs.content + chunk,
                            });
                            return true;
                        }).run();
                    }
                });
            };

            try {
                const res = await f_GetStreamResponse(
                    context?.data.preferences.email!,
                    prompt,
                    article.id,
                    streamCallback,
                    article.title,
                    "selected_paragraph",
                    hierarchy,
                    textAfter,
                    textBefore,
                    "",
                    fullText,
                    html
                );

                if (res.success) { // Success, pass component to end mode
                    editor.state.doc.descendants((node, pos) => {
                        if (node.type.name === 'regenComponent') {
                            editor.chain().setMeta("addToHistory", false).focus().command(({ tr }) => {
                                tr.setNodeMarkup(pos, undefined, {
                                    ...node.attrs,
                                    isFinished: true,
                                });
                                return true;
                            }).run();
                        }
                    });
                } else { // Error, pass component to error mode
                    editor.state.doc.descendants((node, pos) => {
                        if (node.type.name === 'regenComponent') {
                            editor.chain().setMeta("addToHistory", false).focus().command(({ tr }) => {
                                tr.setNodeMarkup(pos, undefined, {
                                    ...node.attrs,
                                    isError: true,
                                    isFinished: true,
                                    errorMessage: res.message
                                });
                                return true;
                            }).run();
                        }
                    });
                }
            } catch (error) {
                editor.state.doc.descendants((node, pos) => {
                    if (node.type.name === 'regenComponent') {
                        editor.chain().setMeta("addToHistory", false).focus().command(({ tr }) => {
                            tr.setNodeMarkup(pos, undefined, {
                                ...node.attrs,
                                isError: true,
                                isFinished: true,
                                errorMessage: "An unexpected error happened, please try again"
                            });
                            return true;
                        }).run();
                    }
                });
            }
        };

handleStreamResponse();

Step 4 : When the stream is finished, the user can click to choose between the previous content or the new one, in both case I remove the regenComponent node before inserting the desired content

editor.chain().focus().setMeta("addToHistory", false)
        // Remove the current node
        .deleteRange({ from: position, to: position + node.nodeSize })
        .run();

Step 5 : I paste the choosen content into the editor :

editor.chain().focus()
        // Insert content at the same position
        .insertContentAt(position, newContent.replace(/\n/g, ""), {
          parseOptions: { preserveWhitespace: false },
        })
        .focus()
        .run();

Everything works well except for the history part. At that time when I click “undo”, the editor turns empty :

It skips correctly the regenComponent I want to avoid but doesn’t return to the initial state (the state having “my text”). And that’s what I’m unable to understand for now.

My first intuition was that as I didn’t use .setMeta("addToHistory", false) in the step 1, this was the transaction that was used when I clicked “undo” and that’s why I got a blank editor.

The problem is, after clicking undo, I have nothing else in the history as I can undo only one time. So my initial state completely disappeared.

And even if I add .setMeta("addToHistory", false) at Step 1, the behavior is exactly the same.

And yes I know the code is not optimized for now, it’s while I’m looking for a solution to fix this