Skip to content

Move setCaretOffset into onMouseDown and preventAutoscrollEnd back into onFocus#4371

Merged
raineorshine merged 12 commits into
cybersemics:mainfrom
ethan-james:4298-bug-prevent-autoscroll-end-on-focus
Jun 23, 2026
Merged

Move setCaretOffset into onMouseDown and preventAutoscrollEnd back into onFocus#4371
raineorshine merged 12 commits into
cybersemics:mainfrom
ethan-james:4298-bug-prevent-autoscroll-end-on-focus

Conversation

@ethan-james

@ethan-james ethan-james commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Fixes #4298

Overview

Triggering preventAutoscrollEnd in onMouseUp was problematic because mouseup isn't always dispatched, and when it is dispatched, it doesn't necessarily mean that the editable was focused. Moving preventAutoscrollEnd back into onFocus means that setCaretOffset needs to fire earlier, such as in onMouseDown where it spent most of its time during development of #3410.

Complications (solved)

  • Native iOS drag-and-drop text selection breaks when setCaretOffset is called onMouseDown.

Solution: In order to preserve native behavior, it is necessary to keep track of whether a press has ended (touchend has been dispatched) or whether it is ongoing in order to short-circuit onMouseDown.

  • Using setCaretOffset updates the Redux state in a way that triggers the shouldSetSelection effect, which creates duplicate invocations of preventAutoscroll and selection.set().

Solution: Call allowDefaultSelection at the same time as setCaretOffset to prevent shouldSetSelection from being true.


Follow-Up (speculative)

If we want, we can pursue the possibility of moving all selection.set behavior into the shouldSetSelection effect rather than maintaining separate selection.set behavior in setCaretOffset. The flow would look like:

  1. setCaretOffset updates caretOffset in the Redux store
  2. the shouldSetSelection effect evaluates its conditions and determines whether to act
  3. preventAutoscroll and selection.set are called in one declarative place

Confidence Level

low-to-medium pending further testing

I verified that deferring selection.set until the effect runs seemed to work well enough when tapping between thoughts, although triggering the selection.set in setCaretOffset is still necessary when tapping within the active thought because the shouldSetSelection effect isn't currently triggered when only the state.cursorOffset value updates.

I didn't test any of the commands that rely on shouldSetSelection, and I am somewhat confident that the flicker/lag can be avoided by either using useEffect as it is currently structured, or by changing to useLayoutEffect, which introduces additional uncertainty regarding all of those commands that need to be tested.

Pros

  1. consolidating all selection.set behavior would make useEditMode easier to reason about
  2. a single declarative solution for selection handling within editables would be more robust
  3. do away with allowDefaultSelection entirely (Remove allowDefaultSelection #4179)

Cons

  1. the shouldSetSelection effect is widely used, and many commands would need to be tested if it were changed
  2. calling selection.set in setCaretOffset guarantees that the caret position with minimal lag/flicker, which may be more difficult to guarantee if it is offloaded onto the React render cycle

Test Coverage (unsuccessful)

I tried to add a Browserstack iOS test, but I was never able to get it to fail on main (#4403)

@ethan-james ethan-james self-assigned this Jun 5, 2026
@ethan-james ethan-james marked this pull request as draft June 5, 2026 23:16
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

@ethan-james is attempting to deploy a commit to the Cybersemics Institute Team on Vercel.

A member of the Team first needs to authorize it.

@ethan-james ethan-james marked this pull request as ready for review June 16, 2026 21:42
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
em Error Error Jun 17, 2026 6:18pm

@raineorshine raineorshine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! The code looks fine.

Follow-Up (speculative)

If we want, we can pursue the possibility of moving all selection.set behavior into the shouldSetSelection effect rather than maintaining separate selection.set behavior in setCaretOffset. The flow would look like:

  1. setCaretOffset updates caretOffset in the Redux store
  2. the shouldSetSelection effect evaluates its conditions and determines whether to act
  3. preventAutoscroll and selection.set are called in one declarative place

That makes sense to me, and more closely matches the earlier declarative design. We can try this in a separate PR unless you think otherwise.

Test Coverage (unsuccessful)

I tried to add a Browserstack iOS test, but I was never able to get it to fail on main (#4403)

If manual testing doesn't turn up anything, I'm okay merging this PR and then working on an automated test afterwards. Since browser selection behavior is critical, fragile, and high risk, we will want to add test coverage.


@BayuAri This is a high risk change that requires extensive testing of the browser selection across all platforms. Any of the test cases from #3374 (comment) are fair game, but use your best judgment as you decide what to test. I'll let you manually test first before me. Thank you!

@raineorshine raineorshine requested a review from BayuAri June 17, 2026 18:46
@ethan-james

Copy link
Copy Markdown
Collaborator Author

Thank you! The code looks fine.

🎉

That makes sense to me, and more closely matches the earlier declarative design. We can try this in a separate PR unless you think otherwise.

Yes, I think exploring it in a separate PR makes sense.

If manual testing doesn't turn up anything, I'm okay merging this PR and then working on an automated test afterwards. Since browser selection behavior is critical, fragile, and high risk, we will want to add test coverage.

Sounds good. It would be good to explore whether mouseup is dispatched on the correct element or not to see if the tap helper is behaving the same as a real-world tap.

@BayuAri

BayuAri commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Original issue is fixed.
Caret is placed according on the correct thought

Issue A: Toolbar is jumping down and up when user moves the caret between Thoughts

- Hello
   - A
   - B
   - C
- World
   - F
   - G
   - H
- Salsa
- Tango

Step to Reproduce

  1. Place caret at the end of "Hello"
  2. Move the caret to the end of "World"
  3. Move the caret to the end of "Salsa"
  4. Move the caret to the end of "Tango"

Current behavior

The toolbar is jumping down and up.
Focus is always placed under the toolbar, so the thought is auto scrolled up.

As reference, the behavior on main is a bit difference, the toolbar is jittery and it would jump but not as always as the built on this PR. I am not sure if this is a regression or not, but the jumping is too frequent on the PR

image

PR/4371

Issue.A.Toolbar.is.jumping.down.and.up.when.user.moves.the.caret.between.Thoughts.MP4

main

Behavior.of.the.toolbar.on.main.MP4

Expected behavior

Toolbar should not jump when user moves caret between thoughts

Side note: a blank area under the Thought World on the main video is logged as #4408
Thought this is not reproducible on this PR, I will revisit the ticket when this PR is merged onto main for a double check.

image

@BayuAri BayuAri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found no more issue on this PR aside from Issue A: Toolbar is jumping down and up when user moves the caret between Thoughts

I logged 2 related issue uncovered with this PR tested as follow
#4408
#4426

@raineorshine

Copy link
Copy Markdown
Contributor

I found no more issue on this PR aside from Issue A: Toolbar is jumping down and up when user moves the caret between Thoughts

@ethan-james What's your assessment of this issue? My impression from Bayu is that it was preexisting but exacerbated in this PR.

@ethan-james

ethan-james commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

I found no more issue on this PR aside from Issue A: Toolbar is jumping down and up when user moves the caret between Thoughts

@ethan-james What's your assessment of this issue? My impression from Bayu is that it was preexisting but exacerbated in this PR.

I hadn't really noticed it on the iPhone 12 Pro, although it's not too hard to reproduce. It is much more dramatic on iPhone 16 Pro Max. I zeroed in on a test case where I toggle between F and G, and you can see that it looks pretty similar between the two branches.

Main:

Screen.Recording.2026-06-23.at.10.04.56.AM.mov

206dcb9:

Screen.Recording.2026-06-23.at.10.03.44.AM.mov

I broadened my tests to see if there were more cases of the toolbar jumping in the branch than there were on main, but my assessment is that they're pretty similar, at least on the iPhone 16 Pro Max simulator and the iPhone 12 Pro device.

@raineorshine

Copy link
Copy Markdown
Contributor

Thanks. Looks like a preventAutoscroll issue. I'm satisfied merging this and then addressing the scroll issue separately. It might even be solved by #4435.

@ethan-james

Copy link
Copy Markdown
Collaborator Author

Thanks. Looks like a preventAutoscroll issue. I'm satisfied merging this and then addressing the scroll issue separately. It might even be solved by #4435.

Yes, the test case looks perfect on the issue-3765 branch.

@raineorshine raineorshine merged commit 1a9f091 into cybersemics:main Jun 23, 2026
12 checks passed
ethan-james added a commit to ethan-james/em that referenced this pull request Jul 8, 2026
Bring the caretFocus.ts spec in line with the reproduction branch: the
finger-sized right-edge tap on a non-cursor thought, with the detailed
root-cause explanation (Safari touch-adjustment retargeting + the stale
offsetRef onMouseUp focus path that cybersemics#4371 removed). On this fixed branch
focus is prevented, so the spec asserts the keyboard stays down.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] Caret stays on old sibling with children

3 participants