From 4895e55c22b8296332158d8d45f4c7d05dbd3876 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 3 Oct 2026 20:18:15 +0400 Subject: [PATCH] fix(app): end a page switch quietly when another one takes over (#845) Switching pages again before the previous page finished preparing aborted the first switch, which rejected with an AbortError. Callers start page switches without awaiting them, so the rejection surfaced as an error toast. --- CHANGELOG.md | 1 + src/app/editor/session/create.ts | 7 ++++++- tests/app/editor/session/page-switch.test.ts | 22 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/app/editor/session/page-switch.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 428dd7536..5f7fb6bf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ ### Fixed +- Stop showing a “signal is aborted without reason” error when you switch pages again before the previous page has finished loading. - Export layers with two shadows as one `effects` prop instead of repeating the `shadow` attribute, background blurs as `backgroundBlur` instead of a layer blur, hidden children with `visible={false}` instead of leaving them out, and per-corner radii even when the uniform radius is 0. - Apply `strokeAlign`, `strokeDash`, `minH`, and `maxH` in design JSX, which were accepted but ignored, and make `minW` and `maxW` set the layer's minimum and maximum width rather than only clamping its initial width. - Render the canvas with the Vue SDK's `CanvasRoot` and `CanvasSurface`; CanvasKit never started there and the canvas stayed blank. diff --git a/src/app/editor/session/create.ts b/src/app/editor/session/create.ts index f8fa3c9b9..e066c8f73 100644 --- a/src/app/editor/session/create.ts +++ b/src/app/editor/session/create.ts @@ -181,7 +181,12 @@ export function createEditorStore(initialGraph?: SceneGraph) { } succeeded = true } catch (error) { - if (preparation.signal.aborted) throw error + if (preparation.signal.aborted) { + // Another switch took over; its page is the one to show, so this one ends quietly. + // A caller's own preparation reports its cancellation itself. + if (ownsPreparation) return + throw error + } if (ownsPreparation) { const presentationTimedOut = error instanceof Error && error.message === 'The operation was timed out' diff --git a/tests/app/editor/session/page-switch.test.ts b/tests/app/editor/session/page-switch.test.ts new file mode 100644 index 000000000..c5081b471 --- /dev/null +++ b/tests/app/editor/session/page-switch.test.ts @@ -0,0 +1,22 @@ +import 'fake-indexeddb/auto' +import { expect, test } from 'bun:test' + +import { createEditorStore } from '@/app/editor/session/create' + +test('a page switch overtaken by another resolves instead of rejecting', async () => { + const store = createEditorStore() + // Stand in for the canvas, which acknowledges each scene it presents. + store.onPreparationEvent('preparation:updated', (preparation) => { + if (preparation.phase === 'preparing-render') + store.preparationController.acknowledgePresentation(store.state.sceneVersion) + }) + const b = store.graph.addPage('B').id + const c = store.graph.addPage('C').id + + const first = store.switchPage(b) + const second = store.switchPage(c) + + await expect(first).resolves.toBeUndefined() + await second + expect(store.state.currentPageId).toBe(c) +})