Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions PAPERCUTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ House rules:

---

## 2026-09-04 — Launcher times out waiting for BLOOM_AUTOMATION_READY while a direct dotnet watch works
- **Cut:** `go.mjs` / `launcherControl.mjs --ensure-running` built Bloom in ~6s, printed `dotnet watch ⌚ Loaded 2 project(s)`, then never saw the ready marker and tore the whole stack down after the 120s `launchTimeoutMs` in `scripts/watchBloomExe.mjs` — three times in a row. Running `dotnet watch run --project src/BloomExe/BloomExe.csproj --non-interactive -- --automation` by hand from the same shell started Bloom and printed `BLOOM_AUTOMATION_READY` within seconds (alongside a running BetaInternal, so it was not the single-instance token).
- **Idea:** Find what differs when `watchBloomExe.mjs` spawns dotnet watch (`--vite-port`/`--label` args, control-port env, stdout piping) and make the launcher print dotnet watch's later output or the Bloom PID's window titles when it gives up, so the failure is diagnosable. Consider making the timeout configurable.
- **Context:** worktree Format-Gear-Positioning-356 at the Version6.5 tip, while fixing BL-16809; hit by Claude.

## 2026-07-30 — Visual regression suite reports only the first stale image per case
- **Cut:** Each case in `src/BloomVisualRegressionTests/index.spec.ts` compares the book preview
and then every bloom-player page in sequence, and every comparison throws on failure — so the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ export const TopicChooserDialog: React.FunctionComponent<

const dialogTitle = useL10n("Choose Topic", "TopicChooser.Title");

// Tell edit tab to disable everything when the dialog is up.
// (Without this, the page list is not disabled since the modal
// div only exists in the book pane. Once the whole edit tab is inside
// one browser, this would not be necessary.)
// Tell C# to lock the workspace tabs while the dialog is up.
React.useEffect(() => {
if (propsForBloomDialog.open === undefined) return;

Expand Down
5 changes: 4 additions & 1 deletion src/BloomBrowserUI/bookEdit/bloomField/BloomField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import BloomMessageBoxSupport from "../../utils/bloomMessageBoxSupport";
import { tryProcessHyperlink } from "./hyperlinks";
import { EditableDivUtils } from "../js/editableDivUtils";
import $ from "jquery";
import { showLinkTargetChooserDialog } from "../../react_components/LinkTargetChooser/LinkTargetChooserDialogLauncher";
import { getWorkspaceBundleExports } from "../js/workspaceFrames";
import { getLocalization } from "../../react_components/l10n";
import { kNoIndentClass } from "../textContextMenu/noIndent";

Expand Down Expand Up @@ -401,6 +401,9 @@ export default class BloomField {

ckeditor.addCommand("setupHyperlink", {
exec: function (edt) {
// Shown from the workspace root so its backdrop covers the page list too.
const showLinkTargetChooserDialog =
getWorkspaceBundleExports().showLinkTargetChooserDialog;
showLinkTargetChooserDialog("", (url) => {
if (!url) return;
get("app/selectedBookInfo", (bookInfo) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ export const CopyrightAndLicenseDialog: React.FunctionComponent<{

const dialogTitle = useL10n("Copyright and License", "CopyrightAndLicense");

// Tell edit tab to disable everything when the dialog is up.
// (Without this, the page list is not disabled since the modal
// div only exists in the book pane. Once the whole edit tab is inside
// one browser, this would not be necessary.)
// Tell C# to lock the workspace tabs while the dialog is up.
React.useEffect(() => {
if (propsForBloomDialog.open === undefined) return;

Expand Down
5 changes: 3 additions & 2 deletions src/BloomBrowserUI/bookEdit/js/linkGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import WebSocketManager, {
IBloomWebSocketEvent,
} from "../../utils/WebSocketManager";
import { postJson } from "../../utils/bloomApi";
import { showBookGridSetupDialog } from "../../react_components/BookGridSetup/BookGridSetupDialog";
import { getWorkspaceBundleExports } from "./workspaceFrames";
import { Link } from "../../react_components/BookGridSetup/BookLinkTypes";

function getLanguage1Tag(): string {
Expand Down Expand Up @@ -56,7 +56,8 @@ export function editLinkGrid(linkGrid: HTMLElement) {
};
});

showBookGridSetupDialog(
// Shown from the workspace root so its backdrop covers the page list too.
getWorkspaceBundleExports().showBookGridSetupDialog(
currentLinks,
// callback if they press OK
(links: Link[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ import {
showDialogToChooseSoundFileAsync,
} from "../games/GameTool";
import { showTalkingBookTool } from "../talkingBook/showTalkingBookTool";
import { showLinkTargetChooserDialog } from "../../../react_components/LinkTargetChooser/LinkTargetChooserDialogLauncher";
import { kBloomBlue } from "../../../bloomMaterialUITheme";
import { trackEvent } from "../../../utils/bloomApi";
import {
Expand Down Expand Up @@ -1149,6 +1148,9 @@ export const controlRegistry: Record<TopLevelControlId, IControlDefinition> = {
// element itself (not on any nested image container).
const currentUrl =
ctx.canvasElement.getAttribute("data-href") ?? "";
// Shown from the workspace root so its backdrop covers the page list too.
const showLinkTargetChooserDialog =
getWorkspaceBundleExports().showLinkTargetChooserDialog;
showLinkTargetChooserDialog(currentUrl, (newUrl) => {
if (newUrl) {
ctx.canvasElement.setAttribute("data-href", newUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,7 @@ export const AdjustTimingsDialog: React.FunctionComponent<{
);
const [timingsFilePath, setTimingsFilePath] = useState<string>();

// Tell edit tab to disable everything when the dialog is up.
// (Without this, the page list is not disabled since the modal
// div only exists in the book pane. Once the whole edit tab is inside
// one browser, this would not be necessary.)
// Tell C# to lock the workspace tabs while the dialog is up.
React.useEffect(() => {
if (propsForBloomDialog.open === undefined) return;
postBoolean("editView/setModalState", propsForBloomDialog.open);
Expand Down
20 changes: 20 additions & 0 deletions src/BloomBrowserUI/bookEdit/workspaceRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
hideColorPickerDialog as doHideColorPickerDialog,
} from "../react_components/color-picking/colorPickerDialog";
import { postJson } from "../utils/bloomApi";
import { Link } from "../react_components/BookGridSetup/BookLinkTypes";
import "../modified_libraries/jquery-ui/jquery-ui-1.10.3.custom.min.js"; //for dialog()
import $ from "jquery";

Expand All @@ -33,6 +34,14 @@ export interface IWorkspaceExports {
hideColorPickerDialog(): void;
showCopyrightAndLicenseDialog(imageUrl?: string): void;
showEditViewTopicChooserDialog(): void;
showLinkTargetChooserDialog(
currentUrl: string,
onSetUrl: (url: string) => void,
): void;
showBookGridSetupDialog(
currentLinks: Link[],
setLinksCallback: (links: Link[]) => void,
): void;
showAdjustTimingsDialogFromWorkspaceRoot(
currentTextBox: HTMLElement,
// The split and applyTimingsFile calls both return a list of new timings,
Expand Down Expand Up @@ -70,6 +79,13 @@ import { getEditablePageBundleExports } from "./js/workspaceFrames";
export { getEditablePageBundleExports };
import { showPageChooserDialog } from "../pageChooser/PageChooserDialog";
export { showPageChooserDialog };
// These two are launched from code that runs in the page iframe. They must be shown from here,
// the workspace root, so that their modal backdrop covers the whole workspace including the
// page list; rendered in the page iframe the backdrop covers only the book pane (BL-16809).
import { showLinkTargetChooserDialog } from "../react_components/LinkTargetChooser/LinkTargetChooserDialogLauncher";
export { showLinkTargetChooserDialog };
import { showBookGridSetupDialog } from "../react_components/BookGridSetup/BookGridSetupDialog";
export { showBookGridSetupDialog };

import "../lib/errorHandler";
import { showBookSettingsDialog } from "./bookAndPageSettings/BookAndPageSettingsDialog";
Expand Down Expand Up @@ -449,6 +465,8 @@ interface WorkspaceBundleApi {
getToolboxBundleExports: typeof getToolboxBundleExports;
getEditablePageBundleExports: typeof getEditablePageBundleExports;
showPageChooserDialog: typeof showPageChooserDialog;
showLinkTargetChooserDialog: typeof showLinkTargetChooserDialog;
showBookGridSetupDialog: typeof showBookGridSetupDialog;
showBookSettingsDialog: typeof showBookSettingsDialog;
showRegistrationDialog: typeof showRegistrationDialogForEditTab;
showAboutDialog: typeof showAboutDialog;
Expand Down Expand Up @@ -495,6 +513,8 @@ window.workspaceBundle = {
getToolboxBundleExports,
getEditablePageBundleExports,
showPageChooserDialog,
showLinkTargetChooserDialog,
showBookGridSetupDialog,
Comment thread
JohnThomson marked this conversation as resolved.
showBookSettingsDialog,
showRegistrationDialog: showRegistrationDialogForEditTab,
showAboutDialog,
Expand Down
5 changes: 1 addition & 4 deletions src/BloomBrowserUI/pageChooser/PageChooserDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,7 @@ export const PageChooserDialog: React.FunctionComponent<
HTMLDivElement | undefined
>(undefined);

// Tell edit tab to disable everything when the dialog is up.
// (Without this, the page list is not disabled since the modal
// div only exists in the book pane. Once the whole edit tab is inside
// one browser, this would not be necessary.)
// Tell C# to lock the workspace tabs while the dialog is up.
useEffect(() => {
if (open === undefined) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import * as React from "react";
import { ShowEditViewDialog } from "../../bookEdit/workspaceRoot";
import { LinkTargetChooserDialog } from "./LinkTargetChooserDialog";

// Call this only from the workspace root (from the page iframe, go through
// getWorkspaceBundleExports().showLinkTargetChooserDialog): ShowEditViewDialog renders in the
// calling frame's document, and only in the root does the dialog's backdrop cover the page list.
export const showLinkTargetChooserDialog = (
currentUrl: string,
onSetUrl: (url: string) => void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const RegistrationDialogLauncher: React.FunctionComponent<

React.useEffect(() => {
if (props.dialogEnvironment?.mode === Mode.Edit) {
// Tell edit tab to disable everything when the dialog is up
// Tell C# to lock the workspace tabs while the dialog is up
postBoolean("editView/setModalState", propsForBloomDialog.open);
}
}, [props.dialogEnvironment?.mode, propsForBloomDialog.open]);
Expand Down
17 changes: 12 additions & 5 deletions src/BloomExe/Edit/EditingView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ internal void StartNavigationToEditPage(IPage page)
_model.SetupServerWithCurrentBookToolboxContents();
WorkspaceView.ReloadWorkspaceRootDocument();
}
SetModalState(false); // ensure _pageListView is enabled (BL-9712).
SetModalState(false); // ensure the tabs are not left locked (BL-9712).
#if MEMORYCHECK
// Check memory for the benefit of developers.
Bloom.Utils.MemoryManagement.CheckMemory(
Expand Down Expand Up @@ -1545,18 +1545,25 @@ public void Dispose()
public string HelpTopicUrl => "/Tasks/Edit_tasks/Edit_tasks_overview.htm";

/// <summary>
/// Prevent navigation, e.g. while a dialog box is showing in the browser control
/// Lock workspace navigation (the tabs), e.g. while a dialog box is showing in the browser
/// control. Calls nest: each true must be matched by a false.
/// </summary>
/// <remarks>
/// This used to disable the page list as well, because the page list lived in its own
/// browser and a modal dialog's backdrop in the main browser could not cover it. The whole
/// edit tab is in one browser now, so the backdrop already blocks the page list, and the
/// C# gate could only do harm: the browser posted the dialog's "closed" notice and the command
/// the dialog confirmed (e.g. Remove Page) as two concurrent requests, and when the command was
/// handled first it was silently refused (BL-16809).
/// </remarks>
internal void SetModalState(bool isModal)
{
if (isModal)
_modalDialogDepth++;
else
_modalDialogDepth = Math.Max(0, _modalDialogDepth - 1);

var isActuallyModal = _modalDialogDepth > 0;
_pageListView.Enabled = !isActuallyModal;
_workspaceView?.SetTabsEnabled(!isActuallyModal);
_workspaceView?.SetTabsEnabled(_modalDialogDepth == 0);
}

public void ShowAddPageDialog()
Expand Down
5 changes: 0 additions & 5 deletions src/BloomExe/Edit/PageListController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,5 @@ public void EmptyThumbnailCache()
{
_thumbNailList.EmptyThumbnailCache();
}

public bool Enabled
{
set { _thumbNailList.Enabled = value; }
}
}
}
6 changes: 2 additions & 4 deletions src/BloomExe/Edit/PageThumbnailList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ private List<IPage> UpdateItemsInternal(IEnumerable<IPage> pages)

internal void PageClicked(IPage page)
{
if (Enabled)
InvokePageSelectedChanged(page);
InvokePageSelectedChanged(page);
}

/// <summary>
Expand All @@ -217,7 +216,7 @@ internal void PageClicked(IPage page)
/// </remarks>
internal bool IsContextMenuCommandEnabled(IPage page, string commandId)
{
if (!Enabled || page == null)
if (page == null)
return false;

switch (commandId)
Expand Down Expand Up @@ -272,7 +271,6 @@ internal void ExecuteContextMenuCommand(IPage page, string commandId)
}

private PageListApi _pageListApi;
internal bool Enabled = true;

// This gets invoked by Javascript (via the PageListApi) when it determines that a particular page has been moved.
// newIndex is the (zero-based) index that the page is moving to
Expand Down