@satanwagen/reviewkit 0.1.2 → 0.1.3
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/CHANGELOG.md +105 -0
- package/README.md +134 -63
- package/dist/activate.cjs +150 -0
- package/dist/activate.cjs.map +1 -0
- package/dist/activate.d.cts +59 -0
- package/dist/activate.d.ts +59 -0
- package/dist/activate.js +21 -0
- package/dist/activate.js.map +1 -0
- package/dist/{chunk-4YNLMSCK.js → chunk-DG6O5XIC.js} +36 -10
- package/dist/chunk-DG6O5XIC.js.map +1 -0
- package/dist/chunk-ETAGGIDN.js +119 -0
- package/dist/chunk-ETAGGIDN.js.map +1 -0
- package/dist/{chunk-YWBFAV57.js → chunk-NSXRFM75.js} +853 -1544
- package/dist/chunk-NSXRFM75.js.map +1 -0
- package/dist/chunk-Q7U43PXE.js +36 -0
- package/dist/chunk-Q7U43PXE.js.map +1 -0
- package/dist/client/index.cjs +4530 -4237
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +49 -3
- package/dist/client/index.js.map +1 -1
- package/dist/effects-EWKHKUDP.js +851 -0
- package/dist/effects-EWKHKUDP.js.map +1 -0
- package/dist/index-BUSmAweg.d.ts +77 -0
- package/dist/index-Dl2Fl0qr.d.cts +77 -0
- package/dist/index.cjs +4544 -4256
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +35 -3
- package/dist/index.js.map +1 -1
- package/dist/lazy.cjs +8988 -0
- package/dist/lazy.cjs.map +1 -0
- package/dist/lazy.d.cts +7 -0
- package/dist/lazy.d.ts +7 -0
- package/dist/lazy.js +8 -0
- package/dist/lazy.js.map +1 -0
- package/dist/next.cjs +8989 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +4 -0
- package/dist/next.d.ts +4 -0
- package/dist/next.js +9 -0
- package/dist/next.js.map +1 -0
- package/dist/schema.cjs +53 -16
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +20 -5
- package/dist/schema.d.ts +20 -5
- package/dist/schema.js +30 -2
- package/dist/schema.js.map +1 -1
- package/package.json +21 -8
- package/cli/emblema-sync.mjs +0 -675
- package/dist/chunk-4YNLMSCK.js.map +0 -1
- package/dist/chunk-YWBFAV57.js.map +0 -1
- package/dist/index-BbucFgZi.d.ts +0 -49
- package/dist/index-CBlJrKkm.d.cts +0 -49
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.3 — 2026-09-02
|
|
4
|
+
|
|
5
|
+
Defaults tightened so a production build behaves the way its code reads.
|
|
6
|
+
Several changes are breaking for hosts that relied on the old conveniences;
|
|
7
|
+
each has a one-line migration below.
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- **No activation without a token.** Previously an unset `token` meant "any
|
|
12
|
+
`?review=x` turns the layer on" (plus the `reviewkit` localStorage flag).
|
|
13
|
+
Now, without `token`, nothing activates. Migration: pass `token`. For a
|
|
14
|
+
reviewer-less dev loop pass `devAutoOn`.
|
|
15
|
+
- **No `NODE_ENV === 'development'` auto-on.** The library never reads the
|
|
16
|
+
host's NODE_ENV any more. Migration:
|
|
17
|
+
`devAutoOn={import.meta.env.DEV}` (Vite) or
|
|
18
|
+
`devAutoOn={process.env.NODE_ENV === 'development'}` (Next/webpack).
|
|
19
|
+
- **No default `syncUrl`.** The former default
|
|
20
|
+
`wss://agropolio.fucking.style/rk-sync` pointed at an expired domain. Sync
|
|
21
|
+
now runs only when **both** `syncUrl` and `syncToken` are set; otherwise
|
|
22
|
+
the layer is local-only, no socket. Migration: set `syncUrl` explicitly.
|
|
23
|
+
Same for the CLI: `reviewkit-emblema init --sync-url …` (the room bridge
|
|
24
|
+
stays disabled until both `--sync-url` and `--sync-token` are configured).
|
|
25
|
+
- **CLI moved to `@satanwagen/reviewkit-cli`.** The main package no longer
|
|
26
|
+
ships a `bin`, so a host `npm install` does not pull the Emblema agent.
|
|
27
|
+
Migration: `npx @satanwagen/reviewkit-cli init …` / `serve` (the command
|
|
28
|
+
inside is still `reviewkit-emblema`).
|
|
29
|
+
- **`emblema.repoPath` is optional.** The repo path is Emblema's local
|
|
30
|
+
knowledge (advertised in its `/intake/ping` answer, and present in
|
|
31
|
+
`.reviewkit/emblema-sync.json` for the agent) and no longer belongs in host
|
|
32
|
+
code. `emblema={{}}` now enables the bridge. The v1 dispatch payload keeps
|
|
33
|
+
`v: 1`, but `repoPath` became optional: an intake with a strict "required"
|
|
34
|
+
validator must relax it and fall back to its active project.
|
|
35
|
+
- **Effect sounds moved off the expired domain.** `/ondro` now fetches
|
|
36
|
+
`rick.mp3` from `https://2-59-219-26.sslip.io/rk-assets` (the same VPS,
|
|
37
|
+
reached through a sslip.io hostname so no domain renewal can break it).
|
|
38
|
+
New prop `fxAssetsUrl` overrides the base for self-hosting. Add the origin
|
|
39
|
+
to CSP `media-src` if you send a policy.
|
|
40
|
+
- Legacy activation keys (`review-kit:active`, `reviewkit`) are still cleaned
|
|
41
|
+
up on load but never consulted.
|
|
42
|
+
- **Emblema intake must accept a batch without `repoPath`.** An Emblema build
|
|
43
|
+
that still validates it as required answers HTTP 400; the panel now shows
|
|
44
|
+
that as `emblema rejected` (new push status) instead of `emblema offline`.
|
|
45
|
+
- **CLI:** a `.reviewkit/emblema-sync.json` written by 0.1.2 still carries the
|
|
46
|
+
retired default sync host. `serve` now detects it, logs a re-init hint and
|
|
47
|
+
runs with the bridge disabled instead of reconnecting forever; the local
|
|
48
|
+
`/status` endpoint reports `realtime.state: 'off'` whenever either sync
|
|
49
|
+
setting is missing (previously it said `connecting` with no URL).
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- `enabled?: boolean` master switch (default `true`) so a wrapper can write
|
|
54
|
+
`<ReviewKit enabled={Boolean(TOKEN)} token={TOKEN} />` instead of
|
|
55
|
+
`if (!TOKEN) return null`.
|
|
56
|
+
- `devAutoOn?: boolean` — host-controlled "always on for this load". Still
|
|
57
|
+
subject to `enabled`, `allowedHosts` and `?review=off`.
|
|
58
|
+
- **`@satanwagen/reviewkit/next`** — `'use client'` component: render it from
|
|
59
|
+
a server `layout.tsx`; it evaluates URL + sticky session after mount and
|
|
60
|
+
only then dynamically imports the core. `examples/next-demo` shows it
|
|
61
|
+
together with the CSP headers.
|
|
62
|
+
- **`@satanwagen/reviewkit/lazy`** — the same code-split component for Vite
|
|
63
|
+
and friends; no more hand-rolled `React.lazy` + `Suspense` gate.
|
|
64
|
+
- **`@satanwagen/reviewkit/activate`** — `shouldActivate({ token, enabled,
|
|
65
|
+
allowedHosts, devAutoOn })`, React-free, ~200 B. The supported way for a
|
|
66
|
+
host to gate its own lazy import; the sessionStorage format stays private.
|
|
67
|
+
- One-time console warning when a URL token activates the layer on a
|
|
68
|
+
non-loopback hostname with no `allowedHosts` (see README "Production
|
|
69
|
+
recipe").
|
|
70
|
+
- The `/` command effects (~40 KB of source) load as a separate chunk on
|
|
71
|
+
first use instead of shipping in the core chunk.
|
|
72
|
+
- `probeEmblema()` returns `{ port, repoPath? }`; `pingEmblema()` stays as a
|
|
73
|
+
port-only wrapper.
|
|
74
|
+
- Activation matrix tests (`test/activate.test.mjs`) run against `dist/`
|
|
75
|
+
(`npm test` builds first via `pretest`).
|
|
76
|
+
- A one-time console warning when `?review=<x>` does not match the configured
|
|
77
|
+
token (kept from 0.1.2, now also emitted by `shouldActivate`).
|
|
78
|
+
- `/lazy` and `/next` are effectively ESM entries: the CJS builds exist for
|
|
79
|
+
`require()` consumers but inline the core (esbuild has no CJS code
|
|
80
|
+
splitting). Every modern bundler picks the ESM build.
|
|
81
|
+
- README: activation-state table, CSP requirements, production recipe.
|
|
82
|
+
|
|
83
|
+
### Bundle audit (unminified ESM, `dist/`)
|
|
84
|
+
|
|
85
|
+
| Chunk | 0.1.2 | 0.1.3 |
|
|
86
|
+
| --- | ---: | ---: |
|
|
87
|
+
| core client chunk | 313 KB | 267 KB |
|
|
88
|
+
| effects (lazy, on first `/command`) | in core | 41 KB |
|
|
89
|
+
| `activate` entry | — | 0.2 KB |
|
|
90
|
+
|
|
91
|
+
Largest remaining inputs: `styles.ts` (53 KB), `Panel.tsx` (46 KB). Schema
|
|
92
|
+
validation and IndexedDB persistence are under 10 KB each and were not the
|
|
93
|
+
weight.
|
|
94
|
+
|
|
95
|
+
## 0.1.2
|
|
96
|
+
|
|
97
|
+
- Delete endpoint for Emblema findings, idempotent for unknown ids.
|
|
98
|
+
|
|
99
|
+
## 0.1.1
|
|
100
|
+
|
|
101
|
+
- `reopen` status verb; realtime health in local state.
|
|
102
|
+
|
|
103
|
+
## 0.1.0
|
|
104
|
+
|
|
105
|
+
- First scoped release as `@satanwagen/reviewkit`.
|
package/README.md
CHANGED
|
@@ -13,35 +13,105 @@ npm install @satanwagen/reviewkit
|
|
|
13
13
|
## Two-line integration
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
|
-
import { ReviewKit } from '@satanwagen/reviewkit/
|
|
16
|
+
import { ReviewKit } from '@satanwagen/reviewkit/lazy'; // Vite, CRA, Remix …
|
|
17
|
+
// import { ReviewKit } from '@satanwagen/reviewkit/next'; // Next.js App Router ('use client' built in)
|
|
17
18
|
|
|
18
19
|
// anywhere in your tree, rendered once:
|
|
19
|
-
<ReviewKit />
|
|
20
|
+
<ReviewKit token={import.meta.env.VITE_REVIEW_TOKEN} />
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
That's it. The component renders `null` unless review mode is active, and all UI lives in a shadow root appended to `document.body` — your CSS never leaks in, reviewkit's CSS never leaks out. Safe to ship in production builds.
|
|
23
24
|
|
|
25
|
+
Three entries, same props:
|
|
26
|
+
|
|
27
|
+
| Entry | What it is | Use when |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `@satanwagen/reviewkit/lazy` | Component that decides after mount (URL + sticky session) and only then `import()`s the core chunk. | Vite / CRA / Remix / any client bundler. Visitors without a review link never download the core. |
|
|
30
|
+
| `@satanwagen/reviewkit/next` | Same, with `'use client'` baked into the built file so a server `layout.tsx` can render it directly. | Next.js App Router. See `examples/next-demo`. |
|
|
31
|
+
| `@satanwagen/reviewkit/client` | The core itself, eager. | You already have your own lazy boundary, or you want everything in one chunk. |
|
|
32
|
+
|
|
33
|
+
The code split only exists in the ESM builds (what Vite, Next, webpack 5 pick); the CJS files of `/lazy` and `/next` inline the core for `require()` consumers.
|
|
34
|
+
|
|
35
|
+
And a React-free helper for hosts that gate their own lazy import:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { shouldActivate } from '@satanwagen/reviewkit/activate'; // ~200 B, no React, SSR-safe (false)
|
|
39
|
+
if (shouldActivate({ token: TOKEN })) void import('./reviewHost');
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`shouldActivate` is the supported API for "should this page load show review?"; the sessionStorage format behind the sticky session is private and may change. Its only side effect: `?review=off` clears the sticky opt-in.
|
|
43
|
+
|
|
24
44
|
### Props
|
|
25
45
|
|
|
26
46
|
| Prop | Type | Description |
|
|
27
47
|
| --- | --- | --- |
|
|
28
|
-
| `token` | `string` |
|
|
29
|
-
| `
|
|
48
|
+
| `token` | `string` | **Required for any activation.** The layer turns on only via `?review=<token>` (or the per-tab session such a link starts). Without it nothing activates, whatever the URL says. |
|
|
49
|
+
| `enabled` | `boolean` | Master switch, default `true`. `false` renders nothing, installs nothing. Lets you write `enabled={Boolean(TOKEN)}` instead of `if (!TOKEN) return null`. |
|
|
50
|
+
| `devAutoOn` | `boolean` | Force the layer on for this page load, no link needed. Host-controlled dev convenience (`devAutoOn={import.meta.env.DEV}`); the library never reads `NODE_ENV`. Still subject to `enabled`, `allowedHosts` and `?review=off`. |
|
|
51
|
+
| `allowedHosts` | `string[]` | Hostname allowlist with `*` wildcards (`['localhost', '*.staging.example']`). Outside it nothing activates, token or not. Unset = any host (with a one-time console warning on URL activation on a public host). |
|
|
52
|
+
| `syncUrl` | `string` | WebSocket endpoint of your sync server (`realtime/server`), e.g. `wss://your-site.tld/rk-sync`. **No default.** |
|
|
53
|
+
| `syncToken` | `string` | Shared secret of the sync server. Live collaboration (presence, cursors, shared items) runs only when **both** `syncUrl` and `syncToken` are set; otherwise solo mode, no network. Public in the bundle by nature — a gate against casual visitors, not a secret. |
|
|
54
|
+
| `emblema` | `{ repoPath?: string; port?: number }` | Enables the Emblema bridge (`emblema={{}}` is enough). See "Pairing with Emblema". |
|
|
30
55
|
| `defaultAuthor` | `string` | Pre-fills the reviewer name. |
|
|
56
|
+
| `fxAssetsUrl` | `string` | Base URL for the `/` effect soundtracks (`<base>/rick.mp3` for `/ondro`). Default: the public ReviewKit assets host `https://2-59-219-26.sslip.io/rk-assets`. Override to self-host. Needs `media-src` in CSP. |
|
|
31
57
|
| `position` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | Initial corner of the floating badge (default `bottom-right`; draggable, persisted). |
|
|
32
58
|
| `storageKey` | `string` | Persistence key (default: page origin). |
|
|
33
59
|
| `onSessionChange` | `(session: ReviewSession) => void` | Called (debounced) whenever the session changes. |
|
|
34
60
|
|
|
35
61
|
## How the layer activates
|
|
36
62
|
|
|
37
|
-
|
|
63
|
+
Rules, in order; the first that applies wins. No environment sniffing anywhere.
|
|
64
|
+
|
|
65
|
+
| # | Condition | Result |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| 1 | `enabled={false}` | off |
|
|
68
|
+
| 2 | hostname not in `allowedHosts` (when given) | off |
|
|
69
|
+
| 3 | `?review=off` in the URL | off, and the in-tab sticky session is cleared |
|
|
70
|
+
| 4 | `devAutoOn` | **on** |
|
|
71
|
+
| 5 | no `token` configured | off (`?review=x` logs a one-time warning and stays off) |
|
|
72
|
+
| 6 | `?review=<token>` matches | **on**, starts the per-tab sticky session |
|
|
73
|
+
| 7 | `?review=<something else>` | off |
|
|
74
|
+
| 8 | valid sticky session (same token, < 4 h since last active load) | **on**, expiry renewed |
|
|
75
|
+
| 9 | otherwise | off |
|
|
76
|
+
|
|
77
|
+
Activation-state matrix for the common cases:
|
|
78
|
+
|
|
79
|
+
| token | build | URL | sticky | result |
|
|
80
|
+
| --- | --- | --- | --- | --- |
|
|
81
|
+
| unset | dev or prod | none / `?review=x` | any | off |
|
|
82
|
+
| unset | dev, with `devAutoOn` | any except `off` | any | on |
|
|
83
|
+
| set | prod | `?review=<token>` | — | on (sticky starts) |
|
|
84
|
+
| set | prod | none | valid | on |
|
|
85
|
+
| set | prod | none | none / expired / other token | off |
|
|
86
|
+
| set | prod | `?review=wrong` | valid | off |
|
|
87
|
+
| set | any | `?review=off` | valid | off, sticky cleared |
|
|
88
|
+
|
|
89
|
+
The sticky session is per tab (`sessionStorage`), bound to the token and renewed on every active load, so a reviewer who opened a review link keeps reviewing across full page loads on other routes. The gear menu's **Keep review on in this tab** toggles it explicitly; **End review** (or `?review=off`) clears everything and returns the clean site. Leftover keys from pre-0.1.3 versions (`review-kit:active`, `reviewkit`) are removed on load and never consulted.
|
|
90
|
+
|
|
91
|
+
### Production recipe
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<ReviewKit
|
|
95
|
+
enabled={Boolean(TOKEN)}
|
|
96
|
+
token={TOKEN}
|
|
97
|
+
allowedHosts={['staging.your-site.tld', '*.preview.your-site.tld']}
|
|
98
|
+
syncUrl="wss://your-site.tld/rk-sync"
|
|
99
|
+
syncToken={SYNC_TOKEN}
|
|
100
|
+
/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`token` + `allowedHosts` together: the token says *who*, the allowlist says *where*. Without `allowedHosts` a URL activation on a non-loopback host logs one console warning. `TOKEN`/`SYNC_TOKEN` are public in the bundle by design; use the `/lazy` or `/next` entry so they at least live in the review chunk, not the main one.
|
|
38
104
|
|
|
39
|
-
|
|
40
|
-
2. The reviewer's explicit **“Keep on in this tab”** toggle in the panel — a per-tab, token-bound opt-in that expires after ~4 hours. Nothing persists silently.
|
|
105
|
+
### CSP
|
|
41
106
|
|
|
42
|
-
|
|
107
|
+
If the host sends a `Content-Security-Policy`, `connect-src` must allow:
|
|
43
108
|
|
|
44
|
-
|
|
109
|
+
- the sync server origin, as `wss://your-site.tld` (or the `ws:` origin in dev),
|
|
110
|
+
- `http://127.0.0.1:*` when the `emblema` prop is set (intake ports 48752–48755, pairing agent 48770–48773).
|
|
111
|
+
|
|
112
|
+
and `media-src` must allow `https://2-59-219-26.sslip.io` (or your `fxAssetsUrl`) for the `/` effect sounds; without it the effects run silent.
|
|
113
|
+
|
|
114
|
+
With `connect-src 'self'` alone the WebSocket is blocked and the layer silently stays local-only. `examples/next-demo/next.config.ts` has a complete header.
|
|
45
115
|
|
|
46
116
|
## Using it (reviewer flow)
|
|
47
117
|
|
|
@@ -121,70 +191,64 @@ Programmatic helpers are exported too: `parseSessionJson` / `parseSession` (vali
|
|
|
121
191
|
// src/main.tsx
|
|
122
192
|
import { StrictMode } from 'react';
|
|
123
193
|
import { createRoot } from 'react-dom/client';
|
|
124
|
-
import { ReviewKit } from '@satanwagen/reviewkit/
|
|
194
|
+
import { ReviewKit } from '@satanwagen/reviewkit/lazy';
|
|
125
195
|
import { App } from './App';
|
|
126
196
|
|
|
127
197
|
createRoot(document.getElementById('root')!).render(
|
|
128
198
|
<StrictMode>
|
|
129
199
|
<App />
|
|
130
|
-
<ReviewKit token={import.meta.env.VITE_REVIEW_TOKEN} />
|
|
200
|
+
<ReviewKit token={import.meta.env.VITE_REVIEW_TOKEN} devAutoOn={import.meta.env.DEV} />
|
|
131
201
|
</StrictMode>,
|
|
132
202
|
);
|
|
133
203
|
```
|
|
134
204
|
|
|
135
|
-
No Vite config changes are needed — `reviewkit` ships prebuilt ESM/CJS with types. In dev the layer is
|
|
205
|
+
No Vite config changes are needed — `reviewkit` ships prebuilt ESM/CJS with types. In `vite dev` the layer is on thanks to `devAutoOn`; in production reviewers open `https://your-site.tld/?review=<token>`, and visitors without the link never download the review chunk (the `/lazy` entry splits it automatically).
|
|
136
206
|
|
|
137
|
-
Until the npm release lands you can also depend on it via a file path — this is exactly what the
|
|
207
|
+
Until the npm release lands you can also depend on it via a file path — this is exactly what the demos do:
|
|
138
208
|
|
|
139
209
|
```jsonc
|
|
140
210
|
// package.json
|
|
141
211
|
"dependencies": { "@satanwagen/reviewkit": "file:../path/to/ReviewKit" }
|
|
142
212
|
```
|
|
143
213
|
|
|
144
|
-
npm symlinks the folder, so after editing reviewkit run `npm run build` in it and restart/reload Vite.
|
|
214
|
+
npm symlinks the folder, so after editing reviewkit run `npm run build` in it and restart/reload Vite. With React 18 add `resolve: { dedupe: ['react', 'react-dom'] }` to `vite.config.ts` so the linked package's own `node_modules` cannot introduce a second React. Run `npm run build` in `ReviewKit/` at least once before installing, since the package resolves to `dist/`.
|
|
145
215
|
|
|
146
|
-
###
|
|
216
|
+
### Gating a hand-rolled lazy boundary
|
|
147
217
|
|
|
148
|
-
|
|
218
|
+
If you keep your own wrapper (for example to keep `syncToken` in a chunk you control), gate it with `shouldActivate` instead of reading ReviewKit's storage keys:
|
|
149
219
|
|
|
150
|
-
```
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
220
|
+
```tsx
|
|
221
|
+
// ReviewLayer.tsx
|
|
222
|
+
import { lazy, Suspense, useEffect, useState } from 'react';
|
|
223
|
+
import { shouldActivate } from '@satanwagen/reviewkit/activate';
|
|
154
224
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// dedupe guarantees a single React instance (Fable is on React 18):
|
|
158
|
-
resolve: { dedupe: ['react', 'react-dom'] }
|
|
159
|
-
```
|
|
225
|
+
const TOKEN = import.meta.env.VITE_REVIEW_TOKEN;
|
|
226
|
+
const ReviewHost = lazy(() => import('./reviewHost')); // renders <ReviewKit token={TOKEN} syncUrl=… syncToken=… />
|
|
160
227
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const TOKEN = 'agropolio'
|
|
166
|
-
const SESSION_KEY = 'review-kit:active' // same key reviewkit uses internally
|
|
167
|
-
|
|
168
|
-
function shouldLoad(): boolean {
|
|
169
|
-
try {
|
|
170
|
-
const param = new URLSearchParams(window.location.search).get('review')
|
|
171
|
-
if (param === 'off') { window.sessionStorage.removeItem(SESSION_KEY); return false }
|
|
172
|
-
if (param === TOKEN) return true
|
|
173
|
-
return window.sessionStorage.getItem(SESSION_KEY) === '1' // survive reloads in-tab
|
|
174
|
-
} catch { return false }
|
|
228
|
+
export function ReviewLayer() {
|
|
229
|
+
const [load, setLoad] = useState(false);
|
|
230
|
+
useEffect(() => { if (shouldActivate({ token: TOKEN })) setLoad(true); }, []);
|
|
231
|
+
return load ? <Suspense fallback={null}><ReviewHost /></Suspense> : null;
|
|
175
232
|
}
|
|
233
|
+
```
|
|
176
234
|
|
|
177
|
-
|
|
235
|
+
## Integrating into a Next.js site (App Router)
|
|
178
236
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
237
|
+
```tsx
|
|
238
|
+
// app/layout.tsx (a server component — that's fine)
|
|
239
|
+
import { ReviewKit } from '@satanwagen/reviewkit/next';
|
|
240
|
+
|
|
241
|
+
export default function RootLayout({ children }) {
|
|
242
|
+
return (
|
|
243
|
+
<html><body>
|
|
244
|
+
{children}
|
|
245
|
+
<ReviewKit token={process.env.NEXT_PUBLIC_REVIEW_TOKEN} devAutoOn={process.env.NODE_ENV === 'development'} />
|
|
246
|
+
</body></html>
|
|
247
|
+
);
|
|
184
248
|
}
|
|
185
249
|
```
|
|
186
250
|
|
|
187
|
-
The
|
|
251
|
+
The `/next` entry is a client component; it renders nothing on the server and evaluates the URL/sticky session after mount before importing the core. Add the CSP `connect-src` entries above if you send a policy. Full example: `examples/next-demo`.
|
|
188
252
|
|
|
189
253
|
## Adding ReviewKit to another site (with live collaboration)
|
|
190
254
|
|
|
@@ -203,19 +267,19 @@ Step by step, for any site on any hosting (nginx, IIS, Vercel, …).
|
|
|
203
267
|
|
|
204
268
|
Run `npm run build` inside ReviewKit at least once for the `file:`/git variants (the package resolves to `dist/`). With React 18 add `resolve.dedupe: ['react','react-dom']` to your bundler config when using a `file:` link.
|
|
205
269
|
|
|
206
|
-
**2. Integrate — two lines, done
|
|
270
|
+
**2. Integrate — two lines, done:**
|
|
207
271
|
|
|
208
272
|
```tsx
|
|
209
|
-
import { ReviewKit } from '@satanwagen/reviewkit/
|
|
273
|
+
import { ReviewKit } from '@satanwagen/reviewkit/lazy';
|
|
210
274
|
|
|
211
|
-
<ReviewKit token="my-site-token" syncToken="<the sync server's shared secret>" />
|
|
275
|
+
<ReviewKit token="my-site-token" syncUrl="wss://your-site.tld/rk-sync" syncToken="<the sync server's shared secret>" />
|
|
212
276
|
```
|
|
213
277
|
|
|
214
|
-
|
|
278
|
+
Live collaboration (presence, live cursors, instantly shared items) runs only when **both** `syncUrl` and `syncToken` are set — there is no default server. Rooms are keyed by the page's origin + path, so different sites never see each other's feedback or presence. If the server is unreachable the tool silently runs local-only (no console errors); omit either prop to disable networking entirely.
|
|
215
279
|
|
|
216
|
-
**What
|
|
280
|
+
**What a shared server implies (honesty section):** the token and `syncToken` are readable in the browser bundle, so anyone holding a review URL can join and create rooms. Run the server for your own sites only; it is guarded by the shared token, per-connection rate limits (40 msg/s, burst 80), 1 MiB frame cap, 16 clients and 2000 items per room, and a live-room ceiling (`RK_SYNC_MAX_ROOMS`, default 500).
|
|
217
281
|
|
|
218
|
-
**3.
|
|
282
|
+
**3. Run the sync server** — `realtime/server/`: `npm run build`, run `dist/server.js` with `RK_SYNC_TOKEN` (+ optional `RK_SYNC_PORT`, `RK_SYNC_DATA`, `RK_SYNC_MAX_ROOMS`, and `RK_SYNC_ORIGINS` if you *want* an origin allowlist — empty/unset means any origin, which is the default deployment) as a service, point `syncUrl` at it, then put a WebSocket-aware reverse proxy in front:
|
|
219
283
|
|
|
220
284
|
*nginx* (as deployed on satan):
|
|
221
285
|
|
|
@@ -233,7 +297,7 @@ That's the whole setup. `syncUrl` **defaults to the always-on ReviewKit sync ser
|
|
|
233
297
|
|
|
234
298
|
With an `RK_SYNC_ORIGINS` allowlist active, rejected origins are logged (`[origin] rejected …`) and closed with code 4005; the client quietly stays local.
|
|
235
299
|
|
|
236
|
-
**4. Token caveat:** don't reuse secrets that protect anything else, and keep tokens out of the main bundle (
|
|
300
|
+
**4. Token caveat:** don't reuse secrets that protect anything else, and keep tokens out of the main bundle (use the `/lazy` or `/next` entry). Add `allowedHosts` in production (see "Production recipe").
|
|
237
301
|
|
|
238
302
|
**5. Verify it works:** open `https://your-site/?review=<token>` — the Review badge appears; the panel header shows **live** (green dot) when the sync server accepted you, or *local only* when not. In the Network tab you should see one WebSocket to `/rk-sync` with a `welcome` frame. Open the same URL in a second browser: avatars, live cursors and instantly-appearing items confirm collaboration.
|
|
239
303
|
|
|
@@ -241,15 +305,17 @@ That's the whole setup. `syncUrl` **defaults to the always-on ReviewKit sync ser
|
|
|
241
305
|
|
|
242
306
|
ReviewKit pairs with the [Emblema](https://emblema.app) desktop app: once paired, the local sync agent streams review items to Emblema, and the paired Emblema shows them as findings in its ReviewKit tile (statuses flow back to the page).
|
|
243
307
|
|
|
244
|
-
Sync is automatic and realtime — no manual step. With `emblema`
|
|
308
|
+
Sync is automatic and realtime — no manual step. With `emblema={{}}` in the `ReviewKit` config, the browser itself pushes every added or edited open item to a locally running Emblema (debounced 500 ms, silent-fail: an unreachable Emblema never blocks the reviewer; the panel header shows a small `emblema` status dot). The session menu keeps **Resend all to Emblema** as a one-click fallback that re-dispatches every open item on the page.
|
|
309
|
+
|
|
310
|
+
**Which repo gets edited** is Emblema's knowledge, not the website's: the intake advertises its `repoPath` in the `/intake/ping` answer, and the local agent has it in `.reviewkit/emblema-sync.json` (`init --repo`, default: the directory you ran it in). `emblema.repoPath` remains as an optional override only; an absolute path from a reviewer's laptop has no place in host code.
|
|
245
311
|
|
|
246
312
|
**Where the key lives:** the pairing key and state are stored by the local sync agent in `<project>/.reviewkit/emblema-sync.json`. The key (`emk_…`) is a secret — the panel only ever displays it shortened (`emk_…last4`) and puts the full value on the clipboard.
|
|
247
313
|
|
|
248
|
-
**Set up and generate a key** (in the project repo):
|
|
314
|
+
**Set up and generate a key** (in the project repo; the CLI is its own package so host installs stay CLI-free):
|
|
249
315
|
|
|
250
316
|
```sh
|
|
251
|
-
npx reviewkit-
|
|
252
|
-
npx reviewkit-
|
|
317
|
+
npx @satanwagen/reviewkit-cli init --origin https://your-site --sync-url wss://your-site/rk-sync --sync-token <secret>
|
|
318
|
+
npx @satanwagen/reviewkit-cli serve # local API + realtime bridge (bridge needs both sync flags)
|
|
253
319
|
```
|
|
254
320
|
|
|
255
321
|
Paste the printed key into Emblema. From the page UI the same lives in the panel's gear menu → **Connect to Emblema…** — the menu entry shows the live pairing status at a glance (green dot = paired, amber = the local agent isn't answering, red = key revoked, grey = not paired), and the dialog has **New key (rotate)**, **Copy key** and **Delete key (revoke)**.
|
|
@@ -257,22 +323,24 @@ Paste the printed key into Emblema. From the page UI the same lives in the panel
|
|
|
257
323
|
**Rotate / revoke from the CLI:**
|
|
258
324
|
|
|
259
325
|
```sh
|
|
260
|
-
npx reviewkit-
|
|
261
|
-
npx reviewkit-
|
|
262
|
-
npx reviewkit-
|
|
326
|
+
npx @satanwagen/reviewkit-cli key # print the current key
|
|
327
|
+
npx @satanwagen/reviewkit-cli rotate # issue a new secret — the old key stops working immediately
|
|
328
|
+
npx @satanwagen/reviewkit-cli revoke # disable the pairing entirely (rotate re-enables)
|
|
263
329
|
```
|
|
264
330
|
|
|
265
331
|
Rotate and revoke take effect immediately, including against an already-running `serve` process — authenticated requests always check the current on-disk key, so a rotated or revoked key can never keep authenticating.
|
|
266
332
|
|
|
267
|
-
|
|
333
|
+
If the host sends a CSP, the bridge needs `connect-src http://127.0.0.1:*` (see "CSP").
|
|
334
|
+
|
|
335
|
+
## Demos
|
|
268
336
|
|
|
269
337
|
```sh
|
|
270
338
|
cd examples/vite-demo
|
|
271
339
|
npm install
|
|
272
|
-
npm run dev # http://localhost:3010 —
|
|
340
|
+
npm run dev # http://localhost:3010 — devAutoOn keeps the layer on in dev
|
|
273
341
|
```
|
|
274
342
|
|
|
275
|
-
Also try `http://localhost:3010/?review=demo` to exercise URL activation.
|
|
343
|
+
Also try `http://localhost:3010/?review=demo` to exercise URL activation. `examples/next-demo` is the same site on Next.js (App Router, `/next` entry, CSP headers) on port 3011.
|
|
276
344
|
|
|
277
345
|
## Development
|
|
278
346
|
|
|
@@ -281,4 +349,7 @@ npm install
|
|
|
281
349
|
npm run typecheck
|
|
282
350
|
npm run lint
|
|
283
351
|
npm run build # tsup → dist/ (ESM + CJS + d.ts)
|
|
352
|
+
npm test # CLI tests + activation matrix (needs dist/)
|
|
284
353
|
```
|
|
354
|
+
|
|
355
|
+
See `CHANGELOG.md` for the 0.1.3 default changes and migration notes.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/activate.ts
|
|
21
|
+
var activate_exports = {};
|
|
22
|
+
__export(activate_exports, {
|
|
23
|
+
STICKY_KEY: () => STICKY_KEY,
|
|
24
|
+
clearSticky: () => clearSticky,
|
|
25
|
+
hostAllowed: () => hostAllowed,
|
|
26
|
+
isLocalHost: () => isLocalHost,
|
|
27
|
+
isStickyEnabled: () => isStickyEnabled,
|
|
28
|
+
reviewParam: () => reviewParam,
|
|
29
|
+
setSticky: () => setSticky,
|
|
30
|
+
shouldActivate: () => shouldActivate
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(activate_exports);
|
|
33
|
+
var STICKY_KEY = "review-kit:sticky";
|
|
34
|
+
var STICKY_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
35
|
+
function safeRemove(storage, key) {
|
|
36
|
+
try {
|
|
37
|
+
storage().removeItem(key);
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readSticky() {
|
|
42
|
+
try {
|
|
43
|
+
const raw = window.sessionStorage.getItem(STICKY_KEY);
|
|
44
|
+
if (!raw) return null;
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.token === "string" && typeof parsed.until === "number") {
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function isStickyEnabled(token) {
|
|
54
|
+
if (token === void 0 || token === "") return false;
|
|
55
|
+
const sticky = readSticky();
|
|
56
|
+
if (!sticky) return false;
|
|
57
|
+
if (sticky.until < Date.now() || sticky.token !== token) {
|
|
58
|
+
safeRemove(() => window.sessionStorage, STICKY_KEY);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
function setSticky(token, on) {
|
|
64
|
+
try {
|
|
65
|
+
if (on && token !== void 0 && token !== "") {
|
|
66
|
+
const value = { token, until: Date.now() + STICKY_TTL_MS };
|
|
67
|
+
window.sessionStorage.setItem(STICKY_KEY, JSON.stringify(value));
|
|
68
|
+
} else {
|
|
69
|
+
window.sessionStorage.removeItem(STICKY_KEY);
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function clearSticky() {
|
|
75
|
+
safeRemove(() => window.sessionStorage, STICKY_KEY);
|
|
76
|
+
}
|
|
77
|
+
function reviewParam() {
|
|
78
|
+
try {
|
|
79
|
+
return new URLSearchParams(window.location.search).get("review");
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function hostAllowed(allowedHosts) {
|
|
85
|
+
if (!allowedHosts || allowedHosts.length === 0) return true;
|
|
86
|
+
let host = "";
|
|
87
|
+
try {
|
|
88
|
+
host = window.location.hostname.toLowerCase();
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return allowedHosts.some((pattern) => {
|
|
93
|
+
const regex = new RegExp(
|
|
94
|
+
"^" + pattern.toLowerCase().split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*") + "$"
|
|
95
|
+
);
|
|
96
|
+
return regex.test(host);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function isLocalHost() {
|
|
100
|
+
try {
|
|
101
|
+
const host = window.location.hostname.toLowerCase();
|
|
102
|
+
return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host.endsWith(".localhost");
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
var warnedNoToken = false;
|
|
108
|
+
var warnedMismatch = false;
|
|
109
|
+
function shouldActivate(options = {}) {
|
|
110
|
+
if (typeof window === "undefined") return false;
|
|
111
|
+
if (options.enabled === false) return false;
|
|
112
|
+
if (!hostAllowed(options.allowedHosts)) return false;
|
|
113
|
+
const param = reviewParam();
|
|
114
|
+
if (param === "off") {
|
|
115
|
+
clearSticky();
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (options.devAutoOn === true) return true;
|
|
119
|
+
const token = options.token;
|
|
120
|
+
if (token === void 0 || token === "") {
|
|
121
|
+
if (param !== null && param !== "" && !warnedNoToken) {
|
|
122
|
+
warnedNoToken = true;
|
|
123
|
+
console.warn(
|
|
124
|
+
"[review-kit] `?review` is present but no `token` is configured; the layer stays off. Pass `token` to <ReviewKit /> (or `devAutoOn` for local development)."
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (param !== null && param !== "") {
|
|
130
|
+
if (param === token) return true;
|
|
131
|
+
if (!warnedMismatch) {
|
|
132
|
+
warnedMismatch = true;
|
|
133
|
+
console.warn("[review-kit] `?review` token does not match the configured token; staying inactive.");
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return isStickyEnabled(token);
|
|
138
|
+
}
|
|
139
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
140
|
+
0 && (module.exports = {
|
|
141
|
+
STICKY_KEY,
|
|
142
|
+
clearSticky,
|
|
143
|
+
hostAllowed,
|
|
144
|
+
isLocalHost,
|
|
145
|
+
isStickyEnabled,
|
|
146
|
+
reviewParam,
|
|
147
|
+
setSticky,
|
|
148
|
+
shouldActivate
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=activate.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/activate.ts"],"sourcesContent":["/**\n * `@satanwagen/reviewkit/activate` — the activation decision, React-free.\n *\n * Hosts that lazy-load the review layer need to know BEFORE importing any\n * React code whether this page load should show the layer at all. This\n * module is that decision, and the only supported way to read ReviewKit's\n * persisted opt-in state (the sessionStorage format is private and may\n * change; `shouldActivate` will not).\n *\n * Rules (no exceptions, no environment sniffing):\n *\n * - `enabled: false` → never.\n * - hostname ∉ `allowedHosts` → never (when an allowlist is given).\n * - `?review=off` → never; also clears the in-tab opt-in.\n * - no `token` configured → never. `?review=x` without a token logs\n * a warning and stays off.\n * - `?review=<token>` → yes.\n * - valid in-tab sticky session → yes (started by an earlier `?review=<token>`\n * load in this tab, bound to the token,\n * ~4 h sliding expiry).\n * - `devAutoOn: true` → yes, regardless of URL/token. The host\n * decides when (e.g. `import.meta.env.DEV`);\n * the library never reads NODE_ENV.\n */\n\nexport interface ActivationOptions {\n /** The review token. Without it the layer never activates (except `devAutoOn`). */\n token?: string;\n /** Master switch. `false` short-circuits everything. Default `true`. */\n enabled?: boolean;\n /** Hostname allowlist with `*` wildcards. Unset = any host. */\n allowedHosts?: string[];\n /** Force the layer on for this page load (dev convenience; host-controlled). */\n devAutoOn?: boolean;\n}\n\n/** @internal sessionStorage key of the per-tab opt-in. Format is private. */\nexport const STICKY_KEY = 'review-kit:sticky';\nconst STICKY_TTL_MS = 4 * 60 * 60 * 1000;\n\ninterface Sticky {\n token: string;\n until: number;\n}\n\nfunction safeRemove(storage: () => Storage, key: string): void {\n try {\n storage().removeItem(key);\n } catch {\n /* storage unavailable */\n }\n}\n\nfunction readSticky(): Sticky | null {\n try {\n const raw = window.sessionStorage.getItem(STICKY_KEY);\n if (!raw) return null;\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n typeof (parsed as Sticky).token === 'string' &&\n typeof (parsed as Sticky).until === 'number'\n ) {\n return parsed as Sticky;\n }\n } catch {\n /* unreadable */\n }\n return null;\n}\n\n/** @internal Is the per-tab opt-in present, unexpired and bound to `token`? */\nexport function isStickyEnabled(token: string | undefined): boolean {\n if (token === undefined || token === '') return false;\n const sticky = readSticky();\n if (!sticky) return false;\n if (sticky.until < Date.now() || sticky.token !== token) {\n safeRemove(() => window.sessionStorage, STICKY_KEY);\n return false;\n }\n return true;\n}\n\n/** @internal Start / renew / end the per-tab opt-in. No-op without a token. */\nexport function setSticky(token: string | undefined, on: boolean): void {\n try {\n if (on && token !== undefined && token !== '') {\n const value: Sticky = { token, until: Date.now() + STICKY_TTL_MS };\n window.sessionStorage.setItem(STICKY_KEY, JSON.stringify(value));\n } else {\n window.sessionStorage.removeItem(STICKY_KEY);\n }\n } catch {\n /* storage unavailable — the choice just won't persist */\n }\n}\n\n/** @internal Clear the per-tab opt-in. */\nexport function clearSticky(): void {\n safeRemove(() => window.sessionStorage, STICKY_KEY);\n}\n\n/** @internal The raw `?review` value of the current URL, or null. */\nexport function reviewParam(): string | null {\n try {\n return new URLSearchParams(window.location.search).get('review');\n } catch {\n return null;\n }\n}\n\n/** Does the current hostname match one of the allowed patterns?\n * '*' matches any characters ('*.dev.example' → a.dev.example, a.b.dev.example). */\nexport function hostAllowed(allowedHosts: string[] | undefined): boolean {\n if (!allowedHosts || allowedHosts.length === 0) return true;\n let host = '';\n try {\n host = window.location.hostname.toLowerCase();\n } catch {\n return false;\n }\n return allowedHosts.some((pattern) => {\n const regex = new RegExp(\n '^' +\n pattern\n .toLowerCase()\n .split('*')\n .map((part) => part.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*') +\n '$',\n );\n return regex.test(host);\n });\n}\n\n/** Loopback hosts where a missing allowlist is expected, not a smell. */\nexport function isLocalHost(): boolean {\n try {\n const host = window.location.hostname.toLowerCase();\n return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host.endsWith('.localhost');\n } catch {\n return false;\n }\n}\n\nlet warnedNoToken = false;\nlet warnedMismatch = false;\n\n/**\n * Should the review layer be active for this page load?\n *\n * Read-only except for one deliberate side effect: `?review=off` clears the\n * in-tab opt-in, so a host that gates a lazy import on this function still\n * honours \"off\" on the next load. Safe to call on the server (returns false).\n */\nexport function shouldActivate(options: ActivationOptions = {}): boolean {\n if (typeof window === 'undefined') return false;\n if (options.enabled === false) return false;\n if (!hostAllowed(options.allowedHosts)) return false;\n\n const param = reviewParam();\n if (param === 'off') {\n clearSticky();\n return false;\n }\n if (options.devAutoOn === true) return true;\n\n const token = options.token;\n if (token === undefined || token === '') {\n if (param !== null && param !== '' && !warnedNoToken) {\n warnedNoToken = true;\n console.warn(\n '[review-kit] `?review` is present but no `token` is configured; the layer stays off. ' +\n 'Pass `token` to <ReviewKit /> (or `devAutoOn` for local development).',\n );\n }\n return false;\n }\n if (param !== null && param !== '') {\n if (param === token) return true;\n if (!warnedMismatch) {\n warnedMismatch = true;\n console.warn('[review-kit] `?review` token does not match the configured token; staying inactive.');\n }\n return false;\n }\n return isStickyEnabled(token);\n}\n"],"mappings":"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","names":[]}
|