Resolve the @TODO in checkDarkMode.ts by adding 3 new detection
strategies and expanding data-attribute coverage:
- Check data-color-mode, data-bs-theme, data-color-scheme attributes
- Read CSS color-scheme property and <meta name="color-scheme"> tag
- Inspect background of SPA containers (#app, #root, #__next, etc.)
- Detect light text color as a dark-theme signal
The #moveCursorToTarget() method recursively schedules itself via
requestAnimationFrame, creating a continuous animation loop for the
AI cursor. However, dispose() only removes the DOM wrapper element
without stopping this loop, causing:
- CPU waste: rAF callback continues executing every frame (~60fps)
after the mask is disposed, performing unnecessary calculations
on a detached cursor element.
- Resource leak: Each SimulatorMask instance creates an unrecoverable
animation loop that persists for the lifetime of the page.
- Console noise: style assignments to removed DOM nodes may produce
browser warnings.
Fix: Add a #disposed boolean flag, checked at the top of
#moveCursorToTarget() to short-circuit the recursion. Set the flag
to true in dispose() before removing DOM elements.
Changes:
- Add #disposed field (default false)
- Guard #moveCursorToTarget() with early return when #disposed
- Set #disposed = true in dispose() before cleanup
Replace broken el.hasAttribute("aria-") with a curated list of 27
aria attributes checked via hasAttribute. Each check is O(1).
WAI-ARIA 1.2 defines ~50 aria attributes total per MDN.
Of these ~27 appear on interactive elements such as buttons,
inputs, sliders, and dialogs. The remaining ~20 are structural
container attributes like aria-live, aria-colcount, and
aria-rowspan that only appear on non-interactive containers.
Checking them would not change results.
Two bugs in the scroll direction logic:
1. Vertical scroll with `pixels` ignores the `down` boolean because the
`??` operator bypasses the direction multiplier when pixels is provided.
Fix: move the direction multiplier outside the `??` so it applies to
both the pixels and numPages paths.
2. Horizontal scroll with `pixels` applies direction twice - once in
PageController.ts and again in actions.ts - causing a double negation
that reverses the intended direction. Fix: remove the redundant
direction logic from actions.ts since PageController already signs
the scroll amount.
Also removes the now-unused `down` and `right` parameters from the
scrollVertically() and scrollHorizontally() action functions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline style.display with CSS class toggling.
Changes:
- Add .visible class to CSS module
- Use classList.add/remove instead of style.display
(cherry picked from commit 33465bbf520b65908c18d8022f259803253a7621)
When typing into contenteditable elements (e.g. LinkedIn post editor),
the synthetic event approach (Plan A) may fail silently — the events
fire but the editor's internal state doesn't update, leaving the
element empty.
This adds an automatic fallback: after Plan A, we verify the text was
actually inserted by checking element.innerText. If it wasn't, we
fall back to execCommand('insertText') which integrates natively with
most rich-text editors including LinkedIn, Quill, and Slate.js.
The fallback uses proper Selection/Range API to select-all before
replacing, and preserves the undo stack since execCommand is handled
by the browser natively.
Fixes#168
Elements detected as interactive via heuristic methods (cursor:pointer
style, interactive class names, event listeners) had empty attributes
because `isInteractiveCandidate()` was used as the gate for attribute
extraction. This function only recognizes standard HTML tags and ARIA
attributes, missing heuristic detections.
After interactivity is confirmed by `isInteractiveElement()`, backfill
attributes for elements that were missed. This ensures
`includeAttributes` (e.g. `['class']`) works correctly for all
interactive elements, not just semantically standard ones.
Closes#124
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
## Changes
Based on @gaomeng1900's comprehensive testing feedback:
1. **Document known limitations**
- Slate.js and Draft.js do not work with synthetic events
- Draft.js: Cannot be supported via DOM manipulation (by design, unmaintained)
- Monaco/CodeMirror: Require direct JS instance access
2. **Clarify tested editors**
- Works: Quill, LinkedIn, simple contenteditable editors
- Does not work: Slate.js, Draft.js
3. **Preserve execCommand as fallback**
- execCommand works better for LinkedIn, Quill, and Draft.js
- Kept as commented fallback (deprecated API)
- Users can uncomment if synthetic events don't work
Co-authored-by: gaomeng1900 <gaomeng1900@users.noreply.github.com>
## Changes
1. **Fix keyboard event semantics** (per review feedback)
- Only dispatch keydown/keyup for single-character input
- Avoids inconsistent event payloads for multi-character strings
- Prevents confusion in editors that correlate key events with text changes
2. **Remove extra blank line**
- Formatting consistency
Reviewer noted that dispatching key events with only the last character
of multi-character text creates semantic inconsistency with the actual
DOM mutation (which inserts the full string at once).
This fix follows the suggested change from the review.