mesurer-solid 0.1.8 → 0.1.9-beta.1

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.
@@ -10,7 +10,6 @@ The normal agent workflow requires no Mesurer MCP server, localhost daemon, Send
10
10
  npx --yes --package=mesurer-solid mesurer-skill install
11
11
  ```
12
12
 
13
- Use `mesurer-solid@beta` only when intentionally validating a prerelease.
14
13
 
15
14
  The installer writes a self-contained skill and injection artifact:
16
15
 
@@ -124,13 +123,15 @@ Use the high-level Context and saved-intent methods for normal UI work. The lowe
124
123
  | `distance(a, b)` | Measure two selector targets. |
125
124
  | `viewport()` | Read viewport/document dimensions, scrolling, device pixel ratio, and overflow. |
126
125
  | `feedback(selectors?)` | Read viewport data, inspected elements, plugin description, and plugin state together. |
127
- | `command(id, args?)` | Execute a registered Mesurer command. |
126
+ | `command(id, args?)` | Execute a registered Mesurer command and return its JSON-safe result when present. |
128
127
  | `state()` | Read plugin state. |
129
128
  | `stable(frames?)` | Wait for fonts and rendering before measuring again. |
130
129
  | `textEdits()` / `textEdit(id)` | Read saved direct-text and typography intent. |
131
130
 
132
131
  Context adds `capabilities()`, `context()`, `contextText()`, `select()`, `annotations()`, `review()`, `capturePlan()`, `prepareCapture()`, and `finishCapture()`.
133
132
 
133
+ These inspection methods accept general DOM elements, including SVG. `select()` and point inspection can return SVG targets, and Context preserves their selector, tag, and geometry. Arrange and direct text editing remain HTML-only mutation paths.
134
+
134
135
  Arrange adds `arrangements()`, `arrange()`, `showArrange()`, `arrangeCapturePlan()`, and `reviewArrange()`.
135
136
 
136
137
  Use `capabilities().capabilities` before calling optional Context, Arrange, or text-edit paths. The complete public list is in [Capabilities](../../docs/CAPABILITIES.md).
@@ -148,7 +149,7 @@ const context = await window.__MESURER__.select([
148
149
 
149
150
  Each selector must resolve to exactly one target. Missing or ambiguous selectors throw rather than binding to a guess.
150
151
 
151
- For multi-selection, inspect every selected target and the relevant pair relationships. Prefer `selection.visualContext.distances`; use `distance(a, b)` when a needed pair has no existing evidence.
152
+ For multi-selection, inspect every selected target and the relevant pair relationships. Prefer `selection.visualContext.distances`; use `distance(a, b)` when a needed pair has no existing evidence. Box, guide, container, and diagonal semantics are defined in [Measurements and distance geometry](../../docs/MEASUREMENTS.md).
152
153
 
153
154
  ## Arrange intent
154
155
 
@@ -241,7 +242,7 @@ try {
241
242
  }
242
243
  ```
243
244
 
244
- The optional human `screenshot()` plugin from `mesurer-solid/plugins` is a separate camera workflow. It is not an agent delivery capability. Preserve an existing human preview unless the task is specifically about Screenshot behavior.
245
+ The optional human `screenshot()` plugin from `mesurer-solid/plugins` is a separate camera workflow. It may use application-native, Chromium extension, or browser display capture internally, but none of those paths changes the agent contract above. Do not add or configure a host capture path only to collect coding-agent evidence. Preserve an existing human preview unless the task is specifically about Screenshot behavior.
245
246
 
246
247
  ## Optional human-to-Codex delivery
247
248
 
@@ -253,7 +254,7 @@ Delivery uses Codex's native durable queue. Mesurer queues once and keeps the qu
253
254
 
254
255
  The browser plugin connects lazily on the first Queue to Codex or thread-picker action. Page affinity and an explicit destination override persist per tab. If several registered threads are visible and the page has no saved destination, the user must choose one.
255
256
 
256
- The typed `codex:v1` service exposes `health()`, `listThreads()`, `useThread(thread)`, `delivery(deliveryId)`, and `send({ thread })`. Queue submission is single-flight. A matched completed turn may remove only the annotation ids sent with that delivery; interrupted, failed, ambiguous, or unreadable work keeps them.
257
+ The typed `codex:v1` service exposes `health()`, `listThreads()`, `useThread(thread)`, `delivery(deliveryId)`, and canonical `queue(request?)`. `send(request?)` remains a compatibility alias. The generic plugin command is `codex.queue`; `codex.send` remains its compatibility alias. Queue submission is single-flight. A matched completed turn may remove only the annotation ids sent with that delivery; interrupted, failed, ambiguous, or unreadable work keeps them.
257
258
 
258
259
  Mesurer does not create new Codex threads. Create or open the thread in Codex and let `SessionStart` register it. Turn completion is transport lifecycle, not proof that the requested UI result is correct.
259
260
 
package/README.md CHANGED
@@ -20,7 +20,6 @@ or:
20
20
  npm install -D mesurer-solid
21
21
  ```
22
22
 
23
- Use `mesurer-solid@beta` only when intentionally testing a prerelease.
24
23
 
25
24
  ## Usage
26
25
 
@@ -38,10 +37,12 @@ if (import.meta.env.DEV) {
38
37
  }
39
38
  ```
40
39
 
41
- For Vite, put this in the existing browser entry such as `src/main.tsx`, `src/main.ts`, or `src/index.tsx`. In Electron, use the renderer entry. In SSR applications, mount from a client-only module or lifecycle.
40
+ For Vite, put this in the existing browser entry such as `src/main.tsx`, `src/main.ts`, or `src/index.tsx`. In Electron, use the renderer entry and keep privileged Electron work in preload/main. If preload exposes `window.__MESURER_HOST__.captureScreenshot`, Screenshot uses native capture automatically. Renderer configuration remains `screenshot()`; there is no Electron-specific Screenshot factory or provider option. In SSR applications, mount from a client-only module or lifecycle.
42
41
 
43
42
  `src/dev/mesurer.ts` is an optional organization pattern, not a required filename or directory. Do not mount Mesurer from `vite.config.ts`, server/API code, Node-only scripts, an Electron main process, or a module that also executes during SSR.
44
43
 
44
+ The returned handle owns the mount. Await `mesurer.ready` when startup completion, initial rendered stability, or direct host access matters, call `mesurer.dispose()` for explicit cleanup, or pass `signal` when an existing lifecycle should own disposal.
45
+
45
46
  Full placement examples: [Getting started](https://github.com/jhomra21/mesurer-solid/blob/main/docs/GETTING_STARTED.md).
46
47
 
47
48
  ## First-party plugins
@@ -50,13 +51,14 @@ All public first-party plugin factories are exported from `mesurer-solid/plugins
50
51
 
51
52
  ```ts
52
53
  import { mountMesurer } from "mesurer-solid"
53
- import { arrange, context, screenshot } from "mesurer-solid/plugins"
54
+ import { arrange, context, layoutGuides, screenshot } from "mesurer-solid/plugins"
54
55
 
55
56
  const mesurer = mountMesurer({
56
57
  agent: true,
57
58
  plugins: [
58
59
  context(),
59
60
  arrange(),
61
+ layoutGuides(),
60
62
  screenshot(),
61
63
  ],
62
64
  })
@@ -64,7 +66,11 @@ const mesurer = mountMesurer({
64
66
 
65
67
  The base inspector includes Select, X-ray, Rulers, Typography, Guides, Distance, Settings, plugin hosting, direct text editing, and the low-level inspection API. Native Color Picker is available only when the host exposes an operational `EyeDropper`.
66
68
 
67
- For explicit plugin composition, `mesurer-solid/plugins` also exports `select`, `xray`, `colorPicker`, `rulers`, `typography`, `guides`, `distance`, `settings`, `defaults`, and `compose`.
69
+ `mesurer-solid/plugins` also exports the built-in factories for lower-level composition. Normal mounts already include the built-ins. Use `excludeBuiltins` with names such as `"xray"`, `"typography"`, and `"colorPicker"` when a mount should omit one.
70
+
71
+ Resolve plugin-owned capabilities with `await mesurer.service<T>(serviceId)`. The helper waits for configured plugins to finish loading and keeps normal consumers on the mounted interface instead of requiring `pluginHost.service.get(...)`. Falsy registered values are returned unchanged.
72
+
73
+ Advanced integrations may supply their own `pluginHost`. That host remains caller-owned; disposing or aborting the Mesurer mount does not dispose it. Use `onPluginHost` only when code needs the host before startup settles.
68
74
 
69
75
  | Entry | Purpose |
70
76
  | --- | --- |
@@ -77,27 +83,47 @@ For explicit plugin composition, `mesurer-solid/plugins` also exports `select`,
77
83
  | `mesurer-codex` | Run the optional loopback Codex queue companion |
78
84
  | `mesurer-codex-connect` | Start or reuse the matching Codex companion and register the current Codex session |
79
85
 
86
+ Programmatic injection reuses an existing connected instance by default. Lifecycle-owning integrations can set `recoverDisconnected: true` in `MesurerInjectConfig` to remount Mesurer when page DOM replacement disconnects its host. The option defaults to `false`, so ordinary one-shot injection does not silently reappear after disposal.
87
+
80
88
  ## Features
81
89
 
82
- - Select one or many rendered elements and inspect exact geometry.
90
+ - Select one or many rendered HTML or SVG elements and inspect exact geometry.
83
91
  - Measure distance and pairwise multi-selection spacing.
84
92
  - Use X-ray, guides, rulers, and persisted settings.
93
+ - Add page-scoped columns, rows, or pixel grids with the optional `layoutGuides()` plugin. Guide edits participate in plugin undo/redo, and Context includes the current page's saved guides with their visibility state.
85
94
  - Inspect Typography and preview reversible direct copy/style changes.
86
95
  - Arrange selected UI into a Desired position without changing source.
87
- - Capture visible-tab regions through the optional Screenshot plugin.
96
+ - Capture page regions through the optional Screenshot plugin. It selects native host capture, the Chromium extension adapter, or browser display capture internally.
88
97
  - Read selection, measurements, guides, annotations, layout, styles, and saved human intent through Context and agent APIs.
89
98
  - Keep saved annotations across same-tab reloads and conservatively rebind them to their original DOM targets; markers, cards, and ownership evidence stay attached through scrolling, repeated-note markers stay local, Add Note remains available while a note is open, and cards/composers occlude Select hover and selection chrome.
90
99
  - Extend the runtime with tools, settings, overlays, commands, hooks, state, and services.
91
100
  - Compact the toolbar to active controls without changing tool state or order.
101
+ - Choose System, Light, or Dark appearance while keeping the same theme across isolated and document-backed Mesurer UI.
92
102
 
93
103
  Arrange is not a toolbar mode. It can be activated before a selection exists and enables Select automatically. Turning Arrange off leaves Select active; turning Select off exits Arrange.
94
104
 
105
+ Toolbar dragging starts after the pointer crosses the drag threshold. Dragging from Settings, Guide, or plugin triggers closes the open menu or panel. Pointer activity inside menus, dialogs, form controls, editable regions, and sliders does not drag the toolbar.
106
+
107
+ Select and agent point inspection use the same rendered hit-test path. They can target SVG and visible `pointer-events:none` descendants instead of collapsing those descendants to an interactive ancestor. Open shadow roots are traversed; closed shadow roots remain browser-owned boundaries. Context and annotations accept the same SVG targets. Arrange and direct text editing only mutate HTML elements.
108
+
109
+ Persisted workspace evidence is page-scoped by route, including sorted query parameters. In-tab navigation swaps the current page workspace without carrying page-owned guides or selection state to another route. Toolbar placement remains tab-session UI and survives those route changes and reloads.
110
+
95
111
  Direct text editing respects native editing boundaries. Descendants of an editable ancestor remain native, while a nested `contenteditable="false"` boundary ends inherited editability and can become a Mesurer target when the normal direct-text rules pass. Mixed inline copy can target the exact direct text run before or after an inline child without flattening or recreating that child, and the host element keeps its native DOM APIs throughout the interaction.
96
112
 
97
113
  While direct text editing is active, Mesurer keeps one visible edit ring, keeps the selected dimensions pill and Typography separated by the same `2px` rendered gap when the card is below the source, and keeps Typography stationary during ordinary pointer movement. The selection-adjacent Add Note button is suppressed only for the active edit and returns when the editor closes; saved annotation markers and panels remain available.
98
114
 
99
115
  Mesurer previews text, styles, and Arrange transforms only while it still owns the value it applied. Host-authored changes take ownership and are preserved through undo/redo, Live review, cleanup, and disposal.
100
116
 
117
+ ## Appearance
118
+
119
+ The default appearance is `"system"`. Users can change it under **Settings > General > Appearance**, or applications can choose the initial mode:
120
+
121
+ ```ts
122
+ mountMesurer({ theme: "light" })
123
+ ```
124
+
125
+ The setting accepts `"system"`, `"light"`, or `"dark"` and persists with the other Mesurer settings. System mode follows `prefers-color-scheme`.
126
+
101
127
  ## Shortcuts
102
128
 
103
129
  Global shortcuts are enabled by default. Turn them off from **Settings > General > Shortcuts** or pass `shortcutsEnabled: false` to `mountMesurer()`. Toolbar controls, editor-local keys, and Escape/cancel behavior remain available.
@@ -111,6 +137,7 @@ Global shortcuts are enabled by default. Turn them off from **Settings > General
111
137
  | `R` | Rulers |
112
138
  | `A` | Typography |
113
139
  | `G` | Guides |
140
+ | `L` | Layout Guides when the plugin is enabled |
114
141
  | `H` / `V` | Horizontal / vertical guide orientation |
115
142
  | `Alt` / `Option` | Distance overlay |
116
143
  | `Cmd/Ctrl + ,` | Settings |
@@ -170,8 +197,10 @@ See [Queue Context feedback to Codex](https://github.com/jhomra21/mesurer-solid/
170
197
  - [Capabilities](https://github.com/jhomra21/mesurer-solid/blob/main/docs/CAPABILITIES.md)
171
198
  - [Getting started](https://github.com/jhomra21/mesurer-solid/blob/main/docs/GETTING_STARTED.md)
172
199
  - [Direct text editing and Typography](https://github.com/jhomra21/mesurer-solid/blob/main/docs/TEXT_EDITING.md)
173
- - [Arrange](https://github.com/jhomra21/mesurer-solid/blob/main/docs/ARRANGE.md)
200
+ - [Arrange](https://github.com/jhomra21/mesurer-solid/blob/main/docs/ARRANGE.md) and [Layout Guides](https://github.com/jhomra21/mesurer-solid/blob/main/docs/LAYOUT_GUIDES.md)
201
+ - [Measurements and distance geometry](https://github.com/jhomra21/mesurer-solid/blob/main/docs/MEASUREMENTS.md)
174
202
  - [Screenshots](https://github.com/jhomra21/mesurer-solid/blob/main/docs/SCREENSHOTS.md)
203
+ - [Electron renderer example](https://github.com/jhomra21/mesurer-solid/blob/main/examples/electron-renderer/README.md)
175
204
  - [Context workflow](https://github.com/jhomra21/mesurer-solid/blob/main/docs/CONTEXT_WORKFLOW.md)
176
205
  - [Queue Context feedback to Codex](https://github.com/jhomra21/mesurer-solid/blob/main/docs/CODEX.md)
177
206
  - [Browser and agent integration](https://github.com/jhomra21/mesurer-solid/blob/main/docs/BROWSER_HARNESS.md)
package/dist/agent.d.ts CHANGED
@@ -110,20 +110,20 @@ export type AgentViewportSnapshot = {
110
110
  export type AgentFeedbackSnapshot = {
111
111
  viewport: AgentViewportSnapshot;
112
112
  elements: AgentElementInspection[];
113
- plugins: MesurerPluginDescription | undefined;
113
+ plugins: MesurerPluginDescription;
114
114
  pluginState: PluginStateSnapshot;
115
115
  };
116
116
  type AgentCommandArgs = Parameters<MesurerPluginHost["command"]["execute"]>[1];
117
117
  export type MesurerAgentHarness = {
118
118
  ready(): Promise<void>;
119
- describe(): Promise<MesurerPluginDescription | undefined>;
119
+ describe(): Promise<MesurerPluginDescription>;
120
120
  inspect(selector: string, index?: number): AgentElementInspection | null;
121
121
  inspectAll(selector: string, limit?: number): AgentElementInspection[];
122
122
  at(x: number, y: number): AgentElementInspection | null;
123
123
  distance(a: string, b: string): AgentDistance | null;
124
124
  viewport(): AgentViewportSnapshot;
125
125
  feedback(selectors?: string[]): Promise<AgentFeedbackSnapshot>;
126
- command(id: string, args?: AgentCommandArgs): Promise<void>;
126
+ command(id: string, args?: AgentCommandArgs): ReturnType<MesurerPluginHost["command"]["execute"]>;
127
127
  state(): Promise<PluginStateSnapshot>;
128
128
  textEdits(): Promise<MesurerTextEditIntent[]>;
129
129
  textEdit(id: string): Promise<MesurerTextEditIntent>;
@@ -11,14 +11,16 @@ export type MesurerCodexPluginOptions = {
11
11
  /** Remove annotations included in a delivery after Codex reports that turn finished. Defaults to true. */
12
12
  clearCompletedAnnotations?: boolean;
13
13
  };
14
- export type MesurerCodexSendRequest = {
15
- /** Optional instruction for this send. */
14
+ export type MesurerCodexQueueRequest = {
15
+ /** Optional instruction for this queue operation. */
16
16
  instruction?: string;
17
- /** Send only these saved annotation ids. When omitted, all saved annotations are sent. */
17
+ /** Queue only these saved annotation ids. When omitted, all saved annotations are queued. */
18
18
  annotationIds?: string[];
19
- /** Send to a particular bridge-visible Codex thread for this request. */
19
+ /** Queue to a particular bridge-visible Codex thread for this request. */
20
20
  thread?: string;
21
21
  };
22
+ /** @deprecated Use `MesurerCodexQueueRequest`. */
23
+ export type MesurerCodexSendRequest = MesurerCodexQueueRequest;
22
24
  export type MesurerCodexDeliveryStatus = "queued" | "working" | "completed" | "interrupted";
23
25
  export type MesurerCodexDispatchStatus = "persisting" | "persisted" | "resumed" | "already-loaded" | "unsupported" | "wake-failed" | "untracked" | "desktop-local" | "waiting-active" | "desktop-sent" | "desktop-opened" | "desktop-send-uncertain" | "desktop-wait-failed";
24
26
  export type MesurerCodexDelivery = {
@@ -35,7 +37,7 @@ export type MesurerCodexDelivery = {
35
37
  createdAt: number;
36
38
  updatedAt: number;
37
39
  };
38
- export type MesurerCodexSendResult = {
40
+ export type MesurerCodexQueueResult = {
39
41
  thread: string;
40
42
  output: string;
41
43
  /** Mesurer currently delivers feedback as a queued Codex follow-up, never as an in-flight steer. */
@@ -52,6 +54,8 @@ export type MesurerCodexSendResult = {
52
54
  /** Exact saved annotations included in this queued request. */
53
55
  annotationIds: string[];
54
56
  };
57
+ /** @deprecated Use `MesurerCodexQueueResult`. */
58
+ export type MesurerCodexSendResult = MesurerCodexQueueResult;
55
59
  export type MesurerCodexHealth = {
56
60
  /** Current bridge default target. Null when the bridge has not been bound yet. */
57
61
  thread: string | null;
@@ -91,6 +95,8 @@ export type MesurerCodexService = {
91
95
  * Queue Context for the page-pinned/default target or one explicit bridge-visible thread.
92
96
  * This does not steer or interrupt an in-flight Codex turn.
93
97
  */
94
- send(request?: MesurerCodexSendRequest): Promise<MesurerCodexSendResult>;
98
+ queue(request?: MesurerCodexQueueRequest): Promise<MesurerCodexQueueResult>;
99
+ /** @deprecated Use `queue()`; Mesurer delivery is durable queueing, not in-flight send/steer. */
100
+ send(request?: MesurerCodexQueueRequest): Promise<MesurerCodexQueueResult>;
95
101
  };
96
102
  export declare function codex(options?: MesurerCodexPluginOptions): MesurerPlugin;
@@ -11,7 +11,7 @@ export type MesurerContextService = {
11
11
  context(request?: MesurerContextRequest): Promise<MesurerContextV1>;
12
12
  contextText(request?: MesurerContextRequest): Promise<string>;
13
13
  copyContext(request?: MesurerContextRequest): Promise<void>;
14
- select(selectors: string | string[]): Promise<MesurerContextV1>;
14
+ select(selectors: string | readonly string[]): Promise<MesurerContextV1>;
15
15
  annotations(): Promise<MesurerAnnotation[]>;
16
16
  /** Remove one saved annotation after a trusted workflow has completed it. */
17
17
  removeAnnotation(annotationId: string): Promise<void>;
package/dist/context.d.ts CHANGED
@@ -147,6 +147,18 @@ export type MesurerContextGuide = {
147
147
  orientation: "vertical" | "horizontal";
148
148
  position: number;
149
149
  };
150
+ export type MesurerContextLayoutGuide = {
151
+ id: string;
152
+ kind: "columns" | "rows" | "grid";
153
+ visible: boolean;
154
+ color: string;
155
+ opacity: number;
156
+ count: number;
157
+ size: number;
158
+ gutter: number;
159
+ offset: number;
160
+ align: "stretch" | "min" | "center" | "max";
161
+ };
150
162
  export type MesurerContextMeasurement = {
151
163
  id: string;
152
164
  rect: MesurerContextRect;
@@ -209,6 +221,7 @@ export type MesurerContextV1 = {
209
221
  targets: MesurerContextTarget[];
210
222
  visualContext: {
211
223
  guides: MesurerContextGuide[];
224
+ layoutGuides: MesurerContextLayoutGuide[];
212
225
  measurements: MesurerContextMeasurement[];
213
226
  distances: MesurerContextDistance[];
214
227
  };
@@ -266,7 +279,7 @@ export type MesurerWorkspaceContextSource = {
266
279
  deltaX: number;
267
280
  deltaY: number;
268
281
  snapped?: boolean;
269
- elementRef?: HTMLElement | null;
282
+ elementRef?: Element | null;
270
283
  }>;
271
284
  activeMeasurement: {
272
285
  id: string;
@@ -274,14 +287,14 @@ export type MesurerWorkspaceContextSource = {
274
287
  deltaX: number;
275
288
  deltaY: number;
276
289
  snapped?: boolean;
277
- elementRef?: HTMLElement | null;
290
+ elementRef?: Element | null;
278
291
  } | null;
279
292
  heldDistances: Array<{
280
293
  id: string;
281
294
  rectA: MesurerContextRect;
282
295
  rectB: MesurerContextRect;
283
- elementRefA?: HTMLElement | null;
284
- elementRefB?: HTMLElement | null;
296
+ elementRefA?: Element | null;
297
+ elementRefB?: Element | null;
285
298
  horizontal: {
286
299
  x1: number;
287
300
  x2: number;
@@ -302,14 +315,14 @@ export type MesurerWorkspaceContextSource = {
302
315
  }>;
303
316
  } | null;
304
317
  currentSelection(): {
305
- elements: HTMLElement[];
318
+ elements: Element[];
306
319
  region: MesurerContextRect | null;
307
320
  };
308
321
  annotations(): MesurerAnnotation[];
309
322
  annotation(id: string): (MesurerAnnotation & {
310
323
  resolvedTargets: Array<{
311
324
  target: MesurerAnnotationTarget;
312
- element: HTMLElement | null;
325
+ element: Element | null;
313
326
  }>;
314
327
  }) | null;
315
328
  annotationRect(id: string): MesurerContextRect | null;
@@ -319,6 +332,7 @@ export declare function captureMesurerContext(options: {
319
332
  ownerDocument: Document;
320
333
  ownerWindow: Window;
321
334
  request?: MesurerContextRequest;
335
+ layoutGuides?: MesurerContextLayoutGuide[];
322
336
  }): MesurerContextV1;
323
337
  export declare function formatMesurerContext(context: MesurerContextV1): string;
324
338
  export declare function createMesurerCapturePlan(context: MesurerContextV1): MesurerCapturePlanV1;
@@ -327,5 +341,6 @@ export declare function reviewMesurerAnnotation(options: {
327
341
  ownerDocument: Document;
328
342
  ownerWindow: Window;
329
343
  annotationId: string;
344
+ layoutGuides?: MesurerContextLayoutGuide[];
330
345
  }): MesurerReviewV1;
331
346
  export declare function copyTextToClipboard(ownerDocument: Document, ownerWindow: Window, text: string): Promise<void>;
package/dist/core.d.ts CHANGED
@@ -77,7 +77,7 @@ export type OverlayContribution = {
77
77
  };
78
78
  export type CommandHandler = (args: PluginValue | undefined, context: {
79
79
  source?: PluginValue;
80
- }) => void | Promise<void>;
80
+ }) => void | PluginValue | Promise<void | PluginValue>;
81
81
  export type HookHandler = (event: PluginValue) => void | Promise<void>;
82
82
  export type StateSliceDefinition<T extends PluginValue = PluginValue> = {
83
83
  id: string;
@@ -103,7 +103,7 @@ export type MesurerPluginContext = {
103
103
  };
104
104
  command: {
105
105
  register(id: string, handler: CommandHandler): Registration;
106
- execute(id: string, args?: PluginValue, source?: PluginValue): Promise<void>;
106
+ execute(id: string, args?: PluginValue, source?: PluginValue): Promise<PluginValue | undefined>;
107
107
  };
108
108
  hook: {
109
109
  on(name: string, handler: HookHandler): Registration;
@@ -120,8 +120,8 @@ export type MesurerPluginContext = {
120
120
  export type MesurerPlugin = {
121
121
  id: PluginId;
122
122
  version?: string;
123
- requires?: string[];
124
- provides?: string[];
123
+ requires?: readonly string[];
124
+ provides?: readonly string[];
125
125
  setup(context: MesurerPluginContext): void | Promise<void>;
126
126
  };
127
127
  export type MesurerPluginDescription = {
@@ -175,7 +175,7 @@ export type MesurerPluginHost = {
175
175
  get<T>(id: string): T | undefined;
176
176
  };
177
177
  command: {
178
- execute(id: string, args?: PluginValue, source?: PluginValue): Promise<void>;
178
+ execute(id: string, args?: PluginValue, source?: PluginValue): Promise<PluginValue | undefined>;
179
179
  };
180
180
  hook: {
181
181
  emit(name: string, event: PluginValue): Promise<void>;
@@ -190,6 +190,7 @@ export type MesurerPluginHost = {
190
190
  };
191
191
  export declare const defineMesurerPlugin: <T extends MesurerPlugin>(plugin: T) => T;
192
192
  export declare function createMesurerPluginHost(): MesurerPluginHost;
193
- export declare function createMesurerRuntime(options?: {
194
- plugins?: MesurerPlugin[];
195
- }): Promise<MesurerPluginHost>;
193
+ export type MesurerRuntimeOptions = {
194
+ plugins?: readonly MesurerPlugin[];
195
+ };
196
+ export declare function createMesurerRuntime(options?: MesurerRuntimeOptions): Promise<MesurerPluginHost>;
package/dist/core.js CHANGED
@@ -85,8 +85,10 @@ function i() {
85
85
  if (!o) throw Error(`Unknown Mesurer command: ${e}`);
86
86
  let s = t === 0, c = s ? x("history") : null;
87
87
  t += 1;
88
+ let l;
88
89
  try {
89
- await o.handler(n, { source: a }), await i.emit("command", {
90
+ let t = await o.handler(n, { source: a });
91
+ t !== void 0 && (l = t), await i.emit("command", {
90
92
  id: e,
91
93
  args: n
92
94
  });
@@ -97,6 +99,7 @@ function i() {
97
99
  let e = x("history");
98
100
  C(c, e) || (h.push(c), h.length > r && h.shift(), g.length = 0);
99
101
  }
102
+ return l;
100
103
  }, E = () => {
101
104
  let e = h.pop();
102
105
  return e ? (g.push(x("history")), g.length > r && g.shift(), S(e, "history"), _(void 0, "history"), !0) : !1;
@@ -223,8 +226,8 @@ function i() {
223
226
  plugins: [...a.values()].map(({ plugin: e }) => ({
224
227
  id: e.id,
225
228
  version: e.version,
226
- requires: e.requires ?? [],
227
- provides: e.provides ?? []
229
+ requires: [...e.requires ?? []],
230
+ provides: [...e.provides ?? []]
228
231
  })),
229
232
  tools: z.tools().map(({ active: e, disabled: t, hidden: n, icon: r, menu: i, ...a }) => a),
230
233
  settings: z.settings().map(({ controls: e = [], ...t }) => ({
@@ -258,15 +261,24 @@ function i() {
258
261
  return z;
259
262
  }
260
263
  //#endregion
264
+ //#region ../mesurer-core/dist/runtime.js
265
+ async function a(e = {}) {
266
+ let t = i();
267
+ try {
268
+ for (let n of e.plugins ?? []) await t.load(n);
269
+ return t;
270
+ } catch (e) {
271
+ throw t.dispose(), e;
272
+ }
273
+ }
274
+ //#endregion
261
275
  //#region src/core.ts
262
- var a = (e) => e;
263
- function o() {
276
+ var o = (e) => e;
277
+ function s() {
264
278
  return i();
265
279
  }
266
- async function s(e = {}) {
267
- let t = o();
268
- for (let n of e.plugins ?? []) await t.load(n);
269
- return t;
280
+ async function c(e = {}) {
281
+ return a(e);
270
282
  }
271
283
  //#endregion
272
- export { o as createMesurerPluginHost, s as createMesurerRuntime, a as defineMesurerPlugin };
284
+ export { s as createMesurerPluginHost, c as createMesurerRuntime, o as defineMesurerPlugin };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import { type MesurerAgentHarness, type MesurerTextEditIntent } from "./agent";
2
- import type { ArrangeCapturePlan, ArrangeIntent, ArrangePresentation, ArrangeReview } from "./arrange";
2
+ import { type ArrangeCapturePlan, type ArrangeIntent, type ArrangePresentation, type ArrangeReview } from "./arrange";
3
3
  import type { MesurerAnnotation, MesurerCapturePlanV1, MesurerContextRequest, MesurerContextV1, MesurerReviewV1 } from "./context";
4
4
  import type { MesurerPlugin, MesurerPluginDescription, MesurerPluginHost } from "./core";
5
5
  import { type MesurerHostLayerMode } from "./host-layer";
6
6
  export type ColorPickerFormat = "hex" | "rgb" | "hsl" | "oklch";
7
+ export type MesurerTheme = "system" | "light" | "dark";
8
+ /** Public built-in names, aligned with the factories exported by `mesurer-solid/plugins`. */
9
+ export type MesurerBuiltin = "select" | "xray" | "colorPicker" | "rulers" | "typography" | "guides" | "distance" | "settings";
10
+ /** @deprecated Internal compatibility ids used by the older `excludePlugins` option. */
7
11
  export type MesurerBuiltinPluginId = "select" | "xray" | "color-picker" | "rulers" | "text-inspector" | "guides" | "distance" | "settings";
8
12
  export type LinePattern = "solid" | "dashed" | "dotted";
9
13
  export type LineStyle = {
@@ -80,6 +84,7 @@ export type MesurerStoredSettings = {
80
84
  multiMeasureEnabled?: boolean;
81
85
  persistOnReload?: boolean;
82
86
  shortcutsEnabled?: boolean;
87
+ theme?: MesurerTheme;
83
88
  guideStyle?: Partial<GuideStyle>;
84
89
  selectionSpacingStyle?: Partial<SelectionSpacingStyle>;
85
90
  rulerSettings?: Partial<RulerSettings>;
@@ -106,6 +111,7 @@ export type MesurerPersistence = {
106
111
  saveWorkspace(workspace: MesurerStoredWorkspace): void;
107
112
  clearWorkspace(): void;
108
113
  clearSettings(): void;
114
+ setPageKey?(pageKey: string): void;
109
115
  subscribe?: (listener: (snapshot: MesurerPersistenceSnapshot | null, source?: {
110
116
  settings?: boolean;
111
117
  workspace?: boolean;
@@ -122,6 +128,7 @@ export type MesurerOptions = {
122
128
  hoverHighlightEnabled?: boolean;
123
129
  persistOnReload?: boolean;
124
130
  shortcutsEnabled?: boolean;
131
+ theme?: MesurerTheme;
125
132
  persistKey?: string;
126
133
  persistence?: MesurerPersistence;
127
134
  onPersistenceError?: (cause: unknown) => void;
@@ -139,10 +146,19 @@ export type MesurerOptions = {
139
146
  * Omitted first-party plugins remain toggleable in Settings from the same canonical
140
147
  * registry; callers never maintain a separate availability or Settings list.
141
148
  */
142
- plugins?: MesurerPlugin[];
143
- excludePlugins?: MesurerBuiltinPluginId[];
149
+ plugins?: readonly MesurerPlugin[];
150
+ /** Built-in tools to omit from this mount. Names match the public plugin factories. */
151
+ excludeBuiltins?: readonly MesurerBuiltin[];
152
+ /** @deprecated Use `excludeBuiltins`. This compatibility option uses internal built-in ids. */
153
+ excludePlugins?: readonly MesurerBuiltinPluginId[];
154
+ /**
155
+ * Advanced: supply a plugin host owned outside this mount.
156
+ * The caller remains responsible for disposing the supplied host.
157
+ */
144
158
  pluginHost?: MesurerPluginHost;
159
+ /** Advanced: receive the live host before configured plugin setup settles. */
145
160
  onPluginHost?: (host: MesurerPluginHost) => void;
161
+ /** @deprecated Await `mounted.ready` instead. */
146
162
  onPluginsReady?: (host: MesurerPluginHost) => void;
147
163
  onPluginError?: (cause: unknown, pluginId: string) => void;
148
164
  };
@@ -151,7 +167,16 @@ export type MountMesurerOptions = MesurerOptions & {
151
167
  isolate?: boolean;
152
168
  shadowMode?: ShadowRootMode;
153
169
  topLayer?: boolean;
170
+ /**
171
+ * Expose the browser agent API on the owning Window. The mounted handle always
172
+ * exposes `agent`; this option controls only the global bridge.
173
+ */
154
174
  agent?: boolean | AgentBridgeOptions;
175
+ /**
176
+ * Dispose this mount when the signal aborts.
177
+ * A caller-supplied pluginHost keeps its separate lifecycle.
178
+ */
179
+ signal?: AbortSignal;
155
180
  };
156
181
  export type MesurerAgentCapabilities = {
157
182
  protocol: "mesurer.agent/v1";
@@ -170,7 +195,7 @@ export type MesurerContextHarness = {
170
195
  capabilities(): MesurerAgentCapabilities;
171
196
  context(request?: MesurerContextRequest): Promise<MesurerContextV1>;
172
197
  contextText(request?: MesurerContextRequest): Promise<string>;
173
- select(selectors: string | string[]): Promise<MesurerContextV1>;
198
+ select(selectors: string | readonly string[]): Promise<MesurerContextV1>;
174
199
  annotations(): Promise<MesurerAnnotation[]>;
175
200
  review(annotationId?: string): Promise<MesurerReviewV1 | MesurerReviewV1[]>;
176
201
  capturePlan(request?: MesurerContextRequest): Promise<MesurerCapturePlanV1>;
@@ -186,16 +211,19 @@ export type MesurerArrangeHarness = {
186
211
  };
187
212
  export type MesurerBrowserAgent = MesurerAgentHarness & MesurerContextHarness & MesurerArrangeHarness;
188
213
  export type MountedMesurer = {
189
- element: HTMLDivElement;
190
- root: HTMLDivElement | ShadowRoot;
214
+ readonly element: HTMLDivElement;
215
+ readonly root: HTMLDivElement | ShadowRoot;
191
216
  readonly hostLayer: MesurerHostLayerMode;
217
+ /** @deprecated Await `ready` when direct plugin-host access is required. */
192
218
  readonly pluginHost: MesurerPluginHost | undefined;
193
- readonly ready: Promise<void>;
219
+ /** Resolves to the live plugin host after startup and initial rendered state settle. */
220
+ readonly ready: Promise<MesurerPluginHost>;
194
221
  readonly agent: MesurerBrowserAgent;
222
+ service<T>(id: string): Promise<T>;
195
223
  context(request?: MesurerContextRequest): Promise<MesurerContextV1>;
196
224
  contextText(request?: MesurerContextRequest): Promise<string>;
197
225
  copyContext(request?: MesurerContextRequest): Promise<void>;
198
- select(selectors: string | string[]): Promise<MesurerContextV1>;
226
+ select(selectors: string | readonly string[]): Promise<MesurerContextV1>;
199
227
  annotations(): Promise<MesurerAnnotation[]>;
200
228
  review(annotationId?: string): Promise<MesurerReviewV1 | MesurerReviewV1[]>;
201
229
  capturePlan(request?: MesurerContextRequest): Promise<MesurerCapturePlanV1>;
@@ -209,7 +237,7 @@ export type MountedMesurer = {
209
237
  textEdits(): Promise<MesurerTextEditIntent[]>;
210
238
  textEdit(id: string): Promise<MesurerTextEditIntent>;
211
239
  bringToFront(): void;
212
- describe(): MesurerPluginDescription | undefined;
240
+ describe(): Promise<MesurerPluginDescription>;
213
241
  dispose(): void;
214
242
  };
215
243
  export declare function mountMesurer(options?: MountMesurerOptions): MountedMesurer;
@@ -224,7 +252,7 @@ export { MESURER_VERSION } from "./version";
224
252
  export type { AgentDistance, AgentEdges, AgentElementInspection, AgentFeedbackSnapshot, AgentRect, AgentViewportSnapshot, CreateMesurerAgentHarnessOptions, MesurerAgentHarness, MesurerTextEditIntent, MesurerTextEditService, MesurerTextStyleChange, MesurerTextStyleProperty, } from "./agent";
225
253
  export type { ArrangeCapturePlan, ArrangeIntent, ArrangeOffset, ArrangePresentation, ArrangeRect, ArrangeReview, ArrangeReviewTarget, ArrangeTarget, MesurerArrangeService, } from "./arrange";
226
254
  export { captureMesurerContext, copyTextToClipboard, createMesurerCapturePlan, formatMesurerContext, reviewMesurerAnnotation, } from "./context";
227
- export type { MesurerAnnotation, MesurerAnnotationBaseline, MesurerAnnotationTarget, MesurerCapturePlanV1, MesurerContextDistance, MesurerContextEdges, MesurerContextGuide, MesurerContextMeasurement, MesurerContextRect, MesurerContextRequest, MesurerContextTarget, MesurerContextV1, MesurerElementFingerprint, MesurerElementInspection, MesurerReviewChange, MesurerReviewMetricChange, MesurerReviewPresenceChange, MesurerReviewV1, } from "./context";
255
+ export type { MesurerAnnotation, MesurerAnnotationBaseline, MesurerAnnotationTarget, MesurerCapturePlanV1, MesurerContextDistance, MesurerContextEdges, MesurerContextGuide, MesurerContextLayoutGuide, MesurerContextMeasurement, MesurerContextRect, MesurerContextRequest, MesurerContextTarget, MesurerContextV1, MesurerElementFingerprint, MesurerElementInspection, MesurerReviewChange, MesurerReviewMetricChange, MesurerReviewPresenceChange, MesurerReviewV1, } from "./context";
228
256
  export { createMesurerPluginHost, createMesurerRuntime, defineMesurerPlugin } from "./core";
229
257
  export type { CommandHandler as MesurerCommandHandler, MesurerPlugin, MesurerPluginContext, MesurerPluginDescription, MesurerPluginHost, OverlayContribution, Registration as MesurerRegistration, SettingsContribution, StateSliceDefinition, ToolContribution, } from "./core";
230
258
  export type { MesurerHostLayerMode } from "./host-layer";