Skip to content

Honor a minimum Bloom version in collection settings (BL-16690) - #8200

Closed
andrew-polk wants to merge 10 commits into
Version6.4from
BL-16690
Closed

Honor a minimum Bloom version in collection settings (BL-16690)#8200
andrew-polk wants to merge 10 commits into
Version6.4from
BL-16690

Conversation

@andrew-polk

@andrew-polk andrew-polk commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Honors a MinimumBloomVersion element in a collection's .bloomCollection file, in preparation for Cloud syncing. If the collection requires a newer Bloom than the one running, we refuse to open it and offer the user the two choices the card asks for: upgrade Bloom, or open a different collection.

There is no UI for setting the flag yet — it has to be added to the file by hand, as the card specifies.

How it works

  • CollectionSettings reads MinimumBloomVersion, and writes it back out on Save() when non-empty. The write-back matters: Save() rebuilds the file from scratch, so without it a hand-added flag would silently disappear the first time the user changed anything in Collection Settings.

  • MinimumBloomVersionCheck (new) does the gating. It reads just the one element rather than loading a whole CollectionSettings, since we have to answer the question before committing to build a ProjectContext. A missing, empty, or unparseable value imposes no restriction — locking someone out of their own collection over a typo would be worse than ignoring it.

  • The gate lives in Program.OpenProjectWindow, the single point all the ways of opening a collection funnel through (startup from MRU, the collection chooser, a command-line path, switching collections mid-session). Every one of those callers already responds to a false return by putting up the collection chooser, so "open a different collection" needed no new plumbing.

  • HideSplashScreenForDialog (new, in StartupScreenManager) clears the splash out of the way without consuming the one-shot DoLastOfAllAfterClosingSplashScreen, which is what brings the main window to the front. CloseSplashScreen() would have spent it at the gate, so the main window would never come forward when a collection finally opened.

Decisions worth flagging for review

  • Major.minor comparison only, matching Bloom's other two version gates (BookStorage's feature requirements and BookDownload's minVersion). So 6.5 is satisfied by any 6.5.x or later, and a build number in the requirement is ignored. Both versions are reported as major.minor too, so we don't claim to enforce a build number we don't look at.

  • Channel plays no part. A version number means the same thing on every channel, so an alpha tester running ahead of the release is correctly let in. (This was the card's open question.)

  • "Upgrade Bloom" opens the installers page and quits, rather than doing an in-app Velopack update. Velopack reports progress solely through toasts, and ToastHost is mounted only in app/App.tsx, which does not exist before a collection opens. More decisively, Velopack only offers the latest build on the user's own channel, so a Release user blocked by a 6.5 requirement would be told they are up to date and left with no way forward.

  • No XLF entries — the strings use LocalizationManager.GetString with English fallbacks, per the AGENTS.md allowance for experimental features.

Testing

24 new tests in MinimumBloomVersionCheckTests cover the comparison table, reading the element, unparseable/absent files, that the setting survives a Load/Save round trip, and that the requirement is reported as major.minor. Verified in the running app that the dialog reads 'The collection "Fake TC Books" requires Bloom 6.6 or greater. You are running Bloom 6.4.'

Note the base branch is Version6.4, not master.

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

Devin review


This change is Reviewable

andrew-polk and others added 3 commits August 12, 2026 16:22
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

In preparation for Cloud syncing, honor a MinimumBloomVersion element in the
collection settings file. If the collection asks for a newer Bloom than the one
running, we refuse to open it and offer the user two ways forward: upgrade
Bloom, or open a different collection.

There is no UI for setting the flag yet; it has to be added to the
.bloomCollection file by hand, as the card specifies.

- CollectionSettings reads MinimumBloomVersion, and writes it back out on Save
  when it is non-empty. The write-back matters: Save() rebuilds the file from
  scratch, so without it the hand-added flag would silently disappear the first
  time the user changed anything in Collection Settings.

- New MinimumBloomVersionCheck does the gating. It reads just the one element
  out of the file rather than loading a whole CollectionSettings, since we have
  to answer the question before committing to build a ProjectContext. A missing,
  empty, or unparseable value imposes no restriction; locking someone out of
  their own collection over a typo would be worse than ignoring it.

- The gate lives in Program.OpenProjectWindow, which is the single point all the
  ways of opening a collection funnel through (startup from MRU, the collection
  chooser, a command-line path, and switching collections mid-session). Every
  one of those callers already responds to a false return by putting up the
  collection chooser, so "open a different collection" needed no new plumbing.
  "Upgrade Bloom" opens the installers page and quits, which is what Bloom
  already does when a download needs a newer version.

We compare major and minor only, matching Bloom's other two version gates
(BookStorage's feature requirements and BookDownload's minVersion), so "6.5" is
satisfied by any 6.5.x or later. On the card's open question about channels: the
channel plays no part, because a version number means the same thing on every
channel -- an alpha tester running ahead of the release is correctly let in.

No XLF entries; the strings use LocalizationManager.GetString with English
fallbacks, per the AGENTS.md allowance for experimental features.

Tests: added MinimumBloomVersionCheckTests (20 cases: the version comparison
table, reading the element, unparseable/absent files, and that the setting
survives a Load/Save round trip). Those 20 pass, as do the 77 existing
CollectionSettingsTests. Tests requiring output/browser (BookStorageTests,
CollectionModelTests) cannot run in this worktree because the front end has
never been built here; they fail on missing template directories both before and
after this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

Follow-ups to the minimum-version gate, from review:

- Report both versions as major.minor, since that is what we actually compare.
  Saying "requires Bloom 6.5 ... you are running 6.4.900" invites the reader to
  compare 900 against 5, and echoing a declared "6.5.132" verbatim would claim we
  enforce a build number we ignore. Verified on screen: the dialog now reads
  'The collection "Fake TC Books" requires Bloom 6.6 or greater. You are running
  Bloom 6.4.'

- Put the Help menu's "opens an external website" icon (MUI Launch) on the
  Upgrade Bloom button, since that button leaves Bloom for the installers page.
  This meant teaching BloomMessageBox's buttons about an optional icon, on both
  the C# and Typescript sides; "launch" is the only value so far. It is passed as
  endIcon, the usual placement on a button for an external-link affordance (the
  Help menu's leading position is a menu-layout convention, not a semantic one).

- Stop consuming StartupScreenManager's one-shot actions when clearing the splash
  screen out of the way of the dialog. CloseSplashScreen() invokes and then nulls
  DoLastOfAllAfterClosingSplashScreen, which is what brings the main window to the
  front; spending it at our gate means the main window never comes to the front
  when a collection finally does open. Added HideSplashScreenForDialog(), which
  does exactly what DoStartupAction already does for a task with
  ShouldHideSplashScreen -- the existing comments there say this case is
  deliberately different from closing.

Also considered and rejected, with the developer, an in-app Velopack upgrade
instead of the installers page. The blocker is not the lack of a browser (the
dialog is a ReactDialog, so we do have one) but that Velopack reports progress
solely through toasts, and ToastHost is mounted only in app/App.tsx, which does
not exist before a collection opens. More decisively, Velopack only offers the
latest build on the user's own channel, so a Release user blocked by a 6.5
requirement would be told they are up to date and left with no way forward.

Tests: 101 pass (the 77 existing CollectionSettingsTests plus 24 in
MinimumBloomVersionCheckTests, 4 of them new here for the major.minor
reporting). eslint and prettier clean on the changed .tsx.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

Move the Launch icon on the "Upgrade Bloom" button from the trailing position to
the leading one, matching where the Help menu puts it on its "website" item.

Uses BloomButton's own iconBeforeText prop rather than passing MUI's startIcon
through, since that is the API BloomButton offers for exactly this.

Verified by rendering BloomMessageBox with a throwaway vitest: the upgrade button
gets a MuiButton-startIcon containing an svg, has no MuiButton-endIcon, and the
button that asked for no icon has neither. The test was removed afterward rather
than kept, since it asserts on MUI's internal class names and would be brittle.

The C# side is a comment correction only, so the C# tests are unaffected; the
compile is clean. They were not re-run because Bloom is currently running and
holds the output exe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
andrew-polk and others added 2 commits August 13, 2026 11:09
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

Two bugs in the new minimum-version gate, found by review (the second was found
independently by both the local reviewer and Devin).

ReopenProject discarded OpenProjectWindow's return value, so the comment I wrote
claiming "every caller already responds to a false return by putting up the
collection chooser" was simply untrue. The shell is already closed on that path,
so a collection that refuses to open would leave Bloom running with no window at
all -- invisible, killable only from Task Manager, and blocking the next launch
via the single-instance mutex. This is reachable now that a collection can start
refusing mid-session: a Team Collection member on a newer Bloom adds
MinimumBloomVersion, it syncs down, and a collection-settings change triggers a
reopen. It now falls back to the chooser like the other callers, and the comment
is corrected.

ReportCollectionNeedsNewerBloom returned void, so a caller could not tell "the
user chose to upgrade and we have already called Application.Exit()" from "the
user wants a different collection" -- both came back as false. A caller could
therefore put the collection chooser up while Bloom was shutting down, starting a
fresh modal message loop that could keep the process alive until the 20-second
forced-shutdown watchdog fired. It now reports whether we are quitting, and
OpenProjectWindow returns true in that case purely to stop callers starting new
UI on the way out (commented as such, since it is not a real "opened").

Tests: 101 pass. The full C# suite has ~26 failures in this worktree, all in
epub/audio/publish areas untouched by this change, with filesystem/temp-dir error
signatures and a failure set that differs between runs -- environmental, not
caused by this branch. Front-end suite green (524 passed).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

RunningBloomVersion fell back to 1.0.0 when the assembly version could not be
read. That fallback was worse than no fallback: 1.0.0 fails every declared
minimum, so the one case it "handled" would have locked the user out of every
collection carrying the flag -- the exact opposite of what the rest of this class
takes care to do, which is err toward letting people in.

A loaded assembly always has a version, so per AGENTS.md's fail-fast guidance
("if you expect a foo to be defined, just use foo") the guard is gone and the
reasoning is recorded in the comment.

Raised by Devin as an informational finding.

Tests: 101 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
null,
$"<strong>{header}</strong><br/><br/>{explanation}",
buttons,
MessageBoxIcon.Warning

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.

[Devin] Bug: Warning symbol is missing from the “needs a newer Bloom” dialog

The dialog asks for a warning symbol, but none appears — it shows as a plain message.

Why: MessageBoxIcon.Warning and MessageBoxIcon.Exclamation are the same underlying value in .NET, and ToString() returns the first-declared name, so what actually reaches the display side is "exclamation". The Typescript only recognises "warning" and "asterisk", so it draws nothing. This is the same trap the existing comment in BloomMessageBox.tsx already notes for Information arriving as "Asterisk".

Confirmed: a screenshot of the running dialog during this preflight shows the text and both buttons but no warning triangle.

This is pre-existing and affects every BloomMessageBox.ShowWarning call in Bloom, not just this new dialog — so fixing it (teaching the Typescript side that "exclamation" means warning) would give warning icons back to warning dialogs app-wide. Because that is a visible change well beyond this card, it has been left for the developer to decide rather than changed here.

Comment thread src/BloomExe/Collection/MinimumBloomVersionCheck.cs
Default = true,
},
};
var result = BloomMessageBox.Show(

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.

[Devin] Investigate: Message box is a fixed 500x200, and this message is longer than usual

Bloom's message box is a fixed 500x200 with no auto-sizing. This message is a bold heading plus a two-clause sentence, plus two fairly wide buttons — more than most.

Checked during this preflight: with the collection "Fake TC Books" it renders correctly with room to spare. The residual risk is a much longer collection name or a translated message pushing it past the fixed height.

Left as is for now (the size is a property of the shared message box, not of this dialog), but worth a look at long names.

Comment thread src/BloomExe/Program.cs
Comment thread src/BloomExe/Program.cs
Comment thread src/BloomExe/Program.cs
Comment thread src/BloomExe/Collection/MinimumBloomVersionCheck.cs
@andrew-polk

Copy link
Copy Markdown
Contributor Author

[Claude Opus 5] Consulted Devin on 2026-08-13 up to commit a362bbec9c44babed2743656368a6b7bf262bfa1.

It raised one bug and six things to investigate; each is its own review thread above, so they can be answered one at a time.

Two are already closed out: the collection staying at the top of the recently-used list is intended (and self-corrects once you open anything else), and the shutdown race after "Upgrade Bloom" was already fixed in 70fb4b31, before Devin flagged it.

The other five are waiting on a developer decision — the missing warning symbol on the dialog, a bare version number like 7 silently meaning nothing, the fixed message-box size, Team Collections being judged on the local settings file before it syncs, and the command-line/bulk-upload paths not being gated.

Six further items were informational and are not mirrored here. One of those — a fallback version number that could have locked users out — was fixed in a362bbec (Devin's copy of that note describes the earlier commit).

CI is green, and CodeRabbit is switched off for this repo in .coderabbit.yml.

andrew-polk and others added 2 commits August 13, 2026 12:04
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

Devin pointed out that the minimum-version check only covers the interactive ways
of opening a collection; the command-line commands and BulkUploader build a
collection directly and walk straight past it.

Decision (Andrew): leave it that way, and write down why. Those tools have no user
to read a dialog or choose a different collection, so gating them would convert a
silent problem into a failed overnight job. The flag as it stands is a message to
a person, and people are on the interactive paths.

The comment also names the condition that would overturn the decision: if the flag
ever has to protect the files themselves rather than warn someone, this is the
spot to revisit.

Tests: 101 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

A regression in my own earlier fix, caught by Devin.

To stop the collection chooser being raised during shutdown, OpenProjectWindow
reports success when the user picks "Upgrade Bloom". But OpenCollection reads that
as "the collection opened" and records it as the most-recently-used one. So a user
who chose a too-new collection from the chooser, clicked Upgrade, and then thought
better of the download would come back to the blocked collection on the next start
instead of the one they had actually been working in.

The shutdown case is now marked with a flag, so OpenCollection can tell a real open
from "handled, don't start any more UI" and leaves the recently-used list alone.

Tests: 101 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread src/BloomExe/Program.cs
Comment thread src/BloomExe/Program.cs
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

The collection folder for these cases was derived from the version string being
tested, after trimming. Two of the cases differ only by surrounding whitespace, so
both landed on the same folder and wrote over each other's settings file. They pass
either way, but only because each writes immediately before it reads; it is exactly
the sort of shared state that turns into a confusing intermittent failure later.

Each case now names its own collection.

Raised by Devin as an informational finding.

Tests: 101 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread src/BloomExe/Program.cs
@andrew-polk

Copy link
Copy Markdown
Contributor Author

[Claude Opus 5] Consulted Devin again on 2026-08-13, up to commit 5eae3004f4130746d939b23986339272f50f468f — the last of several rounds as the developer's decisions were carried out.

Since the previous log, Devin found one genuine regression in a fix made earlier in this same review: reporting "success" on the upgrade path meant a collection Bloom had just refused was recorded as the last-used one, so abandoning the download and restarting landed you back on the blocked collection instead of the one you were working in. Fixed in 3f63ff9f.

It also caught two smaller things, both fixed: a fallback version number that would have blocked every flagged collection (a362bbec), and two test cases sharing a temp file (5eae3004).

Three findings were assessed and closed with the reasoning on their threads: joining a Team Collection isn't gated (but opening the result is, so the user is still stopped where it matters); the blocked collection staying top of the recently-used list (intended, and self-correcting); and whether other startup tasks can run after we ask Bloom to quit (they can't, because the quit message stops the idle processing that drives them — noted on the thread that this one rests on reasoning rather than an observed run).

Three were decided by the developer and recorded on their threads: the bare version number, the Team Collection sync ordering (now an open question for the designer on this card), and the command-line paths (left ungated, with the reasoning written into the code).

Two threads remain open, both deliberately: the missing warning symbol and the fixed message-box size. Both turn out to affect dialogs across Bloom rather than just this one, so the developer is deciding them separately rather than having them changed here.

CI is green. CodeRabbit is switched off for this repo in .coderabbit.yml.

andrew-polk and others added 2 commits August 13, 2026 21:11
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

Swap the two buttons and make upgrading the default. It now sits in the rightmost,
primary position, drawn filled and holding the initial focus, with "Open a
Different Collection" to its left as the secondary choice.

Upgrading is the thing we actually want the user to do -- opening something else is
the way out for someone who can't upgrade right now -- so the dialog should lead
with it rather than making it the quieter option.

Tests: 101 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
https://issues.bloomlibrary.org/youtrack/issue/BL-16690

The "Upgrade Bloom" button now opens the site's /downloads page.

Note this only changes the button added by this card. The equivalent link behind
the "this book needs a newer Bloom" message (web/controllers/AppApi.cs, the
app/showDownloadsPage endpoint) still points at /installers, and BookDownload
points at /download, so the three are not consistent. Left alone deliberately:
changing them would alter messages beyond this card.

Tests: 24 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@andrew-polk

Copy link
Copy Markdown
Contributor Author

superceded by #8206

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