Skip to content

Paragraph spacing is large, and a break cannot be turned around #370

Description

@HMarzban

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

  1. Open a pad you can edit.
  2. Under a heading, press Enter after each short line, as in the screenshots.
  3. Compare that spacing with one paragraph that contains a Shift+Enter line break.
  4. Put the caret at the start of the second paragraph and press Backspace.
  5. 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:

  • The spacing between two body paragraphs is smaller than 0.75em. The chosen value is written on this issue before the change ships.
  • Short lines under a heading no longer sit as far apart as in Edward's screenshots.
  • The spacing between a heading and the paragraph after it stays clear of the paragraph spacing.
  • Lists, headings, tables, code blocks, and blockquotes keep the spacing they have today.
  • A Cypress check reads the computed margin-top of a body paragraph and fails if it is still 0.75em.
  • That check also reads a list, a heading, a table cell, a code block, and a blockquote. Their spacing still matches the values recorded before the change.

Turning a break around:

  • One action turns a paragraph break into a line break and keeps both lines of text.
  • One action turns a line break into a paragraph break and keeps both lines of text.
  • Backspace at the start of a paragraph after a paragraph still joins the text into one paragraph.
  • In a browser, Backspace just after Shift+Enter removes the line break. Write the result on this issue.
  • A Jest check on the pad stack fails unless both conversions keep the text.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    EditorTiptap & ProsemirrorUIUrgentbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions