mesurer-solid 0.1.0-beta.12 → 0.1.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.
@@ -1,27 +1,38 @@
1
1
  # Mesurer agent integration
2
2
 
3
- Mesurer uses standards and a browser contract instead of harness-specific integrations.
3
+ Mesurer's agent integration is deliberately direct: **the agent reads and manipulates Mesurer through the same rendered page it is already controlling**.
4
+
5
+ There is no Mesurer MCP, WebMCP, ACP, localhost feedback daemon, Send-to-agent callback, chat/session bridge, or harness-specific Mesurer adapter.
6
+
7
+ The central contract is stronger than “Mesurer is available”:
8
+
9
+ > **A Mesurer visual operation should return structured context to the harness.** The agent should consume existing human context before editing and obtain fresh Mesurer context/review for the affected rendered UI before claiming completion.
4
10
 
5
11
  ```text
6
- Agent Skill window.__MESURER__ ACP
7
- how/when to use it visual context + validation standardized delivery
8
- \ | /
9
- \______________________|______________________/
10
- |
11
- any capable harness
12
+ human selection / annotation OR agent-known changed target
13
+
14
+ real page + window.__MESURER__
15
+
16
+ context() / select() / review()
17
+ structured rendered evidence
18
+ agent reasoning + source edit
19
+
20
+ normal render/HMR
21
+
22
+ fresh context() / select() / review()
23
+
24
+ validated result
12
25
  ```
13
26
 
14
- There is no required OpenCode, Pi, Cursor, Codex, or other Mesurer adapter package.
27
+ The page is the shared state boundary. Mesurer never needs to know which chat, thread, model, or agent is using it.
15
28
 
16
29
  ## Install the portable Agent Skill
17
30
 
18
- The npm package ships one canonical `mesurer-ui` Agent Skill:
19
-
20
31
  ```bash
21
32
  npx --yes --package=mesurer-solid@beta mesurer-skill install
22
33
  ```
23
34
 
24
- Use `--force` only when intentionally replacing an existing local copy. The install is self-contained: it writes the skill plus the exact packaged classic injector to:
35
+ The installed skill is self-contained:
25
36
 
26
37
  ```text
27
38
  .agents/skills/mesurer-ui/
@@ -30,144 +41,337 @@ Use `--force` only when intentionally replacing an existing local copy. The inst
30
41
  └── inject-script.js
31
42
  ```
32
43
 
33
- The skill teaches agents to use Mesurer for frontend visual work, consume human annotations before editing, and revalidate the rendered result after HMR instead of treating typecheck/build success as visual completion.
44
+ The skill defines the same context-first workflow described here.
45
+
46
+ ## Reuse a live human instance first
47
+
48
+ Before injecting anything:
49
+
50
+ ```js
51
+ const hasMesurer = Boolean(
52
+ window.__MESURER__ &&
53
+ window.__MESURER_INSTANCE__?.element?.isConnected
54
+ )
55
+
56
+ if (hasMesurer) {
57
+ await window.__MESURER__.ready()
58
+ }
59
+ ```
60
+
61
+ If Mesurer exists, use that exact instance. The person may already have selected elements, placed guides, measured gaps, held distances, enabled rulers/X-ray, or saved annotations. Read that state before changing it.
62
+
63
+ The injector also reuses a live injected instance by default. Deliberate destructive replacement requires:
64
+
65
+ ```js
66
+ window.__MESURER_CONFIG__ = { reuseExisting: false }
67
+ ```
34
68
 
35
- ## Default browser integration: inject
69
+ Do not use that while consuming human review state.
36
70
 
37
- **Default host-project mutation budget: zero.** If the existing browser, Electron, WebView, or automation harness can execute JavaScript in the target renderer, reuse that channel.
71
+ ## Inject only when absent
38
72
 
39
- When the Agent Skill is installed, read `.agents/skills/mesurer-ui/assets/inject-script.js` and evaluate those bytes in the page. No project dependency is required after the transient installer exits.
73
+ Default host-project mutation budget is zero. Reuse the browser, Electron, WebView, Playwright, CDP, or other evaluation channel the harness already owns.
40
74
 
41
- When `mesurer-solid` is already installed as a project/tooling dependency, the equivalent package path is the `/inject-script` export:
75
+ With the installed skill, evaluate `.agents/skills/mesurer-ui/assets/inject-script.js`. With the npm package installed, use `mesurer-solid/inject-script`.
42
76
 
43
77
  ```js
44
- import { readFile } from "node:fs/promises";
45
- import { fileURLToPath } from "node:url";
78
+ import { readFile } from "node:fs/promises"
79
+ import { fileURLToPath } from "node:url"
46
80
 
47
81
  const source = await readFile(
48
82
  fileURLToPath(import.meta.resolve("mesurer-solid/inject-script")),
49
83
  "utf8",
50
- );
84
+ )
51
85
 
52
- await browser.evaluate(source);
53
- await browser.evaluate(`window.__MESURER__.ready()`);
86
+ const alreadyPresent = await browser.evaluate(() => Boolean(
87
+ window.__MESURER__ &&
88
+ window.__MESURER_INSTANCE__?.element?.isConnected
89
+ ))
90
+
91
+ if (!alreadyPresent) {
92
+ await browser.evaluate(source)
93
+ }
94
+
95
+ await browser.evaluate(() => window.__MESURER__.ready())
54
96
  ```
55
97
 
56
- Both routes evaluate the same built injector artifact. The injection entry points install the removable `mesurer.context` plugin by default. A harness that deliberately wants only the low-level inspector can set:
98
+ Do not create a second browser/CDP connection, Mesurer server, special app build, or source mutation merely to inspect a page the harness already controls.
99
+
100
+ ## Capability contract
101
+
102
+ After `ready()`:
57
103
 
58
104
  ```js
59
- window.__MESURER_CONFIG__ = { context: false };
105
+ window.__MESURER__.capabilities()
106
+ ```
107
+
108
+ The context-oriented capability surface is:
109
+
110
+ ```text
111
+ context
112
+ select
113
+ annotations
114
+ review
115
+ capturePlan
116
+ ```
117
+
118
+ `select` is an agent/harness operation; it does not add a human context-toolbar button. Human context controls remain:
119
+
120
+ ```text
121
+ Copy Context
122
+ Copy Selection
123
+ Add Note
60
124
  ```
61
125
 
62
- Do not create another Chromium instance, another CDP connection, a Mesurer-specific server, a special application build, or source changes merely to inspect an app that the harness can already evaluate.
126
+ There is no `send`, `screenshots`, or `sendContext` delivery capability.
127
+
128
+ ## Context acquisition precedence
63
129
 
64
- ## Discover the browser contract
130
+ Harnesses should follow this order.
65
131
 
66
- Wait for plugin setup before reading dynamic capabilities:
132
+ ### 1. Existing human evidence exists read it first
67
133
 
68
134
  ```js
69
- if (window.__MESURER__) {
70
- await window.__MESURER__.ready()
71
- window.__MESURER__.capabilities()
72
- }
135
+ const workspace = await window.__MESURER__.context()
136
+ const annotations = await window.__MESURER__.annotations()
137
+
138
+ let selection = null
139
+ try {
140
+ selection = await window.__MESURER__.context({ scope: "selection" })
141
+ } catch {}
142
+ ```
143
+
144
+ For relevant annotations:
145
+
146
+ ```js
147
+ const context = await window.__MESURER__.context({
148
+ annotation: annotation.id,
149
+ })
150
+ ```
151
+
152
+ Do not overwrite a meaningful live human selection until its context has been consumed and retained by the current agent task.
153
+
154
+ ### 2. No relevant selection and intended target is ambiguous → ask the user
155
+
156
+ When the user's visual reference cannot be mapped confidently to exact rendered elements or a region, ask the person to select the intended element(s) or drag the intended region in Mesurer.
157
+
158
+ Then read:
159
+
160
+ ```js
161
+ const context = await window.__MESURER__.context({ scope: "selection" })
162
+ ```
163
+
164
+ Do not guess merely to avoid asking for a selection.
165
+
166
+ ### 3. No relevant selection and agent knows exact target(s) → use `select()`
167
+
168
+ If the harness knows exactly which rendered elements correspond to the change, it should select them itself:
169
+
170
+ ```js
171
+ const context = await window.__MESURER__.select("#pricing-card")
172
+ ```
173
+
174
+ or:
175
+
176
+ ```js
177
+ const context = await window.__MESURER__.select([
178
+ "#pricing-card",
179
+ "#pricing-cta",
180
+ ])
181
+ ```
182
+
183
+ `select()` is deliberately context-returning. In one operation it:
184
+
185
+ 1. switches Mesurer to Select;
186
+ 2. visibly highlights the exact rendered targets;
187
+ 3. makes them the live selection;
188
+ 4. waits for the selection to settle;
189
+ 5. returns selection-scoped `MesurerContextV1`.
190
+
191
+ The return value is the point. A harness should not call `select()` only for visual highlighting and then ignore the context.
192
+
193
+ Every supplied selector must resolve to exactly one target inside Mesurer's page target. Invalid, missing, or ambiguous selectors throw. Refine the selector or ask the user to select the intended target rather than guessing.
194
+
195
+ This makes a useful post-edit pattern trivial:
196
+
197
+ ```js
198
+ await window.__MESURER__.stable()
199
+
200
+ const evidence = await window.__MESURER__.select([
201
+ changedSelectorA,
202
+ changedSelectorB,
203
+ ])
204
+
205
+ // `evidence` is the exact rendered result the agent should reason from.
206
+ ```
207
+
208
+ ## What context contains
209
+
210
+ `MesurerContextV1` is JSON-safe and uses `viewport-css-px` coordinates:
211
+
212
+ ```text
213
+ schema / id / createdAt
214
+ scope
215
+ page
216
+ viewport / DPR / scroll
217
+ coordinateSpace
218
+ regions
219
+ visualState
220
+ rulersVisible
221
+ xrayVisible
222
+ targets[]
223
+ ref
224
+ inspection.selector
225
+ inspection.rect
226
+ margin / padding / border
227
+ typography
228
+ appearance
229
+ layout
230
+ scroll / overflow
231
+ visualContext
232
+ guides[]
233
+ measurements[]
234
+ distances[]
235
+ ```
236
+
237
+ Prefer these rendered numbers over screenshot estimates or source-level assumptions.
238
+
239
+ ## Multi-selection is relational
240
+
241
+ When several targets are selected, consume every target's complete inspection and the relevant relationships between them.
242
+
243
+ Use existing `visualContext.distances` first. For a needed pair not represented there:
244
+
245
+ ```js
246
+ const pair = window.__MESURER__.distance(selectorA, selectorB)
247
+ ```
248
+
249
+ A small selection can produce evidence such as:
250
+
251
+ ```text
252
+ Card A width: 320px
253
+ Card B width: 320px
254
+ A → B horizontal gap: 24px
255
+ A/B top-edge delta: 0px
256
+ ```
257
+
258
+ For large repeated sets, focus on adjacent/repeated/user-relevant relationships instead of dumping all O(n²) pairs.
259
+
260
+ ## Context is required before and after meaningful visual edits
261
+
262
+ ### Before editing
263
+
264
+ If the user supplied visual evidence, retain it before HMR can replace nodes:
265
+
266
+ ```js
267
+ const before = await window.__MESURER__.context({ scope: "selection" })
268
+ ```
269
+
270
+ or:
271
+
272
+ ```js
273
+ const before = await window.__MESURER__.context({ annotation: annotationId })
274
+ ```
275
+
276
+ ### After editing
277
+
278
+ First wait for the actual rendered page:
279
+
280
+ ```js
281
+ await window.__MESURER__.stable()
73
282
  ```
74
283
 
75
- `capabilities().capabilities.context` reflects whether the `context:v1` plugin service is currently present. Removing `mesurer.context` switches the context/review/capture capabilities off dynamically while the original inspection API keeps working.
284
+ Then obtain fresh evidence using the strongest path.
76
285
 
77
- ### Human-in-the-loop context
286
+ Human annotation:
78
287
 
79
- With the plugin loaded:
288
+ ```js
289
+ const review = await window.__MESURER__.review(annotationId)
290
+ ```
291
+
292
+ Still-relevant human selection:
80
293
 
81
294
  ```js
82
- await window.__MESURER__.annotations()
83
- await window.__MESURER__.context({ annotation: annotationId })
84
- await window.__MESURER__.context({ scope: "selection" })
85
- await window.__MESURER__.context()
86
- await window.__MESURER__.contextText({ annotation: annotationId })
295
+ const after = await window.__MESURER__.context({ scope: "selection" })
87
296
  ```
88
297
 
89
- `context()` combines the human's selected elements or dragged region and note with exact DOM inspection and relevant guides, measurements, and held distances. Scoped contexts expose their requested viewport rectangles in `regions`, so a region-only note remains useful even when no DOM element sits inside it. Transient hover/drag state is excluded.
298
+ Agent knows exact changed rendered targets:
299
+
300
+ ```js
301
+ const after = await window.__MESURER__.select([
302
+ changedSelectorA,
303
+ changedSelectorB,
304
+ ])
305
+ ```
90
306
 
91
- ### Revalidate after edits
307
+ Target identity is ambiguous after the change: ask the user to select the intended result and then read selection context.
308
+
309
+ For meaningful visual work, lint/typecheck/tests/build are not enough. If Mesurer is available and the changed UI can be identified, a harness should not report completion without fresh Mesurer evidence for that UI.
310
+
311
+ ## Annotation review
92
312
 
93
313
  ```js
94
314
  await window.__MESURER__.stable()
95
315
  const review = await window.__MESURER__.review(annotationId)
96
316
  ```
97
317
 
98
- Annotations retain exact live DOM identity while the original node remains connected. After DOM replacement/HMR, rebinding is deliberately conservative: strong IDs are preferred, and weaker fingerprints must resolve uniquely. Ambiguous or incompatible replacements are reported stale instead of silently attaching the note to another element.
318
+ `review()` compares the human baseline against fresh context and reports exact pixel changes/missing evidence:
319
+
320
+ ```text
321
+ gap: 37px → 24px
322
+ left-edge mismatch: 4px → 0px
323
+ width: 318px → 320px
324
+ expected target/guide/measurement missing
325
+ ```
99
326
 
100
- `review()` matches targets by stable annotation target IDs rather than regenerated selectors. Relevant baseline evidence that genuinely disappears is reported with `kind: "missing"` instead of being silently omitted.
327
+ If the requested result remains numerically wrong, continue editing.
101
328
 
102
- ### Clean screenshots
329
+ ## Screenshots complement context
103
330
 
104
- The outer harness owns real browser screenshots. The context plugin defines the evidence frame:
331
+ Mesurer supplies capture scope; the outer harness supplies real pixels:
105
332
 
106
333
  ```js
107
- const plan = await window.__MESURER__.capturePlan({ annotation: annotationId })
334
+ const plan = await window.__MESURER__.capturePlan({ scope: "selection" })
335
+
108
336
  await window.__MESURER__.prepareCapture()
109
337
  try {
110
- // harness screenshot: current viewport
111
- // close-up when present: plan.captures.find(c => c.id === "focus")
338
+ // use the harness's real screenshot primitive
112
339
  } finally {
113
340
  await window.__MESURER__.finishCapture()
114
341
  }
115
342
  ```
116
343
 
117
- Capture planning includes the scoped `regions`, so an arbitrary whitespace/alignment annotation can still produce a focused close-up. Capture mode hides toolbars, settings, comment editors, and action panels while preserving rulers, guides, selection/annotation markers, measurements, distance overlays, and pixel labels.
344
+ Use the signals together:
118
345
 
119
- Use screenshots together with structured context. Screenshots are strong visual evidence; Mesurer geometry is stronger evidence for exact spacing/alignment claims.
346
+ ```text
347
+ Mesurer context → exact geometry, box model, styles, distances, overflow
348
+ real screenshot → composition, hierarchy, clipping, color, visual judgment
349
+ ```
120
350
 
121
- ## Source-mounted integrations
351
+ ## Source-mounted usage
122
352
 
123
- When Mesurer is mounted from application code, explicitly install the same plugin:
353
+ When Mesurer is intentionally mounted from application code:
124
354
 
125
355
  ```ts
126
- import {
127
- contextPlugin,
128
- mountMeasurer,
129
- } from "mesurer-solid";
356
+ import { contextPlugin, mountMeasurer } from "mesurer-solid"
130
357
 
131
358
  const mesurer = mountMeasurer({
132
359
  agent: true,
133
360
  plugins: [contextPlugin()],
134
- });
135
- ```
136
-
137
- Browser/harness delivery capabilities are plugin options:
138
-
139
- ```ts
140
- contextPlugin({
141
- evidenceProvider: async ({ context, plan }) => [],
142
- sendContext: async ({ context, text, images }) => {
143
- // Send using the ACP session already owned by the host.
144
- },
145
361
  })
146
362
  ```
147
363
 
148
- Remove the complete extension through the normal plugin host:
149
-
150
- ```ts
151
- mesurer.pluginHost?.remove("mesurer.context");
152
- ```
153
-
154
- The context UI, annotation runtime, shortcuts, service, and listeners are disposed together.
364
+ The same API is available on `mesurer.agent` and, when configured, `window.__MESURER__`:
155
365
 
156
- ## ACP delivery
157
-
158
- Mesurer does not own an ACP process or session. The ACP client/harness that already owns the session sends Mesurer output.
159
-
160
- ```ts
161
- import { toAcpContentBlocks } from "mesurer-solid";
162
-
163
- const blocks = toAcpContentBlocks(context, images);
366
+ ```js
367
+ const context = await mesurer.agent.select("#target")
164
368
  ```
165
369
 
166
- The result is one context text block plus optional labeled image blocks. The calling ACP client is responsible for session selection, capability negotiation, and `session/prompt`.
370
+ No transport callback is involved.
167
371
 
168
- ## Existing low-level API
372
+ ## Low-level inspection
169
373
 
170
- These JSON-safe primitives remain available whether or not `mesurer.context` is loaded:
374
+ These remain useful for focused queries:
171
375
 
172
376
  ```js
173
377
  window.__MESURER__.inspect(".selector")
@@ -180,10 +384,23 @@ await window.__MESURER__.state()
180
384
  await window.__MESURER__.stable()
181
385
  ```
182
386
 
183
- When an agent is mounted with a scoped root, `inspect`, `inspectAll`, `distance`, and `at` all respect that root. A document-level hit test is never returned by `at()` unless the hit element belongs to the configured root.
387
+ Prefer `context()`, `select()`, and `review()` for visual development because they provide context that directly carries human intent or agent-selected affected UI into reasoning.
388
+
389
+ ## Harness completion rule
184
390
 
185
- Prefer scoped `context()` and `review()` for normal human-in-the-loop visual development when the context plugin is available; use the low-level primitives for narrower measurement questions.
391
+ A good harness-level visual completion loop is:
186
392
 
187
- ## Ownership boundary
393
+ ```text
394
+ 1. discover/reuse Mesurer
395
+ 2. consume existing human context
396
+ 3. if target ambiguous, ask user to select
397
+ 4. otherwise select known affected rendered target(s) when needed
398
+ 5. consume returned context
399
+ 6. edit normal source
400
+ 7. wait for stable render
401
+ 8. get fresh review/context; use select() for known changed targets
402
+ 9. optionally capture real screenshot
403
+ 10. iterate until rendered evidence supports the claim
404
+ ```
188
405
 
189
- The base Mesurer runtime owns measurement, inspection, plugin composition, and the low-level browser API. `mesurer.context` owns annotations, context formatting/capture/review behavior, and its UI. The outer harness owns navigation, clicks, typing, screenshots, tabs/windows, authentication, browser lifetime, source editing, dev servers, and ACP session/process ownership.
406
+ **Context is the output of the Mesurer step, not an optional side effect.**