@plannotator/pi-extension 0.28.45 → 0.28.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -61,6 +61,10 @@ body[data-plannotator-pinpoint-cursor],
61
61
  body[data-plannotator-pinpoint-cursor] * {
62
62
  cursor: crosshair !important;
63
63
  }
64
+ /* Armed, body listens for clicks so a tap pins (setTapTarget): no grey flash over the whole page on each tap. */
65
+ body[data-plannotator-pinpoint-cursor] {
66
+ -webkit-tap-highlight-color: transparent;
67
+ }
64
68
  /* A plain click on a link with a real destination follows it while armed
65
69
  * (Cmd/Ctrl-click pins it), so such a link keeps the pointer that says so,
66
70
  * until the modifier is held. A link that goes nowhere (no href, "", "#",
@@ -509,14 +513,31 @@ export const BRIDGE_SCRIPT = `(function() {
509
513
  var annotateModeActive = true;
510
514
  // A native host decides link navigations itself (HtmlViewer hostNavigates).
511
515
  var hostNavigates = false;
516
+ // WebKit's touch model (iOS, and Playwright's WebKit on Linux) turns a tap
517
+ // into mouse events and a click only when the tapped element or an ancestor
518
+ // up to <body> listens for a click; listeners on document and window (all
519
+ // of the bridge's) do not count. Without this, a tap on plain text while
520
+ // armed sends no click and pins nothing. So while armed in pinpoint, body
521
+ // listens (a no-op), and its tap highlight is off: the pin draws its own box.
522
+ var tapTargetBody = null;
523
+ function noteTap() {}
524
+ function setTapTarget(on) {
525
+ var body = on ? document.body : null;
526
+ if (body === tapTargetBody) return;
527
+ if (tapTargetBody) tapTargetBody.removeEventListener('click', noteTap);
528
+ tapTargetBody = body;
529
+ if (body) body.addEventListener('click', noteTap);
530
+ }
512
531
  function updatePinpointCursor() {
513
532
  if (!document.body) return;
514
533
  if (annotateModeActive && currentInputMethod === 'pinpoint') {
515
534
  document.body.setAttribute('data-plannotator-pinpoint-cursor', '');
516
535
  if (!LIVE) document.body.setAttribute('data-plannotator-frame-inert', '');
536
+ setTapTarget(true);
517
537
  } else {
518
538
  document.body.removeAttribute('data-plannotator-pinpoint-cursor');
519
539
  document.body.removeAttribute('data-plannotator-frame-inert');
540
+ setTapTarget(false);
520
541
  }
521
542
  }
522
543
  var pinpointHover = null;
@@ -873,11 +894,21 @@ export const BRIDGE_SCRIPT = `(function() {
873
894
  else if (type === PREFIX + 'reveal-pin') {
874
895
  // A host whose own panel covers the page's lower part (a phone's comment
875
896
  // sheet) shrank the frame: bring the pinned draft back into view.
897
+ // clearBottom: the panel floats over the frame instead (the Inbox's card).
876
898
  if (pendingPinEl && pendingPinEl.isConnected) {
877
- try { pendingPinEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (ex) {}
899
+ var clearBottom = e.data.clearBottom;
900
+ if (typeof clearBottom === 'number' && isFinite(clearBottom) && clearBottom > 0) {
901
+ revealAbove(pendingPinEl, Math.min(clearBottom, window.innerHeight));
902
+ } else {
903
+ try { pendingPinEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (ex) {}
904
+ }
878
905
  }
879
906
  }
880
907
 
908
+ else if (type === PREFIX + 'set-touch-selection') {
909
+ setTouchSelection(e.data.active === true);
910
+ }
911
+
881
912
  else if (type === PREFIX + 'scroll-to') {
882
913
  // Selecting an annotation scrolls its first resolved target into view
883
914
  // and flashes the overlay focus highlight over EVERY rect of EVERY
@@ -4125,6 +4156,67 @@ export const BRIDGE_SCRIPT = `(function() {
4125
4156
  return true;
4126
4157
  }
4127
4158
 
4159
+ // The pinned element in the frame's top part, a host's panel over the last
4160
+ // covered pixels: its middle in the middle of what shows, or its top when it
4161
+ // is taller. Already in sight, nothing moves.
4162
+ function revealAbove(el, covered) {
4163
+ var shown = window.innerHeight - covered;
4164
+ var box = el.getBoundingClientRect();
4165
+ if (box.top >= 0 && box.bottom <= shown) return;
4166
+ var by = box.height > shown ? box.top : box.top + box.height / 2 - shown / 2;
4167
+ var root = document.scrollingElement;
4168
+ for (var node = el.parentElement; node && by; node = node.parentElement) {
4169
+ if (node !== root && !/auto|scroll/.test(getComputedStyle(node).overflowY)) continue;
4170
+ var before = node.scrollTop;
4171
+ node.scrollTop = before + by;
4172
+ by -= node.scrollTop - before;
4173
+ }
4174
+ }
4175
+
4176
+ // --- Touch: a selection made with a finger ---
4177
+ // A long press and its handles send no mouseup, so the capture-phase mouseup
4178
+ // listener above never sees them. Where the host asks for it (HtmlViewer
4179
+ // touchSelection) and the main pointer is a finger, the selection is read
4180
+ // from selectionchange once it rests, as the Markdown viewer does. A collapsed
4181
+ // selection is not taken: moving focus out of the frame can collapse it, and
4182
+ // a tap on the page already closes the draft through mouseup. What the bridge
4183
+ // reported already (a pin's own selection, a draft that did not change) is
4184
+ // not reported again. The query and the wait are utils/touchSelection.ts's
4185
+ // (this file is vendored whole and imports nothing; a test keeps them equal).
4186
+ var touchSelectionOn = false;
4187
+ var touchSelectionTimer = 0;
4188
+ function isReportedRange(range) {
4189
+ if (!pendingRange) return false;
4190
+ try {
4191
+ return pendingRange.compareBoundaryPoints(Range.START_TO_START, range) === 0
4192
+ && pendingRange.compareBoundaryPoints(Range.END_TO_END, range) === 0;
4193
+ } catch (ex) {
4194
+ return false;
4195
+ }
4196
+ }
4197
+ function takeTouchSelection() {
4198
+ var sel = window.getSelection();
4199
+ if (!sel || sel.isCollapsed || !sel.rangeCount) return;
4200
+ if (isReportedRange(sel.getRangeAt(0))) return;
4201
+ if (!(sel.toString() || '').trim()) return;
4202
+ // New words chosen over a pin: the selection is the draft now.
4203
+ clearMultiTargets();
4204
+ clearPendingPin();
4205
+ handleSelection();
4206
+ }
4207
+ function onTouchSelectionChange() {
4208
+ clearTimeout(touchSelectionTimer);
4209
+ touchSelectionTimer = setTimeout(takeTouchSelection, 400);
4210
+ }
4211
+ function setTouchSelection(active) {
4212
+ var on = active && window.matchMedia('(pointer: coarse)').matches;
4213
+ if (on === touchSelectionOn) return;
4214
+ touchSelectionOn = on;
4215
+ clearTimeout(touchSelectionTimer);
4216
+ if (on) document.addEventListener('selectionchange', onTouchSelectionChange);
4217
+ else document.removeEventListener('selectionchange', onTouchSelectionChange);
4218
+ }
4219
+
4128
4220
  // --- Touch: a tap on a link follows it, press-and-hold pins it ---
4129
4221
  // The owner's amendment to html-link-click (2026-10-20): on a phone there is
4130
4222
  // no Cmd/Ctrl, so while armed a TAP on a link follows it (the click handlers
@@ -431,8 +431,8 @@ export interface PlannotatorConfig {
431
431
  * announcement (`POST /api/inbox/settings` `{ voice }`).
432
432
  */
433
433
  inboxVoice?: { on?: boolean; phonesOnly?: boolean; engine?: "codex" | "api" };
434
- /** The voice announcement: the list intro's rule (`dueAt`, or `shownAt` on a fresh install), claimed with `POST /api/inbox/voice/intro`. */
435
- inboxVoiceIntro?: { dueAt?: string; shownAt?: string };
434
+ /** The voice announcement: the list intro's rule (`dueAt`, or `shownAt` on a fresh install), claimed with `POST /api/inbox/voice/intro`. `skippedAt`: the check found no ChatGPT login, so it never asks again. */
435
+ inboxVoiceIntro?: { dueAt?: string; shownAt?: string; skippedAt?: string };
436
436
  /**
437
437
  * The Board (approval "inbox-board", 2026-10-26 amendment): something the
438
438
  * person turns on, off by default. On: the sidebar's Board row and page,
@@ -6,10 +6,10 @@
6
6
  import type { GuideViewerAssets } from "./guide-format.ts";
7
7
 
8
8
  export const GUIDE_VIEWER_MANIFEST: Omit<GuideViewerAssets, "baseUrl"> = {
9
- js: "viewer.D8s5nXA_.js",
10
- css: "viewer.CfyK-nt_.css",
11
- jsIntegrity: "sha384-ttJm9w/o3SyLUMGneTh8OaaifLy3MT7HZB9/kBHqJ0+45DYhxzmOuWUBAYhNK3vm",
12
- cssIntegrity: "sha384-Egde7gEF8IuWkM4XoUBzRwFHrsOZewlgRG9X1g2ZKsiL0y89Iy/NvYtlfPpadq8f",
9
+ js: "viewer.BfflE3Ru.js",
10
+ css: "viewer.b_pZ_mPX.css",
11
+ jsIntegrity: "sha384-1EH/kDTH8mGiMlSMNNZVhSX+/faRqfHb+0y/rlCoFbHG5hoxc3xf5njsncjsu95X",
12
+ cssIntegrity: "sha384-7tITytAS3P1UFEPudEv1zIuOiOvjfnyNmjJj+HGZZUqJUfCBi2PIIX5rNL5WlC9M",
13
13
  langs: {
14
14
  "astro": "chunks/astro.Ts5EKq2l.js",
15
15
  "c": "chunks/c.BIGW1oBm.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plannotator/pi-extension",
3
- "version": "0.28.45",
3
+ "version": "0.28.46",
4
4
  "type": "module",
5
5
  "description": "Plannotator Pi extension - interactive plan review with annotations, annotate agent messages, and review code/PRs",
6
6
  "author": "backnotprop",