fix(panel): one tap edits the plan, with the caret on the words tapped
Merged
DA
5d ago+385+385+012345678901234567890123456789-308-308-012345678901234567890123456789
Summary
The developer tried #4119 on their phone and chose one tap edits: caret and keyboard where the tap lands. They also reported that the caret doesn't go where they tapped, because the page moves. Two commits:
- Revert #4119. On touch, the first tap opens the editor again, as with a mouse. This removes the second-tap rule, the title's read-only mode, and their tests.
- The caret lands on the words tapped. The editor draws a picture, a table, and a diagram's preview at different heights from the read view, so opening it moves everything below them. On the mobile-audit fake's plan at iPhone 13 width, text moves 59 px after the picture and 243 px after the Mermaid diagram. The caret was placed by the tap's coordinates, so it landed on whatever text had moved under the finger, and the page looked as if it jumped.
- A click now hands the editor the words it landed on: the section, the text node's words, how many identical nodes came before it in that section, and the offset (
PlacedWordsincontract.ts). It reads them withcaretRangeFromPoint, or Firefox'scaretPositionFromPoint. - The editor finds the same words in the same section and puts the caret there (
wordsAtinplan-editor.ts). - The plan's column then scrolls by however much the editor moved them, so the tapped line stays under the finger. It does this again each time the editor's height changes as later drawings land, such as a diagram preview or a chip, until the developer scrolls, presses or types (
wordsTopon the handle, aResizeObserverinPlanDocumentEditor).wordsTopanswers only while the editor's text and caret are the ones it opened on the found words with. So the fallback, a merged answer, a typed word, or a moved caret all scroll nothing. - Words in code blocks are counted apart from other words, so a diagram's code (shown as a picture in the read view, but kept in the editor) never takes the caret (from review).
- The scroll correction reads the same placement the editor opens with, so a press on the room while the bundle loads scrolls nothing (from review).
- The tap's coordinates are still the fallback, for example a tap off any words.
- A click now hands the editor the words it landed on: the section, the text node's words, how many identical nodes came before it in that section, and the offset (
This applies to the desktop too, where a click below a picture or diagram had the same offset.
Evidence
pnpm --filter @luke/panel typecheck,pnpm knipand oxlint on the changed files pass. Biome's findings onpackages/panel/srcare already onmain.- Panel suites
tasks,desktop,settingsandplan-editor: 200 files and 2317 tests pass. - New tests:
placement.test.ts: an editor opened on placed words puts the caret at the second matching node in the right section, at the offset. It fails when the words lookup is disabled.plan-document-editor.test.ts: a click hands over the words it landed on, using a stubbedcaretRangeFromPointas the browser boundary.plan-document-editor.test.ts: the column scrolls by the distance the editor moved the words.placement.test.ts: a paragraph's words aren't matched in a diagram's code that repeats them.placement.test.ts: when the words aren't in their section, the section is empty, or it's past the text's end, the caret falls back to the click's point andwordsTopanswers nothing.wordsTopstops answering once the caret is put elsewhere, or once a merged answer replaces the words' paragraph.plan-document-editor.test.ts: the words stay under the finger when the editor grows after opening, and a scroll by the developer lets them go. A click whose words weren't found scrolls nothing.plan-document-editor.test.ts: a press on the room after a click, while the bundle loads, opens at the end and scrolls nothing.
- Real Chromium with iPhone 13 touch emulation against
pnpm --filter @luke/web devand the mobile-audit fake. Each row is one tap:
| Tapped | Words under the finger | Caret after opening | Line before → after | Column scrolled |
|---|---|---|---|---|
| "Refresh 60 seconds…" (below the picture) | Refre|sh | Refre|sh | 331 → 327 | +59 |
| "Ship behind the…" (below the diagram) | Ship |behind | Ship |behind | 267 → 263 | +243 |
Before this change, the same taps would have put the caret 59 px and 243 px lower: two or more lines down, or in a later block.
Notes
- Still to check by hand on an iPhone: when the keyboard rises, iOS scrolls on its own so the caret stays visible above it. That shouldn't move the caret, but emulation can't show it.
Made with Luke