fix(mobile): cursor stays on archived thought after drag-to-archive#4405
fix(mobile): cursor stays on archived thought after drag-to-archive#4405Copilot wants to merge 3 commits into
Conversation
1d83798 to
6979171
Compare
#4077) When a focused thought is archived by dragging it to the DropGutter, a spurious post-drag tap/focus event fires on the now-hidden Editable and re-sets the cursor onto the archived thought, overriding the previous sibling that archiveThought correctly selected. Guard setCursorOnThought (the chokepoint for both onFocus and handleTapBehavior) to skip archived thoughts, resolving the thought's real path via thoughtToPath since the Editable path may no longer include the =archive ancestor. Adds a regression e2e test in DropGutter.ts.
6979171 to
99cfec5
Compare
STR C: Cursor is moved to the above of the archived thought.Step to Reproduce
Current behaviorCursor is moved to Expected behaviorCursor should stay on |
STR D: Cursor is moved to the below of the archived thought when the archived thought is firstStep to Reproduce
Current behaviorCursor is moved to Expected behaviorCursor should stay on |
…ferent thought (#4077) archiveThought unconditionally moved the cursor to a sibling of the archived thought. When a thought is dragged to the DropGutter while the cursor is on a different thought, the cursor now stays on its last known position. Only relocate the cursor when it is on or within the thought being archived.
Update — cursor-on-archive fix (#4077)Pushed What was fixedSTR A / STR B — cursor stays on the archived thought (commit STR C / STR D — cursor jumps off the last known position (commit const cursorOnArchived = isDescendantPath(state.cursor, path)
// …
cursorOnArchived ? setCursor({ path: cursorNew, … }) : nullThe cursor now stays on its last known position ( These are two distinct, complementary root causes (spurious focus vs. unconditional cursor relocation) with no overlap. Tests
Note on the
|
| `, | ||
| }), | ||
| setCursor(['Five']), | ||
| (state: State) => archiveThought({ path: contextToPath(state, ['Two'])! })(state), |
There was a problem hiding this comment.
archiveThought is curried so you don't need to pass state. Look at the way archiveThought is used in the other tests.
There was a problem hiding this comment.
Done — both tests now use the curried form, resolving the path from state first and calling archiveThought({ path })(state) without passing state to archiveThought.
| // Do not set the cursor on an archived thought. After a focused thought is archived by | ||
| // dragging it to the DropGutter, a spurious tap/focus event can fire on its (now hidden) | ||
| // Editable, which would otherwise override the cursor that archiveThought placed on the | ||
| // previous sibling. The Editable's path may no longer include the =archive ancestor, so the | ||
| // thought's real path is resolved to determine whether it is archived. (#4077) | ||
| if (isThoughtArchived(state, thoughtToPath(state, head(path)))) return |
There was a problem hiding this comment.
This doesn't seem right to me. If hidden thoughts are shown, then we very well may want to set the cursor on the archived thought.
A better solution would be to prevent the spurious tap/focus event to begin with.
There was a problem hiding this comment.
Replaced the archived-path guard. The override actually came from a spurious post-drag focus on the now-hidden Editable, so onFocus now only sets the cursor when isVisible. This prevents the spurious focus from re-cursoring without ever blocking archived thoughts: when hidden thoughts are shown, isVisible is true and the cursor sets normally.
| // The cursor should be placed on the previous sibling ("One"), not remain on the archived thought. | ||
| const cursorValue = await page.evaluate(() => { | ||
| const em = window.em as WindowEm | ||
| const cursor = em.testHelpers.getState().cursor |
There was a problem hiding this comment.
getState() is definitely not allowed in puppeteer tests. This is an integration test and should not have direct access to Redux state. It needs to interact with the browser like the user.
There was a problem hiding this comment.
Removed getState() from all three DropGutter tests; they now read the cursor value via getEditingText() ([data-editing=true] [data-editable]).
|
@copilot Please address the feedback. |
… feedback (#4077) Co-authored-by: raineorshine <750276+raineorshine@users.noreply.github.com>
|
|
#4077
After dragging a thought to the scroll zone (DropGutter) to archive it, the cursor behaved incorrectly: it remained on the archived thought (STR A/B), or jumped off its last known position when a different thought was archived (STR C/D).
Root cause (STR A/B):
archiveThoughtcorrectly sets the cursor to the previous sibling. However, on mobile a spurious post-drag focus event fires on the now-hiddenEditableof the archived thought, going throughsetCursorOnThoughtand overriding the cursor back onto the archived thought.Root cause (STR C/D):
archiveThoughtwas unconditionally relocating the cursor to a sibling of the archived thought, even when a different thought (not the cursor's) was archived.Changes
src/components/Editable.tsx— Prevent the spurious focus from re-cursoring at its source:onFocusonly sets the cursor whenisVisible, so a focus event on the now-hidden archived thought no longer overrides the cursor. When hidden thoughts are shown,isVisibleis true and the cursor can still be set on them.src/actions/archiveThought.ts— Only relocate the cursor when it is on (or within) the thought being archived (isDescendantPath(state.cursor, path)); otherwise preserve the cursor's last known position.src/actions/__tests__/archiveThought.ts— Added reducer tests for STR C/D using the curriedarchiveThoughtform.src/e2e/puppeteer/__tests__/DropGutter.ts— Added regression e2e tests for the STR A and STR C/D drag flows, asserting the cursor via user-observable DOM (getEditingText()) rather than Redux state.