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