@weatherboard/annotation 0.3.0 → 0.6.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 (66) hide show
  1. package/ADOPTING.md +146 -11
  2. package/COMPATIBILITY.md +15 -0
  3. package/README.md +33 -8
  4. package/dist/anchor.cjs +1 -1
  5. package/dist/anchor.cjs.map +2 -2
  6. package/dist/anchor.mjs +1 -1
  7. package/dist/anchor.mjs.map +2 -2
  8. package/dist/arming.d.cts +1 -3
  9. package/dist/arming.d.mts +1 -3
  10. package/dist/arming.d.ts +1 -3
  11. package/dist/bootstrap-runtime.d.cts +30 -0
  12. package/dist/bootstrap-runtime.d.mts +30 -0
  13. package/dist/bootstrap-runtime.d.ts +30 -0
  14. package/dist/bootstrap.cjs +2 -0
  15. package/dist/bootstrap.cjs.map +7 -0
  16. package/dist/bootstrap.d.cts +7 -0
  17. package/dist/bootstrap.d.mts +7 -0
  18. package/dist/bootstrap.d.ts +7 -0
  19. package/dist/bootstrap.mjs +2 -0
  20. package/dist/bootstrap.mjs.map +7 -0
  21. package/dist/host.cjs +1 -1
  22. package/dist/host.cjs.map +3 -3
  23. package/dist/host.mjs +1 -1
  24. package/dist/host.mjs.map +3 -3
  25. package/dist/index.cjs +4 -4
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.cts +1 -1
  28. package/dist/index.d.mts +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.mjs +4 -4
  31. package/dist/index.mjs.map +4 -4
  32. package/dist/markers.cjs +11 -4
  33. package/dist/markers.cjs.map +3 -3
  34. package/dist/markers.d.cts +39 -2
  35. package/dist/markers.d.mts +39 -2
  36. package/dist/markers.d.ts +39 -2
  37. package/dist/markers.mjs +11 -4
  38. package/dist/markers.mjs.map +3 -3
  39. package/dist/protocol.cjs +1 -1
  40. package/dist/protocol.cjs.map +3 -3
  41. package/dist/protocol.d.cts +7 -0
  42. package/dist/protocol.d.mts +7 -0
  43. package/dist/protocol.d.ts +7 -0
  44. package/dist/protocol.mjs +1 -1
  45. package/dist/protocol.mjs.map +3 -3
  46. package/dist/read.cjs +1 -1
  47. package/dist/read.cjs.map +3 -3
  48. package/dist/read.d.cts +46 -1
  49. package/dist/read.d.mts +46 -1
  50. package/dist/read.d.ts +46 -1
  51. package/dist/read.mjs +1 -1
  52. package/dist/read.mjs.map +3 -3
  53. package/dist/selection.cjs +5 -0
  54. package/dist/selection.cjs.map +7 -0
  55. package/dist/selection.d.cts +27 -0
  56. package/dist/selection.d.mts +27 -0
  57. package/dist/selection.d.ts +27 -0
  58. package/dist/selection.mjs +5 -0
  59. package/dist/selection.mjs.map +7 -0
  60. package/dist/session.d.cts +10 -0
  61. package/dist/session.d.mts +10 -0
  62. package/dist/session.d.ts +10 -0
  63. package/dist/transport.d.cts +10 -0
  64. package/dist/transport.d.mts +10 -0
  65. package/dist/transport.d.ts +10 -0
  66. package/package.json +37 -12
package/ADOPTING.md CHANGED
@@ -98,23 +98,40 @@ pointed at production anyway.
98
98
 
99
99
  ```ts
100
100
  // middleware.ts
101
- import { ANNOTATION_ARMED_COOKIE, readAnnotationToken } from "@weatherboard/annotation/protocol";
101
+ import {
102
+ ANNOTATION_ARMED_COOKIE,
103
+ ANNOTATION_ARMED_COOKIES,
104
+ ANNOTATION_TOKEN_PARAMS,
105
+ readAnnotationToken,
106
+ } from "@weatherboard/annotation/protocol";
102
107
 
103
108
  const presented = readAnnotationToken(request.nextUrl.searchParams);
104
109
  const expected = process.env.ANNOTATION_TOKEN;
110
+ const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
111
+ const overHttps = request.nextUrl.protocol === "https:";
105
112
 
106
113
  if (expected && presented && presented.length === expected.length && presented === expected) {
107
- const overHttps = request.nextUrl.protocol === "https:";
108
114
  response.cookies.set(ANNOTATION_ARMED_COOKIE, "1", {
109
115
  httpOnly: false, // the client bundle is what reads it
110
116
  sameSite: overHttps ? "none" : "lax",
111
117
  secure: overHttps,
112
118
  path: "/",
113
119
  });
120
+ } else if (attempted) {
121
+ for (const name of ANNOTATION_ARMED_COOKIES) {
122
+ response.cookies.set(name, "", {
123
+ httpOnly: false,
124
+ sameSite: overHttps ? "none" : "lax",
125
+ secure: overHttps,
126
+ path: "/",
127
+ maxAge: 0,
128
+ expires: new Date(0),
129
+ });
130
+ }
114
131
  }
115
132
  ```
116
133
 
117
- Four details, each of which has cost somebody a day:
134
+ Five details, each of which has cost somebody a day:
118
135
 
119
136
  - **The cookie holds `1`, never the token.** It records that the token was
120
137
  presented once, so navigation inside the frame stays armed without the secret
@@ -127,6 +144,10 @@ Four details, each of which has cost somebody a day:
127
144
  - **…but `None` is only honoured with `Secure`**, and `Secure` is dropped over
128
145
  plain http — so a local `next dev` would set a cookie the browser silently
129
146
  discards. Fall back to `Lax`, which is same-origin anyway.
147
+ - **A fresh rejected token expires both cookie spellings.** A browser may still
148
+ carry a valid cookie from an earlier link. Leaving it in place turns the new
149
+ rejection into an accepted launch and lets the old capture/read/run context
150
+ survive under a different review link.
130
151
 
131
152
  ### First-party, the cookie is an ordinary one — use `armedCookie()` (A-241)
132
153
 
@@ -140,12 +161,63 @@ Do not re-derive the attributes. `@weatherboard/annotation/protocol` exports the
140
161
  cookie itself, so the two halves cannot disagree:
141
162
 
142
163
  ```ts
143
- import { armedCookie, clearedCookie, ANNOTATION_ARMED_COOKIE } from "@weatherboard/annotation/protocol";
164
+ import {
165
+ armedCookie,
166
+ clearedCookie,
167
+ ANNOTATION_ARMED_COOKIES,
168
+ ANNOTATION_TOKEN_PARAMS,
169
+ readAnnotationToken,
170
+ } from "@weatherboard/annotation/protocol";
144
171
 
145
- // after YOUR comparison against YOUR secret has succeeded, and only then:
146
- response.headers.append("set-cookie", armedCookie());
172
+ const presented = readAnnotationToken(request.nextUrl.searchParams);
173
+ const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
174
+ if (presented && presented === process.env.ANNOTATION_TOKEN) {
175
+ response.headers.append("set-cookie", armedCookie());
176
+ } else if (attempted) {
177
+ for (const name of ANNOTATION_ARMED_COOKIES) {
178
+ response.headers.append("set-cookie", clearedCookie(name));
179
+ }
180
+ }
181
+ ```
182
+
183
+ Put the package bootstrap in the first raw script in `<head>`, before styles,
184
+ analytics and hydration:
185
+
186
+ ```tsx
187
+ import { annotationBootstrapScript } from "@weatherboard/annotation/bootstrap";
188
+
189
+ <script dangerouslySetInnerHTML={{ __html: annotationBootstrapScript() }} />
147
190
  ```
148
191
 
192
+ The script synchronously strips `ai_annotate`, its legacy spelling,
193
+ `ai_capture`, `ai_read` and `ai_run`. When middleware accepted the arming link,
194
+ it keeps capture/read/run in this tab's `sessionStorage`, so same-tab routing
195
+ and a hard reload remain armed after the URL is clean. When middleware rejected
196
+ the link, it persists only `{ status: "rejected", capture: "present" | "missing" }`.
197
+ No credential from an unarmed link is stored.
198
+
199
+ The client mount can consume that signal once and report a generic failure:
200
+
201
+ ```ts
202
+ import {
203
+ consumeAnnotationAttempt,
204
+ reportAnnotationStartupFailure,
205
+ } from "@weatherboard/annotation/bootstrap";
206
+
207
+ const attempt = consumeAnnotationAttempt();
208
+ if (attempt?.status === "rejected") {
209
+ return reportAnnotationStartupFailure("rejected-arming");
210
+ }
211
+ if (attempt?.capture === "missing") {
212
+ return reportAnnotationStartupFailure("missing-capture");
213
+ }
214
+ ```
215
+
216
+ The reporter owns one host, `#ai-annotation-startup-status`, and returns a
217
+ cleanup function. Import and mount failures use `"import-failed"` and
218
+ `"mount-failed"`. All copy is package-owned and contains no token or product
219
+ name.
220
+
149
221
  It takes no value argument on purpose — the cookie holds a marker, never the
150
222
  token, and the first bullet above is the reason. The page half calls
151
223
  `readArming()`, which reads that cookie, strips the tokens out of the URL with
@@ -341,20 +413,36 @@ panel with the comment, who raised it, when, and what has happened to it.
341
413
 
342
414
  ```tsx
343
415
  const { readToken } = readArming(); // from `ai_read`, stripped and stored
344
- if (!readToken) return; // a link minted before markers shipped
345
416
 
346
417
  const { createAnnotationReader } = await import("@weatherboard/annotation/read");
347
418
  const { mountAnnotationMarkers } = await import("@weatherboard/annotation/markers");
348
419
 
349
420
  const layer = mountAnnotationMarkers({
350
- reader: createAnnotationReader({
351
- endpoint: "https://<console>/ingest/annotations",
352
- readToken,
353
- }),
421
+ // `reader` is a REQUIRED NULLABLE, not an optional. Mount whenever armed and
422
+ // pass null when there is no token — the count line then says so on the page.
423
+ // Returning early instead leaves an armed page blank, which is the one state
424
+ // this layer exists to make impossible. See "The count line is the feature".
425
+ reader: readToken
426
+ ? createAnnotationReader({
427
+ endpoint: "https://<console>/ingest/annotations",
428
+ readToken,
429
+ })
430
+ : null,
354
431
  });
355
432
  await layer.refresh(); // and again after a route change
356
433
  ```
357
434
 
435
+ **Call `refresh()` even with a null reader.** It is what replaces the *"Looking
436
+ for annotations…"* line with *"No read credential, so annotations were not
437
+ requested."* Treating a null reader as "nothing to do" leaves an armed page
438
+ loading forever — a mistake that is not obvious from the signature, and one the
439
+ first adopter made.
440
+
441
+ **Treat an empty token as absent, not as a credential.** `createAnnotationReader`
442
+ throws on `""`, and `?ai_read=` with nothing after it is a query string anyone
443
+ can type. Guard on presence, or a stray character in a URL takes the whole mount
444
+ down and the toolbar with it.
445
+
358
446
  **It is a second credential, not the capture token.** The console refuses a write
359
447
  claim at the read endpoint and a read claim at the ingest, so handing
360
448
  `captureToken` to the reader does not degrade to a read — it fails. The read
@@ -376,6 +464,34 @@ the read, whatever the answer: *"3 annotations here, 1 shown"*. Zero markers is
376
464
  exactly what "working, nothing here" looks like and a dozen distinct causes
377
465
  produce it, so a layer that drew only icons would be silent in every one.
378
466
 
467
+ **A finished capture is hidden, and the line says how many.** The console
468
+ suppresses `done` and `cancelled` by default, so the sentence gains a third
469
+ clause when it is holding something back — *"3 annotations here, 1 shown · 2
470
+ resolved"* — and the pill grows a button that re-reads with them included. You
471
+ do not have to wire either one up; both are in the layer.
472
+
473
+ The clause matters most on the page where `total` is **zero and history is
474
+ not**: without it, a component with three finished conversations behind it reads
475
+ *"No annotations on this page"*, and a reviewer who fixed something and came back
476
+ to check what they had said would find no evidence there had ever been anything.
477
+ `total` counts what the response is about, so a suppressed capture is outside it
478
+ and is never added in — that is what keeps *"5 annotations here, 2 shown"* from
479
+ appearing on a page where nothing is wrong.
480
+
481
+ Pass `showResolved: true` to start wide. The default is `false`, matching the
482
+ console's own default deliberately: a layer that always asked for everything
483
+ would move that decision to a place nobody reading the console's code would see.
484
+
485
+ **If you drive the reader yourself**, `read(path, { includeResolved })` is the
486
+ only way to see them, and the console honours **only** the literal
487
+ `?resolved=include` — `true`, `1`, `yes`, `Include` and a trailing space all get
488
+ the default silently. Use the option rather than building the query string.
489
+
490
+ **Overriding `labels.count`?** It now takes a third argument,
491
+ `(total, shown, resolvedHidden)`. A two-parameter override still compiles and
492
+ still works — and will not say what is being held back, which is the one thing
493
+ that argument is for.
494
+
379
495
  **`refresh()` never rejects.** A refused CORS preflight arrives as `TypeError:
380
496
  Failed to fetch` with no status and no body; it becomes a sentence in the count
381
497
  line rather than an exception in your page.
@@ -446,3 +562,22 @@ misattributed.
446
562
  Every step above is a thing that, when absent, produces no error, no warning and
447
563
  no log line — only an absence. When you are debugging, prefer the check that
448
564
  distinguishes causes over the check that confirms your guess.
565
+
566
+ ## Host-owned contextual feedback
567
+
568
+ `@weatherboard/annotation/selection` exposes `relativePoint`,
569
+ `captureTextSelection`, `resolveTextSelection` and `prepareTextSelections` without
570
+ arming the developer SDK, mounting UI or sending network requests. Production
571
+ hosts such as Pitch own authentication, capture intent, target persistence and
572
+ permission-scoped reads. Import this subpath directly; importing the root SDK
573
+ still observes its existing production restrictions.
574
+
575
+ Mark authored text containers with `data-annotation-text="host-field-path"`.
576
+ Capture returns UTF-16 offsets, exact quote and bounded prefix/suffix using CRLF
577
+ normalisation only. A field path is evidence qualified by a host content revision,
578
+ not a permanent identity. Pass whether content is unchanged when resolving;
579
+ edited content requires a unique quote/context match. Missing or ambiguous
580
+ locations must remain reviewable as unplaced feedback. `prepareTextSelections`
581
+ shares one bounded text-map snapshot across annotations in a layout frame; discard
582
+ it after content or layout changes. Neither helper reads application controls as
583
+ authored text or persists DOM nodes.
package/COMPATIBILITY.md CHANGED
@@ -95,3 +95,18 @@ 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
+ ### Additive bootstrap subpath and hosted-mode metadata (A-1375)
108
+
109
+ Version 0.6.0 adds `./bootstrap` for synchronous credential stripping,
110
+ per-tab continuation and token-free startup status. It also adds the exact
111
+ `x-weatherboard-annotation-mode: first-party` constants to `./protocol`. The
112
+ wire payload version and all existing names remain unchanged.
package/README.md CHANGED
@@ -20,23 +20,37 @@ adding to your supply chain.
20
20
  npm i @weatherboard/annotation
21
21
  ```
22
22
 
23
- ## Six entry points
23
+ ## Eight entry points
24
24
 
25
25
  They are split by **who imports them**, not by topic.
26
26
 
27
27
  | Import | Runs where | For |
28
28
  |---|---|---|
29
29
  | `@weatherboard/annotation` | The browser | The page SDK — overlay, target resolution, state collection, screenshots |
30
+ | `@weatherboard/annotation/bootstrap` | The browser, before hydration | Immediate URL stripping, arming reads and token-free startup status |
30
31
  | `@weatherboard/annotation/protocol` | Anywhere, no DOM | The wire names and the two readers. This is what a middleware imports |
31
32
  | `@weatherboard/annotation/host` | The reviewing console | The parent half of the channel |
32
33
  | `@weatherboard/annotation/anchor` | The browser | `resolveAnchor` — where a capture's marker goes, or why it goes nowhere |
33
34
  | `@weatherboard/annotation/read` | The browser | `createAnnotationReader` — the captures already made on this page |
34
35
  | `@weatherboard/annotation/markers` | The browser | The marker layer and its panel |
36
+ | `@weatherboard/annotation/selection` | The browser | Authored-content text/point selection and placement helpers |
35
37
 
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.
38
+ The final four remain separate because products can choose capture, marker
39
+ rendering and authored-content selection independently. A product that merely
40
+ captures should not pay for readers, marker UI or selection helpers. They are
41
+ subpaths rather than a tree-shaking promise for the same reason `/protocol` is.
42
+
43
+ `/bootstrap` is also a subpath rather than a tree-shaking promise. Put
44
+ `annotationBootstrapScript()` in the first raw script in `<head>`, then keep the
45
+ root entry behind a dynamic import. A normal visitor pays for URL and cookie
46
+ reads without downloading the toolbar or state collector.
47
+
48
+ Package-authored browser UI keeps its styles zero-dependency too. Declare
49
+ colour, spacing and radius literals once as local `--ai-*` custom properties,
50
+ then consume them with `var()` in the same style string. Do not import a
51
+ product's design tokens: a toolbar or startup notice runs inside somebody
52
+ else's page, where those names and values are not ours. New runtime surfaces
53
+ must pass `pnpm gyde:check`; do not add their findings to the allowance ledger.
40
54
 
41
55
  `/protocol` is a subpath rather than a tree-shaking promise on purpose: an edge
42
56
  middleware is often bundled as CJS, and CJS does not shake. Importing the root
@@ -63,11 +77,21 @@ right is yours, because only you hold the secret.
63
77
 
64
78
  ```ts
65
79
  // middleware.ts
66
- import { ANNOTATION_ARMED_COOKIE, readAnnotationToken } from "@weatherboard/annotation/protocol";
80
+ import {
81
+ ANNOTATION_ARMED_COOKIE,
82
+ ANNOTATION_ARMED_COOKIES,
83
+ ANNOTATION_TOKEN_PARAMS,
84
+ readAnnotationToken,
85
+ } from "@weatherboard/annotation/protocol";
67
86
 
68
87
  const token = readAnnotationToken(request.nextUrl.searchParams);
69
- if (token !== undefined && token === process.env.ANNOTATION_TOKEN) {
88
+ const attempted = ANNOTATION_TOKEN_PARAMS.some((name) => request.nextUrl.searchParams.has(name));
89
+ if (token !== null && token === process.env.ANNOTATION_TOKEN) {
70
90
  response.cookies.set(ANNOTATION_ARMED_COOKIE, "1", { httpOnly: false, sameSite: "lax" });
91
+ } else if (attempted) {
92
+ for (const name of ANNOTATION_ARMED_COOKIES) {
93
+ response.cookies.set(name, "", { path: "/", maxAge: 0, expires: new Date(0) });
94
+ }
71
95
  }
72
96
  ```
73
97
 
@@ -75,7 +99,8 @@ if (token !== undefined && token === process.env.ANNOTATION_TOKEN) {
75
99
  for an absent *or empty* parameter. An empty token compared with `===` against
76
100
  your secret is a rejection; an empty token treated as present is an app armed
77
101
  with the empty string, which fails later and reads like a bad secret rather than
78
- a missing one.
102
+ a missing one. A fresh rejected attempt must expire **both** supported cookie
103
+ spellings so an already-armed browser cannot reuse its previous session.
79
104
 
80
105
  ## Source refs
81
106
 
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 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 O={};R(O,{anchorFromTarget:()=>S,resolveAnchor:()=>A,tallyAnchors:()=>_});module.exports=y(O);var s="data-src";function g(e){return e.selection==="region"?"region":"element"}var m=8;function u(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=u(o.getAttribute(s));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(u(n.getAttribute(s)))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(`[${s}]`),r=[];for(let i=0;i<o.length;i++){let a=o[i],c=u(a.getAttribute(s));if(!c||!l(t,c))continue;let p=u(h(a.parentElement)?.getAttribute(s)??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