Skip to content

fix(editor): heal empty block ids that crash the editor#679

Merged
h4yfans merged 1 commit into
mainfrom
fix/editor-empty-block-id
Jul 3, 2026
Merged

fix(editor): heal empty block ids that crash the editor#679
h4yfans merged 1 commit into
mainfrom
fix/editor-empty-block-id

Conversation

@h4yfans

@h4yfans h4yfans commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

A few notes showed "Editor Error — The editor encountered an error", technical details starting with Block doesn't have id at kt…. Refresh didn't help.

Root cause: those notes had a block persisted with an empty-string id (""). BlockNote's kt resolver runs if (!id) throw "Block doesn't have id", and !"" === true, so an empty id reads as missing and the editor crashes on mount. The error boundary then shows "Editor Error". The note text itself is fine — only the block's id attribute is empty.

Why it happened

The markdown→CRDT converter mints one gap paragraph per extra blank line, and createEmptyParagraph() hard-coded id: ''. The headless serializer (blocksToYXmlFragment) regenerates a missing id into a real UUID, but writes an explicit empty string as-is. So any note with 2+ consecutive blank lines that re-hydrated from markdown — seedFromMarkdown, an external .md edit, or sync — persisted an empty-id block. Notes edited purely in the live editor keep real UUIDs, which is why only some notes broke, and why reload never fixed them (the empty id is in the persisted CRDT).

Fix

  • createEmptyParagraph() no longer seeds id: '' (uses randomUUID()).
  • blocksToYFragment defensively stamps a UUID on any block whose id is falsy before serializing — closes the empty-string footgun at the write choke point.
  • repairEmptyBlockIds() walks the CRDT fragment on note open and re-ids any empty/missing block container, so already-corrupted notes self-heal and re-persist (wired into crdt-provider load path).

Tests

blocknote-converter.test.ts — new "block id integrity" suite:

  • markdown with multi-blank-line gaps never yields an empty container id
  • blocksToYFragment regenerates a falsy id
  • repairEmptyBlockIds stamps missing ids and leaves valid ones untouched

Reproduce

Externally edit a vault note's .md to have a double blank line between two lines of text, then reopen the note — before this change it throws "Editor Error".

Notes with 2+ consecutive blank lines that re-hydrated from markdown
(seed-from-markdown, external .md edit, or sync) were persisted with an
empty-string block id. BlockNote's headless serializer regenerates a
MISSING id but writes an explicit '' as-is; on mount the renderer's block
resolver throws "Block doesn't have id" and the editor error boundary
shows "Editor Error". Refresh can't help because the empty id lives in the
persisted CRDT.

- createEmptyParagraph no longer seeds id: '' (uses randomUUID)
- blocksToYFragment defensively stamps a uuid on any falsy block id
- repairEmptyBlockIds heals already-corrupted notes on open (crdt-provider)
- regression tests for the converter output + repair helper
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memry-docs Ready Ready Preview, Comment Jul 3, 2026 12:04am
memrynote-landing Ready Ready Preview, Comment Jul 3, 2026 12:04am

@github-actions github-actions Bot added bug Something isn't working test labels Jul 3, 2026
@h4yfans h4yfans marked this pull request as ready for review July 3, 2026 00:21
@h4yfans h4yfans merged commit 7ec1ab5 into main Jul 3, 2026
13 of 15 checks passed
@h4yfans h4yfans deleted the fix/editor-empty-block-id branch July 3, 2026 00:21
h4yfans added a commit that referenced this pull request Jul 3, 2026
Desktop tests (Unit coverage step) had accumulated pre-existing failures
masked behind the CLI-test red (which skipped the desktop step):
- crdt-provider: add repairEmptyBlockIds to blocknote-converter mock (#679)
- date rendering (date-grouping, format-task-due, tab-preview-card,
  missing-small-components, component-major-surfaces): expect DD.MM.YYYY
  default from configurable date format (#671)
- general-section.i18n: tabCloseButton is selects[3] after the added
  dateFormat select (#671)
- settings-sections: editor/journal switch indices shift +1 after the
  calendar notes-on-calendar switch (#667)
- missing-small-components: add getI18n to react-i18next mock (fixes an
  unhandled rejection that failed the whole run)

E2E auth-state-machine: recovery-phrase words are semantic <li> after the
React Doctor a11y pass (#639) dropped explicit role=listitem; query by 'li'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant