helldots 0.7.0 → 0.8.0

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.
package/README.md CHANGED
@@ -186,6 +186,95 @@ Three ways out, cheapest first:
186
186
  - **Leave it.** Captures of such a page stay misaligned where text is
187
187
  concerned; everything else about them is correct.
188
188
 
189
+ ### When a capture is slow
190
+
191
+ A screenshot is not a screenshot. The browser does not hand that to
192
+ JavaScript, so the renderer re-creates the page instead: it clones the DOM,
193
+ reads every element's computed style, inlines all of it, serialises the
194
+ result into an SVG `<foreignObject>` and rasterises that.
195
+
196
+ Reading the styles is the render. A browser exposes around 527 computed
197
+ properties per element, and the renderer reads all of them — on a page with
198
+ a few thousand live nodes that is hundreds of thousands of property reads,
199
+ and it is ~91% of the cost. Two things follow, in this order:
200
+
201
+ - **Nothing waits for it.** Clicking or dragging places the marker and opens
202
+ the comment box immediately; the render runs behind it and the images drop
203
+ in when they land. A dragged region shows a "Capturing…" slot in the
204
+ attachment strip until its crop arrives, and Send waits for it if you get
205
+ there first. This is always on; there is nothing to configure.
206
+ - **The page no longer freezes while it happens.** The capture hands the
207
+ main thread back to the browser every 8 ms, so the page keeps painting and
208
+ accepting keystrokes even on a render that runs for a second. Also always
209
+ on — and it is what makes the point above worth anything, since a box you
210
+ can see but cannot type into is not much better than no box.
211
+ - **`fastCapture: true`** narrows those reads to a curated list of the
212
+ properties that change a pixel — measured at ~2.7x off that phase on a
213
+ 12 000-element page, and pixel-identical to a full capture on the pages it
214
+ was verified against.
215
+
216
+ `fastCapture` is off by default because the list is a fidelity contract, and
217
+ no list is provably complete for a page this library has never seen: a
218
+ property it does not name is simply absent from the image. Turn it on for a
219
+ heavy page, look at one capture before trusting it, and open an issue if
220
+ something comes out wrong — the fix is one more entry in the list.
221
+
222
+ There is a third lever if your page embeds same-origin iframes.
223
+ **`skipIframeContent: true`** renders them blank instead of cloning what is
224
+ inside. An iframe's cost is invisible from the outside — the renderer walks
225
+ into the frame and clones its whole document, so a page that reports 242
226
+ elements can be a capture of 9 245. Measured at 2374 ms against 82 ms on one
227
+ 9 000-node embedded frame.
228
+
229
+ The `<iframe>` element itself is kept: its box, its border, the space it
230
+ occupies. That matters more than it sounds — removing the element instead
231
+ would slide everything below it up by the frame's height while the crop is
232
+ still taken at live page coordinates, which puts the bottom of every capture
233
+ out of register.
234
+
235
+ A cross-origin frame has nothing to gain here. The renderer cannot read into
236
+ it, so it is already blank in the output, and contrary to a common guess it
237
+ does not stall or wait on one either.
238
+
239
+ What none of them touches is rasterisation, which is a single browser
240
+ operation with no JavaScript inside it. On a very long page that stays a
241
+ few hundred milliseconds of unavoidable work.
242
+
243
+ ### When one dead asset holds a capture up
244
+
245
+ To inline the page's images and fonts the renderer re-fetches them, and gives
246
+ each one 30 seconds before giving up. A URL that never answers stalls the
247
+ capture until that fires. The capture still succeeds — that asset becomes a
248
+ transparent placeholder — but it waits first.
249
+
250
+ The wait is bounded rather than multiplied: one dead asset and ten cost the
251
+ same, because they are waited on concurrently. What it is not is one times the
252
+ timeout. The setting drives two waits in sequence on the same asset — first
253
+ for the image already on the page to finish loading, then for the fetch that
254
+ inlines it — so the real cost is a consistent ~2x. The 30 second default is
255
+ therefore about a minute.
256
+
257
+ **`captureTimeout: 5000`** cuts that to roughly ten seconds. It is left at
258
+ the default because lowering it trades a slow capture for a silently
259
+ incomplete one: an asset that was only slow, rather than dead, gets dropped
260
+ and leaves a hole with nothing to say so. Since these are assets your page
261
+ has already loaded, most come from cache instantly and the tail is exactly
262
+ the large or uncached ones you would be wrong to drop. Set it if you have
263
+ measured your own page and decided which way you would rather it failed.
264
+
265
+ ### Very long pages
266
+
267
+ Browsers cap how large a canvas can be — 65 535 pixels in a dimension in
268
+ Chromium, less in Firefox, and a separate and much lower area cap on mobile
269
+ Safari. A page past that cap cannot be rendered at full scale, so HellDots
270
+ fits the scale to what the browser will actually paint and checks that the
271
+ result holds pixels before using it.
272
+
273
+ Nothing below the cap changes. Past it the capture goes soft in proportion:
274
+ a 68 000px page renders at 0.96, a 140 000px page at 0.47. If even the
275
+ smallest attempt comes back empty, the capture fails through `onError`
276
+ rather than attaching a blank image.
277
+
189
278
  ### Keeping screenshots out of your database
190
279
 
191
280
  Every image is stored as a base64 data URL inside the record — around 33 KB
@@ -475,6 +564,9 @@ OS: iOS 17.2
475
564
  | `persistence` | `"localStorage"` \| `"none"` | `"none"` | Auto save/restore, or handle it yourself via callbacks |
476
565
  | `autoScreenshot` | `boolean` | `true` | Capture a screenshot and environment snapshot per comment |
477
566
  | `embedCrossOriginFonts` | `boolean` | `false` | Fetch unreadable stylesheets so their web fonts reach the capture |
567
+ | `fastCapture` | `boolean` | `false` | Read a curated style list instead of all ~527 computed properties |
568
+ | `skipIframeContent` | `boolean` | `false` | Render embedded documents as blank instead of cloning them |
569
+ | `captureTimeout` | `number` | `30000` | Milliseconds one remote asset may hold a capture up |
478
570
  | `locale` | `string` | browser language | `"en"` and `"es"` ship; anything else falls back per key |
479
571
  | `linkParam` | `string` | `"helldotsComment"` | Query param used by "Copy link" URLs |
480
572
  | `navigate` | `(page: string) => void` | full page load | SPA router hook for the widget's cross-page jumps |