@pixelmatters/markup 1.31.7 → 1.32.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelmatters/markup",
3
- "version": "1.31.7",
3
+ "version": "1.32.0",
4
4
  "description": "Embeddable feedback widget for collecting visual bug reports, screenshots, and comments on live web apps.",
5
5
  "keywords": [
6
6
  "annotation",
@@ -64,7 +64,7 @@
64
64
  "@types/react": "^19.3.0",
65
65
  "@types/react-dom": "^19.3.0",
66
66
  "convex": "^1.38.0",
67
- "html-to-image": "^1.11.13",
67
+ "html-to-image": "1.11.13",
68
68
  "preact": "^10.29.8",
69
69
  "radix-ui": "^1.6.7",
70
70
  "react": "^19.3.0",
@@ -7,7 +7,7 @@ sources:
7
7
  - 'Pixelmatters/markup:packages/widget/src/widget.ts'
8
8
  metadata:
9
9
  library: pixelmatters-markup
10
- library_version: '1.31.7'
10
+ library_version: '1.32.0'
11
11
  ---
12
12
 
13
13
  `@pixelmatters/markup` is a Preact widget that runs inside a shadow DOM and talks to a hosted Convex backend at `https://<deployment>.convex.site`. The host page calls `init({ apiUrl, apiKey })` once at the app root and gets back a `destroy()` function. Everything else lives inside the widget bundle: pin anchoring, threads, mentions, identity, screenshots, real-time updates.
@@ -26,7 +26,7 @@ For a `<script>`-tag drop-in (no bundler / CMS / page-builder), use the inline E
26
26
 
27
27
  ```html
28
28
  <script type="module">
29
- import { init } from 'https://esm.sh/@pixelmatters/markup@1.31.7'
29
+ import { init } from 'https://esm.sh/@pixelmatters/markup@1.32.0'
30
30
  init({ apiUrl: '…', apiKey: '…' })
31
31
  </script>
32
32
  ```
@@ -36,7 +36,7 @@ If the host disallows inline JS, use the auto-init form. `data-markup-widget="tr
36
36
  ```html
37
37
  <script
38
38
  type="module"
39
- src="https://esm.sh/@pixelmatters/markup@1.31.7"
39
+ src="https://esm.sh/@pixelmatters/markup@1.32.0"
40
40
  data-markup-widget="true"
41
41
  data-api-url="…"
42
42
  data-api-key="…"
@@ -135,7 +135,7 @@ init({
135
135
  screenshots: {
136
136
  enabled: true, // default true; set false to disable screenshot capture
137
137
  strictScrub: false, // mask EVERY input/select/textarea (not just sensitive)
138
- redactSelector: '.private', // extra CSS selector; matched elements always masked
138
+ redactSelector: '.private', // extra CSS selector; masked unless inside data-markup-safe
139
139
  },
140
140
  })
141
141
  ```
@@ -148,15 +148,16 @@ Auto-masked by default (regardless of `strictScrub`):
148
148
 
149
149
  HTML attributes the widget honours on the host page:
150
150
 
151
- - `data-markup-private` and `data-markup-redact` are always masked, on top of auto-detection.
152
- - `data-markup-safe` **exempts** any element carrying it, or any descendant of one, from auto-detection and from `redactSelector` / `strictScrub`. Use it sparingly: on a wrapper it un-protects everything inside.
151
+ - `data-markup-private` and `data-markup-redact` are always masked, on top of auto-detection, even inside a `data-markup-safe` wrapper. The element and everything inside it (text, images, icons, child elements) become one solid black block, so it can go on a whole container. On a `<canvas>`, `<video>` or `<iframe>` it also blacks out any image beside it in the same parent.
152
+ - `data-markup-safe` **exempts** the descendants of the element carrying it from auto-detection and from `redactSelector` / `strictScrub`. The element itself is not exempt, so put it on a wrapper, not on the field. Use it sparingly: it un-protects every field inside except those marked `data-markup-private` / `data-markup-redact`.
153
153
  - `data-markup-skip` removes the element from the screenshot entirely, rather than masking it.
154
+ - All of the above reaches into open shadow roots (web components), nested ones included. A `data-markup-safe` on a component's host exempts its shadow fields. `redactSelector` is matched inside each shadow root separately, so a descendant selector can't cross a shadow boundary. A closed shadow root's own content is never captured; light-DOM children slotted into it are, and are masked as usual.
154
155
 
155
156
  Tell users with strict compliance needs to opt into `strictScrub: true` and pin sensitive areas with `data-markup-private` rather than relying on the default heuristics.
156
157
 
157
158
  End users can also switch capture off for themselves with **Auto-capture screenshots** in the overflow menu. `screenshots.enabled: false` from the host still wins, and that row renders disabled.
158
159
 
159
- Capture degrades rather than failing outright: an image the browser won't hand over (a third-party avatar served without CORS headers is the usual culprit) comes through blank while the rest of the page captures; the raster is capped at 1.5x device pixel ratio and encoded as WebP (JPEG where that isn't supported), then walked down a quality-then-scale ladder until it lands near 400 KB, with the server's 2 MB cap as the hard limit; and if it still can't produce an image the composer says _Screenshot unavailable_ and the comment posts without one.
160
+ Capture degrades rather than failing outright: an image the browser won't hand over (a third-party avatar served without CORS headers is the usual culprit) comes through blank while the rest of the page captures, as does an image that hasn't answered within 3 seconds (a font that slow falls back to the next one in its stack); a capture still unfinished after 30 seconds is abandoned; the raster is capped at 1.5x device pixel ratio and encoded as WebP (JPEG where that isn't supported), then walked down a quality-then-scale ladder until it lands near 400 KB, with the server's 2 MB cap as the hard limit; and if it still can't produce an image the composer says _Screenshot unavailable_ and the comment posts without one.
160
161
 
161
162
  ### Anchors & source paths
162
163
 
@@ -206,7 +207,7 @@ Walk these in order when the widget is misbehaving:
206
207
  | Verified identity doesn't survive reload | Host code clears `localStorage['markup.identity']` on logout | Only clear it on Markup-specific sign-out, not on host logout |
207
208
  | Popup sign-in flashes and closes | Popup origin failed `allowedDomains` check, or the stored JWT was expired and dropped | Add the host to the project's allowlist; the popup re-validates. Expired JWTs are dropped on the next load, so sign in again |
208
209
  | HMR leaves duplicate toolbars in dev | Hot reload re-runs `init` without cleanup | Use `useMarkup` on React, or return the `destroy` from `onMounted` / `onMount` so HMR can call it. (The history wrapper itself is module-scoped, so repeated init/destroy cycles no longer leak listeners.) |
209
- | Screenshot is missing my custom field | Auto-mask matched the input (e.g. `autocomplete="cc-number"`) or your selector | Inspect with the widget devtools panel; add `data-markup-safe` on the wrapper to exempt it from auto-detection |
210
+ | Screenshot is missing my custom field | Auto-mask matched the input (e.g. `autocomplete="cc-number"`) or your selector | Expand the composer's "N fields redacted" chip to see which selectors were masked; add `data-markup-safe` on a wrapper (not the field itself) to exempt it from auto-detection |
210
211
  | Widget invisible behind host UI | Z-index conflict on the shadow host element | The widget mounts `<div id="markup-widget">` at `document.body`; raise its `z-index` from host CSS if you must |
211
212
 
212
213
  ## Don'ts
@@ -215,7 +216,7 @@ Walk these in order when the widget is misbehaving:
215
216
  - **Don't wrap in a provider component.** `init` is the whole public API.
216
217
  - **Don't call `init` inside a route component.** Mount at the app root once.
217
218
  - **Don't hardcode the API key.** Use env vars so rotations don't require a code change.
218
- - **Don't ship CDN URLs without a version pin.** `@pixelmatters/markup@1.31.7`, not `@pixelmatters/markup`.
219
+ - **Don't ship CDN URLs without a version pin.** `@pixelmatters/markup@1.32.0`, not `@pixelmatters/markup`.
219
220
  - **Don't add `https://*.convex.site` to `connect-src` and assume that's the whole CSP story.** The deployment domain (`<your-deployment>.convex.site`) is what the widget hits over HTTP, and it must be listed explicitly. Live thread updates go to `wss://<your-deployment>.convex.cloud`, an origin the widget derives from `apiUrl`, so `connect-src` needs both. `img-src` needs `blob:`, `data:`, and the host serving profile pictures; `style-src` needs `'unsafe-inline'` because the widget appends its stylesheet as a `<style>` element inside its shadow root. Add `https://esm.sh` to `script-src` only if you took the CDN path.
220
221
 
221
222
  ## Versioning & releases