Skip to content

fix(admin): delete tables from block actions menu - #2272

Merged
khoinguyenpham04 merged 6 commits into
mainfrom
fix/2266-table-block-delete
Jul 29, 2026
Merged

fix(admin): delete tables from block actions menu#2272
khoinguyenpham04 merged 6 commits into
mainfrom
fix/2266-table-block-delete

Conversation

@khoinguyenpham04

@khoinguyenpham04 khoinguyenpham04 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes the portable text editor's block actions menu so whole tables can be deleted. It also moves the menu to Kumo's dropdown primitive, keeps it pinned to the block that opened it, preserves its exit animation, and prevents pointer movement between blocks from dismissing or relocating it.

Closes #2266

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Codex (GPT-5)

Screenshots / test output

  • Admin tests: 1,249 passed across 104 files.
  • Admin typecheck passed.
  • Oxlint completed with zero diagnostics.
  • Verified in English and Arabic RTL, including whole-table deletion and stable menu positioning during open and exit animations.

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks fix/2266-table-block-delete. Updated automatically when the playground redeploys.

Copilot AI review requested due to automatic review settings July 29, 2026 10:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 614 lines across 8 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7844a81

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

This PR includes changesets to release 17 packages
Name Type
@emdash-cms/admin Patch
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds 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

@github-actions github-actions Bot added the review/awaiting-author Reviewed; waiting on the author to respond label Jul 29, 2026
@khoinguyenpham04 khoinguyenpham04 self-assigned this Jul 29, 2026
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Jul 29, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 7844a81 Jul 29 2026, 01:50 PM

@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2272

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2272

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2272

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2272

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2272

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2272

emdash

npm i https://pkg.pr.new/emdash@2272

create-emdash

npm i https://pkg.pr.new/create-emdash@2272

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2272

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2272

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2272

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2272

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2272

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2272

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2272

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2272

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2272

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2272

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2272

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2272

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2272

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2272

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2272

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2272

commit: 7844a81

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 7844a81 Jul 29 2026, 01:49 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 7844a81 Jul 29 2026, 01:49 PM

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach is sound: the actual table-deletion fix is the focused allowTableNodeSelection: true option on the TipTap Table extension, and the rest of the PR replaces the hand-rolled Floating UI overlay with Kumo's DropdownMenu primitive plus lockDragHandle pinning so the menu stays anchored during exit animations. That fits EmDash's conventions (Kumo components, Lingui strings, logical Tailwind, RTL-aware side selection).

I read the diff, the full changed files, the sibling HeadingDropdownMenu pattern, and checked against AGENTS.md (localization, RTL, comment discipline, changesets). The implementation is largely solid and well-tested — the new tests cover whole-table deletion, menu pinning while the drag handle is locked, exit-transition completion, Escape-to-submenu, and click-outside dismissal.

The remaining issues are small: two stale/misleading comments in block-menu.test.tsx and a couple of React-idiom suggestions in BlockMenu.tsx. Nothing here blocks the stated bug fix, but the stale comments should be fixed before merge.


Findings

  • [needs fixing] packages/admin/tests/editor/block-menu.test.tsx:11

    This docstring is now misleading. BlockMenu was rewritten to use Kumo's DropdownMenu primitive and no longer calls createPortal itself. Stale comments violate the comment discipline in AGENTS.md.

     * BlockMenu is a standalone component that takes an editor instance,
     * an anchor element, and open/close callbacks. It renders via Kumo's
     * DropdownMenu primitive.
    
  • [needs fixing] packages/admin/tests/editor/block-menu.test.tsx:143

    The wrapper comment still references the old Floating UI implementation. BlockMenu no longer uses useFloating, so this will confuse the next person reading the test.

     * Wrapper component that renders BlockMenu with an anchor element.
     * This is needed because Kumo's DropdownMenu.Content anchors to a real DOM element.
    
  • [suggestion] packages/admin/src/components/editor/BlockMenu.tsx:175

    Assigning to a ref during render is a React anti-pattern; in concurrent/StrictMode renders the DOM node passed to DropdownMenu.Content can be written speculatively before commit. Prefer syncing the anchor in a layout effect (or passing an element directly if Kumo supports it).

    	const anchorRef = React.useRef<HTMLElement | null>(anchorElement);
    	const menuActionsRef = React.useRef<{ unmount: () => void; close: () => void } | null>(null);
    	const direction = getLocaleDir(i18n.locale);
    
    	React.useLayoutEffect(() => {
    		anchorRef.current = anchorElement;
    	}, [anchorElement]);
    
  • [suggestion] packages/admin/src/components/editor/BlockMenu.tsx:189

    If the editor selection is no longer a NodeSelection when the user clicks Duplicate/Delete (e.g. focus moved), the action silently does nothing and the menu stays open. The old implementation closed the menu defensively in this branch. Consider keeping that fallback:

    	const handleDuplicate = () => {
    		if (!(editor.state.selection instanceof NodeSelection)) {
    			onClose();
    			return;
    		}
    
  • [suggestion] packages/admin/src/components/editor/BlockMenu.tsx:206

    Same defensive-close regression as handleDuplicate — if the selection isn't a NodeSelection, Delete leaves the menu open.

    	const handleDelete = () => {
    		if (!(editor.state.selection instanceof NodeSelection)) {
    			onClose();
    			return;
    		}
    

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 29, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is the right fix for #2266 and follows EmDash conventions well. Enabling allowTableNodeSelection resolves the table-deletion regression, and switching the block menu to Kumo's DropdownMenu aligns with the Kumo-only component policy. The exit-animation and pin-to-opening-block behavior are localized in BlockMenu and DragHandleWrapper, the new strings are wrapped for Lingui, RTL is handled via getLocaleDir, and both user-facing effects have changesets. I also like that the diff adds a regression test specifically for deleting a selected table rather than only asserting implementation details.

I checked the diff, full changed files, import usage, and the locales config. The implementation is mostly clean, but I found one real correctness issue and two quality nits:

  1. BlockMenu.tsx writes anchorRef.current during render. That is a React anti-pattern and can leave the menu anchored to a stale or detached element under concurrent rendering, which is exactly the positioning stability this PR is trying to guarantee.
  2. block-menu.test.tsx still says the menu is rendered via createPortal, which is no longer true.
  3. The test named for hover-between-blocks behavior doesn't actually exercise the trigger-hover path it's protecting.

None of these are merge blockers, but the render-phase ref write should be fixed before merge.


Findings

  • [needs fixing] packages/admin/src/components/editor/BlockMenu.tsx:173

    Writing anchorRef.current during render is a React anti-pattern: refs are not render-state, and a discarded or concurrent render can leave anchorRef.current pointing to a stale or detached anchor node. That undermines the pinned-positioning goal of this PR.

    Move the sync into a layout effect so the ref is updated outside of render:

    	React.useLayoutEffect(() => {
    		if (anchorElement) anchorRef.current = anchorElement;
    	}, [anchorElement]);
    
  • [suggestion] packages/admin/tests/editor/block-menu.test.tsx:11

    The file JSDoc still says the menu renders "via createPortal to document.body", but the implementation now uses Kumo's DropdownMenu and no longer calls createPortal. Keep evergreen comments in sync with the code:

     * Renders through Kumo's dropdown primitive, anchored to the selected block.
    
  • [suggestion] packages/admin/tests/editor/block-menu.test.tsx:531

    This test hovers a highlighted menu item and then hovers a paragraph. Hovering a paragraph cannot fire Base UI's trigger-hover close reason, so the test would pass even if the eventDetails.cancel() branch for "trigger-hover" were removed. It therefore won't catch the regression the PR title describes—pointer movement between blocks relocating/dismissing the menu.

    Rewrite it against the trigger (simulate the drag handle moving to a different block while the menu is open) and assert the menu anchor stays pinned to the original block. The sibling test "keeps the controls and menu pinned to the block that opened the menu" is a good model.

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 29, 2026
@khoinguyenpham04

khoinguyenpham04 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

This PR is the right fix for #2266 and follows EmDash conventions well. Enabling allowTableNodeSelection resolves the table-deletion regression, and switching the block menu to Kumo's DropdownMenu aligns with the Kumo-only component policy. The exit-animation and pin-to-opening-block behavior are localized in BlockMenu and DragHandleWrapper, the new strings are wrapped for Lingui, RTL is handled via getLocaleDir, and both user-facing effects have changesets. I also like that the diff adds a regression test specifically for deleting a selected table rather than only asserting implementation details.

I checked the diff, full changed files, import usage, and the locales config. The implementation is mostly clean, but I found one real correctness issue and two quality nits:

  1. BlockMenu.tsx writes anchorRef.current during render. That is a React anti-pattern and can leave the menu anchored to a stale or detached element under concurrent rendering, which is exactly the positioning stability this PR is trying to guarantee.
  2. block-menu.test.tsx still says the menu is rendered via createPortal, which is no longer true.
  3. The test named for hover-between-blocks behavior doesn't actually exercise the trigger-hover path it's protecting.

None of these are merge blockers, but the render-phase ref write should be fixed before merge.

Findings

  • [needs fixing] packages/admin/src/components/editor/BlockMenu.tsx:173
    Writing anchorRef.current during render is a React anti-pattern: refs are not render-state, and a discarded or concurrent render can leave anchorRef.current pointing to a stale or detached anchor node. That undermines the pinned-positioning goal of this PR.
    Move the sync into a layout effect so the ref is updated outside of render:
    	React.useLayoutEffect(() => {
    		if (anchorElement) anchorRef.current = anchorElement;
    	}, [anchorElement]);
    
  • [suggestion] packages/admin/tests/editor/block-menu.test.tsx:11
    The file JSDoc still says the menu renders "via createPortal to document.body", but the implementation now uses Kumo's DropdownMenu and no longer calls createPortal. Keep evergreen comments in sync with the code:
     * Renders through Kumo's dropdown primitive, anchored to the selected block.
    
  • [suggestion] packages/admin/tests/editor/block-menu.test.tsx:531
    This test hovers a highlighted menu item and then hovers a paragraph. Hovering a paragraph cannot fire Base UI's trigger-hover close reason, so the test would pass even if the eventDetails.cancel() branch for "trigger-hover" were removed. It therefore won't catch the regression the PR title describes—pointer movement between blocks relocating/dismissing the menu.
    Rewrite it against the trigger (simulate the drag handle moving to a different block while the menu is open) and assert the menu anchor stays pinned to the original block. The sibling test "keeps the controls and menu pinned to the block that opened the menu" is a good model.

Accepted 1–2: moved the ref update to useLayoutEffect and fixed the stale comment.

Rejected 3: removing trigger-hover caused the regression test to fail, proving the existing test exercises and requires that path.

@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Jul 29, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR remains the right fix for #2266. It enables whole-table deletion via allowTableNodeSelection, switches the block menu to Kumo's DropdownMenu (consistent with the Kumo-only UI policy), and localizes the exit-animation / pin-to-opening-block behavior in BlockMenu and DragHandleWrapper. Strings are Lingui-wrapped, RTL direction is handled via getLocaleDir, and the regression tests now cover table deletion and drag-handle pinning.

Two of the three prior findings are addressed in this round:

  • ✅ The render-phase anchorRef.current write is now inside React.useLayoutEffect, so the anchor can't stale-point to a detached node under concurrent rendering.
  • ✅ The stale block-menu.test.tsx JSDoc that still referenced createPortal has been updated to describe the Kumo dropdown primitive.

One quality issue from the prior review is still present: the test that is meant to protect against the menu dismissing/relocating when the pointer moves between blocks doesn't actually exercise Base UI's trigger-hover close path. The remaining finding has details and a suggested fix.

I re-read the changed components, the interaction tests, the changesets, and the locale/config helper. No new logic bugs or AGENTS.md violations were introduced; the only open item is the test-coverage gap below.

Comment thread packages/admin/tests/editor/block-menu.test.tsx
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 29, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond overlap and removed review/needs-rereview Author pushed changes since the last review labels Jul 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@khoinguyenpham04
khoinguyenpham04 force-pushed the fix/2266-table-block-delete branch from 93ae019 to 7844a81 Compare July 29, 2026 13:45
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 29, 2026
@khoinguyenpham04
khoinguyenpham04 merged commit 087a825 into main Jul 29, 2026
47 checks passed
@khoinguyenpham04
khoinguyenpham04 deleted the fix/2266-table-block-delete branch July 29, 2026 14:04
@emdashbot emdashbot Bot mentioned this pull request Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/admin overlap review/needs-rereview Author pushed changes since the last review size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot click the Delete option on paragraph selector

2 participants