Skip to content

Add a Layer submenu to the canvas element menu (BL-15992) - #8372

Open
JohnThomson wants to merge 1 commit into
masterfrom
BL-15992-canvas-layer
Open

JohnThomson wants to merge 1 commit into
masterfrom
BL-15992-canvas-layer

Conversation

@JohnThomson

@JohnThomson JohnThomson commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Problem

When canvas elements (pictures, speech bubbles, text boxes, videos, buttons, tables) overlap on a page, whichever one was added last is drawn on top, and there has been no way to change that. A user who wants a caption to sit in front of a picture they added later, or a bubble to tuck behind another, has had to delete and re-create elements in the right order. Requested on the community forum and tracked as BL-15992.

What the PR does

  • Adds a Layer submenu to the canvas element menu (the "..." button below a selected element, and the right-click menu), in its own group just before Duplicate and Delete, with Bring Forward (Ctrl+]), Bring to Front (Ctrl+Shift+]), Send Backwards (Ctrl+[) and Send to Back (Ctrl+Shift+[), the same keys PowerPoint and Illustrator use. The shortcuts work whenever a canvas element is selected and text is not being edited; they accept either the physical bracket keys or the bracket characters, so they also work on keyboard layouts where the brackets live elsewhere (an AltGr-typed bracket counts as the bracket).
  • The submenu is hidden for the background image, which always stays behind everything, and is disabled when the selected element is the only one that could move. The forward commands are disabled when the element is already at the front and the backward ones when it is already at the back.
  • Stacking order is the order of the elements in the page's DOM, while ComicalJS draws and hit-tests bubbles by a separate level stored on each element. A new CanvasElementZOrder module moves the element in the DOM and then renumbers every level to match, background first, before asking Comical to redraw. A parent bubble and its child bubbles (which share a level) move together as one unit; a family that had become scattered in the DOM is gathered next to its parent first, so every move is exactly one step. On game pages, draggable pieces and fixed pieces only reorder among their own kind, matching the existing rule that draggables stay on top; a family with any draggable member counts as draggable. The game code that pushes draggables to the top now uses the same level-renumbering helper.
  • Nested menu rows can now be disabled (previously the shared nested menu item ignored disabled), five new localizable strings are added, and unit tests cover the moves, the menu, and the shortcuts.
  • Updates comicaljs to 0.4.2 (Stop stale Bubble objects from reacting after Comical replaces them comical-js#121). Comical used to leak the previous generation of bubble objects each time Bloom turned editing back on for a canvas; after a level renumbering a stale family bubble threw inside Comical, which showed as a "JavaScript error" toast on any Layer move on a page with a child bubble.
  • An e2e test, src/BloomE2E/tests/canvas-layer-order.spec.ts (Notion test case 840, https://app.notion.com/p/Canvas-element-Layer-order-3f14bb19df12813cbe45c8744719b80c), drives all of this in a real Bloom: the menu's enabled states, every command from the menu and from its shortcut with Comical's levels following the DOM order, a click on two overlapping bubbles selecting the front one, the order surviving a page change, and a bubble family moving as one with no script error.

Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-15992

Devin review


This change is Reviewable

Comment thread DistFiles/localization/en/Bloom.xlf
Comment thread DistFiles/localization/en/Bloom.xlf
Comment thread src/BloomBrowserUI/bookEdit/toolbox/canvas/README.md
@JohnThomson

Copy link
Copy Markdown
Contributor Author

[Claude Fable 5.1] Consulted Devin on 2026-09-17 up to commit 1ca995d (three review rounds: 47de296, b40d243, 1ca995d).

Devin raised three bugs and three flags on the first commit, and one more bug on the second. All seven are mirrored as review threads below, each closed with its outcome:

The review of the final commit re-listed the same six earlier items, all of which the reviewed code already fixes or which were assessed above; it found nothing new. No Informational items. CI (pr-automation) passed on every commit; no other review bot posted.

@JohnThomson

Copy link
Copy Markdown
Contributor Author

[Claude Sonnet 5] Checked the feature in a running Bloom from this branch (menu placement, icons, greyed-out states at the top and bottom of the stack, background image without a Layer item, keyboard shortcuts, and a parent/child bubble family moving as one unit all behave as described).

One dependency came out of that check: moving an element on a page that has a child bubble shows a "A JavaScript error occurred" toast. The move itself completes correctly; the error is a latent ComicalJS bug that the level renumbering exposes. Every time Bloom turns bubble editing back on for a canvas, Comical leaks the previous generation of bubble objects with their mutation observers still attached, and a stale family bubble that wakes up after its level changed finds no relatives and dereferences undefined in uniteShapes.

The fix is in ComicalJS: BloomBooks/comical-js#121. Once that is merged (the publish workflow releases the next patch version automatically), this PR needs comicaljs bumped to that version in src/BloomBrowserUI/package.json; until then the toast will appear on family moves in this branch.

@JohnThomson

Copy link
Copy Markdown
Contributor Author

[Claude Fable 5.1] Consulted Devin on 2026-09-17 up to commit 813e959 (fourth round, for the comicaljs 0.4.2 bump). It re-listed only the six items already closed on their threads above and found nothing new. CI (pr-automation) passed; Reviewable's status stays pending until its mirrored discussions are marked resolved there.

Also verified in a running Bloom with 0.4.2 actually served: Layer moves on a page with a child bubble no longer raise the JavaScript error toast.

@JohnThomson JohnThomson left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@JohnThomson reviewed 19 files and all commit messages, and made 1 comment.
Reviewable status: 0 of 19 files reviewed, 7 unresolved discussions.

@JohnThomson
JohnThomson marked this pull request as ready for review September 17, 2026 22:28

@JohnThomson JohnThomson left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@JohnThomson+AGNT resolved 7 discussions.
Reviewable status: 0 of 19 files reviewed, all discussions resolved.

Comment thread src/BloomE2E/tests/canvas-layer-order.spec.ts
@JohnThomson

Copy link
Copy Markdown
Contributor Author

[Claude Opus 5.5] Consulted Devin on 2026-10-08 up to commit ed2683e (the merge of master, which also carries the new e2e test from d4d2f8a).

CI (pr-automation) passed on this commit.

@JohnThomson
JohnThomson marked this pull request as draft October 8, 2026 23:00
@JohnThomson

Copy link
Copy Markdown
Contributor Author

[Claude Opus 5.5] Consulted Devin on 2026-10-08 up to commit e39b679 (after merging master twice more, and giving the new canvas table element the Layer submenu).

Nothing new. Devin re-listed findings that already have outcomes on their threads: the AltGr Shift-bracket case (left as is by the developer), the script-error watch (not an issue), and the earlier fixed or answered items. CI (pr-automation) passed on this commit.

@JohnThomson JohnThomson left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@JohnThomson reviewed 21 files and all commit messages.
Reviewable status: 0 of 26 files reviewed, 2 unresolved discussions.

https://issues.bloomlibrary.org/youtrack/issue/BL-15992

Overlapping canvas elements were always stacked in the order they were added,
with no way to change it. This adds a Layer submenu to the canvas element menu
(the "..." button and the right-click menu), in its own group just before
Duplicate and Delete: Bring Forward (Ctrl+]), Bring to Front (Ctrl+Shift+]),
Send Backwards (Ctrl+[) and Send to Back (Ctrl+Shift+[). Every canvas element
type gets it, tables dragged onto a Canvas page included, except the background
image, which must stay behind everything. The submenu is disabled when there is
only one movable element; the forward items are disabled at the top of the stack
and the backward items at the bottom. LocalizableNestedMenuItem now passes
`disabled` through so the submenu row itself can be disabled. The shortcuts
accept either the physical US bracket keys or the bracket characters (an
AltGr-typed bracket counts as the one-step shortcut), and are left alone when
no canvas element is selected or text is being edited.

Stacking order is the DOM order of the elements in the bloom-canvas, while
ComicalJS draws and hit-tests bubbles by the `level` in each element's
data-bubble spec. The new CanvasElementZOrder module moves the element in the
DOM and then renumbers every level to match (background image first, at level
1) before calling Comical.update(). A bubble family (parent plus child bubbles,
which share a level) moves as one unit; a family that had become scattered in
the DOM is gathered next to its parent first, so every move is exactly one
step. On game pages elements only move among their own kind (draggable or
fixed), matching the rule that draggables stay on top; a family with any
draggable member counts as draggable. The game code that pushes draggables to
the top shares the level-renumbering helper.

comicaljs is updated to 0.4.2 (BloomBooks/comical-js#121): Comical leaked the
previous generation of Bubble objects each time editing was turned back on,
and after a level renumbering a stale family bubble threw in uniteShapes,
which showed as a "JavaScript error" toast on any Layer move on a page with a
child bubble.

Five new strings in Bloom.xlf (EditTab.Toolbox.CanvasTool.Layer.*). Also fixes
stale identifiers in the canvas toolbox README and logs a papercut about
build/agent-vite.ps1 reporting exit code 1 on a successful build.

Tests: CanvasElementZOrder.test.ts (moves, families, level renumbering,
draggable bands), layerMenuItem.test.ts (menu placement, per-type inclusion,
disabled states) and CanvasElementKeyboardProvider.test.ts (shortcuts,
layouts). The e2e test src/BloomE2E/tests/canvas-layer-order.spec.ts (Notion
test case 840) drives it in a real Bloom: the menu's enabled states, every
command from the menu and from its shortcut with Comical's levels following
the DOM order, a click on two overlapping bubbles selecting the front one, the
order surviving a page change, and a bubble family moving as one with no
script error. Its helpers are helpers/canvasLayers.ts and helpers/scriptErrors.ts,
plus submenu support and Add Child Bubble in helpers/canvasElements.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JohnThomson
JohnThomson force-pushed the BL-15992-canvas-layer branch from e39b679 to d9312b4 Compare October 9, 2026 19:37
@JohnThomson
JohnThomson marked this pull request as ready for review October 9, 2026 19:37
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.

1 participant