Inconsistent Root Node in ProseMirror Document

Hello everyone,

I am currently facing an issue with the root node in ProseMirror where the root appears to be a table node instead of the usual doc node. I have confirmed this by using the descendants method to traverse the nodes and by checking view.state.doc.nodeAt(0).

Here’s the snippet I used for traversal:

view.state.doc.descendants((node, pos) => {
  console.log('Node type:', node.type.name, 'at pos:', pos);
});

// Node type: table at pos: 0
// Node type: table_row at pos: 1
// Node type: table_header at pos: 2
// Node type: paragraph at pos: 3
// Node type: table_header at pos: 6
// Node type: paragraph at pos: 7
// Node type: paragraph at pos: 12

However, the situation gets even more confusing. When I print the JSON representation of the document using view.state.doc.toJSON() , the root node shows up as doc , which is what I would expect.

console.log(JSON.stringify(view.state.doc.toJSON(), null, 2));
   // {
    //     "type": "doc",
    //     "content": [
    //       {
    //         "type": "table",
    //         "attrs": {
    //           "widthSet": "",
    //           "changingWidthSet": "",
    //           "rootWidth": 0,
    //           "delay": 0
    //         },
    //         "content": [
    //           {
    //             "type": "table_row",
    //             "content": [
    //               {
    //                 "type": "table_header",
    //                 "attrs": {
    //                   "colspan": 1,
    //                   "rowspan": 1,
    //                   "alignment": "left"
    //                 },
    //                 "content": [
    //                   {
    //                     "type": "paragraph",
    //                     "attrs": {
    //                       "indent": 0,
    //                       "align": "none"
    //                     }
    //                   }
    //                 ]
    //               },
    //               {
    //                 "type": "table_header",
    //                 "attrs": {
    //                   "colspan": 1,
    //                   "rowspan": 1,
    //                   "alignment": "left"
    //                 },
    //                 "content": [
    //                   {
    //                     "type": "paragraph",
    //                     "attrs": {
    //                       "indent": 0,
    //                       "align": "none"
    //                     }
    //                   }
    //                 ]
    //               }
    //             ]
    //           }
    //         ]
    //       },
    //       {
    //         "type": "paragraph",
    //         "attrs": {
    //           "indent": 0,
    //           "align": "none"
    //         }
    //       }
    //     ]
    //   }

descendants will not yield the node you call it on (it’s not a descendant of itself), and nodeAt(0) will give you the node at the start of the document, not the document node itself. So everything is, in fact, consistent. state.doc.type.name should give you "doc".

Ok,here is the started question。

const table = $cell.node(-1);
const tableStart = $cell.start(-1);

tr.setNodeMarkup(tableStart, null, {
  ...table.attrs,
  changingWidthSet: '',
});
console.log(view.state.doc.nodeAt(tableStart)); // node type is table_row

and the attrs of table is not changed success.

Are you expecting tr.setNodeMarkup to affect the view without dispatching the resulting transaction? Because it doesn’t do that. It just builds up the transaction, which has zero effect until it is dispatched.

I am certainly dispatch it .

    tr
        .setNodeMarkup(tableStart, null, {
            ...table.attrs,
            changingWidthSet: '',
        })
        .setMeta('addToHistory', false)
        .setMeta('addToRemote', false),
        console.log('Before dispatch:', table.attrs);
    view.dispatch(tr);
    console.log('After dispatch:', view.state.doc.nodeAt(tableStart),view.state.doc.nodeAt(tableStart)?.attrs); //  node type is table_row, and  attrs is  {}
tr
        .setNodeMarkup(tableStart-1, null, {
            ...table.attrs,
            changingWidthSet: '',
        })
        .setMeta('addToHistory', false)
        .setMeta('addToRemote', false)

It is working , but I am not sure ,Is it the design

If tableStart is the position at the start of the table’s content, then yeah, you’ll need to subtract one to get the actual table position.

Is it right

tr
        .setNodeMarkup(tableStart===1 ? 0 : tableStart, null, {
            ...table.attrs,
            changingWidthSet: '',
        })

That looks terrible. Use $cell.before(...) instead of $cell.start(...), and get the actual position of the node so you don’t have to do any further arithmetic on it.

That’s great. thank you :grin: