Widen the edge-tab tap target, split scroll from reposition (#9)

The 7px edge tab is well below a usable touch target on a phone. This widens
only its *hit* area — the visible sliver still measures exactly 7 × 44 and
`offsetWidth`/`offsetHeight` still report it, so `placeFab`, `applyFabPos` and
the edge-snap maths are untouched.

- An invisible `#hit` child extends the tappable box inward to `28 × 72`.
  `overflow: hidden` had to go (it would clip `#hit`), so the rounded-corner
  clip for the dwell-progress fill moves onto `#fill` via `border-radius: inherit`.
- `touch-action` is resolved by the browser at gesture start, so the strip
  cannot be both browser-scrolled and script-dragged. `#fab` keeps
  `touch-action: none`, owns every gesture, and `makeDraggable` splits by
  intent: a plain swipe from `#hit` scrolls the page via `window.scrollBy`, a
  ~400ms hold arms a reposition drag (tab brightens and grows a ring), and the
  visible sliver still drags immediately with no hold.
- Adds the previously missing `pointercancel` reset, and snaps + saves on
  cancel so an OS-claimed gesture (Android's swipe-back starts in exactly this
  screen region) cannot strand the tab mid-screen.
- Clears a stale `dataset.dragged` on pointerdown, so a scroll or drag that
  produces no trailing click cannot swallow the *next* tap.

## Testing

`node --check` clean, `node --test userscript/test/logic.test.js` 14/14 pass.
The gesture code has no unit test — `logic.test.js` runs under node with no DOM
and this branch deliberately does not add a DOM harness. Manual device checks
(tap / swipe-to-scroll / hold-to-arm / sliver-drag on a chapter page) are the
real verification and are pending.

## Known ceilings

- The scroll is hand-rolled: no momentum or fling, and it assumes the document
  is the scroller rather than a nested container. Flagged in a `ponytail:`
  comment with the upgrade path.
- Gesture state is not keyed by `pointerId`, so a second finger corrupts an
  in-progress gesture. Pre-existing, not a regression.
- A >400ms still press on the *visible* sliver shows the armed ring even though
  the sliver never needs a hold. Cosmetic only.

Reviewed-on: #9
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
This commit was merged in pull request #9.
This commit is contained in:
2026-07-28 20:36:25 +07:00
committed by sulthan
parent 0416354c06
commit 1dc5b2ab65
2 changed files with 99 additions and 14 deletions
+8 -1
View File
@@ -96,7 +96,14 @@ Bromite userscript (isolated world, per-site adapters, localStorage cache)
5. **UI** — rendered inside a **Shadow DOM** root to isolate from site CSS
(critical on mobile). Three tabs (All / Favourites / Archived) and a row of
link chips to the web UI and both manga sites; `WEB_BASE` sits in the CONFIG
block next to `API_BASE`.
block next to `API_BASE`. The FAB is a `7 × 44` edge tab whose *hit* area is
widened to `28 × 72` by an invisible `#hit` child; `#fab` must keep
`touch-action: none` and must **not** regain `overflow: hidden`. Because
`touch-action` is resolved at gesture start, the strip cannot be both
browser-scrolled and script-dragged, so `makeDraggable` splits by intent: a
swipe from `#hit` scrolls via `window.scrollBy`, a hold of `ARM_MS` arms a
reposition drag, and the visible sliver drags with no hold. See
`docs/superpowers/specs/2026-07-28-edge-tab-hitbox-design.md`.
6. **SPA navigation** — Asura is Astro, client-routed on the comic/chapter pages: patch `history.pushState`/`replaceState` + listen `popstate`, re-run `detect()` on URL change so auto-update fires without reload. Demonic uses classic reloads (initial `document-idle` run suffices).
### Live URL shapes (verified 2026-07-26, may drift — re-check against live pages before trusting)