@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weatherboard/annotation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Click an element in a running app, get the source location that rendered it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotation",
|
|
@@ -49,6 +49,36 @@
|
|
|
49
49
|
"default": "./dist/host.cjs"
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
"./anchor": {
|
|
53
|
+
"import": {
|
|
54
|
+
"types": "./dist/anchor.d.mts",
|
|
55
|
+
"default": "./dist/anchor.mjs"
|
|
56
|
+
},
|
|
57
|
+
"require": {
|
|
58
|
+
"types": "./dist/anchor.d.cts",
|
|
59
|
+
"default": "./dist/anchor.cjs"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"./read": {
|
|
63
|
+
"import": {
|
|
64
|
+
"types": "./dist/read.d.mts",
|
|
65
|
+
"default": "./dist/read.mjs"
|
|
66
|
+
},
|
|
67
|
+
"require": {
|
|
68
|
+
"types": "./dist/read.d.cts",
|
|
69
|
+
"default": "./dist/read.cjs"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"./markers": {
|
|
73
|
+
"import": {
|
|
74
|
+
"types": "./dist/markers.d.mts",
|
|
75
|
+
"default": "./dist/markers.mjs"
|
|
76
|
+
},
|
|
77
|
+
"require": {
|
|
78
|
+
"types": "./dist/markers.d.cts",
|
|
79
|
+
"default": "./dist/markers.cjs"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
52
82
|
"./package.json": "./package.json"
|
|
53
83
|
},
|
|
54
84
|
"main": "./dist/index.cjs",
|
|
@@ -67,11 +97,15 @@
|
|
|
67
97
|
"LICENSE"
|
|
68
98
|
],
|
|
69
99
|
"//sizeBudget": "Gzipped bytes per published entry point, checked by `build.mjs` on every build and therefore in CI. A budget is here rather than in a separate config because the number is part of the package's contract: this code runs on every page of every managed product, and 'it grew a bit each time' is how a 4KB SDK becomes a 40KB one with nobody having decided. Raising a number is allowed; doing it in the same commit as the growth, where a reviewer sees both, is the point.",
|
|
70
|
-
"//sizeBudget-
|
|
100
|
+
"//sizeBudget-anchor": "A-275, and the reason it is a FOURTH ENTRY POINT rather than a re-export from `.`: the marker layer (A-277) runs on the product page and needs `resolveAnchor`, but the SDK that only CAPTURES does not, and folding it into index would put it in the bundle of every managed product whether or not that product renders markers. THE NUMBER HERE WAS WRONG ONCE AND THAT IS WHY IT IS SPELLED OUT: the first commit of this note said 961, measured by bundling `src/anchor.ts` alone with esbuild, and `build.mjs` printed 996 for the same commit - the standalone bundle and the real build are not the same measurement, and a stated figure that disagrees with the gate is worse than no figure. MEASURED BY RUNNING `node build.mjs` AND READING ITS OUTPUT, which is the only number the budget is ever compared against: 1036 as shipped, after A-275's review cycle added `RouteAnchor.path` and the path check. Budget 1100, so headroom is 1100/1036 = 6.2%. Deliberately not raised to restore the slack the review spent: this entry point exists to grow when A-277 needs it to, and the budget's job is to make that a decision somebody makes. Looser in PROPORTION than index (0.83%) for the reason protocol is: 64 bytes is 6% here and would be 0.7% of index.",
|
|
101
|
+
"//sizeBudget-index": "A-241 adds arming: 8700 -> 9400 for index and 700 -> 850 for protocol. MEASURED by rebuilding, four numbers. Baseline at A-240's merge commit fceb183: index 8562, protocol 646 - the two figures the previous note recorded, re-measured rather than trusted. As shipped: index 9281, protocol 788. And with the ./arming re-export block deleted from src/index.ts but everything else present: 8913. SO readArming AND ITS TYPES ARE 368 OF THE 719, AND 351 ARE NOT: 142 of that is protocol's own growth (armedCookie, clearedCookie, ANNOTATION_ARMED_VALUE, REVIEW_RUN_PARAM), which index pays too because it re-exports protocol whole, and the rest is isAnnotatableBuild with its ANNOTATABLE_ENVS allowlist, the two-gate expression in mountAnnotation, and Done's teardown. That 351 is the number worth knowing: a consumer who never calls readArming still pays it, because the kill-switch is not optional and must not be tree-shakeable. TWO REVIEW CYCLES MOVED IT AND BOTH COSTS ARE MEASURED: the fail-closed allowlist +27 (9239 -> 9266) and Done's teardown +15 (9266 -> 9281). HEADROOM IS PER ENTRY POINT AND THE TWO ARE STATED SEPARATELY, because one figure for both was false here once already: index 9400/9281 = 1.28%, against A-240's 8700/8562 = 1.61%, so tighter and DELIBERATELY NOT TOPPED UP - the budget's job is to make the next growth a decision, and quietly restoring slack each time is how it stops doing that; protocol 850/788 = 7.87%, against its previous 700/646 = 8.36%. protocol runs looser in PROPORTION only because it is small - 62 bytes is 7.87% there and would be 0.67% of index. The argument for the toolbar's own 2553 bytes is in docs/decisions/a-240-annotation-toolbar-size.md, extended there for this slice rather than restated here. A-254 MOVED IT AGAIN AND THE BUDGET IS DELIBERATELY NOT RAISED: index 9281 -> 9300, +19 gzipped bytes for the armed-before-persist early return in readArming, re-measured by running build.mjs before and after. Headroom is now 9400/9300 = 1.06%. That is tight on purpose - 19 bytes bought the end of an unarmed page storing an attacker-supplied capture token, and the next growth after it should be a decision somebody makes rather than slack somebody spends. A-277 MOVED IT AGAIN, TWICE, AND BOTH ARE MEASURED BY REBUILDING: index 9471 -> 9531, +60 gzipped bytes, and protocol 788 -> 800, +12. The 12 is READ_TOKEN_PARAM and its comment-free constant; the remaining 48 in index is arming carrying the read token - a fourth stripped parameter, a third storage key, a `readToken` field on `Arming`, and the read/write branches for it in `readArming`. THAT 48 IS PAID BY EVERY CONSUMER INCLUDING ONES THAT NEVER MOUNT A MARKER, and it is paid deliberately rather than moved into ./markers: the strip is the thing that gets a signed credential out of an address bar, it has to run in the first milliseconds of the page, and a product that mounts only the toolbar still receives an `ai_read` in its URL and still must not leave it there. Budgets 9550 -> 9650 and 850 -> 870. 9650 rather than 9600: at 9600 the headroom is 69 bytes, which is a budget that fails on the next sentence somebody writes rather than on the next decision somebody makes. 9650/9531 = 1.25% and 870/800 = 8.75%, so index is LOOSER than A-285 left it (0.83%) and that is stated rather than hidden - the slack was restored on purpose this once, because A-279 (replies) is the next slice into this file and a budget with 19 bytes in it makes that slice's first commit a budget argument instead of a design one. A-285 MOVED IT AGAIN AND THE BUDGET IS RAISED, WITH THE COST SPLIT AND MEASURED: index 9300 -> 9471, +171 gzipped bytes, by rebuilding three times. 9300 is the baseline at this branch point, re-measured rather than trusted. 9426 with transport.ts alone - safeRefusalReason, the IngestRefusal fields and the body read on the refusal branch, 126 bytes. 9471 with the toolbar rendering it and index re-exporting the two functions, a further 45. THAT 171 IS WHAT IT COSTS FOR A REFUSAL TO REACH A HUMAN AT ALL: before it, every sentence the ingest composed - the link issued for another deployment, the empty comment - was thrown away by the only client that could show it, and every failure read 'That did not send'. 9550 rather than 9500: 9500 would leave 29 bytes, which is a budget that fails on the next comment somebody writes rather than on the next decision somebody makes. 9550/9471 = 0.83%, against A-254's 9400/9300 = 1.06% - so still tighter than it was, and deliberately not topped up.",
|
|
71
102
|
"sizeBudget": {
|
|
72
|
-
"protocol":
|
|
73
|
-
"index":
|
|
74
|
-
"host": 660
|
|
103
|
+
"protocol": 870,
|
|
104
|
+
"index": 9650,
|
|
105
|
+
"host": 660,
|
|
106
|
+
"anchor": 1100,
|
|
107
|
+
"read": 1700,
|
|
108
|
+
"markers": 4250
|
|
75
109
|
},
|
|
76
110
|
"engines": {
|
|
77
111
|
"node": ">=18"
|
|
@@ -80,9 +114,12 @@
|
|
|
80
114
|
"@playwright/test": "^1.62.1",
|
|
81
115
|
"@types/node": "^22.0.0",
|
|
82
116
|
"esbuild": "^0.28.2",
|
|
117
|
+
"linkedom": "^0.18.13",
|
|
83
118
|
"tsx": "^4.21.0",
|
|
84
119
|
"typescript": "~5.7.2"
|
|
85
120
|
},
|
|
121
|
+
"//sizeBudget-markers": "A-277, and a SIXTH ENTRY POINT for ./anchor and ./read's reason, which applies here with the most weight of the three: the marker layer, its panel and its stylesheet run ONLY in a product that renders markers, and folding them into index would put all of it in the bundle of every page of every managed product including the ones that will never show one. MEASURED BY RUNNING `node build.mjs` AND READING ITS OUTPUT, which is the only number the budget is ever compared against: 3449 as shipped. The first draft of this note said 3400 and the build printed 3449 on the same commit - the number is written down AFTER the gate has been run, never before, because a stated figure that disagrees with the gate is worse than no figure. 3449 was the figure BEFORE a browser was pointed at it; the clamp-into-viewport, the collision nudge and the date formatting cost a further 181, so the shipped number is 3630 and every one of those 181 bytes bought a defect that no fixture test could see (a pin at y:-11, two pins overlapping by 22 of their 22 pixels, and an ISO instant rendered raw beside a name). Tokenising the CSS onto `:host` custom properties - the same names and values `toolbar.ts` uses, so the two surfaces a reviewer sees on one page are one design - cost a further 168 and cleared the design audit's untokenised-colour and untokenised-space findings, which must not be answered by lengthening `gyde-allowance.json`. Shipped at 3800 (rebuilt after two 16px offsets also became a var). A REVIEW CYCLE THEN ADDED 79 MORE, and they are the most load-bearing bytes in this entry point: `freeSpot`'s bound. The collision walk it replaced was a bare `while` with no exit but success, and on a viewport measuring zero - a hidden iframe, a prerender, a stubbed `win` - two pins were enough to make it spin forever, LOCKING THE MAIN THREAD OF A CUSTOMER'S PAGE. Shipped at 3879. Budget 3950, so headroom is 3950/3879 = 1.8% - TIGHTER in proportion than anchor (6.2%) and read (6.4%) on purpose, because this is the largest entry point after index and a percentage of it is a lot of bytes. What is IN it: the CSS string and the DOM construction are most of it, and neither is optional - the layer lives in a shadow root so the product's stylesheet cannot reach in and it cannot reach out, and every value reaches the DOM through textContent because everything rendered here came off the network from a console this page does not control. placeMarkers, the clustering and the count-line sentence are small; resolveAnchor is pulled in from ./anchor by tree-shaking and is 1036 of the total on its own, which is why this is not 2400. A-323 MOVED IT AND THE BUDGET IS RAISED, WITH THE COST SPLIT AND MEASURED BY RUNNING `node build.mjs` THREE TIMES: markers 3879 -> 4186, +307 gzipped bytes. 3879 is the baseline at this branch point, re-measured rather than trusted. 4149 with the `.toggle` CSS rules deleted but everything else present, so THE STYLESHEET IS 37 OF THE 307 AND THE OTHER 270 IS NOT: those 270 are the three label strings (showResolved, hideResolved, and the resolved clause on count), the paintToggle state function, the toggle node with its click handler and its aria-pressed, and the .count-text span that lets the sentence be rewritten without removing the button. WHAT THE 307 BUYS: A-288 made the console suppress done and cancelled by default, and until this entry point could COUNT what was suppressed and ASK for it back, deploying that console would have made resolved markers vanish from a live product page silently - a marker silently absent is indistinguishable from a marker never made, which is the failure the counted envelope exists to prevent, reintroduced by a display default. A 728-BYTE MISTAKE IS RECORDED IN markers.ts ABOVE PIN BECAUSE IT IS THE NON-OBVIOUS COST IN THIS FILE: the placement argument was first written as a comment INSIDE the CSS template literal, where a comment is a string in the shipped bundle - it measured 4607, 657 over budget, and moving the identical prose four lines out to a TypeScript comment took it to 4186 with no behaviour change at all. Budget 3950 -> 4250, so headroom is 4250/4186 = 1.53%, TIGHTER in proportion than A-277 left it (3950/3879 = 1.8%) and deliberately not topped up: A-279 (replies) is the next slice into this file and its bytes should be a decision somebody makes rather than slack somebody restored. Prose in this file is free; only what lands inside CSS is payload.",
|
|
122
|
+
"//sizeBudget-read": "A-276, and a FIFTH ENTRY POINT for ./anchor reason: the read client runs only in a product that RENDERS markers, and folding it into index would tax every page of every managed product with code most of them never call. MEASURED BY RUNNING node build.mjs AND READING ITS OUTPUT, which is the only number the budget is ever compared against: 1597 as shipped. Budget 1700, so headroom is 1700/1597 = 6.4% - deliberately in line with anchor 1100/1036 = 6.2% rather than tighter, because A-277 is the slice that will grow this one and the budget job is to make that growth a decision somebody makes. It is looser in PROPORTION than index 0.83% for the reason protocol is: 100 bytes is 6% here and would be 1% of index. What is IN it: the envelope and marker types are erased at build, so the bytes are parseAnnotationsResponse and its three shape checks (the anchor union, the marker, the counts), createAnnotationReader, and assertConcreteEndpoint pulled in from transport.ts by tree-shaking. The sanitising is most of it and is not optional - comment and reviewer are rendered inside the customers own page, and safeIssueUrl header has the argument. A-323 MOVED IT AND THE BUDGET IS DELIBERATELY NOT RAISED: read 1593 -> 1652, +59 gzipped bytes, re-measured by running build.mjs before and after. What the 59 buys is the ONLY way an SDK can see a finished capture: the resolved=include search param behind an includeResolved option, the RESOLVED_INCLUDE constant, and resolvedHidden on the parser. The constant is not an indulgence - the console compares params.get('resolved') === 'include' and its own tests pin that true, 1, yes, Include, INCLUDE and a trailing space all get the DEFAULT, so a client sending any near-miss would widen nothing, report nothing wrong, and leave a page saying '2 resolved' beside a button that does not work. THE FIELD IS ADDITIVE AND wireVersion STAYS AT 1: parseAnnotationsResponse already ignores unknown fields and answers 0 for an absent count, so an older SDK meeting the A-288 console is unaffected and this SDK meeting a pre-A-288 console reads resolvedHidden: 0, which is the honest reading of a console that hides nothing. Headroom is now 1700/1652 = 2.9%, down from 6.4% - the slack A-276 left for A-277 has been spent by A-323 instead, which is what it was for.",
|
|
86
123
|
"scripts": {
|
|
87
124
|
"build": "node build.mjs",
|
|
88
125
|
"typecheck": "tsc --noEmit",
|
|
@@ -91,6 +128,12 @@
|
|
|
91
128
|
"//test": "`--test-timeout` is not decoration (A-240). Without it, breaking `httpTransport`'s abort did not turn this suite RED — it hung `pnpm test` forever, because the pending fetch keeps the event loop alive and node:test has no default deadline. CI times out, so it never read as a pass; but a wall-clock stall names no test and blames whatever was printed last, and this repository's standard is a NAMED FAILING TEST. 30s is a ceiling and not a budget: MEASURED, the slowest test in this suite is 203ms (`a 500 reaches the SDK's error handler`, which sleeps 200ms on purpose), so the limit is ~150x it and no honest test can reach it.",
|
|
92
129
|
"test": "tsx --test --test-timeout=30000 src/*.test.ts",
|
|
93
130
|
"e2e": "playwright test",
|
|
131
|
+
"//measure:anchors": "A-275's mutation battery, against a LIVE deployment. `pnpm --filter @weatherboard/annotation measure:anchors -- --url https://<host>/ --compare-url /other/id`. NOT in `pnpm verify` and it must never be: it needs the network and a browser, and verify is forbidden to acquire either. It exists because the numbers in docs/decisions/a-275-anchor-placement.md were, for one review cycle, reproducible only by the agent that took them - a measurement nobody else can re-run is indistinguishable from an asserted one. Read-only: it navigates and evaluates, and there is no click or form submission in it, because staging shares the production database. Exits 2 (COULD NOT ANSWER, not a pass) when the page carries no source refs, which is what production looks like.",
|
|
132
|
+
"measure:anchors": "node anchor-measure.mjs",
|
|
133
|
+
"//fixtures:anchors": "A-275. Rebuilds `test-fixtures/anchors/page-after-fix.html`, `page-moved.html` and `anchors.json` from `page.html`. NOT part of `pnpm test`: the outputs are checked in and the tests read them, so running it in the gate would let a resolver change quietly regenerate the fixtures it is being measured against. Regenerate on purpose, and re-pin the counts in `anchor.test.ts` when you do.",
|
|
134
|
+
"fixtures:anchors": "tsx anchor-fixtures.mjs",
|
|
135
|
+
"//bundle:gate": "A-277 done-condition 5. Does a PRODUCTION build of a product's bundle contain the toolbar or the marker layer? NOT part of `pnpm test` and NOT part of `pnpm verify`, for one reason and it is not speed: it runs a bundler and reads the emitted artifact, and a unit test structurally cannot answer this question - `isAnnotatableBuild` returning false is a different claim from the bundler having DELETED the code behind it. N-905 shipped that exact confusion four times: passing `process.env` as a VALUE defeats per-key inlining, so the branch cannot be folded and the import ships, while every unit test passes because a unit test injects the value it expects. THREE builds and two of them must find the code: gated+production is the assertion, gated+preview and N-905's own shape+production are what prove the grep can SEE the toolbar rather than reporting an absence from a bundle that never held anything. It walks `metafile.outputs[].imports` from the entry rather than reading every .js on disk, because esbuild emits a chunk for every dynamic-import target whether or not anything reaches it - MEASURED, the folded build's entry is 57 bytes with an EMPTY imports list beside four emitted files. It is in CI as its own job for the standing reason: a gate nobody runs is a gate that does not exist. Exit 0/1/2, and 2 is COULD NOT ANSWER rather than a pass.",
|
|
136
|
+
"bundle:gate": "node bundle-gate.mjs",
|
|
94
137
|
"//smoke": "The consumer test — pack the real tarball, install it outside the workspace, import it four ways. NOT part of `pnpm test`, and not because it is slow: it shells out to `npm install`, which needs the network, and `verify` is forbidden to acquire one. It runs as its own CI job. See the header of smoke.mjs for why nothing else in this repository can catch what it catches.",
|
|
95
138
|
"smoke": "node smoke.mjs",
|
|
96
139
|
"//drift": "A-187. Compares this source's wire names against the version ALREADY PUBLISHED, when they are the same version. `smoke.mjs` proves the tarball we are about to publish; every unit test reads this source; neither looks at the registry. The hole is publish 0.1.0, then edit protocol.ts without bumping — a hundred consumers have one spelling, this console has another, and everything here is green. Needs the network, so it runs in the package-consumer job rather than in `verify`.",
|