getobsrv 0.8.0 → 0.10.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/README.md +19 -6
- package/out/main/index.js +35 -6
- package/out/mcp/server.js +11 -1
- package/out/preload/app.js +10 -0
- package/out/renderer/assets/{index-Cgx8iixu.js → index-CQVE5hF3.js} +541 -153
- package/out/renderer/assets/{index-BeroS4wv.css → index-CZUlXhEb.css} +233 -82
- package/out/renderer/index.html +2 -2
- package/out/shared/control.js +12 -1
- package/out/shared/ipcPayloads.js +7 -0
- package/package.json +2 -1
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
--chrome-0: #141414;
|
|
107
107
|
--chrome-1: #1c1c1c;
|
|
108
108
|
--chrome-2: #262626;
|
|
109
|
+
--chrome-3: #3a3a3a;
|
|
109
110
|
--line: #333333;
|
|
110
111
|
--text-0: #ededed;
|
|
111
112
|
--text-1: #8a8a8a;
|
|
@@ -131,66 +132,206 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
|
|
|
131
132
|
.app { height: 100%; display: flex; flex-direction: column; }
|
|
132
133
|
.muted { color: var(--text-1); }
|
|
133
134
|
.warn { color: var(--warn); }
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
/*
|
|
137
|
-
.
|
|
138
|
-
|
|
135
|
+
.chrome .warn { white-space: nowrap; }
|
|
136
|
+
|
|
137
|
+
/* Two strips. Browsing above; the controls that describe the simulated
|
|
138
|
+
screen below, on a darker ground so the rows read as separate registers.
|
|
139
|
+
44 + 38 = 82 must equal TOOLBAR_H in src/main/ipc.ts. Pinned heights also
|
|
140
|
+
keep a late reflow from resizing the panes under NativeSlot's observer. */
|
|
141
|
+
.chrome { flex: 0 0 auto; }
|
|
142
|
+
.chrome-row {
|
|
143
|
+
box-sizing: border-box;
|
|
139
144
|
display: flex;
|
|
140
145
|
align-items: center;
|
|
141
146
|
gap: 8px;
|
|
142
147
|
padding: 0 10px;
|
|
143
|
-
background: var(--chrome-1);
|
|
144
148
|
border-bottom: 1px solid var(--line);
|
|
145
149
|
}
|
|
146
|
-
.
|
|
150
|
+
.chrome-browse { height: 44px; background: var(--chrome-1); }
|
|
151
|
+
.chrome-screen { height: 38px; background: var(--chrome-0); }
|
|
152
|
+
.chrome-spacer { flex: 1 1 auto; }
|
|
153
|
+
|
|
154
|
+
/* No blanket `button` rule here, ever. The old `.toolbar button { width:
|
|
155
|
+
26px }` was specificity (0,1,1) and silently clipped the update button to
|
|
156
|
+
"v0."; every control now sizes itself. */
|
|
157
|
+
.icon-button {
|
|
158
|
+
flex: 0 0 auto;
|
|
159
|
+
width: 30px;
|
|
160
|
+
height: 30px;
|
|
161
|
+
display: inline-flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
justify-content: center;
|
|
147
164
|
background: var(--chrome-2);
|
|
148
165
|
color: var(--text-0);
|
|
149
166
|
border: 1px solid var(--line);
|
|
150
|
-
border-radius:
|
|
151
|
-
width: 26px;
|
|
152
|
-
height: 24px;
|
|
167
|
+
border-radius: 5px;
|
|
153
168
|
cursor: pointer;
|
|
154
169
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
color: var(--text-0);
|
|
158
|
-
border: 1px solid var(--line);
|
|
159
|
-
border-radius: 4px;
|
|
160
|
-
height: 24px;
|
|
161
|
-
}
|
|
162
|
-
.url-form { flex: 1 1 auto; min-width: 120px; }
|
|
170
|
+
|
|
171
|
+
.url-form { flex: 1 1 auto; min-width: 160px; }
|
|
163
172
|
.url-form input {
|
|
164
173
|
width: 100%;
|
|
165
|
-
height:
|
|
174
|
+
height: 30px;
|
|
166
175
|
box-sizing: border-box;
|
|
167
|
-
padding: 0
|
|
176
|
+
padding: 0 10px;
|
|
177
|
+
font-size: 12.5px;
|
|
168
178
|
background: var(--field);
|
|
169
179
|
color: var(--text-0);
|
|
170
180
|
border: 1px solid var(--line);
|
|
171
|
-
border-radius:
|
|
181
|
+
border-radius: 5px;
|
|
172
182
|
}
|
|
173
183
|
.url-form input[readonly] { color: var(--text-1); }
|
|
184
|
+
|
|
185
|
+
.status-cluster { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; font-size: 10px; }
|
|
174
186
|
.badge-error {
|
|
175
187
|
color: var(--error);
|
|
176
188
|
border: 1px solid var(--error);
|
|
177
189
|
border-radius: 4px;
|
|
178
|
-
padding:
|
|
190
|
+
padding: 2px 6px;
|
|
191
|
+
font-family: var(--mono);
|
|
192
|
+
font-variant-numeric: tabular-nums;
|
|
193
|
+
}
|
|
194
|
+
/* An update is neither a warning nor an error, so it carries no colour. */
|
|
195
|
+
.update-button {
|
|
196
|
+
flex: 0 0 auto;
|
|
197
|
+
height: 30px;
|
|
198
|
+
padding: 0 10px;
|
|
199
|
+
white-space: nowrap;
|
|
200
|
+
font-family: var(--mono);
|
|
201
|
+
font-size: 11.5px;
|
|
179
202
|
font-variant-numeric: tabular-nums;
|
|
203
|
+
background: var(--chrome-2);
|
|
204
|
+
color: var(--text-0);
|
|
205
|
+
border: 1px solid var(--line);
|
|
206
|
+
border-radius: 5px;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Our surface, the platform's popup: the real select sits transparent on
|
|
211
|
+
top, keeping the native menu, keyboard handling and Playwright's
|
|
212
|
+
`selectOption` working unchanged. */
|
|
213
|
+
.select-shell {
|
|
214
|
+
position: relative;
|
|
215
|
+
flex: 0 0 auto;
|
|
216
|
+
display: inline-flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
height: 30px;
|
|
219
|
+
box-sizing: border-box;
|
|
220
|
+
padding: 0 26px 0 9px;
|
|
221
|
+
font-size: 11.5px;
|
|
222
|
+
white-space: nowrap;
|
|
223
|
+
background: var(--chrome-2);
|
|
224
|
+
color: var(--text-0);
|
|
225
|
+
border: 1px solid var(--line);
|
|
226
|
+
border-radius: 5px;
|
|
227
|
+
}
|
|
228
|
+
.select-shell select {
|
|
229
|
+
position: absolute;
|
|
230
|
+
inset: 0;
|
|
231
|
+
width: 100%;
|
|
232
|
+
height: 100%;
|
|
233
|
+
opacity: 0;
|
|
234
|
+
appearance: none;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
}
|
|
237
|
+
.select-shell .select-chevron {
|
|
238
|
+
position: absolute;
|
|
239
|
+
right: 7px;
|
|
240
|
+
display: flex;
|
|
241
|
+
color: var(--text-1);
|
|
242
|
+
pointer-events: none;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Segmented groups. Pressed is a fill step and weight, never hue. */
|
|
246
|
+
.segmented {
|
|
247
|
+
flex: 0 0 auto;
|
|
248
|
+
display: inline-flex;
|
|
249
|
+
height: 30px;
|
|
250
|
+
/* A `div`, unlike a `button`, does not get the UA's border-box: without
|
|
251
|
+
this the 1px border makes the group 32px and it sits 1px proud of every
|
|
252
|
+
other 30px control in the row. */
|
|
253
|
+
box-sizing: border-box;
|
|
254
|
+
border: 1px solid var(--line);
|
|
255
|
+
border-radius: 5px;
|
|
256
|
+
overflow: hidden;
|
|
257
|
+
}
|
|
258
|
+
.segmented button {
|
|
259
|
+
height: 100%;
|
|
260
|
+
padding: 0 11px;
|
|
261
|
+
font-size: 11.5px;
|
|
262
|
+
background: var(--chrome-2);
|
|
263
|
+
color: var(--text-1);
|
|
264
|
+
border: 0;
|
|
265
|
+
border-right: 1px solid var(--line);
|
|
266
|
+
border-radius: 0;
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
}
|
|
269
|
+
.segmented button:last-child { border-right: 0; }
|
|
270
|
+
.segmented button[aria-pressed='true'] { background: var(--chrome-3); color: var(--text-0); }
|
|
271
|
+
|
|
272
|
+
/* Right-aligned deliberately: the native pane is an OS-level WebContentsView
|
|
273
|
+
that covers renderer paint, so a menu reaching the left half of the window
|
|
274
|
+
would be invisible. 200px against a 900px minWidth always fits the target
|
|
275
|
+
pane. */
|
|
276
|
+
.overflow { position: relative; flex: 0 0 auto; }
|
|
277
|
+
.overflow-menu {
|
|
278
|
+
position: absolute;
|
|
279
|
+
top: calc(100% + 4px);
|
|
280
|
+
right: 0;
|
|
281
|
+
z-index: 10;
|
|
282
|
+
width: 200px;
|
|
283
|
+
padding: 5px 0;
|
|
284
|
+
font-size: 11.5px;
|
|
285
|
+
background: var(--chrome-2);
|
|
286
|
+
border: 1px solid var(--line);
|
|
287
|
+
border-radius: 6px;
|
|
288
|
+
}
|
|
289
|
+
.menu-row {
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
gap: 8px;
|
|
293
|
+
width: 100%;
|
|
294
|
+
box-sizing: border-box;
|
|
295
|
+
height: 30px;
|
|
296
|
+
padding: 0 11px;
|
|
297
|
+
text-align: left;
|
|
298
|
+
background: none;
|
|
299
|
+
color: var(--text-0);
|
|
300
|
+
border: 0;
|
|
301
|
+
border-radius: 0;
|
|
302
|
+
cursor: pointer;
|
|
303
|
+
}
|
|
304
|
+
.menu-row:hover { background: var(--chrome-3); }
|
|
305
|
+
/* Which drawer is open. The fill step is the chrome's one pressed idiom,
|
|
306
|
+
shared with `.segmented`; `:hover` is the same fill, so the pressed row also
|
|
307
|
+
carries a 2px inset rule down its leading edge. That marker is the part that
|
|
308
|
+
survives hover — hovering any row fills it, but only the open drawer's row
|
|
309
|
+
keeps the rule — and it is the same neutral weight the surround control uses
|
|
310
|
+
to mark its selection. No hue, here or anywhere else in the chrome. */
|
|
311
|
+
.menu-row[aria-pressed='true'] {
|
|
312
|
+
background: var(--chrome-3);
|
|
313
|
+
box-shadow: inset 2px 0 0 var(--text-0);
|
|
314
|
+
}
|
|
315
|
+
.menu-sep { height: 1px; margin: 5px 0; background: var(--line); }
|
|
316
|
+
/* Sized to the 16px icons the other rows lead with, and stripped of the UA's
|
|
317
|
+
own margins, so every label in the menu starts at the same x — a checkbox
|
|
318
|
+
row and an icon row otherwise indent differently. */
|
|
319
|
+
.overflow-menu input[type='checkbox'] {
|
|
320
|
+
accent-color: var(--text-0);
|
|
321
|
+
flex: 0 0 auto;
|
|
322
|
+
width: 16px;
|
|
323
|
+
height: 16px;
|
|
324
|
+
margin: 0;
|
|
180
325
|
}
|
|
181
|
-
.pixel-exact { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
|
|
182
|
-
/* The checked state would otherwise take the OS accent colour. */
|
|
183
|
-
.toolbar input[type='checkbox'] { accent-color: var(--text-0); }
|
|
184
326
|
|
|
185
|
-
/* Focus is a 1px inset neutral ring
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
text field) shows the ring. */
|
|
189
|
-
.toolbar :is(button, select, input):focus { outline: none; }
|
|
190
|
-
.toolbar :is(button, select, input):focus-visible {
|
|
327
|
+
/* Focus is a 1px inset neutral ring, never Chromium's accent outline. */
|
|
328
|
+
.chrome :is(button, select, input):focus { outline: none; }
|
|
329
|
+
.chrome :is(button, select, input):focus-visible {
|
|
191
330
|
outline: 1px solid var(--text-1);
|
|
192
331
|
outline-offset: -1px;
|
|
193
332
|
}
|
|
333
|
+
/* The real select is transparent, so its ring must land on the shell. */
|
|
334
|
+
.select-shell:has(select:focus-visible) { outline: 1px solid var(--text-1); outline-offset: -1px; }
|
|
194
335
|
|
|
195
336
|
.panes { flex: 1 1 auto; display: flex; min-height: 0; }
|
|
196
337
|
.pane {
|
|
@@ -232,11 +373,29 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
|
|
|
232
373
|
}
|
|
233
374
|
.pane-footer .role { color: var(--text-0); letter-spacing: 0.08em; }
|
|
234
375
|
|
|
235
|
-
/* Segmented surround control
|
|
236
|
-
|
|
376
|
+
/* Segmented surround control: the same 30px border-box as `.segmented` and
|
|
377
|
+
`.select-shell`, so the screen row has one hit-target rhythm. `box-sizing`
|
|
378
|
+
for the same reason `.segmented` needs it — a `div` gets no border-box from
|
|
379
|
+
the UA, so the 1px border would otherwise push the group to 32px.
|
|
380
|
+
|
|
381
|
+
The swatch stays 12px: the swatch is the affordance, the button around it is
|
|
382
|
+
just the target. The pressed state stays an inset `--text-0` ring, and is
|
|
383
|
+
the one place the chrome does not use the `--chrome-3` fill step — a fill
|
|
384
|
+
ringing a 12px swatch competes with the swatch for the eye, and `--chrome-3`
|
|
385
|
+
lands two steps from the graphite swatch itself. This is the one control
|
|
386
|
+
whose whole job is judging a tint, so the marker stays off the tint. */
|
|
387
|
+
.surround-control {
|
|
388
|
+
box-sizing: border-box;
|
|
389
|
+
flex: 0 0 auto;
|
|
390
|
+
display: inline-flex;
|
|
391
|
+
height: 30px;
|
|
392
|
+
border: 1px solid var(--line);
|
|
393
|
+
border-radius: 5px;
|
|
394
|
+
overflow: hidden;
|
|
395
|
+
}
|
|
237
396
|
.surround-control button {
|
|
238
|
-
width:
|
|
239
|
-
height:
|
|
397
|
+
width: 28px;
|
|
398
|
+
height: 100%;
|
|
240
399
|
border: 0;
|
|
241
400
|
border-right: 1px solid var(--line);
|
|
242
401
|
border-radius: 0;
|
|
@@ -247,28 +406,25 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
|
|
|
247
406
|
.surround-control button[aria-pressed='true'] { box-shadow: inset 0 0 0 2px var(--text-0); }
|
|
248
407
|
.surround-swatch { display: block; width: 12px; height: 12px; margin: 0 auto; }
|
|
249
408
|
|
|
409
|
+
/* Nothing sits to the target pane's left in solo view, so the divider goes. */
|
|
410
|
+
.panes[data-panes='target'] .target-pane { border-left: 0; }
|
|
411
|
+
|
|
250
412
|
/* Numbers never jitter as a slider moves. */
|
|
251
413
|
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
|
|
252
414
|
|
|
253
|
-
/*
|
|
254
|
-
|
|
255
|
-
border-color: var(--text-0);
|
|
256
|
-
background: var(--chrome-1);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/* Agent control: same neutral chrome as every other toggle; the activity
|
|
260
|
-
badge is text-weight only — an agent driving the window is a status, not
|
|
261
|
-
an alert. */
|
|
262
|
-
.toolbar .agent-toggle { width: auto; padding: 0 8px; white-space: nowrap; }
|
|
415
|
+
/* Agent control opens a loopback server, so the chip persists while it is
|
|
416
|
+
enabled; recent activity is a brighter text weight, not a colour. */
|
|
263
417
|
.agent-activity {
|
|
418
|
+
flex: 0 0 auto;
|
|
264
419
|
color: var(--text-1);
|
|
265
420
|
border: 1px solid var(--line);
|
|
266
421
|
border-radius: 4px;
|
|
267
|
-
padding:
|
|
268
|
-
font-size:
|
|
269
|
-
letter-spacing: 0.
|
|
422
|
+
padding: 2px 7px;
|
|
423
|
+
font-size: 10px;
|
|
424
|
+
letter-spacing: 0.08em;
|
|
270
425
|
white-space: nowrap;
|
|
271
426
|
}
|
|
427
|
+
.agent-activity.active { color: var(--text-0); border-color: var(--text-0); }
|
|
272
428
|
|
|
273
429
|
/* Drawers sit beside the panes, never over them: the native WebContentsView is
|
|
274
430
|
an OS-level overlay and would cover anything painted on top of it. */
|
|
@@ -363,7 +519,6 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
|
|
|
363
519
|
}
|
|
364
520
|
.strip button:focus { outline: none; }
|
|
365
521
|
.strip button:focus-visible { outline: 1px solid var(--text-1); outline-offset: 1px; }
|
|
366
|
-
.toolbar .close-image { width: auto; padding: 0 8px; }
|
|
367
522
|
/* `safe`: a file wider than the pane scrolls from its left edge instead of
|
|
368
523
|
losing it off-screen. */
|
|
369
524
|
.image-pane .pane-body { display: flex; align-items: flex-start; justify-content: safe center; }
|
|
@@ -396,6 +551,27 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
|
|
|
396
551
|
`left: 0` holds while a wide canvas is scrolled horizontally. The fill is a
|
|
397
552
|
neutral grey: only the rule under it is red, so the one chromatic thing on
|
|
398
553
|
screen is the error itself. */
|
|
554
|
+
/* At 1:1 the render usually overflows and the pane is a scroll box, so it
|
|
555
|
+
starts at the top-left and you pan around it. When it is *smaller* than the
|
|
556
|
+
pane — a small preset, fit mode, or solo target on a wide window — hugging
|
|
557
|
+
the corner leaves it stranded against the chrome with dead field to the
|
|
558
|
+
right. Centre it instead.
|
|
559
|
+
|
|
560
|
+
`safe` is what lets one rule serve both cases: it falls back to start
|
|
561
|
+
alignment the moment the content overflows, so the top-left of an oversized
|
|
562
|
+
render stays reachable by scrolling. Plain `center` would push it out of
|
|
563
|
+
reach. The image pane already centres this way. */
|
|
564
|
+
.target-pane .pane-body {
|
|
565
|
+
display: flex;
|
|
566
|
+
flex-direction: column;
|
|
567
|
+
align-items: safe center;
|
|
568
|
+
justify-content: safe center;
|
|
569
|
+
}
|
|
570
|
+
/* The stall notice spans the pane, not the canvas: it is sticky at `left: 0`
|
|
571
|
+
and must stay pane-width while a wide canvas scrolls under it, so it opts
|
|
572
|
+
out of the centring its sibling wants. */
|
|
573
|
+
.target-pane .pane-body > .stall { align-self: stretch; }
|
|
574
|
+
|
|
399
575
|
/* position: relative anchors the agent highlight to the canvas origin. */
|
|
400
576
|
.target-wrap { min-width: min-content; position: relative; }
|
|
401
577
|
|
|
@@ -438,28 +614,15 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
|
|
|
438
614
|
.stall button:focus { outline: none; }
|
|
439
615
|
.stall button:focus-visible { outline: 1px solid var(--text-1); outline-offset: 1px; }
|
|
440
616
|
|
|
441
|
-
/* v1.1 fit & pan. The 1:1/Fit
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
.
|
|
445
|
-
width: auto;
|
|
446
|
-
height: 22px;
|
|
447
|
-
padding: 0 8px;
|
|
448
|
-
border: 0;
|
|
449
|
-
border-right: 1px solid var(--line);
|
|
450
|
-
border-radius: 0;
|
|
451
|
-
background: var(--chrome-2);
|
|
452
|
-
font-variant-numeric: tabular-nums;
|
|
453
|
-
cursor: pointer;
|
|
454
|
-
}
|
|
455
|
-
.view-control button:last-child { border-right: 0; }
|
|
456
|
-
.view-control button[aria-pressed='true'] {
|
|
457
|
-
box-shadow: inset 0 0 0 1px var(--text-0);
|
|
458
|
-
font-weight: 600;
|
|
459
|
-
}
|
|
617
|
+
/* v1.1 fit & pan. The 1:1/Fit control is a `Segmented`, so `.segmented` is
|
|
618
|
+
the whole of its styling — the old `.view-control button` rules lived here,
|
|
619
|
+
and being (0,1,1) and later in the file they beat `.segmented button` and
|
|
620
|
+
split the two groups apart. Nothing about 1:1/Fit is control-specific. */
|
|
460
621
|
|
|
461
|
-
/*
|
|
462
|
-
|
|
622
|
+
/* Option is what a view gesture needs, so Option is what the cursor answers:
|
|
623
|
+
the jump-to-1:1 zoom in fit, the grab affordances for the pan chords
|
|
624
|
+
(middle drag, Option+drag) at 1:1. Unmodified, both views are interactive
|
|
625
|
+
and the page's own cursors show through. Cursors only — no overlay may sit
|
|
463
626
|
on the pixels under inspection. */
|
|
464
627
|
.target-canvas.fit { cursor: zoom-in; }
|
|
465
628
|
.target-canvas.pan-ready { cursor: grab; }
|
|
@@ -478,18 +641,6 @@ html, body, #root { margin: 0; height: 100%; background: var(--chrome-0); color:
|
|
|
478
641
|
.browser-notice p { margin: 0.75rem 0 0; color: var(--text-1); }
|
|
479
642
|
.browser-notice code { font-family: var(--mono); }
|
|
480
643
|
|
|
481
|
-
/* An update is neither a warning nor an error, so it carries no colour: the
|
|
482
|
-
only chromatic pixels in this chrome stay reserved for real attention. */
|
|
483
|
-
/* `.toolbar button` is (0,1,1) and pins every button to a 26px square, so this
|
|
484
|
-
needs two classes to outrank it — a bare `.update-button` renders as "v0.". */
|
|
485
|
-
.toolbar .update-button {
|
|
486
|
-
font-family: var(--mono);
|
|
487
|
-
font-variant-numeric: tabular-nums;
|
|
488
|
-
white-space: nowrap;
|
|
489
|
-
width: auto;
|
|
490
|
-
flex: 0 0 auto;
|
|
491
|
-
padding: 0 8px;
|
|
492
|
-
}
|
|
493
644
|
.version-block { margin: 8px 0; }
|
|
494
645
|
.version-row {
|
|
495
646
|
display: flex;
|
package/out/renderer/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:" />
|
|
6
6
|
<title>Obsrv</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-CQVE5hF3.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CZUlXhEb.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/out/shared/control.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.defaultControlFilePath = defaultControlFilePath;
|
|
|
9
9
|
exports.presetApplyError = presetApplyError;
|
|
10
10
|
exports.profileApplyError = profileApplyError;
|
|
11
11
|
exports.viewModeApplyError = viewModeApplyError;
|
|
12
|
+
exports.panesApplyError = panesApplyError;
|
|
12
13
|
exports.pixelExactApplyError = pixelExactApplyError;
|
|
13
14
|
exports.parseClick = parseClick;
|
|
14
15
|
exports.parseHighlight = parseHighlight;
|
|
@@ -48,6 +49,7 @@ exports.CONTROL_COMMANDS = [
|
|
|
48
49
|
'setPreset',
|
|
49
50
|
'setProfile',
|
|
50
51
|
'setViewMode',
|
|
52
|
+
'setPanes',
|
|
51
53
|
'captureVisible',
|
|
52
54
|
// v0.5 drive controls (spec §14 "Drive controls").
|
|
53
55
|
'scroll',
|
|
@@ -151,6 +153,9 @@ function profileApplyError(id) {
|
|
|
151
153
|
function viewModeApplyError(v) {
|
|
152
154
|
return v === '1:1' || v === 'fit' ? null : `setViewMode payload must be { mode: '1:1' | 'fit' }`;
|
|
153
155
|
}
|
|
156
|
+
function panesApplyError(v) {
|
|
157
|
+
return v === 'both' || v === 'target' ? null : `setPanes payload must be { panes: 'both' | 'target' }`;
|
|
158
|
+
}
|
|
154
159
|
function pixelExactApplyError(v) {
|
|
155
160
|
return typeof v === 'boolean' ? null : 'setPixelExact payload must be { on: boolean }';
|
|
156
161
|
}
|
|
@@ -222,5 +227,11 @@ function parseControlStatus(raw) {
|
|
|
222
227
|
return null;
|
|
223
228
|
if (mode !== 'url' && mode !== 'image')
|
|
224
229
|
return null;
|
|
225
|
-
|
|
230
|
+
// An app older than this field is common — the MCP server ships on npm and
|
|
231
|
+
// the app ships as a DMG, so they update independently. Absent defaults;
|
|
232
|
+
// present-but-wrong is still a malformed status.
|
|
233
|
+
const panes = raw.panes ?? 'both';
|
|
234
|
+
if (panes !== 'both' && panes !== 'target')
|
|
235
|
+
return null;
|
|
236
|
+
return { version, url, presetId, profileId, viewMode, panes, mode };
|
|
226
237
|
}
|
|
@@ -153,10 +153,17 @@ function parseUiState(raw) {
|
|
|
153
153
|
return null;
|
|
154
154
|
if (mode !== 'url' && mode !== 'image')
|
|
155
155
|
return null;
|
|
156
|
+
// A renderer older than this field cannot exist (both ship in one app), but
|
|
157
|
+
// the report is validated like any other payload: absent defaults to both,
|
|
158
|
+
// present-but-wrong drops the whole report.
|
|
159
|
+
const panes = raw.panes ?? 'both';
|
|
160
|
+
if (panes !== 'both' && panes !== 'target')
|
|
161
|
+
return null;
|
|
156
162
|
return {
|
|
157
163
|
presetId,
|
|
158
164
|
profileId,
|
|
159
165
|
viewMode,
|
|
166
|
+
panes,
|
|
160
167
|
mode,
|
|
161
168
|
targetBounds: parseRect(raw.targetBounds),
|
|
162
169
|
canvasBounds: parseRect(raw.canvasBounds),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getobsrv",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "See your site the way 1x screens see it",
|
|
5
5
|
"main": "./out/main/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"@vitest/browser": "^3.2.7",
|
|
43
43
|
"electron-builder": "^26.15.3",
|
|
44
44
|
"electron-vite": "^3.1.0",
|
|
45
|
+
"lucide-react": "^1.34.0",
|
|
45
46
|
"playwright": "^1.62.1",
|
|
46
47
|
"typescript": "^5.9.3",
|
|
47
48
|
"vite": "^6.4.3",
|