Structural editors: cryptic/misleading Yjs encode error for invalid node JSON — pre-validate against the schema and report the offending node path #409

Open
opened 2026-07-07 20:51:49 +03:00 by agent_vscode · 0 comments
Collaborator

Problem

The structural editing tools (patchNode, insertNode, tableUpdateCell, updatePageJson, transformPage) account for ~34 thrown failures in agent dialog history (2026-06-17…07-07 DB analysis, see docs/reading-ai-logs.md), typically:

Failed to encode document to Yjs (fromJSON): Unknown node type: undefined.
A node/mark attribute likely holds a value Yjs cannot store (e.g. undefined).

Unknown node type: undefined means a nested node in the agent-supplied JSON has no type (or an unknown type) — prosemirror's Schema.nodeType throws it. Two code problems make the agent retry blind (UI logs show consecutive identical failures):

  1. Validation gap: patchNode (packages/mcp/src/client.ts:2087) checks typeof node.type === "string" for the ROOT only; nested content children are never validated. The error then surfaces deep in the Yjs encode, after the collab session is already open.
  2. Misleading hint: findUnstorableAttr (packages/mcp/src/lib/node-ops.ts:331) only scans attrs/marks values for undefined/function/symbol/bigint — it cannot see a missing/unknown type. So unstorableYjsError (packages/mcp/src/lib/collaboration.ts:31) falls through to the generic "A node/mark attribute likely holds a value Yjs cannot store" hint, pointing the model at attributes when the actual problem is a node shape (e.g. {"text": "foo"} missing "type": "text").

Fix sketch

  1. findInvalidNode(doc) walker in node-ops.ts: depth-first over the JSON; return the path and a short key summary of the first node whose type is not a string or not present in docmostSchema.nodes (and marks not in docmostSchema.marks), e.g.:
    node.content[2].content[0]: missing "type" (keys: text, marks) — did you mean {"type": "text", ...}?
  2. Use it in unstorableYjsError (collaboration.ts:31) BEFORE the attrs hint: check findInvalidNode first, fall back to findUnstorableAttr, keep the generic sentence only as the last resort.
  3. Pre-validate before the collab write in patchNode / insertNode / updatePageJson (and the table-cell paragraph builder path): run the walker (or docmostSchema.nodeFromJSON(target).check() in try/catch for full schema validation — nodeFromJSON gives precise PM errors) and throw the rich error BEFORE getCollabTokenWithReauth/mutatePageContent. Fail fast: no collab session open, no page lock taken, deterministic message.
  4. Tool descriptions (packages/mcp/src/tool-specs.ts, patchNode/insertNode/updatePageJson): state explicitly that EVERY node, including nested children, must carry a string type from the Docmost schema, and that text leaves are {"type": "text", "text": "..."}.

Notes

  • sanitizeForYjs already strips undefined attr values — keep it; this issue is about the node-shape failure mode it cannot fix.
  • transformPage executes agent-authored JS producing a doc — it benefits automatically from (1)+(2) since it shares the encode path.
  • Unit tests: nested typeless node → path-precise error, thrown before any collab connection (mock seam mutatePage not called); unknown node type name → same; valid doc unaffected; findUnstorableAttr fallback still fires for a genuine undefined-attr case.
  • Related: #407 (persisting these errors to DB history) — independent, but together they turn today's blind Yjs retries into visible, self-correcting failures.
## Problem The structural editing tools (`patchNode`, `insertNode`, `tableUpdateCell`, `updatePageJson`, `transformPage`) account for ~34 thrown failures in agent dialog history (2026-06-17…07-07 DB analysis, see `docs/reading-ai-logs.md`), typically: ``` Failed to encode document to Yjs (fromJSON): Unknown node type: undefined. A node/mark attribute likely holds a value Yjs cannot store (e.g. undefined). ``` `Unknown node type: undefined` means a **nested node in the agent-supplied JSON has no `type`** (or an unknown type) — prosemirror's `Schema.nodeType` throws it. Two code problems make the agent retry blind (UI logs show consecutive identical failures): 1. **Validation gap**: `patchNode` (`packages/mcp/src/client.ts:2087`) checks `typeof node.type === "string"` for the ROOT only; nested `content` children are never validated. The error then surfaces deep in the Yjs encode, after the collab session is already open. 2. **Misleading hint**: `findUnstorableAttr` (`packages/mcp/src/lib/node-ops.ts:331`) only scans `attrs`/`marks` values for undefined/function/symbol/bigint — it cannot see a missing/unknown `type`. So `unstorableYjsError` (`packages/mcp/src/lib/collaboration.ts:31`) falls through to the generic "A node/mark attribute likely holds a value Yjs cannot store" hint, pointing the model at attributes when the actual problem is a node shape (e.g. `{"text": "foo"}` missing `"type": "text"`). ## Fix sketch 1. **`findInvalidNode(doc)` walker in `node-ops.ts`**: depth-first over the JSON; return the path and a short key summary of the first node whose `type` is not a string or not present in `docmostSchema.nodes` (and marks not in `docmostSchema.marks`), e.g.: `node.content[2].content[0]: missing "type" (keys: text, marks) — did you mean {"type": "text", ...}?` 2. **Use it in `unstorableYjsError`** (collaboration.ts:31) BEFORE the attrs hint: check `findInvalidNode` first, fall back to `findUnstorableAttr`, keep the generic sentence only as the last resort. 3. **Pre-validate before the collab write** in `patchNode` / `insertNode` / `updatePageJson` (and the table-cell paragraph builder path): run the walker (or `docmostSchema.nodeFromJSON(target).check()` in try/catch for full schema validation — nodeFromJSON gives precise PM errors) and throw the rich error BEFORE `getCollabTokenWithReauth`/`mutatePageContent`. Fail fast: no collab session open, no page lock taken, deterministic message. 4. **Tool descriptions** (`packages/mcp/src/tool-specs.ts`, patchNode/insertNode/updatePageJson): state explicitly that EVERY node, including nested children, must carry a string `type` from the Docmost schema, and that text leaves are `{"type": "text", "text": "..."}`. ## Notes - `sanitizeForYjs` already strips `undefined` attr values — keep it; this issue is about the node-shape failure mode it cannot fix. - `transformPage` executes agent-authored JS producing a doc — it benefits automatically from (1)+(2) since it shares the encode path. - Unit tests: nested typeless node → path-precise error, thrown before any collab connection (mock seam `mutatePage` not called); unknown node type name → same; valid doc unaffected; findUnstorableAttr fallback still fires for a genuine undefined-attr case. - Related: #407 (persisting these errors to DB history) — independent, but together they turn today's blind Yjs retries into visible, self-correcting failures.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#409