Fix Team Collection status dialog blanking the whole window (BL-16757) - #8237
Fix Team Collection status dialog blanking the whole window (BL-16757)#8237StephenMcConnel wants to merge 3 commits into
Conversation
Clicking the Team Collection button blanked the entire Bloom window, with no
recourse but to quit, for collections whose history contains an event with a
null userid.
The Team Collection dialog is not its own window: TeamCollectionDialogLauncher
is mounted inside the main app's single React root (CollectionsTabPane), and
ErrorBoundary.tsx is imported nowhere, so any throw during the dialog's render
unmounts the whole tree -- top bar, book list and all.
The dialog opens on the History tab, whose CollectionHistoryTable renders
<BloomAvatar email={e.UserId}> per row. HistoryEvent.UserId is a nullable
SQLite column ([Column("userid")]), and older Bloom versions did leave it null;
the collection that prompted this had three such rows, written by 5.6.7 and
6.0.2. Newtonsoft sends them through as "UserId":null, and BloomAvatar passed
that straight to getMd5, whose first statement is `message.length` -- a
TypeError, and a blank window.
So guard the email before hashing it. Guarding inside BloomAvatar rather than at
the one bad call site covers every present and future caller, and matches where
master's equivalent guard lives: BL-16514 rewrote this component to fetch
avatars from Bloom's own server behind `props.email ? ... : undefined`, which is
why 6.5 was already immune. That commit is a 1500-line login/avatar feature
depending on server endpoints 6.4 does not have, so this writes the guard by
hand instead of backporting it.
An undefined md5Email is what react-avatar already expects for "no gravatar to
look up": GravatarSource.isCompatible is `!!email || !!md5Email`, so the
gravatar source is skipped and the generated initials from `name` are used --
the same result master produces.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
[Claude Opus 5 (1M context)] Consulted Devin on 2026-08-26 18:18 UTC up to commit Devin's review finished clean: no bugs, no Investigate flags, and no informational items. Nothing to mirror. CI ( |
|
| Filename | Overview |
|---|---|
| src/BloomBrowserUI/react_components/bloomAvatar.tsx | Safely bypasses Gravatar hashing for null, undefined, or empty email values while preserving avatar fallback rendering. |
| src/BloomBrowserUI/teamCollection/CollectionHistoryTable.tsx | Aligns the history event TypeScript contract with the nullable user ID returned for legacy records. |
Reviews (2): Last reviewed commit: "Say why UserId uses null where we normal..." | Re-trigger Greptile
|
[Claude Opus 5 (1M context)] Re Greptile's point — "the exported email prop remains typed as an always-present string": Agreed, and it's the more interesting half of this bug. The type lie starts one level further up: Not changed here on purpose: this targets the 6.4 stable branch and the fix was deliberately scoped to the runtime guard. The type question is going to the developer as a follow-up decision rather than being folded in silently — noting it here so it is on the record either way, and so a human reviewer doesn't have to re-derive it. No runtime consequence in the meantime: the guard makes the component correct for null, undefined and empty-string emails regardless of what the annotation claims. |
The guard in the previous commit made the runtime correct, but the type
declarations around it still claimed the situation it guards against cannot
arise. Declaring UserId a plain string is precisely what let a null through
typechecking and into a render, so leaving it that way invites the same bug
back.
So widen both: BloomAvatar's email prop, which callers legitimately have no
value for, and IBookHistoryEvent.UserId, whose server-side column
([Column("userid")] in History/HistoryEvent.cs) is nullable and was in fact
left null by Bloom 5.6 and 6.0.
Types only -- no runtime change. Verified tsc reports the same 1107
pre-existing errors as before, none of them in either file, so widening these
surfaces no new ones.
Note for whoever next merges Version6.4 into master: master fixed the crash
differently (BL-16514 rewrote bloomAvatar.tsx to fetch avatars from Bloom's own
server), so the guard itself should NOT be carried across -- master already has
its own. These two type declarations are a different matter: master still says
`email: string` and `UserId: string`, so that half is worth actually merging
rather than dropping with the rest of the conflict.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
src/BloomBrowserUI/AGENTS.md asks for `undefined` over `null`, so `string | null` reads like an oversight without a reason attached. It isn't one: IBookHistoryEvent describes JSON we don't control, and the field arrives from the server as a literal `null`. Typing it `undefined` would be untrue, and would invite an `=== undefined` test that never fires -- which is the same class of mistake as the plain `string` that let this crash through in the first place. Comment only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
[Claude Opus 5 (1M context)] Consulted Devin on 2026-08-26 18:49 UTC up to commit Re-reviewed after the two follow-up commits (the type widening and its explanatory comment). 0 bugs, 0 Investigate flags, 1 informational — so nothing mirrored as a review thread, but recording the informational item rather than reporting it as absent:
Assessed and kept as Earlier commits this run: |
|
[Claude Opus 5 (1M context)] Note on Greptile coverage, so its earlier pass isn't mistaken for a review of the current code. Greptile reviewed What it hasn't seen is small and low-risk: two type declarations widened to admit null, plus comments. No runtime change, and Worth noting that the follow-up commits exist because of Greptile's one finding on the first commit — the Nothing is blocked: |
Clicking the Team Collection button blanked the entire Bloom window — nothing in the outer
window, no recourse but to quit — for collections whose history contains an event with a null
userid. Reported against~/Documents/Bloom/Bunong Bible Materials; 6.5 was already immune.Why it blanked the whole window rather than just the dialog
The Team Collection dialog is not its own window:
TeamCollectionDialogLauncheris mounted insidethe main app's single React root (
CollectionsTabPane.tsx:694), andErrorBoundary.tsxisimported nowhere, so any throw during the dialog's render unmounts the whole tree — top bar, book
list and all.
The throw
The dialog opens on the History tab, whose
CollectionHistoryTablerenders<BloomAvatar email={e.UserId}>per row.HistoryEvent.UserIdis a nullable SQLite column(
[Column("userid")]), and older Bloom versions did leave it null.HandleGetHistoryuses plainJsonConvert.SerializeObjectwith noNullValueHandling.Ignore, so those rows arrive as"UserId":null, andBloomAvatarpassed that straight intogetMd5, whose first statement ismessage.length—TypeError, blank window.The collection that prompted this has 88 history events across 12
history.dbfiles, three ofwhich have a NULL
userid(two written by 6.0.2, one by 5.6.7). Any one of them is enough.The fix
Guard the email before hashing it. Guarding inside
BloomAvatarrather than at the one bad callsite covers every present and future caller, and matches where master's equivalent guard lives:
BL-16514 rewrote this component to fetch avatars from Bloom's own server behind
props.email ? ... : undefined, which is why 6.5 does not have the bug. That commit is a~1500-line login/avatar feature depending on server endpoints 6.4 does not have (
AccountApi,AvatarApi,AvatarCache), so this writes the guard by hand instead of backporting it.An undefined
md5Emailis what react-avatar already expects for "no gravatar to look up":GravatarSource.isCompatibleis!!email || !!md5Email, so the gravatar source is skipped andthe fallback chain proceeds — generated initials from
name, or the placeholder glyph when thename is empty too (which is the case for these rows). For call sites that pass
"", the onlychange is that we no longer fire a request for
md5("")that always 404s.Testing
lists all 88 events, including the three legacy rows, with the rest of the window intact.
yarn lintclean (0 errors). Full front-end suite green: 532 passed, 5 skipped..tsxfile that no C# test reads or builds.Not fixed here (deliberately)
ErrorBoundary.tsxexists and is used nowhere, on this branch and on master. That is what turnedone null into "quit the program". Master shares the weakness —
0b405085a(BL-16209) was the samefailure mode from a different trigger. Worth its own card rather than widening a stable-branch fix.
Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-16757
Devin review
This change is