annotation-kit 0.0.0-stage → 0.1.1

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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +75 -0
  3. package/README.md +114 -2
  4. package/README.zh-CN.md +112 -0
  5. package/dist/annotation-kit.css +157 -0
  6. package/dist/chunks/references-ukB-tSle.js +1126 -0
  7. package/dist/chunks/references-ukB-tSle.js.map +1 -0
  8. package/dist/electron/element-context.cjs +72 -0
  9. package/dist/electron/guest-preload.cjs +327 -0
  10. package/dist/electron.js +394 -0
  11. package/dist/electron.js.map +1 -0
  12. package/dist/index.js +910 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/react.js +2282 -0
  15. package/dist/react.js.map +1 -0
  16. package/dist/styles.d.ts +2 -0
  17. package/dist/types/address.d.ts +53 -0
  18. package/dist/types/address.d.ts.map +1 -0
  19. package/dist/types/client/Workbench.d.ts +11 -0
  20. package/dist/types/client/Workbench.d.ts.map +1 -0
  21. package/dist/types/client/composer/PacketPreview.d.ts +12 -0
  22. package/dist/types/client/composer/PacketPreview.d.ts.map +1 -0
  23. package/dist/types/client/composer/ReferenceCard.d.ts +12 -0
  24. package/dist/types/client/composer/ReferenceCard.d.ts.map +1 -0
  25. package/dist/types/client/composer/attachments.d.ts +10 -0
  26. package/dist/types/client/composer/attachments.d.ts.map +1 -0
  27. package/dist/types/client/composer/commit.d.ts +4 -0
  28. package/dist/types/client/composer/commit.d.ts.map +1 -0
  29. package/dist/types/client/composer/packets.d.ts +53 -0
  30. package/dist/types/client/composer/packets.d.ts.map +1 -0
  31. package/dist/types/client/composer/reference-presentation.d.ts +4 -0
  32. package/dist/types/client/composer/reference-presentation.d.ts.map +1 -0
  33. package/dist/types/client/error-copy.d.ts +69 -0
  34. package/dist/types/client/error-copy.d.ts.map +1 -0
  35. package/dist/types/client/errors.d.ts +10 -0
  36. package/dist/types/client/errors.d.ts.map +1 -0
  37. package/dist/types/client/files/FileSelectionAction.d.ts +7 -0
  38. package/dist/types/client/files/FileSelectionAction.d.ts.map +1 -0
  39. package/dist/types/client/files/SelectionCard.d.ts +14 -0
  40. package/dist/types/client/files/SelectionCard.d.ts.map +1 -0
  41. package/dist/types/client/files/document-workflow.d.ts +34 -0
  42. package/dist/types/client/files/document-workflow.d.ts.map +1 -0
  43. package/dist/types/client/files/pages/DocumentChrome.d.ts +30 -0
  44. package/dist/types/client/files/pages/DocumentChrome.d.ts.map +1 -0
  45. package/dist/types/client/files/pages/PageSelectionAction.d.ts +7 -0
  46. package/dist/types/client/files/pages/PageSelectionAction.d.ts.map +1 -0
  47. package/dist/types/client/files/pages/batches.d.ts +21 -0
  48. package/dist/types/client/files/pages/batches.d.ts.map +1 -0
  49. package/dist/types/client/files/pages/geometry.d.ts +16 -0
  50. package/dist/types/client/files/pages/geometry.d.ts.map +1 -0
  51. package/dist/types/client/files/pages/image-store.d.ts +8 -0
  52. package/dist/types/client/files/pages/image-store.d.ts.map +1 -0
  53. package/dist/types/client/files/pages/images.d.ts +41 -0
  54. package/dist/types/client/files/pages/images.d.ts.map +1 -0
  55. package/dist/types/client/files/pages/interaction.d.ts +55 -0
  56. package/dist/types/client/files/pages/interaction.d.ts.map +1 -0
  57. package/dist/types/client/files/pages/selection.d.ts +25 -0
  58. package/dist/types/client/files/pages/selection.d.ts.map +1 -0
  59. package/dist/types/client/files/pages/snapshot.d.ts +31 -0
  60. package/dist/types/client/files/pages/snapshot.d.ts.map +1 -0
  61. package/dist/types/client/files/references.d.ts +80 -0
  62. package/dist/types/client/files/references.d.ts.map +1 -0
  63. package/dist/types/client/files/selection.d.ts +11 -0
  64. package/dist/types/client/files/selection.d.ts.map +1 -0
  65. package/dist/types/client/files/sheets/SheetRecovery.d.ts +12 -0
  66. package/dist/types/client/files/sheets/SheetRecovery.d.ts.map +1 -0
  67. package/dist/types/client/files/sheets/SheetSurface.d.ts +27 -0
  68. package/dist/types/client/files/sheets/SheetSurface.d.ts.map +1 -0
  69. package/dist/types/client/files/sheets/geometry.d.ts +30 -0
  70. package/dist/types/client/files/sheets/geometry.d.ts.map +1 -0
  71. package/dist/types/client/files/sheets/navigation.d.ts +4 -0
  72. package/dist/types/client/files/sheets/navigation.d.ts.map +1 -0
  73. package/dist/types/client/files/sheets/snapshot.d.ts +45 -0
  74. package/dist/types/client/files/sheets/snapshot.d.ts.map +1 -0
  75. package/dist/types/client/locales.d.ts +148 -0
  76. package/dist/types/client/locales.d.ts.map +1 -0
  77. package/dist/types/client/messages/SentAnnotations.d.ts +18 -0
  78. package/dist/types/client/messages/SentAnnotations.d.ts.map +1 -0
  79. package/dist/types/client/messages/presentation.d.ts +39 -0
  80. package/dist/types/client/messages/presentation.d.ts.map +1 -0
  81. package/dist/types/client/model.d.ts +68 -0
  82. package/dist/types/client/model.d.ts.map +1 -0
  83. package/dist/types/client/quoted-context.d.ts +3 -0
  84. package/dist/types/client/quoted-context.d.ts.map +1 -0
  85. package/dist/types/client/responses/ResponseCard.d.ts +12 -0
  86. package/dist/types/client/responses/ResponseCard.d.ts.map +1 -0
  87. package/dist/types/client/responses/ResponseOverlay.d.ts +7 -0
  88. package/dist/types/client/responses/ResponseOverlay.d.ts.map +1 -0
  89. package/dist/types/client/responses/composer.d.ts +11 -0
  90. package/dist/types/client/responses/composer.d.ts.map +1 -0
  91. package/dist/types/client/responses/controller.d.ts +39 -0
  92. package/dist/types/client/responses/controller.d.ts.map +1 -0
  93. package/dist/types/client/responses/references.d.ts +62 -0
  94. package/dist/types/client/responses/references.d.ts.map +1 -0
  95. package/dist/types/client/responses/selection.d.ts +43 -0
  96. package/dist/types/client/responses/selection.d.ts.map +1 -0
  97. package/dist/types/electron/bridge.d.mts +45 -0
  98. package/dist/types/electron/bridge.d.mts.map +1 -0
  99. package/dist/types/electron/build-preload.d.mts +4 -0
  100. package/dist/types/electron/build-preload.d.mts.map +1 -0
  101. package/dist/types/electron/element-context.d.cts +7 -0
  102. package/dist/types/electron/element-context.d.cts.map +1 -0
  103. package/dist/types/electron/guest-preload.d.cts +2 -0
  104. package/dist/types/electron/guest-preload.d.cts.map +1 -0
  105. package/dist/types/electron/index.d.ts +5 -0
  106. package/dist/types/electron/index.d.ts.map +1 -0
  107. package/dist/types/host-contracts.d.ts +104 -0
  108. package/dist/types/host-contracts.d.ts.map +1 -0
  109. package/dist/types/index.d.ts +32 -0
  110. package/dist/types/index.d.ts.map +1 -0
  111. package/dist/types/react.d.ts +14 -0
  112. package/dist/types/react.d.ts.map +1 -0
  113. package/dist/types/shared/desktop.d.ts +75 -0
  114. package/dist/types/shared/desktop.d.ts.map +1 -0
  115. package/dist/types/shared/surface.d.ts +11 -0
  116. package/dist/types/shared/surface.d.ts.map +1 -0
  117. package/dist/types/ui/icons.d.ts +11 -0
  118. package/dist/types/ui/icons.d.ts.map +1 -0
  119. package/dist/types/ui/regions.d.ts +28 -0
  120. package/dist/types/ui/regions.d.ts.map +1 -0
  121. package/package.json +83 -3
@@ -0,0 +1,327 @@
1
+ /* Sandboxed webview preload. Only the owning Desktop main process can command it. */
2
+ 'use strict'
3
+
4
+ const { ipcRenderer } = require('electron')
5
+ const COMMAND = 'aione-desktop:annotation-guest-command'
6
+ const SELECTION = 'aione-desktop:annotation-guest-selection'
7
+ const PAINTED = 'aione-desktop:annotation-guest-painted'
8
+ const reactGrabPrimitives = /* AIONE_REACT_GRAB_PRIMITIVES */ null
9
+ const { elementContext } = /* AIONE_ELEMENT_CONTEXT */ null
10
+
11
+ let mode = 'off'
12
+ let epoch = ''
13
+ let host
14
+ let layer
15
+ let shield
16
+ let dragStart
17
+ let preview
18
+ let currentTarget
19
+ let currentSelection
20
+ let dragCompleted = false
21
+ let frameStyle
22
+ const markers = new Map()
23
+
24
+ function rectOf(rect) {
25
+ const x = Math.max(0, rect.x)
26
+ const y = Math.max(0, rect.y)
27
+ const right = Math.min(window.innerWidth, rect.x + rect.width)
28
+ const bottom = Math.min(window.innerHeight, rect.y + rect.height)
29
+ return { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) }
30
+ }
31
+
32
+ function cssSelector(element) {
33
+ const parts = []
34
+ let node = element
35
+ while (node && node.nodeType === Node.ELEMENT_NODE && parts.length < 12) {
36
+ const tag = node.tagName.toLowerCase()
37
+ if (node.id) {
38
+ parts.unshift(`${tag}#${CSS.escape(node.id)}`)
39
+ const root = node.getRootNode()
40
+ if (root instanceof ShadowRoot) {
41
+ parts.unshift('>>>')
42
+ node = root.host
43
+ continue
44
+ }
45
+ break
46
+ }
47
+ let index = 1
48
+ for (let previous = node.previousElementSibling; previous; previous = previous.previousElementSibling) {
49
+ if (previous.tagName === node.tagName) index++
50
+ }
51
+ parts.unshift(`${tag}:nth-of-type(${index})`)
52
+ const root = node.getRootNode()
53
+ if (node.parentElement) node = node.parentElement
54
+ else if (root instanceof ShadowRoot) {
55
+ parts.unshift('>>>')
56
+ node = root.host
57
+ } else break
58
+ }
59
+ return parts.join(' > ').replaceAll(' > >>> > ', ' >>> ').slice(0, 2048)
60
+ }
61
+
62
+ function elementAt(x, y) {
63
+ shield.style.pointerEvents = 'none'
64
+ if (frameStyle?.sheet) frameStyle.sheet.disabled = true
65
+ let element = document.elementFromPoint(x, y)
66
+ try {
67
+ const grabbed = reactGrabPrimitives?.getElementAtPoint(x, y)
68
+ if (grabbed && grabbed.ownerDocument === document && grabbed !== host) element = grabbed
69
+ } catch { /* Sites with unusual DOMs still use native hit testing. */ }
70
+ shield.style.pointerEvents = 'auto'
71
+ if (frameStyle?.sheet) frameStyle.sheet.disabled = false
72
+ let offsetX = 0
73
+ let offsetY = 0
74
+ const frames = []
75
+ for (let depth = 0; depth < 8 && element; depth++) {
76
+ if (element.shadowRoot) {
77
+ const nested = element.shadowRoot.elementFromPoint(x - offsetX, y - offsetY)
78
+ if (nested && nested !== element) { element = nested; continue }
79
+ }
80
+ if (element.tagName === 'IFRAME') {
81
+ let frameDocument
82
+ const frameRect = element.getBoundingClientRect()
83
+ const transformed = getComputedStyle(element).transform !== 'none'
84
+ || Math.abs(frameRect.width - element.offsetWidth) > 1
85
+ || Math.abs(frameRect.height - element.offsetHeight) > 1
86
+ try { frameDocument = element.contentDocument } catch { /* Cross-origin frame: select its region. */ }
87
+ if (frameDocument && !transformed) {
88
+ const nested = frameDocument.elementFromPoint(
89
+ x - offsetX - frameRect.x - element.clientLeft,
90
+ y - offsetY - frameRect.y - element.clientTop)
91
+ if (nested) {
92
+ frames.push(cssSelector(element))
93
+ offsetX += frameRect.x + element.clientLeft
94
+ offsetY += frameRect.y + element.clientTop
95
+ element = nested
96
+ continue
97
+ }
98
+ }
99
+ }
100
+ break
101
+ }
102
+ if (!element || element === host || element === document.documentElement) return undefined
103
+ const bounds = element.getBoundingClientRect()
104
+ const rect = rectOf({ x: bounds.x + offsetX, y: bounds.y + offsetY, width: bounds.width, height: bounds.height })
105
+ if (rect.width < 1 || rect.height < 1) return undefined
106
+ let selectedSelector = cssSelector(element)
107
+ try {
108
+ if (!(element.getRootNode() instanceof ShadowRoot)) {
109
+ selectedSelector = reactGrabPrimitives?.getElementSelector(element) || selectedSelector
110
+ }
111
+ } catch { /* Native selector remains usable. */ }
112
+ return { element, selector: [...frames, selectedSelector].join(' >> ').slice(0, 2048), rect,
113
+ frame: frames.length ? frames.join(' >> ') : 'top document' }
114
+ }
115
+
116
+ function selection(kind, rect, details = {}) {
117
+ return {
118
+ kind, rect, viewport: { width: window.innerWidth, height: window.innerHeight },
119
+ scroll: { x: window.scrollX, y: window.scrollY },
120
+ url: location.href, title: document.title.slice(0, 2048), ...details,
121
+ }
122
+ }
123
+
124
+ function emitSelection(value) {
125
+ currentSelection = value
126
+ ipcRenderer.send(SELECTION, { epoch, selection: value })
127
+ }
128
+
129
+ function drawBox(box, number) {
130
+ const outline = document.createElement('div')
131
+ outline.style.cssText = 'position:absolute;box-sizing:border-box;border:2px solid #1677ff;background:rgba(22,119,255,.07);pointer-events:none;'
132
+ outline.style.left = `${box.x}px`
133
+ outline.style.top = `${box.y}px`
134
+ outline.style.width = `${box.width}px`
135
+ outline.style.height = `${box.height}px`
136
+ if (number !== undefined) {
137
+ const badge = document.createElement('span')
138
+ badge.textContent = String(number)
139
+ badge.style.cssText = 'position:absolute;left:0;top:0;padding:1px 6px;border-radius:0 0 4px 0;background:#1677ff;color:#fff;font:700 12px/18px system-ui;'
140
+ outline.append(badge)
141
+ }
142
+ layer.append(outline)
143
+ return outline
144
+ }
145
+
146
+ function showPreview(rect) {
147
+ preview?.remove()
148
+ preview = rect && rect.width > 0 && rect.height > 0 ? drawBox(rect) : undefined
149
+ }
150
+
151
+ function targetRect(element) {
152
+ if (!element?.isConnected) return undefined
153
+ const bounds = element.getBoundingClientRect()
154
+ let x = bounds.x
155
+ let y = bounds.y
156
+ let frame = element.ownerDocument.defaultView?.frameElement
157
+ for (let depth = 0; frame && depth < 8; depth++) {
158
+ const parent = frame.getBoundingClientRect()
159
+ if (getComputedStyle(frame).transform !== 'none'
160
+ || Math.abs(parent.width - frame.offsetWidth) > 1
161
+ || Math.abs(parent.height - frame.offsetHeight) > 1) return undefined
162
+ x += parent.x + frame.clientLeft
163
+ y += parent.y + frame.clientTop
164
+ frame = frame.ownerDocument.defaultView?.frameElement
165
+ }
166
+ return rectOf({ x, y, width: bounds.width, height: bounds.height })
167
+ }
168
+
169
+ function stillCurrent(request) {
170
+ const selected = request.selection
171
+ if (!currentSelection || JSON.stringify(selected) !== JSON.stringify(currentSelection)
172
+ || selected.url !== location.href
173
+ || selected.viewport.width !== window.innerWidth || selected.viewport.height !== window.innerHeight
174
+ || selected.scroll.x !== window.scrollX || selected.scroll.y !== window.scrollY) return false
175
+ if (selected.kind === 'region' && !currentTarget) return true
176
+ const now = targetRect(currentTarget)
177
+ if (selected.element && JSON.stringify(elementContext(currentTarget, selected.element.frame)) !== JSON.stringify(selected.element)) return false
178
+ return now && ['x', 'y', 'width', 'height'].every(key => Math.abs(now[key] - selected.rect[key]) <= 1)
179
+ }
180
+
181
+ function ensureLayer() {
182
+ if (host?.isConnected) return
183
+ host = document.createElement('div')
184
+ host.style.cssText = 'all:initial;position:fixed;inset:0;z-index:2147483647;pointer-events:none;contain:layout style paint;'
185
+ const root = host.attachShadow({ mode: 'closed' })
186
+ layer = document.createElement('div')
187
+ layer.style.cssText = 'position:absolute;inset:0;pointer-events:none;'
188
+ shield = document.createElement('div')
189
+ shield.style.cssText = 'position:absolute;inset:0;pointer-events:none;cursor:crosshair;touch-action:none;'
190
+ root.append(layer, shield)
191
+ document.documentElement.append(host)
192
+ const clearOnViewportChange = () => {
193
+ markers.clear()
194
+ layer.replaceChildren()
195
+ preview = undefined
196
+ dragStart = undefined
197
+ currentTarget = undefined
198
+ currentSelection = undefined
199
+ if (mode !== 'off') ipcRenderer.send(SELECTION, { epoch, selection: null })
200
+ }
201
+ window.addEventListener('scroll', clearOnViewportChange, true)
202
+ let viewportWidth = window.innerWidth, viewportHeight = window.innerHeight
203
+ window.addEventListener('resize', () => {
204
+ // Electron capturePage can emit resize without changing the viewport.
205
+ // Invalidating that notification would cancel the capture it belongs to.
206
+ if (viewportWidth === window.innerWidth && viewportHeight === window.innerHeight) return
207
+ viewportWidth = window.innerWidth
208
+ viewportHeight = window.innerHeight
209
+ clearOnViewportChange()
210
+ })
211
+ window.addEventListener('keydown', event => {
212
+ if (mode === 'off' || !event.isTrusted || event.isComposing || event.key !== 'Escape') return
213
+ event.preventDefault()
214
+ event.stopImmediatePropagation()
215
+ ipcRenderer.send(SELECTION, { epoch, escape: true })
216
+ }, true)
217
+ shield.addEventListener('pointermove', event => {
218
+ if (!event.isTrusted) return
219
+ if (mode === 'element' && !dragStart && !currentSelection) showPreview(elementAt(event.clientX, event.clientY)?.rect)
220
+ if (dragStart && (mode === 'region' || Math.hypot(event.clientX - dragStart.x, event.clientY - dragStart.y) > 5)) {
221
+ showPreview(rectOf({ x: Math.min(dragStart.x, event.clientX), y: Math.min(dragStart.y, event.clientY),
222
+ width: Math.abs(event.clientX - dragStart.x), height: Math.abs(event.clientY - dragStart.y) }))
223
+ }
224
+ })
225
+ shield.addEventListener('click', event => {
226
+ if (mode !== 'element' || !event.isTrusted) return
227
+ event.preventDefault()
228
+ event.stopPropagation()
229
+ if (dragCompleted) { dragCompleted = false; return }
230
+ const result = elementAt(event.clientX, event.clientY)
231
+ if (!result) return
232
+ currentTarget = result.element
233
+ showPreview(result.rect)
234
+ const fallback = result.element.tagName === 'IFRAME' || result.element.tagName === 'CANVAS'
235
+ || (result.element.tagName.includes('-') && !result.element.shadowRoot)
236
+ if (fallback) {
237
+ emitSelection(selection('region', result.rect, { anchor: { x: event.clientX, y: event.clientY } }))
238
+ return
239
+ }
240
+ emitSelection(selection('element', result.rect, {
241
+ selector: result.selector,
242
+ text: (result.element.innerText ?? result.element.textContent ?? '').trim().slice(0, 2048),
243
+ element: elementContext(result.element, result.frame),
244
+ anchor: { x: event.clientX, y: event.clientY },
245
+ }))
246
+ }, true)
247
+ shield.addEventListener('pointerdown', event => {
248
+ if (mode === 'off' || !event.isTrusted || event.button !== 0) return
249
+ event.preventDefault()
250
+ event.stopPropagation()
251
+ dragStart = { x: event.clientX, y: event.clientY }
252
+ dragCompleted = false
253
+ shield.setPointerCapture(event.pointerId)
254
+ }, true)
255
+ shield.addEventListener('pointerup', event => {
256
+ if (mode === 'off' || !dragStart || !event.isTrusted) return
257
+ event.preventDefault()
258
+ event.stopPropagation()
259
+ const x = Math.min(dragStart.x, event.clientX)
260
+ const y = Math.min(dragStart.y, event.clientY)
261
+ const rect = rectOf({ x, y, width: Math.abs(event.clientX - dragStart.x), height: Math.abs(event.clientY - dragStart.y) })
262
+ dragStart = undefined
263
+ if (mode === 'element' && (rect.width < 6 || rect.height < 6)) return
264
+ dragCompleted = true
265
+ showPreview(rect)
266
+ currentTarget = undefined
267
+ if (rect.width >= 3 && rect.height >= 3) emitSelection(selection('region', rect, { anchor: { x, y } }))
268
+ }, true)
269
+ shield.addEventListener('pointercancel', () => { dragStart = undefined; showPreview(undefined) })
270
+ }
271
+
272
+ function setFrameInputBlocked(blocked) {
273
+ if (!blocked) { frameStyle?.remove(); frameStyle = undefined; return }
274
+ if (frameStyle?.isConnected) return
275
+ frameStyle = document.createElement('style')
276
+ frameStyle.textContent = 'iframe { pointer-events: none !important; }'
277
+ document.head.append(frameStyle)
278
+ }
279
+
280
+ ipcRenderer.on(COMMAND, (_event, request) => {
281
+ if (!request || typeof request !== 'object') return
282
+ if (request.kind === 'mode' && ['off', 'element', 'region'].includes(request.mode)
283
+ && typeof request.epoch === 'string') {
284
+ if (epoch !== request.epoch) { markers.clear(); layer?.replaceChildren(); preview = undefined; currentTarget = undefined; currentSelection = undefined }
285
+ epoch = request.epoch
286
+ mode = request.mode
287
+ currentTarget = undefined
288
+ currentSelection = undefined
289
+ showPreview(undefined)
290
+ ensureLayer()
291
+ markers.clear()
292
+ layer.replaceChildren()
293
+ shield.style.pointerEvents = mode === 'off' ? 'none' : 'auto'
294
+ setFrameInputBlocked(mode !== 'off')
295
+ if (mode === 'off') { markers.clear(); layer.replaceChildren(); preview = undefined; currentTarget = undefined; currentSelection = undefined }
296
+ return
297
+ }
298
+ if (request.kind === 'verify' && request.epoch === epoch && typeof request.requestId === 'string') {
299
+ ipcRenderer.send(PAINTED, { requestId: request.requestId, epoch,
300
+ ...(!stillCurrent(request) ? { error: 'selection-moved' } : {}) })
301
+ return
302
+ }
303
+ if (request.kind !== 'paint' || request.epoch !== epoch
304
+ || !Number.isSafeInteger(request.number) || request.number < 1 || request.number > 999
305
+ || typeof request.requestId !== 'string' || !request.selection?.rect) return
306
+ ensureLayer()
307
+ if (!stillCurrent(request)) {
308
+ ipcRenderer.send(PAINTED, { requestId: request.requestId, epoch, error: 'selection-moved' })
309
+ return
310
+ }
311
+ showPreview(undefined)
312
+ markers.get(request.number)?.remove()
313
+ markers.set(request.number, drawBox(request.selection.rect, request.number))
314
+ let acknowledged = false
315
+ const acknowledge = () => {
316
+ if (acknowledged) return
317
+ acknowledged = true
318
+ clearTimeout(fallback)
319
+ if (request.epoch !== epoch || !host.isConnected) return
320
+ ipcRenderer.send(PAINTED, { requestId: request.requestId, epoch,
321
+ ...(!stillCurrent(request) ? { error: 'selection-moved' } : {}) })
322
+ }
323
+ // Chromium can suspend both animation frames while the Desktop window is occluded.
324
+ // A delayed timer still lets capturePage force a composite of the new marker.
325
+ const fallback = setTimeout(acknowledge, 100)
326
+ requestAnimationFrame(() => requestAnimationFrame(acknowledge))
327
+ })