@weatherboard/annotation 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/ADOPTING.md +147 -10
  2. package/COMPATIBILITY.md +29 -0
  3. package/README.md +84 -10
  4. package/dist/anchor.cjs +1 -1
  5. package/dist/anchor.cjs.map +2 -2
  6. package/dist/anchor.mjs.map +2 -2
  7. package/dist/arming.d.cts +18 -21
  8. package/dist/arming.d.mts +18 -21
  9. package/dist/arming.d.ts +18 -21
  10. package/dist/bootstrap-runtime.d.cts +30 -0
  11. package/dist/bootstrap-runtime.d.mts +30 -0
  12. package/dist/bootstrap-runtime.d.ts +30 -0
  13. package/dist/bootstrap.cjs +2 -0
  14. package/dist/bootstrap.cjs.map +7 -0
  15. package/dist/bootstrap.d.cts +8 -0
  16. package/dist/bootstrap.d.mts +8 -0
  17. package/dist/bootstrap.d.ts +8 -0
  18. package/dist/bootstrap.mjs +2 -0
  19. package/dist/bootstrap.mjs.map +7 -0
  20. package/dist/host.cjs +1 -1
  21. package/dist/host.cjs.map +3 -3
  22. package/dist/host.mjs +1 -1
  23. package/dist/host.mjs.map +3 -3
  24. package/dist/index.cjs +4 -4
  25. package/dist/index.cjs.map +4 -4
  26. package/dist/index.d.cts +10 -4
  27. package/dist/index.d.mts +10 -4
  28. package/dist/index.d.ts +10 -4
  29. package/dist/index.mjs +4 -4
  30. package/dist/index.mjs.map +4 -4
  31. package/dist/markers.cjs +2 -2
  32. package/dist/markers.cjs.map +2 -2
  33. package/dist/markers.mjs +2 -2
  34. package/dist/markers.mjs.map +2 -2
  35. package/dist/page-capture.cjs +2 -0
  36. package/dist/page-capture.cjs.map +7 -0
  37. package/dist/page-capture.d.cts +115 -0
  38. package/dist/page-capture.d.mts +115 -0
  39. package/dist/page-capture.d.ts +115 -0
  40. package/dist/page-capture.mjs +2 -0
  41. package/dist/page-capture.mjs.map +7 -0
  42. package/dist/protocol.cjs +1 -1
  43. package/dist/protocol.cjs.map +3 -3
  44. package/dist/protocol.d.cts +19 -0
  45. package/dist/protocol.d.mts +19 -0
  46. package/dist/protocol.d.ts +19 -0
  47. package/dist/protocol.mjs +1 -1
  48. package/dist/protocol.mjs.map +3 -3
  49. package/dist/read.cjs +1 -1
  50. package/dist/read.cjs.map +2 -2
  51. package/dist/read.mjs +1 -1
  52. package/dist/read.mjs.map +2 -2
  53. package/dist/screenshot.d.cts +56 -7
  54. package/dist/screenshot.d.mts +56 -7
  55. package/dist/screenshot.d.ts +56 -7
  56. package/dist/selection.cjs +5 -0
  57. package/dist/selection.cjs.map +7 -0
  58. package/dist/selection.d.cts +27 -0
  59. package/dist/selection.d.mts +27 -0
  60. package/dist/selection.d.ts +27 -0
  61. package/dist/selection.mjs +5 -0
  62. package/dist/selection.mjs.map +7 -0
  63. package/dist/session.d.cts +17 -0
  64. package/dist/session.d.mts +17 -0
  65. package/dist/session.d.ts +17 -0
  66. package/dist/shortcut.d.cts +52 -0
  67. package/dist/shortcut.d.mts +52 -0
  68. package/dist/shortcut.d.ts +52 -0
  69. package/dist/toolbar.d.cts +13 -0
  70. package/dist/toolbar.d.mts +13 -0
  71. package/dist/toolbar.d.ts +13 -0
  72. package/dist/transport.d.cts +10 -0
  73. package/dist/transport.d.mts +10 -0
  74. package/dist/transport.d.ts +10 -0
  75. package/package.json +45 -10
package/ADOPTING.md CHANGED
@@ -41,8 +41,12 @@ npm i @weatherboard/annotation
41
41
  npm i -D @weatherboard/annotation-next
42
42
  ```
43
43
 
44
- The runtime package has **zero dependencies** and ships in your app. The
45
- transform runs only in your build.
44
+ The runtime package has **one dependency**, `modern-screenshot` (MIT), and ships
45
+ in your app. It is the approved exception to this package's no-dependencies
46
+ rule (A-2385): it renders the viewport screenshot, and it is loaded with a
47
+ dynamic `import()` at capture time only, so your bundler splits it into its own
48
+ chunk and an armed page that is never clicked never fetches it. The transform
49
+ runs only in your build.
46
50
 
47
51
  **What breaks:** `-D` on the second one is not cosmetic. `annotation-next`
48
52
  depends on a Babel parser, and npm installs a package's `dependencies` whichever
@@ -98,23 +102,40 @@ pointed at production anyway.
98
102
 
99
103
  ```ts
100
104
  // middleware.ts
101
- import { ANNOTATION_ARMED_COOKIE, readAnnotationToken } from "@weatherboard/annotation/protocol";
105
+ import {
106
+ ANNOTATION_ARMED_COOKIE,
107
+ ANNOTATION_ARMED_COOKIES,
108
+ ANNOTATION_TOKEN_PARAMS,
109
+ readAnnotationToken,
110
+ } from "@weatherboard/annotation/protocol";
102
111
 
103
112
  const presented = readAnnotationToken(request.nextUrl.searchParams);
104
113
  const expected = process.env.ANNOTATION_TOKEN;
114
+ const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
115
+ const overHttps = request.nextUrl.protocol === "https:";
105
116
 
106
117
  if (expected && presented && presented.length === expected.length && presented === expected) {
107
- const overHttps = request.nextUrl.protocol === "https:";
108
118
  response.cookies.set(ANNOTATION_ARMED_COOKIE, "1", {
109
119
  httpOnly: false, // the client bundle is what reads it
110
120
  sameSite: overHttps ? "none" : "lax",
111
121
  secure: overHttps,
112
122
  path: "/",
113
123
  });
124
+ } else if (attempted) {
125
+ for (const name of ANNOTATION_ARMED_COOKIES) {
126
+ response.cookies.set(name, "", {
127
+ httpOnly: false,
128
+ sameSite: overHttps ? "none" : "lax",
129
+ secure: overHttps,
130
+ path: "/",
131
+ maxAge: 0,
132
+ expires: new Date(0),
133
+ });
134
+ }
114
135
  }
115
136
  ```
116
137
 
117
- Four details, each of which has cost somebody a day:
138
+ Five details, each of which has cost somebody a day:
118
139
 
119
140
  - **The cookie holds `1`, never the token.** It records that the token was
120
141
  presented once, so navigation inside the frame stays armed without the secret
@@ -127,6 +148,10 @@ Four details, each of which has cost somebody a day:
127
148
  - **…but `None` is only honoured with `Secure`**, and `Secure` is dropped over
128
149
  plain http — so a local `next dev` would set a cookie the browser silently
129
150
  discards. Fall back to `Lax`, which is same-origin anyway.
151
+ - **A fresh rejected token expires both cookie spellings.** A browser may still
152
+ carry a valid cookie from an earlier link. Leaving it in place turns the new
153
+ rejection into an accepted launch and lets the old capture/read/run context
154
+ survive under a different review link.
130
155
 
131
156
  ### First-party, the cookie is an ordinary one — use `armedCookie()` (A-241)
132
157
 
@@ -140,14 +165,65 @@ Do not re-derive the attributes. `@weatherboard/annotation/protocol` exports the
140
165
  cookie itself, so the two halves cannot disagree:
141
166
 
142
167
  ```ts
143
- import { armedCookie, clearedCookie, ANNOTATION_ARMED_COOKIE } from "@weatherboard/annotation/protocol";
168
+ import {
169
+ armedCookie,
170
+ clearedCookie,
171
+ ANNOTATION_ARMED_COOKIES,
172
+ ANNOTATION_TOKEN_PARAMS,
173
+ readAnnotationToken,
174
+ } from "@weatherboard/annotation/protocol";
144
175
 
145
- // after YOUR comparison against YOUR secret has succeeded, and only then:
146
- response.headers.append("set-cookie", armedCookie());
176
+ const presented = readAnnotationToken(request.nextUrl.searchParams);
177
+ const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
178
+ if (presented && presented === process.env.ANNOTATION_TOKEN) {
179
+ response.headers.append("set-cookie", armedCookie());
180
+ } else if (attempted) {
181
+ for (const name of ANNOTATION_ARMED_COOKIES) {
182
+ response.headers.append("set-cookie", clearedCookie(name));
183
+ }
184
+ }
185
+ ```
186
+
187
+ Put the package bootstrap in the first raw script in `<head>`, before styles,
188
+ analytics and hydration:
189
+
190
+ ```tsx
191
+ import { annotationBootstrapScript } from "@weatherboard/annotation/bootstrap";
192
+
193
+ <script dangerouslySetInnerHTML={{ __html: annotationBootstrapScript() }} />
194
+ ```
195
+
196
+ The script synchronously strips `ai_annotate`, its legacy spelling,
197
+ `ai_capture`, `ai_read` and `ai_run`. When middleware accepted the arming link,
198
+ it keeps capture/read/run in this tab's `sessionStorage`, so same-tab routing
199
+ and a hard reload remain armed after the URL is clean. When middleware rejected
200
+ the link, it persists only `{ status: "rejected", capture: "present" | "missing" }`.
201
+ No credential from an unarmed link is stored.
202
+
203
+ The client mount can consume that signal once and report a generic failure:
204
+
205
+ ```ts
206
+ import {
207
+ consumeAnnotationAttempt,
208
+ reportAnnotationStartupFailure,
209
+ } from "@weatherboard/annotation/bootstrap";
210
+
211
+ const attempt = consumeAnnotationAttempt();
212
+ if (attempt?.status === "rejected") {
213
+ return reportAnnotationStartupFailure("rejected-arming");
214
+ }
215
+ if (attempt?.capture === "missing") {
216
+ return reportAnnotationStartupFailure("missing-capture");
217
+ }
147
218
  ```
148
219
 
149
- It takes no value argument on purpose — the cookie holds a marker, never the
150
- token, and the first bullet above is the reason. The page half calls
220
+ The reporter owns one host, `#ai-annotation-startup-status`, and returns a
221
+ cleanup function. Import and mount failures use `"import-failed"` and
222
+ `"mount-failed"`. All copy is package-owned and contains no token or product
223
+ name.
224
+
225
+ It takes no value argument on purpose — the middleware's arming cookie holds a
226
+ marker, never the token. The page half calls
151
227
  `readArming()`, which reads that cookie, strips the tokens out of the URL with
152
228
  `replaceState`, and hands the capture token to `httpTransport`.
153
229
 
@@ -268,6 +344,32 @@ dynamic import costs a chunk fetch, so the first few milliseconds of an armed
268
344
  page are genuinely unobserved. That is the trade. What you must *not* do is defer
269
345
  the collector to click time, which observes nothing at all.
270
346
 
347
+ ### The keyboard shortcut — install it before the armed check (A-2384)
348
+
349
+ The mount above returns on an unarmed page before importing anything, so a key
350
+ listener inside `mountAnnotation` cannot hear the chord there. Install the
351
+ pre-arm listener from `./bootstrap` **above** that return, on every page:
352
+
353
+ ```ts
354
+ import { DEFAULT_HOTKEY, installAnnotationShortcut } from "@weatherboard/annotation/bootstrap";
355
+
356
+ useEffect(() => installAnnotationShortcut({
357
+ build,
358
+ armUrl: process.env.NEXT_PUBLIC_ANNOTATION_ARM_URL!, // https://<studio>/annotate/arm?product=<id>&target=staging
359
+ hotkey: DEFAULT_HOTKEY,
360
+ }), []);
361
+ ```
362
+
363
+ and pass the same `hotkey` to `mountAnnotation`. There is no default `armUrl`,
364
+ for the reason step 5 gives for origins. Studio's route does the signed-in
365
+ reviewer check and refuses anything but the registered staging target; your
366
+ middleware still decides arming from the token it is sent back with. A
367
+ production build installs no listener.
368
+
369
+ **What breaks:** installed below the `isAnnotationArmed` return, the chord
370
+ works only on a page that is already armed, which looks like a shortcut that
371
+ "sometimes" works.
372
+
271
373
  ## 7. `parentOrigin` is one address, not a list
272
374
 
273
375
  `postMessage`'s second argument is **not an allowlist**. It is an address, and
@@ -490,3 +592,38 @@ misattributed.
490
592
  Every step above is a thing that, when absent, produces no error, no warning and
491
593
  no log line — only an absence. When you are debugging, prefer the check that
492
594
  distinguishes causes over the check that confirms your guess.
595
+
596
+ ## Host-owned contextual feedback
597
+
598
+ `@weatherboard/annotation/selection` exposes `relativePoint`,
599
+ `captureTextSelection`, `resolveTextSelection` and `prepareTextSelections` without
600
+ arming the developer SDK, mounting UI or sending network requests. Production
601
+ hosts such as Pitch own authentication, capture intent, target persistence and
602
+ permission-scoped reads. Import this subpath directly; importing the root SDK
603
+ still observes its existing production restrictions.
604
+
605
+ Mark authored text containers with `data-annotation-text="host-field-path"`.
606
+ Capture returns UTF-16 offsets, exact quote and bounded prefix/suffix using CRLF
607
+ normalisation only. A field path is evidence qualified by a host content revision,
608
+ not a permanent identity. Pass whether content is unchanged when resolving;
609
+ edited content requires a unique quote/context match. Missing or ambiguous
610
+ locations must remain reviewable as unplaced feedback. `prepareTextSelections`
611
+ shares one bounded text-map snapshot across annotations in a layout frame; discard
612
+ it after content or layout changes. Neither helper reads application controls as
613
+ authored text or persists DOM nodes.
614
+
615
+ ## After adopting: prove the capture end to end on staging
616
+
617
+ A package release cannot prove its own capture path, because that needs a host
618
+ that consumes the published version. After a product adopts it on staging:
619
+
620
+ 1. Arm at the Studio arm endpoint, press the chord, and file one capture with
621
+ its viewport screenshot.
622
+ 2. Confirm the capture reached ingest with the screenshot attached, and that the
623
+ filed issue carries the image.
624
+ 3. For the annotate-to-merge loop, the product's registry row must let
625
+ `workflowAutomationDecision` say yes for that origin: its preview host,
626
+ image ref and base SHA. Then confirm one `annotation_workflows` row and a
627
+ sandbox run that opens a pull request.
628
+ 4. Record the capture id, the issue, the run id and the pull request on the
629
+ adoption issue.
package/COMPATIBILITY.md CHANGED
@@ -95,3 +95,32 @@ whoever drops it, in the same change, and not by whoever finds out.
95
95
 
96
96
  `0.x` while the wire protocol is still moving. **1.0 is the statement that a
97
97
  hundred consumers can rely on it**, not a way to look finished.
98
+
99
+ ### Additive selection subpath (A-391)
100
+
101
+ The workspace adds `./selection` with ESM, CommonJS and TypeScript exports.
102
+ Existing SDK wire versions and production arming rules are unchanged. Pitch uses
103
+ the workspace source; external consumers need a subsequently published package
104
+ version containing this entry point. The tarball smoke test verifies its exports;
105
+ this feature does not itself publish an npm release.
106
+
107
+ ### In-app shortcut and cross-tab arming (A-2384)
108
+
109
+ Version 0.7.0 adds `hotkey` to `mountAnnotation` and `installAnnotationShortcut`
110
+ to `./bootstrap`. Both are opt-in: absent `hotkey` means what it meant before, an
111
+ immediately visible toolbar. Passing a chord (`DEFAULT_HOTKEY` is
112
+ `Mod+Shift+Period`) starts an armed toolbar dormant and toggles it. The
113
+ bootstrap listener re-arms through Studio's `/annotate/arm` when no fresh
114
+ mounted toolbar claims the chord, and carries an open intent in session storage
115
+ so the next armed mount opens. `showResult` attaches a dormant toolbar.
116
+ `readArming` copies accepted capture and read tokens into a secure, expiring
117
+ first-party cookie; a new tab uses it when its session storage has no token.
118
+ The arming marker remains the middleware's decision, and the console still
119
+ verifies tokens at its endpoint.
120
+
121
+ ### Additive bootstrap subpath and hosted-mode metadata (A-1375)
122
+
123
+ Version 0.6.0 adds `./bootstrap` for synchronous credential stripping,
124
+ per-tab continuation and token-free startup status. It also adds the exact
125
+ `x-weatherboard-annotation-mode: first-party` constants to `./protocol`. The
126
+ wire payload version and all existing names remain unchanged.
package/README.md CHANGED
@@ -11,8 +11,11 @@ sentence. What comes back is not a screenshot and a guess — it is the file and
11
11
  line that rendered that element, the ancestors that led to it, the console
12
12
  errors and failed requests from that session, and the build the page came from.
13
13
 
14
- Zero dependencies. It runs inside your production site, so it has no business
15
- adding to your supply chain.
14
+ One runtime dependency, by exception: `modern-screenshot` (MIT), which renders
15
+ the viewport into the screenshot a capture carries. It is loaded with a dynamic
16
+ `import()` on the first capture only, so a page that is never annotated
17
+ downloads none of it. Nothing else: it runs inside your production site, so it
18
+ has no business adding to your supply chain.
16
19
 
17
20
  ## Install
18
21
 
@@ -20,23 +23,37 @@ adding to your supply chain.
20
23
  npm i @weatherboard/annotation
21
24
  ```
22
25
 
23
- ## Six entry points
26
+ ## Eight entry points
24
27
 
25
28
  They are split by **who imports them**, not by topic.
26
29
 
27
30
  | Import | Runs where | For |
28
31
  |---|---|---|
29
32
  | `@weatherboard/annotation` | The browser | The page SDK — overlay, target resolution, state collection, screenshots |
33
+ | `@weatherboard/annotation/bootstrap` | The browser, before hydration | Immediate URL stripping, arming reads and token-free startup status |
30
34
  | `@weatherboard/annotation/protocol` | Anywhere, no DOM | The wire names and the two readers. This is what a middleware imports |
31
35
  | `@weatherboard/annotation/host` | The reviewing console | The parent half of the channel |
32
36
  | `@weatherboard/annotation/anchor` | The browser | `resolveAnchor` — where a capture's marker goes, or why it goes nowhere |
33
37
  | `@weatherboard/annotation/read` | The browser | `createAnnotationReader` — the captures already made on this page |
34
38
  | `@weatherboard/annotation/markers` | The browser | The marker layer and its panel |
39
+ | `@weatherboard/annotation/selection` | The browser | Authored-content text/point selection and placement helpers |
35
40
 
36
- The last three are separate for one reason: they run only in a product that
37
- RENDERS markers, and a product that merely captures should not pay for them. They
38
- are subpaths rather than a tree-shaking promise for the same reason `/protocol`
39
- is.
41
+ The final four remain separate because products can choose capture, marker
42
+ rendering and authored-content selection independently. A product that merely
43
+ captures should not pay for readers, marker UI or selection helpers. They are
44
+ subpaths rather than a tree-shaking promise for the same reason `/protocol` is.
45
+
46
+ `/bootstrap` is also a subpath rather than a tree-shaking promise. Put
47
+ `annotationBootstrapScript()` in the first raw script in `<head>`, then keep the
48
+ root entry behind a dynamic import. A normal visitor pays for URL and cookie
49
+ reads without downloading the toolbar or state collector.
50
+
51
+ Package-authored browser UI keeps its styles zero-dependency too. Declare
52
+ colour, spacing and radius literals once as local `--ai-*` custom properties,
53
+ then consume them with `var()` in the same style string. Do not import a
54
+ product's design tokens: a toolbar or startup notice runs inside somebody
55
+ else's page, where those names and values are not ours. New runtime surfaces
56
+ must pass `pnpm gyde:check`; do not add their findings to the allowance ledger.
40
57
 
41
58
  `/protocol` is a subpath rather than a tree-shaking promise on purpose: an edge
42
59
  middleware is often bundled as CJS, and CJS does not shake. Importing the root
@@ -44,6 +61,52 @@ there would drag the overlay into a bundle that has no `document`.
44
61
 
45
62
  ## The page half
46
63
 
64
+ ### The in-app shortcut (A-2384)
65
+
66
+ The shortcut has two halves, because a host loads the toolbar only on an armed
67
+ page (ADOPTING.md, step 6) and an ordinary staging page has nothing loaded to
68
+ hear a key.
69
+
70
+ 1. **Pre-arm, on every page.** From the small `./bootstrap` entry point, next to
71
+ the bootstrap script:
72
+
73
+ ```ts
74
+ import { DEFAULT_HOTKEY, installAnnotationShortcut } from "@weatherboard/annotation/bootstrap";
75
+
76
+ installAnnotationShortcut({
77
+ build, // the same BuildInfo the mount gets; production installs nothing
78
+ armUrl: process.env.NEXT_PUBLIC_ANNOTATION_ARM_URL, // Studio's /annotate/arm?product=<id>&target=staging
79
+ hotkey: DEFAULT_HOTKEY,
80
+ });
81
+ ```
82
+
83
+ 2. **The mount, on an armed page.** Pass the middleware-backed `readArming()`
84
+ result and the same `hotkey` to `mountAnnotation()`.
85
+
86
+ With a fresh token the mounted toolbar claims the chord and toggles itself.
87
+ Otherwise — nothing mounted, no token, a token within five minutes of expiry,
88
+ or a session ended with Done — the chord is unclaimed, and the pre-arm listener
89
+ writes a credential-free open intent to session storage and navigates the tab
90
+ to `armUrl` with a `return` path from which every annotation parameter has
91
+ been removed. Studio checks the signed-in reviewer, mints the tokens and sends
92
+ the tab back; the host's middleware arms on that request, the bootstrap script
93
+ strips the query, and the mount opens the toolbar once because the intent is
94
+ under ten minutes old. One chord arms and opens. `e2e/shortcut.spec.ts` runs
95
+ that loop in Chromium, through to a capture with a screenshot.
96
+
97
+ The hotkey is opt-in: with no `hotkey` (absent or `null`) the toolbar shows
98
+ immediately, as every mount before 0.7.0 did. `DEFAULT_HOTKEY` is
99
+ `Mod+Shift+Period`, and `Mod` accepts Command or Control. The chord works while
100
+ a form field has focus. A result shown by `showResult` always reveals the
101
+ toolbar.
102
+
103
+ `readArming` strips credentials from the deep link, keeps them in session
104
+ storage, and copies an accepted token pair to an expiring
105
+ `Secure; SameSite=Lax; Path=/` cookie so a new tab can recover them. That
106
+ cookie is sent on product-origin requests, so product request logs must exclude
107
+ its value. The package reads the signed token's expiry without verifying the
108
+ signature; the console verifies it when used.
109
+
47
110
  ```ts
48
111
  import { createAnnotationSdk, isAnnotationArmed, readAnnotationToken } from "@weatherboard/annotation";
49
112
 
@@ -63,11 +126,21 @@ right is yours, because only you hold the secret.
63
126
 
64
127
  ```ts
65
128
  // middleware.ts
66
- import { ANNOTATION_ARMED_COOKIE, readAnnotationToken } from "@weatherboard/annotation/protocol";
129
+ import {
130
+ ANNOTATION_ARMED_COOKIE,
131
+ ANNOTATION_ARMED_COOKIES,
132
+ ANNOTATION_TOKEN_PARAMS,
133
+ readAnnotationToken,
134
+ } from "@weatherboard/annotation/protocol";
67
135
 
68
136
  const token = readAnnotationToken(request.nextUrl.searchParams);
69
- if (token !== undefined && token === process.env.ANNOTATION_TOKEN) {
137
+ const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
138
+ if (token !== null && token === process.env.ANNOTATION_TOKEN) {
70
139
  response.cookies.set(ANNOTATION_ARMED_COOKIE, "1", { httpOnly: false, sameSite: "lax" });
140
+ } else if (attempted) {
141
+ for (const name of ANNOTATION_ARMED_COOKIES) {
142
+ response.cookies.set(name, "", { path: "/", maxAge: 0, expires: new Date(0) });
143
+ }
71
144
  }
72
145
  ```
73
146
 
@@ -75,7 +148,8 @@ if (token !== undefined && token === process.env.ANNOTATION_TOKEN) {
75
148
  for an absent *or empty* parameter. An empty token compared with `===` against
76
149
  your secret is a rejection; an empty token treated as present is an app armed
77
150
  with the empty string, which fails later and reads like a bad secret rather than
78
- a missing one.
151
+ a missing one. A fresh rejected attempt must expire **both** supported cookie
152
+ spellings so an already-armed browser cannot reuse its previous session.
79
153
 
80
154
  ## Source refs
81
155
 
package/dist/anchor.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var f=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var R=(e,n)=>{for(var t in n)f(e,t,{get:n[t],enumerable:!0})},b=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of E(n))!T.call(e,r)&&r!==t&&f(e,r,{get:()=>n[r],enumerable:!(o=x(n,r))||o.enumerable});return e};var y=e=>b(f({},"__esModule",{value:!0}),e);var L={};R(L,{anchorFromTarget:()=>S,resolveAnchor:()=>A,tallyAnchors:()=>_});module.exports=y(L);var u="data-src";function g(e){return e.selection==="region"?"region":"element"}var m=8;function s(e){if(!e)return null;let n=e.lastIndexOf(":");if(n<=0)return null;let t=e.lastIndexOf(":",n-1);if(t<=0)return null;let o=e.slice(0,t),r=Number(e.slice(t+1,n)),i=Number(e.slice(n+1));return!o||!Number.isFinite(r)||!Number.isFinite(i)?null:{file:o,line:r,column:i}}function d(e,n=m){let t=[],o=e;for(;o&&t.length<n;){let r=s(o.getAttribute(u));if(r){let i=t[t.length-1];i&&i.file===r.file&&i.line===r.line||t.push(r)}o=o.parentElement}return t}function h(e){let n=e;for(;n;){if(s(n.getAttribute(u)))return n;n=n.parentElement}return null}function l(e,n){return e.file!==n.file||e.line!==n.line?!1:e.column===void 0||n.column===void 0?!0:e.column===n.column}function k(e,n){if(e.length===0)return!1;if(e.length<m){if(n.length!==e.length)return!1}else if(n.length<e.length)return!1;return e.every((t,o)=>{let r=n[o];return r!==void 0&&l(t,r)})}function N(e,n){let t={file:e.file,line:e.line};e.column!==void 0&&(t.column=e.column);let o=n.querySelectorAll(`[${u}]`),r=[];for(let i=0;i<o.length;i++){let a=o[i],c=s(a.getAttribute(u));if(!c||!l(t,c))continue;let p=s(h(a.parentElement)?.getAttribute(u)??null);p&&l(p,c)||r.push(a)}return r}function A(e,n){switch(e.kind){case"route":{if(!e.path)return"none";let t=n.location?.pathname;return!t||t!==e.path?"none":n.body??"none"}case"dom-path":{if(!e.path)return"none";let t;try{t=n.querySelectorAll(e.path)}catch{return"none"}return t.length===0?"none":t.length>1?"ambiguous":t[0]}case"source-ref":{if(e.chain.length===0)return"none";let t=N(e,n).filter(o=>k(e.chain,d(o)));return t.length===0?"none":t.length>1?"ambiguous":t[0]}default:return"none"}}function S(e,n={}){let t=()=>n.path===void 0?{kind:"route"}:{kind:"route",path:n.path};if(g(e)==="region")return t();let o=e.sourceChain??[],r=o[0],i=e.source??r;if(i&&r&&l(i,r)){let a={kind:"source-ref",file:i.file,line:i.line,chain:o};return i.column!==void 0&&(a.column=i.column),n.ordinal!==void 0&&(a.ordinal=n.ordinal),a}return e.domPath?{kind:"dom-path",path:e.domPath}:t()}function _(e,n){let t={total:e.length,placed:0,ambiguous:0,unresolved:0};for(let o of e){let r=A(o,n);r==="ambiguous"?t.ambiguous+=1:r==="none"?t.unresolved+=1:t.placed+=1}return t}
1
+ "use strict";var f=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var T=(e,n)=>{for(var t in n)f(e,t,{get:n[t],enumerable:!0})},R=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of E(n))!b.call(e,r)&&r!==t&&f(e,r,{get:()=>n[r],enumerable:!(o=x(n,r))||o.enumerable});return e};var y=e=>R(f({},"__esModule",{value:!0}),e);var O={};T(O,{anchorFromTarget:()=>S,resolveAnchor:()=>A,tallyAnchors:()=>_});module.exports=y(O);var u="data-src";function g(e){return e.selection==="region"?"region":"element"}var m=8;function s(e){if(!e)return null;let n=e.lastIndexOf(":");if(n<=0)return null;let t=e.lastIndexOf(":",n-1);if(t<=0)return null;let o=e.slice(0,t),r=Number(e.slice(t+1,n)),i=Number(e.slice(n+1));return!o||!Number.isFinite(r)||!Number.isFinite(i)?null:{file:o,line:r,column:i}}function d(e,n=m){let t=[],o=e;for(;o&&t.length<n;){let r=s(o.getAttribute(u));if(r){let i=t[t.length-1];i&&i.file===r.file&&i.line===r.line||t.push(r)}o=o.parentElement}return t}function h(e){let n=e;for(;n;){if(s(n.getAttribute(u)))return n;n=n.parentElement}return null}function l(e,n){return e.file!==n.file||e.line!==n.line?!1:e.column===void 0||n.column===void 0?!0:e.column===n.column}function k(e,n){if(e.length===0)return!1;if(e.length<m){if(n.length!==e.length)return!1}else if(n.length<e.length)return!1;return e.every((t,o)=>{let r=n[o];return r!==void 0&&l(t,r)})}function N(e,n){let t={file:e.file,line:e.line};e.column!==void 0&&(t.column=e.column);let o=n.querySelectorAll(`[${u}]`),r=[];for(let i=0;i<o.length;i++){let a=o[i],c=s(a.getAttribute(u));if(!c||!l(t,c))continue;let p=s(h(a.parentElement)?.getAttribute(u)??null);p&&l(p,c)||r.push(a)}return r}function A(e,n){switch(e.kind){case"route":{if(!e.path)return"none";let t=n.location?.pathname;return!t||t!==e.path?"none":n.body??"none"}case"dom-path":{if(!e.path)return"none";let t;try{t=n.querySelectorAll(e.path)}catch{return"none"}return t.length===0?"none":t.length>1?"ambiguous":t[0]}case"source-ref":{if(e.chain.length===0)return"none";let t=N(e,n).filter(o=>k(e.chain,d(o)));return t.length===0?"none":t.length>1?"ambiguous":t[0]}default:return"none"}}function S(e,n={}){let t=()=>n.path===void 0?{kind:"route"}:{kind:"route",path:n.path};if(g(e)==="region")return t();let o=e.sourceChain??[],r=o[0],i=e.source??r;if(i&&r&&l(i,r)){let a={kind:"source-ref",file:i.file,line:i.line,chain:o};return i.column!==void 0&&(a.column=i.column),n.ordinal!==void 0&&(a.ordinal=n.ordinal),a}return e.domPath?{kind:"dom-path",path:e.domPath}:t()}function _(e,n){let t={total:e.length,placed:0,ambiguous:0,unresolved:0};for(let o of e){let r=A(o,n);r==="ambiguous"?t.ambiguous+=1:r==="none"?t.unresolved+=1:t.placed+=1}return t}
2
2
  //# sourceMappingURL=anchor.cjs.map