mesurer-solid 0.1.1-beta.0 → 0.1.1-beta.2

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.
@@ -28,10 +28,14 @@ The page is the shared state boundary. Mesurer never needs to know which chat, t
28
28
 
29
29
  ## Install the portable Agent Skill
30
30
 
31
+ Use the stable package by default:
32
+
31
33
  ```bash
32
- npx --yes --package=mesurer-solid@beta mesurer-skill install
34
+ npx --yes --package=mesurer-solid mesurer-skill install
33
35
  ```
34
36
 
37
+ Use `mesurer-solid@beta` only when intentionally validating a prerelease.
38
+
35
39
  The installed skill is self-contained:
36
40
 
37
41
  ```text
@@ -41,7 +45,7 @@ The installed skill is self-contained:
41
45
  └── inject-script.js
42
46
  ```
43
47
 
44
- The skill defines the same context-first workflow described here.
48
+ The skill defines the same context-first workflow described here, including the distinction between Mesurer's optional human screenshot plugin and screenshots owned by the agent's outer browser harness.
45
49
 
46
50
  ## Reuse a live human instance first
47
51
 
@@ -58,7 +62,7 @@ if (hasMesurer) {
58
62
  }
59
63
  ```
60
64
 
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.
65
+ If Mesurer exists, use that exact instance. The person may already have selected elements, placed guides, measured gaps, held distances, enabled rulers/X-ray, saved annotations, or kept a screenshot preview open. Read and preserve that state before changing it.
62
66
 
63
67
  The injector also reuses a live injected instance by default. Deliberate destructive replacement requires:
64
68
 
@@ -97,6 +101,14 @@ await browser.evaluate(() => window.__MESURER__.ready())
97
101
 
98
102
  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
103
 
104
+ Normal injection keeps screenshot capture disabled unless requested:
105
+
106
+ ```js
107
+ window.__MESURER_CONFIG__ = { screenshot: true }
108
+ ```
109
+
110
+ The first-party Chrome extension enables the screenshot plugin automatically because its human-facing camera tool can use the extension's visible-tab capture bridge.
111
+
100
112
  ## Capability contract
101
113
 
102
114
  After `ready()`:
@@ -123,7 +135,7 @@ Copy Selection
123
135
  Add Note
124
136
  ```
125
137
 
126
- There is no `send`, `screenshots`, or `sendContext` delivery capability.
138
+ There is no `send`, `screenshots`, or `sendContext` **delivery capability**. The optional `mesurer.screenshot` plugin is a separate human capture tool/service and does not add image delivery to the context API.
127
139
 
128
140
  ## Context acquisition precedence
129
141
 
@@ -326,9 +338,32 @@ expected target/guide/measurement missing
326
338
 
327
339
  If the requested result remains numerically wrong, continue editing.
328
340
 
329
- ## Screenshots complement context
341
+ ## Human screenshot plugin vs agent screenshot evidence
342
+
343
+ Mesurer has two intentionally different screenshot paths.
344
+
345
+ ### Human capture tool
346
+
347
+ The optional `mesurer.screenshot` plugin gives the person a camera tool inside Mesurer:
348
+
349
+ ```ts
350
+ import { mountMeasurer } from "mesurer-solid"
351
+ import { screenshotPlugin } from "mesurer-solid/screenshot"
352
+
353
+ const mesurer = mountMeasurer({
354
+ plugins: [screenshotPlugin()],
355
+ })
356
+ ```
357
+
358
+ The user can drag a viewport region, capture a real HiDPI-aware PNG, optionally copy/download it, keep a persistent draggable thumbnail, and open a larger Copy/Save viewer. Normal browser hosts use `getDisplayMedia()`; the first-party Chrome extension uses `chrome.tabs.captureVisibleTab()` through its isolated-world bridge and therefore avoids the screen-share chooser.
330
359
 
331
- Mesurer supplies capture scope; the outer harness supplies real pixels:
360
+ For advanced mounted integrations, the typed `MesurerScreenshotService` is available from the plugin host under service id `screenshot`. It is not part of `window.__MESURER__`'s context/delivery capability surface.
361
+
362
+ Agents should preserve an existing human screenshot preview unless the task explicitly asks them to test, close, replace, or otherwise manipulate the screenshot feature.
363
+
364
+ ### Agent verification screenshot
365
+
366
+ For coding-agent verification, the outer harness should normally continue to own screenshot bytes so the task can control the exact browser, viewport, timing, and artifact destination. Mesurer supplies clean capture scope/presentation:
332
367
 
333
368
  ```js
334
369
  const plan = await window.__MESURER__.capturePlan({ scope: "selection" })
@@ -348,6 +383,8 @@ Mesurer context → exact geometry, box model, styles, distances, overflow
348
383
  real screenshot → composition, hierarchy, clipping, color, visual judgment
349
384
  ```
350
385
 
386
+ Do not replace exact Mesurer measurements with pixel estimates from either screenshot path.
387
+
351
388
  ## Source-mounted usage
352
389
 
353
390
  When Mesurer is intentionally mounted from application code:
@@ -367,7 +404,7 @@ The same API is available on `mesurer.agent` and, when configured, `window.__MES
367
404
  const context = await mesurer.agent.select("#target")
368
405
  ```
369
406
 
370
- No transport callback is involved.
407
+ Screenshot capture can be composed independently with `screenshotPlugin()` when the host wants the human camera tool. No transport callback is involved.
371
408
 
372
409
  ## Low-level inspection
373
410
 
@@ -391,7 +428,7 @@ Prefer `context()`, `select()`, and `review()` for visual development because th
391
428
  A good harness-level visual completion loop is:
392
429
 
393
430
  ```text
394
- 1. discover/reuse Mesurer
431
+ 1. discover/reuse Mesurer and preserve human state
395
432
  2. consume existing human context
396
433
  3. if target ambiguous, ask user to select
397
434
  4. otherwise select known affected rendered target(s) when needed
@@ -399,8 +436,10 @@ A good harness-level visual completion loop is:
399
436
  6. edit normal source
400
437
  7. wait for stable render
401
438
  8. get fresh review/context; use select() for known changed targets
402
- 9. optionally capture real screenshot
439
+ 9. optionally capture a real screenshot through the outer harness
403
440
  10. iterate until rendered evidence supports the claim
404
441
  ```
405
442
 
443
+ When the task is specifically testing Mesurer's screenshot plugin, exercise its camera/preview/viewer path as the feature under test; otherwise do not substitute it for the harness's normal screenshot primitive.
444
+
406
445
  **Context is the output of the Mesurer step, not an optional side effect.**
package/README.md CHANGED
@@ -6,13 +6,23 @@ The renderer is implemented privately in Solid 2, but consumers can use Solid 1/
6
6
 
7
7
  ## Install
8
8
 
9
+ Stable releases use the `latest` dist-tag:
10
+
9
11
  ```bash
10
12
  bun add -d mesurer-solid
11
13
  # or
12
14
  npm install -D mesurer-solid
13
15
  ```
14
16
 
15
- `mesurer-solid@0.1.0` is the stable package on the `latest` dist-tag. Prereleases through `0.1.0-beta.11` used the old scoped package name; current releases use `mesurer-solid`.
17
+ Use the `beta` tag only when you intentionally want to test a prerelease:
18
+
19
+ ```bash
20
+ bun add -d mesurer-solid@beta
21
+ # or
22
+ npm install -D mesurer-solid@beta
23
+ ```
24
+
25
+ Prereleases through `0.1.0-beta.11` used the old scoped package name; current releases use `mesurer-solid`.
16
26
 
17
27
  ## Mount the base inspector
18
28
 
@@ -274,17 +284,23 @@ const mesurer = mountMeasurer({
274
284
  })
275
285
  ```
276
286
 
277
- The camera tool lets the user drag a viewport region. Mesurer captures the visible page, crops against the real captured bitmap scale so HiDPI displays stay accurate, optionally writes PNG data to the clipboard and/or downloads a PNG, then restores the inspector UI and shows a compact preview.
287
+ The camera tool lets the user drag a viewport region. Mesurer captures the real visible page, converts CSS viewport coordinates to the captured bitmap scale so Retina/HiDPI crops remain exact, temporarily hides its control chrome from the pixels, then restores the previous inspector presentation.
288
+
289
+ A successful capture can automatically copy PNG data to the clipboard and/or download a PNG according to persistent plugin settings. Those output operations are best-effort: if clipboard or download access is unavailable, the captured PNG is still kept for preview/viewer use and Mesurer reports the available result instead of discarding it.
290
+
291
+ When `contextPlugin()` and/or `screenshotPlugin()` are mounted, Settings → General → Plugins exposes compact persisted controls for the human-facing plugin surface. Context can hide or restore its toolbar/annotation controls without removing the typed context service. Screenshot can hide or restore the camera tool and control automatic copy, automatic download, and whether measurement presentation is included in captured pixels.
292
+
293
+ After capture, Mesurer shows a persistent draggable thumbnail. A new thumbnail starts in the bottom-right with an 8px viewport inset. Dragging preserves the existing viewport-clamping behavior, so the preview stays inside that safe boundary. The thumbnail can be dismissed, dragged around the viewport, right-clicked with the browser's native image context menu, or clicked to open a larger viewer. The viewer preserves native image right-click behavior and adds explicit Copy, Save, and Close controls. Escape or backdrop click closes the viewer without discarding the thumbnail. A short status message confirms whether the screenshot was copied, saved, captured, or could not complete an optional output.
278
294
 
279
- Normal browser hosts use `getDisplayMedia()` and reuse a live capture stream to avoid prompting for every region. The first-party Chrome extension uses `chrome.tabs.captureVisibleTab()` through its extension bridge, so its screenshot path does not open the screen-share chooser.
295
+ Normal browser hosts use `getDisplayMedia()` and reuse a live capture stream to avoid prompting for every region. The first-party Chrome extension uses `chrome.tabs.captureVisibleTab()` through its isolated-world extension bridge, so its screenshot path does not open the screen-share chooser and does not require a broad `<all_urls>` permission.
280
296
 
281
- Programmatic users can get the typed screenshot service from the plugin host with service id `screenshot`. `start()` opens region selection, `cancel()` closes it, `capture(rect)` captures an exact CSS-pixel viewport rectangle, and `setSettings()` updates the persistent copy/download preferences.
297
+ Programmatic mounted users can get the typed `MesurerScreenshotService` from the plugin host with service id `screenshot`. `start()` opens region selection, `cancel()` closes it, `capture(rect)` captures an exact CSS-pixel viewport rectangle, `settings()` reads copy/download preferences, and `setSettings()` updates those persistent preferences.
282
298
 
283
299
  Screenshot does not claim the global `C` shortcut because the context workflow already uses `C` and `Shift+C`.
284
300
 
285
301
  ## Clean screenshot evidence for agents
286
302
 
287
- The optional screenshot plugin is a human capture tool. Agent verification can continue to let the existing browser harness own screenshots while Mesurer plans a clean evidence frame:
303
+ The optional screenshot plugin is a human capture tool, not an agent-delivery channel. Agent verification can continue to let the existing browser harness own deterministic task screenshots while Mesurer plans a clean evidence frame:
288
304
 
289
305
  ```js
290
306
  const plan = await window.__MESURER__.capturePlan({ scope: "selection" })
@@ -296,7 +312,7 @@ try {
296
312
  }
297
313
  ```
298
314
 
299
- Use screenshots for visual composition and Mesurer context for exact numeric claims.
315
+ Use screenshots for visual composition and Mesurer context for exact numeric claims. A screenshot plugin image does not create a `screenshots` delivery capability on `window.__MESURER__` and does not replace context/select/review evidence.
300
316
 
301
317
  ## Context UI
302
318
 
@@ -325,7 +341,7 @@ The installer leaves:
325
341
  └── inject-script.js
326
342
  ```
327
343
 
328
- The skill teaches the context-first workflow, including when to consume human selection, when to ask for a selection, when to self-select changed targets, multi-selection reads, and fresh post-edit verification. See [`AGENT_INTEGRATION.md`](./AGENT_INTEGRATION.md).
344
+ The skill teaches the context-first workflow, including when to consume human selection, when to ask for a selection, when to self-select changed targets, multi-selection reads, fresh post-edit verification, and the distinction between the optional human screenshot plugin and harness-owned agent screenshot evidence. See [`AGENT_INTEGRATION.md`](./AGENT_INTEGRATION.md).
329
345
 
330
346
  ## Low-level agent API
331
347
 
@@ -350,7 +366,7 @@ import {
350
366
  } from "mesurer-solid/core"
351
367
  ```
352
368
 
353
- Plugins can contribute tools, commands, hooks, overlays, settings, state, services, history/persistence, renderer-owned UI, and lifecycle cleanup.
369
+ Plugins can contribute tools, commands, hooks, overlays, settings, state, services, history/persistence, renderer-owned UI, and lifecycle cleanup. Plugin-defined toggle settings can register through the plugin host and appear in the canonical General → Plugins Settings section without hard-coding the plugin into Mesurer's renderer.
354
370
 
355
371
  ## Public surface
356
372
 
@@ -2,8 +2,9 @@ import type { MesurerPlugin } from "./core";
2
2
  import { type MesurerAnnotation, type MesurerCapturePlanV1, type MesurerContextRequest, type MesurerContextV1, type MesurerReviewV1 } from "./context";
3
3
  export declare const MESURER_CONTEXT_PLUGIN_ID = "mesurer.context";
4
4
  export declare const MESURER_CONTEXT_SERVICE_ID = "context:v1";
5
+ export declare const MESURER_CONTEXT_SETTINGS_STATE_ID = "mesurer.context.settings";
5
6
  export type MesurerContextPluginOptions = {
6
- /** Render Copy Context, Copy Selection, Add Note, and annotation UI. Defaults to true. */
7
+ /** Initial human-facing Context UI state. The Settings toggle can change it at runtime. Defaults to true. */
7
8
  ui?: boolean;
8
9
  };
9
10
  export type MesurerContextService = {
package/dist/core.d.ts CHANGED
@@ -2,8 +2,8 @@ export type Registration = {
2
2
  readonly dispose: () => void;
3
3
  };
4
4
  export type PluginId = string;
5
- type PluginScalar = string | number | boolean | null;
6
- type PluginValue = PluginScalar | PluginValue[] | {
5
+ export type PluginScalar = string | number | boolean | null;
6
+ export type PluginValue = PluginScalar | PluginValue[] | {
7
7
  [key: string]: PluginValue;
8
8
  };
9
9
  export type PluginStateSnapshot = {
@@ -23,12 +23,39 @@ export type ToolContribution = {
23
23
  };
24
24
  active?: () => boolean;
25
25
  disabled?: () => boolean;
26
+ hidden?: () => boolean;
26
27
  };
28
+ export type SettingsToggleContribution = {
29
+ type: "toggle";
30
+ id: string;
31
+ label: string;
32
+ description?: string;
33
+ value(): boolean;
34
+ set(value: boolean): void | Promise<void>;
35
+ disabled?: () => boolean;
36
+ };
37
+ export type SettingsControlContribution = SettingsToggleContribution;
27
38
  export type SettingsContribution = {
28
39
  id: string;
29
40
  label: string;
30
41
  order?: number;
31
42
  builtin?: string;
43
+ controls?: SettingsControlContribution[];
44
+ };
45
+ export type SettingsControlDescription = {
46
+ type: "toggle";
47
+ id: string;
48
+ label: string;
49
+ description: string | undefined;
50
+ value: boolean;
51
+ disabled: boolean;
52
+ };
53
+ export type SettingsDescription = {
54
+ id: string;
55
+ label: string;
56
+ order?: number;
57
+ builtin?: string;
58
+ controls: SettingsControlDescription[];
32
59
  };
33
60
  export type OverlayContribution = {
34
61
  id: string;
@@ -50,6 +77,7 @@ export type MesurerPluginContext = {
50
77
  register<T extends PluginValue>(definition: StateSliceDefinition<T>): Registration;
51
78
  get<T extends PluginValue>(id: string): T | undefined;
52
79
  update<T extends PluginValue>(id: string, update: (value: T) => T): void;
80
+ subscribe(listener: () => void): Registration;
53
81
  };
54
82
  tool: {
55
83
  register(contribution: ToolContribution): Registration;
@@ -98,7 +126,7 @@ export type MesurerPluginDescription = {
98
126
  order?: number;
99
127
  builtin?: string;
100
128
  }>;
101
- settings: SettingsContribution[];
129
+ settings: SettingsDescription[];
102
130
  overlays: OverlayContribution[];
103
131
  state: Array<{
104
132
  id: string;
@@ -151,4 +179,3 @@ export declare function createMesurerPluginHost(): MesurerPluginHost;
151
179
  export declare function createMesurerRuntime(options?: {
152
180
  plugins?: MesurerPlugin[];
153
181
  }): Promise<MesurerPluginHost>;
154
- export {};
package/dist/core.js CHANGED
@@ -125,7 +125,13 @@ function i() {
125
125
  return p.has(t.id) || p.set(t.id, t.initial), r;
126
126
  },
127
127
  get: k.get,
128
- update: k.update
128
+ update: k.update,
129
+ subscribe(e) {
130
+ let t = i.on("changed", (t) => {
131
+ (t.reason === "state" || t.reason === "history") && e();
132
+ });
133
+ return n({ dispose: t });
134
+ }
129
135
  },
130
136
  tool: { register: (t) => n(_(o, e, t)) },
131
137
  settings: { register: (t) => n(_(s, e, t)) },
@@ -203,8 +209,18 @@ function i() {
203
209
  requires: e.requires ?? [],
204
210
  provides: e.provides ?? []
205
211
  })),
206
- tools: I.tools().map(({ active: e, disabled: t, icon: n, ...r }) => r),
207
- settings: I.settings(),
212
+ tools: I.tools().map(({ active: e, disabled: t, hidden: n, icon: r, ...i }) => i),
213
+ settings: I.settings().map(({ controls: e = [], ...t }) => ({
214
+ ...t,
215
+ controls: e.map((e) => ({
216
+ type: e.type,
217
+ id: e.id,
218
+ label: e.label,
219
+ description: e.description,
220
+ value: e.value(),
221
+ disabled: e.disabled?.() ?? !1
222
+ }))
223
+ })),
208
224
  overlays: I.overlays(),
209
225
  state: [...f.values()].map(({ id: e, history: t, persist: n }) => ({
210
226
  id: e,
package/dist/index.d.ts CHANGED
@@ -190,6 +190,7 @@ export type MountedMeasurer = {
190
190
  };
191
191
  export declare function mountMeasurer(options?: MountMeasurerOptions): MountedMeasurer;
192
192
  export { createMesurerAgentHarness } from "./agent";
193
+ export { MESURER_VERSION } from "./version";
193
194
  export type { AgentDistance, AgentEdges, AgentElementInspection, AgentFeedbackSnapshot, AgentRect, AgentViewportSnapshot, CreateMesurerAgentHarnessOptions, MesurerAgentHarness, } from "./agent";
194
195
  export { captureMesurerContext, copyTextToClipboard, createMesurerCapturePlan, formatMesurerContext, reviewMesurerAnnotation, } from "./context";
195
196
  export type { MesurerAnnotation, MesurerAnnotationBaseline, MesurerAnnotationTarget, MesurerCapturePlanV1, MesurerContextDistance, MesurerContextEdges, MesurerContextGuide, MesurerContextMeasurement, MesurerContextRect, MesurerContextRequest, MesurerContextTarget, MesurerContextV1, MesurerElementFingerprint, MesurerElementInspection, MesurerReviewChange, MesurerReviewMetricChange, MesurerReviewPresenceChange, MesurerReviewV1, } from "./context";