Skip to content

fix: use lax type guards for text block and span identification#2421

Merged
christianhg merged 1 commit intomainfrom
fix/is-text-block-node
Mar 27, 2026
Merged

fix: use lax type guards for text block and span identification#2421
christianhg merged 1 commit intomainfrom
fix/is-text-block-node

Conversation

@christianhg
Copy link
Copy Markdown
Member

@christianhg christianhg commented Mar 27, 2026

Adds isTextBlockNode and sweeps the codebase to use lax type guards (isTextBlockNode, isSpanNode) where the strict versions (isTextBlock, isSpan) aren't needed.

isTextBlock from @portabletext/schema requires children to be an array. isSpan requires text to be a string. These strict checks are correct when the code accesses .children or .text on the narrowed type, but they cause type lies at discriminator sites. For example, isBlock used !isTextBlock(parent) to determine if a node's parent is a text block. If a text block arrives without children, isTextBlock returns false, and isBlock incorrectly returns true for its children - spans get misidentified as blocks.

isTextBlockNode checks _type against schema.block.name without requiring children. isSpanNode (from #2420) checks _type against schema.span.name without requiring text. These are the right guards for code that just needs to know what type a node is.

The sweep follows a simple rule: use the lax guard everywhere except where .children or .text is accessed on the narrowed type. 26 files changed across behaviors, selectors, operations, node traversal, Slate core, and rendering.

The normalization plugin uses isTextBlockNode for markDefs and style checks (which don't need children), and keeps isTextBlock for span merging and orphaned annotation removal (which do). The rendering layer gets fallbacks for text blocks without children and spans without text - render null instead of throwing.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 27, 2026

🦋 Changeset detected

Latest commit: 3ffb53e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@portabletext/editor Patch
@portabletext/plugin-character-pair-decorator Patch
@portabletext/plugin-emoji-picker Patch
@portabletext/plugin-input-rule Patch
@portabletext/plugin-markdown-shortcuts Patch
@portabletext/plugin-one-line Patch
@portabletext/plugin-paste-link Patch
@portabletext/plugin-sdk-value Patch
@portabletext/plugin-typeahead-picker Patch
@portabletext/plugin-typography Patch
@portabletext/toolbar Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

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

Project Deployment Actions Updated (UTC)
portable-text-editor-documentation Ready Ready Preview, Comment Mar 27, 2026 7:41pm
portable-text-example-basic Ready Ready Preview, Comment Mar 27, 2026 7:41pm
portable-text-playground Ready Ready Preview, Comment Mar 27, 2026 7:41pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026

📦 Bundle Stats — @portabletext/editor

Compared against main (56c20c34)

@portabletext/editor

Metric Value vs main (56c20c3)
Internal (raw) 760.3 KB +419 B, +0.1%
Internal (gzip) 143.0 KB -142 B, -0.1%
Bundled (raw) 1.37 MB +342 B, +0.0%
Bundled (gzip) 305.8 KB -104 B, -0.0%
Import time 97ms +1ms, +1.4%

@portabletext/editor/behaviors

Metric Value vs main (56c20c3)
Internal (raw) 467 B -
Internal (gzip) 207 B -
Bundled (raw) 424 B -
Bundled (gzip) 171 B -
Import time 6ms -0ms, -0.8%

@portabletext/editor/plugins

Metric Value vs main (56c20c3)
Internal (raw) 2.5 KB -
Internal (gzip) 910 B -
Bundled (raw) 2.3 KB -
Bundled (gzip) 839 B -
Import time 12ms -0ms, -0.1%

@portabletext/editor/selectors

Metric Value vs main (56c20c3)
Internal (raw) 60.5 KB +322 B, +0.5%
Internal (gzip) 9.5 KB +71 B, +0.7%
Bundled (raw) 56.9 KB +262 B, +0.5%
Bundled (gzip) 8.7 KB +42 B, +0.5%
Import time 10ms -0ms, -0.8%

@portabletext/editor/utils

Metric Value vs main (56c20c3)
Internal (raw) 24.2 KB -
Internal (gzip) 4.7 KB -
Bundled (raw) 22.2 KB -
Bundled (gzip) 4.4 KB -
Import time 9ms -0ms, -1.0%

🗺️ . · ./behaviors · ./plugins · ./selectors · ./utils · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@christianhg christianhg force-pushed the fix/is-text-block-node branch from 7d4d2e3 to 3ffb53e Compare March 27, 2026 19:41
@christianhg christianhg changed the title fix: handle text blocks with missing children property during normalization and rendering fix: use lax type guards for text block and span identification Mar 27, 2026
@christianhg christianhg enabled auto-merge (rebase) March 27, 2026 19:42
@christianhg christianhg disabled auto-merge March 27, 2026 19:49
@christianhg christianhg merged commit b97146c into main Mar 27, 2026
18 of 20 checks passed
@christianhg christianhg deleted the fix/is-text-block-node branch March 27, 2026 19:49
@ecoscript ecoscript bot mentioned this pull request Mar 27, 2026
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