@weatherboard/annotation 0.2.2 → 0.4.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.
- package/ADOPTING.md +96 -0
- package/README.md +9 -1
- package/dist/anchor.cjs +2 -0
- package/dist/anchor.cjs.map +7 -0
- package/dist/anchor.d.cts +295 -0
- package/dist/anchor.d.mts +295 -0
- package/dist/anchor.d.ts +295 -0
- package/dist/anchor.mjs +2 -0
- package/dist/anchor.mjs.map +7 -0
- package/dist/arming.d.cts +30 -2
- package/dist/arming.d.mts +30 -2
- package/dist/arming.d.ts +30 -2
- package/dist/host.cjs +1 -1
- package/dist/host.cjs.map +1 -1
- package/dist/host.mjs.map +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +3 -3
- package/dist/markers.cjs +64 -0
- package/dist/markers.cjs.map +7 -0
- package/dist/markers.d.cts +283 -0
- package/dist/markers.d.mts +283 -0
- package/dist/markers.d.ts +283 -0
- package/dist/markers.mjs +64 -0
- package/dist/markers.mjs.map +7 -0
- package/dist/protocol.cjs +1 -1
- package/dist/protocol.cjs.map +3 -3
- package/dist/protocol.d.cts +29 -0
- package/dist/protocol.d.mts +29 -0
- package/dist/protocol.d.ts +29 -0
- package/dist/protocol.mjs +1 -1
- package/dist/protocol.mjs.map +3 -3
- package/dist/read.cjs +2 -0
- package/dist/read.cjs.map +7 -0
- package/dist/read.d.cts +194 -0
- package/dist/read.d.mts +194 -0
- package/dist/read.d.ts +194 -0
- package/dist/read.mjs +2 -0
- package/dist/read.mjs.map +7 -0
- package/dist/transport.d.cts +7 -1
- package/dist/transport.d.mts +7 -1
- package/dist/transport.d.ts +7 -1
- package/package.json +48 -5
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { resolveAnchor } from "./anchor.mjs";
|
|
2
|
+
import type { AnnotationMarker, AnnotationReader } from "./read.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Markers on the components a reviewer annotated, and the panel behind them
|
|
5
|
+
* (A-277).
|
|
6
|
+
*
|
|
7
|
+
* ===========================================================================
|
|
8
|
+
* WHY THIS IS A SIXTH ENTRY POINT AND NOT PART OF `.`
|
|
9
|
+
* ===========================================================================
|
|
10
|
+
*
|
|
11
|
+
* `./anchor` and `./read` both make the same argument and it applies here with
|
|
12
|
+
* the most weight: this runs only in a product that RENDERS markers, and the SDK
|
|
13
|
+
* that only captures does not need a byte of it. Folding it into `index` would
|
|
14
|
+
* put the marker layer, the panel and its stylesheet into the bundle of every
|
|
15
|
+
* page of every managed product, including the ones that will never show one.
|
|
16
|
+
*
|
|
17
|
+
* ===========================================================================
|
|
18
|
+
* THE COUNT LINE IS THE FEATURE. THE ICONS ARE THE GARNISH.
|
|
19
|
+
* ===========================================================================
|
|
20
|
+
*
|
|
21
|
+
* On a read path **zero markers is exactly what "working, nothing here" looks
|
|
22
|
+
* like**, and the runbook lists twelve distinct causes that produce it. So an
|
|
23
|
+
* armed page ALWAYS renders the line, before and after the read, whatever the
|
|
24
|
+
* answer — *"3 annotations here, 1 shown"* — and that is not a status indicator,
|
|
25
|
+
* it is the only thing that makes an anchor which cannot place VISIBLE rather
|
|
26
|
+
* than silent.
|
|
27
|
+
*
|
|
28
|
+
* The sentence is built from the PAGE's own tally, with the envelope's `total`
|
|
29
|
+
* as the denominator. It is deliberately not built from the envelope's
|
|
30
|
+
* `anchored`: the server has no DOM and cannot know whether a marker lands, and
|
|
31
|
+
* two numbers called "placed" meaning different things is the shape this
|
|
32
|
+
* repository keeps getting burned by. `anchored` is a diagnostic for the
|
|
33
|
+
* runbook — it separates *the console has nothing for this page* from *the
|
|
34
|
+
* console has three and your DOM matched none of them* — and it is exposed on
|
|
35
|
+
* `MarkerRender` rather than rendered as though it were the page's answer.
|
|
36
|
+
*
|
|
37
|
+
* ===========================================================================
|
|
38
|
+
* MOST MARKERS ON A LIST PAGE ARE ROUTE-LEVEL, AND THAT IS A DESIGNED STATE
|
|
39
|
+
* ===========================================================================
|
|
40
|
+
*
|
|
41
|
+
* MEASURED by A-275 against live staging pages: on a listing page only about
|
|
42
|
+
* **25%** of annotated elements resolve uniquely, and about 75% are ambiguous
|
|
43
|
+
* identical siblings. That is not a defect to be worked around — a source ref
|
|
44
|
+
* names a COMPONENT, and two identical cards are genuinely indistinguishable
|
|
45
|
+
* from one. Wrong-placement was **0** across every mutation tested, and it is
|
|
46
|
+
* zero because the resolver refuses rather than guesses.
|
|
47
|
+
*
|
|
48
|
+
* So this layer is built for the refusing case rather than treating it as
|
|
49
|
+
* degraded:
|
|
50
|
+
*
|
|
51
|
+
* - an `"ambiguous"` anchor NEVER gets a marker. A marker on the wrong element
|
|
52
|
+
* does not degrade this feature, it inverts it.
|
|
53
|
+
* - markers that resolve to the SAME element are drawn as one icon carrying a
|
|
54
|
+
* count, and the panel lists all of them. Without that, a route anchor — the
|
|
55
|
+
* honest home for a region capture and for any page with no source refs —
|
|
56
|
+
* would stack every marker on the page at one point, and three captures would
|
|
57
|
+
* render as one icon with two hidden underneath it.
|
|
58
|
+
* - a route anchor resolves to `document.body`, so its cluster sits in the
|
|
59
|
+
* page's own top-right corner and reads as *"about this page"*. That is what
|
|
60
|
+
* the anchor means.
|
|
61
|
+
*
|
|
62
|
+
* ===========================================================================
|
|
63
|
+
* IT NEVER RE-RESOLVES SOMETHING IT DID NOT READ FROM THE CONSOLE
|
|
64
|
+
* ===========================================================================
|
|
65
|
+
*
|
|
66
|
+
* `resolveAnchor` is IMPORTED from `./anchor` and nothing here reimplements any
|
|
67
|
+
* part of it. That is A-275's whole point: the function that measured the scheme
|
|
68
|
+
* and the function that places the markers must be one function, or the
|
|
69
|
+
* measurement is about code nobody runs.
|
|
70
|
+
*
|
|
71
|
+
* ===========================================================================
|
|
72
|
+
* THIS RUNS INSIDE SOMEBODY ELSE'S PRODUCTION SITE
|
|
73
|
+
* ===========================================================================
|
|
74
|
+
*
|
|
75
|
+
* Everything rendered here came off the network. `read.ts` sanitises it — bounded
|
|
76
|
+
* strings, control characters dropped, a marker that cannot be read skipped — and
|
|
77
|
+
* this file adds the second half of that guarantee: every value reaches the DOM
|
|
78
|
+
* through `textContent` and never through `innerHTML`, so a console that has been
|
|
79
|
+
* compromised or has merely gone wrong cannot put markup into a customer's page.
|
|
80
|
+
* There is no `href` anywhere in this layer, which is why `safeIssueUrl` has no
|
|
81
|
+
* caller here.
|
|
82
|
+
*
|
|
83
|
+
* The whole layer lives in a shadow root, so the product's stylesheet cannot
|
|
84
|
+
* reach in and this cannot reach out.
|
|
85
|
+
*/
|
|
86
|
+
/** The layer's host node. Namespaced, and never product-named (A-98). */
|
|
87
|
+
export declare const MARKERS_ID = "ai-annotation-markers";
|
|
88
|
+
export interface AnnotationMarkerLabels {
|
|
89
|
+
/** The region the whole layer announces itself as. */
|
|
90
|
+
region: string;
|
|
91
|
+
/**
|
|
92
|
+
* Built with the three counts: `(total, shown, resolvedHidden) => string`.
|
|
93
|
+
*
|
|
94
|
+
* `resolvedHidden` is a third argument rather than a separate line because a
|
|
95
|
+
* count in one place and a caveat in another is how a reader concludes the
|
|
96
|
+
* first number was the whole answer. A consumer overriding this with a
|
|
97
|
+
* two-parameter function still typechecks and still gets the first two
|
|
98
|
+
* numbers — but it will not say what is being held back, which is the one
|
|
99
|
+
* thing this argument exists for.
|
|
100
|
+
*/
|
|
101
|
+
count: (total: number, shown: number, resolvedHidden: number) => string;
|
|
102
|
+
/**
|
|
103
|
+
* The control that widens the read, and the label it carries in each state.
|
|
104
|
+
*
|
|
105
|
+
* A count with no way to act on it is a fact a reviewer can do nothing with;
|
|
106
|
+
* a control with no count is one nobody knows to press. Both, or neither.
|
|
107
|
+
*/
|
|
108
|
+
showResolved: (resolvedHidden: number) => string;
|
|
109
|
+
hideResolved: string;
|
|
110
|
+
/** Shown before the first read completes. */
|
|
111
|
+
loading: string;
|
|
112
|
+
/** The console answered and refused. `reason` is the envelope's, or empty. */
|
|
113
|
+
refused: (reason: string) => string;
|
|
114
|
+
/** The read did not complete at all — no status, no body. */
|
|
115
|
+
unreachable: string;
|
|
116
|
+
/** No read token, so nothing was even attempted. */
|
|
117
|
+
noCredential: string;
|
|
118
|
+
/** Freshness, from `asOf`. */
|
|
119
|
+
asOf: (age: string) => string;
|
|
120
|
+
freshnessUnknown: string;
|
|
121
|
+
close: string;
|
|
122
|
+
}
|
|
123
|
+
export declare const DEFAULT_MARKER_LABELS: AnnotationMarkerLabels;
|
|
124
|
+
export interface AnnotationMarkersOptions {
|
|
125
|
+
/**
|
|
126
|
+
* The read client, from `createAnnotationReader`. Required: a marker layer
|
|
127
|
+
* with no way to read is a layer that renders an empty page confidently.
|
|
128
|
+
*/
|
|
129
|
+
reader: AnnotationReader | null;
|
|
130
|
+
/**
|
|
131
|
+
* May this page render markers at all?
|
|
132
|
+
*
|
|
133
|
+
* `false` renders NOTHING — no host node, no shadow root, no stylesheet —
|
|
134
|
+
* exactly as `createAnnotationToolbar` does, and for the same reason: an
|
|
135
|
+
* unarmed page that shipped this code must leave no residue in a customer's
|
|
136
|
+
* DOM. Every method still answers, so a caller does not have to branch.
|
|
137
|
+
*/
|
|
138
|
+
armed?: boolean;
|
|
139
|
+
doc?: Document;
|
|
140
|
+
win?: Window;
|
|
141
|
+
/**
|
|
142
|
+
* The path to ask about. Defaults to `location.pathname`.
|
|
143
|
+
*
|
|
144
|
+
* The RESOLVED path and never the route pattern. A pattern is one value for
|
|
145
|
+
* every page of a dynamic route, so asking by pattern returns another page's
|
|
146
|
+
* captures — measured at 161 of 414 anchors placing confidently on the wrong
|
|
147
|
+
* `[slug]`, with zero refusals, in `docs/decisions/a-275-anchor-placement.md`.
|
|
148
|
+
*/
|
|
149
|
+
path?: string;
|
|
150
|
+
labels?: Partial<AnnotationMarkerLabels>;
|
|
151
|
+
/**
|
|
152
|
+
* Start with the resolved captures showing (A-288/A-323). Defaults to `false`.
|
|
153
|
+
*
|
|
154
|
+
* The default matches the console's, deliberately: a layer that asked for
|
|
155
|
+
* everything on every page would put the default back where A-288 decided it
|
|
156
|
+
* should not be, from the other side of the wire, and nobody would see it in
|
|
157
|
+
* the console's code.
|
|
158
|
+
*/
|
|
159
|
+
showResolved?: boolean;
|
|
160
|
+
/** For the age in the freshness line. Defaults to `Date.now`. */
|
|
161
|
+
now?: () => number;
|
|
162
|
+
}
|
|
163
|
+
/** What one render actually put on the page. Returned so a caller can assert on it. */
|
|
164
|
+
export interface MarkerRender {
|
|
165
|
+
/** The envelope's `total` — what the console holds for this path. */
|
|
166
|
+
total: number;
|
|
167
|
+
/** Markers whose anchor resolved to exactly one element, and were drawn. */
|
|
168
|
+
shown: number;
|
|
169
|
+
/** Anchors that resolved to more than one element. NEVER drawn. */
|
|
170
|
+
ambiguous: number;
|
|
171
|
+
/** Anchors that matched nothing in this document. */
|
|
172
|
+
unresolved: number;
|
|
173
|
+
/**
|
|
174
|
+
* The CONSOLE's count of anchors it could serve as placeable at all.
|
|
175
|
+
*
|
|
176
|
+
* Carried through for the runbook's checklist and deliberately NOT rendered as
|
|
177
|
+
* though it were `shown`. See this file's header.
|
|
178
|
+
*/
|
|
179
|
+
anchored: number;
|
|
180
|
+
/**
|
|
181
|
+
* The envelope's `resolvedHidden` — finished captures the console held back.
|
|
182
|
+
*
|
|
183
|
+
* Zero while `showingResolved` is true, because nothing is then being held
|
|
184
|
+
* back. That is the console's number and not an inference: a layer that
|
|
185
|
+
* remembered the previous read's figure would keep reporting it after the
|
|
186
|
+
* capture had been archived.
|
|
187
|
+
*/
|
|
188
|
+
resolvedHidden: number;
|
|
189
|
+
/** Whether this render asked for `?resolved=include`. */
|
|
190
|
+
showingResolved: boolean;
|
|
191
|
+
/** Distinct elements drawn on. Lower than `shown` wherever markers clustered. */
|
|
192
|
+
clusters: number;
|
|
193
|
+
/** The envelope's `asOf` — the console's Linear cursor, or `""`. */
|
|
194
|
+
asOf: string;
|
|
195
|
+
/** The line a human reads. Exactly the text in the DOM. */
|
|
196
|
+
line: string;
|
|
197
|
+
}
|
|
198
|
+
export interface AnnotationMarkers {
|
|
199
|
+
/** Read, resolve, draw. Never rejects: a failure is a rendered sentence. */
|
|
200
|
+
refresh(): Promise<MarkerRender>;
|
|
201
|
+
/** Is this node ours? For an SDK that must not capture its own furniture. */
|
|
202
|
+
owns(node: unknown): boolean;
|
|
203
|
+
/** The last render, or null before the first. */
|
|
204
|
+
last(): MarkerRender | null;
|
|
205
|
+
destroy(): void;
|
|
206
|
+
}
|
|
207
|
+
export interface Spot {
|
|
208
|
+
left: number;
|
|
209
|
+
top: number;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* A position for this pin that no already-placed pin occupies — or, when there
|
|
213
|
+
* is none, the ideal one, OVERLAPPING.
|
|
214
|
+
*
|
|
215
|
+
* ===========================================================================
|
|
216
|
+
* IT GIVES UP. THAT IS THE WHOLE POINT OF THIS FUNCTION EXISTING.
|
|
217
|
+
* ===========================================================================
|
|
218
|
+
*
|
|
219
|
+
* This was a bare `while` loop with no exit but success, and it could not
|
|
220
|
+
* terminate. Once `left` floored at `MARGIN` the wrap computed
|
|
221
|
+
* `Math.max(MARGIN, MARGIN - STEP) === MARGIN` and `top = MARGIN`, so the search
|
|
222
|
+
* returned to a position it had already rejected and tried it again, forever.
|
|
223
|
+
*
|
|
224
|
+
* REPRODUCED, not reasoned about: two clusters on two distinct elements with
|
|
225
|
+
* `innerWidth` and `innerHeight` both 0 — `refresh()` never returned, killed at
|
|
226
|
+
* 20s. `position()` is called by `draw()` and by every `scroll` and `resize`
|
|
227
|
+
* listener, so that is **a locked main thread inside a customer's production
|
|
228
|
+
* page**, which is strictly worse than the thrown read `refresh()` refuses to
|
|
229
|
+
* allow.
|
|
230
|
+
*
|
|
231
|
+
* A zero-sized viewport is not exotic: a hidden iframe, a prerender, a host that
|
|
232
|
+
* stubs `win`. Two pins are then enough. A short embedded viewport reaches it
|
|
233
|
+
* honestly too — 300×200 offers about 77 slots against a possible 100 pins.
|
|
234
|
+
*
|
|
235
|
+
* And the irony is the useful part. `linkedom` returns zeros for every rect,
|
|
236
|
+
* which is exactly why the clamp above had to be found in a browser — and it is
|
|
237
|
+
* exactly why no fixture test saw this. **The zero-rect case is a layout hazard
|
|
238
|
+
* AND a termination hazard, and the first fix addressed only the layout.**
|
|
239
|
+
*
|
|
240
|
+
* So the walk is bounded twice over: `MAX_PROBES`, and a `left` that has run out
|
|
241
|
+
* of columns. When it gives up, the pin goes at its clamped ideal and is allowed
|
|
242
|
+
* to sit on top of another. **An overlapping marker is a visible imperfection; a
|
|
243
|
+
* locked main thread is the product broken.**
|
|
244
|
+
*
|
|
245
|
+
* Exported so `markers.test.ts` can assert termination directly, under
|
|
246
|
+
* `linkedom`, with no browser: only the QUALITY of the placement needs one.
|
|
247
|
+
*/
|
|
248
|
+
export declare function freeSpot(ideal: Spot, taken: readonly Spot[], maxTop: number): Spot;
|
|
249
|
+
/**
|
|
250
|
+
* A duration a human reads. Two units at most, mirroring `fleet/freshness.ts` —
|
|
251
|
+
* the point of the line is "is this minutes or hours", and the seconds in
|
|
252
|
+
* "2h 10m 4s" are noise that makes the answer take longer to find.
|
|
253
|
+
*
|
|
254
|
+
* Reimplemented rather than imported because that module is server-side and this
|
|
255
|
+
* package must not depend on the console. It is eleven lines of arithmetic with
|
|
256
|
+
* a test on it, which is the honest version of that trade.
|
|
257
|
+
*/
|
|
258
|
+
export declare function formatMarkerAge(ms: number): string;
|
|
259
|
+
/**
|
|
260
|
+
* Group markers by the element each one resolves to, refusing the ambiguous.
|
|
261
|
+
*
|
|
262
|
+
* Exported and pure so the placement rule is testable over a parsed fixture
|
|
263
|
+
* without a browser — the same split `anchor.test.ts` uses, and the reason this
|
|
264
|
+
* layer's most important behaviour (`ambiguous` is never drawn) is covered by
|
|
265
|
+
* `pnpm verify` rather than only by a browser nobody schedules.
|
|
266
|
+
*/
|
|
267
|
+
export declare function placeMarkers(markers: readonly AnnotationMarker[], doc: Parameters<typeof resolveAnchor>[1]): {
|
|
268
|
+
clusters: {
|
|
269
|
+
element: unknown;
|
|
270
|
+
markers: AnnotationMarker[];
|
|
271
|
+
}[];
|
|
272
|
+
ambiguous: number;
|
|
273
|
+
unresolved: number;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Mount the marker layer.
|
|
277
|
+
*
|
|
278
|
+
* Does not read anything until `refresh()` is called, so a caller decides when
|
|
279
|
+
* the page is settled enough to measure. The count line renders `loading`
|
|
280
|
+
* immediately, because an armed page with nothing on it at all is the state this
|
|
281
|
+
* whole layer exists to make impossible.
|
|
282
|
+
*/
|
|
283
|
+
export declare function mountAnnotationMarkers(options: AnnotationMarkersOptions): AnnotationMarkers;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { resolveAnchor } from "./anchor.js";
|
|
2
|
+
import type { AnnotationMarker, AnnotationReader } from "./read.js";
|
|
3
|
+
/**
|
|
4
|
+
* Markers on the components a reviewer annotated, and the panel behind them
|
|
5
|
+
* (A-277).
|
|
6
|
+
*
|
|
7
|
+
* ===========================================================================
|
|
8
|
+
* WHY THIS IS A SIXTH ENTRY POINT AND NOT PART OF `.`
|
|
9
|
+
* ===========================================================================
|
|
10
|
+
*
|
|
11
|
+
* `./anchor` and `./read` both make the same argument and it applies here with
|
|
12
|
+
* the most weight: this runs only in a product that RENDERS markers, and the SDK
|
|
13
|
+
* that only captures does not need a byte of it. Folding it into `index` would
|
|
14
|
+
* put the marker layer, the panel and its stylesheet into the bundle of every
|
|
15
|
+
* page of every managed product, including the ones that will never show one.
|
|
16
|
+
*
|
|
17
|
+
* ===========================================================================
|
|
18
|
+
* THE COUNT LINE IS THE FEATURE. THE ICONS ARE THE GARNISH.
|
|
19
|
+
* ===========================================================================
|
|
20
|
+
*
|
|
21
|
+
* On a read path **zero markers is exactly what "working, nothing here" looks
|
|
22
|
+
* like**, and the runbook lists twelve distinct causes that produce it. So an
|
|
23
|
+
* armed page ALWAYS renders the line, before and after the read, whatever the
|
|
24
|
+
* answer — *"3 annotations here, 1 shown"* — and that is not a status indicator,
|
|
25
|
+
* it is the only thing that makes an anchor which cannot place VISIBLE rather
|
|
26
|
+
* than silent.
|
|
27
|
+
*
|
|
28
|
+
* The sentence is built from the PAGE's own tally, with the envelope's `total`
|
|
29
|
+
* as the denominator. It is deliberately not built from the envelope's
|
|
30
|
+
* `anchored`: the server has no DOM and cannot know whether a marker lands, and
|
|
31
|
+
* two numbers called "placed" meaning different things is the shape this
|
|
32
|
+
* repository keeps getting burned by. `anchored` is a diagnostic for the
|
|
33
|
+
* runbook — it separates *the console has nothing for this page* from *the
|
|
34
|
+
* console has three and your DOM matched none of them* — and it is exposed on
|
|
35
|
+
* `MarkerRender` rather than rendered as though it were the page's answer.
|
|
36
|
+
*
|
|
37
|
+
* ===========================================================================
|
|
38
|
+
* MOST MARKERS ON A LIST PAGE ARE ROUTE-LEVEL, AND THAT IS A DESIGNED STATE
|
|
39
|
+
* ===========================================================================
|
|
40
|
+
*
|
|
41
|
+
* MEASURED by A-275 against live staging pages: on a listing page only about
|
|
42
|
+
* **25%** of annotated elements resolve uniquely, and about 75% are ambiguous
|
|
43
|
+
* identical siblings. That is not a defect to be worked around — a source ref
|
|
44
|
+
* names a COMPONENT, and two identical cards are genuinely indistinguishable
|
|
45
|
+
* from one. Wrong-placement was **0** across every mutation tested, and it is
|
|
46
|
+
* zero because the resolver refuses rather than guesses.
|
|
47
|
+
*
|
|
48
|
+
* So this layer is built for the refusing case rather than treating it as
|
|
49
|
+
* degraded:
|
|
50
|
+
*
|
|
51
|
+
* - an `"ambiguous"` anchor NEVER gets a marker. A marker on the wrong element
|
|
52
|
+
* does not degrade this feature, it inverts it.
|
|
53
|
+
* - markers that resolve to the SAME element are drawn as one icon carrying a
|
|
54
|
+
* count, and the panel lists all of them. Without that, a route anchor — the
|
|
55
|
+
* honest home for a region capture and for any page with no source refs —
|
|
56
|
+
* would stack every marker on the page at one point, and three captures would
|
|
57
|
+
* render as one icon with two hidden underneath it.
|
|
58
|
+
* - a route anchor resolves to `document.body`, so its cluster sits in the
|
|
59
|
+
* page's own top-right corner and reads as *"about this page"*. That is what
|
|
60
|
+
* the anchor means.
|
|
61
|
+
*
|
|
62
|
+
* ===========================================================================
|
|
63
|
+
* IT NEVER RE-RESOLVES SOMETHING IT DID NOT READ FROM THE CONSOLE
|
|
64
|
+
* ===========================================================================
|
|
65
|
+
*
|
|
66
|
+
* `resolveAnchor` is IMPORTED from `./anchor` and nothing here reimplements any
|
|
67
|
+
* part of it. That is A-275's whole point: the function that measured the scheme
|
|
68
|
+
* and the function that places the markers must be one function, or the
|
|
69
|
+
* measurement is about code nobody runs.
|
|
70
|
+
*
|
|
71
|
+
* ===========================================================================
|
|
72
|
+
* THIS RUNS INSIDE SOMEBODY ELSE'S PRODUCTION SITE
|
|
73
|
+
* ===========================================================================
|
|
74
|
+
*
|
|
75
|
+
* Everything rendered here came off the network. `read.ts` sanitises it — bounded
|
|
76
|
+
* strings, control characters dropped, a marker that cannot be read skipped — and
|
|
77
|
+
* this file adds the second half of that guarantee: every value reaches the DOM
|
|
78
|
+
* through `textContent` and never through `innerHTML`, so a console that has been
|
|
79
|
+
* compromised or has merely gone wrong cannot put markup into a customer's page.
|
|
80
|
+
* There is no `href` anywhere in this layer, which is why `safeIssueUrl` has no
|
|
81
|
+
* caller here.
|
|
82
|
+
*
|
|
83
|
+
* The whole layer lives in a shadow root, so the product's stylesheet cannot
|
|
84
|
+
* reach in and this cannot reach out.
|
|
85
|
+
*/
|
|
86
|
+
/** The layer's host node. Namespaced, and never product-named (A-98). */
|
|
87
|
+
export declare const MARKERS_ID = "ai-annotation-markers";
|
|
88
|
+
export interface AnnotationMarkerLabels {
|
|
89
|
+
/** The region the whole layer announces itself as. */
|
|
90
|
+
region: string;
|
|
91
|
+
/**
|
|
92
|
+
* Built with the three counts: `(total, shown, resolvedHidden) => string`.
|
|
93
|
+
*
|
|
94
|
+
* `resolvedHidden` is a third argument rather than a separate line because a
|
|
95
|
+
* count in one place and a caveat in another is how a reader concludes the
|
|
96
|
+
* first number was the whole answer. A consumer overriding this with a
|
|
97
|
+
* two-parameter function still typechecks and still gets the first two
|
|
98
|
+
* numbers — but it will not say what is being held back, which is the one
|
|
99
|
+
* thing this argument exists for.
|
|
100
|
+
*/
|
|
101
|
+
count: (total: number, shown: number, resolvedHidden: number) => string;
|
|
102
|
+
/**
|
|
103
|
+
* The control that widens the read, and the label it carries in each state.
|
|
104
|
+
*
|
|
105
|
+
* A count with no way to act on it is a fact a reviewer can do nothing with;
|
|
106
|
+
* a control with no count is one nobody knows to press. Both, or neither.
|
|
107
|
+
*/
|
|
108
|
+
showResolved: (resolvedHidden: number) => string;
|
|
109
|
+
hideResolved: string;
|
|
110
|
+
/** Shown before the first read completes. */
|
|
111
|
+
loading: string;
|
|
112
|
+
/** The console answered and refused. `reason` is the envelope's, or empty. */
|
|
113
|
+
refused: (reason: string) => string;
|
|
114
|
+
/** The read did not complete at all — no status, no body. */
|
|
115
|
+
unreachable: string;
|
|
116
|
+
/** No read token, so nothing was even attempted. */
|
|
117
|
+
noCredential: string;
|
|
118
|
+
/** Freshness, from `asOf`. */
|
|
119
|
+
asOf: (age: string) => string;
|
|
120
|
+
freshnessUnknown: string;
|
|
121
|
+
close: string;
|
|
122
|
+
}
|
|
123
|
+
export declare const DEFAULT_MARKER_LABELS: AnnotationMarkerLabels;
|
|
124
|
+
export interface AnnotationMarkersOptions {
|
|
125
|
+
/**
|
|
126
|
+
* The read client, from `createAnnotationReader`. Required: a marker layer
|
|
127
|
+
* with no way to read is a layer that renders an empty page confidently.
|
|
128
|
+
*/
|
|
129
|
+
reader: AnnotationReader | null;
|
|
130
|
+
/**
|
|
131
|
+
* May this page render markers at all?
|
|
132
|
+
*
|
|
133
|
+
* `false` renders NOTHING — no host node, no shadow root, no stylesheet —
|
|
134
|
+
* exactly as `createAnnotationToolbar` does, and for the same reason: an
|
|
135
|
+
* unarmed page that shipped this code must leave no residue in a customer's
|
|
136
|
+
* DOM. Every method still answers, so a caller does not have to branch.
|
|
137
|
+
*/
|
|
138
|
+
armed?: boolean;
|
|
139
|
+
doc?: Document;
|
|
140
|
+
win?: Window;
|
|
141
|
+
/**
|
|
142
|
+
* The path to ask about. Defaults to `location.pathname`.
|
|
143
|
+
*
|
|
144
|
+
* The RESOLVED path and never the route pattern. A pattern is one value for
|
|
145
|
+
* every page of a dynamic route, so asking by pattern returns another page's
|
|
146
|
+
* captures — measured at 161 of 414 anchors placing confidently on the wrong
|
|
147
|
+
* `[slug]`, with zero refusals, in `docs/decisions/a-275-anchor-placement.md`.
|
|
148
|
+
*/
|
|
149
|
+
path?: string;
|
|
150
|
+
labels?: Partial<AnnotationMarkerLabels>;
|
|
151
|
+
/**
|
|
152
|
+
* Start with the resolved captures showing (A-288/A-323). Defaults to `false`.
|
|
153
|
+
*
|
|
154
|
+
* The default matches the console's, deliberately: a layer that asked for
|
|
155
|
+
* everything on every page would put the default back where A-288 decided it
|
|
156
|
+
* should not be, from the other side of the wire, and nobody would see it in
|
|
157
|
+
* the console's code.
|
|
158
|
+
*/
|
|
159
|
+
showResolved?: boolean;
|
|
160
|
+
/** For the age in the freshness line. Defaults to `Date.now`. */
|
|
161
|
+
now?: () => number;
|
|
162
|
+
}
|
|
163
|
+
/** What one render actually put on the page. Returned so a caller can assert on it. */
|
|
164
|
+
export interface MarkerRender {
|
|
165
|
+
/** The envelope's `total` — what the console holds for this path. */
|
|
166
|
+
total: number;
|
|
167
|
+
/** Markers whose anchor resolved to exactly one element, and were drawn. */
|
|
168
|
+
shown: number;
|
|
169
|
+
/** Anchors that resolved to more than one element. NEVER drawn. */
|
|
170
|
+
ambiguous: number;
|
|
171
|
+
/** Anchors that matched nothing in this document. */
|
|
172
|
+
unresolved: number;
|
|
173
|
+
/**
|
|
174
|
+
* The CONSOLE's count of anchors it could serve as placeable at all.
|
|
175
|
+
*
|
|
176
|
+
* Carried through for the runbook's checklist and deliberately NOT rendered as
|
|
177
|
+
* though it were `shown`. See this file's header.
|
|
178
|
+
*/
|
|
179
|
+
anchored: number;
|
|
180
|
+
/**
|
|
181
|
+
* The envelope's `resolvedHidden` — finished captures the console held back.
|
|
182
|
+
*
|
|
183
|
+
* Zero while `showingResolved` is true, because nothing is then being held
|
|
184
|
+
* back. That is the console's number and not an inference: a layer that
|
|
185
|
+
* remembered the previous read's figure would keep reporting it after the
|
|
186
|
+
* capture had been archived.
|
|
187
|
+
*/
|
|
188
|
+
resolvedHidden: number;
|
|
189
|
+
/** Whether this render asked for `?resolved=include`. */
|
|
190
|
+
showingResolved: boolean;
|
|
191
|
+
/** Distinct elements drawn on. Lower than `shown` wherever markers clustered. */
|
|
192
|
+
clusters: number;
|
|
193
|
+
/** The envelope's `asOf` — the console's Linear cursor, or `""`. */
|
|
194
|
+
asOf: string;
|
|
195
|
+
/** The line a human reads. Exactly the text in the DOM. */
|
|
196
|
+
line: string;
|
|
197
|
+
}
|
|
198
|
+
export interface AnnotationMarkers {
|
|
199
|
+
/** Read, resolve, draw. Never rejects: a failure is a rendered sentence. */
|
|
200
|
+
refresh(): Promise<MarkerRender>;
|
|
201
|
+
/** Is this node ours? For an SDK that must not capture its own furniture. */
|
|
202
|
+
owns(node: unknown): boolean;
|
|
203
|
+
/** The last render, or null before the first. */
|
|
204
|
+
last(): MarkerRender | null;
|
|
205
|
+
destroy(): void;
|
|
206
|
+
}
|
|
207
|
+
export interface Spot {
|
|
208
|
+
left: number;
|
|
209
|
+
top: number;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* A position for this pin that no already-placed pin occupies — or, when there
|
|
213
|
+
* is none, the ideal one, OVERLAPPING.
|
|
214
|
+
*
|
|
215
|
+
* ===========================================================================
|
|
216
|
+
* IT GIVES UP. THAT IS THE WHOLE POINT OF THIS FUNCTION EXISTING.
|
|
217
|
+
* ===========================================================================
|
|
218
|
+
*
|
|
219
|
+
* This was a bare `while` loop with no exit but success, and it could not
|
|
220
|
+
* terminate. Once `left` floored at `MARGIN` the wrap computed
|
|
221
|
+
* `Math.max(MARGIN, MARGIN - STEP) === MARGIN` and `top = MARGIN`, so the search
|
|
222
|
+
* returned to a position it had already rejected and tried it again, forever.
|
|
223
|
+
*
|
|
224
|
+
* REPRODUCED, not reasoned about: two clusters on two distinct elements with
|
|
225
|
+
* `innerWidth` and `innerHeight` both 0 — `refresh()` never returned, killed at
|
|
226
|
+
* 20s. `position()` is called by `draw()` and by every `scroll` and `resize`
|
|
227
|
+
* listener, so that is **a locked main thread inside a customer's production
|
|
228
|
+
* page**, which is strictly worse than the thrown read `refresh()` refuses to
|
|
229
|
+
* allow.
|
|
230
|
+
*
|
|
231
|
+
* A zero-sized viewport is not exotic: a hidden iframe, a prerender, a host that
|
|
232
|
+
* stubs `win`. Two pins are then enough. A short embedded viewport reaches it
|
|
233
|
+
* honestly too — 300×200 offers about 77 slots against a possible 100 pins.
|
|
234
|
+
*
|
|
235
|
+
* And the irony is the useful part. `linkedom` returns zeros for every rect,
|
|
236
|
+
* which is exactly why the clamp above had to be found in a browser — and it is
|
|
237
|
+
* exactly why no fixture test saw this. **The zero-rect case is a layout hazard
|
|
238
|
+
* AND a termination hazard, and the first fix addressed only the layout.**
|
|
239
|
+
*
|
|
240
|
+
* So the walk is bounded twice over: `MAX_PROBES`, and a `left` that has run out
|
|
241
|
+
* of columns. When it gives up, the pin goes at its clamped ideal and is allowed
|
|
242
|
+
* to sit on top of another. **An overlapping marker is a visible imperfection; a
|
|
243
|
+
* locked main thread is the product broken.**
|
|
244
|
+
*
|
|
245
|
+
* Exported so `markers.test.ts` can assert termination directly, under
|
|
246
|
+
* `linkedom`, with no browser: only the QUALITY of the placement needs one.
|
|
247
|
+
*/
|
|
248
|
+
export declare function freeSpot(ideal: Spot, taken: readonly Spot[], maxTop: number): Spot;
|
|
249
|
+
/**
|
|
250
|
+
* A duration a human reads. Two units at most, mirroring `fleet/freshness.ts` —
|
|
251
|
+
* the point of the line is "is this minutes or hours", and the seconds in
|
|
252
|
+
* "2h 10m 4s" are noise that makes the answer take longer to find.
|
|
253
|
+
*
|
|
254
|
+
* Reimplemented rather than imported because that module is server-side and this
|
|
255
|
+
* package must not depend on the console. It is eleven lines of arithmetic with
|
|
256
|
+
* a test on it, which is the honest version of that trade.
|
|
257
|
+
*/
|
|
258
|
+
export declare function formatMarkerAge(ms: number): string;
|
|
259
|
+
/**
|
|
260
|
+
* Group markers by the element each one resolves to, refusing the ambiguous.
|
|
261
|
+
*
|
|
262
|
+
* Exported and pure so the placement rule is testable over a parsed fixture
|
|
263
|
+
* without a browser — the same split `anchor.test.ts` uses, and the reason this
|
|
264
|
+
* layer's most important behaviour (`ambiguous` is never drawn) is covered by
|
|
265
|
+
* `pnpm verify` rather than only by a browser nobody schedules.
|
|
266
|
+
*/
|
|
267
|
+
export declare function placeMarkers(markers: readonly AnnotationMarker[], doc: Parameters<typeof resolveAnchor>[1]): {
|
|
268
|
+
clusters: {
|
|
269
|
+
element: unknown;
|
|
270
|
+
markers: AnnotationMarker[];
|
|
271
|
+
}[];
|
|
272
|
+
ambiguous: number;
|
|
273
|
+
unresolved: number;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Mount the marker layer.
|
|
277
|
+
*
|
|
278
|
+
* Does not read anything until `refresh()` is called, so a caller decides when
|
|
279
|
+
* the page is settled enough to measure. The count line renders `loading`
|
|
280
|
+
* immediately, because an armed page with nothing on it at all is the state this
|
|
281
|
+
* whole layer exists to make impossible.
|
|
282
|
+
*/
|
|
283
|
+
export declare function mountAnnotationMarkers(options: AnnotationMarkersOptions): AnnotationMarkers;
|
package/dist/markers.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
var C="data-src";var B=8;function S(e){if(!e)return null;let t=e.lastIndexOf(":");if(t<=0)return null;let n=e.lastIndexOf(":",t-1);if(n<=0)return null;let r=e.slice(0,n),o=Number(e.slice(n+1,t)),i=Number(e.slice(t+1));return!r||!Number.isFinite(o)||!Number.isFinite(i)?null:{file:r,line:o,column:i}}function j(e,t=B){let n=[],r=e;for(;r&&n.length<t;){let o=S(r.getAttribute(C));if(o){let i=n[n.length-1];i&&i.file===o.file&&i.line===o.line||n.push(o)}r=r.parentElement}return n}function q(e){let t=e;for(;t;){if(S(t.getAttribute(C)))return t;t=t.parentElement}return null}function H(e,t){return e.file!==t.file||e.line!==t.line?!1:e.column===void 0||t.column===void 0?!0:e.column===t.column}function J(e,t){if(e.length===0)return!1;if(e.length<B){if(t.length!==e.length)return!1}else if(t.length<e.length)return!1;return e.every((n,r)=>{let o=t[r];return o!==void 0&&H(n,o)})}function Z(e,t){let n={file:e.file,line:e.line};e.column!==void 0&&(n.column=e.column);let r=t.querySelectorAll(`[${C}]`),o=[];for(let i=0;i<r.length;i++){let s=r[i],c=S(s.getAttribute(C));if(!c||!H(n,c))continue;let d=S(q(s.parentElement)?.getAttribute(C)??null);d&&H(d,c)||o.push(s)}return o}function V(e,t){switch(e.kind){case"route":{if(!e.path)return"none";let n=t.location?.pathname;return!n||n!==e.path?"none":t.body??"none"}case"dom-path":{if(!e.path)return"none";let n;try{n=t.querySelectorAll(e.path)}catch{return"none"}return n.length===0?"none":n.length>1?"ambiguous":n[0]}case"source-ref":{if(e.chain.length===0)return"none";let n=Z(e,t).filter(r=>J(e.chain,j(r)));return n.length===0?"none":n.length>1?"ambiguous":n[0]}default:return"none"}}var X="ai-annotation-markers",Q="2147483002",ee={region:"Annotations on this page",count:(e,t,n)=>{let r=e===0?"No annotations on this page":`${e} annotation${e===1?"":"s"} here, ${t} shown`;return n>0?`${r} \xB7 ${n} resolved`:r},showResolved:e=>`Show ${e} resolved annotation${e===1?"":"s"}`,hideResolved:"Hide resolved annotations",loading:"Looking for annotations\u2026",refused:e=>e==="retired-link"?"This annotation link is an older format the console no longer accepts. Open Request changes again.":e==="rate-limited"?"The console is rate-limiting this page's reads. Wait a moment.":"The console refused this page's read.",unreachable:"Could not reach the console for this page's annotations.",noCredential:"No read credential, so annotations were not requested.",asOf:e=>`status as of ${e} ago`,freshnessUnknown:"the console cannot say how fresh these statuses are",close:"Close"},K={total:0,shown:0,ambiguous:0,unresolved:0,anchored:0,resolvedHidden:0,showingResolved:!1,clusters:0,asOf:"",line:""},ne=`
|
|
2
|
+
:host{
|
|
3
|
+
--ai-bg:#fff;
|
|
4
|
+
--ai-fg:#18181b;
|
|
5
|
+
--ai-line:#d4d4d8;
|
|
6
|
+
--ai-line-soft:#e4e4e7;
|
|
7
|
+
--ai-muted:#52525b;
|
|
8
|
+
--ai-accent:#2563eb;
|
|
9
|
+
--ai-accent-line:#1d4ed8;
|
|
10
|
+
--ai-invert-bg:#18181b;
|
|
11
|
+
--ai-invert-fg:#fafafa;
|
|
12
|
+
--ai-space-1:4px;
|
|
13
|
+
--ai-space-2:6px;
|
|
14
|
+
--ai-space-3:8px;
|
|
15
|
+
--ai-space-4:10px;
|
|
16
|
+
--ai-space-5:12px;
|
|
17
|
+
--ai-radius-control:6px;
|
|
18
|
+
--ai-radius-card:8px;
|
|
19
|
+
--ai-radius-pill:999px;
|
|
20
|
+
--ai-pin:22px;
|
|
21
|
+
--ai-edge:16px;
|
|
22
|
+
all:initial;position:fixed;inset:0;z-index:${Q};pointer-events:none;
|
|
23
|
+
font:13px/1.45 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
|
24
|
+
color:var(--ai-fg);color-scheme:light}
|
|
25
|
+
*,*::before,*::after{box-sizing:border-box}
|
|
26
|
+
[hidden]{display:none!important}
|
|
27
|
+
.count{
|
|
28
|
+
position:absolute;left:var(--ai-edge);bottom:var(--ai-edge);pointer-events:auto;
|
|
29
|
+
background:var(--ai-invert-bg);color:var(--ai-invert-fg);
|
|
30
|
+
border-radius:var(--ai-radius-pill);
|
|
31
|
+
padding:var(--ai-space-2) var(--ai-space-5);max-width:min(60vw,420px);
|
|
32
|
+
display:flex;align-items:center;gap:var(--ai-space-3);flex-wrap:wrap}
|
|
33
|
+
.toggle{
|
|
34
|
+
pointer-events:auto;font:inherit;font-size:12px;white-space:nowrap;
|
|
35
|
+
background:var(--ai-invert-bg);color:var(--ai-invert-fg);
|
|
36
|
+
border:1px solid var(--ai-invert-fg);border-radius:var(--ai-radius-pill);
|
|
37
|
+
padding:0 var(--ai-space-3);cursor:pointer}
|
|
38
|
+
.toggle:focus-visible{outline:2px solid var(--ai-invert-fg);outline-offset:2px}
|
|
39
|
+
.pin{
|
|
40
|
+
position:absolute;pointer-events:auto;
|
|
41
|
+
width:var(--ai-pin);height:var(--ai-pin);
|
|
42
|
+
display:flex;align-items:center;justify-content:center;
|
|
43
|
+
border:1px solid var(--ai-accent-line);border-radius:var(--ai-radius-pill);
|
|
44
|
+
background:var(--ai-accent);color:var(--ai-invert-fg);
|
|
45
|
+
font:600 11px/1 ui-sans-serif,system-ui,sans-serif;cursor:pointer;padding:0}
|
|
46
|
+
.pin:focus-visible{outline:2px solid var(--ai-fg);outline-offset:2px}
|
|
47
|
+
.panel{
|
|
48
|
+
position:absolute;pointer-events:auto;width:min(320px,80vw);
|
|
49
|
+
background:var(--ai-bg);border:1px solid var(--ai-line);
|
|
50
|
+
border-radius:var(--ai-radius-card);padding:var(--ai-space-5);
|
|
51
|
+
max-height:50vh;overflow:auto}
|
|
52
|
+
.entry+.entry{margin-top:var(--ai-space-5);border-top:1px solid var(--ai-line-soft);
|
|
53
|
+
padding-top:var(--ai-space-5)}
|
|
54
|
+
.who{color:var(--ai-muted);font-size:12px;margin:0 0 var(--ai-space-1)}
|
|
55
|
+
.what{margin:0 0 var(--ai-space-2);white-space:pre-wrap;overflow-wrap:anywhere}
|
|
56
|
+
.state{margin:0;font-size:12px;color:var(--ai-muted)}
|
|
57
|
+
.fresh{margin:var(--ai-space-2) 0 0;font-size:11px;color:var(--ai-muted)}
|
|
58
|
+
.close{
|
|
59
|
+
pointer-events:auto;margin-top:var(--ai-space-4);font:inherit;
|
|
60
|
+
background:var(--ai-bg);color:var(--ai-fg);border:1px solid var(--ai-line);
|
|
61
|
+
border-radius:var(--ai-radius-control);
|
|
62
|
+
padding:var(--ai-space-1) var(--ai-space-4);cursor:pointer}
|
|
63
|
+
`,T=22,v=4,W=(e,t,n)=>Math.min(Math.max(e,t),n),G=T+2,te=128;function re(e,t,n){let{left:r,top:o}=e,i=(s,c)=>t.some(d=>Math.abs(d.left-s)<T&&Math.abs(d.top-c)<T);for(let s=0;s<te;s+=1){if(!i(r,o))return{left:r,top:o};if(o+=G,o>n&&(o=v,r-=G,r<v))break}return{left:e.left,top:e.top}}function u(e,t,n){let r=e.createElement(t);if(n)for(let o of Object.keys(n))r.setAttribute(o,n[o]??"");return r}function oe(e){let t=Math.max(0,Math.floor(e/1e3));if(t<60)return`${t}s`;let n=Math.floor(t/60);if(n<60)return`${n}m`;let r=Math.floor(n/60);if(r<24){let s=n%60;return s===0?`${r}h`:`${r}h ${s}m`}let o=Math.floor(r/24),i=r%24;return i===0?`${o}d`:`${o}d ${i}h`}function ae(e,t){let n=new Map,r=0,o=0;for(let i of e){let s=V(i.anchor,t);if(s==="ambiguous"){r+=1;continue}if(s==="none"){o+=1;continue}let c=n.get(s);c?c.push(i):n.set(s,[i])}return{clusters:[...n].map(([i,s])=>({element:i,markers:s})),ambiguous:r,unresolved:o}}function fe(e){let t={...ee,...e.labels};if(e.armed===!1)return{refresh:()=>Promise.resolve(K),owns:()=>!1,last:()=>null,destroy:()=>{}};let n=e.doc??document,r=e.win??n.defaultView??window,o=e.now??Date.now,i=u(n,"div",{id:X}),s=i.attachShadow({mode:"open"}),c=u(n,"style");c.textContent=ne,s.appendChild(c);let d=u(n,"style");s.appendChild(d);let O=u(n,"div",{class:"count",role:"status","aria-label":t.region}),k=u(n,"span",{class:"count-text"});k.textContent=t.loading,O.appendChild(k);let A=u(n,"button",{type:"button",class:"toggle",hidden:""});O.appendChild(A),s.appendChild(O);let h=u(n,"div",{class:"panel",role:"dialog","aria-modal":"false","aria-label":t.region,hidden:""});s.appendChild(h),n.body.appendChild(i);let M=null,E=[],N=!1,R=e.showResolved===!0;function _(a){if(R)A.textContent=t.hideResolved;else if(a>0)A.textContent=t.showResolved(a);else{A.setAttribute("hidden",""),A.textContent="";return}A.setAttribute("aria-pressed",R?"true":"false"),A.removeAttribute("hidden")}function P(){if(E.length===0){d.textContent="";return}let a=r.innerWidth||0,p=r.innerHeight||0,g=[],b=[];E.forEach(({element:w},l)=>{let y=w.getBoundingClientRect(),f=W(Math.round(y.right-11),v,Math.max(v,a-T-v)),m=W(Math.round(y.top-11),v,Math.max(v,p-T-v)),x=re({left:f,top:m},g,p-T-v);g.push(x),b.push(`.pin[data-i="${l}"]{left:${x.left}px;top:${x.top}px}`)}),d.textContent=b.join("")}let L=()=>{N||P()};r.addEventListener("scroll",L,!0),r.addEventListener("resize",L);function I(){for(h.setAttribute("hidden","");h.firstChild;)h.removeChild(h.firstChild)}function Y(a,p){I();for(let f of a){let m=u(n,"div",{class:"entry"}),x=u(n,"p",{class:"who"}),D=f.createdAt?new Date(f.createdAt):null;x.textContent=D!==null&&!Number.isNaN(D.getTime())?`${f.reviewer} \xB7 ${D.toISOString().slice(0,10)}`:f.reviewer,m.appendChild(x);let U=u(n,"p",{class:"what"});U.textContent=f.comment,m.appendChild(U);let z=u(n,"p",{class:"state"});z.textContent=f.status.display,m.appendChild(z),h.appendChild(m)}let g=u(n,"p",{class:"fresh"}),b=M?.asOf??"",w=b===""?NaN:Date.parse(b);g.textContent=Number.isNaN(w)?t.freshnessUnknown:t.asOf(oe(o()-w)),h.appendChild(g);let l=u(n,"button",{type:"button",class:"close"});l.textContent=t.close,l.addEventListener("click",()=>I()),h.appendChild(l);let y=p.getBoundingClientRect();d.textContent=`${d.textContent??""}.panel{left:${Math.round(Math.max(8,y.right-320))}px;top:${Math.round(y.top+20)}px}`,h.removeAttribute("hidden")}function $(a,p){for(let{pin:l}of E)l.remove();if(E=[],I(),a===null){k.textContent=p??t.unreachable,_(0);let l={...K,showingResolved:R,line:k.textContent};return M=l,P(),l}let g=ae(a.annotations,n),b=0;g.clusters.forEach((l,y)=>{let f=l.element,m=u(n,"button",{type:"button",class:"pin","data-i":String(y),"aria-label":`${l.markers.length} annotation${l.markers.length===1?"":"s"}`});m.textContent=l.markers.length===1?"1":String(l.markers.length),m.addEventListener("click",x=>{x.preventDefault(),x.stopPropagation(),Y(l.markers,f)}),s.appendChild(m),E.push({pin:m,element:f}),b+=l.markers.length}),k.textContent=a.ok||a.reason===void 0?t.count(a.total,b,a.resolvedHidden):t.refused(a.reason),_(a.resolvedHidden);let w={total:a.total,shown:b,ambiguous:g.ambiguous,unresolved:g.unresolved,anchored:a.anchored,resolvedHidden:a.resolvedHidden,showingResolved:R,clusters:g.clusters.length,asOf:a.asOf,line:k.textContent??""};return M=w,P(),w}A.addEventListener("click",a=>{a.preventDefault(),a.stopPropagation(),!N&&(R=!R,_(M?.resolvedHidden??0),k.textContent=t.loading,F.refresh())});let F={async refresh(){if(N)return K;if(e.reader===null)return $(null,t.noCredential);let a=e.path??n.location?.pathname??"/";try{let p=await e.reader.read(a,{includeResolved:R});return $(p,null)}catch{return $(null,t.unreachable)}},owns(a){if(a===i)return!0;let p=a;return typeof p?.closest=="function"?p.closest(`#${X}`)===i:!1},last:()=>M,destroy(){N=!0,r.removeEventListener("scroll",L,!0),r.removeEventListener("resize",L);for(let{pin:a}of E)a.remove();E=[],i.remove()}};return F}export{ee as DEFAULT_MARKER_LABELS,X as MARKERS_ID,oe as formatMarkerAge,re as freeSpot,fe as mountAnnotationMarkers,ae as placeMarkers};
|
|
64
|
+
//# sourceMappingURL=markers.mjs.map
|