editor-entry.js: say correctly what Shift+Enter used to do
Review finding. The keymap comment said the keystroke "reached no command at all and fell through to the browser". The first half is right and the second is false, and the false half is the one a maintainer would act on: it says the pre-fix behaviour was native contenteditable editing, which would have inserted a `<br>` that the schema's own parseDOM re-parses as a hard_break — i.e. that the break sometimes worked before this binding existed. It never could. prosemirror-view's captureKeyDown returns true for keyCode 13 unconditionally, shift or not (dist/index.js:2787), and its caller preventDefaults whenever handleKeyDown fails but captureKeyDown succeeds (:3217). So Shift+Enter matched no binding, was captured and preventDefaulted, and did nothing whatsoever. The event never reached the browser. The comment now says that, and says the wrong reading out loud so the next person does not have to re-derive why it is wrong. Nothing else in that comment changed — the rest was verified correct. The bundle rebuild is byte-identical, since esbuild strips comments.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fixtures/editor-entry.js+13 −2
Intent
<task-notification> <task-id>aa50c41a9659b8b6e</task-id> <tool-use-id>toolu_01JXva2s5feXvTzr1BhAadnU</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/4a30c557-129a-4e57-8aa3-0c7518c36cf1/tasks/aa50c41a9659b8b6e.output</output-file> <status>completed</status> <summary>Agent "Branch 1: typography, line break, PDF margin" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>**Status: all three fixes done, gate green.** Report at `.superpowers/typography-report.md`. Commits on `worktree-typography`: - `d708412` PDF: draw text one margin in from the page edge (FIX 3) - `b76eb50` Paragraphs stack like lines, not like blocks (FIX 1) - `24f5376` Shift+Enter inserts a line break, end to end (FIX 2) - `6c5898e` one-word comment fix in show.html (a positional claim I'd written was false) Tests: 7 new (1 PDF margin, 2 Rust hard_break, 4 Markdown hard_break, 2 PDF hard_break — 9 total), each watched failing first; the one that passed vacuously on its first run (Markdown trailing break) was re-run against the naive emitter so it could fail honestly before the trim went in. Existing `TestPageGeometryMatchesTheStylesheet` also caught the CSS/PDF drift as intended. Gate exit 0, run again on the final tree. Concerns: `1lh` needs Chrome 109/Safari 16.4/Firefox 120 and has no fallback; nothing drove a real browser, so the `hard_break` node name is proven by reading y-prosemirror/schema-basic sources plus Rust tests, not end to end; I zeroed `paragraphSpaceBelow`/`listItemSpace`/`listSpaceBelow` in pdf.go and removed the `li` margin rule — both beyond the letter of FIX 1, argued in the report; nothing pins those three PDF constants to the stylesheet.</result> <usage><subagent_tokens>228535</subagent_tokens><tool_uses>152</tool_uses><duration_ms>1920428</duration_ms></usage> </task-notification>
<task-notification> <task-id>a1aeb4cbab56cf868</task-id> <tool-use-id>toolu_01MNoaS1qAq9NJUXmvKApcHY</tool-use-id> <output-file>/home/paulca/.cache/tmp/claude-1001/-home-paulca-amadan-net-oficina-docs/4a30c557-129a-4e57-8aa3-0c7518c36cf1/tasks/a1aeb4cbab56cf868.output</output-file> <status>completed</status> <summary>Agent "Fable review of typography branch" finished</summary> <note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note> <result>All tests pass. I have verified every load-bearing claim against the actual sources. Here is the review. --- ## Verdict: approve with changes One change is required by this repo's own standard (a false mechanism claim in a comment — the branch's fourth commit exists precisely because that standard is taken seriously); one test extension is strongly recommended. Everything else is sound. The core engineering — the margin fix, the `p:empty` reasoning, the hard_break plumbing, the Markdown break form — checks out against the real sources, not just the report's account. ## Findings, most severe first ### 1. Comment states a false mechanism: Shift+Enter never "fell through to the browser" (genuine defect, small fix) `fixtures/editor-entry.js` (keymap comment, ~line 345): "so Shift+Enter reached no command at all and fell through to the browser." That is not what happened. `prosemirror-view/dist/index.js:2787-2789` — `captureKeyDown` returns `true` for `code == 13` (Enter) unconditionally, shift or no shift — and line 3217-3219 then calls `event.preventDefault()` whenever `handleKeyDown` fails but `captureKeyDown` succeeds. So pre-fix, Shift+Enter was swallowed: preventDefaulted with no command run. The browser's contenteditable editing never acted. Failure scenario: a maintainer debugging key handling reads this comment and concludes the pre-fix behavior was browser-native `<br>` insertion — which the schema's `parseDOM` would have re-parsed as a `hard_break`, i.e. that the break "sometimes worked" before. It never could. The rest of the comment (baseKeymap has Enter and Mod-Enter only; the shift-less fallback is for single-character keys) is verified correct — only the final clause is wrong. Smallest fix: replace "fell through to the browser" with "was then captured and preventDefaulted by prosemirror-view, which treats every Enter as handled (captureKeyDown), so the keystroke did nothing at all." ### 2. The three zeroed PDF constants are unpinned, and the branch raises the stakes on that gap (recommended change) Confirmed: `TestPageGeometryMatchesTheStylesheet` (`internal/docs/export_test.go:196-232`) reads eight values — page width/height/margin, body size, line-height, three size marks. `paragraphSpaceBelow`, `listItemSpace`, `listSpaceBelow` (`internal/export/pdf.go:59-65`) are reached by none of its regexes. The implementer says so; the report is accurate. Failure scenario: someone restores `[docs-text] p { margin-block: 0 1em }` in show.html (a plausible "revert the density change" edit). `BodyLeading` drift would be caught; the margin drift would not — no test fails, and the PDF silently becomes a differently-spaced document while pdf.go's own header comment ("all mirroring show.html's stylesheet") asserts it cannot. The gap predates this branch in kind (1.0/0.35 were never pinned either), but this branch made the constants load-bearing zeroes and wrote a comment promising the mirror is complete. Smallest fix: three more cases in the geometry test's table — `\[docs-text\] p \{[^}]*margin-block:\s*0\s*[;}]` pinned against `paragraphSpaceBelow == 0`, likewise `ul, ol`, and an assertion that no `[docs-text] li {` margin rule exists. The test's own "must FIND its pattern" convention handles restructuring. ### 3. Narrow regression: a whitespace-only paragraph now vanishes from the read view (record, or fix later) `internal/docs/views/show.html:280-285`. `:empty` in every shipped browser means no children at all — a text node of one space defeats it. An author who types a single space on an otherwise blank line produces `<p> </p>`: visible as a line in the editor (ProseMirror preserves it), zero height in the read view (whitespace collapses, `:empty` doesn't match, margin is now 0). Pre-branch it showed as a ~1em gap via the margin. This is the same editor/read-view asymmetry the `p:empty` rule exists to close, still open for the whitespace case — and the PDF agrees with the read view (a space token never opens a line, so `paragraph` draws and advances nothing), so two of three renderers agree. No cheap CSS fix exists; normalizing whitespace-only paragraphs at render time would be the real fix. Obscure enough to record rather than block. ### 4. Verified sound — the points the review was asked to be hardest on - **`p:empty` reasoning: true.** `prosemirror-view/dist/index.js:1989-2016` — `addTextblockHacks` inserts `<br class="ProseMirror-trailingBreak">` into every empty textblock (`!lastChild` branch), so the editor's empty `<p>` has a child and never matches `:empty`. The read view's `<p></p>` is pinned byte-exact by `empty_block_is_preserved_like_html` (`core/tests/fragment.rs:126`) and written through as `{{.HTML}}` with no template whitespace (`show.html:676`, one line). And even if `:empty` ever matched in the editor, `min-block-size: 1lh` equals the natural line height — the "phantom height" failure mode is impossible by construction. - **PDF margin: all sites covered.** The whole exporter draws through exactly one `Page.Text` call (`pdf.go:582`, inside `l.draw`) and one `Page.Rule` call (`pdf.go:557`). Every x fed to them comes from the three fixed sites (marker at 426, rule at 557, text at 561); the title renders through the same `block`→`paragraph`→`drawLine` path, and the overlong-word path (`pdf.go:515`) also lands in `drawLine`. `maxWidth := contentWidthPt - ctx.indent` is correct: `contentWidthPt = PageWidthPt - 2*MarginPt` already excludes both margins, and `indent` stays margin-relative — the comment at `pdf.go:197-204` states the invariant that keeps it so. Spot-check: top-level quote rule lands at `72 + 12 − 12 = MarginPt`, which the test asserts. - **hard_break contract: the sources support it.** `prosemirror-schema-basic/dist/index.js:106` defines `hard_break`; `y-prosemirror/dist/y-prosemirror.cjs:949` is `new Y.XmlElement(node.type.name)` verbatim. HTML (`<br>`, void, children rendered not dropped), text (one `\n`, early-return before the block logic), JSON (no change needed — childless elements already emit `{"type":"hard_break","content":[]}`, which the Go tests consume by that name), PDF (`brk` token), Markdown all agree on the name. The report's caveat stands: no browser drove it end to end, and a `hard_break` vector in `generate-fragment.mjs` would be cheap corroborating evidence from the JS side. - **Markdown form: backslash-newline, and it round-trips.** CommonMark's backslash-before-line-ending hard break; survives trailing-whitespace stripping, unlike two spaces. The trailing-drop (`TrimSuffix` loop, not `TrimRight` — verified it preserves an author's escaped `\\`), heading-flatten (verified `ReplaceAll` leaves an escaped backslash intact: `a\\` + break → `a\\ `), and continuation-indent behaviors are each correct and each tested against a watched failure. - **Scope creep: each piece was required.** `listItemSpace`/`listSpaceBelow`/`paragraphSpaceBelow` zeroed: with `[docs-text] p` at zero on screen, leaving any of them would make the PDF a differently-spaced document than the page while the file claims to mirror it — and the `li` rule removal is forced by the inversion argument (items at 0.35em would be looser than paragraphs at 0), which I checked against the final stylesheet and accept. Keeping the constants named at 0 rather than deleting them is the right call. ### 5. Test quality: no sixth vacuous test found I hunted for one. All seven new tests have shown failing output against the exact mistake they guard, including the re-driven `TestMarkdownDropsAHardBreakAtTheEndOfAParagraph`. The position-reading `runs()` regex (`pdfread_test.go`) is coupled to `internal/pdf`'s exact operator format, but a format change makes it return nothing and every consumer `Fatal`s on the length check ("nothing was drawn" / "got 0 runs, want 2") — loud, not silent. The one assertion that never failed on this branch is the JSON third of `renders_a_hard_break_in_every_renderer`; it pins pre-existing childless-element behavior that the Go exporters now depend on, which is a legitimate pin, and it travels with two assertions that did fail. I re-ran the export, geometry, and Rust fragment suites: all pass. ## Worth recording (no action needed) - **Quote rule gap at blank lines in the PDF.** `advance` (`pdf.go:572`) takes `ctx` and ignores it, so a blank line inside a blockquote (consecutive hard breaks — newly reachable; empty paragraph — pre-existing) gets no rule segment where the screen's border is continuous. - **String-level break-trimming has one unreachable dangling-escape.** A text run ending in a literal backslash followed by a raw `\n` (raw Yjs bytes only; `escapeMarkdown` passes `\n` through) forms a `\\\n` suffix the paragraph trim mistakes for a hard break, leaving a dangling `\`. Node-level trimming (drop trailing `hard_break` nodes before `inlineString`) would eliminate the ambiguity if this class ever matters. - **`1lh` with no fallback**: failure mode on pre-2023 browsers is exactly the pre-fix behavior; I agree with omitting the duplicated-value fallback. - **Pagination-test headroom**: `TestPDFNumbersEveryListItemAcrossAPageBreak` now clears its ≥2-page threshold with little slack after the density change; a longer fixture would future-proof it. - The commit-message miscount ("five places", four bullets) in 24f5376 — correctly left unamended. The implementer's report is unusually honest and its uncertain points (§1-7) are all real and all correctly weighted. Fix finding 1, preferably add finding 2's three test cases, and merge.</result> <usage><subagent_tokens>88307</subagent_tokens><tool_uses>28</tool_uses><duration_ms>356983</duration_ms></usage> </task-notification>
diff --git a/fixtures/editor-entry.js b/fixtures/editor-entry.js| index 1ea56ea..5dd4f35 100644 |
| --- a/fixtures/editor-entry.js |
| +++ b/fixtures/editor-entry.js |
| @@ -350,8 +350,19 @@ export async function mountEditor(el) { |
| // baseKeymap has Enter and Mod-Enter only, and |
| // prosemirror-keymap looks up the full modified name for a |
| // named key like Enter (its shift-less fallback is for |
| - // single-character keys), so Shift+Enter reached no command at |
| - // all and fell through to the browser. |
| + // single-character keys), so the keystroke matched no binding, |
| + // was then captured and preventDefaulted by prosemirror-view, |
| + // which treats every Enter as handled (captureKeyDown returns |
| + // true for keyCode 13 whether or not shift is down, and its |
| + // caller preventDefaults whenever handleKeyDown fails and |
| + // captureKeyDown succeeds), and so did nothing at all. |
| + // |
| + // Worth stating precisely, because the plausible wrong reading |
| + // is that the keystroke fell through to the browser's own |
| + // contenteditable handling, inserted a native <br>, and was |
| + // re-parsed into this node by the schema's parseDOM — i.e. |
| + // that a break sometimes worked before this binding existed. |
| + // It never could: the event never reached the browser. |
| 'Shift-Enter': insertHardBreak, |
| }), |
| keymap(baseKeymap), |