snagset 0.0.1
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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/astro.d.ts +48 -0
- package/dist/astro.js +50 -0
- package/dist/astro.js.map +1 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/next.d.ts +51 -0
- package/dist/next.js +17 -0
- package/dist/next.js.map +1 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.js +19 -0
- package/dist/react.js.map +1 -0
- package/dist/vendor/types/index.d.ts +1 -0
- package/dist/vendor/types/index.js +4 -0
- package/dist/vendor/types/names.d.ts +207 -0
- package/dist/vendor/types/names.js +254 -0
- package/dist/vite.d.ts +51 -0
- package/dist/vite.js +20 -0
- package/dist/vite.js.map +1 -0
- package/dist/vue.d.ts +65 -0
- package/dist/vue.js +55 -0
- package/dist/vue.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eurico Sá Fernandes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# `snagset`
|
|
2
|
+
|
|
3
|
+
Click-to-comment review for a live website. Your client clicks the thing that's
|
|
4
|
+
wrong and types; you get the pin, the screenshot, the console and the route.
|
|
5
|
+
|
|
6
|
+
One import, or one script tag. Both do exactly the same thing.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install snagset
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Vanilla
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import snagset from "snagset";
|
|
16
|
+
|
|
17
|
+
snagset.init({ host: "https://review.example.com", site: "prj_…" });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That is the whole API. `snagset.destroy()` takes it back off; `snagset.tag({…})`
|
|
21
|
+
gives you the tag as data if you would rather render it yourself.
|
|
22
|
+
|
|
23
|
+
## React
|
|
24
|
+
|
|
25
|
+
```jsx
|
|
26
|
+
import Snagset from "snagset/react";
|
|
27
|
+
|
|
28
|
+
<Snagset host="https://review.example.com" site="prj_…" />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Vue
|
|
32
|
+
|
|
33
|
+
```vue
|
|
34
|
+
<script setup>
|
|
35
|
+
import Snagset from "snagset/vue";
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<Snagset host="https://review.example.com" site="prj_…" />
|
|
40
|
+
</template>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Next.js
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// app/layout.tsx
|
|
47
|
+
import { Snagset } from "snagset/next";
|
|
48
|
+
|
|
49
|
+
<body>
|
|
50
|
+
{children}
|
|
51
|
+
<Snagset host="https://review.example.com" site="prj_…" />
|
|
52
|
+
</body>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
No `"use client"` — it renders the script tag on the server, so it costs zero
|
|
56
|
+
client JavaScript. Leave the props off and it reads `NEXT_PUBLIC_SNAGSET_HOST`
|
|
57
|
+
and `NEXT_PUBLIC_SNAGSET_SITE`, so each environment configures itself from
|
|
58
|
+
`.env`. Works in the Pages Router too.
|
|
59
|
+
|
|
60
|
+
## Vite — and Nuxt, SvelteKit, SolidStart, anything on Vite
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// vite.config.ts
|
|
64
|
+
import { snagset } from "snagset/vite";
|
|
65
|
+
|
|
66
|
+
export default defineConfig({
|
|
67
|
+
plugins: [snagset({ host: "https://review.example.com", site: "prj_…" })],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
One line in a file the project already has; the tag is injected before
|
|
72
|
+
`</body>`. In Nuxt, put it under `vite: { plugins: […] }` in `nuxt.config`.
|
|
73
|
+
Want it only in dev? Use Vite's own idiom:
|
|
74
|
+
`mode === "development" ? snagset({…}) : undefined`. Shipping the tag to
|
|
75
|
+
production is safe either way — who sees the toolbar is a dashboard setting,
|
|
76
|
+
checked by the tag on load, not something baked into your build.
|
|
77
|
+
|
|
78
|
+
## Astro, Rails — anything that renders a layout
|
|
79
|
+
|
|
80
|
+
```astro
|
|
81
|
+
---
|
|
82
|
+
import { snagsetTag } from "snagset/astro";
|
|
83
|
+
const tag = snagsetTag({ host: "https://review.example.com", site: "prj_…" });
|
|
84
|
+
---
|
|
85
|
+
<script src={tag.src} {...tag.attrs} defer is:inline></script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`is:inline` matters — without it Astro bundles the script and rewrites the
|
|
89
|
+
`src`, and the widget finds its chunks relative to its own `src`.
|
|
90
|
+
|
|
91
|
+
## Or no npm at all
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<script src="https://review.example.com/snag.js" data-snag-site="prj_…" defer></script>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
That is what this package writes for you. There is no feature behind the npm
|
|
98
|
+
install — if a script tag suits your stack better, use it. It works on
|
|
99
|
+
WordPress, Webflow, Shopify and Rails, where a React-only tool cannot go.
|
|
100
|
+
|
|
101
|
+
## Options
|
|
102
|
+
|
|
103
|
+
Every `data-snag-*` attribute, as a camelCase key. `host` and `site` are the
|
|
104
|
+
only required ones.
|
|
105
|
+
|
|
106
|
+
| Option | What it does |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `host` | Your Snagset instance |
|
|
109
|
+
| `site` | The project uid, from the dashboard |
|
|
110
|
+
| `env` | Which environment this build is — `staging`, `production` |
|
|
111
|
+
| `theme` | `light`, `dark`, or leave it to follow the page |
|
|
112
|
+
| `lang` | UI language |
|
|
113
|
+
| `position` | Where the toolbar sits |
|
|
114
|
+
| `hotkey` | Default is `alt+c` |
|
|
115
|
+
| `branch`, `build` | Stamped onto every comment, for "which deploy was this?" |
|
|
116
|
+
|
|
117
|
+
## What this package is
|
|
118
|
+
|
|
119
|
+
**A loader.** It writes the script tag and nothing else — the widget itself is
|
|
120
|
+
served by your Snagset instance, at your instance's version.
|
|
121
|
+
|
|
122
|
+
That is deliberate. The widget is a lazy chunk graph: about 10 kB loads on
|
|
123
|
+
every page and the remaining ~245 kB arrives only if somebody opens the toolbar.
|
|
124
|
+
Bundling it here would put all of it into your bundle, and pin a widget version
|
|
125
|
+
in your `package.json` that can drift from the server it talks to.
|
|
126
|
+
|
|
127
|
+
It also means the bytes come from your own origin, so your CSP is one
|
|
128
|
+
`script-src 'self'` entry and there is no third party to justify to anyone.
|
|
129
|
+
|
|
130
|
+
## Requirements
|
|
131
|
+
|
|
132
|
+
A Snagset instance. It self-hosts on Node and Postgres —
|
|
133
|
+
[snagset/snagset](https://github.com/snagset/snagset), or
|
|
134
|
+
`npx create-snagset` to scaffold one.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
**Early release.** This is a `0.0.x` line: the tag it writes is stable and
|
|
139
|
+
tested, but expect the odd rough edge, and pin the version if you need
|
|
140
|
+
stability. Issues and notes are welcome at
|
|
141
|
+
[snagset/snagset](https://github.com/snagset/snagset).
|
|
142
|
+
|
|
143
|
+
MIT.
|
package/dist/astro.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { tagFor, type InitOptions } from "./index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* `snagset/astro` — D63.
|
|
4
|
+
*
|
|
5
|
+
* Astro is the case where the OTHER answer is better, and this file says so
|
|
6
|
+
* rather than pretending otherwise.
|
|
7
|
+
*
|
|
8
|
+
* Astro renders on the server and ships zero JavaScript by default. A component
|
|
9
|
+
* that exists only to run a `init()` effect in the browser would need a client
|
|
10
|
+
* directive (`client:load`), which means shipping a hydration runtime to boot a
|
|
11
|
+
* script tag — paying for a framework to do something HTML does natively.
|
|
12
|
+
*
|
|
13
|
+
* So this subpath gives you the tag as data, to render in your layout:
|
|
14
|
+
*
|
|
15
|
+
* ```astro
|
|
16
|
+
* ---
|
|
17
|
+
* import { snagsetTag } from "snagset/astro";
|
|
18
|
+
* const tag = snagsetTag({ host: "https://review.example.com", site: "prj_…" });
|
|
19
|
+
* ---
|
|
20
|
+
* <script src={tag.src} {...tag.attrs} defer is:inline></script>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* `is:inline` matters: without it Astro takes ownership of the script, bundles
|
|
24
|
+
* it, and rewrites the `src` — which breaks `resolveAssetBase`, because the
|
|
25
|
+
* widget finds its sibling chunks relative to its own `<script src>`.
|
|
26
|
+
*
|
|
27
|
+
* The same function is what a plain Nuxt/SvelteKit/Rails layout wants, and
|
|
28
|
+
* `useHead`/`svelte:head` consume it directly.
|
|
29
|
+
*/
|
|
30
|
+
export declare function snagsetTag(options: InitOptions): {
|
|
31
|
+
src: string;
|
|
32
|
+
attrs: Record<string, string>;
|
|
33
|
+
/** The whole thing as a string, for a template that wants one. */
|
|
34
|
+
html: string;
|
|
35
|
+
};
|
|
36
|
+
export { tagFor };
|
|
37
|
+
/**
|
|
38
|
+
* Default-exported too, so a consumer who prefers defaults has one — but the
|
|
39
|
+
* NAMED import is what the docs show.
|
|
40
|
+
*
|
|
41
|
+
* `import snagset from "snagset/astro"` would give you a callable `snagset(…)`
|
|
42
|
+
* beside the core's `snagset.init(…)`, which is two different shapes under one
|
|
43
|
+
* name. `snagsetTag` says what it returns.
|
|
44
|
+
*
|
|
45
|
+
* The same function is on the core as `snagset.tag(…)` — it touches no DOM, so
|
|
46
|
+
* it is safe to call while rendering on a server.
|
|
47
|
+
*/
|
|
48
|
+
export default snagsetTag;
|
package/dist/astro.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { tagFor } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* `snagset/astro` — D63.
|
|
4
|
+
*
|
|
5
|
+
* Astro is the case where the OTHER answer is better, and this file says so
|
|
6
|
+
* rather than pretending otherwise.
|
|
7
|
+
*
|
|
8
|
+
* Astro renders on the server and ships zero JavaScript by default. A component
|
|
9
|
+
* that exists only to run a `init()` effect in the browser would need a client
|
|
10
|
+
* directive (`client:load`), which means shipping a hydration runtime to boot a
|
|
11
|
+
* script tag — paying for a framework to do something HTML does natively.
|
|
12
|
+
*
|
|
13
|
+
* So this subpath gives you the tag as data, to render in your layout:
|
|
14
|
+
*
|
|
15
|
+
* ```astro
|
|
16
|
+
* ---
|
|
17
|
+
* import { snagsetTag } from "snagset/astro";
|
|
18
|
+
* const tag = snagsetTag({ host: "https://review.example.com", site: "prj_…" });
|
|
19
|
+
* ---
|
|
20
|
+
* <script src={tag.src} {...tag.attrs} defer is:inline></script>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* `is:inline` matters: without it Astro takes ownership of the script, bundles
|
|
24
|
+
* it, and rewrites the `src` — which breaks `resolveAssetBase`, because the
|
|
25
|
+
* widget finds its sibling chunks relative to its own `<script src>`.
|
|
26
|
+
*
|
|
27
|
+
* The same function is what a plain Nuxt/SvelteKit/Rails layout wants, and
|
|
28
|
+
* `useHead`/`svelte:head` consume it directly.
|
|
29
|
+
*/
|
|
30
|
+
export function snagsetTag(options) {
|
|
31
|
+
const { src, attrs } = tagFor(options);
|
|
32
|
+
const rendered = Object.entries(attrs)
|
|
33
|
+
.map(([k, v]) => (v === "" ? ` ${k}` : ` ${k}="${v.replace(/"/g, """)}"`))
|
|
34
|
+
.join("");
|
|
35
|
+
return { src, attrs, html: `<script src="${src}"${rendered} defer></script>` };
|
|
36
|
+
}
|
|
37
|
+
export { tagFor };
|
|
38
|
+
/**
|
|
39
|
+
* Default-exported too, so a consumer who prefers defaults has one — but the
|
|
40
|
+
* NAMED import is what the docs show.
|
|
41
|
+
*
|
|
42
|
+
* `import snagset from "snagset/astro"` would give you a callable `snagset(…)`
|
|
43
|
+
* beside the core's `snagset.init(…)`, which is two different shapes under one
|
|
44
|
+
* name. `snagsetTag` says what it returns.
|
|
45
|
+
*
|
|
46
|
+
* The same function is on the core as `snagset.tag(…)` — it touches no DOM, so
|
|
47
|
+
* it is safe to call while rendering on a server.
|
|
48
|
+
*/
|
|
49
|
+
export default snagsetTag;
|
|
50
|
+
//# sourceMappingURL=astro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.js","sourceRoot":"","sources":["../src/astro.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,YAAY,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,UAAU,CAAC,OAAoB;IAM7C,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;SAC9E,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,QAAQ,kBAAkB,EAAE,CAAC;AACjF,CAAC;AAED,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB;;;;;;;;;;GAUG;AACH,eAAe,UAAU,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { type ScriptAttr } from "./vendor/types/names";
|
|
2
|
+
/**
|
|
3
|
+
* `snagset` — the npm way in. D63, and D37's structure with D63's name.
|
|
4
|
+
*
|
|
5
|
+
* ── THIS PACKAGE DOES NOT CONTAIN THE WIDGET, AND THAT IS THE DESIGN ──
|
|
6
|
+
*
|
|
7
|
+
* It writes the script tag for you. Nothing more. The question that deserves an
|
|
8
|
+
* answer before anyone reads further is *why not just bundle it*, and the
|
|
9
|
+
* answer is measured rather than argued:
|
|
10
|
+
*
|
|
11
|
+
* | file | bytes |
|
|
12
|
+
* |---|---|
|
|
13
|
+
* | `snag.js` — the eager stub | **10 230** |
|
|
14
|
+
* | the other nine chunks, lazy | 245 945 |
|
|
15
|
+
*
|
|
16
|
+
* The widget is a lazy chunk graph. Ten kilobytes load on every page; the
|
|
17
|
+
* screenshot code, the panels, the fuzzy anchorer and the rest arrive only if
|
|
18
|
+
* somebody actually opens the toolbar. Bundling that into `snagset` would put a
|
|
19
|
+
* quarter of a megabyte into the host application's own bundle — or force them
|
|
20
|
+
* to teach their bundler about our code-splitting — and foundations §6's eager
|
|
21
|
+
* envelope, the budget the whole architecture is built around, would be a
|
|
22
|
+
* number about a file nobody loads.
|
|
23
|
+
*
|
|
24
|
+
* Three more things fall out of the shim that a bundle would break:
|
|
25
|
+
*
|
|
26
|
+
* · **No version skew.** The chunks are fetched from the server at the
|
|
27
|
+
* server's version. A bundled widget pinned in someone's `package.json` at
|
|
28
|
+
* an older version would request chunk names that build no longer has.
|
|
29
|
+
* · **Same-origin stays same-origin** (D38, A7). The bytes come from the
|
|
30
|
+
* instance the widget already talks to, so a client's CSP is one
|
|
31
|
+
* `script-src 'self'` entry and there is no third party to justify.
|
|
32
|
+
* · **The script tag is still sacred** (D19, LOCK L5) — literally, not by
|
|
33
|
+
* analogy. This package emits exactly the tag the docs tell you to paste,
|
|
34
|
+
* so a bug here cannot produce behaviour the tag cannot.
|
|
35
|
+
*
|
|
36
|
+
* ── AND NO CUSTOM ELEMENT ──
|
|
37
|
+
*
|
|
38
|
+
* D37 sketched registering one so that "Vue, Svelte and Astro need no adapter
|
|
39
|
+
* at all". That was reasoning about a *bundled* widget, where a framework needs
|
|
40
|
+
* something to render. With a shim there is nothing to render — `init()` is
|
|
41
|
+
* already framework-agnostic, and the framework subpaths are lifecycle glue
|
|
42
|
+
* over this file rather than wrappers around an element. The custom element
|
|
43
|
+
* would be bytes added to the eager stub to solve a problem this shape does not
|
|
44
|
+
* have.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Everything the script tag accepts, as an object.
|
|
48
|
+
*
|
|
49
|
+
* Keys are the attribute names minus their `data-snag-` prefix, which is the
|
|
50
|
+
* mapping `scriptAttr()` already owns — so this type cannot drift from the
|
|
51
|
+
* attribute registry, and a new attribute is accepted here the day it is
|
|
52
|
+
* registered rather than the day somebody remembers this file.
|
|
53
|
+
*/
|
|
54
|
+
export type SnagsetConfig = Partial<Record<ScriptAttr, string | number | boolean>>;
|
|
55
|
+
export interface InitOptions extends SnagsetConfig {
|
|
56
|
+
/**
|
|
57
|
+
* The instance. `https://review.example.com`.
|
|
58
|
+
*
|
|
59
|
+
* Required, and the ONE thing with no sensible default: the widget has to
|
|
60
|
+
* know which server to talk to, and guessing `location.origin` would be
|
|
61
|
+
* wrong on every site that is not itself the instance — which is all of them.
|
|
62
|
+
*/
|
|
63
|
+
host: string;
|
|
64
|
+
/** The project uid — `prj_…`, from the dashboard's Sites screen. */
|
|
65
|
+
site: string;
|
|
66
|
+
/** Where to put the tag. Defaults to `document.head`. */
|
|
67
|
+
target?: Element;
|
|
68
|
+
}
|
|
69
|
+
/** The tag this package writes, exposed for tests and for docs to render. */
|
|
70
|
+
export declare function tagFor(options: InitOptions): {
|
|
71
|
+
src: string;
|
|
72
|
+
attrs: Record<string, string>;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Start Snagset on this page.
|
|
76
|
+
*
|
|
77
|
+
* ```js
|
|
78
|
+
* import snagset from "snagset";
|
|
79
|
+
*
|
|
80
|
+
* snagset.init({ host: "https://review.example.com", site: "prj_…" });
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* ── WHY `init` AND NOT `mount` ──
|
|
84
|
+
*
|
|
85
|
+
* Because the product already had a name for this and it was not mine to
|
|
86
|
+
* choose. [02 §5](../../../specs/02-widget-runtime.md) defines
|
|
87
|
+
* **`Snagset.init(config)`** as the public JS API — *"may be called repeatedly
|
|
88
|
+
* before activation; each call merges"* — and `window.Snagset.init` /
|
|
89
|
+
* `window.Snagset.destroy` are on the live object right now.
|
|
90
|
+
*
|
|
91
|
+
* The first version of this package called it `mount`/`unmount`, which is React
|
|
92
|
+
* vocabulary imported into a package whose underlying object has said `init`
|
|
93
|
+
* since 02 draft 1. A developer reading `snagset.mount()` in their app and
|
|
94
|
+
* `Snagset.init()` in the console would reasonably conclude they were two
|
|
95
|
+
* different things. Raised by Eurico — *"init seems better than mount"* — and
|
|
96
|
+
* he is right for a better reason than either of us gave at the time.
|
|
97
|
+
*
|
|
98
|
+
* ── REPEATED CALLS MERGE, LIKE THE SPEC SAYS ──
|
|
99
|
+
*
|
|
100
|
+
* A second `init()` does not add a second tag (that would boot a second
|
|
101
|
+
* widget). It forwards the new config to `Snagset.init()`, which deep-merges
|
|
102
|
+
* it — so this behaves the way §5 documents rather than being a same-named
|
|
103
|
+
* function with different rules. When the widget has not finished loading the
|
|
104
|
+
* config is QUEUED and flushed on the script's `load` event, because a caller
|
|
105
|
+
* has no way to know whether an async script has arrived yet and "your second
|
|
106
|
+
* call was silently dropped depending on timing" is not a contract.
|
|
107
|
+
*
|
|
108
|
+
* Returns a function that removes the tag.
|
|
109
|
+
*/
|
|
110
|
+
declare function init(options: InitOptions): () => void;
|
|
111
|
+
/**
|
|
112
|
+
* Take Snagset off the page — [02 §5](../../../specs/02-widget-runtime.md)'s
|
|
113
|
+
* `Snagset.destroy()`.
|
|
114
|
+
*
|
|
115
|
+
* Delegates to the widget's OWN teardown rather than removing the script
|
|
116
|
+
* element, because removing a script element does nothing at all: the code has
|
|
117
|
+
* already run, the listeners are already bound, and the toolbar is already in
|
|
118
|
+
* the DOM. That is the difference between a teardown that works and one that
|
|
119
|
+
* appears to.
|
|
120
|
+
*/
|
|
121
|
+
declare function destroy(): void;
|
|
122
|
+
/**
|
|
123
|
+
* Is the tag on this page?
|
|
124
|
+
*
|
|
125
|
+
* `isInstalled`, not `isActive`: 02 §5's `state` is `inert | dormant | active`
|
|
126
|
+
* and "active" already means *the toolbar is open*, which is a different
|
|
127
|
+
* question with a different answer. Read `window.Snagset.state` for that one.
|
|
128
|
+
*/
|
|
129
|
+
declare function isInstalled(): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* The default export — the package's own name, carrying 02 §5's verbs.
|
|
132
|
+
*
|
|
133
|
+
* An OBJECT with `init`, not a callable. The first version made `snagset({…})`
|
|
134
|
+
* itself the call, on the argument that one verb should be the noun; with the
|
|
135
|
+
* verb named `init` that argument evaporates, and `snagset({…})` alongside
|
|
136
|
+
* `snagset.init({…})` would be two ways to do one thing in a brand-new API.
|
|
137
|
+
* This is the shape every SDK a developer has installed before this one has —
|
|
138
|
+
* `posthog.init`, `Sentry.init`, `mixpanel.init` — and now also the shape the
|
|
139
|
+
* widget's own global has, which matters more.
|
|
140
|
+
*/
|
|
141
|
+
export declare const snagset: {
|
|
142
|
+
init: typeof init;
|
|
143
|
+
destroy: typeof destroy;
|
|
144
|
+
isInstalled: typeof isInstalled;
|
|
145
|
+
tag: typeof tagFor;
|
|
146
|
+
};
|
|
147
|
+
export { init, destroy, isInstalled };
|
|
148
|
+
export default snagset;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { GLOBALS, scriptAttr } from "./vendor/types/names.js";
|
|
2
|
+
const INSTALLED = "__snagset_npm_installed__";
|
|
3
|
+
/** The tag this package writes, exposed for tests and for docs to render. */
|
|
4
|
+
export function tagFor(options) {
|
|
5
|
+
const host = String(options.host).replace(/\/+$/, "");
|
|
6
|
+
const attrs = {};
|
|
7
|
+
for (const [key, value] of Object.entries(options)) {
|
|
8
|
+
if (key === "target" || key === "host" || value === undefined || value === null)
|
|
9
|
+
continue;
|
|
10
|
+
// `false` is dropped rather than written as the string "false". Every one
|
|
11
|
+
// of these attributes is read by `loader.ts` as a STRING, and the presence
|
|
12
|
+
// of `data-snag-disabled="false"` reads as "disabled" to anything doing a
|
|
13
|
+
// has-attribute check — which is the shape of kill switch §6 uses.
|
|
14
|
+
if (value === false)
|
|
15
|
+
continue;
|
|
16
|
+
attrs[scriptAttr(key)] = value === true ? "" : String(value);
|
|
17
|
+
}
|
|
18
|
+
// `host` is both the config and the source, exactly as a hand-pasted tag has
|
|
19
|
+
// it: the tag's own `src` is what `resolveAssetBase` falls back to, so the
|
|
20
|
+
// chunks resolve with no extra configuration.
|
|
21
|
+
attrs[scriptAttr("host")] = host;
|
|
22
|
+
return { src: `${host}/snag.js`, attrs };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Start Snagset on this page.
|
|
26
|
+
*
|
|
27
|
+
* ```js
|
|
28
|
+
* import snagset from "snagset";
|
|
29
|
+
*
|
|
30
|
+
* snagset.init({ host: "https://review.example.com", site: "prj_…" });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* ── WHY `init` AND NOT `mount` ──
|
|
34
|
+
*
|
|
35
|
+
* Because the product already had a name for this and it was not mine to
|
|
36
|
+
* choose. [02 §5](../../../specs/02-widget-runtime.md) defines
|
|
37
|
+
* **`Snagset.init(config)`** as the public JS API — *"may be called repeatedly
|
|
38
|
+
* before activation; each call merges"* — and `window.Snagset.init` /
|
|
39
|
+
* `window.Snagset.destroy` are on the live object right now.
|
|
40
|
+
*
|
|
41
|
+
* The first version of this package called it `mount`/`unmount`, which is React
|
|
42
|
+
* vocabulary imported into a package whose underlying object has said `init`
|
|
43
|
+
* since 02 draft 1. A developer reading `snagset.mount()` in their app and
|
|
44
|
+
* `Snagset.init()` in the console would reasonably conclude they were two
|
|
45
|
+
* different things. Raised by Eurico — *"init seems better than mount"* — and
|
|
46
|
+
* he is right for a better reason than either of us gave at the time.
|
|
47
|
+
*
|
|
48
|
+
* ── REPEATED CALLS MERGE, LIKE THE SPEC SAYS ──
|
|
49
|
+
*
|
|
50
|
+
* A second `init()` does not add a second tag (that would boot a second
|
|
51
|
+
* widget). It forwards the new config to `Snagset.init()`, which deep-merges
|
|
52
|
+
* it — so this behaves the way §5 documents rather than being a same-named
|
|
53
|
+
* function with different rules. When the widget has not finished loading the
|
|
54
|
+
* config is QUEUED and flushed on the script's `load` event, because a caller
|
|
55
|
+
* has no way to know whether an async script has arrived yet and "your second
|
|
56
|
+
* call was silently dropped depending on timing" is not a contract.
|
|
57
|
+
*
|
|
58
|
+
* Returns a function that removes the tag.
|
|
59
|
+
*/
|
|
60
|
+
function init(options) {
|
|
61
|
+
// SSR guard first. This package is imported at module scope in a Next.js or
|
|
62
|
+
// Nuxt component, so it IS evaluated on the server, and touching `document`
|
|
63
|
+
// there throws before anything can explain itself.
|
|
64
|
+
if (typeof document === "undefined")
|
|
65
|
+
return () => { };
|
|
66
|
+
const w = window;
|
|
67
|
+
if (w[INSTALLED]) {
|
|
68
|
+
merge(options);
|
|
69
|
+
return () => { };
|
|
70
|
+
}
|
|
71
|
+
if (!options.host)
|
|
72
|
+
throw new Error("snagset: `host` is required — the URL of your Snagset instance.");
|
|
73
|
+
if (!options.site)
|
|
74
|
+
throw new Error("snagset: `site` is required — the project uid, which looks like prj_….");
|
|
75
|
+
const { src, attrs } = tagFor(options);
|
|
76
|
+
const el = document.createElement("script");
|
|
77
|
+
el.src = src;
|
|
78
|
+
el.defer = true;
|
|
79
|
+
for (const [name, value] of Object.entries(attrs))
|
|
80
|
+
el.setAttribute(name, value);
|
|
81
|
+
el.addEventListener("load", flush, { once: true });
|
|
82
|
+
w[INSTALLED] = true;
|
|
83
|
+
(options.target ?? document.head).appendChild(el);
|
|
84
|
+
return () => {
|
|
85
|
+
el.remove();
|
|
86
|
+
w[INSTALLED] = false;
|
|
87
|
+
queued.length = 0;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Config handed to `init()` after the first call, before the widget exists. */
|
|
91
|
+
const queued = [];
|
|
92
|
+
function widget() {
|
|
93
|
+
if (typeof window === "undefined")
|
|
94
|
+
return undefined;
|
|
95
|
+
return window[GLOBALS.api];
|
|
96
|
+
}
|
|
97
|
+
function merge(options) {
|
|
98
|
+
const api = widget();
|
|
99
|
+
// `target` is OURS — where to put the tag — and means nothing to the widget.
|
|
100
|
+
const { target: _drop, ...config } = options;
|
|
101
|
+
if (api?.init)
|
|
102
|
+
api.init(config);
|
|
103
|
+
else
|
|
104
|
+
queued.push(options);
|
|
105
|
+
}
|
|
106
|
+
function flush() {
|
|
107
|
+
const pending = queued.splice(0);
|
|
108
|
+
for (const options of pending)
|
|
109
|
+
merge(options);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Take Snagset off the page — [02 §5](../../../specs/02-widget-runtime.md)'s
|
|
113
|
+
* `Snagset.destroy()`.
|
|
114
|
+
*
|
|
115
|
+
* Delegates to the widget's OWN teardown rather than removing the script
|
|
116
|
+
* element, because removing a script element does nothing at all: the code has
|
|
117
|
+
* already run, the listeners are already bound, and the toolbar is already in
|
|
118
|
+
* the DOM. That is the difference between a teardown that works and one that
|
|
119
|
+
* appears to.
|
|
120
|
+
*/
|
|
121
|
+
function destroy() {
|
|
122
|
+
if (typeof window === "undefined")
|
|
123
|
+
return;
|
|
124
|
+
widget()?.destroy?.();
|
|
125
|
+
window[INSTALLED] = false;
|
|
126
|
+
queued.length = 0;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Is the tag on this page?
|
|
130
|
+
*
|
|
131
|
+
* `isInstalled`, not `isActive`: 02 §5's `state` is `inert | dormant | active`
|
|
132
|
+
* and "active" already means *the toolbar is open*, which is a different
|
|
133
|
+
* question with a different answer. Read `window.Snagset.state` for that one.
|
|
134
|
+
*/
|
|
135
|
+
function isInstalled() {
|
|
136
|
+
return typeof window !== "undefined" && !!window[INSTALLED];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The default export — the package's own name, carrying 02 §5's verbs.
|
|
140
|
+
*
|
|
141
|
+
* An OBJECT with `init`, not a callable. The first version made `snagset({…})`
|
|
142
|
+
* itself the call, on the argument that one verb should be the noun; with the
|
|
143
|
+
* verb named `init` that argument evaporates, and `snagset({…})` alongside
|
|
144
|
+
* `snagset.init({…})` would be two ways to do one thing in a brand-new API.
|
|
145
|
+
* This is the shape every SDK a developer has installed before this one has —
|
|
146
|
+
* `posthog.init`, `Sentry.init`, `mixpanel.init` — and now also the shape the
|
|
147
|
+
* widget's own global has, which matters more.
|
|
148
|
+
*/
|
|
149
|
+
export const snagset = { init, destroy, isInstalled, tag: tagFor };
|
|
150
|
+
export { init, destroy, isInstalled };
|
|
151
|
+
export default snagset;
|
|
152
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { type InitOptions } from "./index.ts";
|
|
3
|
+
/**
|
|
4
|
+
* `snagset/next` — D67.
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* // app/layout.tsx
|
|
8
|
+
* import { Snagset } from "snagset/next";
|
|
9
|
+
*
|
|
10
|
+
* <body>
|
|
11
|
+
* {children}
|
|
12
|
+
* <Snagset host="https://review.example.com" site="prj_…" />
|
|
13
|
+
* </body>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ── A SERVER COMPONENT, NOT A CLIENT EFFECT ──
|
|
17
|
+
*
|
|
18
|
+
* `snagset/react` runs `init()` in a `useEffect`, because a generic React tree
|
|
19
|
+
* offers nothing better. Next offers something better: this component has no
|
|
20
|
+
* hooks and no `"use client"`, so in the App Router it renders on the server
|
|
21
|
+
* and the script tag arrives IN the HTML — zero client JavaScript, zero
|
|
22
|
+
* hydration, byte-for-byte the tag the docs tell you to paste. It degrades
|
|
23
|
+
* cleanly too: in the Pages Router (or any client tree) it renders the same
|
|
24
|
+
* element and React mounts it, which boots the script just the same.
|
|
25
|
+
*
|
|
26
|
+
* ── WHY THERE IS NO `withSnagset(nextConfig)` WRAPPER ──
|
|
27
|
+
*
|
|
28
|
+
* Vercel's own toolbar ships one because their setup needs `next.config`
|
|
29
|
+
* rewrites to proxy toolbar traffic through the deployment. Ours needs no
|
|
30
|
+
* proxy: the widget is served by the studio's instance and talks back to that
|
|
31
|
+
* same origin (D38). A config wrapper would exist to look like theirs, not to
|
|
32
|
+
* do anything — the component is the whole install.
|
|
33
|
+
*
|
|
34
|
+
* ── PROPS, OR `NEXT_PUBLIC_SNAGSET_*` ──
|
|
35
|
+
*
|
|
36
|
+
* A bare `<Snagset />` reads `NEXT_PUBLIC_SNAGSET_HOST` and
|
|
37
|
+
* `NEXT_PUBLIC_SNAGSET_SITE`, so per-environment values live where a Next
|
|
38
|
+
* project already keeps them (`.env.local`, the host's env UI) instead of in
|
|
39
|
+
* the layout. The names carry Next's own public prefix — deliberately NOT bare
|
|
40
|
+
* `SNAGSET_*`, which is the server's reserved namespace (08 §4): an instance
|
|
41
|
+
* colocated with the app must never find widget config in variables it treats
|
|
42
|
+
* as its own and refuses to boot on.
|
|
43
|
+
*
|
|
44
|
+
* Missing config renders NOTHING rather than throwing: this sits in the root
|
|
45
|
+
* layout, and a review widget that can take down every page of the site over
|
|
46
|
+
* an unset env var would violate the product's oldest rule. The dev-only
|
|
47
|
+
* console line is the diagnostic.
|
|
48
|
+
*/
|
|
49
|
+
export type SnagsetProps = Partial<InitOptions>;
|
|
50
|
+
export declare function Snagset(props?: SnagsetProps): ReturnType<typeof createElement> | null;
|
|
51
|
+
export default Snagset;
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { tagFor } from "./index.js";
|
|
3
|
+
export function Snagset(props = {}) {
|
|
4
|
+
const hasEnv = typeof process !== "undefined" && !!process.env;
|
|
5
|
+
const host = props.host ?? (hasEnv ? process.env.NEXT_PUBLIC_SNAGSET_HOST : undefined);
|
|
6
|
+
const site = props.site ?? (hasEnv ? process.env.NEXT_PUBLIC_SNAGSET_SITE : undefined);
|
|
7
|
+
if (!host || !site) {
|
|
8
|
+
if (hasEnv && process.env.NODE_ENV !== "production") {
|
|
9
|
+
console.warn("snagset/next: rendering nothing — set `host` and `site` props, or NEXT_PUBLIC_SNAGSET_HOST and NEXT_PUBLIC_SNAGSET_SITE.");
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const { src, attrs } = tagFor({ ...props, host, site });
|
|
14
|
+
return createElement("script", { src, defer: true, ...attrs });
|
|
15
|
+
}
|
|
16
|
+
export default Snagset;
|
|
17
|
+
//# sourceMappingURL=next.js.map
|
package/dist/next.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.js","sourceRoot":"","sources":["../src/next.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,MAAM,EAAoB,MAAM,YAAY,CAAC;AAkEtD,MAAM,UAAU,OAAO,CAAC,QAAsB,EAAE;IAC9C,MAAM,MAAM,GAAG,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;IAC/D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAQ,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAQ,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,IAAI,MAAM,IAAI,OAAQ,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YACrD,OAAO,CAAC,IAAI,CACV,0HAA0H,CAC3H,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,OAAO,aAAa,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,eAAe,OAAO,CAAC"}
|