Don't let a failed page save trap the user in Bloom (BL-16776) - #8254
Don't let a failed page save trap the user in Bloom (BL-16776)#8254andrew-polk wants to merge 3 commits into
Conversation
A branding html value can contain an img marked as the cover image. Restoring the branding element replaces everything inside it, orphaning that img, which UpdateDomFromDataSet then tried to update -- NRE, because the update code assumes the parents it collected the node with. Skip nodes that are no longer in the document. The rest is about not being trapped when a save fails for any reason: - DoPostSaveAction now calls the caller's failureAction when the post-save action throws, not just when applying the page content does. - OnTabAboutToChange completes the tab change in a finally, so a page that cannot be saved no longer locks the user into the Edit tab. - The collection-closing save reports and continues instead of propagating, so shutdown never depends on the save succeeding. Before this, every further attempt to close ran the same failing save and Bloom could only be ended from Task Manager. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Leaving the Edit tab now reports a failed Book.Save() and completes the tab change, rather than throwing out of the post-save action. Throwing let the tab change finish in a finally but then sent the state machine back to Navigating on a page whose tab we had just left; reporting keeps it on the same terminal path a successful save takes. Notes why this is not the swallowing removed in "Fix up error handling around save failures in the state machine": there, catching let a save continue with missing content. Here the page content has already reached the DOM or thrown, and all we abandon is writing it out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| src/BloomExe/Book/BookData.cs | Adds an attachment check that safely skips stale nodes orphaned by earlier DOM replacements during data synchronization. |
| src/BloomExe/Edit/EditingModel.cs | Reports full-save failures while allowing tab transitions and shutdown to complete instead of trapping the user. |
| src/BloomExe/Edit/EditingStateMachine.cs | Runs the registered failure callback when post-save work throws before restoring a navigable editing state. |
| src/BloomTests/Book/BookDataTests.cs | Adds a focused regression test covering a branding update that detaches a nested cover-image node. |
Reviews (1): Last reviewed commit: "Report the tab-change save failure inste..." | Re-trigger Greptile
The custom-layout pass had the same shape as the second pass but no IsStillInDocument check, so an update there that detached a later collected node would crash the same way. Devin flagged the asymmetry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
[Claude Opus 5] Consulted Devin on 2026-08-28 11:05 PDT up to commit Re-review clean — no bugs and no Investigate flags. Four informational items, none mirrored: three of them just confirm the reasoning behind the change (that Greptile also reviewed the branch with no findings. |
Problem
Switch a front cover to custom layout, add an image to it, and the book can reach a state where
the page can never be saved again. Every action that saves — clicking a page thumbnail, switching
to the Collections or Publish tab, even quitting — puts up "Bloom had trouble saving a page" and
then does nothing else. There is no way out of the program except Task Manager. Reported from
6.5.3007 with Little Zebra branding (BL-16776).
Cause
A branding html value can itself contain an
<img>marked as the cover image.UpdateDomFromDataSetcollects every
data-booknode up front, then restores the branding element — which replaceseverything inside it, orphaning that img. When the orphan's turn comes, the update code dereferences
parents it no longer has and throws.
That alone would be one failed save. What makes it inescapable is separate: leaving the Edit tab and
shutting down both run their real work only after
Book.Save()returns, so a save that throwsblocks each of them permanently.
Fix
UpdateDomFromDataSetskips collected nodes that an earlier update has since detached from thedocument. This is the crash itself.
Book.Save()and changes tabs anyway, instead ofthrowing out of the post-save action.
EditingStateMachine.DoPostSaveActioncalls the caller'sfailureActionwhen the post-saveaction throws, not only when applying the page content does — completing the recovery design
that already existed for the other failure path.
The wrong-image half of the report — the branding logo being designated as the cover image in the
first place, by
normalizeCoverImageDesignationinbloomImages.ts— is not touched here, and hasno card of its own yet. BL-16780 (PR #8256) is the nearest work: it stops the same class of image
being picked as the book icon, in
Book.GetCoverImagePathAndElt, and builds exactly the rule thefront end needs (an image counts only inside a
bloom-imageContainer/bloom-canvas, plus abranding/license/QR class exclusion). But it leaves the front-end designation code untouched, so it
does not prevent the corruption this PR recovers from.
Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-16776
Devin review
This change is