@pixelmatters/markup 1.18.4 → 1.18.6
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 +72 -72
- package/dist/widget.js +43 -34
- package/dist/widget.js.map +1 -1
- package/package.json +1 -1
- package/skills/install-markup-widget/SKILL.md +60 -60
package/README.md
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://bundlephobia.com/package/@pixelmatters/markup)
|
|
6
6
|
|
|
7
|
-
Pin-anchored feedback for live web apps. Drop in a script tag and your stakeholders can leave threaded comments, reactions, and annotated screenshots on top of any page
|
|
7
|
+
Pin-anchored feedback for live web apps. Drop in a script tag and your stakeholders can leave threaded comments, reactions, and annotated screenshots on top of any page, without touching the host's CSS, build, or routing.
|
|
8
8
|
|
|
9
9
|
## Highlights
|
|
10
10
|
|
|
11
|
-
- **Pin anywhere
|
|
12
|
-
- **Threads, in real time
|
|
13
|
-
- **@-mentions
|
|
14
|
-
- **Annotated screenshots
|
|
15
|
-
- **Drop-in identity
|
|
16
|
-
- **Agent replies are labelled
|
|
17
|
-
- **Style-isolated
|
|
18
|
-
- **SPA-aware
|
|
19
|
-
- **Respects the platform
|
|
20
|
-
- **Tiny
|
|
11
|
+
- **Pin anywhere.** Click anywhere on the page to attach a comment to that exact element. Pins re-anchor across reflow using a CSS selector plus a viewport-fraction fallback.
|
|
12
|
+
- **Threads, in real time.** Replies stream in via WebSocket. Per-comment edit, delete (with tombstones), and emoji reactions.
|
|
13
|
+
- **@-mentions.** Typing `@` in the composer opens a project-member picker and inserts a chip; the `@[Name](userId)` wire format never reaches the screen. Mentioned teammates are notified, and signed-in users read those notifications from the toolbar's inbox.
|
|
14
|
+
- **Annotated screenshots.** Opt-in capture with the pin marker drawn on the image and embedded fonts, so the snapshot matches what the user saw.
|
|
15
|
+
- **Drop-in identity.** Anonymous by default, with a popup-based sign-in that survives Safari ITP and Chrome storage partitioning. Signed-in authors show their profile picture; everyone else gets initials.
|
|
16
|
+
- **Agent replies are labelled.** A comment written by an AI agent through Markup's MCP server carries a bot badge. It's posted under a team member's name, so the badge is the only way a visitor can tell a machine answered.
|
|
17
|
+
- **Style-isolated.** Runs inside an open shadow root with `:host { all: initial }`, so host CSS can't bleed in and widget CSS can't bleed out.
|
|
18
|
+
- **SPA-aware.** Patches `history.pushState` / `replaceState` and follows `popstate` to refresh threads on route changes.
|
|
19
|
+
- **Respects the platform.** Honours `prefers-reduced-motion` and `prefers-color-scheme`, with full keyboard navigation and focus traps in popovers.
|
|
20
|
+
- **Tiny API, tiny config.** `init({ apiUrl, apiKey })` is enough to start. No global CSS to import, no provider to wrap.
|
|
21
21
|
|
|
22
|
-
Built with [Preact](https://preactjs.com).
|
|
22
|
+
Built with [Preact](https://preactjs.com). It ships as a single ESM bundle, so wiring it into any framework takes a one-liner in your root component. Snippets below.
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
@@ -32,14 +32,14 @@ yarn add @pixelmatters/markup
|
|
|
32
32
|
npm install @pixelmatters/markup
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
CDN drop-in
|
|
35
|
+
CDN drop-in, no build step. Paste this just before `</body>`:
|
|
36
36
|
|
|
37
37
|
```html
|
|
38
38
|
<script type="module">
|
|
39
|
-
// Pin the exact version
|
|
40
|
-
import { init } from 'https://esm.sh/@pixelmatters/markup@1.18.
|
|
39
|
+
// Pin the exact version; esm.sh resolves it from npm
|
|
40
|
+
import { init } from 'https://esm.sh/@pixelmatters/markup@1.18.6'
|
|
41
41
|
// or
|
|
42
|
-
// import { init } from 'https://esm.run/@pixelmatters/markup@1.18.
|
|
42
|
+
// import { init } from 'https://esm.run/@pixelmatters/markup@1.18.6'
|
|
43
43
|
|
|
44
44
|
init({
|
|
45
45
|
apiUrl: 'https://your-deployment.convex.site',
|
|
@@ -50,14 +50,14 @@ CDN drop-in (no build step) — paste this just before `</body>`:
|
|
|
50
50
|
</script>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
> **Why pin the version?** CDN URLs without a version (`@pixelmatters/markup`) resolve to whatever's `latest` on npm
|
|
53
|
+
> **Why pin the version?** CDN URLs without a version (`@pixelmatters/markup`) resolve to whatever's `latest` on npm, so a future major release will break your page with no warning. Always pin (`@pixelmatters/markup@1.18.6`).
|
|
54
54
|
|
|
55
|
-
If your platform doesn't allow inline JS (some CMS / page-builder editors), use the auto-init form instead
|
|
55
|
+
If your platform doesn't allow inline JS (some CMS / page-builder editors), use the auto-init form instead. Point a `<script src=…>` at the bundle and pass config via `data-*` attributes:
|
|
56
56
|
|
|
57
57
|
```html
|
|
58
58
|
<script
|
|
59
59
|
type="module"
|
|
60
|
-
src="https://esm.sh/@pixelmatters/markup@1.18.
|
|
60
|
+
src="https://esm.sh/@pixelmatters/markup@1.18.6"
|
|
61
61
|
data-markup-widget="true"
|
|
62
62
|
data-api-url="https://your-deployment.convex.site"
|
|
63
63
|
data-api-key="markup_..."
|
|
@@ -66,7 +66,7 @@ If your platform doesn't allow inline JS (some CMS / page-builder editors), use
|
|
|
66
66
|
></script>
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`data-markup-widget="true"` is required
|
|
69
|
+
`data-markup-widget="true"` is required, since it's how the bootstrap finds its own `<script>` tag (`document.currentScript` is `null` for `type="module"`). The recognised attributes are `data-api-url`, `data-api-key`, `data-position`, `data-theme`, and `data-dashboard-url`; the `screenshots` options are only available through `init()`.
|
|
70
70
|
|
|
71
71
|
## Quickstart
|
|
72
72
|
|
|
@@ -153,20 +153,20 @@ export default function App() {
|
|
|
153
153
|
|
|
154
154
|
Mounts the widget. Always tears down any existing instance before mounting, so calling `init` again (with the same or a different config) is safe. Returns the `destroy` function.
|
|
155
155
|
|
|
156
|
-
| Option | Type | Default | Description
|
|
157
|
-
| -------------- | ---------------------------------------------------- | ---------------- |
|
|
158
|
-
| `apiUrl` | `string` | required | Convex deployment site URL (`https://*.convex.site`)
|
|
159
|
-
| `apiKey` | `string` | required | Project API key
|
|
160
|
-
| `position` | `'bottom-right' \| 'bottom-left' \| 'bottom-center'` | `'bottom-right'` | Initial placement for the toolbar
|
|
161
|
-
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Initial theme
|
|
162
|
-
| `analytics` | `boolean` | `true` | Product telemetry
|
|
163
|
-
| `screenshots` | `ScreenshotsConfig` | capture enabled | Capture and PII-scrub options
|
|
164
|
-
| `dashboardUrl` | `string` |
|
|
156
|
+
| Option | Type | Default | Description |
|
|
157
|
+
| -------------- | ---------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
158
|
+
| `apiUrl` | `string` | required | Convex deployment site URL (`https://*.convex.site`) |
|
|
159
|
+
| `apiKey` | `string` | required | Project API key. Mint one in the dashboard |
|
|
160
|
+
| `position` | `'bottom-right' \| 'bottom-left' \| 'bottom-center'` | `'bottom-right'` | Initial placement for the toolbar. Users can move it with the Position picker in the toolbar's overflow menu |
|
|
161
|
+
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Initial theme. `'light'` or `'dark'`, or `'auto'` (default) to follow the host's `prefers-color-scheme`. Users can change this from the overflow menu; their choice persists and wins over this option from then on |
|
|
162
|
+
| `analytics` | `boolean` | `true` | Product telemetry: counts of widget interactions, sent to Markup. Adds no third-party script, sets no cookie, writes nothing to storage, and carries no identifier for your users. See [Product telemetry](#product-telemetry) |
|
|
163
|
+
| `screenshots` | `ScreenshotsConfig` | capture enabled | Capture and PII-scrub options. See [Screenshots & privacy](#screenshots--privacy) |
|
|
164
|
+
| `dashboardUrl` | `string` | none | Dashboard URL the identity menu links to as **Account →** for signed-in users; it also retargets the overflow menu's "Powered by Markup" line. Omit it and the Account entry is hidden. Mostly useful for self-hosters, whose dashboard origin the widget can't know statically |
|
|
165
165
|
|
|
166
166
|
<details>
|
|
167
167
|
<summary><code>fab</code> (deprecated, ignored since 1.15.0)</summary>
|
|
168
168
|
|
|
169
|
-
The floating action button was replaced by the toolbar pill in 1.15.0, and a pill has no variants
|
|
169
|
+
The floating action button was replaced by the toolbar pill in 1.15.0, and a pill has no variants: neither `'default'` nor `'icon-only'` describes anything the widget can render. The option and the `WidgetFab` type are still exported so upgrading needs no code change; `init()` drops the value and logs a one-time console warning. Both go away in the next major:
|
|
170
170
|
|
|
171
171
|
```diff
|
|
172
172
|
init({
|
|
@@ -180,9 +180,9 @@ The floating action button was replaced by the toolbar pill in 1.15.0, and a pil
|
|
|
180
180
|
|
|
181
181
|
### Product telemetry
|
|
182
182
|
|
|
183
|
-
The widget reports counts of its own interactions
|
|
184
|
-
comment submitted, a screenshot captured
|
|
185
|
-
it earn their place. Turn it off with `analytics: false`, or
|
|
183
|
+
The widget reports counts of its own interactions, such as a comment
|
|
184
|
+
started, a comment submitted, or a screenshot captured, so we can tell
|
|
185
|
+
which parts of it earn their place. Turn it off with `analytics: false`, or
|
|
186
186
|
`data-analytics="false"` on the script tag.
|
|
187
187
|
|
|
188
188
|
What it is not, concretely:
|
|
@@ -209,45 +209,45 @@ The widget mounts a single compact pill in the corner set by `position`:
|
|
|
209
209
|
|
|
210
210
|
| Control | What it does |
|
|
211
211
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
212
|
-
| **Comment** | Arms placement
|
|
212
|
+
| **Comment** | Arms placement. The next click on the page drops a pin, and it flips to a cancel icon while armed. |
|
|
213
213
|
| **Inbox** | Mention notifications for this project, with an unread badge. Signed-in users only; anonymous visitors don't get the button. |
|
|
214
214
|
| **Pins** (eye) | Hides or shows every pin without hiding the toolbar. |
|
|
215
215
|
| **Identity** | Avatar button. Anonymous: a sign-in prompt plus "Forget me on this site". Signed in: name, email, an **Account →** link when `dashboardUrl` is set, and **Sign out**. |
|
|
216
216
|
| **Overflow** (`☰`) | **Appearance** (Light / Dark / Auto), **Position** (left / center / right), an **Auto-capture screenshots** toggle, **Privacy & data**, **Keyboard shortcuts**, **Hide for this session**, and the widget version. |
|
|
217
217
|
|
|
218
|
-
Appearance persists to the host page's `localStorage` (`markup:widget:theme`) and, once a user has set it, takes precedence over the `theme` option on every later `init()`. Position, pin visibility, and the auto-capture toggle are per-mount
|
|
218
|
+
Appearance persists to the host page's `localStorage` (`markup:widget:theme`) and, once a user has set it, takes precedence over the `theme` option on every later `init()`. Position, pin visibility, and the auto-capture toggle are per-mount. They reset on reload, and `position` seeds the toolbar again.
|
|
219
219
|
|
|
220
220
|
The last two menu rows open panels anchored to the same corner as the toolbar:
|
|
221
221
|
|
|
222
|
-
- **Privacy & data
|
|
223
|
-
- **Keyboard shortcuts
|
|
222
|
+
- **Privacy & data.** A plain-language account of what the widget holds, generated from the live session rather than from a policy document: who you're posting as, what a comment sends, what sits in this site's storage, whether captures are on, and the single host the widget talks to. Anonymous visitors also get **Forget me on this site** here, with the same arm-then-confirm as the identity menu.
|
|
223
|
+
- **Keyboard shortcuts.** The table below, in the modifier vocabulary of the reader's platform. The menu is the only way in on purpose: a key binding for it (`?` being the conventional one) would compete with whatever your app already binds, and the widget can't see your handlers to know.
|
|
224
224
|
|
|
225
225
|
### Keyboard & mouse
|
|
226
226
|
|
|
227
|
-
| Shortcut | Action
|
|
228
|
-
| --------------------- |
|
|
229
|
-
| `c` | Start placing a markup (ignored while typing)
|
|
230
|
-
| `@` | In a composer, open the member picker
|
|
231
|
-
| `cmd/ctrl + enter` | Post the comment being written
|
|
232
|
-
| `esc` | Cancel placement, dismiss the mention picker, or close the open popover / menu
|
|
233
|
-
| `cmd/ctrl + .` | Toggle HUD visibility
|
|
234
|
-
| `cmd/ctrl + click` | Click the toolbar's comment button to hide the HUD with a hint toast
|
|
235
|
-
| Drag a popover header | Move the open thread / new-thread popover; resets to the pin on reopen
|
|
227
|
+
| Shortcut | Action |
|
|
228
|
+
| --------------------- | -------------------------------------------------------------------------------- |
|
|
229
|
+
| `c` | Start placing a markup (ignored while typing) |
|
|
230
|
+
| `@` | In a composer, open the member picker. `↑`/`↓` to move, `enter` or `tab` to pick |
|
|
231
|
+
| `cmd/ctrl + enter` | Post the comment being written |
|
|
232
|
+
| `esc` | Cancel placement, dismiss the mention picker, or close the open popover / menu |
|
|
233
|
+
| `cmd/ctrl + .` | Toggle HUD visibility |
|
|
234
|
+
| `cmd/ctrl + click` | Click the toolbar's comment button to hide the HUD with a hint toast |
|
|
235
|
+
| Drag a popover header | Move the open thread / new-thread popover; resets to the pin on reopen |
|
|
236
236
|
|
|
237
237
|
## Screenshots & privacy
|
|
238
238
|
|
|
239
|
-
By default, the widget captures the visible viewport as a JPEG before you submit a thread. Sensitive fields are blacked out **before** the image is produced
|
|
239
|
+
By default, the widget captures the visible viewport as a JPEG before you submit a thread. Sensitive fields are blacked out **before** the image is produced. The live DOM is mutated only for the duration of the capture, then restored. No image content leaves the browser until the user explicitly attaches the screenshot and posts.
|
|
240
240
|
|
|
241
241
|
**Auto-scrubbed (zero config):**
|
|
242
242
|
|
|
243
243
|
- `input[type="password"]`
|
|
244
244
|
- Any `<input>` whose `autocomplete` attribute contains `cc-number`, `cc-csc`, `cc-exp`, `cc-name`, `cc-type`, `current-password`, `new-password`, or `one-time-code`
|
|
245
245
|
|
|
246
|
-
**Attribute API
|
|
246
|
+
**Attribute API.** Add any of these to an element to control capture:
|
|
247
247
|
|
|
248
248
|
| Attribute | Behaviour |
|
|
249
249
|
| --------------------- | ---------------------------------------------------------------------- |
|
|
250
|
-
| `data-markup-private` | Always mask
|
|
250
|
+
| `data-markup-private` | Always mask; contents are replaced with a solid block. |
|
|
251
251
|
| `data-markup-redact` | Alias for `data-markup-private`. |
|
|
252
252
|
| `data-markup-safe` | Exempts descendants from the default auto-detect rules (escape hatch). |
|
|
253
253
|
| `data-markup-skip` | Removes the element from the screenshot entirely. |
|
|
@@ -258,19 +258,19 @@ By default, the widget captures the visible viewport as a JPEG before you submit
|
|
|
258
258
|
| ---------------------------- | --------- | ------- | ---------------------------------------------------------- |
|
|
259
259
|
| `screenshots.enabled` | `boolean` | `true` | Set to `false` to disable capture entirely. |
|
|
260
260
|
| `screenshots.strictScrub` | `boolean` | `false` | Also masks all `input`, `select`, and `textarea` elements. |
|
|
261
|
-
| `screenshots.redactSelector` | `string` |
|
|
261
|
+
| `screenshots.redactSelector` | `string` | none | Custom CSS selector; matched elements are always masked. |
|
|
262
262
|
|
|
263
263
|
When a screenshot is attached in the composer, a chip shows how many fields were redacted. Clicking it expands the list of CSS selectors that were masked.
|
|
264
264
|
|
|
265
|
-
Users can also switch capture off for themselves with **Auto-capture screenshots** in the toolbar's overflow menu. A host that set `screenshots.enabled: false` still wins
|
|
265
|
+
Users can also switch capture off for themselves with **Auto-capture screenshots** in the toolbar's overflow menu. A host that set `screenshots.enabled: false` still wins. The row renders disabled and says so, rather than offering a control that does nothing.
|
|
266
266
|
|
|
267
267
|
The overflow menu's **Privacy & data** panel restates all of this for the person using the widget, and reflects whichever of the three capture states is live: captures on, switched off by the user, or disabled by the host.
|
|
268
268
|
|
|
269
269
|
### When a capture doesn't work out
|
|
270
270
|
|
|
271
|
-
Capture degrades instead of failing
|
|
271
|
+
Capture degrades instead of failing outright:
|
|
272
272
|
|
|
273
|
-
- **An image the browser won't hand over**
|
|
273
|
+
- **An image the browser won't hand over** comes through blank, and the rest of the page still captures. A third-party avatar served without CORS headers is the usual culprit. It used to abort the whole screenshot.
|
|
274
274
|
- **An oversized capture** is re-encoded until it fits the server's 2 MB cap: quality drops first (0.85 → 0.6), then the raster shrinks (full → ¾ → ½), because a smaller sharp screenshot beats a full-size illegible one.
|
|
275
275
|
- **If nothing works**, the composer reads _Screenshot unavailable_ and the comment posts without one. Previously the row just disappeared, which looked identical to screenshots being switched off for the project.
|
|
276
276
|
|
|
@@ -280,19 +280,19 @@ Capture degrades instead of failing silently:
|
|
|
280
280
|
- All UI lives in that shadow root, with `:host { all: initial }` blocking style inheritance.
|
|
281
281
|
- The host element is `position: fixed; inset: 0; pointer-events: none`, so the widget paints over the entire viewport without blocking the host's clicks; only the toolbar and active popovers opt back in to pointer events.
|
|
282
282
|
- Pins are anchored as `(x, y)` fractions of the document plus a best-effort CSS selector (via [`@medv/finder`](https://github.com/antonmedv/finder)). The selector wins when it still resolves; the fraction is the fallback so pins survive layout changes.
|
|
283
|
-
- Live thread updates come over a WebSocket to the deployment's `*.convex.cloud` origin, which the widget derives from `apiUrl
|
|
284
|
-
- Identity lives in **host-page** `localStorage` under `markup.identity`, keyed to the top-level site. On first load the widget mints a server-signed anonymous JWT via `POST /widget/anon-identity` so the backend can verify the `authorClientId` on every anon write
|
|
283
|
+
- Live thread updates come over a WebSocket to the deployment's `*.convex.cloud` origin, which the widget derives from `apiUrl`. Everything else, meaning comments, identity, screenshots and error reports, goes to `*.convex.site` over HTTP.
|
|
284
|
+
- Identity lives in **host-page** `localStorage` under `markup.identity`, keyed to the top-level site. On first load the widget mints a server-signed anonymous JWT via `POST /widget/anon-identity` so the backend can verify the `authorClientId` on every anon write. Tampering with the cached `clientId` invalidates the signature. Verified identities upgrade to a `Bearer` JWT via the popup flow described below.
|
|
285
285
|
|
|
286
286
|
## Identity
|
|
287
287
|
|
|
288
288
|
Anonymous by default, with two opt-in upgrade paths:
|
|
289
289
|
|
|
290
|
-
- **Sign in with Markup
|
|
291
|
-
- **Continue as a guest
|
|
290
|
+
- **Sign in with Markup.** The widget POSTs to `/widget/popup-exchange` with the apiKey + Origin gate, gets back a single-use 60-second code, then opens `${apiUrl}/widget/auth?code=…` in a popup. Because the popup is first-party to the deployment origin, the better-auth session cookie is sent normally (sidestepping third-party cookie blocks). The popup `postMessage`s a verified identity plus a short-lived server-signed `Bearer` JWT back to the host page, which attaches it as `Authorization: Bearer` on subsequent writes. Where partitioned (CHIPS) cookies are available the tokens are held in `HttpOnly` cookies and the session refreshes for up to 7 days; where they aren't, the widget falls back to `markup.identity` in the host page's `localStorage` and the session is capped at 24 hours, since a token stored there is readable by any script on the page. The apiKey never appears in the popup URL.
|
|
291
|
+
- **Continue as a guest.** Name and optional email, stored alongside the server-issued anonymous `clientId` and JWT minted on first load.
|
|
292
292
|
|
|
293
293
|
The popup origin is validated against the project's `allowedDomains` before any identity is returned, so only embeds on approved domains can resolve dashboard sessions. Verified JWTs can be invalidated before their TTL via the dashboard's "Sign out everywhere" action.
|
|
294
294
|
|
|
295
|
-
Signed-in authors render their profile picture
|
|
295
|
+
Signed-in authors render their profile picture on their comments and on the toolbar's identity button. The URL is validated as `http(s)` both when it arrives and when it's read back out of `localStorage`, because that record lives on the customer's origin where any script could rewrite a value the widget hands to an `<img src>`. Anonymous visitors, accounts with no picture, and images the host's CSP blocks all fall back to initials.
|
|
296
296
|
|
|
297
297
|
**Forget me on this site** (in the anonymous identity menu) clears the cached name, email, and client id from the host page's `localStorage`. Comments already posted stay where they are.
|
|
298
298
|
|
|
@@ -302,12 +302,12 @@ Signed-in authors render their profile picture — on their comments and on the
|
|
|
302
302
|
|
|
303
303
|
1. Sign in to the Markup dashboard.
|
|
304
304
|
2. Open your project → **Settings → API Keys**.
|
|
305
|
-
3. Click **New key**, label it, and copy the raw key
|
|
305
|
+
3. Click **New key**, label it, and copy the raw key. It is shown once.
|
|
306
306
|
4. Open **Settings → Domains** and add the host domain (`app.example.com`, `*.staging.example.com`). Production deployments do not auto-allow `localhost`; add it to the allowlist explicitly if you test against the deployment from `http://localhost`.
|
|
307
307
|
|
|
308
308
|
## AI prompt
|
|
309
309
|
|
|
310
|
-
This package ships its own Agent Skill
|
|
310
|
+
This package ships its own Agent Skill: persistent install and troubleshooting guidance for your agent, instead of a prompt you paste once and lose. Install it rather than the prompt below where you can.
|
|
311
311
|
|
|
312
312
|
If `@pixelmatters/markup` is already a dependency, the skill is already on disk under `node_modules`, versioned with the release you installed. [TanStack Intent](https://tanstack.com/intent/latest) wires it into whatever agent config you use (`AGENTS.md`, `CLAUDE.md`, `.cursorrules`, …):
|
|
313
313
|
|
|
@@ -315,7 +315,7 @@ If `@pixelmatters/markup` is already a dependency, the skill is already on disk
|
|
|
315
315
|
npx @tanstack/intent@latest install
|
|
316
316
|
```
|
|
317
317
|
|
|
318
|
-
Re-run it when you add another Intent-enabled package. A `pnpm update` of this one is enough on its own
|
|
318
|
+
Re-run it when you add another Intent-enabled package. A `pnpm update` of this one is enough on its own, because the skill travels with the tarball and refreshes in place.
|
|
319
319
|
|
|
320
320
|
If you'd rather pull it straight from the repo, or you're deciding whether to adopt the widget at all and haven't installed it yet, the [`skills`](https://www.npmjs.com/package/skills) CLI takes it by path:
|
|
321
321
|
|
|
@@ -323,7 +323,7 @@ If you'd rather pull it straight from the repo, or you're deciding whether to ad
|
|
|
323
323
|
npx skills add Pixelmatters/markup/packages/widget/skills/install-markup-widget
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
-
Pass the full path, not just the repository
|
|
326
|
+
Pass the full path, not just the repository. That is where this skill lives, and a bare repository reference resolves to a different set of skills.
|
|
327
327
|
|
|
328
328
|
Otherwise, paste the block below into Claude, ChatGPT, Cursor, or any other LLM and it'll wire the widget into your codebase end-to-end.
|
|
329
329
|
|
|
@@ -353,24 +353,24 @@ init({
|
|
|
353
353
|
dashboardUrl?: string, // optional: adds an "Account →" link to the identity menu
|
|
354
354
|
screenshots?: {
|
|
355
355
|
enabled?: boolean, // default true
|
|
356
|
-
strictScrub?: boolean, // default false
|
|
356
|
+
strictScrub?: boolean, // default false; also masks every input/select/textarea
|
|
357
357
|
redactSelector?: string, // extra CSS selector to mask
|
|
358
358
|
},
|
|
359
|
-
}) // returns a destroy() function
|
|
359
|
+
}) // returns a destroy() function; call it on unmount / logout / route teardown
|
|
360
360
|
```
|
|
361
361
|
|
|
362
|
-
There is no `fab` option any more
|
|
362
|
+
There is no `fab` option any more. It's accepted, ignored, and warns once. Drop it if you find one in my config.
|
|
363
363
|
|
|
364
364
|
For React/Vue/Solid hosts, call `init()` from a mount lifecycle hook
|
|
365
365
|
(`useEffect`, `onMounted`, `onMount`) and call the returned `destroy` on
|
|
366
|
-
cleanup. There's no framework-specific entrypoint
|
|
367
|
-
public
|
|
366
|
+
cleanup. There's no framework-specific entrypoint; `init` is the whole
|
|
367
|
+
public API.
|
|
368
368
|
|
|
369
369
|
For a `<script>` tag drop-in (no bundler), use the inline ESM form and **pin the version**:
|
|
370
370
|
|
|
371
371
|
```html
|
|
372
372
|
<script type="module">
|
|
373
|
-
import { init } from 'https://esm.sh/@pixelmatters/markup@1.18.
|
|
373
|
+
import { init } from 'https://esm.sh/@pixelmatters/markup@1.18.6'
|
|
374
374
|
|
|
375
375
|
init({
|
|
376
376
|
apiUrl: '...',
|
|
@@ -386,7 +386,7 @@ If inline JS is disallowed (some CMS / page-builder editors), use the auto-init
|
|
|
386
386
|
```html
|
|
387
387
|
<script
|
|
388
388
|
type="module"
|
|
389
|
-
src="https://esm.sh/@pixelmatters/markup@1.18.
|
|
389
|
+
src="https://esm.sh/@pixelmatters/markup@1.18.6"
|
|
390
390
|
data-markup-widget="true"
|
|
391
391
|
data-api-url="..."
|
|
392
392
|
data-api-key="..."
|
|
@@ -405,7 +405,7 @@ If inline JS is disallowed (some CMS / page-builder editors), use the auto-init
|
|
|
405
405
|
|
|
406
406
|
Constraints:
|
|
407
407
|
|
|
408
|
-
- Do **not** add CSS imports or provider components
|
|
408
|
+
- Do **not** add CSS imports or provider components. The widget needs neither.
|
|
409
409
|
- Do **not** hardcode the key.
|
|
410
410
|
- If the project has a CSP: add `https://<MY_DEPLOYMENT>.convex.site` and `wss://<MY_DEPLOYMENT>.convex.cloud` to `connect-src`, `blob:` and `data:` (plus the host of our profile pictures) to `img-src`, and `'unsafe-inline'` to `style-src`. Add `https://esm.sh` to `script-src` only if I'm using the `<script>` tag path.
|
|
411
411
|
````
|
|
@@ -419,14 +419,14 @@ If the host page ships a CSP, the widget needs:
|
|
|
419
419
|
| `connect-src` | `https://<deployment>.convex.site` | threads, comments, identity, screenshot upload, error reports |
|
|
420
420
|
| `connect-src` | `wss://<deployment>.convex.cloud` | live thread updates |
|
|
421
421
|
| `img-src` | wherever your team's profile pictures are hosted, plus `blob:` and `data:` | avatars, the screenshot preview thumbnail, and the capture pipeline |
|
|
422
|
-
| `script-src` | `https://esm.sh` | CDN path only
|
|
422
|
+
| `script-src` | `https://esm.sh` | CDN path only; a bundled install needs nothing here |
|
|
423
423
|
| `style-src` | `'unsafe-inline'` | the widget appends its stylesheet as a `<style>` element inside its own shadow root |
|
|
424
424
|
|
|
425
425
|
Only `img-src` degrades gracefully: a blocked avatar falls back to initials, and a blocked image in a capture comes through blank. A missing `connect-src` entry stops the widget working at all, and a missing `style-src` one leaves it unstyled.
|
|
426
426
|
|
|
427
427
|
## Browser support
|
|
428
428
|
|
|
429
|
-
Modern evergreen browsers (Chrome, Edge, Firefox, Safari) and their mobile equivalents. The widget uses native ESM, shadow DOM, and `IntersectionObserver
|
|
429
|
+
Modern evergreen browsers (Chrome, Edge, Firefox, Safari) and their mobile equivalents. The widget uses native ESM, shadow DOM, and `IntersectionObserver`, with no IE11 or legacy bundle.
|
|
430
430
|
|
|
431
431
|
## Self-hosting Markup
|
|
432
432
|
|