From 752f65aab92c96f25914360fd604a8e4d0fdc9f0 Mon Sep 17 00:00:00 2001 From: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:43:12 +0100 Subject: [PATCH 1/3] refactor(clipboard-plugin): remove meta from editorjs clipboard payload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `application/x-editor-js` clipboard payload carried a `meta.version` field hardcoded to '3.0.0' with a `@todo get version info from Core`. Nothing read it — no paste handler exists yet, and a repo-wide grep finds the MIME type only in this plugin and its spec. Shipping a placeholder version in a public clipboard format is worse than shipping nothing: a future paste handler that trusted the field would branch on a value that never reflected reality. Removing it now, before a consumer exists, costs no migration. The payload becomes `{ blocks }`. The object wrapper is kept rather than serializing the bare array so that reintroducing a sibling key later stays additive for readers. BREAKING CHANGE: the `application/x-editor-js` clipboard payload no longer includes `meta`. No exported TypeScript API changes — `ClipboardEditorJSObject` and `Meta` were module-private; the break is on the wire format only. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../.openspec.yaml | 2 + .../remove-clipboard-payload-meta/design.md | 40 +++++++++++++++++++ .../remove-clipboard-payload-meta/proposal.md | 30 ++++++++++++++ .../specs/clipboard-plugin/spec.md | 34 ++++++++++++++++ .../remove-clipboard-payload-meta/tasks.md | 22 ++++++++++ openspec/specs/clipboard-plugin/spec.md | 7 +++- .../clipboard-plugin/src/index.spec.ts | 11 ++++- .../plugins/clipboard-plugin/src/index.ts | 27 ++----------- 8 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 openspec/changes/remove-clipboard-payload-meta/.openspec.yaml create mode 100644 openspec/changes/remove-clipboard-payload-meta/design.md create mode 100644 openspec/changes/remove-clipboard-payload-meta/proposal.md create mode 100644 openspec/changes/remove-clipboard-payload-meta/specs/clipboard-plugin/spec.md create mode 100644 openspec/changes/remove-clipboard-payload-meta/tasks.md diff --git a/openspec/changes/remove-clipboard-payload-meta/.openspec.yaml b/openspec/changes/remove-clipboard-payload-meta/.openspec.yaml new file mode 100644 index 00000000..c0a81625 --- /dev/null +++ b/openspec/changes/remove-clipboard-payload-meta/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-21 diff --git a/openspec/changes/remove-clipboard-payload-meta/design.md b/openspec/changes/remove-clipboard-payload-meta/design.md new file mode 100644 index 00000000..63efa8b5 --- /dev/null +++ b/openspec/changes/remove-clipboard-payload-meta/design.md @@ -0,0 +1,40 @@ +## Context + +`ClipboardPlugin` (`packages/plugins/clipboard-plugin/src/index.ts`) writes three flavours of the same selection onto the native clipboard on `ui:copy`: `text/plain`, `text/html`, and a custom `application/x-editor-js` JSON payload. That third payload is currently `{ blocks, meta: { version: '3.0.0' } }`, where the version is a hardcoded literal accompanied by a `@todo get version info from Core`. + +Nothing in the repository reads the payload — a repo-wide grep for `application/x-editor-js` finds only the plugin itself, its co-located test, and the capability spec. Paste handling does not exist yet, so this is the last moment where the format can be narrowed without a migration path for internal consumers. + +This change is deliberately small: it is a format simplification, not a redesign of clipboard handling. It is recorded as a change rather than a drive-by edit because the payload shape is a documented spec-level requirement. + +## Goals / Non-Goals + +**Goals:** + +- Make the `application/x-editor-js` payload exactly `{ blocks }`. +- Remove the module-private `Meta` interface and the `@todo` that pointed at a version-from-Core plumbing task that no longer needs doing. +- Keep the spec (`openspec/specs/clipboard-plugin/spec.md`) truthful about the emitted shape. + +**Non-Goals:** + +- Designing or implementing a paste handler that reads the payload. +- Introducing versioning by another route (payload envelope, MIME type suffix, separate clipboard entry). If a future change needs to distinguish formats, it decides then, with a real consumer to design against. +- Changing the MIME type string, the `text/plain` / `text/html` payloads, or the copy-event flow. +- Sourcing an editor version from Core for any other purpose. + +## Decisions + +**Drop `meta` entirely rather than populate `version` from Core.** The alternative — plumbing a real version through `EditorAPI` into the plugin — was rejected because no consumer needs it. A version field earns its place when something branches on it; adding the plumbing first means maintaining a public format field on speculation. Deleting it is reversible: reintroducing `meta` later is an additive change to the payload, whereas a wrong version value baked into the format is not something readers can recover from. + +**Keep `{ blocks }` as an object rather than serializing the bare array.** Emitting `JSON.stringify(blocks)` would be marginally smaller, but the object wrapper is what makes future extension additive — a reader that destructures `{ blocks }` keeps working when a sibling key appears, while a reader parsing a top-level array would break on any envelope change. The wrapper costs eleven bytes. + +**No versioning shim or dual-write.** The plugin could write both shapes for a transition period. Rejected: there is no reader to transition, and a dual-write would immediately become the compatibility burden the change exists to avoid. + +**Type change is module-private.** `ClipboardEditorJSObject` and `Meta` are not exported from the package, so removing `Meta` alters no published `.d.ts` surface. The break is on the wire only, which is why the proposal marks it BREAKING despite no TypeScript API moving. + +## Risks / Trade-offs + +**An out-of-repo consumer reads `meta.version` from the clipboard** → Low: the payload has never been documented outside the spec file, the plugin landed recently (`f14b7ce`), and the value it carried was a placeholder, not a real version. Any such consumer was branching on a constant. Accepted without a deprecation window. + +**A future paste implementation genuinely needs format discrimination** → Reintroducing a sibling key alongside `blocks` is additive and cheap; the object wrapper decision above preserves that path. Deciding the shape then, against a real reader, produces a better field than the placeholder does now. + +**Stryker mutation testing on the plugin flags the simplified factory** → `#createClipboardObject` becomes a near-trivial wrapper, which gives mutation testing less to bite on. This is a reduction in code under test, not a coverage regression; the payload assertion in the co-located spec still pins the exact serialized string. diff --git a/openspec/changes/remove-clipboard-payload-meta/proposal.md b/openspec/changes/remove-clipboard-payload-meta/proposal.md new file mode 100644 index 00000000..89bb418d --- /dev/null +++ b/openspec/changes/remove-clipboard-payload-meta/proposal.md @@ -0,0 +1,30 @@ +## Why + +The `application/x-editor-js` clipboard payload written by `ClipboardPlugin` carries a `meta.version` field that is hardcoded to `'3.0.0'` with a `@todo get version info from Core` — it is not read by anything and there is no paste-side consumer that could act on it. Shipping a placeholder version in a public clipboard format is worse than shipping nothing: any future paste handler that trusts the field would branch on a value that never reflected reality. Removing it now, before a paste handler exists, keeps the format honest and costs nothing. + +## What Changes + +- **BREAKING** (payload format): the `application/x-editor-js` clipboard payload becomes `{ blocks }` — the `meta` object and its `version` field are removed. +- Delete the `Meta` interface and the `meta` member of `ClipboardEditorJSObject` in `packages/plugins/clipboard-plugin/src/index.ts`. +- Simplify `#createClipboardObject` to return the blocks array wrapper only, dropping the `@todo` about sourcing the version from Core. +- Update the co-located test asserting the serialized payload to expect `{ blocks }`. + +The break is contained: no code in this repository reads the payload, and no paste handling exists yet. Third-party readers of the clipboard format, if any exist, must stop expecting `meta`. + +## Capabilities + +### New Capabilities + +None. + +### Modified Capabilities + +- `clipboard-plugin`: the "Rich clipboard data on copy" requirement changes the shape of the `application/x-editor-js` payload from `{ blocks, meta: { version } }` to `{ blocks }`. + +## Impact + +- **Code**: `packages/plugins/clipboard-plugin/src/index.ts` (interfaces + `#createClipboardObject`), `packages/plugins/clipboard-plugin/src/index.spec.ts` (payload assertion). +- **Specs**: `openspec/specs/clipboard-plugin/spec.md` — purpose paragraph and the "Populating clipboard data for a block selection" scenario. +- **APIs**: no exported TypeScript API changes; `ClipboardEditorJSObject` and `Meta` are module-private. Only the on-the-wire clipboard payload changes. +- **Dependencies / other packages**: none. `grep` over the repo finds the MIME type referenced only inside the clipboard plugin and its spec. +- **Docs**: `docs/` contains no clipboard documentation, so nothing there is superseded. The plugin README describes the plugin at one line and needs no edit. diff --git a/openspec/changes/remove-clipboard-payload-meta/specs/clipboard-plugin/spec.md b/openspec/changes/remove-clipboard-payload-meta/specs/clipboard-plugin/spec.md new file mode 100644 index 00000000..64e035f0 --- /dev/null +++ b/openspec/changes/remove-clipboard-payload-meta/specs/clipboard-plugin/spec.md @@ -0,0 +1,34 @@ +## MODIFIED Requirements + +### Requirement: Rich clipboard data on copy +The system SHALL provide `ClipboardPlugin`, which subscribes to the `ui:copy` event on construction and, when blocks are selected, populates the native clipboard event with `text/plain`, `text/html`, and `application/x-editor-js` data and prevents the native copy action. + +#### Scenario: Populating clipboard data for a block selection +- **GIVEN** one or more blocks are selected and the native copy event exposes `clipboardData` +- **WHEN** the `ui:copy` event fires +- **THEN** the plugin prevents the native event's default action and calls `clipboardData.setData` with the DOM selection's plain text (`text/plain`), the DOM selection's cloned range contents as HTML (`text/html`), and a JSON-serialized `{ blocks }` object (`application/x-editor-js`) built from the selected blocks + +#### Scenario: Omitting metadata from the EditorJS payload +- **GIVEN** blocks are selected and the `application/x-editor-js` payload is being built +- **WHEN** the plugin serializes the payload +- **THEN** the resulting JSON contains `blocks` as its only key and carries no `meta` object or version field + +#### Scenario: Falling back to native copy when no blocks are selected +- **GIVEN** `api.selection.selectedBlocks` is empty +- **WHEN** the `ui:copy` event fires +- **THEN** the plugin returns without calling `preventDefault` or touching `clipboardData`, leaving the browser's native copy behavior intact + +#### Scenario: Falling back to native copy when clipboard access is unavailable +- **GIVEN** blocks are selected but the native event's `clipboardData` is `undefined` (or `window.getSelection()` returns `null`) +- **WHEN** the `ui:copy` event fires +- **THEN** the plugin returns without calling `preventDefault`, leaving the browser's native copy behavior intact + +#### Scenario: Building HTML from a multi-range selection +- **GIVEN** the DOM selection has zero or more `Range`s +- **WHEN** the plugin builds the `text/html` payload +- **THEN** it clones each range's contents into a shared `