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.
@@ -12782,7 +12782,13 @@ const useStore = create()((set) => ({
12782
12782
  update: null,
12783
12783
  image: null,
12784
12784
  surround: "graphite",
12785
- viewMode: "1:1",
12785
+ // Fit, not 1:1: fit never enlarges past 1:1, so a render that already fits
12786
+ // its pane opens at true magnification anyway, while one that does not is
12787
+ // shown whole instead of as its top-left corner. Fit is interactive, so
12788
+ // this costs nothing — and the footer names the actual magnification
12789
+ // whenever fit is minifying.
12790
+ viewMode: "fit",
12791
+ panes: "both",
12786
12792
  fitScale: null,
12787
12793
  agentPan: null,
12788
12794
  agentHighlight: null,
@@ -12810,6 +12816,9 @@ const useStore = create()((set) => ({
12810
12816
  setImage: (image) => set({ image }),
12811
12817
  setSurround: (surround) => set({ surround }),
12812
12818
  setViewMode: (viewMode) => set({ viewMode }),
12819
+ // No `agentHighlight: null` here, unlike setPreset: hiding a pane does not
12820
+ // re-raster the target, so the highlight still marks the pixels it marked.
12821
+ setPanes: (panes) => set({ panes }),
12813
12822
  setFitScale: (fitScale2) => set({ fitScale: fitScale2 }),
12814
12823
  requestAgentPan: (p) => set((s) => ({ agentPan: { ...p, seq: (s.agentPan?.seq ?? 0) + 1 } })),
12815
12824
  clearAgentPan: () => set({ agentPan: null }),
@@ -14021,7 +14030,6 @@ function TargetCanvas({ onFatal, imageFrame }) {
14021
14030
  canvas.addEventListener("wheel", onWheel, { passive: false });
14022
14031
  const onWindowUp = (e) => {
14023
14032
  if (useStore.getState().mode !== "url") return;
14024
- if (useStore.getState().viewMode !== "1:1") return;
14025
14033
  if (panRef.current || e.button === 1) return;
14026
14034
  if (e.altKey && !forwardDrag.current) return;
14027
14035
  if (e.target === canvas) return;
@@ -14095,7 +14103,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14095
14103
  return body instanceof HTMLElement ? body : null;
14096
14104
  };
14097
14105
  const startPan = (e) => {
14098
- if (viewMode !== "1:1") return;
14106
+ if (fit) return;
14099
14107
  if (!(e.button === 1 || e.button === 0 && e.altKey)) return;
14100
14108
  const body = paneBody();
14101
14109
  if (!body) return;
@@ -14144,7 +14152,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14144
14152
  pendingJump.current = null;
14145
14153
  }, [viewMode]);
14146
14154
  const jumpTo1x = (e) => {
14147
- if (viewMode !== "fit") return;
14155
+ if (!fit || !e.altKey) return;
14148
14156
  const r = e.currentTarget.getBoundingClientRect();
14149
14157
  pendingJump.current = jumpScroll(
14150
14158
  e.clientX - r.left,
@@ -14185,7 +14193,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14185
14193
  }, [agentHighlight, clearAgentHighlight]);
14186
14194
  const send = (type) => (e) => {
14187
14195
  if (mode !== "url") return;
14188
- if (viewMode !== "1:1" || panRef.current || e.button === 1) return;
14196
+ if (panRef.current || e.button === 1) return;
14189
14197
  if (e.altKey && !forwardDrag.current) return;
14190
14198
  const out = mouseEvent(type, e, e.currentTarget.getBoundingClientRect(), scale * dsf / dpr);
14191
14199
  if (out) {
@@ -14216,7 +14224,7 @@ function TargetCanvas({ onFatal, imageFrame }) {
14216
14224
  "canvas",
14217
14225
  {
14218
14226
  ref: canvasRef,
14219
- className: `target-canvas${fit ? " fit" : panning ? " panning" : altHeld ? " pan-ready" : ""}`,
14227
+ className: `target-canvas${panning ? " panning" : altHeld ? fit ? " fit" : " pan-ready" : ""}`,
14220
14228
  tabIndex: 0,
14221
14229
  style: { width: `${cssW}px`, height: `${cssH}px` },
14222
14230
  onClick: jumpTo1x,
@@ -14228,12 +14236,12 @@ function TargetCanvas({ onFatal, imageFrame }) {
14228
14236
  onMouseUp: send("mouseUp"),
14229
14237
  onMouseMove: send("mouseMove"),
14230
14238
  onKeyDown: (e) => {
14231
- if (mode !== "url" || viewMode !== "1:1") return;
14239
+ if (mode !== "url") return;
14232
14240
  if (!e.metaKey && !e.ctrlKey) e.preventDefault();
14233
14241
  for (const ev of keyDownEvents(e)) window.obsrv.sendInput(ev);
14234
14242
  },
14235
14243
  onKeyUp: (e) => {
14236
- if (mode !== "url" || viewMode !== "1:1") return;
14244
+ if (mode !== "url") return;
14237
14245
  const ev = keyUpEvent(e);
14238
14246
  if (ev) window.obsrv.sendInput(ev);
14239
14247
  }
@@ -14271,6 +14279,321 @@ function Toast() {
14271
14279
  if (!toast) return null;
14272
14280
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "toast", role: "status", children: toast });
14273
14281
  }
14282
+ /**
14283
+ * @license lucide-react v1.34.0 - ISC
14284
+ *
14285
+ * This source code is licensed under the ISC license.
14286
+ * See the LICENSE file in the root directory of this source tree.
14287
+ */
14288
+ const mergeClasses = (...classes) => classes.filter((className, index, array) => {
14289
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
14290
+ }).join(" ").trim();
14291
+ /**
14292
+ * @license lucide-react v1.34.0 - ISC
14293
+ *
14294
+ * This source code is licensed under the ISC license.
14295
+ * See the LICENSE file in the root directory of this source tree.
14296
+ */
14297
+ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
14298
+ /**
14299
+ * @license lucide-react v1.34.0 - ISC
14300
+ *
14301
+ * This source code is licensed under the ISC license.
14302
+ * See the LICENSE file in the root directory of this source tree.
14303
+ */
14304
+ const toCamelCase = (string) => string.replace(
14305
+ /^([A-Z])|[\s-_]+(\w)/g,
14306
+ (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()
14307
+ );
14308
+ /**
14309
+ * @license lucide-react v1.34.0 - ISC
14310
+ *
14311
+ * This source code is licensed under the ISC license.
14312
+ * See the LICENSE file in the root directory of this source tree.
14313
+ */
14314
+ const toPascalCase = (string) => {
14315
+ const camelCase = toCamelCase(string);
14316
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
14317
+ };
14318
+ /**
14319
+ * @license lucide-react v1.34.0 - ISC
14320
+ *
14321
+ * This source code is licensed under the ISC license.
14322
+ * See the LICENSE file in the root directory of this source tree.
14323
+ */
14324
+ var defaultAttributes = {
14325
+ xmlns: "http://www.w3.org/2000/svg",
14326
+ width: 24,
14327
+ height: 24,
14328
+ viewBox: "0 0 24 24",
14329
+ fill: "none",
14330
+ stroke: "currentColor",
14331
+ strokeWidth: 2,
14332
+ strokeLinecap: "round",
14333
+ strokeLinejoin: "round"
14334
+ };
14335
+ /**
14336
+ * @license lucide-react v1.34.0 - ISC
14337
+ *
14338
+ * This source code is licensed under the ISC license.
14339
+ * See the LICENSE file in the root directory of this source tree.
14340
+ */
14341
+ const hasA11yProp = (props) => {
14342
+ for (const prop in props) {
14343
+ if (prop.startsWith("aria-") || prop === "role" || prop === "title") {
14344
+ return true;
14345
+ }
14346
+ }
14347
+ return false;
14348
+ };
14349
+ const LucideContext = reactExports.createContext({});
14350
+ const useLucideContext = () => reactExports.useContext(LucideContext);
14351
+ const Icon$1 = reactExports.forwardRef(
14352
+ ({ color, size, strokeWidth, absoluteStrokeWidth, className = "", children, iconNode, ...rest }, ref) => {
14353
+ const {
14354
+ size: contextSize = 24,
14355
+ strokeWidth: contextStrokeWidth = 2,
14356
+ absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
14357
+ color: contextColor = "currentColor",
14358
+ className: contextClass = ""
14359
+ } = useLucideContext() ?? {};
14360
+ const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;
14361
+ return reactExports.createElement(
14362
+ "svg",
14363
+ {
14364
+ ref,
14365
+ ...defaultAttributes,
14366
+ width: size ?? contextSize ?? defaultAttributes.width,
14367
+ height: size ?? contextSize ?? defaultAttributes.height,
14368
+ stroke: color ?? contextColor,
14369
+ strokeWidth: calculatedStrokeWidth,
14370
+ className: mergeClasses("lucide", contextClass, className),
14371
+ ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
14372
+ ...rest
14373
+ },
14374
+ [
14375
+ ...iconNode.map(([tag, attrs]) => reactExports.createElement(tag, attrs)),
14376
+ ...Array.isArray(children) ? children : [children]
14377
+ ]
14378
+ );
14379
+ }
14380
+ );
14381
+ /**
14382
+ * @license lucide-react v1.34.0 - ISC
14383
+ *
14384
+ * This source code is licensed under the ISC license.
14385
+ * See the LICENSE file in the root directory of this source tree.
14386
+ */
14387
+ const createLucideIcon = (iconName, iconNode) => {
14388
+ const Component = reactExports.forwardRef(
14389
+ ({ className, ...props }, ref) => reactExports.createElement(Icon$1, {
14390
+ ref,
14391
+ iconNode,
14392
+ className: mergeClasses(
14393
+ `lucide-${toKebabCase(toPascalCase(iconName))}`,
14394
+ `lucide-${iconName}`,
14395
+ className
14396
+ ),
14397
+ ...props
14398
+ })
14399
+ );
14400
+ Component.displayName = toPascalCase(iconName);
14401
+ return Component;
14402
+ };
14403
+ /**
14404
+ * @license lucide-react v1.34.0 - ISC
14405
+ *
14406
+ * This source code is licensed under the ISC license.
14407
+ * See the LICENSE file in the root directory of this source tree.
14408
+ */
14409
+ const __iconNode$7 = [
14410
+ ["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
14411
+ ["path", { d: "M19 12H5", key: "x3x0zl" }]
14412
+ ];
14413
+ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$7);
14414
+ /**
14415
+ * @license lucide-react v1.34.0 - ISC
14416
+ *
14417
+ * This source code is licensed under the ISC license.
14418
+ * See the LICENSE file in the root directory of this source tree.
14419
+ */
14420
+ const __iconNode$6 = [
14421
+ ["path", { d: "M5 12h14", key: "1ays0h" }],
14422
+ ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
14423
+ ];
14424
+ const ArrowRight = createLucideIcon("arrow-right", __iconNode$6);
14425
+ /**
14426
+ * @license lucide-react v1.34.0 - ISC
14427
+ *
14428
+ * This source code is licensed under the ISC license.
14429
+ * See the LICENSE file in the root directory of this source tree.
14430
+ */
14431
+ const __iconNode$5 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
14432
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$5);
14433
+ /**
14434
+ * @license lucide-react v1.34.0 - ISC
14435
+ *
14436
+ * This source code is licensed under the ISC license.
14437
+ * See the LICENSE file in the root directory of this source tree.
14438
+ */
14439
+ const __iconNode$4 = [
14440
+ ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }],
14441
+ ["circle", { cx: "12", cy: "5", r: "1", key: "gxeob9" }],
14442
+ ["circle", { cx: "12", cy: "19", r: "1", key: "lyex9k" }]
14443
+ ];
14444
+ const EllipsisVertical = createLucideIcon("ellipsis-vertical", __iconNode$4);
14445
+ /**
14446
+ * @license lucide-react v1.34.0 - ISC
14447
+ *
14448
+ * This source code is licensed under the ISC license.
14449
+ * See the LICENSE file in the root directory of this source tree.
14450
+ */
14451
+ const __iconNode$3 = [
14452
+ ["path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8", key: "1p45f6" }],
14453
+ ["path", { d: "M21 3v5h-5", key: "1q7to0" }]
14454
+ ];
14455
+ const RotateCw = createLucideIcon("rotate-cw", __iconNode$3);
14456
+ /**
14457
+ * @license lucide-react v1.34.0 - ISC
14458
+ *
14459
+ * This source code is licensed under the ISC license.
14460
+ * See the LICENSE file in the root directory of this source tree.
14461
+ */
14462
+ const __iconNode$2 = [
14463
+ [
14464
+ "path",
14465
+ {
14466
+ d: "M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915",
14467
+ key: "1i5ecw"
14468
+ }
14469
+ ],
14470
+ ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
14471
+ ];
14472
+ const Settings = createLucideIcon("settings", __iconNode$2);
14473
+ /**
14474
+ * @license lucide-react v1.34.0 - ISC
14475
+ *
14476
+ * This source code is licensed under the ISC license.
14477
+ * See the LICENSE file in the root directory of this source tree.
14478
+ */
14479
+ const __iconNode$1 = [
14480
+ ["path", { d: "M10 5H3", key: "1qgfaw" }],
14481
+ ["path", { d: "M12 19H3", key: "yhmn1j" }],
14482
+ ["path", { d: "M14 3v4", key: "1sua03" }],
14483
+ ["path", { d: "M16 17v4", key: "1q0r14" }],
14484
+ ["path", { d: "M21 12h-9", key: "1o4lsq" }],
14485
+ ["path", { d: "M21 19h-5", key: "1rlt1p" }],
14486
+ ["path", { d: "M21 5h-7", key: "1oszz2" }],
14487
+ ["path", { d: "M8 10v4", key: "tgpxqk" }],
14488
+ ["path", { d: "M8 12H3", key: "a7s4jb" }]
14489
+ ];
14490
+ const SlidersHorizontal = createLucideIcon("sliders-horizontal", __iconNode$1);
14491
+ /**
14492
+ * @license lucide-react v1.34.0 - ISC
14493
+ *
14494
+ * This source code is licensed under the ISC license.
14495
+ * See the LICENSE file in the root directory of this source tree.
14496
+ */
14497
+ const __iconNode = [
14498
+ ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
14499
+ ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
14500
+ ];
14501
+ const X = createLucideIcon("x", __iconNode);
14502
+ const ICONS = {
14503
+ back: ArrowLeft,
14504
+ forward: ArrowRight,
14505
+ reload: RotateCw,
14506
+ overflow: EllipsisVertical,
14507
+ close: X,
14508
+ sliders: SlidersHorizontal,
14509
+ gear: Settings,
14510
+ chevron: ChevronDown
14511
+ };
14512
+ function Icon({ name, size = 16 }) {
14513
+ const Glyph = ICONS[name];
14514
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Glyph, { size, strokeWidth: 1.5, "aria-hidden": "true", focusable: "false" });
14515
+ }
14516
+ function OverflowMenu({ children }) {
14517
+ const [open, setOpen] = reactExports.useState(false);
14518
+ const ref = reactExports.useRef(null);
14519
+ const buttonRef = reactExports.useRef(null);
14520
+ const close = () => setOpen(false);
14521
+ reactExports.useEffect(() => {
14522
+ if (!open) return;
14523
+ const onDown = (e) => {
14524
+ if (!ref.current?.contains(e.target)) setOpen(false);
14525
+ };
14526
+ const onKey = (e) => {
14527
+ if (e.key !== "Escape") return;
14528
+ setOpen(false);
14529
+ buttonRef.current?.focus();
14530
+ };
14531
+ const onBlur = () => setOpen(false);
14532
+ document.addEventListener("mousedown", onDown);
14533
+ document.addEventListener("keydown", onKey);
14534
+ window.addEventListener("blur", onBlur);
14535
+ const offNativeFocus = window.obsrv.onNativeFocused(onBlur);
14536
+ return () => {
14537
+ document.removeEventListener("mousedown", onDown);
14538
+ document.removeEventListener("keydown", onKey);
14539
+ window.removeEventListener("blur", onBlur);
14540
+ offNativeFocus();
14541
+ };
14542
+ }, [open]);
14543
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "overflow", ref, children: [
14544
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14545
+ "button",
14546
+ {
14547
+ ref: buttonRef,
14548
+ className: "icon-button overflow-button",
14549
+ type: "button",
14550
+ title: "More",
14551
+ "aria-label": "More",
14552
+ "aria-haspopup": "true",
14553
+ "aria-expanded": open,
14554
+ onClick: () => setOpen((o) => !o),
14555
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "overflow" })
14556
+ }
14557
+ ),
14558
+ open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-menu", children: children(close) })
14559
+ ] });
14560
+ }
14561
+ function Segmented({
14562
+ className,
14563
+ ariaLabel,
14564
+ value,
14565
+ options,
14566
+ onChange
14567
+ }) {
14568
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `segmented ${className}`, role: "group", "aria-label": ariaLabel, children: options.map((o) => /* @__PURE__ */ jsxRuntimeExports.jsx(
14569
+ "button",
14570
+ {
14571
+ type: "button",
14572
+ className: o.className,
14573
+ title: o.title,
14574
+ "aria-pressed": value === o.id,
14575
+ onClick: () => onChange(o.id),
14576
+ children: o.label
14577
+ },
14578
+ o.id
14579
+ )) });
14580
+ }
14581
+ function Select({ className, value, label, ariaLabel, onChange, children }) {
14582
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "select-shell", children: [
14583
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "select-label", "aria-hidden": "true", children: label }),
14584
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "select-chevron", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "chevron", size: 14 }) }),
14585
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14586
+ "select",
14587
+ {
14588
+ className,
14589
+ value,
14590
+ "aria-label": ariaLabel,
14591
+ onChange: (e) => onChange(e.target.value),
14592
+ children
14593
+ }
14594
+ )
14595
+ ] });
14596
+ }
14274
14597
  const SURROUNDS = [
14275
14598
  { id: "black", label: "Black surround", swatch: "#000000" },
14276
14599
  { id: "graphite", label: "Graphite surround", swatch: "#2a2a2a" },
@@ -14280,6 +14603,10 @@ const VIEWS = [
14280
14603
  { id: "1:1", label: "1:1", title: "Actual size" },
14281
14604
  { id: "fit", label: "Fit", title: "Fit the pane — not pixel-exact" }
14282
14605
  ];
14606
+ const PANES = [
14607
+ { id: "both", label: "Both", title: "Native and target side by side" },
14608
+ { id: "target", label: "Target", title: "The target render alone, full width" }
14609
+ ];
14283
14610
  function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
14284
14611
  const mode = useStore((s) => s.mode);
14285
14612
  const presetId = useStore((s) => s.presetId);
@@ -14300,6 +14627,8 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
14300
14627
  const setSurround = useStore((s) => s.setSurround);
14301
14628
  const viewMode = useStore((s) => s.viewMode);
14302
14629
  const setViewMode = useStore((s) => s.setViewMode);
14630
+ const panes = useStore((s) => s.panes);
14631
+ const setPanes = useStore((s) => s.setPanes);
14303
14632
  const agentControl = useStore((s) => s.settings.agentControl);
14304
14633
  const setSettings = useStore((s) => s.setSettings);
14305
14634
  const inputRef = reactExports.useRef(null);
@@ -14344,152 +14673,206 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
14344
14673
  setUrl(applied);
14345
14674
  setDraft(applied);
14346
14675
  };
14347
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "toolbar", children: [
14348
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", title: "Back", onClick: () => window.obsrv.back(), children: "‹" }),
14349
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", title: "Forward", onClick: () => window.obsrv.forward(), children: "›" }),
14350
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", title: "Reload", onClick: () => window.obsrv.reload(), children: "⟳" }),
14351
- /* @__PURE__ */ jsxRuntimeExports.jsx("form", { className: "url-form", onSubmit: submit, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
14352
- "input",
14353
- {
14354
- ref: inputRef,
14355
- value: draft,
14356
- readOnly,
14357
- spellCheck: false,
14358
- placeholder: "Enter a URL, or drop a PNG",
14359
- onChange: (e) => setDraft(e.target.value),
14360
- onKeyDown: (e) => {
14361
- if (e.key === "Escape") setDraft(barText);
14676
+ const presetLabel = SCREEN_PRESETS.find((p) => p.id === presetId)?.label ?? "Custom";
14677
+ const profileLabel = PANEL_PROFILES.find((p) => p.id === profileId)?.label ?? profileId;
14678
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "chrome", children: [
14679
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "chrome-row chrome-browse", children: [
14680
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14681
+ "button",
14682
+ {
14683
+ className: "icon-button",
14684
+ type: "button",
14685
+ title: "Back",
14686
+ "aria-label": "Back",
14687
+ onClick: () => window.obsrv.back(),
14688
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "back" })
14362
14689
  }
14363
- }
14364
- ) }),
14365
- mode === "image" && /* @__PURE__ */ jsxRuntimeExports.jsx(
14366
- "button",
14367
- {
14368
- className: "close-image",
14369
- type: "button",
14370
- title: "Back to the live page",
14371
- "aria-label": "Back to the live page",
14372
- onClick: () => setMode("url"),
14373
- children: "✕"
14374
- }
14375
- ),
14376
- loading && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "muted", children: "loading…" }),
14377
- error && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "badge-error", title: error.description, children: error.code }),
14378
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
14379
- "select",
14380
- {
14381
- className: "preset-select",
14382
- value: presetId,
14383
- onChange: (e) => setPreset(e.target.value),
14384
- children: [
14385
- /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Laptops", children: SCREEN_PRESETS.filter((p) => p.group === "laptop").map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id)) }),
14386
- /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Desktops", children: SCREEN_PRESETS.filter((p) => p.group === "desktop").map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id)) }),
14387
- /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Mobile", children: SCREEN_PRESETS.filter((p) => p.group === "mobile").map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id)) }),
14388
- /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: CUSTOM_PRESET_ID, children: "Custom" })
14389
- ]
14390
- }
14391
- ),
14392
- viewport.clamped && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "warn", children: [
14393
- "clamped to ",
14394
- viewport.width,
14395
- "×",
14396
- viewport.height
14397
- ] }),
14398
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "view-control", role: "group", "aria-label": "Target view", children: VIEWS.map((v) => /* @__PURE__ */ jsxRuntimeExports.jsx(
14399
- "button",
14400
- {
14401
- type: "button",
14402
- className: v.id === "fit" ? "view-fit" : "view-1x",
14403
- title: v.title,
14404
- "aria-pressed": viewMode === v.id,
14405
- onClick: () => setViewMode(v.id),
14406
- children: v.label
14407
- },
14408
- v.id
14409
- )) }),
14410
- /* @__PURE__ */ jsxRuntimeExports.jsx(
14411
- "select",
14412
- {
14413
- className: "profile-select",
14414
- value: profileId,
14415
- onChange: (e) => setProfile(e.target.value),
14416
- children: PANEL_PROFILES.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id))
14417
- }
14418
- ),
14419
- /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "pixel-exact", children: [
14690
+ ),
14420
14691
  /* @__PURE__ */ jsxRuntimeExports.jsx(
14421
- "input",
14692
+ "button",
14422
14693
  {
14423
- type: "checkbox",
14424
- checked: pixelExact,
14425
- onChange: (e) => setPixelExact(e.target.checked)
14694
+ className: "icon-button",
14695
+ type: "button",
14696
+ title: "Forward",
14697
+ "aria-label": "Forward",
14698
+ onClick: () => window.obsrv.forward(),
14699
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "forward" })
14700
+ }
14701
+ ),
14702
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14703
+ "button",
14704
+ {
14705
+ className: "icon-button",
14706
+ type: "button",
14707
+ title: "Reload",
14708
+ "aria-label": "Reload",
14709
+ onClick: () => window.obsrv.reload(),
14710
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "reload" })
14426
14711
  }
14427
14712
  ),
14428
- "Pixel-exact"
14713
+ /* @__PURE__ */ jsxRuntimeExports.jsx("form", { className: "url-form", onSubmit: submit, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
14714
+ "input",
14715
+ {
14716
+ ref: inputRef,
14717
+ value: draft,
14718
+ readOnly,
14719
+ spellCheck: false,
14720
+ placeholder: "Enter a URL, or drop a PNG",
14721
+ onChange: (e) => setDraft(e.target.value),
14722
+ onKeyDown: (e) => {
14723
+ if (e.key === "Escape") setDraft(barText);
14724
+ }
14725
+ }
14726
+ ) }),
14727
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "status-cluster", children: [
14728
+ mode === "image" && /* @__PURE__ */ jsxRuntimeExports.jsx(
14729
+ "button",
14730
+ {
14731
+ className: "icon-button close-image",
14732
+ type: "button",
14733
+ title: "Back to the live page",
14734
+ "aria-label": "Back to the live page",
14735
+ onClick: () => setMode("url"),
14736
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "close" })
14737
+ }
14738
+ ),
14739
+ loading && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "muted", children: "loading…" }),
14740
+ error && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "badge-error", title: error.description, children: error.code }),
14741
+ viewport.clamped && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "warn", children: [
14742
+ "clamped to ",
14743
+ viewport.width,
14744
+ "×",
14745
+ viewport.height
14746
+ ] }),
14747
+ update?.status === "available" && update.latest !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
14748
+ "button",
14749
+ {
14750
+ className: "update-button",
14751
+ type: "button",
14752
+ title: `Obsrv ${update.latest} is available — opens the download page`,
14753
+ onClick: () => void window.obsrv.openRelease(),
14754
+ children: [
14755
+ "v",
14756
+ update.latest,
14757
+ " ↓"
14758
+ ]
14759
+ }
14760
+ )
14761
+ ] }),
14762
+ /* @__PURE__ */ jsxRuntimeExports.jsx(OverflowMenu, { children: (close) => /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
14763
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "menu-row pixel-exact", children: [
14764
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14765
+ "input",
14766
+ {
14767
+ type: "checkbox",
14768
+ checked: pixelExact,
14769
+ onChange: (e) => setPixelExact(e.target.checked)
14770
+ }
14771
+ ),
14772
+ "Pixel-exact"
14773
+ ] }),
14774
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
14775
+ "button",
14776
+ {
14777
+ className: "menu-row toggle-panel",
14778
+ type: "button",
14779
+ "aria-pressed": drawer === "panel",
14780
+ onClick: () => {
14781
+ onTogglePanel();
14782
+ close();
14783
+ },
14784
+ children: [
14785
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "sliders" }),
14786
+ "Panel controls"
14787
+ ]
14788
+ }
14789
+ ),
14790
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
14791
+ "button",
14792
+ {
14793
+ className: "menu-row toggle-settings",
14794
+ type: "button",
14795
+ "aria-pressed": drawer === "settings",
14796
+ onClick: () => {
14797
+ onToggleSettings();
14798
+ close();
14799
+ },
14800
+ children: [
14801
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: "gear" }),
14802
+ "Settings"
14803
+ ]
14804
+ }
14805
+ ),
14806
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "menu-sep" }),
14807
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "menu-row agent-toggle", children: [
14808
+ /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: agentControl, onChange: toggleAgent }),
14809
+ "Agent control"
14810
+ ] })
14811
+ ] }) })
14429
14812
  ] }),
14430
- update?.status === "available" && update.latest !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
14431
- "button",
14432
- {
14433
- className: "update-button",
14434
- type: "button",
14435
- title: `Obsrv ${update.latest} is available — opens the download page`,
14436
- onClick: () => void window.obsrv.openRelease(),
14437
- children: [
14438
- "v",
14439
- update.latest,
14440
- " "
14441
- ]
14442
- }
14443
- ),
14444
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "surround-control", role: "group", "aria-label": "Pane surround", children: SURROUNDS.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(
14445
- "button",
14446
- {
14447
- type: "button",
14448
- title: s.label,
14449
- "aria-label": s.label,
14450
- "aria-pressed": surround === s.id,
14451
- onClick: () => setSurround(s.id),
14452
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "surround-swatch", style: { background: s.swatch } })
14453
- },
14454
- s.id
14455
- )) }),
14456
- agentActive && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "agent-activity", children: "AGENT" }),
14457
- /* @__PURE__ */ jsxRuntimeExports.jsx(
14458
- "button",
14459
- {
14460
- className: "agent-toggle",
14461
- type: "button",
14462
- title: "Agent control — let a local agent drive this window",
14463
- "aria-label": "Agent control",
14464
- "aria-pressed": agentControl,
14465
- onClick: toggleAgent,
14466
- children: "Agent"
14467
- }
14468
- ),
14469
- /* @__PURE__ */ jsxRuntimeExports.jsx(
14470
- "button",
14471
- {
14472
- className: "toggle-panel",
14473
- type: "button",
14474
- title: "Panel controls",
14475
- "aria-label": "Panel controls",
14476
- "aria-pressed": drawer === "panel",
14477
- onClick: onTogglePanel,
14478
- children: ""
14479
- }
14480
- ),
14481
- /* @__PURE__ */ jsxRuntimeExports.jsx(
14482
- "button",
14483
- {
14484
- className: "toggle-settings",
14485
- type: "button",
14486
- title: "Settings",
14487
- "aria-label": "Settings",
14488
- "aria-pressed": drawer === "settings",
14489
- onClick: onToggleSettings,
14490
- children: ""
14491
- }
14492
- )
14813
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "chrome-row chrome-screen", children: [
14814
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
14815
+ Select,
14816
+ {
14817
+ className: "preset-select",
14818
+ value: presetId,
14819
+ label: presetLabel,
14820
+ ariaLabel: "Target screen",
14821
+ onChange: setPreset,
14822
+ children: [
14823
+ /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Laptops", children: SCREEN_PRESETS.filter((p) => p.group === "laptop").map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id)) }),
14824
+ /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Desktops", children: SCREEN_PRESETS.filter((p) => p.group === "desktop").map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id)) }),
14825
+ /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Mobile", children: SCREEN_PRESETS.filter((p) => p.group === "mobile").map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id)) }),
14826
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: CUSTOM_PRESET_ID, children: "Custom" })
14827
+ ]
14828
+ }
14829
+ ),
14830
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14831
+ Segmented,
14832
+ {
14833
+ className: "view-control",
14834
+ ariaLabel: "Target view",
14835
+ value: viewMode,
14836
+ options: VIEWS.map((v) => ({ ...v, className: v.id === "fit" ? "view-fit" : "view-1x" })),
14837
+ onChange: setViewMode
14838
+ }
14839
+ ),
14840
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14841
+ Segmented,
14842
+ {
14843
+ className: "panes-control",
14844
+ ariaLabel: "Panes",
14845
+ value: panes,
14846
+ options: PANES.map((p) => ({ ...p, className: `panes-${p.id}` })),
14847
+ onChange: setPanes
14848
+ }
14849
+ ),
14850
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
14851
+ Select,
14852
+ {
14853
+ className: "profile-select",
14854
+ value: profileId,
14855
+ label: profileLabel,
14856
+ ariaLabel: "Panel profile",
14857
+ onChange: setProfile,
14858
+ children: PANEL_PROFILES.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id))
14859
+ }
14860
+ ),
14861
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "surround-control", role: "group", "aria-label": "Pane surround", children: SURROUNDS.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx(
14862
+ "button",
14863
+ {
14864
+ type: "button",
14865
+ title: s.label,
14866
+ "aria-label": s.label,
14867
+ "aria-pressed": surround === s.id,
14868
+ onClick: () => setSurround(s.id),
14869
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "surround-swatch", style: { background: s.swatch } })
14870
+ },
14871
+ s.id
14872
+ )) }),
14873
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "chrome-spacer" }),
14874
+ agentControl && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `agent-activity${agentActive ? " active" : ""}`, children: "AGENT" })
14875
+ ] })
14493
14876
  ] });
14494
14877
  }
14495
14878
  function App() {
@@ -14517,6 +14900,7 @@ function App() {
14517
14900
  const presetId = useStore((s) => s.presetId);
14518
14901
  const profileId = useStore((s) => s.profileId);
14519
14902
  const viewMode = useStore((s) => s.viewMode);
14903
+ const panes = useStore((s) => s.panes);
14520
14904
  reactExports.useEffect(() => {
14521
14905
  window.obsrv.getHostInfo().then(setHost, (e) => console.warn("obsrv: getHostInfo failed", e));
14522
14906
  window.obsrv.getSettings().then(setSettings, (e) => console.warn("obsrv: getSettings failed", e));
@@ -14544,6 +14928,9 @@ function App() {
14544
14928
  reactExports.useEffect(() => {
14545
14929
  window.obsrv.setMode(mode);
14546
14930
  }, [mode]);
14931
+ reactExports.useEffect(() => {
14932
+ window.obsrv.setNativeVisible(panes === "both");
14933
+ }, [panes]);
14547
14934
  reactExports.useEffect(() => {
14548
14935
  const el = targetPaneRef.current;
14549
14936
  if (!el) return;
@@ -14574,14 +14961,15 @@ function App() {
14574
14961
  };
14575
14962
  }, []);
14576
14963
  reactExports.useEffect(() => {
14577
- window.obsrv.reportUiState({ presetId, profileId, viewMode, mode, targetBounds, canvasBounds });
14578
- }, [presetId, profileId, viewMode, mode, targetBounds, canvasBounds]);
14964
+ window.obsrv.reportUiState({ presetId, profileId, viewMode, panes, mode, targetBounds, canvasBounds });
14965
+ }, [presetId, profileId, viewMode, panes, mode, targetBounds, canvasBounds]);
14579
14966
  reactExports.useEffect(() => {
14580
14967
  return window.obsrv.onAgentApply((patch) => {
14581
14968
  const s = useStore.getState();
14582
14969
  if (patch.presetId !== void 0) s.setPreset(patch.presetId);
14583
14970
  if (patch.profileId !== void 0) s.setProfile(patch.profileId);
14584
14971
  if (patch.viewMode !== void 0) s.setViewMode(patch.viewMode);
14972
+ if (patch.panes !== void 0) s.setPanes(patch.panes);
14585
14973
  if (patch.pixelExact !== void 0) s.setPixelExact(patch.pixelExact);
14586
14974
  if (patch.panTo !== void 0) s.requestAgentPan(patch.panTo);
14587
14975
  if (patch.highlight !== void 0) s.showAgentHighlight(patch.highlight);
@@ -14647,15 +15035,15 @@ function App() {
14647
15035
  /* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, { drawer, onTogglePanel: toggle("panel"), onToggleSettings: toggle("settings") }),
14648
15036
  /* @__PURE__ */ jsxRuntimeExports.jsx(DropZone, { onImage }),
14649
15037
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "body", children: [
14650
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "panes", children: [
14651
- mode === "image" && image ? /* @__PURE__ */ jsxRuntimeExports.jsx(
15038
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "panes", "data-panes": panes, children: [
15039
+ panes === "both" && (mode === "image" && image ? /* @__PURE__ */ jsxRuntimeExports.jsx(
14652
15040
  ImagePane,
14653
15041
  {
14654
15042
  src: image.objectUrl,
14655
15043
  width: image.natural.width,
14656
15044
  height: image.natural.height
14657
15045
  }
14658
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(NativeSlot, {}),
15046
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(NativeSlot, {})),
14659
15047
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pane target-pane", ref: targetPaneRef, children: [
14660
15048
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pane-body", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TargetCanvas, { onFatal: setFatal, imageFrame }) }),
14661
15049
  /* @__PURE__ */ jsxRuntimeExports.jsx(TargetFooter, {})