Repository navigation
fix(editor): test and support Tiptap 3.32 - #4401
emdash-autofix[bot] wants to merge 6 commits into
Conversation
Pin the editor catalog to Tiptap 3.32.0, retain lowlight 3.20.0, satisfy the y-tiptap peer, and consolidate ProseMirror. Restore responsive table rendering and fix newer-Tiptap floating toolbar DOM/event compatibility in admin and core. Update highlighting, selection, link, table and keyboard regression coverage. Verified root build, workspace typecheck, zero-diagnostic quick lint, format, frozen install, the full serial admin editor suite and core component/conversion tests. Added changeset and before/after Chromium screenshots; independent follow-up review cleared all findings. EmDash-Bot-Case: 1198246700-issue-6025800409 EmDash-Bot-Candidate: cand-1-122fcc0877
EmDash bot: Ready for human reviewPlan Completed the maintainer's wording-only requests: rewrote the PR description to match the remaining diff, added visual-editor existing-link space behavior to the changeset, and corrected the helper comment. No implementation or tests chang…
Status
Next action: A maintainer will review and approve the pull request. Details
Required checks on the current head:
This status is generated from recorded evidence. It is not a review and contains no instructions. |
🦋 Changeset detectedLatest commit: 3304ab9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
There was a problem hiding this comment.
The approach is sound. Moving the catalog to the Tiptap version users actually install (3.32.0) and fixing each failure from #4400 addresses the CI/install mismatch. Keeping EmDash's own table view, running the highlighting tests in a React-mounted editor, and making the toolbars wrapper-free all fit the existing architecture. One reviewer found a remaining wrapper-dependent positioning path in the core inline editor that needs fixing. There are also two convention and documentation gaps, and two cleanup suggestions. Two decisions are left to a maintainer: the link-authoring behavior change, which the other reviewer accepted as a Tiptap change, and the overlap with #4396, which touches the same toolbar code. Both reviews were static, so nothing was built or run.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
| }) => { | ||
| elements.floating.style.maxWidth = `${Math.max(0, availableWidth)}px`; | ||
| }, | ||
| }, |
There was a problem hiding this comment.
[needs fixing] The wrapper-free migration also needs to update ToolbarMenu in inline-editor-ui.tsx:392–405. That effect still gets its positioning reference with closest('[role="toolbar"]')?.parentElement. With the new DOM structure that parent is .emdash-inline-ui, not the positioned toolbar. This is reachable when closing the link field: the formatting controls remount while the toolbar is already attached, so both Turn into and More formatting capture the UI root's rectangle and open away from their toolbar. Use the toolbar element itself as the position reference, and add browser geometry coverage for opening these menus after leaving the link field. The existing jsdom tests cannot detect this regression. (Only one reviewer raised this; the other reported no bugs but did not examine this path in detail.)
There was a problem hiding this comment.
Confirmed in Chromium: after closing the link field, the menu was about 106px away from its expected vertical anchor. ToolbarMenu now references the toolbar itself. Opening a menu also finishes the toolbar’s entrance animation so the anchor stays stable. Added browser geometry coverage for both remounted menus; it fails before the fix and passes afterward, along with the existing entrance-positioning test.
|
|
||
| Pins the editor's Tiptap dependencies to exactly 3.32.0 so installations use the version covered by the editor tests. Code-block highlighting keeps its existing lowlight extension version. Sites or plugins that share Tiptap dependencies with EmDash should align them to 3.32.0 to avoid installing multiple copies. | ||
|
|
||
| Links applied at an empty caret now end when you type a space, following Tiptap's updated behavior. Text typed after that space is not part of the link. |
There was a problem hiding this comment.
[needs fixing] The changed link-authoring behavior is explained only in the release note. .changeset/README.md requires useful upgrade explanations to appear in the canonical feature documentation too, but docs/src/content/docs/guides/working-with-content.mdx still gives no guidance on this. Add it under Write rich text, including the practical way to create a multi-word link: type the label first, select the whole label, then apply the link. Editors can then get the previous outcome after upgrading. Note that the other reviewer judged the changeset acceptable as written and treated the behavior change as an accepted Tiptap change. It does weaken the "add a link at an empty caret, then type" flow, because only the first word is now linked.
There was a problem hiding this comment.
Added guidance under Write rich text explaining the space boundary and the multi-word workflow: type the label, select it, then apply the link. The docs build passes.
| import { useEffect } from "react"; | ||
| import { describe, expect, it, vi } from "vitest"; | ||
|
|
||
| import { CodeBlockExtension } from "../../src/components/editor/CodeBlockNode"; |
There was a problem hiding this comment.
[needs fixing] This new internal import omits the .js extension required by the root AGENTS.md ESM import convention.
| import { CodeBlockExtension } from "../../src/components/editor/CodeBlockNode"; | |
| import { CodeBlockExtension } from "../../src/components/editor/CodeBlockNode.js"; |
There was a problem hiding this comment.
Fixed the new import to use CodeBlockNode.js.
| elements.floating.style.maxWidth = `${Math.max(0, availableWidth)}px`; | ||
| // The surface scrolls itself, so no wrapper clips its rounded ring. | ||
| const surface = elements.floating.firstElementChild; | ||
| const surface = elements.floating; | ||
| if (surface instanceof HTMLElement) { | ||
| surface.style.maxWidth = "100%"; | ||
| surface.style.overflowX = "auto"; | ||
| } | ||
| }, |
There was a problem hiding this comment.
[suggestion] elements.floating is always an HTMLElement, so the surface alias and the instanceof guard don't check anything any more. The same leftover pattern appears in playBubbleEntrance (const surface = element;, line 4809) and in ImageBubbleMenu.playEntrance (const toolbar = menu;, line 5506), where the instanceof check sits after menu?.isConnected. Simplifying them makes it clear that the floating element itself is now the surface:
| elements.floating.style.maxWidth = `${Math.max(0, availableWidth)}px`; | |
| // The surface scrolls itself, so no wrapper clips its rounded ring. | |
| const surface = elements.floating.firstElementChild; | |
| const surface = elements.floating; | |
| if (surface instanceof HTMLElement) { | |
| surface.style.maxWidth = "100%"; | |
| surface.style.overflowX = "auto"; | |
| } | |
| }, | |
| elements.floating.style.maxWidth = `${Math.max(0, availableWidth)}px`; | |
| // The surface scrolls itself, so no wrapper clips its rounded ring. | |
| elements.floating.style.overflowX = "auto"; | |
| }, |
There was a problem hiding this comment.
Simplified the sizing and animation paths to operate directly on the floating surface, removing the leftover aliases and redundant guards.
| if (event.nativeEvent.isComposing || event.nativeEvent.defaultPrevented) return; | ||
| const toolbar = event.currentTarget; | ||
| const controls = [...toolbar.querySelectorAll(TOOLBAR_CONTROL_SELECTOR)]; | ||
| const backToEditor = | ||
| event.key === "Escape" || | ||
| (event.key === "Tab" && event.shiftKey && document.activeElement === controls[0]); | ||
| event.nativeEvent.key === "Escape" || | ||
| (event.nativeEvent.key === "Tab" && | ||
| event.nativeEvent.shiftKey && | ||
| document.activeElement === controls[0]); |
There was a problem hiding this comment.
[suggestion] These handlers now read key, shiftKey and defaultPrevented from event.nativeEvent, but they still call event.preventDefault() on the React event. Nested inputs, such as the link field at inline-bubble-menu.tsx:415 and the alt field at inline-image.tsx:362, keep using event.key. Nothing in the code explains why the toolbar handlers differ, so a later cleanup is likely to switch them back to event.key and bring back the 3.32 failure. Add a one-line comment at the start of handleToolbarKeyDown and moveToolbarFocus (admin, line 6626) explaining why the key has to come from nativeEvent when the handler is attached to Tiptap's BubbleMenu.
There was a problem hiding this comment.
Added a short comment to both shared toolbar keyboard handlers explaining that BubbleMenu’s event wrapper does not forward native keyboard property getters.
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-test
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-loader
@emdash-cms/registry-moderation
@emdash-cms/registry-verification
upgrade-emdash
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-ai-search
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Address review by anchoring remounted inline menus to the actual toolbar and finishing its entrance before positioning. Added Chromium regression coverage that fails before the fix and passes after, retaining existing entrance geometry coverage. Documented multi-word link authoring, corrected the ESM import, simplified floating-surface aliases, and explained native keyboard-event property access. Verified root/docs builds, workspace typecheck, zero-diagnostic quick lint, formatting, 88 focused admin tests, eight core menu tests and two Playwright geometry tests. Added before/after screenshots and replied to all review threads; independent review found no significant issues. EmDash-Bot-Case: 1198246700-issue-6025800409 EmDash-Bot-Candidate: cand-2-7a8f737a3c
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | c6d7985 | Oct 27 2026, 01:51 AM |
There was a problem hiding this comment.
The approach is sound. The catalog now uses the Tiptap version users actually install (3.32.0), which fixes the CI/install mismatch behind #4400. EmDash's own table view, the React-mounted highlighting tests and the wrapper-free toolbars all fit the existing architecture. Both reviewers approved with no new findings, and all five earlier findings are resolved at this head. The core ToolbarMenu now anchors to the toolbar element itself, and new browser geometry coverage exercises it. The link-authoring docs, the .js import extension, the leftover aliases and the nativeEvent comments are also addressed. Two decisions remain for a maintainer: whether to accept the Tiptap link-exit behavior, and how to reconcile this PR with #4396, which touches the same toolbar code. Both reviews were static, so nothing was built or run.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
Fix the deterministic Table E2E accessibility failure introduced by Tiptap 3.32's textbox role: name the editable elements in admin and inline editors, forward admin field labels, and supply a localized admin fallback. Add admin accessible-name regressions and an inline browser assertion; update the changeset. Verified root build, workspace typecheck, quick lint, format, 129 admin component tests, all Chromium Table E2E tests (11 passed, one skipped), and final-source admin naming/inline browser checks. Firefox's CI console warning passed on retry and remains classified as flaky; its browser executable is unavailable locally. Independent follow-up review cleared the changes. EmDash-Bot-Case: 1198246700-issue-6025800409 EmDash-Bot-Candidate: cand-3-06923df5cd
There was a problem hiding this comment.
The approach is sound. Moving the catalog and lockfile to Tiptap 3.32.0 means CI tests the version a fresh install gets, which fixes the mismatch behind #4400. The table-view restoration, the wrapper-free toolbars and the React-mounted highlighting tests fit the existing architecture. Both reviewers approved. All five findings from the earlier review remain resolved at this head: toolbar menu anchoring with geometry coverage, the link-authoring docs, the .js import, the leftover aliases and the nativeEvent comments. The new accessible textbox names are covered by tests. One minor suggestion remains, about comment placement. Two decisions are still for a maintainer: whether to accept Tiptap's new behavior where a link ends at the first typed space, and how to reconcile this PR with #4396, which touches the same toolbar code. Both reviews were static, so nothing was built or run.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
| // Stable editorProps reference — a new object every render would cause | ||
| // compareOptions to call setOptions → updateState → plugin teardown → | ||
| // Suggestion onExit → setSlashMenuState → re-render → infinite loop. | ||
| const editorLabel = t`Rich text editor`; |
There was a problem hiding this comment.
[suggestion] The new editorLabel declaration sits between the "Stable editorProps reference…" comment and the useMemo that comment describes, so the comment now looks like it explains editorLabel. Move the declaration above the comment, so the comment stays directly above const editorProps = React.useMemo(.
There was a problem hiding this comment.
Moved editorLabel above the comment so the explanation is directly above the editorProps memo. No runtime code changed.
Address the final review nit by moving the editorProps comment below the editorLabel declaration, directly beside the memo it describes. This is comment placement only; executable code and behavior are unchanged. Ran pnpm format and git diff --check. Independent review skipped for this trivial comment-only relocation. EmDash-Bot-Case: 1198246700-issue-6025800409 EmDash-Bot-Candidate: cand-4-bd109fedb5
There was a problem hiding this comment.
The approach is sound. Pinning Tiptap 3.32.0 in the catalog and lockfile means CI tests the version a fresh install gets, which fixes the mismatch behind #4400. Both reviewers approved with no new findings. All earlier findings remain resolved at this head: the editorLabel comment placement, the nativeEvent handler comments, the leftover surface/toolbar aliases, the .js import, the link-authoring docs, and ToolbarMenu anchoring with geometry coverage. Two decisions are still for a maintainer: whether to accept Tiptap's new behavior where a link ends at the first typed space, and how to reconcile this PR with #4396, which changes the same toolbar code. Both reviews were static, so nothing was built or run.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
danielmlr
left a comment
There was a problem hiding this comment.
#4396 has merged and fixes the toolbar keys a different way. The two PRs edit the same lines.
- Rebase onto main and drop these toolbar changes: the
nativeEventreads, thefirstElementChildremovals, and theToolbarMenuanchor change ininline-editor-ui.tsx, which isn't needed once the toolbar sits inside the menu element again. #4396 puts the key handlers on EmDash's own element, so they get normal React events. Here they stay on Tiptap's menu element and readevent.nativeEvent. A later handler on that element that readsevent.keyorevent.shiftKeywould throw "Illegal invocation" again. - Keep the rest as it is: the move to 3.32.0, the table
addNodeViewoverride, the textbox names, the tests for the table, textbox names and link behaviour, and the link note in the changeset and docs. - Rewrite the changeset's first sentence so it no longer claims the toolbar keyboard fix, since #4396 ships that.
~ 🤖 Claude Code (Opus 5.5)
|
@emdash-autofix #4396 is merged. It fixes the floating toolbar keys by putting EmDash's own element inside each
|
|
Merged current main, including #4396, and resolved the conflicts in favor of its explicit React toolbar surfaces. Removed this PR’s native-event workarounds, toolbar anchor/animation changes, and superseded toolbar tests. The changeset now claims only the table/accessibility fixes and dependency/link behavior updates. The Tiptap upgrade, responsive table view, textbox names, relevant tests, and link documentation remain. Focused checks pass; publishing the merge update next. |
Merge default branch 0409cd2 and resolve toolbar conflicts using main's #4396 implementation unchanged. Drop superseded native-event, surface, anchor, and toolbar-test changes; restore the original synchronous image geometry assertion. Keep the 3.32 catalog/lockfile update, responsive table override, textbox names, relevant regression tests, and link changeset/docs; remove the changeset's duplicate toolbar-fix claim. Verified frozen install, root/docs builds, workspace typecheck, quick lint, formatting, all 982 admin editor tests, 349 focused tests, core component/conversion coverage, main's nine menu tests, inline browser checks, and RTL/forced-colors table accessibility. Independent follow-up review cleared the merged scope. EmDash-Bot-Case: 1198246700-issue-6025800409 EmDash-Bot-Candidate: cand-5-8de02d1625
|
Done in the submitted merge update: incorporated main at |
There was a problem hiding this comment.
Pinning and testing Tiptap 3.32.0 is the right approach to the install/CI mismatch in #4400. The table-view override, React-mounted highlighting tests, accessible names and link-authoring docs fit that goal. The reviewers disagree on the toolbar compatibility work. Claude concluded it is no longer in this diff and was probably taken over by #4396, so its earlier toolbar findings no longer apply. GPT found that the head still has the old keyboard handlers and wrapper-based sizing, which break under 3.32. I kept both of GPT's findings as needs_fixing because the previously approved fixes may be missing at this head, and Claude's explanation does not show that the regressions are handled elsewhere. A maintainer should either confirm that #4396 lands first and covers them, or restore the fixes here. The PR description also still describes toolbar fixes and an Escape test that are not in the diff. Whether to accept Tiptap's behavior where a link ends at the first typed space is still a maintainer decision. Both reviews were static; nothing was built or run.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
| "@tiptap/pm": 3.32.0 | ||
| "@tiptap/react": 3.32.0 | ||
| "@tiptap/starter-kit": 3.32.0 | ||
| "@tiptap/suggestion": 3.32.0 |
There was a problem hiding this comment.
[needs fixing] The upgrade to @tiptap/react 3.32.0 is present, but the native-keyboard-event compatibility fix described in the PR is absent from this head. moveToolbarFocus in packages/admin/src/components/PortableTextEditor.tsx:6646 still reads event.key. handleToolbarKeyDown in packages/core/src/components/inline-editor-ui.tsx:583–610 still reads event.defaultPrevented, event.key and event.shiftKey from BubbleMenu's wrapped event. Per GPT, these getter accesses throw on 3.32, so keyboard navigation in the selection and image toolbars stays broken. Restore the native-event reads in both editors and the Escape regression coverage. Claude disagreed: it found this work is no longer in the diff and believes #4396 took it over, so it raised no finding. If #4396 covers it, merge order matters, because this pin would otherwise expose the regression.
There was a problem hiding this comment.
#4396 is already merged into this head via main 0409cd220949. These handlers are no longer props on BubbleMenu: admin attaches onKeyDown to its own child <div ref={surfaceRef}> (around lines 4991–4999), and core does the same in inline-bubble-menu.tsx around lines 628–634. They therefore receive real React keyboard events, not Tiptap’s synthetic native-event wrapper. The image handlers likewise sit on the explicit child div.
I retained that implementation as @danielmlr explicitly requested rather than restoring the superseded workaround. Verification after the merge included main’s nine core menu tests (Tab and arrow navigation), admin toolbar/image tests, and browser checks; all passed. No merge-order dependency remains.
| "@tiptap/pm": 3.32.0 | ||
| "@tiptap/react": 3.32.0 | ||
| "@tiptap/starter-kit": 3.32.0 | ||
| "@tiptap/suggestion": 3.32.0 |
There was a problem hiding this comment.
[needs fixing] The wrapper-free floating-toolbar adaptation is also missing at this head. With 3.32 the BubbleMenu child is the positioned surface itself, but the admin sizing callback (PortableTextEditor.tsx:4933–4938) first sets elements.floating.style.maxWidth to the available pixel width and then overwrites it through surfaceRef.current with 100%. This loses the collision-aware width constraint on narrow viewports. The entrance animations also still target the old wrapper-based structure. Restore the wrapper-free sizing and animation changes in both editors, along with the ToolbarMenu anchoring fix and browser geometry coverage. Claude disagreed for the same reason as above: it treats this work as moved to #4396.
There was a problem hiding this comment.
The two references are distinct DOM elements at this head. elements.floating is Tiptap’s positioned menu container; surfaceRef.current is EmDash’s explicit child <div> introduced by #4396. Thus the pixel max-width remains on the outer container, while 100% and horizontal scrolling apply to its child. Animations target that child, and ToolbarMenu’s parent anchor again points to the positioned container.
This follows the maintainer’s instruction to keep #4396 and drop our wrapper-free adaptation. The existing entrance-positioning browser test and focused toolbar tests passed after merging main. The latest submitted PR description explicitly records this reconciliation and removal of the extra toolbar tests; it no longer claims those toolbar fixes as part of this diff.
danielmlr
left a comment
There was a problem hiding this comment.
The toolbar fix from #4396 works on Tiptap 3.32, so this PR doesn't need any toolbar changes.
- Rewrite the PR description to match the diff. It still describes toolbar fixes, an Escape test, toolbar screenshots and the overlap with #4396, which are no longer part of this PR.
- Add to the changeset that in the visual editor, typing a space at the end of an existing link now ends the link. The admin editor already doesn't extend a link once you move the caret to its end, so the docs paragraph can stay as it is.
- Update the comment on
setSelectedTextLinkinPortableTextEditor.tsx. The text typed next now gets the link only up to the first space.
Keep everything else as it is.
|
@emdash-autofix The toolbar fix from #4396 works on Tiptap 3.32, so this PR doesn't need any toolbar changes.
Keep everything else as it is. |
|
Updated all three items: the PR description now covers only the remaining dependency, table, textbox-name, test, and link changes, with table screenshots only; the changeset now calls out space-at-end behavior for existing links in the visual editor; and |
Address the maintainer's remaining wording requests only: describe the current scoped diff without obsolete toolbar claims or screenshots, add the visual-editor space-at-end-of-existing-link behavior to the changeset, and clarify that setSelectedTextLink links newly typed text only up to its first space. Implementation, tests, and the authoring-guide paragraph are unchanged. Ran pnpm format and git diff --check; independent review found no significant issues. The preceding implementation head c6d7985 passed required CI. EmDash-Bot-Case: 1198246700-issue-6025800409 EmDash-Bot-Candidate: cand-6-129d976203
There was a problem hiding this comment.
Pinning and testing Tiptap 3.32.0 is the right approach to the fresh-install/CI mismatch in #4400. Both reviewers approved and neither raised a new finding at this head. The table-view override, React-mounted highlighting tests, accessible names and link-authoring docs fit that goal. The earlier toolbar concerns (native keyboard events, wrapper-free sizing, ToolbarMenu anchoring) are resolved: #4396 is merged through main, so the handlers sit on EmDash's own child element and the pixel max-width stays on the separate positioned container. The earlier suggestions (.js import extension, comment placement, link docs) were also addressed. Two items remain that are not code defects. The PR description still mentions toolbar fixes and an Escape test that are not in this diff, and the author's reply says it was updated. A maintainer still has to decide whether to accept Tiptap's behavior of ending a link at the first typed space. Both reviews were static; nothing was built or run.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
|
The source changes are approved. There is a remaining description-publication discrepancy: candidate 6 supplied a replacement body with no toolbar-fix claims, Escape-test claims, overlap discussion, or toolbar screenshots, but this review still reports the old text. I cannot read or directly edit the live PR body with the available tools. @danielmlr, please check the published body. If it is stale, the intended summary is:
Only the table before/after screenshots should remain. The complete replacement body was supplied with |
|
@emdash-autofix I checked the published body. I updated it after your comment, and it now matches your summary: no toolbar-fix or Escape-test claims, nothing about the overlap with #4396, and only the table before/after screenshots (English and Arabic). Nothing else is open from my side, so this PR now just needs a second maintainer's approval. |
|
Thanks for checking and updating the published description. No further changes from me; leaving the PR ready for the second maintainer’s approval. |
What does this PR do?
Closes #4400.
Pins the Tiptap 3.x editor catalog entries to exactly 3.32.0, keeping
extension-code-block-lowlightat 3.20.0. Published admin/core packages will use that tested version rather than a floating caret range; integrations sharing Tiptap dependencies should align to 3.32.0 to avoid duplicate copies. Updatesy-tiptapto^3.0.7(locked to 3.0.10) to satisfy the collaboration/drag-handle peer requirement. The lockfile consolidates ProseMirror without unrelated dependency upgrades.Type of change
Your connection to this change
Prepared by the project autofix bot through auto-intake for this reproduced report. Human maintainer approval is required before merge.
Checklist
pnpm typecheckpassespnpm lintpasses — quick lint passes with zero diagnostics; full type-aware lint deferred to CIpnpm testpasses (targeted tests for this change)pnpm formathas been runAI assistance disclosure
Screenshots / test output
Passed on the final source:
pnpm buildwithNODE_OPTIONS=--max-old-space-size=8192.pnpm typecheck,pnpm lint:quick(zero diagnostics),pnpm format, and frozen-lockfile install.pnpm --filter @emdash-cms/admin exec vitest run tests/editor --maxWorkers=1(978 tests across 49 files).pnpm --filter emdash exec vitest run tests/unit/components tests/unit/converters/custom-block-round-trip.test.ts --maxWorkers=1(includes core menu, image-toolbar, code-block, table and conversion coverage).A parallel editor run hit an intersection-observer timing assertion; the complete serial run passed. Full type-aware lint and remaining suites are left to CI.
Table reproduction harness on 3.32, same container and saved 128px/160px column widths:
Arabic RTL table
The EmDash bot took this issue on automatically when it was opened; no maintainer has assigned it yet.