Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d582cc5
Notice when Team Collection monitoring stops working, and go Disconne…
StephenMcConnel Sep 9, 2026
0881ae9
Address Devin review: watcher-callback thread safety, and test the he…
StephenMcConnel Sep 9, 2026
2a4d797
Address Devin round 2: probe-exception strike, stale overflow write, …
StephenMcConnel Sep 9, 2026
e2b436f
Address Devin round 3: disconnect race, message-log snapshots, droppe…
StephenMcConnel Sep 9, 2026
5226b60
Address Devin round 4: a failed sync no longer disables the heartbeat…
StephenMcConnel Sep 9, 2026
1250e4b
Start watching the Books folder if Dropbox delivers it later (BL-16729)
StephenMcConnel Sep 10, 2026
13b9b68
Persist log messages under the lock, so the file matches memory (BL-1…
StephenMcConnel Sep 10, 2026
2a43b0c
Don't let a failed OpenForms lookup swallow a disconnect (BL-16729)
StephenMcConnel Sep 10, 2026
4e854a2
Marshal via the UI SynchronizationContext, not a form lookup (BL-16729)
StephenMcConnel Sep 10, 2026
2bfde49
Correct the comment on why the Books watcher can be deferred (BL-16729)
StephenMcConnel Sep 10, 2026
259f65a
Merge branch 'master' into BL-16729-TeamCollectionWhenDropboxStops
StephenMcConnel Sep 16, 2026
26dbdcd
Keep log file writes off the messages lock, and off the UI thread (BL…
StephenMcConnel Sep 17, 2026
0a69a60
Handle deleted TC books (BL-16729)
StephenMcConnel Sep 17, 2026
f7bba89
Merge remote-tracking branch 'origin/BL-16729-TeamCollectionWhenDropb…
StephenMcConnel Sep 17, 2026
f38cfff
Fix two Devin findings in the disconnect paths (BL-16729)
StephenMcConnel Sep 17, 2026
04e5b13
Merge branch 'master' into BL-16729-TeamCollectionWhenDropboxStops
StephenMcConnel Sep 28, 2026
4b0c44e
Split the repo catch-up scan into its two halves (BL-16729)
StephenMcConnel Sep 28, 2026
a4734d1
Merge remote-tracking branch 'origin/master' into BL-16729-TeamCollec…
StephenMcConnel Sep 28, 2026
59f577e
Restore the OverflowChecker fixtures prettier had reformatted (BL-16729)
StephenMcConnel Sep 28, 2026
ec600ed
Keep prettier off the generated OverflowChecker fixtures for real (BL…
StephenMcConnel Sep 28, 2026
de42fbe
Don't strand a Books watcher when monitoring stops mid-retry (BL-16729)
StephenMcConnel Sep 28, 2026
cb92798
Keep the collection id when we disconnect mid-session (BL-16729)
StephenMcConnel Sep 28, 2026
70be9a5
Act on three of the preflight decisions (BL-16729)
StephenMcConnel Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions DistFiles/localization/en/BloomMediumPriority.xlf
Comment thread
StephenMcConnel marked this conversation as resolved.
Comment thread
StephenMcConnel marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,21 @@
<note>ID: ImageLibrary.ThisPage</note>
<note>Short link text used in setup instructions (e.g., as in "go to this page"). Should be lowercase as it appears mid-sentence.</note>
</trans-unit>
<trans-unit id="TeamCollection.LostContactWithRepo" translate="no">
Comment thread
StephenMcConnel marked this conversation as resolved.
Comment thread
StephenMcConnel marked this conversation as resolved.
<source xml:lang="en">Bloom can no longer watch the Team Collection folder at "{0}", so it will not see changes made by your teammates. Usually this means the folder, or the drive or network it is on, is no longer available.</source>
<note>ID: TeamCollection.LostContactWithRepo</note>
<note>Shown in the Team Collection status dialog when Bloom loses its ability to watch the shared Team Collection folder part way through a session, for example because the network share or Dropbox folder disappears. {0} is replaced with the full path of the shared folder. "Bloom" is a product name and must not be translated.</note>
</trans-unit>
<trans-unit id="TeamCollection.NoLongerSeeingChanges" translate="no">
<source xml:lang="en">Bloom can no longer see the Team Collection folder, so you will not see changes made by your teammates.</source>
<note>ID: TeamCollection.NoLongerSeeingChanges</note>
<note>A short pop-up notification (toast) in the bottom-right corner of the window, shown the moment Bloom notices part way through a session that it has lost contact with the shared Team Collection folder. Clicking it opens the Team Collection dialog, which shows the longer TeamCollection.LostContactWithRepo message. Keep it short; it has to fit in a small pop-up. "Bloom" is a product name and must not be translated.</note>
</trans-unit>
<trans-unit id="TeamCollection.MayHaveMissedChanges" translate="no">
<source xml:lang="en">Bloom may have missed some changes your teammates made. Please click "Reload Collection" to be sure you have the latest.</source>
<note>ID: TeamCollection.MayHaveMissedChanges</note>
<note>Used both as a pop-up notification (toast) and in the Team Collection status dialog, when so many files changed at once that Bloom's file-change notifications overflowed and some were lost. "Reload Collection" is the label of a button in that dialog and should match its translation there. "Bloom" is a product name and must not be translated.</note>
</trans-unit>
<trans-unit id="BookProcessor.HousekeepingMessage">
<source xml:lang="en">Please wait while Bloom does some housekeeping on your book...</source>
<note>ID: BookProcessor.HousekeepingMessage</note>
Expand Down
6 changes: 5 additions & 1 deletion src/BloomBrowserUI/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ pnpm-lock.yaml

# Overflow test fixtures, generated from their .pug (see the note there). Prettier would add
# whitespace inside the test boxes, which changes what the overflow tests measure.
bookEdit/OverflowChecker/*Fixture.html
# The leading **/ matters: the pre-commit hook runs pretty-quick, which resolves the git
# root and passes repo-root-relative paths (src/BloomBrowserUI/bookEdit/...). A pattern
# anchored at this directory does not match those, so the fixtures got reformatted --
# and four OverflowChecker tests failed -- on any commit that happened to stage them.
**/bookEdit/OverflowChecker/*Fixture.html
Comment thread
StephenMcConnel marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export const CollectionsTabBookPane: React.FunctionComponent<{

setBookTeamCollectionStatus((prevBookStatus) => ({
Comment thread
StephenMcConnel marked this conversation as resolved.
...prevBookStatus,
disconnected: true,
// The field is isDisconnected; setting "disconnected" left the panel
// rendering the book as available for checkout. See BL-16729.
isDisconnected: true,
error: errorMessage,
}));
},
Expand Down
230 changes: 120 additions & 110 deletions src/BloomE2E/tests/reader-tool-stage-and-level.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,113 +45,123 @@ test.beforeEach(async ({ page }) => {
// The old toolbox code leans on that late restore to correct other things, so the fix is left to
// the rework. toolbox-sections.spec.ts has the same problem and a fuller account.

test.fixme("each book remembers its own decodable stage [Test Case ID 442]", async ({
page,
}) => {
const firstBook = await makeBasicBookWithReaderTool(
page,
"decodableReader",
);
await setReaderPhase(page, "decodableReader", 2);
const secondBook = await makeBasicBookWithReaderTool(
page,
"decodableReader",
);
await setReaderPhase(page, "decodableReader", 4);

await editBook(page, firstBook);
await openReaderTool(page, "decodableReader");
await expectReaderToolToShow(
page,
"decodableReader",
2,
"The first book should come back on the stage it was left on.",
);

await editBook(page, secondBook);
await openReaderTool(page, "decodableReader");
await expectReaderToolToShow(
page,
"decodableReader",
4,
"The second book should come back on its own stage, not the first book's.",
);
});

test.fixme("each book remembers its own level [Test Case ID 442]", async ({
page,
}) => {
const firstBook = await makeBasicBookWithReaderTool(page, "leveledReader");
await setReaderPhase(page, "leveledReader", 2);
const secondBook = await makeBasicBookWithReaderTool(page, "leveledReader");
await setReaderPhase(page, "leveledReader", 3);

await editBook(page, firstBook);
await openReaderTool(page, "leveledReader");
await expectReaderToolToShow(
page,
"leveledReader",
2,
"The first book should come back on the level it was left on.",
);

await editBook(page, secondBook);
await openReaderTool(page, "leveledReader");
await expectReaderToolToShow(
page,
"leveledReader",
3,
"The second book should come back on its own level, not the first book's.",
);
});

test.fixme("a new book starts on the stage last chosen, not the one last seen [Test Case ID 441]", async ({
page,
}) => {
const bookLeftOnStage2 = await makeBasicBookWithReaderTool(
page,
"decodableReader",
);
await setReaderPhase(page, "decodableReader", 2);
await makeBasicBookWithReaderTool(page, "decodableReader");
await setReaderPhase(page, "decodableReader", 4);
// Look at the first book again. Its stage 2 is restored, which must not become the default.
await editBook(page, bookLeftOnStage2);
await openReaderTool(page, "decodableReader");
// sanity check: the restore we are guarding against really did show stage 2
await expectReaderToolToShow(
page,
"decodableReader",
2,
"The first book should have come back on stage 2 before the new book was made.",
);

await makeBookFromTemplate(page, "Decodable Reader");
await openReaderTool(page, "decodableReader");

await expectReaderToolToShow(
page,
"decodableReader",
4,
"A new decodable book should start on the stage last chosen with the arrows (4), " +
"not on the stage of the book last looked at (2), nor on stage 1.",
);
});

test.fixme("a new book starts on the level last chosen [Test Case ID 460]", async ({
page,
}) => {
await makeBasicBookWithReaderTool(page, "leveledReader");
await setReaderPhase(page, "leveledReader", 3);

// makeBasicBookWithReaderTool waits for the new book to show the level Bloom reports as its
// default, so what this test adds is that the default really is the level chosen above.
await makeBasicBookWithReaderTool(page, "leveledReader");

await expectReaderToolToShow(
page,
"leveledReader",
3,
"A new book should start on the level last chosen, not on level 1.",
);
});
test.fixme(
Comment thread
StephenMcConnel marked this conversation as resolved.
Comment thread
StephenMcConnel marked this conversation as resolved.
"each book remembers its own decodable stage [Test Case ID 442]",
async ({ page }) => {
const firstBook = await makeBasicBookWithReaderTool(
page,
"decodableReader",
);
await setReaderPhase(page, "decodableReader", 2);
const secondBook = await makeBasicBookWithReaderTool(
page,
"decodableReader",
);
await setReaderPhase(page, "decodableReader", 4);

await editBook(page, firstBook);
await openReaderTool(page, "decodableReader");
await expectReaderToolToShow(
page,
"decodableReader",
2,
"The first book should come back on the stage it was left on.",
);

await editBook(page, secondBook);
await openReaderTool(page, "decodableReader");
await expectReaderToolToShow(
page,
"decodableReader",
4,
"The second book should come back on its own stage, not the first book's.",
);
},
);

test.fixme(
"each book remembers its own level [Test Case ID 442]",
async ({ page }) => {
const firstBook = await makeBasicBookWithReaderTool(
page,
"leveledReader",
);
await setReaderPhase(page, "leveledReader", 2);
const secondBook = await makeBasicBookWithReaderTool(
page,
"leveledReader",
);
await setReaderPhase(page, "leveledReader", 3);

await editBook(page, firstBook);
await openReaderTool(page, "leveledReader");
await expectReaderToolToShow(
page,
"leveledReader",
2,
"The first book should come back on the level it was left on.",
);

await editBook(page, secondBook);
await openReaderTool(page, "leveledReader");
await expectReaderToolToShow(
page,
"leveledReader",
3,
"The second book should come back on its own level, not the first book's.",
);
},
);

test.fixme(
"a new book starts on the stage last chosen, not the one last seen [Test Case ID 441]",
async ({ page }) => {
const bookLeftOnStage2 = await makeBasicBookWithReaderTool(
page,
"decodableReader",
);
await setReaderPhase(page, "decodableReader", 2);
await makeBasicBookWithReaderTool(page, "decodableReader");
await setReaderPhase(page, "decodableReader", 4);
// Look at the first book again. Its stage 2 is restored, which must not become the default.
await editBook(page, bookLeftOnStage2);
await openReaderTool(page, "decodableReader");
// sanity check: the restore we are guarding against really did show stage 2
await expectReaderToolToShow(
page,
"decodableReader",
2,
"The first book should have come back on stage 2 before the new book was made.",
);

await makeBookFromTemplate(page, "Decodable Reader");
await openReaderTool(page, "decodableReader");

await expectReaderToolToShow(
page,
"decodableReader",
4,
"A new decodable book should start on the stage last chosen with the arrows (4), " +
"not on the stage of the book last looked at (2), nor on stage 1.",
);
},
);

test.fixme(
"a new book starts on the level last chosen [Test Case ID 460]",
async ({ page }) => {
await makeBasicBookWithReaderTool(page, "leveledReader");
await setReaderPhase(page, "leveledReader", 3);

// makeBasicBookWithReaderTool waits for the new book to show the level Bloom reports as its
// default, so what this test adds is that the default really is the level chosen above.
await makeBasicBookWithReaderTool(page, "leveledReader");

await expectReaderToolToShow(
page,
"leveledReader",
3,
"A new book should start on the level last chosen, not on level 1.",
);
},
);
Loading
Loading