You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add a Divider row to the Slash menu once a user asks for one #350
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.
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/coresrc/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).
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.
apps/webapp/cypress/e2e/editor/slash/slash-menu.cy.ts: add the two cases in describe('Slash menu (desktop)'). Use the file's startIn(html, blockType) helper.
Line numbers are hints as of 2026-09-28; the agent searches by symbol.
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.
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.
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
/divshows one row "Divider"./hr,/line,/ruleand/separatoralso find it./divtext.Blocked by
slash/slashContext.tsfor the row check.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:
horizontalRulenode, and---on an empty line already inserts one (@tiptap/extension-horizontal-rule3.31.3src/horizontal-rule.ts:131).apps/webapp/src/components/TipTap/slash/slashItems.ts:49-119).insertContentAtreplaces an empty non-code textblock with block content (@tiptap/core3.31.3src/commands/insertContentAt.ts:149-156). So a Divider picked in an empty heading replaces the heading, and its Section goes with it.canInsertNodewalks up to the document and returns true almost everywhere (@tiptap/coresrc/utilities/canInsertNode.ts). The paddocisheading block*(TipTap/extensions/title-document/title-document.ts:14), so it accepts a Divider at any caret. Soe.can().setHorizontalRule()hides nothing.paragraph block*,@tiptap/extension-list3.31.3src/item/list-item.ts:72). A pad task item is the same, because the pad setsnested: true(TipTap/TipTap.tsx,TaskItem.configure;src/task-item/task-item.ts:104). So a Divider there can leave a stray empty bullet (unverified).setHorizontalRulemoves the caret into the next textblock after the insert (horizontal-rule.ts:90-98).Desired behavior:
divider, label "Divider", iconIcons.minus, aliaseshr,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.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 isdocorblockquote. Walk only the caret depth.dismissSoftKeyboard. Unlike the media row, this row opens no second surface, so the phone keyboard stays up.hint: '---'on the row.Where to start:
slash/slashItems.ts: add the row after Code Block. Update the comment at:48, which says the names match Slash menu to insert a block #251.slash/slashContext.ts: add the paragraph-parent check beside the checks from Undo a Slash pick with one Mod-z, and hide rows that lift their own block #332. If Show the Markdown shortcut on each Slash menu row that has one #346 has shipped, it holdsisTopLevelParagraph(editor). Reuse it for thedochalf, and add only theblockquotehalf.apps/webapp/cypress/e2e/editor/slash/slash-menu.cy.ts: add the two cases indescribe('Slash menu (desktop)'). Use the file'sstartIn(html, blockType)helper.Line numbers are hints as of 2026-09-28; the agent searches by symbol.
Rules that apply:
CONTEXT.md§Pad outline: "Divider" is thehorizontalRulenode 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 isdoc, and a heading owns the blocks after it.apps/webapp/src/components/TipTap/CLAUDE.md§Editor Performance: thecancheck runs on everyitems()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: userealType, and gate on focus beforecy.realPress.Verify:
bun run checkmake dev-local. Note the webapp port; it is not always 3000. Fromapps/webapp, runbunx cypress run --spec cypress/e2e/editor/slash/slash-menu.cy.ts --env EDITOR_BASE_URL=http://localhost:<port>.startIn('<h1>Doc</h1><p></p><h2>Next</h2>'). Type/div, press Enter, then typex.xlands in a paragraph after the Divider, and the H2 still reads "Next".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.runfor baresetHorizontalRule(), and case 1 fails. Swapcanfore.can().setHorizontalRule(), and case 2 fails. Put both back./editorplayground, light and dark (setdata-themeon<html>):/hr,/line,/ruleand/separatoron a top-level empty line. Each shows the Divider row./divand press Enter. Press Mod-z once. The line reads/divagain./divshows the row. In an empty bullet item and an empty task item, it does not.Out of scope