oficina / docs Public

WebKit's copy marker is not a line the author left

Paul's real clipboard bytes, captured off the Google Docs copy this
branch is about, end like this:

  …<p>…Four…</p></b><br class="Apple-interchange-newline"><br
  class="Apple-interchange-newline">

Two of them, after the closing </b> of Google's wrapper. WebKit appends
that marker to a copy ending at a block boundary — Safari and Chrome
both do it — to record where the copy stopped. ProseMirror's clipboard
parser ignores a <br> that is the last child of a non-inline parent,
which disposes of exactly one; the first has a next sibling, survives,
becomes a hard_break, and is the blank line that appeared under a paste
with nothing after it.

The previous commit turned that into an empty paragraph rather than a
two-line one, which was an improvement and still one blank line more
than Google shows. Ignoring the marker by its class removes it.

Discriminating on the class rather than on position is what makes this
safe, and it is the reason this is not simply "drop a trailing empty
paragraph". A blank line the author really did leave at the end is a
<br> Google puts INSIDE its wrapper, carrying no class; a positional
rule would eat it. Both cases are checked against the real bytes: the
copy with no trailing blank now yields exactly the five blocks Google
shows, and the same copy with a real trailing blank keeps six.

The rule goes ahead of schema-basic's own, which follow unchanged, so a
<br> the author typed is still a break — losing that would silently
delete every Shift+Enter on the way in, which the tests now pin along
with the requirement that every other field of the node spec comes
through untouched, since the node's name and shape are the wire format.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmyRH8GkGkt5ay8gDAUKNm
Paul Campbell pushed by paul@keymail.dev 9782cbfbbacc28942f71af4ab920247b0dbc8488 parent 1bfbcd7
4 files changed, +75 −15
  • fixtures/editor-entry.js +8 −2
  • fixtures/paste.mjs +25 −0
  • fixtures/paste.test.mjs +30 −1
  • internal/docs/static/{editor.a4e6489413ecd74e.js => editor.7b30ca1e00c791c0.js} +12 −12

Intent

Human

<task-notification> <task-id>bhm8fjmvz</task-id> <tool-use-id>toolu_015TrH1JK3X54PhCG9pAXqJs</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/b67dca22-59bf-4290-8b46-545183f24ea9/tasks/bhm8fjmvz.output</output-file> <status>failed</status> <summary>Background command "Re-run the full CI gate" failed with exit code 1</summary> </task-notification>

Human

<task-notification> <task-id>botigsb37</task-id> <tool-use-id>toolu_017LSrM7qzzMDvUjdDRCp8qZ</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/b67dca22-59bf-4290-8b46-545183f24ea9/tasks/botigsb37.output</output-file> <status>completed</status> <summary>Background command "Re-run the gate with a writable Go cache" completed (exit code 0)</summary> </task-notification>

Human

<task-notification> <task-id>bjjhh9p11</task-id> <tool-use-id>toolu_01DEBLEM9aHQpkjDmC8kXs5L</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/b67dca22-59bf-4290-8b46-545183f24ea9/tasks/bjjhh9p11.output</output-file> <status>completed</status> <summary>Background command "Poll hub CI until it finishes" completed (exit code 0)</summary> </task-notification>

Human

=== text/html === <b id="docs-internal-guid-c8d9abf1-7fff-bd58-9d7a-e16744140628" style="font-size: medium; font-style: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none; caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-weight: normal;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial, sans-serif; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-alternates: normal; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial, sans-serif; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-alternates: normal; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial, sans-serif; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-alternates: normal; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial, sans-serif; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-alternates: normal; font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Four</span></p></b><br class="Apple-interchange-newline" style="font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none; caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0);"><br class="Apple-interchange-newline" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;"> === text/plain === "One\nTwo\nThree\n\nFour\n"

Human

make a plan to handle images, links, and tables. Images can be rendered immediately and uploaded async to object storage

fixtures/editor-entry.js

Loading diff…

fixtures/paste.mjs

Loading diff…

fixtures/paste.test.mjs

Loading diff…

internal/docs/static/{editor.a4e6489413ecd74e.js => editor.7b30ca1e00c791c0.js}

Loading diff…