Skip to content

Make page gutter understandable#7712

Open
hatton wants to merge 1 commit intomasterfrom
BL-15957-visual-gutter
Open

Make page gutter understandable#7712
hatton wants to merge 1 commit intomasterfrom
BL-15957-visual-gutter

Conversation

@hatton
Copy link
Member

@hatton hatton commented Feb 26, 2026


Open with Devin

This change is Reviewable

Copilot AI review requested due to automatic review settings February 26, 2026 01:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the page gutter visibility in Bloom's edit mode by adding visual indicators and tooltips to help users understand the gutter space reserved for binding (staples, spiral binding, etc.). The gutter is shown as a colored overlay with a dashed border when hovering over pages, and tooltip targets enable helpful explanatory text when hovering over the gutter region.

Changes:

  • Added visual gutter overlay that appears on hover in edit mode, using modern CSS :is() selectors
  • Created a new tooltip system for page hover tooltips with localization support
  • Refactored CSS selector in appearance theme to use cleaner :is() syntax and added A4Landscape support
  • Added code style documentation about preferring TypeScript function syntax and modern CSS features

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/content/appearanceThemes/appearance-theme-zero-margin-ebook.css Refactored selector using :is() and added A4Landscape to zero-margin pages
src/BloomBrowserUI/bookEdit/js/pageHoverTooltips.ts New module implementing tooltip system for gutter visualization with localization
src/BloomBrowserUI/bookEdit/js/bloomEditing.ts Integrated new tooltip setup/cleanup into page lifecycle
src/BloomBrowserUI/bookEdit/css/editMode.less Added CSS for tooltip bubble, targets, and gutter visualization overlay
src/BloomBrowserUI/AGENTS.md Documented preferences for function syntax and modern CSS features

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +171 to +182
},
)
.off("mousemove.bloomPageTooltip")
.on("mousemove.bloomPageTooltip", function (e) {
moveTooltipBubble(e as unknown as MousePositionEvent);
})
.off("mouseleave.bloomPageTooltip")
.on(
"mouseleave.bloomPageTooltip",
`.${kTooltipTargetClass}, #formatButton`,
() => {
setActiveTooltipText(undefined);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mousemove handler is attached to the entire document, which means it will fire on every mouse movement anywhere on the page, even when no tooltip is active. This could have minor performance implications on lower-end devices. Consider attaching the mousemove listener only when a tooltip is actually shown (in setActiveTooltipText when activeTooltipText is set) and removing it when hidden.

Suggested change
},
)
.off("mousemove.bloomPageTooltip")
.on("mousemove.bloomPageTooltip", function (e) {
moveTooltipBubble(e as unknown as MousePositionEvent);
})
.off("mouseleave.bloomPageTooltip")
.on(
"mouseleave.bloomPageTooltip",
`.${kTooltipTargetClass}, #formatButton`,
() => {
setActiveTooltipText(undefined);
// Attach mousemove handler only while a tooltip target is hovered
$(document)
.off("mousemove.bloomPageTooltip")
.on(
"mousemove.bloomPageTooltip",
function (moveEvent) {
moveTooltipBubble(
moveEvent as unknown as MousePositionEvent,
);
},
);
},
)
.off("mouseleave.bloomPageTooltip")
.on(
"mouseleave.bloomPageTooltip",
`.${kTooltipTargetClass}, #formatButton`,
() => {
setActiveTooltipText(undefined);
// Detach mousemove handler when tooltip is no longer active
$(document).off("mousemove.bloomPageTooltip");

Copilot uses AI. Check for mistakes.
## Code Style

- Always use arrow functions and function components in React
- For functions, prefer typescript "function" syntax over const foo = () ==> functions.
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the arrow function syntax. It should be "() =>" not "() ==>".

Suggested change
- For functions, prefer typescript "function" syntax over const foo = () ==> functions.
- For functions, prefer typescript "function" syntax over const foo = () => functions.

Copilot uses AI. Check for mistakes.
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +203 to +207
export function getBodyInnerHtmlWithoutPageHoverTooltips(): string {
const bodyClone = document.body.cloneNode(true) as HTMLElement;
removePageHoverTooltipMarkupFrom(bodyClone);
return bodyClone.innerHTML;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Clone-based save replaces direct innerHTML access — performance and correctness tradeoff

The previous code at src/BloomBrowserUI/bookEdit/js/bloomEditing.ts:1348 was simply document.body.innerHTML. The new code calls getBodyInnerHtmlWithoutPageHoverTooltips() which does a full document.body.cloneNode(true), then removes tooltip markup from the clone, then returns clone.innerHTML. This is a deep clone of the entire page body on every save. For complex pages with many elements (canvas elements, images, translation groups), this could be noticeably more expensive than the prior direct property access. The function comment at line 1311 notes "We don't want this to become an async method" suggesting performance sensitivity. The clone is synchronous so correctness is preserved, but the cost scales with page complexity. An alternative approach — temporarily removing tooltip elements from the live DOM, getting innerHTML, then re-inserting them — would avoid the clone cost but would be riskier if an error occurs mid-operation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +183 to +195
.bloom-page:is(.side-left, .side-right):not(.outsideFrontCover):not(
.outsideBackCover
)::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: var(--page-gutter);
background-color: var(--page-structure-color);
box-sizing: border-box;
pointer-events: none;
z-index: 2;
opacity: 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 ::before pseudo-element on .bloom-page conflicts with MXB-Book-Literacy-Prepub branding

The new gutter visualization uses ::before on .bloom-page:is(.side-left, .side-right) at line 183. The branding file at src/content/branding/MXB-Book-Literacy-Prepub/branding.less:20 also sets ::before on .bloom-page (with lower specificity) to show a "Versión Preliminar" watermark. Since an element can only have one ::before pseudo-element, the gutter rule (higher specificity due to additional class selectors) would win on pages with side-left or side-right, hiding the watermark on those pages in edit mode. This is a pre-existing branding that applies to all .bloom-page elements. The practical impact is limited since the branding is project-specific and the gutter only shows on hover, but it's worth being aware of if other brandings adopt similar patterns.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +11 to +15
.numberedPage:not(.bloom-interactive-page):is(
.Device16x9Landscape,
.Device16x9Portrait,
.A4Landscape
) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 A4Landscape added to zero-margin theme — broadens scope of zero-margin behavior

The selector change at line 11-15 adds .A4Landscape to the list of page sizes that get --page-margin: 0mm in the zero-margin-ebook theme. Previously only Device16x9Landscape and Device16x9Portrait were affected. This is a behavioral change: A4Landscape pages using this theme will now have zero margins, zero vertical split width, and zero horizontal split height. The default theme at src/content/appearanceThemes/appearance-theme-default.css:132-143 sets --page-margin: 15mm and --page-gutter: 10mm for A4Landscape. This change would override both (gutter via the margin override, since gutter is added to margin in basePage.less:673,682). Confirm this is the intended behavior for A4Landscape books using this ebook theme.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@hatton hatton force-pushed the BL-15957-visual-gutter branch from 6e431bb to 9fe955c Compare February 27, 2026 15:33
Also, can now add tooltips to various elements on the page
@hatton hatton force-pushed the BL-15957-visual-gutter branch from 9fe955c to 687cad2 Compare February 27, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants