@pixelmatters/markup 1.18.3 → 1.18.5
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 +74 -80
- package/dist/widget.js +16 -16
- package/dist/widget.js.map +1 -1
- package/package.json +2 -2
- package/skills/install-markup-widget/SKILL.md +61 -61
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixelmatters/markup",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.5",
|
|
4
4
|
"description": "Embeddable feedback widget for collecting visual bug reports, screenshots, and comments on live web apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotation",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"tanstack-intent",
|
|
14
14
|
"widget"
|
|
15
15
|
],
|
|
16
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://markup.pixelmatters.dev",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/Pixelmatters/markup/issues"
|
|
19
19
|
},
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: install-markup-widget
|
|
3
|
-
description: Install, configure, and troubleshoot the `@pixelmatters/markup` feedback widget
|
|
3
|
+
description: Install, configure, and troubleshoot the `@pixelmatters/markup` feedback widget, a drop-in script that lets end-users pin threaded comments on any web page. Use this skill whenever the host project depends on `@pixelmatters/markup`, imports `init` / `destroy` from it, references a `markup_…` API key, or the user asks about installing / debugging the Markup feedback widget.
|
|
4
4
|
sources:
|
|
5
5
|
- 'Pixelmatters/markup:packages/widget/README.md'
|
|
6
6
|
- 'Pixelmatters/markup:docs/widget-guide.md'
|
|
7
7
|
- 'Pixelmatters/markup:packages/widget/src/widget.ts'
|
|
8
8
|
metadata:
|
|
9
9
|
library: pixelmatters-markup
|
|
10
|
-
library_version: '1.18.
|
|
10
|
+
library_version: '1.18.5'
|
|
11
11
|
---
|
|
12
12
|
|
|
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
|
|
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.
|
|
14
14
|
|
|
15
|
-
The widget renders a single compact **toolbar pill** in one bottom corner: comment, inbox (signed-in users only), pin-visibility toggle, identity menu, and an overflow menu holding Appearance, Position, an auto-capture toggle, and "Hide for this session". Installs predating 1.15.0 described a floating action button
|
|
15
|
+
The widget renders a single compact **toolbar pill** in one bottom corner: comment, inbox (signed-in users only), pin-visibility toggle, identity menu, and an overflow menu holding Appearance, Position, an auto-capture toggle, and "Hide for this session". Installs predating 1.15.0 described a floating action button. That's gone, along with drag-to-reposition.
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
@@ -26,17 +26,17 @@ 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.18.
|
|
29
|
+
import { init } from 'https://esm.sh/@pixelmatters/markup@1.18.5'
|
|
30
30
|
init({ apiUrl: '…', apiKey: '…' })
|
|
31
31
|
</script>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
If the host disallows inline JS, use the auto-init form. `data-markup-widget="true"` is required
|
|
34
|
+
If the host disallows inline JS, use the auto-init form. `data-markup-widget="true"` is required, since it's how the bundle finds its own script element under `type="module"`:
|
|
35
35
|
|
|
36
36
|
```html
|
|
37
37
|
<script
|
|
38
38
|
type="module"
|
|
39
|
-
src="https://esm.sh/@pixelmatters/markup@1.18.
|
|
39
|
+
src="https://esm.sh/@pixelmatters/markup@1.18.5"
|
|
40
40
|
data-markup-widget="true"
|
|
41
41
|
data-api-url="…"
|
|
42
42
|
data-api-key="…"
|
|
@@ -49,18 +49,18 @@ The recognised attributes are `data-api-url`, `data-api-key`, `data-position`, `
|
|
|
49
49
|
|
|
50
50
|
## Wire-up
|
|
51
51
|
|
|
52
|
-
There is no framework-specific entrypoint
|
|
52
|
+
There is no framework-specific entrypoint: no `@pixelmatters/markup/react`, no provider to wrap. Every framework calls `init()` from a mount hook (`useEffect`, `onMounted`, `onMount`, …) and the function it returns on cleanup. A module-level `destroy` is exported too, for call sites that can't hold onto the returned one; it tears down whichever instance is currently mounted.
|
|
53
53
|
|
|
54
54
|
```ts
|
|
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
|
|
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.
|
|
59
59
|
|
|
60
|
-
If `apiUrl` or `apiKey` is missing, `init` logs a `[markup]` console warning and returns a no-op `destroy` rather than mounting
|
|
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
|
|
|
62
62
|
```ts
|
|
63
|
-
// React
|
|
63
|
+
// React, in App.tsx
|
|
64
64
|
useEffect(() => {
|
|
65
65
|
return init({
|
|
66
66
|
apiUrl: import.meta.env.VITE_MARKUP_API_URL,
|
|
@@ -86,18 +86,18 @@ const stop = init({ apiUrl, apiKey })
|
|
|
86
86
|
window.addEventListener('beforeunload', stop)
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Tear down on logout if you don't want the widget visible to signed-out users. `init` is safe to call repeatedly
|
|
89
|
+
Tear down on logout if you don't want the widget visible to signed-out users. `init` is safe to call repeatedly, and each call destroys the previous instance first.
|
|
90
90
|
|
|
91
91
|
## Credentials
|
|
92
92
|
|
|
93
|
-
- `apiUrl`: the project's Convex deployment site URL
|
|
93
|
+
- `apiUrl`: the project's Convex deployment site URL, copied from **Markup dashboard → Settings → Install**. Format: `https://<deployment>.convex.site`.
|
|
94
94
|
- `apiKey`: mint from **Settings → API Keys**. The raw key is shown **exactly once**; if it's lost, revoke and mint a new one.
|
|
95
95
|
|
|
96
96
|
Conventions:
|
|
97
97
|
|
|
98
|
-
- Store both in env vars (`VITE_MARKUP_API_URL`, `VITE_MARKUP_API_KEY`, or the framework equivalent
|
|
98
|
+
- Store both in env vars (`VITE_MARKUP_API_URL`, `VITE_MARKUP_API_KEY`, or the framework equivalent, such as `NEXT_PUBLIC_…` or `PUBLIC_…`).
|
|
99
99
|
- Add to `.env.example`. Ensure `.env*.local` is in `.gitignore` (most Vite / Next templates already do this).
|
|
100
|
-
- The API key is public
|
|
100
|
+
- The API key is public by design, gated server-side by the project's `allowedDomains`. Treating it like a secret anyway keeps it out of git history and forces rotation through the dashboard rather than ad-hoc.
|
|
101
101
|
|
|
102
102
|
## Domain allowlist
|
|
103
103
|
|
|
@@ -107,19 +107,19 @@ Production deployments **do not** auto-allow `localhost`. Before the widget will
|
|
|
107
107
|
2. Add the exact host (`app.example.com`) or a glob (`*.staging.example.com`).
|
|
108
108
|
3. To test against the live deployment from local dev, add `localhost` explicitly.
|
|
109
109
|
|
|
110
|
-
A request from an unlisted origin gets `403 Origin not allowed` with no CORS headers
|
|
110
|
+
A request from an unlisted origin gets `403 Origin not allowed` with no CORS headers, so browser devtools shows it as a CORS error rather than a clean JSON error.
|
|
111
111
|
|
|
112
112
|
## Config options
|
|
113
113
|
|
|
114
|
-
| Option | Type | Default | Notes
|
|
115
|
-
| -------------- | ---------------------------------------------------- | ----------------- |
|
|
116
|
-
| `apiUrl` | `string` | required | `https://*.convex.site`
|
|
117
|
-
| `apiKey` | `string` | required | `markup_…`
|
|
118
|
-
| `position` | `'bottom-right' \| 'bottom-left' \| 'bottom-center'` | `'bottom-right'` | Initial toolbar corner
|
|
119
|
-
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | `'auto'` follows the host's `prefers-color-scheme`. A user's pick in the overflow menu persists and outranks this on later `init()`
|
|
120
|
-
| `screenshots` | `ScreenshotsConfig` (see below) | enabled, defaults | Capture toggle
|
|
121
|
-
| `dashboardUrl` | `string` |
|
|
122
|
-
| `fab` | `'default' \| 'icon-only'` |
|
|
114
|
+
| Option | Type | Default | Notes |
|
|
115
|
+
| -------------- | ---------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
116
|
+
| `apiUrl` | `string` | required | `https://*.convex.site` |
|
|
117
|
+
| `apiKey` | `string` | required | `markup_…` |
|
|
118
|
+
| `position` | `'bottom-right' \| 'bottom-left' \| 'bottom-center'` | `'bottom-right'` | Initial toolbar corner. Users move it with the Position picker in the overflow menu, and the choice is per-mount and resets on reload |
|
|
119
|
+
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | `'auto'` follows the host's `prefers-color-scheme`. A user's pick in the overflow menu persists and outranks this on later `init()` |
|
|
120
|
+
| `screenshots` | `ScreenshotsConfig` (see below) | enabled, defaults | Capture toggle and PII-scrub options |
|
|
121
|
+
| `dashboardUrl` | `string` | none | Adds an **Account →** link to the signed-in identity menu and retargets the "Powered by Markup" line. For self-hosters |
|
|
122
|
+
| `fab` | `'default' \| 'icon-only'` | none | **Deprecated, ignored since 1.15.0.** Accepted so old configs still compile; `init()` drops it and warns once. Remove it |
|
|
123
123
|
|
|
124
124
|
### Screenshots & PII scrub
|
|
125
125
|
|
|
@@ -128,9 +128,9 @@ init({
|
|
|
128
128
|
apiUrl,
|
|
129
129
|
apiKey,
|
|
130
130
|
screenshots: {
|
|
131
|
-
enabled: true, // default true
|
|
131
|
+
enabled: true, // default true; set false to disable screenshot capture
|
|
132
132
|
strictScrub: false, // mask EVERY input/select/textarea (not just sensitive)
|
|
133
|
-
redactSelector: '.private', // extra CSS selector
|
|
133
|
+
redactSelector: '.private', // extra CSS selector; matched elements always masked
|
|
134
134
|
},
|
|
135
135
|
})
|
|
136
136
|
```
|
|
@@ -143,27 +143,27 @@ Auto-masked by default (regardless of `strictScrub`):
|
|
|
143
143
|
|
|
144
144
|
HTML attributes the widget honours on the host page:
|
|
145
145
|
|
|
146
|
-
- `data-markup-private`
|
|
147
|
-
- `data-markup-safe`
|
|
148
|
-
- `data-markup-skip`
|
|
146
|
+
- `data-markup-private` and `data-markup-redact` are always masked, on top of auto-detection.
|
|
147
|
+
- `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.
|
|
148
|
+
- `data-markup-skip` removes the element from the screenshot entirely, rather than masking it.
|
|
149
149
|
|
|
150
150
|
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.
|
|
151
151
|
|
|
152
|
-
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
|
|
152
|
+
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.
|
|
153
153
|
|
|
154
|
-
Capture degrades rather than failing
|
|
154
|
+
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; an oversized capture is re-encoded down a quality-then-scale ladder to fit the server's 2 MB cap; and if it still can't produce an image the composer says _Screenshot unavailable_ and the comment posts without one.
|
|
155
155
|
|
|
156
156
|
## Mentions & inbox
|
|
157
157
|
|
|
158
|
-
Typing `@` in a composer opens a picker of project members and inserts a chip
|
|
158
|
+
Typing `@` in a composer opens a picker of project members and inserts a chip, and the stored `@[Display Name](userId)` form never reaches the screen. `↑`/`↓` move, `enter` or `tab` picks, `esc` dismisses. It works for anonymous visitors too; the member list is fetched once per mount with the API key, and a failed fetch degrades to "no picker, no chips" rather than blocking the widget.
|
|
159
159
|
|
|
160
|
-
Mentioned teammates get a notification. **Signed-in** users read those from the inbox button on the toolbar, which carries an unread badge; anonymous visitors don't get the button. A mention that doesn't highlight means nobody was notified
|
|
160
|
+
Mentioned teammates get a notification. **Signed-in** users read those from the inbox button on the toolbar, which carries an unread badge; anonymous visitors don't get the button. A mention that doesn't highlight means nobody was notified: the target isn't a project member, has left, or is the author themselves.
|
|
161
161
|
|
|
162
162
|
Comments written by an AI agent through Markup's MCP server carry a bot badge. They're posted under a team member's name, so the badge is the only signal that a machine wrote it.
|
|
163
163
|
|
|
164
164
|
## Verify the install
|
|
165
165
|
|
|
166
|
-
Don't call it done on a clean build
|
|
166
|
+
Don't call it done on a clean build. The widget can compile in and still never
|
|
167
167
|
mount. Run the dev server, then:
|
|
168
168
|
|
|
169
169
|
1. Confirm the toolbar pill is in the configured bottom corner.
|
|
@@ -171,51 +171,51 @@ mount. Run the dev server, then:
|
|
|
171
171
|
3. Confirm the thread appears on the project in the Markup dashboard. It arrives
|
|
172
172
|
over WebSocket, so it should show without a refresh.
|
|
173
173
|
|
|
174
|
-
A pin that posts but never lands in the dashboard is a project mismatch
|
|
174
|
+
A pin that posts but never lands in the dashboard is a project mismatch. The
|
|
175
175
|
API key belongs to a different project than the one being watched.
|
|
176
176
|
|
|
177
177
|
## Diagnostics
|
|
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
|
|
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
|
|
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, 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
|
|
|
202
|
-
- **Don't import CSS
|
|
203
|
-
- **Don't wrap in a provider component.** `init` is the
|
|
202
|
+
- **Don't import CSS.** There is none to import. Styles live inside the shadow root.
|
|
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.18.
|
|
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
|
|
206
|
+
- **Don't ship CDN URLs without a version pin.** `@pixelmatters/markup@1.18.5`, not `@pixelmatters/markup`.
|
|
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
|
|
210
210
|
|
|
211
|
-
The widget follows semver. Pin to a major in `package.json` (`^1.5.0`) so patch + minor updates flow through `pnpm update`, but a future `2.0` cannot break the host silently. For CDN consumers, pin the exact version
|
|
211
|
+
The widget follows semver. Pin to a major in `package.json` (`^1.5.0`) so patch + minor updates flow through `pnpm update`, but a future `2.0` cannot break the host silently. For CDN consumers, pin the exact version, because there's no lockfile to catch a surprise major.
|
|
212
212
|
|
|
213
213
|
## Reference
|
|
214
214
|
|
|
215
|
-
- Install tutorial: https://
|
|
215
|
+
- Install tutorial: https://markup.pixelmatters.dev/docs/install
|
|
216
216
|
- Package on npm: https://www.npmjs.com/package/@pixelmatters/markup
|
|
217
217
|
- Source: https://github.com/Pixelmatters/markup
|
|
218
218
|
|
|
219
219
|
## When in doubt
|
|
220
220
|
|
|
221
|
-
The widget's `init({ apiUrl, apiKey })` is intentionally the entire API. If a question seems to need a richer
|
|
221
|
+
The widget's `init({ apiUrl, apiKey })` is intentionally the entire API. If a question seems to need a richer API (custom triggers, programmatic pin creation, headless mode), it's almost certainly something the widget doesn't do. Say so plainly rather than inventing options.
|