@pixelmatters/markup 1.21.0 → 1.23.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.21.0",
3
+ "version": "1.23.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",
@@ -47,7 +47,6 @@
47
47
  "provenance": true
48
48
  },
49
49
  "devDependencies": {
50
- "@medv/finder": "^4.0.2",
51
50
  "@playwright/test": "^1.62.1",
52
51
  "@preact/preset-vite": "^2.10.6",
53
52
  "@tanstack/intent": "0.3.6",
@@ -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.21.0'
10
+ library_version: '1.23.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.21.0'
29
+ import { init } from 'https://esm.sh/@pixelmatters/markup@1.23.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.21.0"
39
+ src="https://esm.sh/@pixelmatters/markup@1.23.0"
40
40
  data-markup-widget="true"
41
41
  data-api-url="…"
42
42
  data-api-key="…"
@@ -55,7 +55,7 @@ There is no framework-specific entrypoint: no `@pixelmatters/markup/react`, no p
55
55
  import { init, destroy } from '@pixelmatters/markup'
56
56
  ```
57
57
 
58
- `init` returns the matching `destroy`. Mount **once at the app root**, not per route. The widget patches `history.pushState` / `replaceState` and listens for `popstate` itself, so SPAs work without remounting. Calling `init` repeatedly is safe (the history wrapper is module-scoped, so init/destroy cycles don't stack), but it tears down the previous mount each time, so a render loop calling `init` will thrash. Always anchor it to a one-time mount lifecycle.
58
+ `init` returns the matching `destroy`. Mount **once at the app root**, not per route. The widget patches `history.pushState` / `replaceState` and listens for `popstate` and `hashchange` itself, so SPAs work without remounting, hash routers included. Calling `init` repeatedly is safe (the history wrapper is module-scoped, so init/destroy cycles don't stack), but it tears down the previous mount each time, so a render loop calling `init` will thrash. Always anchor it to a one-time mount lifecycle.
59
59
 
60
60
  If `apiUrl` or `apiKey` is missing, `init` logs a `[markup]` console warning and returns a no-op `destroy` rather than mounting, which is worth knowing when debugging "nothing showed up."
61
61
 
@@ -178,24 +178,24 @@ API key belongs to a different project than the one being watched.
178
178
 
179
179
  Walk these in order when the widget is misbehaving:
180
180
 
181
- | Symptom | Cause | Fix |
182
- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
183
- | Toolbar doesn't appear, console warns `[markup] init() requires both apiUrl and apiKey` | One of the env vars is unset / undefined at the call site | Confirm the framework's env-var prefix (`VITE_…`, `NEXT_PUBLIC_…`, etc.) and that `.env` is being loaded |
184
- | Toolbar doesn't appear, console shows `403 Origin not allowed` | Host is not in `allowedDomains` | Dashboard → Settings → Domains, add the exact host (`app.example.com`) or a wildcard (`*.example.com` matches any subdomain depth) |
185
- | Toolbar renders unstyled, or threads never load | CSP is missing `style-src 'unsafe-inline'` (the stylesheet is a `<style>` element in the shadow root) or the deployment origins under `connect-src` | See the CSP bullet under **Don'ts** |
186
- | Comments show initials where profile pictures are expected | The host's CSP `img-src` blocks the picture host, or the account has none set | Add the picture host to `img-src`. Initials are the intended fallback, not a bug |
187
- | `@` types a literal character, no picker | The member fetch failed (a `403`/`429` on `/widget/members` degrades silently by design) | Check the network tab for `/widget/members`; the usual cause is the domain allowlist |
188
- | `401 Invalid api key` | Key revoked, wrong project, or copy-paste truncation | Mint a fresh key and replace the env var |
189
- | `429` + `Retry-After` header | Rate-limited (per-key pre-auth or per-project bucket) | Stop polling; respect the `Retry-After` seconds. If hit during normal use, ask the user to contact support to raise the project limit |
190
- | `400 Invalid anchor` / `Invalid viewport` / `Field too long` | Widget POSTed a coord outside `[0..1]`, a non-finite viewport size, or an over-long string (route > 256, url > 2048, userAgent > 500, anchorSelector > 1024, authorEmail > 320) | The bundled widget always stays inside these limits, so this only fires if something between the widget and the API rewrote the payload. Check for a misbehaving service worker or proxy |
191
- | Pins drift after host layout changes | Selector resolution failed; falling back to viewport fractions | Expected. Use stable IDs / data-attributes on anchor targets if precision matters |
192
- | Pins disappear after route change in an SPA | `init` was called per-route and remounted state | Move `init` to a single root-level mount; the widget handles history itself |
193
- | Widget styling looks broken inside an iframe | Shadow DOM doesn't pierce frame boundaries | Mount the widget **inside** the iframe document, not the parent |
194
- | 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 |
195
- | 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 |
196
- | HMR leaves duplicate toolbars in dev | Hot reload re-runs `init` without cleanup | Return the `destroy` from `useEffect` / `onMounted` so HMR can call it. (The history wrapper itself is module-scoped, so repeated init/destroy cycles no longer leak listeners.) |
197
- | 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 |
198
- | 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 |
181
+ | Symptom | Cause | Fix |
182
+ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
183
+ | Toolbar doesn't appear, console warns `[markup] init() requires both apiUrl and apiKey` | One of the env vars is unset / undefined at the call site | Confirm the framework's env-var prefix (`VITE_…`, `NEXT_PUBLIC_…`, etc.) and that `.env` is being loaded |
184
+ | Toolbar doesn't appear, console shows `403 Origin not allowed` | Host is not in `allowedDomains` | Dashboard → Settings → Domains, add the exact host (`app.example.com`) or a wildcard (`*.example.com` matches any subdomain depth) |
185
+ | Toolbar renders unstyled, or threads never load | CSP is missing `style-src 'unsafe-inline'` (the stylesheet is a `<style>` element in the shadow root) or the deployment origins under `connect-src` | See the CSP bullet under **Don'ts** |
186
+ | Comments show initials where profile pictures are expected | The host's CSP `img-src` blocks the picture host, or the account has none set | Add the picture host to `img-src`. Initials are the intended fallback, not a bug |
187
+ | `@` types a literal character, no picker | The member fetch failed (a `403`/`429` on `/widget/members` degrades silently by design) | Check the network tab for `/widget/members`; the usual cause is the domain allowlist |
188
+ | `401 Invalid api key` | Key revoked, wrong project, or copy-paste truncation | Mint a fresh key and replace the env var |
189
+ | `429` + `Retry-After` header | Rate-limited (per-key pre-auth or per-project bucket) | Stop polling; respect the `Retry-After` seconds. If hit during normal use, ask the user to contact support to raise the project limit |
190
+ | `400 Invalid anchor` / `Invalid viewport` / `Field too long` | Widget POSTed a coord outside `[0..1]`, a non-finite viewport size, or an over-long string (route > 256, url > 2048, userAgent > 500, anchorSelector > 1024, anchorText > 256, authorEmail > 320) | The bundled widget always stays inside these limits, so this only fires if something between the widget and the API rewrote the payload. Check for a misbehaving service worker or proxy |
191
+ | Pins drift after host layout changes | Selector resolution failed; falling back to viewport fractions | Expected. Use stable IDs / data-attributes on anchor targets if precision matters |
192
+ | Pins disappear after route change in an SPA | `init` was called per-route and remounted state | Move `init` to a single root-level mount; the widget handles history itself |
193
+ | Widget styling looks broken inside an iframe | Shadow DOM doesn't pierce frame boundaries | Mount the widget **inside** the iframe document, not the parent |
194
+ | 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 |
195
+ | 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 |
196
+ | HMR leaves duplicate toolbars in dev | Hot reload re-runs `init` without cleanup | Return the `destroy` from `useEffect` / `onMounted` so HMR can call it. (The history wrapper itself is module-scoped, so repeated init/destroy cycles no longer leak listeners.) |
197
+ | 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 |
198
+ | 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 |
199
199
 
200
200
  ## Don'ts
201
201
 
@@ -203,7 +203,7 @@ Walk these in order when the widget is misbehaving:
203
203
  - **Don't wrap in a provider component.** `init` is the whole public API.
204
204
  - **Don't call `init` inside a route component.** Mount at the app root once.
205
205
  - **Don't hardcode the API key.** Use env vars so rotations don't require a code change.
206
- - **Don't ship CDN URLs without a version pin.** `@pixelmatters/markup@1.21.0`, not `@pixelmatters/markup`.
206
+ - **Don't ship CDN URLs without a version pin.** `@pixelmatters/markup@1.23.0`, not `@pixelmatters/markup`.
207
207
  - **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.
208
208
 
209
209
  ## Versioning & releases