Skip to content

Extract editor selection lifecycle from App.vue#90

Merged
Renakoni merged 1 commit into
mainfrom
refactor/editor-selection-lifecycle
Jul 10, 2026
Merged

Extract editor selection lifecycle from App.vue#90
Renakoni merged 1 commit into
mainfrom
refactor/editor-selection-lifecycle

Conversation

@Renakoni

Copy link
Copy Markdown
Owner

Summary

Fifth PR of the semantic-decomposition series: moves the Android editor selection machinery out of App.vue into a createEditorSelectionLifecycle factory in features/editor. Behavior-preserving; Muya internals and the selection-toolbar UI are untouched, per the TODO.

Boundary

The factory owns:

  • Capture/restore: DOM range capture within the editor root, expanded-range restore that resyncs Muya's selection model and active content block, collapsed-caret restore, edge-collapse after clipboard commands, and the collapsed-tap restore guard for toolbar presses.
  • Selection-toolbar command sequencing for copy/cut/paste/select-all: the deterministic native-clipboard path (Muya clipboard pipeline + native bridge, with the empty-payload skip), the web execCommand path with its clipboard fallback, post-command caret placement, and the native select-all preference that keeps Chromium's touch handles alive.
  • ActionMode handling: dismissal with caret restoration, the outside-tap dismissal flow, and the selection-menu suppression bridge with its never-throws contract.
  • Native selection-tap listener lifecycle with reinstall-idempotence and awaited cleanup, including the tap handler's screen/collapsed/toolbar-hit guards.

Deliberately kept in App: pendingInlineInsertRange and the link/image insert flows (they consume selection capture but belong to the insert feature), the selection/input diagnostics installers (debugging facilities tied to editor init), and canPasteSelection (a UI availability prop). Platform bridges (lib/androidSelection) and the DOM helpers (editorInlineInsert, caretRangeAtPoint) are imported directly by the module; App-owned state comes in as refs/getters. App destructures the same seven function names, so editor init/teardown, the template bindings (@run-selection-command, @dismiss-selection), and the toolbar/insert flows are textually unchanged.

Testing

  • New selectionLifecycle.test.ts (11 tests, happy-dom, mocked platform bridge): capture round-trip, expanded-range restore syncing the Muya model, web-path copy with ActionMode dismissal and caret collapse, native-bridge cut, empty-payload skip, paste sequencing, native-vs-Muya select-all preference, suppression failure tolerance, tap-listener idempotence/cleanup, and the tap handler's dismissal and screen guards. happy-dom is added as a root dev dependency for real Range/Selection behavior — the Muya package already tests with the same environment.
  • Full unit suite 307/307, ESLint, vue-tsc, production build green.
  • Contract E2E 17/17: mobile-selection-toolbar.spec.ts (real-clipboard copy/cut/paste/select-all plus the placeholder-selection guard) and mobile-editor-toolbar.spec.ts (link/image insert flows that ride on selection capture/restore).

Moves the Android editor selection machinery into a
createEditorSelectionLifecycle factory in features/editor: DOM range
capture/restore kept in sync with Muya's selection model (including the
collapsed-restore, edge-collapse, and clear helpers), the
selection-toolbar command sequencing for copy/cut/paste/select-all with
its native-clipboard and execCommand paths, ActionMode dismissal with
caret restoration, the selection-menu suppression bridge, and the native
selection-tap listener lifecycle used to dismiss the toolbar.

Muya internals and the toolbar UI are untouched. App injects the editor
and element getters, screen/readiness refs, the input-diagnostics
snapshot, and the logger, and destructures the same function names, so
editor init/teardown, the toolbar bindings, and the link/image insert
flows are textually unchanged. The insert-flow state
(pendingInlineInsertRange) and the diagnostics installers stay in App —
they belong to the insert and debugging features that consume selection
capture, not to the lifecycle itself.

Adds happy-dom as a root dev dependency so the new module tests can
exercise real Range/Selection behavior; the Muya package already tests
with the same environment.
@Renakoni Renakoni force-pushed the refactor/editor-selection-lifecycle branch from a290f8a to d9cf9dc Compare July 10, 2026 12:41
@Renakoni

Copy link
Copy Markdown
Owner Author

Review finding addressed before merge: the tap-handler test claimed toolbar-hit coverage but elementFromPoint was stubbed to always return null, so the .closest('[data-testid="mobile-selection-toolbar"]') early-return branch never executed. Added a dedicated case that mounts a toolbar container with a button, points elementFromPoint at it, and asserts that a tap on a toolbar button neither finishes the ActionMode nor collapses the expanded selection — the exact behavior that keeps Copy/Cut/Paste/Select-All operating on the selection they were pressed for. As noted in the review, this branch is only reachable from the native tap callback, so the happy-dom unit test is the right (and only practical) home for it.

No production code changed. Re-verified: module tests 12/12, full unit suite 308/308, ESLint, vue-tsc green. Amended into the single commit (d9cf9dc).

@Renakoni Renakoni merged commit 2f9de7a into main Jul 10, 2026
6 checks passed
@Renakoni Renakoni deleted the refactor/editor-selection-lifecycle branch July 10, 2026 12:46
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.

1 participant