Repository navigation
Keep the caret after text pasted into a note - #5992
Conversation
Co-Authored-By: GitHub Copilot Coding Agent unknown (unknown) <198982749+Copilot@users.noreply.github.com> Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>
Preview Deployment · Oct 27, 2026, 11:05 PM UTC · a360992 |
Insert the clipboard's plain text with execCommand('insertText') instead of
letting the browser paste HTML, which WebKit wraps in a styled span and a
trailing newline. Stripping that in onChange left a value that no longer
matched the DOM, so the note re-rendered and the caret jumped to the end.
Co-Authored-By: GitHub Copilot Coding Agent unknown (unknown) <198982749+Copilot@users.noreply.github.com>
Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>
|
@copilot |
…crub test Under Appium, navigator.webdriver is false in mobile Safari, so the cursor scroll after the paste is smooth and moves the note for ~350ms even with transitions disabled. A tap aimed mid-scroll landed on the parent thought. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>
showEditMenu tapped the selection at its viewport coordinates, which in mobile Safari lands 60px above it, under the browser chrome, so no edit menu appeared and the note-paste test failed at its Copy step. Add getScreenOffsetY as tap does, and wait for the note to come to rest before tapping it. Co-Authored-By: GitHub Copilot Coding Agent claude-opus-5.5 (high) <198982749+Copilot@users.noreply.github.com> Co-authored-by: BayuAri <8419585+BayuAri@users.noreply.github.com>
Fixed the failing BrowserStack (iOS) check in 9749cf2, and it's green on that commit along with Lint, Test, Puppeteer and BrowserStack Android. TDD was still running when I posted this.
Both tests passed in the iOS run. |
BayuAri
left a comment
There was a problem hiding this comment.
Confirmed fixed.
Text is pasted without adding extra line breaker and caret remain where it is left off accordingly (not moved to the end of the note or even disappear)
| // Disabling transitions does not stop the smooth scroll that brings the cursor into view. | ||
| await waitForElementAtRest(note) |
There was a problem hiding this comment.
Is this the browser's default behavior of scrolling the focused input to the center of the viewport? Can it be prevented, or does that have to wait for the various autoscroll bugs to be fixed?
It's not ideal to have to wait for a smooth scroll in a test that is about caret placement.
There was a problem hiding this comment.
@copilot Please answer the question above in this thread. Explanation only: don't change any code.
Pointers:
- The scroll comes from our own
scrollCursorIntoView(src/device/scrollCursorIntoView.ts), which usesbehavior: 'smooth'when the cursor is less than a viewport away. It is not the browser's native focus autoscroll, whichpreventAutoscrollalready suppresses for notes and thoughts. scrollCursorIntoViewandscrollToalready scroll instantly under automation (navigator.webdriver ? 'instant' : behavior). Appium leavesnavigator.webdriverfalse in mobile Safari, so that guard never fires on iOS.disableTransitionsonly disables CSS transitions. An explicitbehavioroption also overrides CSSscroll-behavior.- It can be prevented now without waiting on the autoscroll bugs, e.g. by having the helper coerce smooth
window.scrollTo/scrollBycalls to instant. That only removes the animation, though. The cursor scroll is still scheduled on the next tick and throttled to 400ms, so the note can still jump after it is found. KeepwaitForElementAtRestfor now.
There was a problem hiding this comment.
It's not the browser's default. The scroll is em's own scrollCursorIntoView (src/device/scrollCursorIntoView.ts). Safari's native scroll-on-focus is already suppressed for notes and thoughts by preventAutoscroll. After the paste moves the cursor, scrollCursorIntoView scrolls with behavior: 'smooth' whenever the distance is less than one viewport height.
There is already an automation guard, navigator.webdriver ? 'instant' : behavior, in both scrollCursorIntoView and scrollTo. On iOS it never fires, because Appium leaves navigator.webdriver false in mobile Safari. disableTransitions doesn't help either: it only turns off CSS transitions, and an explicit behavior option overrides CSS scroll-behavior anyway.
Can it be prevented? Yes, and it doesn't have to wait for the autoscroll bugs. A test helper could force smooth window.scrollTo/scrollBy calls to scroll instantly.
That only removes the animation, though. scheduleScrollCursorIntoView still runs the scroll on the next tick and throttles it to 400ms. So the note can still move after the test has found it, just in one jump instead of a slide. The test would still have to wait for the scroll to finish, and waitForElementAtRest is what does that. I'd keep it until the cursor scroll gives the test something to wait on, such as a signal that it has finished.
No code changes in this reply, as requested.
The cursor overlay is a child of LayoutTree, so its mount effect ran scrollCursorIntoView before LayoutTree's passive effect had written layoutTreeTop. With layoutTreeTop still 0, a cursor in plain view was measured as behind the toolbar and smooth-scrolled. A layout effect runs before any passive effect in the commit, so the first check now sees the tree's real top. Remove waitForElementAtRest from the note tests, which waited out that scroll instead of preventing it. Co-Authored-By: Claude claude-opus-5-5 (medium) <noreply@anthropic.com>
Opening the keyboard with newThought first let Safari scroll the page, so the cursor scroll after the paste moved the note while the test tapped it. Co-Authored-By: Claude claude-opus-5-5 (medium) <noreply@anthropic.com>
Co-Authored-By: Claude claude-opus-5-5 (medium) <noreply@anthropic.com>
Co-Authored-By: Claude claude-opus-5-5 (medium) <noreply@anthropic.com>
Safari's WebDriver does not implement getProperty, so the textContent read failed every attempt. Co-Authored-By: Claude claude-opus-5-5 (medium) <noreply@anthropic.com>

Pasting into the middle of a note on iOS added a line break and moved the caret to the end of the note. The issue also reports that Android drops the caret and keyboard; that did not reproduce in emulated Android Chrome, so this PR has only been checked against the iOS failure.
Cause: WebKit's paste inserts the text wrapped in a styled span plus a
\n:Note'sonChangethen stripped that markup. The saved value no longer matched the DOM, so react-contenteditable rewroteinnerHTML, which keeps the line break and drops the caret position.Changes
src/components/Note.tsx:onPastenow handles the paste itself.text/plain(ortext/uri-list), callspreventDefault(), and inserts the text withdocument.execCommand('insertText', false, text). The caret lands after the pasted text and the DOM matches the saved value.justPastedstrip inonChange, as before ([iOS PWA] Cannot import url #286).useOnPaste), a note does not runimportData. A multi-line paste stays literal text in the note, as it did before.src/e2e/iOS/helpers/tapEditMenuItem.ts(new): taps a named item (Copy, Paste, …) in the native iOS edit menu. This is the native-context logic that used to live inpasteFromEditMenu, which now calls it.src/e2e/iOS/__tests__/caret.ts: new regression test. It copies "Ipsum" from the thought through the native menu, then pastes it between "Sed " and "tortor" in the note. It expects the note to readSed Ipsumtortor nuncwith the caret at offset 9.docs/cursor-and-caret.md: notes how a note handles paste.