Skip to content

Add a Divider row to the Slash menu once a user asks for one #350

Description

@HMarzban

Parent

#328. Related: #251

What to build

A writer who does not know Markdown has no visible way to add a Divider. Typing --- on an empty line already works, and a heading is too heavy: it adds a Section, a TOC entry and a heading chat.

A "Divider" row in the Slash menu would show the light way to split text. No user has asked for it yet, so this waits for one real request.

The row must never break the outline. It shows only on a paragraph directly under the document or a blockquote.

Acceptance criteria

  • A maintainer has linked one real user request for this row in a comment here.
  • On a top-level empty paragraph, /div shows one row "Divider". /hr, /line, /rule and /separator also find it.
  • Picking it inserts a Divider, and the caret sits on a new empty paragraph under it. The caret does not jump into the next heading.
  • The row shows on an empty paragraph inside a blockquote.
  • The row is absent in an empty heading, a list item and a task item.
  • One Mod-z after the pick gives back the /div text.
  • Two new Cypress cases pass, each proved by sabotage.
  • Slash menu to insert a block #251's row list notes the added row.

Blocked by

Agent brief

Type: HITL — a maintainer must link one real user request for a Divider row (an issue or a written report) in a comment here. Build nothing before that comment exists. The maintainer also notes the added row on #251.

Category: enhancement

Current behavior:

  • The pad loads StarterKit's horizontalRule node, and --- on an empty line already inserts one (@tiptap/extension-horizontal-rule 3.31.3 src/horizontal-rule.ts:131).
  • The Slash menu has no Divider row (apps/webapp/src/components/TipTap/slash/slashItems.ts:49-119).
  • insertContentAt replaces an empty non-code textblock with block content (@tiptap/core 3.31.3 src/commands/insertContentAt.ts:149-156). So a Divider picked in an empty heading replaces the heading, and its Section goes with it.
  • canInsertNode walks up to the document and returns true almost everywhere (@tiptap/core src/utilities/canInsertNode.ts). The pad doc is heading block* (TipTap/extensions/title-document/title-document.ts:14), so it accepts a Divider at any caret. So e.can().setHorizontalRule() hides nothing.
  • A list item must start with a paragraph (paragraph block*, @tiptap/extension-list 3.31.3 src/item/list-item.ts:72). A pad task item is the same, because the pad sets nested: true (TipTap/TipTap.tsx, TaskItem.configure; src/task-item/task-item.ts:104). So a Divider there can leave a stray empty bullet (unverified).
  • setHorizontalRule moves the caret into the next textblock after the insert (horizontal-rule.ts:90-98).

Desired behavior:

  • Row: id divider, label "Divider", icon Icons.minus, aliases hr, line, rule, separator.
  • run: editor.chain().focus().insertContent([{ type: 'horizontalRule' }, { type: 'paragraph' }]).run(). Where the caret lands is unverified in a browser. Cypress case 1 proves it.
  • Do not use bare setHorizontalRule(). It moves the caret into the next textblock, often the next Section's heading.
  • can: true only when the caret's textblock is a paragraph whose parent is doc or blockquote. Walk only the caret depth.
  • Do not call dismissSoftKeyboard. Unlike the media row, this row opens no second surface, so the phone keyboard stays up.
  • If Show the Markdown shortcut on each Slash menu row that has one #346 has shipped, set hint: '---' on the row.

Where to start:

Line numbers are hints as of 2026-09-28; the agent searches by symbol.

Rules that apply:

  • CONTEXT.md §Pad outline: "Divider" is the horizontalRule node in the pad (Show a visible outline on a selected Divider #340 adds the line).
  • CONTEXT.md §Pad tools: each Slash row runs an existing editor command.
  • apps/webapp/src/components/TipTap/CLAUDE.md §Heading Schema: the schema is flat, so a top-level paragraph's parent is doc, and a heading owns the blocks after it.
  • apps/webapp/src/components/TipTap/CLAUDE.md §Editor Performance: the can check runs on every items() call, so keep it to the caret depth.
  • AGENTS.md §Test Policy, case (c): each case names one failure. Case 1: typing after a pick lands in the next Section's heading. Case 2: a pick in an empty heading deletes its Section. Prove each by sabotage.
  • AGENTS.md §Test Orchestration And Authoring: use realType, and gate on focus before cy.realPress.

Verify:

  • bun run check
  • Start the local stack with make dev-local. Note the webapp port; it is not always 3000. From apps/webapp, run bunx cypress run --spec cypress/e2e/editor/slash/slash-menu.cy.ts --env EDITOR_BASE_URL=http://localhost:<port>.
  • Case 1: startIn('<h1>Doc</h1><p></p><h2>Next</h2>'). Type /div, press Enter, then type x. x lands in a paragraph after the Divider, and the H2 still reads "Next".
  • Case 2: startIn('<h1>Doc</h1><h2></h2><p>after</p>', 'heading'), so the caret is in the empty H2. Type /div. No [data-slash-item="divider"] shows.
  • Sabotage: swap run for bare setHorizontalRule(), and case 1 fails. Swap can for e.can().setHorizontalRule(), and case 2 fails. Put both back.
  • Desktop browser on the /editor playground, light and dark (set data-theme on <html>):
    • Type /hr, /line, /rule and /separator on a top-level empty line. Each shows the Divider row.
    • Pick it. The Divider shows. Backspace from the line under it shows the soft ring from Show a visible outline on a selected Divider #340.
    • On a new empty line, type /div and press Enter. Press Mod-z once. The line reads /div again.
    • In an empty blockquote line, /div shows the row. In an empty bullet item and an empty task item, it does not.
  • Phone: a narrow window does not produce the mobile shell. Use a real phone, or a mobile user agent on a real pad route. Tap the row. The keyboard stays up.

Out of scope

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 & ProsemirrorIdea

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions