What happened
Edward Saperia reported this on 2026-09-26. He wrote: "the paragraphs thing is unintuitive though".
He wrote: "I guess these are all separate html paragraphs, but that's not how text editors usually work I think".
Under the heading Table C, each short line is its own paragraph. The spacing between every line looks the same and looks large. A selected line paints one highlight strip. Lines that belong together sit far apart.
He also wrote: "I use enter and shift enter to do this, but it's awkward to manipulate because you can't 'un-enter'".
The maintainer already stated the direction. He wrote: "If you want to stay within the same paragraph, we usually use Shift + Enter. It adds a line break without creating a new paragraph, so the content remains part of the same
tag. If you press Enter, it creates a new paragraph (
), so in this case, I think our best option is to adjust the spacing between paragraphs. I'll dig into it more and get it right."
What we expect
Enter still starts a new paragraph. Shift+Enter still inserts a line break in the same paragraph.
The spacing between paragraphs is smaller, so short lines do not look far apart.
One action turns a paragraph break into a line break. Another action turns a line break into a paragraph break. Neither action drops text.
Steps to reproduce
- Open a pad you can edit.
- Under a heading, press Enter after each short line, as in the screenshots.
- Compare that spacing with one paragraph that contains a Shift+Enter line break.
- Put the caret at the start of the second paragraph and press Backspace.
- Press Shift+Enter, then try to turn that line break into a new paragraph in one step.
The screenshots are not attached. The maintainer will add them.
Findings
Spacing comes from the stylesheet. Read from code, not measured.
- Every block after the first has
margin-top: 0.75em. The rule is .editorWrapper .ProseMirror > * + * in apps/webapp/src/styles/_blocks.scss.
- Tailwind 4.3.3 preflight sets
margin: 0 on every element. The pad imports it from apps/webapp/src/styles/globals.scss. The HTML default margin on p does not apply.
- Pad paragraphs set no
line-height. They inherit line-height: 1.5 from html in that preflight.
- Headings set
line-height: 1.15 in apps/webapp/src/styles/editor/_heading-node.scss.
- A body heading sets its own
margin-top. An h2 uses 1.15em. An h3, h4, h5, or h6 uses 1em. A later h1 uses 1.33em. Those rules beat 0.75em on the heading.
- The paragraph after a heading still has
margin-top: 0.75em. Two paragraphs have the same 0.75em. The document title also sets margin-bottom: 0.5em.
- A paragraph inside a table has
margin: 0 in apps/webapp/src/styles/_prose-mirror-body.scss. A subtitle paragraph sets margin-top: 0.35em.
- A chat message sets
p { margin: 0; line-height: 1.5rem } in globals.scss. The composer sets paragraph margin: 0. Desktop line height is 2rem. Mobile line height is 2.75rem. Both live in apps/webapp/src/styles/_chat-editor.scss.
Keys were measured in headless Jest with the pad stack. They were not measured in a browser.
- StarterKit
HardBreak binds Shift-Enter and Mod-Enter to setHardBreak. The pad leaves both on.
Shift-Enter and Mod-Enter insert a hardBreak in the same paragraph.
- Enter runs
splitBlock and creates a new paragraph. Backspace at the start of that empty paragraph removes it.
- Backspace at the start of a paragraph after a paragraph joins the two paragraphs. The text stays. No line break appears.
- Delete at the end of a paragraph before another paragraph does the same join.
- Backspace at the start of a paragraph after a heading joins that text into the heading.
- Delete at the end of the last paragraph changes nothing.
- No command turns a paragraph break into a line break, or a line break into a paragraph break. The nearby commands are
setHardBreak, splitBlock, joinBackward, and joinForward.
- The Backspace keymap does not delete a
hardBreak. A beforeinput event in jsdom also left the break in place. What a browser does with that key is read from code, not measured.
- HTML keeps the break.
setContent of <p>Discussion:<br>Why</p> returns the same HTML, with a hardBreak in the document.
- Markdown keeps the break.
MarkdownManager with StarterKit writes Discussion: \nWhy and parses that back to a hardBreak. Those are the HardBreak hooks used by apps/hocuspocus.server/src/modules/document-conversion/domain/markdownExport.ts.
- The chat composer does not share Shift+Enter. This was read from source, not run in the probe.
insertComposerNewline in useTiptapEditor.ts inserts a new paragraph. The handler runs before extension shortcuts, so HardBreak does not see Shift+Enter. Mod+Enter returns early and HardBreak still inserts the break.
HeadingFold handles only ArrowUp and ArrowDown, and only when a section is folded. HeadingActions has no key handler. Indent handles Tab and Shift-Tab only. ParagraphStyle handles Enter only at the end of a subtitle.
- After an edit, StarterKit
TrailingNode appends an empty h1. The document content expression starts with heading, so that is the node it appends. This is not the reported spacing. Leave it alone.
No earlier issue, discussion, or settled ruling covers this spacing.
Reference points
Acceptance criteria
Spacing:
Turning a break around:
Blocked by
None — can start now.
Agent brief
Type: HITL for the spacing value. AFK for the two conversions and their tests, after that value is chosen.
Category: bug
Current behavior: Enter creates a paragraph. Shift+Enter inserts a hardBreak in that paragraph. Every following block has margin-top: 0.75em, so each paragraph looks far from the last. Backspace at the start of a paragraph joins it to the block above. No command turns one break into the other.
Desired behavior: Paragraph spacing is smaller, and the maintainer has named the value. Enter and Shift+Enter keep their present jobs. One action turns a paragraph break into a line break. One action turns a line break into a paragraph break. Other blocks keep their spacing.
Where to start: apps/webapp/src/styles/_blocks.scss (.editorWrapper .ProseMirror > * + *). apps/webapp/src/styles/editor/_heading-node.scss (heading margin-top). apps/webapp/src/components/TipTap/TipTap.tsx (StarterKit, HardBreak left on). apps/webapp/src/components/TipTap/extensions/paragraph-style/paragraph-style.ts (Enter only for a subtitle). @tiptap/extension-hard-break (setHardBreak, Shift-Enter, Mod-Enter). Do not change TrailingNode. Search by symbol. File names are hints as of 2026-09-29.
Rules that apply: apps/webapp/src/components/TipTap/CLAUDE.md §Editor Architecture. AGENTS.md §Test Policy allows a Cypress check and a Jest check here, because they pin a reported bug. Run only those new tests. Do not run the whole webapp Jest suite. apps/webapp/src/components/chatroom/CLAUDE.md §MessageComposer: the composer Shift+Enter path is separate. Do not change it in this task.
Verify: Start make dev-local and confirm the webapp port. Build the lines from Steps to reproduce. In the browser, read getComputedStyle for margin-top on two paragraphs, the heading above them, a list, a table cell, a code block, and a blockquote. Check light and dark, then desktop. Press Shift+Enter and Backspace, and record whether the line break goes away. Run the new Cypress spec and the new Jest file and watch both pass.
What happened
Edward Saperia reported this on 2026-09-26. He wrote: "the paragraphs thing is unintuitive though".
He wrote: "I guess these are all separate html paragraphs, but that's not how text editors usually work I think".
Under the heading Table C, each short line is its own paragraph. The spacing between every line looks the same and looks large. A selected line paints one highlight strip. Lines that belong together sit far apart.
He also wrote: "I use enter and shift enter to do this, but it's awkward to manipulate because you can't 'un-enter'".
The maintainer already stated the direction. He wrote: "If you want to stay within the same paragraph, we usually use Shift + Enter. It adds a line break without creating a new paragraph, so the content remains part of the same
tag. If you press Enter, it creates a new paragraph (
), so in this case, I think our best option is to adjust the spacing between paragraphs. I'll dig into it more and get it right."
What we expect
Enter still starts a new paragraph. Shift+Enter still inserts a line break in the same paragraph.
The spacing between paragraphs is smaller, so short lines do not look far apart.
One action turns a paragraph break into a line break. Another action turns a line break into a paragraph break. Neither action drops text.
Steps to reproduce
The screenshots are not attached. The maintainer will add them.
Findings
Spacing comes from the stylesheet. Read from code, not measured.
margin-top: 0.75em. The rule is.editorWrapper .ProseMirror > * + *inapps/webapp/src/styles/_blocks.scss.margin: 0on every element. The pad imports it fromapps/webapp/src/styles/globals.scss. The HTML default margin onpdoes not apply.line-height. They inheritline-height: 1.5fromhtmlin that preflight.line-height: 1.15inapps/webapp/src/styles/editor/_heading-node.scss.margin-top. An h2 uses1.15em. An h3, h4, h5, or h6 uses1em. A later h1 uses1.33em. Those rules beat0.75emon the heading.margin-top: 0.75em. Two paragraphs have the same0.75em. The document title also setsmargin-bottom: 0.5em.margin: 0inapps/webapp/src/styles/_prose-mirror-body.scss. A subtitle paragraph setsmargin-top: 0.35em.p { margin: 0; line-height: 1.5rem }inglobals.scss. The composer sets paragraphmargin: 0. Desktop line height is2rem. Mobile line height is2.75rem. Both live inapps/webapp/src/styles/_chat-editor.scss.Keys were measured in headless Jest with the pad stack. They were not measured in a browser.
HardBreakbindsShift-EnterandMod-EntertosetHardBreak. The pad leaves both on.Shift-EnterandMod-Enterinsert ahardBreakin the same paragraph.splitBlockand creates a new paragraph. Backspace at the start of that empty paragraph removes it.setHardBreak,splitBlock,joinBackward, andjoinForward.hardBreak. Abeforeinputevent in jsdom also left the break in place. What a browser does with that key is read from code, not measured.setContentof<p>Discussion:<br>Why</p>returns the same HTML, with ahardBreakin the document.MarkdownManagerwith StarterKit writesDiscussion: \nWhyand parses that back to ahardBreak. Those are the HardBreak hooks used byapps/hocuspocus.server/src/modules/document-conversion/domain/markdownExport.ts.insertComposerNewlineinuseTiptapEditor.tsinserts a new paragraph. The handler runs before extension shortcuts, soHardBreakdoes not see Shift+Enter. Mod+Enter returns early andHardBreakstill inserts the break.HeadingFoldhandles only ArrowUp and ArrowDown, and only when a section is folded.HeadingActionshas no key handler.Indenthandles Tab and Shift-Tab only.ParagraphStylehandles Enter only at the end of a subtitle.TrailingNodeappends an empty h1. The document content expression starts withheading, so that is the node it appends. This is not the reported spacing. Leave it alone.No earlier issue, discussion, or settled ruling covers this spacing.
Reference points
margin-block: 1emonp. https://html.spec.whatwg.org/multipage/rendering.htmlAcceptance criteria
Spacing:
0.75em. The chosen value is written on this issue before the change ships.margin-topof a body paragraph and fails if it is still0.75em.Turning a break around:
Blocked by
None — can start now.
Agent brief
Type: HITL for the spacing value. AFK for the two conversions and their tests, after that value is chosen.
Category: bug
Current behavior: Enter creates a paragraph. Shift+Enter inserts a
hardBreakin that paragraph. Every following block hasmargin-top: 0.75em, so each paragraph looks far from the last. Backspace at the start of a paragraph joins it to the block above. No command turns one break into the other.Desired behavior: Paragraph spacing is smaller, and the maintainer has named the value. Enter and Shift+Enter keep their present jobs. One action turns a paragraph break into a line break. One action turns a line break into a paragraph break. Other blocks keep their spacing.
Where to start:
apps/webapp/src/styles/_blocks.scss(.editorWrapper .ProseMirror > * + *).apps/webapp/src/styles/editor/_heading-node.scss(headingmargin-top).apps/webapp/src/components/TipTap/TipTap.tsx(StarterKit,HardBreakleft on).apps/webapp/src/components/TipTap/extensions/paragraph-style/paragraph-style.ts(Enteronly for a subtitle).@tiptap/extension-hard-break(setHardBreak,Shift-Enter,Mod-Enter). Do not changeTrailingNode. Search by symbol. File names are hints as of 2026-09-29.Rules that apply:
apps/webapp/src/components/TipTap/CLAUDE.md§Editor Architecture.AGENTS.md§Test Policy allows a Cypress check and a Jest check here, because they pin a reported bug. Run only those new tests. Do not run the whole webapp Jest suite.apps/webapp/src/components/chatroom/CLAUDE.md§MessageComposer: the composer Shift+Enter path is separate. Do not change it in this task.Verify: Start
make dev-localand confirm the webapp port. Build the lines from Steps to reproduce. In the browser, readgetComputedStyleformargin-topon two paragraphs, the heading above them, a list, a table cell, a code block, and a blockquote. Check light and dark, then desktop. Press Shift+Enter and Backspace, and record whether the line break goes away. Run the new Cypress spec and the new Jest file and watch both pass.