diff --git a/src/modules/services/LiveSyncUI/DialogHost.svelte b/src/modules/services/LiveSyncUI/DialogHost.svelte index 111dd4ae..6265c7bf 100644 --- a/src/modules/services/LiveSyncUI/DialogHost.svelte +++ b/src/modules/services/LiveSyncUI/DialogHost.svelte @@ -68,6 +68,8 @@ display: flex; flex-direction: column; padding-bottom: var(--keyboard-height, 0px); + user-select: text; + -webkit-user-select: text; } .dialog-host :global(button) { diff --git a/styles.css b/styles.css index 3d63babd..ab7ca645 100644 --- a/styles.css +++ b/styles.css @@ -12,6 +12,11 @@ background-color: var(--text-muted); } +.vpk-action-dialog__message { + user-select: text; + -webkit-user-select: text; +} + .conflict-dev-name { display: inline-block; min-width: 5em; diff --git a/test/e2e-obsidian/scripts/dialog-mounts.ts b/test/e2e-obsidian/scripts/dialog-mounts.ts index f2109068..99d565de 100644 --- a/test/e2e-obsidian/scripts/dialog-mounts.ts +++ b/test/e2e-obsidian/scripts/dialog-mounts.ts @@ -194,6 +194,28 @@ async function verifyRemoteSizeNoticeAndDialogue(): Promise<{ .filter({ hasText: "Synchronisation paused for compatibility review" }), }); await compatibilityReview.waitFor({ state: "visible", timeout: uiTimeoutMs }); + const message = compatibilityReview.locator(".vpk-action-dialog__message"); + const textSelection = await message.evaluate((element) => { + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(element); + selection?.removeAllRanges(); + selection?.addRange(range); + const selectedText = selection?.toString() ?? ""; + selection?.removeAllRanges(); + return { + selectedText, + userSelect: getComputedStyle(element).userSelect, + }; + }); + if ( + textSelection.userSelect !== "text" || + !textSelection.selectedText.includes("Remote synchronisation is paused on this device") + ) { + throw new Error( + `Expected the action dialogue message to be selectable, received user-select=${textSelection.userSelect} and selected text '${textSelection.selectedText}'.` + ); + } const actions = compatibilityReview.locator(".vpk-action-dialog__actions--vertical"); await actions.waitFor({ state: "visible", timeout: uiTimeoutMs }); const flexDirection = await actions.evaluate((element) => getComputedStyle(element).flexDirection); @@ -286,12 +308,32 @@ async function verifyRemoteSelectionDialogue(mode: DialogueMode): Promise { + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(element); + selection?.removeAllRanges(); + selection?.addRange(range); + const selectedText = selection?.toString() ?? ""; + selection?.removeAllRanges(); + return { + selectedText, + userSelect: getComputedStyle(element).userSelect, + }; + }); + if ( + textSelection.userSelect !== "text" || + !textSelection.selectedText.includes("signalling relay is required for peer discovery") + ) { + throw new Error( + `Expected Svelte dialogue prose to be selectable, received user-select=${textSelection.userSelect} and selected text '${textSelection.selectedText}'.` + ); + } await modal .getByRole("button", { name: "No, please take me back" }) .waitFor({ state: "visible", timeout: uiTimeoutMs }); @@ -831,9 +873,13 @@ async function verifyHatchSurfacesAndSafeActions(): Promise { } }; }, repairRunStateKey); - await liveSyncSettings.getByRole("button", { name: "Recreate all", exact: true }).click({ - timeout: uiTimeoutMs, - }); + await page + .locator(".sls-setting:visible") + .last() + .getByRole("button", { name: "Recreate current chunks", exact: true }) + .click({ + timeout: uiTimeoutMs, + }); await page.waitForFunction( (stateKey) => (globalThis as unknown as Record)[stateKey]?.done === true, @@ -848,9 +894,13 @@ async function verifyHatchSurfacesAndSafeActions(): Promise { throw new Error(`Recreate missing chunks failed: ${repairState.error}`); } - await liveSyncSettings.getByRole("button", { name: "Verify all", exact: true }).click({ - timeout: uiTimeoutMs, - }); + await page + .locator(".sls-setting:visible") + .last() + .getByRole("button", { name: "Verify all", exact: true }) + .click({ + timeout: uiTimeoutMs, + }); await page .locator(".notice") .filter({ hasText: /^done$/u }) diff --git a/updates.md b/updates.md index 2989226e..7d0ee73a 100644 --- a/updates.md +++ b/updates.md @@ -21,6 +21,7 @@ Earlier releases remain available in the 0.25 release history and the legacy rel - First-device P2P setup now accepts a successfully opened signalling room without requiring another peer to be online. Additional-device Fetch still requires selecting a source peer and completing `P2P Rebuild`. - Manual CouchDB setup now distinguishes creating a first database from connecting an additional device to an existing one. Settings mode can save an unverified profile explicitly, while onboarding requires a successful connection, and each proposed server-configuration fix requires separate confirmation. - Differences limited to the chunk hash algorithm, chunk size, or splitter version are now aligned automatically by default. Existing content remains readable, while an explicit opt-out and any difference which also involves an incompatible setting retain manual review. +- Text in setup and review dialogues can now be selected for copying or translation. ### Fixed @@ -31,7 +32,7 @@ Earlier releases remain available in the 0.25 release history and the legacy rel ### Testing -- Added regressions for revision repair, P2P configuration, the distinction between setting up the first device and using Fetch on an additional device, the P2P status pane, CouchDB setup policy, mobile dialogues, conflict-aware chunk reachability, device-progress safeguards, compaction timeouts, shared chunks, collection propagation, and content-addressed chunk recreation. +- Added regressions for revision repair, P2P configuration, the distinction between setting up the first device and using Fetch on an additional device, the P2P status pane, CouchDB setup policy, selectable and mobile dialogues, conflict-aware chunk reachability, device-progress safeguards, compaction timeouts, shared chunks, collection propagation, and content-addressed chunk recreation. ## 1.0.0-beta.2