Skip to content

[BNE-99] Replace pastededuplicate#176

Draft
judithroth wants to merge 2 commits into
devfrom
code-maintenance/bne-99-replace-pastededuplicate
Draft

[BNE-99] Replace pastededuplicate#176
judithroth wants to merge 2 commits into
devfrom
code-maintenance/bne-99-replace-pastededuplicate

Conversation

@judithroth

@judithroth judithroth commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Ticket

https://community.openproject.org/wp/BNE-99

What are you trying to accomplish?

Major refactoring to reduce complexity on the communication between Popover for resizing and the work package link.
One added benefit: Setting up op-blocknote-extensions got a lot easier again.

What approach did you choose and why?

The WpOptionsPopover no longer talks to the chip/card through any shared channel — there's no wpBridge, no instanceId. Instead:

  • The popover is rendered directly inside the chip (InlineWorkPackageChip) or block card (BlockWorkPackageComponent), so it gets plain callback props (onResize, onRemove, onConvertToBlock, etc.) straight
    from that same component's closure.
  • When a popover button is clicked, the owning component acts immediately, addressing its own node rather than broadcasting an event:
    • Inline chip — resize and displayId-healing go through BlockNote's updateInlineContent render prop, which is instance-precise (implemented via the node view's own getPos()). Delete and promote-to-block
      first resolve the chip's real ProseMirror position from its DOM element (findInlineChipAtDOM, via view.posAtDOM), then apply a direct transaction (removeInlineChipAt / promoteInlineChipToBlockAt) — all in
      lib/utils/inlineChipActions.ts.
    • Block card — resize/remove call editor.updateBlock / editor.removeBlocks directly; convert-to-inline calls convertBlockToInlineChip.

Because every action targets the node's actual position or DOM element instead of a shared ID, two identical chips can never collide (no dedup-on-paste needed), and no registration/subscription step is
required — the popover's callbacks already close over everything they need.

Implementation

  - Removed wpBridge, instanceId, useOpBlockNoteExtensions, and PasteDeduplicateInstanceIdsExtension
  entirely.
  - Replaced them with direct, position-based editor mutations in a new lib/utils/inlineChipActions.ts:
  BlockNote's updateInlineContent render prop for resize/heal, and view.posAtDOM + ProseMirror
  transactions for select/delete/promote.
  - Re-keyed the pending-WP-search flow to use the pending wpid itself instead of a separate
  instanceId.
  - Made hash-menu trigger cleanup position-based, preserving the COMMS-806 natural-caret behavior
  verbatim.
  - Stripped instanceId/data-instance-id from both prop schemas and external HTML serialization.
  - Updated OpBlockNoteEditor.tsx in the OpenProject sandbox copy to drop the removed imports/calls.
  - Bumped the package to 0.2.0.

Merge checklist

  • Added/updated tests
  • Added/updated documentation in Lookbook (patterns, previews, etc)
  • Tested major browsers (Chrome, Firefox, Edge, ...)

Matching PR in OpenProject: opf/openproject#24094 (note: Unfinished - op-blocknote-extensions is not released yet)

https://community.openproject.org/wp/BNE-99

Major refactoring to reduce complexity on the communication between Popover for resizing and
the work package link.

  - Removed wpBridge, instanceId, useOpBlockNoteExtensions, and PasteDeduplicateInstanceIdsExtension
  entirely.
  - Replaced them with direct, position-based editor mutations in a new lib/utils/inlineChipActions.ts:
  BlockNote's updateInlineContent render prop for resize/heal, and view.posAtDOM + ProseMirror
  transactions for select/delete/promote.
  - Re-keyed the pending-WP-search flow to use the pending wpid itself instead of a separate
  instanceId.
  - Made hash-menu trigger cleanup position-based, preserving the COMMS-806 natural-caret behavior
  verbatim.
  - Stripped instanceId/data-instance-id from both prop schemas and external HTML serialization.
  - Updated OpBlockNoteEditor.tsx in the OpenProject sandbox copy to drop the removed imports/calls.
  - Bumped the package to 0.2.0.

One added benefit: Setting up op-blocknote-extensions got a lot easier
again.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors inline work package chip interactions to be position-based (DOM/ProseMirror position) instead of instanceId/wpBridge event routing, removing paste-time deduplication and simplifying editor setup while keeping user-facing behaviors (resize/remove/convert) working for both inline chips and block cards.

Changes:

  • Removed wpBridge, instanceId, and the paste-deduplication extension/plugin; updated all schemas, serialization, and tests accordingly.
  • Added lib/utils/inlineChipActions.ts to target chip operations directly via ProseMirror positions / DOM resolution (select/remove/promote/convert).
  • Reworked pending inline insertion/callback routing to key off the pending wpid itself and updated integration/unit tests to cover identical-chips-in-paragraph scenarios.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/lib/utils/inlineChipActions.test.ts Adds unit coverage for the new position-based inline chip actions utilities.
test/lib/utils/id.test.ts Removes tests/imports for deleted makeInstanceId.
test/lib/services/wpBridge.test.ts Deletes tests for the removed wpBridge event bus.
test/lib/components/staticSpec.test.ts Updates spec parsing/serialization expectations after removing instanceId from external HTML.
test/lib/components/integration/wpOptionsPopover.browser.test.tsx Updates popover integration tests to reflect direct mutations (no wpBridge) and new helper usage.
test/lib/components/integration/inlineWorkPackageSameParagraph.browser.test.tsx Adds browser regression coverage for identical inline chips in the same paragraph.
test/lib/components/InlineWorkPackage/externalHtml.test.ts Updates external HTML round-trip tests after removing data-instance-id.
test/lib/components/hashMenu.test.ts Updates hash menu tests for position-based trigger cleanup and removal of instanceId.
test/lib/components/callbacks.test.ts Re-keys pending callback registry tests to use pending wpid instead of a separate key/instanceId.
test/lib/components/BlockWorkPackage/externalHtml.test.ts Updates block external HTML tests after removing data-instance-id.
test/helpers/renderEditor.tsx Simplifies editor setup by removing deduplicate extension and post-mount hook wiring.
src/App.tsx Simplifies sandbox app editor setup by removing deduplicate extension and post-mount hook wiring.
package.json Bumps package version to 0.2.0.
lib/utils/inlineChipActions.ts Introduces the new position-based chip actions (find/select/remove/promote/convert).
lib/utils/id.ts Removes makeInstanceId, leaving only formatWorkPackageId.
lib/utils/cursor.ts Removes old inline-node selection helper tied to instanceId; retains block cursor helper.
lib/services/wpBridge.ts Removes the wpBridge service entirely.
lib/plugins/pasteDeduplicatePlugin.ts Removes paste deduplication plugin now that instanceId is removed.
lib/plugins/pasteDeduplicateExtension.ts Removes the extension wrapper for the paste deduplication plugin.
lib/index.ts Stops exporting removed bridge/id/extension/hook APIs.
lib/hooks/useOpBlockNoteExtensions.ts Removes now-obsolete post-mount wiring hook.
lib/hooks/useInlineWpEvents.ts Removes instanceId/wpBridge-based event handling implementation.
lib/components/WorkPackage/OptionsPopover.tsx Removes instanceId from popover props (no longer needed).
lib/components/SlashMenu.tsx Reworks inline insertion flow to use pending wpid and ProseMirror transactions for resolve/cancel.
lib/components/InlineWorkPackage/spec.tsx Passes updateInlineContent from the node view into the chip component.
lib/components/InlineWorkPackage/InlineWorkPackageChip.tsx Switches chip actions to direct position-based editor mutations and uses updateInlineContent for in-place updates.
lib/components/InlineWorkPackage/inlineConfig.ts Removes instanceId from inline prop schema.
lib/components/InlineWorkPackage/externalHtml.ts Removes instanceId from external HTML compute/parse for inline chips.
lib/components/InlineWorkPackage/callbacks.ts Generates unique pending wpids and uses them directly as callback registry keys.
lib/components/HashMenu/editorUtils.ts Removes instanceId generation and makes trigger cleanup position-based via selection-derived chip position.
lib/components/BlockWorkPackage/types.ts Removes instanceId from block props typing.
lib/components/BlockWorkPackage/externalHtml.ts Removes instanceId from external HTML compute/parse for block cards.
lib/components/BlockWorkPackage/BlockWorkPackage.tsx Converts block→inline via new convertBlockToInlineChip utility (no wpBridge).
lib/components/BlockWorkPackage/blockConfig.ts Removes instanceId from block prop schema.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +197 to +205
const [afterParagraph] = editor.insertBlocks(
[{ type: 'paragraph', content }],
anchorBlockId,
'after'
);
requestAnimationFrame(() => {
editor.focus();
editor.setTextCursorPosition(afterParagraph.id, 'start');
});
Comment on lines +11 to +16
let editor:{ document:unknown; replaceBlocks:(oldBlocks:unknown, newBlocks:unknown) => void };
renderEditor({ onEditor: (e) => { editor = e; } });

await expect.element(page.getByRole('textbox')).toBeVisible();

editor.replaceBlocks(editor.document, [
Comment on lines +62 to +65
// The chip (nodeSize 1) and its trailing space (length 1) were just inserted;
// insertInlineContent leaves the cursor right after the space.
const chipPosition = editor.prosemirrorState.selection.from - 2;
removeTriggerBeforeChip(editor, chipPosition);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants