Skip to content

refactor(core): simplify the BlockInfo API and make it the single vocabulary for block/children plumbing - #3010

Open
nperez0111 wants to merge 2 commits into
container-blocks/remove-content-containersfrom
container-blocks/blockinfo-consolidation
Open

refactor(core): simplify the BlockInfo API and make it the single vocabulary for block/children plumbing#3010
nperez0111 wants to merge 2 commits into
container-blocks/remove-content-containersfrom
container-blocks/blockinfo-consolidation

Conversation

@nperez0111

@nperez0111 nperez0111 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Top of the container-blocks stack. Subsumes #3007 (now closed) — the BlockInfo simplification and the BlockInfo consolidation are one API-simplification change, so they now ship as a single layer.

This PR is all about simplifying the block-plumbing APIs: fewer names, fewer producers, no hand arithmetic, one vocabulary.

One vocabulary: BlockInfo

"Where do this block's children live?" was answered in ~8 different vocabularies (BlockInfo.children, write-target helpers, fixContainer's private repair targets, raw beforePos + 1 walks, structural slice-walk branches, …). This PR consolidates them down to two: getBlockRegions resolves block shape (container vs blockContainer) in one place, and BlockInfo is the position-annotated view everything downstream reads.

  • One shape resolver: getBlockRegions(node) → { outer, content?, childrenHolder? }, consumed by getBlockInfoFromNode. No other code asks "which shape am I".
  • Deleted synonym vocabularies: childrenHolder.ts, ChildrenWriteTarget, fixContainer's private { blockPos, childrenStart } repair targets, and the descend seal-variant trio (now one self-recursive descendToLastInsertionPos returning { pos, crossedSeal } — callers make the 1-line seal decision themselves).
  • Deleted helpers that were bare property reads: getChildrenConfig (= config.children), isContainerType (= config.children !== undefined), isPlaceableAnywhere (= config.placement !== "containerOnly"), isInsertableChild; inlined deleteBlockCollapsingSingletonGroup and the table-caret ±4 arithmetic.

Simpler BlockInfo shape

Field renames — drops the internal jargon (ProseMirror group name strings like "bnBlock" are unchanged; only the TS API is renamed):

Before After
bnBlock block
blockContent content
childContainer children
isWrappedBlock hasContent

Precomputed derived fields — callers kept re-deriving the same positions with hand arithmetic (blockContent.beforePos + 1 at 25+ sites, afterPos - 1 at ~12, empty-inline-block checks at ~6). BlockInfo now carries them directly:

  • contentStart / contentEnd — inside edges of the content node
  • children.childrenStart / children.childrenEnd — inside edges of the child container
  • contentKind: "inline" | "none" | "table" | "other" — replaces raw spec.content string checks
  • isContentEmpty

These stay a discriminated union on hasContent, so the existing narrowing guards keep working.

Fewer producers

6 overlapping producers → 4, named by what you have; the module doc comment carries the decision table:

You have Call
a PM node + its before-pos getBlockInfoFromNode(node, beforePos)
a position just before a block getBlockInfoAt(doc, posBeforeBlock)
an arbitrary position getBlockInfoNearPos(source, pos)
a selection getBlockInfoFromSelection(source)

getBlockInfo and getBlockInfoFromResolvedPos are deleted; getBlockInfoWithManualOffset, getBlockInfoAtNearest, and getBottomNestedBlockInfo are renamed (getBlockInfoFromNode, getBlockInfoNearPos, getLastDescendantBlockInfo).

Navigation helpers moved & fixed

getParentBlockInfo / getPrevBlockInfo / getNextBlockInfo / getLastDescendantBlockInfo move from mergeBlocks.ts to getBlockInfoFromPos.ts (they're generic navigation, not merge logic) and become public.

One parent function (deliberate behavior fix): getParentBlockInfo now has block-model semantics — a block inside a column parents to the column, not the columnList. Fixes the Delete-at-end climb running its seal check on the wrong node for container children. Regression test added.

Named positions over raw arithmetic: childrenStart/End, contentStart/End replace the surviving ±1/nodeSize sites; the Backspace-into-table caret now anchors on the actual previous block's content region, fixing a latent off-by-2 for nested tables.

Testing

  • Core unit suite: 842 passed | 9 skipped
  • xl-multi-column unit suite: 86 passed | 5 skipped
  • Docker e2e (multicolumn keyboardhandlers copypaste indentation dragdrop): matches the base-branch baseline exactly — the only failure is the pre-existing webkit "Delete before shallower block" snapshot, which fails identically on the base branch head.

Review round 1 (a176963)

  • insertBlocks placements renamed: "start"/"end""first-child"/"last-child" (docs, tests, jsdoc updated; the cursor-placement "start"/"end" vocabulary of setTextCursorPosition is unrelated and unchanged).
  • containerNav helpers unified on BlockInfo: descendToFirst/LastInsertionPos both return number | null and take SealOpts (the crossedSeal return flag is gone — the one caller that needed "was a seal the only blocker" asks with a second seal-blind call); getFirstLeafBlock takes and returns BlockInfo. ascendToInsertablePos / getAncestorContainers stay position-based on purpose (their inputs are arbitrary gap positions, not blocks) and say so in jsdoc.
  • Single-use indirections inlined: canMerge + mergeBlocksmergeBlocksCommand (the boolean guard makes the defensive throws statically unreachable, so they're deleted); movedNodeTypecheckPlacementIsValid; seedRefillChildrenrefillContainer; seedDefaultChildren + createContainerChildrenNodeblockToNode.
  • getInsertionPos drops its unreachable exotic-shape guard (getBlockRegions already throws for bnBlock nodes that are neither container nor blockContainer).
  • blockToNode.ts split: the inline/table-content conversion layer now lives in contentToNodes.ts; blockToNode.ts keeps block assembly only.
  • jsdocs added to fixContainersById and flattenNonInsertableBlocks.

@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Aug 25, 2026 3:30pm
blocknote-website Ready Ready Preview Aug 25, 2026 3:30pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ee796c32-fa4e-4298-9a62-f8f1c06dd4a1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://TypeCellOS.github.io/BlockNote/pr-preview/pr-3010/

Built to branch gh-pages at 2026-08-25 15:34 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

…abulary for block/children plumbing

Combines the former block-info and blockinfo-consolidation changes into
one API-simplification pass over BlockInfo.

Field renames (drop internal jargon; PM group name strings unchanged):
- bnBlock -> block
- blockContent -> content
- childContainer -> children
- isWrappedBlock -> hasContent

Precomputed derived fields, replacing hand arithmetic at dozens of
callsites (blockContent.beforePos + 1, afterPos - 1, empty-inline
checks):
- contentStart / contentEnd
- children.childrenStart / children.childrenEnd
- contentKind: "inline" | "none" | "table" | "other"
- isContentEmpty

Producer consolidation: 6 overlapping producers -> 4, named by what you
have (getBlockInfoFromNode, getBlockInfoAt, getBlockInfoNearPos,
getBlockInfoFromSelection). getBlockInfo and
getBlockInfoFromResolvedPos are deleted; getBlockInfoWithManualOffset,
getBlockInfoAtNearest and getBottomNestedBlockInfo are renamed.

One shape resolver: getBlockRegions(node) -> { outer, content?,
childrenHolder? } resolves container vs blockContainer shape in one
place, consumed by getBlockInfoFromNode. Deleted the synonym
vocabularies that answered "where do children live" in parallel:
childrenHolder.ts, ChildrenWriteTarget, fixContainer's private repair
targets, and the descend seal-variant trio (now one self-recursive
descendToLastInsertionPos returning { pos, crossedSeal }).

Deleted helpers that were bare property reads: getChildrenConfig,
isContainerType, isPlaceableAnywhere, isInsertableChild; inlined
deleteBlockCollapsingSingletonGroup and the table-caret +-4 arithmetic.

Navigation helpers (getParentBlockInfo / getPrevBlockInfo /
getNextBlockInfo / getLastDescendantBlockInfo) move from mergeBlocks.ts
to getBlockInfoFromPos.ts and become public. getParentBlockInfo now has
block-model semantics: a block inside a column parents to the column,
not the columnList, fixing the Delete-at-end climb's seal check for
container children.
@nperez0111
nperez0111 force-pushed the container-blocks/blockinfo-consolidation branch from 488eb39 to 727a5df Compare August 25, 2026 14:45
@nperez0111 nperez0111 changed the title refactor(core): make BlockInfo the single vocabulary for block/children plumbing refactor(core): simplify the BlockInfo API and make it the single vocabulary for block/children plumbing Aug 25, 2026
- Rename `insertBlocks` placements "start"/"end" to "first-child"/"last-child"
  (clearer about nesting; docs, tests, and jsdoc updated).
- containerNav helpers now take a BlockInfo and share one shape:
  `descendToFirst/LastInsertionPos` both return `number | null` and accept
  `SealOpts` instead of the `crossedSeal` flag (callers that need "was a seal
  the only blocker" ask with a second seal-blind call); `getFirstLeafBlock`
  takes and returns BlockInfo. `ascendToInsertablePos` and
  `getAncestorContainers` stay position-based on purpose (their inputs are
  arbitrary gap positions, not blocks) and now say so in jsdoc.
- Inline single-use indirections: `canMerge` + `mergeBlocks` fold into
  `mergeBlocksCommand` (the boolean guard makes the defensive throws
  statically unreachable, so they are gone); `movedNodeType` folds into
  `checkPlacementIsValid`; `seedRefillChildren` folds into `refillContainer`;
  `seedDefaultChildren` + `createContainerChildrenNode` fold into
  `blockToNode`; the `descend` closure folds into `getInsertionPos`.
- Drop the unreachable exotic-shape guard in `getInsertionPos`:
  `getBlockRegions` already throws for bnBlock nodes that are neither
  containers nor blockContainer.
- Split the inline/table-content conversion layer out of `blockToNode.ts`
  into `contentToNodes.ts`.
- Add jsdocs to `fixContainersById` and `flattenNonInsertableBlocks`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant