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
#367 and #369 change how tall a heading is drawn. A shorter heading has a shorter hover band. The two issues meet this one on h3 to h6.
A search of issues and discussions found no open request for this hover.
What happened
This work is planned for the next production release of the webapp.
Edward Saperia wrote on 2026-09-26: "It should show whenever I'm over the section, not just the heading".
He also wrote: "for small headings (h3 etc) the vertical height where it shows is really small".
The button is a blue speech-bubble icon in a white circle. It sits on the border between the white document sheet and the grey-blue gutter.
The screenshot is not attached. The maintainer will drag it into this issue.
What we expect
On a desktop, the button shows when the pointer is anywhere over that heading's section.
The section is the heading plus the top-level blocks that follow it, up to the next heading of the same or higher level.
The button stays on the sheet border. The pointer can move from the words to the button, and the button stays shown.
For an h3, h4, h5, or h6, the band that reveals the button is at least 24 CSS pixels tall.
Steps to reproduce
Open an editable pad on a desktop.
Add an h3, then two paragraphs under it.
Rest the pointer on the heading line. The chat button shows.
Move the pointer onto a paragraph under that heading. The button hides.
Repeat with an h6. The heading line is about 18px tall, so the band is easy to miss.
Findings
Measured in a browser on 2026-09-29. The theme was light (docsplus) only. Dark theme was not measured.
The route was /editor. That shell carries history_editor, which hides .ha-wrap. The class was removed in the browser tab only, so the hover rules could run. No file in the repo was changed.
The viewport was 882px wide. The root font size was 16px. The sheet end pad was 2rem. (hover: hover) and (pointer: fine) matched.
The reveal rule lives in apps/webapp/src/styles/components/_heading-actions.scss.
On a fine pointer, the button shows in three cases. The heading matches :hover. .ha-wrap matches :hover. The heading matches :focus-within.
The same block also has .ha-wrap:not(.has-selection):hover .ha-single.
Until one of those matches, .ha-single has opacity 0, visibility hidden, and pointer-events none.
The hover box over the words is the heading border box. Vertical padding was 0. The margin above the heading is outside that box.
A forced :hover on an h6 set the button to opacity 1 and visibility visible.
A forced :hover on the paragraph under an h3 left that button at opacity 0 and visibility hidden.
Eight pixels above the h6 words, the hit target was the editor.
In the gutter, the same offset hit .ha-wrap. That hit is the ::before bridge on .ha-wrap. The bridge was 44px tall and 110px wide.
The bridge covers the path from the heading to the button. It does not cover the blocks under the heading.
The button was 44 by 44 CSS pixels. That is size-11 and $ha-hit-size (2.75rem).
.ha-wrap comes from createHoverChatPlugin in hoverChatPlugin.ts. TipTap.tsx sets hoverChat from editable.
The table below is one section that held h1 through h6, so HeadingScale set the sizes. Line height is 1.15. Hover height is offsetHeight.
Heading
Size
Font size
Line box
Hover height
Margin above
Title
28pt painted
37.33px
42.93px
43px
49.65px
h1
20pt
26.67px
30.66px
31px
35.47px
h2
18.4pt
24.53px
28.21px
28px
28.21px
h3
16.8pt
22.4px
25.76px
26px
22.4px
h4
15.2pt
20.27px
23.3px
23px
20.27px
h5
13.6pt
18.13px
20.85px
21px
18.13px
h6
12pt
16px
18.4px
18px
16px
Title is the first h1. CSS paints it at 28pt. The plugin still writes --hd-size: 20pt on it.
The h1 row is the first heading of a section that also holds h2 through h6. Its rank size is 20pt.
h4, h5, and h6 hover boxes are under 24 CSS pixels. The h3 box is 26px. The button is 44px, and it stays hidden until the pointer hits the line or the gutter bridge.
computeSection in compute-section.ts returns the heading and the top-level siblings that follow it. It stops at the next heading of the same or higher level, or at the end of the document.
Those blocks are siblings. A :hover rule on the heading does not include them.
Read from code, not measured. A folded block has the class heading-fold-hidden and display: none. A hidden block is not a hover target.
Unread was measured. A button with data-unread-count set to 3 reached opacity 1 and visibility visible after the 0.15s fade.
Chat open is read from code, not measured. No rule ties chatRoom.headingId to .ha-wrap. The editor button does not stay shown because chat is open. The TOC row uses menu-active for that state. That row is a different control.
Focus was measured. The caret sat in an h6, and .ProseMirror had focus. The heading did not match :focus-within. The button stayed hidden. A focus() call on the hidden button did not move focus.
The button is a <button type="button"> with tab index 0. While visibility is hidden, the browser does not focus it.
Coarse pointers are read from code, not measured. Inside @media (hover: none), (pointer: coarse), .ha-single stays visible.
Mobile is read from code, not measured. _mobile.scss paints a 32 by 32 tab and forces it visible. selectionChat is off when isMobile is true.
History is read from code, not measured on a history route. hoverChat is false when the editor is not editable. .history_editor also sets .ha-wrap and .ha-selection-comment-dock to display: none.
The selection comment chip is selectionChatPlugin. It shows for a non-heading text selection on a desktop. Its class is ha-selection-comment-dock.
The chip center sits on the sheet border. The shift is translateX(calc(var(--tiptap-inline-pad-end) + 0.5px + 50%)). The docked chat panel and the TOC rail paint above the sheet. That overlap was read from code, not measured.
Notion shows a ⋮⋮ handle in the left margin when the pointer is over a block. That handle belongs to one block. The page does not describe a handle for a heading plus the blocks under it.
Google Docs help says to highlight the text, then use Add comment in the toolbar. A second page says a button appears in the right margin after you select text.
GitHub shows a comment icon when the pointer rests on one line of a pull request diff.
Confluence Cloud says an inline comment can start from highlighted text or from a hover over a section. The steps on that page cover highlighted text, and a hover on an image or a video.
Acceptance criteria
On a desktop, the button shows when the pointer is anywhere over a heading's section.
For an h3, h4, h5, or h6, the reveal band is at least 24 CSS pixels tall. The 24px floor comes from WCAG 2.2 Success Criterion 2.5.8. When blocks follow the heading, the band is the whole section.
The button stays shown while the pointer moves from the heading text to the button.
The button center stays on the sheet border.
Folded sections, the unread state, an open chat, history read-only mode, and the selection comment chip keep their current behavior.
A pointer move does not re-render React, write the store, or rebuild decorations.
Any new listener is removed when the editor is destroyed.
A Cypress spec in apps/webapp/cypress/e2e/ fails before the fix. The spec hovers a paragraph under an h3 and expects the button to show.
The check covers the light theme and the dark theme.
Blocked by
None.
Agent brief
Type: HITL for the method. AFK after the maintainer picks one.
Category: enhancement
Current behavior: The button shows on heading hover, on the gutter bridge, on unread, and on a coarse pointer. It stays hidden when the pointer is on a block under the heading.
Desired behavior: On a desktop, the pointer anywhere over the section shows the button. The button stays on the sheet border.
Where to start:apps/webapp/src/styles/components/_heading-actions.scss (the :hover block, $ha-hit-size, $ha-sheet-border-straddle-x). apps/webapp/src/components/TipTap/extensions/HeadingActions/plugins/hoverChatPlugin.ts (createHoverChatPlugin). apps/webapp/src/components/TipTap/extensions/shared/compute-section.ts (computeSection). Search by symbol. File names are hints as of 2026-09-29.
Rules that apply: Do not rebuild decorations on each pointer move. Do not write the store on each move. Remove any listener when the editor is destroyed. Keep the sheet-border straddle. Keep the chip under the docked chat panel and the TOC rail. Do not change settled TOC behavior. History read-only keeps the widgets hidden. Folded blocks stay hidden. See apps/webapp/CLAUDE.md §TOC And Heading Actions and §Pad Workspace Surfaces. See apps/webapp/src/components/TipTap/CLAUDE.md §Editor Performance and §Heading Schema.
Open choice: The maintainer has not picked a method.
One pointer listener on the editor maps the pointer to a section with computeSection, and sets one class on that heading's .ha-wrap. This path does not rebuild decorations. posAtCoords on every move can force layout. The button stays on the heading, so a long section can show it off screen.
Taller heading padding, or a pseudo-element, is CSS only. It cannot cover following siblings. A tall box over the text steals clicks.
A decoration on every block in the section matches the section model. CSS cannot select the previous heading from a hovered sibling. A wrapper or a listener is still required. A rebuild on each move breaks the performance rule.
Verify: On a desktop, hover an h3 line, then a paragraph under it, then the gutter path to the button. Check an h6. Check unread, an open chat, a folded section, history read-only, and a body text selection. Check the light theme and the dark theme. Run the new Cypress spec. Watch it fail before the fix and pass after it.
Related
#367 and #369 change how tall a heading is drawn. A shorter heading has a shorter hover band. The two issues meet this one on h3 to h6.
A search of issues and discussions found no open request for this hover.
What happened
This work is planned for the next production release of the webapp.
Edward Saperia wrote on 2026-09-26: "It should show whenever I'm over the section, not just the heading".
He also wrote: "for small headings (h3 etc) the vertical height where it shows is really small".
The button is a blue speech-bubble icon in a white circle. It sits on the border between the white document sheet and the grey-blue gutter.
The screenshot is not attached. The maintainer will drag it into this issue.
What we expect
On a desktop, the button shows when the pointer is anywhere over that heading's section.
The section is the heading plus the top-level blocks that follow it, up to the next heading of the same or higher level.
The button stays on the sheet border. The pointer can move from the words to the button, and the button stays shown.
For an h3, h4, h5, or h6, the band that reveals the button is at least 24 CSS pixels tall.
Steps to reproduce
Findings
Measured in a browser on 2026-09-29. The theme was light (
docsplus) only. Dark theme was not measured.The route was
/editor. That shell carrieshistory_editor, which hides.ha-wrap. The class was removed in the browser tab only, so the hover rules could run. No file in the repo was changed.The viewport was 882px wide. The root font size was 16px. The sheet end pad was 2rem.
(hover: hover) and (pointer: fine)matched.The reveal rule lives in
apps/webapp/src/styles/components/_heading-actions.scss.On a fine pointer, the button shows in three cases. The heading matches
:hover..ha-wrapmatches:hover. The heading matches:focus-within.The selector is:
The same block also has
.ha-wrap:not(.has-selection):hover .ha-single.Until one of those matches,
.ha-singlehas opacity 0, visibility hidden, and pointer-events none.The hover box over the words is the heading border box. Vertical padding was 0. The margin above the heading is outside that box.
A forced
:hoveron an h6 set the button to opacity 1 and visibility visible.A forced
:hoveron the paragraph under an h3 left that button at opacity 0 and visibility hidden.Eight pixels above the h6 words, the hit target was the editor.
In the gutter, the same offset hit
.ha-wrap. That hit is the::beforebridge on.ha-wrap. The bridge was 44px tall and 110px wide.The bridge covers the path from the heading to the button. It does not cover the blocks under the heading.
The button was 44 by 44 CSS pixels. That is
size-11and$ha-hit-size(2.75rem)..ha-wrapcomes fromcreateHoverChatPlugininhoverChatPlugin.ts.TipTap.tsxsetshoverChatfromeditable.The table below is one section that held h1 through h6, so HeadingScale set the sizes. Line height is 1.15. Hover height is
offsetHeight.Title is the first h1. CSS paints it at 28pt. The plugin still writes
--hd-size: 20pton it.The h1 row is the first heading of a section that also holds h2 through h6. Its rank size is 20pt.
h4, h5, and h6 hover boxes are under 24 CSS pixels. The h3 box is 26px. The button is 44px, and it stays hidden until the pointer hits the line or the gutter bridge.
computeSectionincompute-section.tsreturns the heading and the top-level siblings that follow it. It stops at the next heading of the same or higher level, or at the end of the document.Those blocks are siblings. A
:hoverrule on the heading does not include them.Read from code, not measured. A folded block has the class
heading-fold-hiddenanddisplay: none. A hidden block is not a hover target.Unread was measured. A button with
data-unread-countset to3reached opacity 1 and visibility visible after the 0.15s fade.Chat open is read from code, not measured. No rule ties
chatRoom.headingIdto.ha-wrap. The editor button does not stay shown because chat is open. The TOC row usesmenu-activefor that state. That row is a different control.Focus was measured. The caret sat in an h6, and
.ProseMirrorhad focus. The heading did not match:focus-within. The button stayed hidden. Afocus()call on the hidden button did not move focus.The button is a
<button type="button">with tab index 0. Whilevisibilityis hidden, the browser does not focus it.Coarse pointers are read from code, not measured. Inside
@media (hover: none), (pointer: coarse),.ha-singlestays visible.Mobile is read from code, not measured.
_mobile.scsspaints a 32 by 32 tab and forces it visible.selectionChatis off whenisMobileis true.History is read from code, not measured on a history route.
hoverChatis false when the editor is not editable..history_editoralso sets.ha-wrapand.ha-selection-comment-docktodisplay: none.The selection comment chip is
selectionChatPlugin. It shows for a non-heading text selection on a desktop. Its class isha-selection-comment-dock.The chip center sits on the sheet border. The shift is
translateX(calc(var(--tiptap-inline-pad-end) + 0.5px + 50%)). The docked chat panel and the TOC rail paint above the sheet. That overlap was read from code, not measured.Reference points
WCAG 2.2 Success Criterion 2.5.8 Target Size (Minimum) asks for a pointer target of at least 24 by 24 CSS pixels. The understanding page is Target Size (Minimum).
The chat button is 44px, so the control meets that size. The hover band over an h6 is 18px. That band is the heading line. The control is the button.
WCAG 2.2 Success Criterion 1.4.13 Content on Hover or Focus says hover content must stay shown while the pointer moves onto it. The understanding page is Content on Hover or Focus.
Notion shows a
⋮⋮handle in the left margin when the pointer is over a block. That handle belongs to one block. The page does not describe a handle for a heading plus the blocks under it.Google Docs help says to highlight the text, then use Add comment in the toolbar. A second page says a button appears in the right margin after you select text.
GitHub shows a comment icon when the pointer rests on one line of a pull request diff.
Confluence Cloud says an inline comment can start from highlighted text or from a hover over a section. The steps on that page cover highlighted text, and a hover on an image or a video.
Acceptance criteria
apps/webapp/cypress/e2e/fails before the fix. The spec hovers a paragraph under an h3 and expects the button to show.Blocked by
None.
Agent brief
Type: HITL for the method. AFK after the maintainer picks one.
Category: enhancement
Current behavior: The button shows on heading hover, on the gutter bridge, on unread, and on a coarse pointer. It stays hidden when the pointer is on a block under the heading.
Desired behavior: On a desktop, the pointer anywhere over the section shows the button. The button stays on the sheet border.
Where to start:
apps/webapp/src/styles/components/_heading-actions.scss(the:hoverblock,$ha-hit-size,$ha-sheet-border-straddle-x).apps/webapp/src/components/TipTap/extensions/HeadingActions/plugins/hoverChatPlugin.ts(createHoverChatPlugin).apps/webapp/src/components/TipTap/extensions/shared/compute-section.ts(computeSection). Search by symbol. File names are hints as of 2026-09-29.Rules that apply: Do not rebuild decorations on each pointer move. Do not write the store on each move. Remove any listener when the editor is destroyed. Keep the sheet-border straddle. Keep the chip under the docked chat panel and the TOC rail. Do not change settled TOC behavior. History read-only keeps the widgets hidden. Folded blocks stay hidden. See
apps/webapp/CLAUDE.md§TOC And Heading Actions and §Pad Workspace Surfaces. Seeapps/webapp/src/components/TipTap/CLAUDE.md§Editor Performance and §Heading Schema.Open choice: The maintainer has not picked a method.
computeSection, and sets one class on that heading's.ha-wrap. This path does not rebuild decorations.posAtCoordson every move can force layout. The button stays on the heading, so a long section can show it off screen.Verify: On a desktop, hover an h3 line, then a paragraph under it, then the gutter path to the button. Check an h6. Check unread, an open chat, a folded section, history read-only, and a body text selection. Check the light theme and the dark theme. Run the new Cypress spec. Watch it fail before the fix and pass after it.