From 82244f0fbaca75b2e66f02ec04594f7c9b129b52 Mon Sep 17 00:00:00 2001 From: sungl <81428141+Sun-GLiang@users.noreply.github.com> Date: Tue, 25 Aug 2026 18:56:43 +0800 Subject: [PATCH 1/3] fix(ui): preserve composer paste undo history Generated-by: Codex --- apps/desktop/e2e/composer-undo.spec.ts | 61 ++++++++++++++++++++++++++ packages/ui/src/composer.tsx | 14 ++++++ 2 files changed, 75 insertions(+) create mode 100644 apps/desktop/e2e/composer-undo.spec.ts diff --git a/apps/desktop/e2e/composer-undo.spec.ts b/apps/desktop/e2e/composer-undo.spec.ts new file mode 100644 index 0000000000..20396d36a4 --- /dev/null +++ b/apps/desktop/e2e/composer-undo.spec.ts @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { Locator } from '@playwright/test'; +import { expect, test, COMPOSER_INPUT } from './fixtures'; + +const TYPED = 'x'; +const PASTED = '中文 & "quoted"\r\nhttps://example.test/path?x=1&y=2\n第二行 <>&'; +const PASTED_AS_PLAIN_TEXT = PASTED.replace(/\r\n/g, '\n'); +const UNDO_SHORTCUT = process.platform === 'darwin' ? 'Meta+z' : 'Control+z'; + +async function expectComposerText( + composer: Locator, + expected: string, +): Promise { + await expect + .poll(() => composer.evaluate((element) => element.innerText.replace(/\r\n/g, '\n'))) + .toBe(expected); +} + +test('plain-text paste is undone separately from prior typing', async ({ window: page }) => { + const composer = page.locator(COMPOSER_INPUT); + + await composer.click(); + await page.keyboard.type(TYPED); + await expectComposerText(composer, TYPED); + + await composer.evaluate((element, pasted) => { + const transfer = new DataTransfer(); + transfer.setData('text/plain', pasted); + element.dispatchEvent( + new ClipboardEvent('paste', { bubbles: true, cancelable: true, clipboardData: transfer }), + ); + }, PASTED); + await expectComposerText(composer, `${TYPED}${PASTED_AS_PLAIN_TEXT}`); + + await page.keyboard.press(UNDO_SHORTCUT); + await expectComposerText(composer, TYPED); + + await page.keyboard.press(UNDO_SHORTCUT); + await expectComposerText(composer, ''); + + await page.keyboard.press(UNDO_SHORTCUT); + await expectComposerText(composer, ''); +}); diff --git a/packages/ui/src/composer.tsx b/packages/ui/src/composer.tsx index 49a09e979c..fbd12c3afb 100644 --- a/packages/ui/src/composer.tsx +++ b/packages/ui/src/composer.tsx @@ -1711,6 +1711,20 @@ export const Composer = forwardRef< hasHistory={false} triggers={triggers} pasteAsToken={pasteAsToken} + onPaste={(_event, pasted) => { + // Astryx has already offered token-adjacent, file, and + // reference-sized-token pastes before it reaches this seam. + // `insertHTML` creates a browser undo transaction distinct + // from the keyboard input immediately before the paste. + if (props.disabled || isReferenceSizedPaste(pasted)) return false; + const plainTextContainer = document.createElement('div'); + plainTextContainer.textContent = pasted; + return document.execCommand( + 'insertHTML', + false, + plainTextContainer.innerHTML.replace(/\r\n?|\n/g, '
'), + ); + }} onFiles={onInputFiles} onKeyDown={onInputKeyDown} onCompositionStart={() => { compositionActiveRef.current = true; }} From 31c55d3743fd1d3cc2d846d57d8689d7289beae6 Mon Sep 17 00:00:00 2001 From: sungl <81428141+Sun-GLiang@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:50:22 +0800 Subject: [PATCH 2/3] chore: retrigger CI Generated-by: Codex From 50ab1d77e0374d0f02c0133fb554f03018fb0f99 Mon Sep 17 00:00:00 2001 From: sungl <81428141+Sun-GLiang@users.noreply.github.com> Date: Tue, 25 Aug 2026 23:51:23 +0800 Subject: [PATCH 3/3] chore: retrigger CI after review Generated-by: Codex