getobsrv 0.12.0 → 0.14.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.
@@ -12558,6 +12558,9 @@ function useShallow(selector) {
12558
12558
  return shallow(prev.current, next) ? prev.current : prev.current = next;
12559
12559
  };
12560
12560
  }
12561
+ function isBlankUrl(url) {
12562
+ return url === "";
12563
+ }
12561
12564
  function boxDownsample(src, factor) {
12562
12565
  if (!Number.isInteger(factor) || factor < 1) throw new RangeError("factor must be an integer >= 1");
12563
12566
  if (factor === 1) return { width: src.width, height: src.height, data: new Uint8ClampedArray(src.data) };
@@ -12702,6 +12705,7 @@ const DEFAULT_SETTINGS = {
12702
12705
  split: 0.5,
12703
12706
  maxTabs: 12
12704
12707
  };
12708
+ const DEFAULT_ORIENTATION = "portrait";
12705
12709
  const SCREEN_PRESETS = [
12706
12710
  // Laptops — ordered largest to smallest panel, then the denser 1080p outlier.
12707
12711
  { id: "laptop-768", label: '1366×768 15.6"', width: 1366, height: 768, diagonalInches: 15.6, deviceScaleFactor: 1, group: "laptop" },
@@ -12735,6 +12739,13 @@ function findProfile(id) {
12735
12739
  if (!p) throw new Error(`unknown profile: ${id}`);
12736
12740
  return p;
12737
12741
  }
12742
+ function applyOrientation(screen, orientation) {
12743
+ if (orientation !== "landscape") return screen;
12744
+ return { ...screen, width: screen.height, height: screen.width };
12745
+ }
12746
+ function screenShape(width, height) {
12747
+ return width > height ? "landscape" : "portrait";
12748
+ }
12738
12749
  function ppi(width, height, diagonalInches) {
12739
12750
  if (!(diagonalInches > 0)) throw new RangeError("diagonalInches must be > 0");
12740
12751
  return Math.hypot(width, height) / diagonalInches;
@@ -12799,6 +12810,7 @@ function blankTab() {
12799
12810
  title: "",
12800
12811
  lastUrl: "",
12801
12812
  presetId: "1080p-24",
12813
+ orientation: DEFAULT_ORIENTATION,
12802
12814
  custom: { width: 1920, height: 1080, diagonalInches: 24 },
12803
12815
  pixelExact: false,
12804
12816
  profileId: PANEL_PROFILES[0].id,
@@ -12860,6 +12872,11 @@ const useStore = create()((set, get) => ({
12860
12872
  // A screen change re-rasters the target, so a highlight's target-pixel rect
12861
12873
  // no longer marks what it marked; the same for the custom fields below.
12862
12874
  setPreset: (presetId) => set(patchActive({ presetId, agentHighlight: null })),
12875
+ // A rotation re-rasters the target exactly as a preset change does, so a
12876
+ // highlight's target-pixel rect no longer marks what it marked. Setting the
12877
+ // orientation already in force writes nothing, so a re-report from an agent
12878
+ // (or a click on the pressed half of the control) costs no re-render.
12879
+ setOrientation: (orientation) => set(patchActiveWith((t) => t.orientation === orientation ? null : { orientation, agentHighlight: null })),
12863
12880
  setCustom: (c) => set(patchActiveWith((t) => ({ custom: { ...t.custom, ...c }, presetId: CUSTOM_PRESET_ID, agentHighlight: null }))),
12864
12881
  setPixelExact: (pixelExact) => set(patchActive({ pixelExact })),
12865
12882
  // Picking a profile drops any hand-tuned slider values.
@@ -12914,7 +12931,8 @@ const useStore = create()((set, get) => ({
12914
12931
  url: info.url,
12915
12932
  title: info.title,
12916
12933
  presetId: info.presetId,
12917
- profileId: info.profileId
12934
+ profileId: info.profileId,
12935
+ orientation: info.orientation
12918
12936
  }
12919
12937
  );
12920
12938
  }
@@ -12952,7 +12970,7 @@ const useStore = create()((set, get) => ({
12952
12970
  function selectTab(s) {
12953
12971
  return s.tabs[s.activeId];
12954
12972
  }
12955
- function selectScreen(s) {
12973
+ function naturalScreen(s) {
12956
12974
  const tab = selectTab(s);
12957
12975
  const preset = SCREEN_PRESETS.find((p) => p.id === tab.presetId);
12958
12976
  return preset ? {
@@ -12962,6 +12980,21 @@ function selectScreen(s) {
12962
12980
  deviceScaleFactor: preset.deviceScaleFactor
12963
12981
  } : tab.custom;
12964
12982
  }
12983
+ function selectScreen(s) {
12984
+ return applyOrientation(naturalScreen(s), selectTab(s).orientation);
12985
+ }
12986
+ function selectScreenShape(s) {
12987
+ const screen = selectScreen(s);
12988
+ return screenShape(screen.width, screen.height);
12989
+ }
12990
+ const ORIENTATIONS = ["portrait", "landscape"];
12991
+ function selectOrientationShapes(s) {
12992
+ const natural = naturalScreen(s);
12993
+ return ORIENTATIONS.map((value) => {
12994
+ const r = applyOrientation(natural, value);
12995
+ return screenShape(r.width, r.height);
12996
+ });
12997
+ }
12965
12998
  function selectDeviceScaleFactor(s) {
12966
12999
  return selectScreen(s).deviceScaleFactor ?? 1;
12967
13000
  }
@@ -13183,7 +13216,8 @@ function TargetFooter() {
13183
13216
  const viewMode = useStore((s) => selectTab(s).viewMode);
13184
13217
  const fitScale2 = useStore((s) => selectTab(s).fitScale);
13185
13218
  const dsf = useStore(selectDeviceScaleFactor);
13186
- const size = mode === "image" && image ? `${image.width}×${image.height}` : `${viewport.width}×${viewport.height}${dsf > 1 ? ` @${dsf}x` : ""}`;
13219
+ const shape = useStore(selectScreenShape);
13220
+ const size = mode === "image" && image ? `${image.width}×${image.height}` : `${viewport.width}×${viewport.height}${dsf > 1 ? ` @${dsf}x` : ""} ${shape}`;
13187
13221
  const depth = params.levels <= 63 ? "6-bit" : "8-bit";
13188
13222
  const magnification = viewMode === "fit" && fitScale2 !== null ? [`fit ×${fitScale2.toFixed(2)}`, "not pixel-exact"] : [`×${scale.toFixed(2)}`];
13189
13223
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -13526,6 +13560,7 @@ function SettingsPanel() {
13526
13560
  const update = useStore((s) => s.update);
13527
13561
  const history = useStore((s) => s.history);
13528
13562
  const custom = useStore(useShallow((s) => selectTab(s).custom));
13563
+ const orientation = useStore((s) => selectTab(s).orientation);
13529
13564
  const viewport = useStore(useShallow(selectViewport));
13530
13565
  const scale = useStore(selectScale);
13531
13566
  const fallback = useStore(selectScaleIsFallback);
@@ -13598,6 +13633,15 @@ function SettingsPanel() {
13598
13633
  ] }),
13599
13634
  /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { children: "Custom screen" }),
13600
13635
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Editing these selects the Custom preset." }),
13636
+ orientation === "landscape" && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "muted custom-rotated", children: [
13637
+ "Rotated, so these render transposed:",
13638
+ " ",
13639
+ (() => {
13640
+ const r = applyOrientation({ ...custom }, orientation);
13641
+ return `${r.width}×${r.height}`;
13642
+ })(),
13643
+ "."
13644
+ ] }),
13601
13645
  /* @__PURE__ */ jsxRuntimeExports.jsx(
13602
13646
  NumberField,
13603
13647
  {
@@ -13729,6 +13773,93 @@ function SettingsPanel() {
13729
13773
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "muted", children: "Typed into the URL bar as suggestions and nowhere else. Turning this off stops recording and keeps what is stored; Clear erases it." })
13730
13774
  ] });
13731
13775
  }
13776
+ function EmptyArt() {
13777
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 244 192", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
13778
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M81.0982 148.486C110.284 172.12 153.104 167.619 176.738 138.433C200.372 109.247 195.872 66.4281 166.686 42.7938C137.5 19.1594 94.6804 23.6599 71.0461 52.8459C47.4117 82.0319 51.9122 124.851 81.0982 148.486Z", fill: "var(--chrome-1)" }),
13779
+ /* @__PURE__ */ jsxRuntimeExports.jsx("mask", { id: "mask0_2395_1391", style: { maskType: "luminance" }, maskUnits: "userSpaceOnUse", x: "55", y: "27", width: "137", height: "137", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M71.0461 52.8459C59.6964 66.8615 54.3794 84.8117 56.2645 102.748C58.1496 120.684 67.0826 137.136 81.0982 148.486C95.1138 159.835 113.064 165.152 131 163.267C148.936 161.382 165.388 152.449 176.738 138.433C188.088 124.418 193.405 106.468 191.519 88.5318C189.634 70.5958 180.701 54.1434 166.686 42.7938C152.67 31.4442 134.72 26.1271 116.784 28.0122C98.8481 29.8974 82.3957 38.8303 71.0461 52.8459Z", fill: "var(--chrome-2)" }) }),
13780
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { mask: "url(#mask0_2395_1391)", children: [
13781
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M127.812 2.31821L126.258 1.05957L29.9718 119.963L31.5261 121.222L127.812 2.31821Z", fill: "var(--chrome-2)" }),
13782
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M135.812 8.31723L134.258 7.05859L37.9718 125.962L39.5261 127.221L135.812 8.31723Z", fill: "var(--chrome-2)" }),
13783
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M141.812 14.3172L140.258 13.0586L43.9718 131.962L45.5261 133.221L141.812 14.3172Z", fill: "var(--chrome-2)" }),
13784
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M149.812 20.3172L148.258 19.0586L51.9718 137.962L53.5261 139.221L149.812 20.3172Z", fill: "var(--chrome-2)" }),
13785
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M156.812 26.3172L155.258 25.0586L58.9718 143.962L60.5261 145.221L156.812 26.3172Z", fill: "var(--chrome-2)" }),
13786
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M164.812 32.3172L163.258 31.0586L66.9718 149.962L68.5261 151.221L164.812 32.3172Z", fill: "var(--chrome-2)" }),
13787
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M171.812 38.3182L170.258 37.0596L73.9718 155.963L75.5261 157.222L171.812 38.3182Z", fill: "var(--chrome-2)" }),
13788
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M178.81 43.3182L177.256 42.0596L80.9698 160.963L82.5241 162.222L178.81 43.3182Z", fill: "var(--chrome-2)" }),
13789
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M186.812 50.3182L185.258 49.0596L88.9718 167.963L90.5261 169.222L186.812 50.3182Z", fill: "var(--chrome-2)" }),
13790
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M193.812 55.3172L192.258 54.0586L95.9718 172.962L97.5261 174.221L193.812 55.3172Z", fill: "var(--chrome-2)" }),
13791
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M200.812 62.3172L199.258 61.0586L102.972 179.962L104.526 181.221L200.812 62.3172Z", fill: "var(--chrome-2)" }),
13792
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M207.81 67.3172L206.256 66.0586L109.97 184.962L111.524 186.221L207.81 67.3172Z", fill: "var(--chrome-2)" }),
13793
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M215.812 74.3172L214.258 73.0586L117.972 191.962L119.526 193.221L215.812 74.3172Z", fill: "var(--chrome-2)" }),
13794
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M230.812 86.3172L229.258 85.0586L132.972 203.962L134.526 205.221L230.812 86.3172Z", fill: "var(--chrome-2)" })
13795
+ ] }),
13796
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M9.66123 96.7396V96.6596C9.89423 95.1426 8.94523 93.6936 7.46023 93.3026C5.97623 92.9116 4.43623 93.7046 3.89123 95.1396V95.2196C3.79523 95.5916 3.48323 95.8686 3.10223 95.9216C2.72023 95.9746 2.34523 95.7916 2.15123 95.4596C2.02223 95.2426 1.98623 94.9826 2.05123 94.7396C2.19523 94.1866 1.86423 93.6226 1.31123 93.4796C0.759233 93.3356 0.195233 93.6666 0.0512334 94.2196C-0.227767 95.7146 0.657233 97.1826 2.10923 97.6346C3.56223 98.0866 5.12323 97.3796 5.74123 95.9896C5.76623 95.9346 5.78623 95.8776 5.80123 95.8196C5.96923 95.3586 6.46023 95.1006 6.93523 95.2256C7.41023 95.3496 7.71223 95.8156 7.63123 96.2996C7.63123 96.2996 7.63123 96.3496 7.63123 96.3796C7.39323 97.6546 8.00123 98.9376 9.13923 99.5606C10.2762 100.184 11.6852 100.006 12.6312 99.1196C12.9552 98.7986 13.2012 98.4086 13.3512 97.9796C13.3752 97.9306 13.3952 97.8806 13.4112 97.8296C13.4702 97.4736 13.7252 97.1826 14.0702 97.0766C14.4142 96.9706 14.7892 97.0686 15.0372 97.3306C15.2852 97.5916 15.3642 97.9706 15.2412 98.3096C15.0982 98.8616 15.4292 99.4256 15.9812 99.5696C16.5342 99.7126 17.0982 99.3816 17.2412 98.8296C17.2412 98.7626 17.2412 98.6956 17.2412 98.6296C17.4402 97.1246 16.4832 95.7086 15.0122 95.3316C13.5422 94.9546 12.0222 95.7346 11.4712 97.1496V97.2296C11.4332 97.6036 11.1772 97.9196 10.8192 98.0356C10.4612 98.1516 10.0682 98.0456 9.81823 97.7646C9.56723 97.4846 9.50623 97.0816 9.66123 96.7396Z", fill: "var(--chrome-3)" }),
13797
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M101.891 23.6396H75.8906C74.2338 23.6396 72.8906 24.9828 72.8906 26.6396V46.6396C72.8906 48.2965 74.2338 49.6396 75.8906 49.6396H101.891C103.547 49.6396 104.891 48.2965 104.891 46.6396V26.6396C104.891 24.9828 103.547 23.6396 101.891 23.6396Z", fill: "var(--chrome-2)", stroke: "var(--text-1)", strokeLinecap: "round", strokeLinejoin: "round" }),
13798
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M89.3285 32.2227C86.7785 32.2227 84.7305 34.2157 84.7305 36.6527C84.7305 39.0907 89.3285 43.8297 89.3285 43.8297C89.3285 43.8297 93.9255 39.1097 93.9255 36.6527C93.9255 34.1967 91.8785 32.2227 89.3285 32.2227ZM89.3285 43.2497C88.9065 42.8047 88.1235 41.9347 87.3405 40.9087C85.8955 39.0327 85.1325 37.5627 85.1325 36.6337C85.1325 34.4087 87.0195 32.5907 89.3285 32.5907C91.6375 32.5907 93.5245 34.4287 93.5245 36.6527C93.5245 37.5627 92.7615 39.0517 91.3155 40.9287C90.5325 41.9347 89.7495 42.8047 89.3285 43.2497Z", stroke: "var(--text-1)", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }),
13799
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M90.4577 37.8657C90.1657 38.1517 89.7647 38.3307 89.3267 38.3307C88.8887 38.3307 88.4877 38.1517 88.1957 37.8657C87.9037 37.5787 87.7207 37.1847 87.7207 36.7547C87.7207 36.3247 87.9037 35.9307 88.1957 35.6447C88.4877 35.3577 88.8887 35.1787 89.3267 35.1787C89.7647 35.1787 90.1657 35.3577 90.4577 35.6447C90.7497 35.9307 90.9317 36.3247 90.9317 36.7547C90.9317 37.1847 90.7497 37.5787 90.4577 37.8657Z", fill: "var(--text-1)" }),
13800
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M77.6568 29.7268C78.3152 29.7268 78.8488 29.2209 78.8488 28.5968C78.8488 27.9727 78.3152 27.4668 77.6568 27.4668C76.9985 27.4668 76.4648 27.9727 76.4648 28.5968C76.4648 29.2209 76.9985 29.7268 77.6568 29.7268Z", fill: "var(--text-1)" }),
13801
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M81.233 29.7268C81.8913 29.7268 82.425 29.2209 82.425 28.5968C82.425 27.9727 81.8913 27.4668 81.233 27.4668C80.5747 27.4668 80.041 27.9727 80.041 28.5968C80.041 29.2209 80.5747 29.7268 81.233 29.7268Z", fill: "var(--text-1)" }),
13802
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M84.8072 29.7268C85.4656 29.7268 85.9992 29.2209 85.9992 28.5968C85.9992 27.9727 85.4656 27.4668 84.8072 27.4668C84.1489 27.4668 83.6152 27.9727 83.6152 28.5968C83.6152 29.2209 84.1489 29.7268 84.8072 29.7268Z", fill: "var(--text-1)" }),
13803
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M163.891 142.64H132.891C131.234 142.64 129.891 143.983 129.891 145.64V161.64C129.891 163.297 131.234 164.64 132.891 164.64H163.891C165.547 164.64 166.891 163.297 166.891 161.64V145.64C166.891 143.983 165.547 142.64 163.891 142.64Z", fill: "var(--chrome-2)", stroke: "var(--text-1)", strokeLinecap: "round", strokeLinejoin: "round" }),
13804
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M148.39 159.14C151.395 159.14 153.831 156.677 153.831 153.64C153.831 150.602 151.395 148.14 148.39 148.14C145.385 148.14 142.949 150.602 142.949 153.64C142.949 156.677 145.385 159.14 148.39 159.14Z", fill: "var(--chrome-2)", stroke: "var(--text-1)", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }),
13805
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M136.42 151.44C137.622 151.44 138.596 150.455 138.596 149.24C138.596 148.025 137.622 147.04 136.42 147.04C135.218 147.04 134.244 148.025 134.244 149.24C134.244 150.455 135.218 151.44 136.42 151.44Z", fill: "var(--text-1)" }),
13806
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M160.362 160.24C161.563 160.24 162.538 159.255 162.538 158.04C162.538 156.825 161.563 155.84 160.362 155.84C159.16 155.84 158.186 156.825 158.186 158.04C158.186 159.255 159.16 160.24 160.362 160.24Z", fill: "var(--text-1)" }),
13807
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M46.4371 145.451L42.7031 141.847L39.0985 145.581L42.8325 149.185L46.4371 145.451Z", fill: "var(--chrome-3)" }),
13808
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M205.179 142.11L197.867 144.993L205.617 149.955L205.179 142.11Z", fill: "var(--chrome-3)" }),
13809
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M35.5066 50.1406L33.8906 47.4466L35.4156 44.6926L38.5576 44.6396L40.1806 47.3326L38.6556 50.0877L35.5066 50.1406Z", fill: "var(--chrome-3)" }),
13810
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M233.886 98.9806L233.867 98.9133C232.816 97.8197 232.746 96.1148 233.704 94.9392C234.663 93.7645 236.348 93.4884 237.631 94.2971L237.695 94.341C237.995 94.5807 238.409 94.6148 238.745 94.43C239.081 94.2452 239.273 93.8761 239.231 93.4948C239.206 93.244 239.08 93.013 238.882 92.8564C238.442 92.506 238.37 91.8652 238.721 91.4253C239.072 90.9857 239.713 90.9142 240.152 91.2646C241.265 92.301 241.427 94.0066 240.528 95.2338C239.629 96.46 237.955 96.8206 236.631 96.0714L236.477 95.9699C236.211 95.758 235.853 95.7035 235.537 95.8274C235.221 95.9513 234.994 96.2338 234.944 96.5697C234.893 96.9059 235.024 97.2426 235.29 97.4546L235.309 97.5218C236.264 98.405 236.541 99.8028 235.995 100.983C235.449 102.163 234.206 102.858 232.915 102.703C232.461 102.638 232.028 102.47 231.651 102.212L231.512 102.127C231.246 101.915 230.888 101.861 230.572 101.985C230.256 102.108 230.029 102.391 229.979 102.727C229.927 103.063 230.059 103.4 230.325 103.612C230.764 103.963 230.836 104.603 230.486 105.043C230.134 105.483 229.493 105.555 229.055 105.204L228.902 105.07C227.851 103.977 227.781 102.272 228.739 101.096C229.698 99.9217 231.383 99.6455 232.665 100.454L232.73 100.498C233 100.705 233.361 100.751 233.676 100.62C233.99 100.488 234.211 100.199 234.254 99.8613C234.298 99.5232 234.157 99.1871 233.886 98.9806Z", fill: "var(--chrome-3)" }),
13811
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M202.603 46.9244C204.049 46.8989 205.202 45.7054 205.176 44.2587C205.15 42.8119 203.957 41.6598 202.51 41.6853C201.063 41.7108 199.911 42.9043 199.937 44.351C199.962 45.7978 201.156 46.95 202.603 46.9244Z", fill: "var(--chrome-3)" }),
13812
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M200.891 80.6396H42.8906C40.6815 80.6396 38.8906 82.4305 38.8906 84.6396V109.64C38.8906 111.849 40.6815 113.64 42.8906 113.64H200.891C203.1 113.64 204.891 111.849 204.891 109.64V84.6396C204.891 82.4305 203.1 80.6396 200.891 80.6396Z", fill: "url(#paint0_linear_2395_1391)", stroke: "var(--text-0)", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
13813
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M67.8537 95.3245V99.7393H65.7358V89.5574H67.794V93.4502H67.8835C68.0559 92.9994 68.3343 92.6465 68.7188 92.3912C69.1032 92.1327 69.5855 92.0035 70.1655 92.0035C70.6958 92.0035 71.1581 92.1195 71.5526 92.3515C71.9503 92.5802 72.2585 92.9099 72.4773 93.3408C72.6993 93.7684 72.8087 94.2805 72.8054 94.877V99.7393H70.6875V95.2549C70.6908 94.7842 70.5715 94.418 70.3295 94.1562C70.0909 93.8943 69.7562 93.7634 69.3253 93.7634C69.0369 93.7634 68.7817 93.8247 68.5597 93.9474C68.3409 94.07 68.1686 94.249 68.0426 94.4843C67.92 94.7163 67.857 94.9964 67.8537 95.3245ZM78.5339 92.1029V93.6938H73.9352V92.1029H78.5339ZM74.9792 90.2733H77.0971V97.3927C77.0971 97.5882 77.127 97.7407 77.1866 97.8501C77.2463 97.9561 77.3291 98.0307 77.4352 98.0738C77.5446 98.1169 77.6705 98.1384 77.813 98.1384C77.9125 98.1384 78.0119 98.1301 78.1113 98.1135C78.2108 98.0937 78.287 98.0787 78.34 98.0688L78.6731 99.6448C78.5671 99.6779 78.4179 99.7161 78.2257 99.7591C78.0334 99.8055 77.7998 99.8337 77.5247 99.8437C77.0143 99.8635 76.5668 99.7956 76.1824 99.6398C75.8012 99.484 75.5046 99.2421 75.2924 98.914C75.0803 98.5858 74.9759 98.1715 74.9792 97.6711V90.2733ZM83.9753 92.1029V93.6938H79.3766V92.1029H83.9753ZM80.4206 90.2733H82.5385V97.3927C82.5385 97.5882 82.5684 97.7407 82.628 97.8501C82.6877 97.9561 82.7705 98.0307 82.8766 98.0738C82.986 98.1169 83.1119 98.1384 83.2544 98.1384C83.3539 98.1384 83.4533 98.1301 83.5527 98.1135C83.6522 98.0937 83.7284 98.0787 83.7814 98.0688L84.1145 99.6448C84.0085 99.6779 83.8593 99.7161 83.6671 99.7591C83.4748 99.8055 83.2412 99.8337 82.9661 99.8437C82.4557 99.8635 82.0082 99.7956 81.6238 99.6398C81.2426 99.484 80.946 99.2421 80.7338 98.914C80.5217 98.5858 80.4173 98.1715 80.4206 97.6711V90.2733ZM85.3549 102.603V92.1029H87.443V93.3856H87.5375C87.6303 93.1801 87.7645 92.9713 87.9402 92.7591C88.1191 92.5437 88.3511 92.3647 88.6362 92.2222C88.9245 92.0764 89.2825 92.0035 89.71 92.0035C90.2669 92.0035 90.7806 92.1493 91.2512 92.441C91.7219 92.7293 92.0981 93.1652 92.3798 93.7485C92.6615 94.3285 92.8024 95.056 92.8024 95.931C92.8024 96.7828 92.6648 97.502 92.3897 98.0887C92.118 98.672 91.7467 99.1145 91.2761 99.4161C90.8088 99.7144 90.2851 99.8635 89.7051 99.8635C89.2941 99.8635 88.9444 99.7956 88.6561 99.6597C88.371 99.5238 88.1374 99.3531 87.9551 99.1476C87.7728 98.9388 87.6336 98.7284 87.5375 98.5162H87.4728V102.603H85.3549ZM87.4281 95.9211C87.4281 96.3751 87.4911 96.7712 87.617 97.1093C87.743 97.4474 87.9252 97.7108 88.1639 97.8998C88.4025 98.0854 88.6925 98.1782 89.0339 98.1782C89.3786 98.1782 89.6703 98.0837 89.9089 97.8948C90.1475 97.7026 90.3282 97.4374 90.4508 97.0993C90.5768 96.758 90.6397 96.3652 90.6397 95.9211C90.6397 95.4803 90.5784 95.0925 90.4558 94.7577C90.3332 94.423 90.1525 94.1611 89.9139 93.9722C89.6752 93.7833 89.3819 93.6888 89.0339 93.6888C88.6892 93.6888 88.3975 93.78 88.1589 93.9623C87.9236 94.1446 87.743 94.4031 87.617 94.7378C87.4911 95.0726 87.4281 95.467 87.4281 95.9211ZM95.4522 99.8685C95.1241 99.8685 94.8424 99.7525 94.6071 99.5205C94.3751 99.2852 94.2591 99.0035 94.2591 98.6753C94.2591 98.3505 94.3751 98.0721 94.6071 97.8401C94.8424 97.6081 95.1241 97.4921 95.4522 97.4921C95.7704 97.4921 96.0488 97.6081 96.2875 97.8401C96.5261 98.0721 96.6454 98.3505 96.6454 98.6753C96.6454 98.8941 96.5891 99.0946 96.4764 99.2769C96.367 99.4559 96.2228 99.6001 96.0439 99.7094C95.8649 99.8155 95.6677 99.8685 95.4522 99.8685ZM95.4522 94.5191C95.1241 94.5191 94.8424 94.4031 94.6071 94.1711C94.3751 93.9391 94.2591 93.6573 94.2591 93.3259C94.2591 93.0011 94.3751 92.7243 94.6071 92.4956C94.8424 92.2636 95.1241 92.1476 95.4522 92.1476C95.7704 92.1476 96.0488 92.2636 96.2875 92.4956C96.5261 92.7243 96.6454 93.0011 96.6454 93.3259C96.6454 93.548 96.5891 93.7501 96.4764 93.9324C96.367 94.1114 96.2228 94.2539 96.0439 94.36C95.8649 94.4661 95.6677 94.5191 95.4522 94.5191ZM102.819 89.0802L99.5376 101.271H97.7131L100.994 89.0802H102.819ZM108.274 89.0802L104.993 101.271H103.168L106.449 89.0802H108.274ZM110.801 99.7393L108.723 92.1029H110.865L112.049 97.2336H112.118L113.351 92.1029H115.454L116.707 97.2037H116.772L117.935 92.1029H120.073L118 99.7393H115.757L114.445 94.9367H114.35L113.038 99.7393H110.801ZM122.709 99.7393L120.631 92.1029H122.774L123.957 97.2336H124.026L125.259 92.1029H127.362L128.615 97.2037H128.68L129.843 92.1029H131.981L129.908 99.7393H127.666L126.353 94.9367H126.259L124.946 99.7393H122.709ZM134.617 99.7393L132.539 92.1029H134.682L135.865 97.2336H135.935L137.168 92.1029H139.271L140.523 97.2037H140.588L141.751 92.1029H143.889L141.816 99.7393H139.574L138.261 94.9367H138.167L136.854 99.7393H134.617Z", fill: "var(--text-1)" }),
13814
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M53.8906 101.64C56.0998 101.64 57.8906 99.8488 57.8906 97.6396C57.8906 95.4305 56.0998 93.6396 53.8906 93.6396C51.6815 93.6396 49.8906 95.4305 49.8906 97.6396C49.8906 99.8488 51.6815 101.64 53.8906 101.64Z", fill: "var(--text-1)" }),
13815
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M151.891 118.08V95.6396L168.891 110.6H160.731L165.491 122.16L162.771 123.52L158.011 112.64L151.891 118.08Z", fill: "var(--text-1)", stroke: "var(--text-0)", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
13816
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M157.891 111.64L152.891 97.6396V115.64L157.891 111.64Z", fill: "var(--chrome-3)" }),
13817
+ /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "paint0_linear_2395_1391", x1: "72.7546", y1: "103.872", x2: "171.027", y2: "103.872", gradientUnits: "userSpaceOnUse", children: [
13818
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { stopColor: "var(--chrome-2)" }),
13819
+ /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "1", stopColor: "var(--chrome-2)" })
13820
+ ] }) })
13821
+ ] });
13822
+ }
13823
+ function EmptyState() {
13824
+ const setUrl = useStore((s) => s.setUrl);
13825
+ const [draft, setDraft] = reactExports.useState("");
13826
+ const [busy, setBusy] = reactExports.useState(false);
13827
+ const [error, setError] = reactExports.useState(null);
13828
+ const submit = async (e) => {
13829
+ e.preventDefault();
13830
+ const url = draft.trim();
13831
+ if (url === "" || busy) return;
13832
+ setBusy(true);
13833
+ setError(null);
13834
+ try {
13835
+ setUrl(await window.obsrv.navigate(url));
13836
+ } catch {
13837
+ setError("That address could not be loaded.");
13838
+ } finally {
13839
+ setBusy(false);
13840
+ }
13841
+ };
13842
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "empty-state", children: [
13843
+ /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyArt, {}),
13844
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "empty-lede", children: "Point Obsrv at a page to see it the way a 1x screen does." }),
13845
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { className: "empty-form", onSubmit: submit, children: [
13846
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
13847
+ "input",
13848
+ {
13849
+ className: "empty-input",
13850
+ value: draft,
13851
+ onChange: (e) => setDraft(e.target.value),
13852
+ placeholder: "localhost:3000",
13853
+ "aria-label": "URL to open",
13854
+ spellCheck: false,
13855
+ autoFocus: true
13856
+ }
13857
+ ),
13858
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "submit", className: "empty-go", disabled: draft.trim() === "" || busy, children: "Open" })
13859
+ ] }),
13860
+ error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "empty-error", role: "alert", children: error })
13861
+ ] });
13862
+ }
13732
13863
  const VERT_SRC = `#version 300 es
13733
13864
  void main() {
13734
13865
  vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
@@ -14698,53 +14829,73 @@ const createLucideIcon = (iconName, iconNode) => {
14698
14829
  * This source code is licensed under the ISC license.
14699
14830
  * See the LICENSE file in the root directory of this source tree.
14700
14831
  */
14701
- const __iconNode$8 = [
14832
+ const __iconNode$a = [
14702
14833
  ["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
14703
14834
  ["path", { d: "M19 12H5", key: "x3x0zl" }]
14704
14835
  ];
14705
- const ArrowLeft = createLucideIcon("arrow-left", __iconNode$8);
14836
+ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$a);
14706
14837
  /**
14707
14838
  * @license lucide-react v1.34.0 - ISC
14708
14839
  *
14709
14840
  * This source code is licensed under the ISC license.
14710
14841
  * See the LICENSE file in the root directory of this source tree.
14711
14842
  */
14712
- const __iconNode$7 = [
14843
+ const __iconNode$9 = [
14713
14844
  ["path", { d: "M5 12h14", key: "1ays0h" }],
14714
14845
  ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
14715
14846
  ];
14716
- const ArrowRight = createLucideIcon("arrow-right", __iconNode$7);
14847
+ const ArrowRight = createLucideIcon("arrow-right", __iconNode$9);
14717
14848
  /**
14718
14849
  * @license lucide-react v1.34.0 - ISC
14719
14850
  *
14720
14851
  * This source code is licensed under the ISC license.
14721
14852
  * See the LICENSE file in the root directory of this source tree.
14722
14853
  */
14723
- const __iconNode$6 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
14724
- const ChevronDown = createLucideIcon("chevron-down", __iconNode$6);
14854
+ const __iconNode$8 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
14855
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$8);
14725
14856
  /**
14726
14857
  * @license lucide-react v1.34.0 - ISC
14727
14858
  *
14728
14859
  * This source code is licensed under the ISC license.
14729
14860
  * See the LICENSE file in the root directory of this source tree.
14730
14861
  */
14731
- const __iconNode$5 = [
14862
+ const __iconNode$7 = [
14732
14863
  ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }],
14733
14864
  ["circle", { cx: "12", cy: "5", r: "1", key: "gxeob9" }],
14734
14865
  ["circle", { cx: "12", cy: "19", r: "1", key: "lyex9k" }]
14735
14866
  ];
14736
- const EllipsisVertical = createLucideIcon("ellipsis-vertical", __iconNode$5);
14867
+ const EllipsisVertical = createLucideIcon("ellipsis-vertical", __iconNode$7);
14737
14868
  /**
14738
14869
  * @license lucide-react v1.34.0 - ISC
14739
14870
  *
14740
14871
  * This source code is licensed under the ISC license.
14741
14872
  * See the LICENSE file in the root directory of this source tree.
14742
14873
  */
14743
- const __iconNode$4 = [
14874
+ const __iconNode$6 = [
14744
14875
  ["path", { d: "M5 12h14", key: "1ays0h" }],
14745
14876
  ["path", { d: "M12 5v14", key: "s699le" }]
14746
14877
  ];
14747
- const Plus = createLucideIcon("plus", __iconNode$4);
14878
+ const Plus = createLucideIcon("plus", __iconNode$6);
14879
+ /**
14880
+ * @license lucide-react v1.34.0 - ISC
14881
+ *
14882
+ * This source code is licensed under the ISC license.
14883
+ * See the LICENSE file in the root directory of this source tree.
14884
+ */
14885
+ const __iconNode$5 = [
14886
+ ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
14887
+ ];
14888
+ const RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode$5);
14889
+ /**
14890
+ * @license lucide-react v1.34.0 - ISC
14891
+ *
14892
+ * This source code is licensed under the ISC license.
14893
+ * See the LICENSE file in the root directory of this source tree.
14894
+ */
14895
+ const __iconNode$4 = [
14896
+ ["rect", { width: "12", height: "20", x: "6", y: "2", rx: "2", key: "1oxtiu" }]
14897
+ ];
14898
+ const RectangleVertical = createLucideIcon("rectangle-vertical", __iconNode$4);
14748
14899
  /**
14749
14900
  * @license lucide-react v1.34.0 - ISC
14750
14901
  *
@@ -14811,7 +14962,12 @@ const ICONS = {
14811
14962
  sliders: SlidersHorizontal,
14812
14963
  gear: Settings,
14813
14964
  chevron: ChevronDown,
14814
- plus: Plus
14965
+ plus: Plus,
14966
+ // The rotate control's two shapes. A plain outline of the screen you get is
14967
+ // the whole affordance — no arrow, no device silhouette: the target may be a
14968
+ // monitor as readily as a phone.
14969
+ portrait: RectangleVertical,
14970
+ landscape: RectangleHorizontal
14815
14971
  };
14816
14972
  function Icon({ name, size = 16 }) {
14817
14973
  const Glyph = ICONS[name];
@@ -14994,6 +15150,9 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
14994
15150
  const setUrl = useStore((s) => s.setUrl);
14995
15151
  const setMode = useStore((s) => s.setMode);
14996
15152
  const setPreset = useStore((s) => s.setPreset);
15153
+ const orientation = useStore((s) => selectTab(s).orientation);
15154
+ const orientationShapes = useStore(useShallow(selectOrientationShapes));
15155
+ const setOrientation = useStore((s) => s.setOrientation);
14997
15156
  const setProfile = useStore((s) => s.setProfile);
14998
15157
  const setPixelExact = useStore((s) => s.setPixelExact);
14999
15158
  const setError = useStore((s) => s.setError);
@@ -15266,6 +15425,23 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
15266
15425
  ]
15267
15426
  }
15268
15427
  ),
15428
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "orientation-control", role: "group", "aria-label": "Screen orientation", children: ORIENTATIONS.map((value, i) => {
15429
+ const shape = orientationShapes[i] ?? value;
15430
+ const name = shape === "landscape" ? "Landscape" : "Portrait";
15431
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
15432
+ "button",
15433
+ {
15434
+ type: "button",
15435
+ className: `orient-${shape}`,
15436
+ title: `${name} — ${value === orientation ? "showing" : "rotate the screen"}`,
15437
+ "aria-label": name,
15438
+ "aria-pressed": orientation === value,
15439
+ onClick: () => setOrientation(value),
15440
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { name: shape })
15441
+ },
15442
+ value
15443
+ );
15444
+ }) }),
15269
15445
  /* @__PURE__ */ jsxRuntimeExports.jsx(
15270
15446
  Segmented,
15271
15447
  {
@@ -15309,7 +15485,6 @@ function Toolbar({ drawer, onTogglePanel, onToggleSettings }) {
15309
15485
  },
15310
15486
  s.id
15311
15487
  )) }),
15312
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "chrome-spacer" }),
15313
15488
  agentControl && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `agent-activity${agentActive ? " active" : ""}`, children: "AGENT" })
15314
15489
  ] })
15315
15490
  ] });
@@ -15342,7 +15517,9 @@ function App() {
15342
15517
  const deviceScaleFactor = useStore(selectDeviceScaleFactor);
15343
15518
  const presetId = useStore((s) => selectTab(s).presetId);
15344
15519
  const profileId = useStore((s) => selectTab(s).profileId);
15520
+ const orientation = useStore((s) => selectTab(s).orientation);
15345
15521
  const viewMode = useStore((s) => selectTab(s).viewMode);
15522
+ const tabUrl = useStore((s) => selectTab(s).url);
15346
15523
  const activeId = useStore((s) => s.activeId);
15347
15524
  const tabOrder = useStore((s) => s.tabOrder);
15348
15525
  const panes = useStore((s) => s.panes);
@@ -15422,13 +15599,14 @@ function App() {
15422
15599
  }, []);
15423
15600
  reactExports.useEffect(() => {
15424
15601
  if (!tabsKnown) return;
15425
- window.obsrv.reportUiState({ tabId: activeId, presetId, profileId, viewMode, panes, mode, targetBounds, canvasBounds });
15426
- }, [tabsKnown, activeId, presetId, profileId, viewMode, panes, mode, targetBounds, canvasBounds]);
15602
+ window.obsrv.reportUiState({ tabId: activeId, presetId, profileId, orientation, viewMode, panes, mode, targetBounds, canvasBounds });
15603
+ }, [tabsKnown, activeId, presetId, profileId, orientation, viewMode, panes, mode, targetBounds, canvasBounds]);
15427
15604
  reactExports.useEffect(() => {
15428
15605
  return window.obsrv.onAgentApply((patch) => {
15429
15606
  const s = useStore.getState();
15430
15607
  if (patch.presetId !== void 0) s.setPreset(patch.presetId);
15431
15608
  if (patch.profileId !== void 0) s.setProfile(patch.profileId);
15609
+ if (patch.orientation !== void 0) s.setOrientation(patch.orientation);
15432
15610
  if (patch.viewMode !== void 0) s.setViewMode(patch.viewMode);
15433
15611
  if (patch.panes !== void 0) s.setPanes(patch.panes);
15434
15612
  if (patch.pixelExact !== void 0) s.setPixelExact(patch.pixelExact);
@@ -15440,6 +15618,7 @@ function App() {
15440
15618
  document.documentElement.dataset.surround = surround;
15441
15619
  }, [surround]);
15442
15620
  const image = images[activeId] ?? null;
15621
+ const blank = mode === "image" ? image === null : isBlankUrl(tabUrl);
15443
15622
  const onImage = async (file, exportScale) => {
15444
15623
  const token = ++dropToken.current;
15445
15624
  const tabId = activeId;
@@ -15542,7 +15721,8 @@ function App() {
15542
15721
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pane target-pane", ref: targetPaneRef, children: [
15543
15722
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pane-body", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TargetCanvas, { onFatal: setFatal, imageFrame }) }),
15544
15723
  /* @__PURE__ */ jsxRuntimeExports.jsx(TargetFooter, {})
15545
- ] })
15724
+ ] }),
15725
+ blank && /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, {})
15546
15726
  ] }),
15547
15727
  drawer === "panel" && /* @__PURE__ */ jsxRuntimeExports.jsx("aside", { className: "drawer", children: /* @__PURE__ */ jsxRuntimeExports.jsx(PanelControls, {}) }),
15548
15728
  drawer === "settings" && /* @__PURE__ */ jsxRuntimeExports.jsx("aside", { className: "drawer", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsPanel, {}) })
@@ -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-gYDbIgqJ.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-DvwDBeho.css">
7
+ <script type="module" crossorigin src="./assets/index-WM0cwf7d.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-BpvyvKfD.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -1,10 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyOrientation = applyOrientation;
4
+ exports.screenShape = screenShape;
3
5
  exports.ppi = ppi;
4
6
  exports.computeScale = computeScale;
5
7
  exports.maxCssViewport = maxCssViewport;
6
8
  exports.clampViewport = clampViewport;
7
9
  const presets_1 = require("./presets");
10
+ /**
11
+ * The screen as it is actually being held. The **one** place the axes are
12
+ * swapped: everything downstream — the viewport handed to `TargetSource`, the
13
+ * clamp, the magnification, the footer — reads the rotated screen and needs no
14
+ * orientation of its own.
15
+ *
16
+ * Rotation must not change the *magnitude* of the physical scale, and does not:
17
+ * `ppi` is `hypot(w, h) / diagonalInches` and `hypot` is symmetric, so the
18
+ * diagonal, the pixel count and the density are all orientation-independent.
19
+ * A phone does not get physically larger by being turned sideways. The unit
20
+ * test asserts it, because it is exactly the kind of thing that drifts.
21
+ *
22
+ * `'portrait'` is the preset as the table stores it; `'landscape'` is that
23
+ * rotated a quarter turn. Every mobile preset — the case this feature exists
24
+ * for — is stored portrait, so for those the names are literal. A monitor
25
+ * preset is stored landscape-natural, so there the pair reads as
26
+ * unrotated/rotated instead; the UI never repeats the flag back at the user,
27
+ * it names the shape the dimensions actually have (`screenShape`), so nothing
28
+ * on screen can contradict the pixels beside it.
29
+ */
30
+ function applyOrientation(screen, orientation) {
31
+ if (orientation !== 'landscape')
32
+ return screen;
33
+ return { ...screen, width: screen.height, height: screen.width };
34
+ }
35
+ /**
36
+ * The shape a pair of dimensions actually has, for anything the user reads. A
37
+ * square screen counts as portrait — it has no landscape reading, and one of
38
+ * the two words has to win.
39
+ */
40
+ function screenShape(width, height) {
41
+ return width > height ? 'landscape' : 'portrait';
42
+ }
8
43
  function ppi(width, height, diagonalInches) {
9
44
  if (!(diagonalInches > 0))
10
45
  throw new RangeError('diagonalInches must be > 0');
@@ -9,6 +9,7 @@ exports.defaultControlFilePath = defaultControlFilePath;
9
9
  exports.presetApplyError = presetApplyError;
10
10
  exports.profileApplyError = profileApplyError;
11
11
  exports.viewModeApplyError = viewModeApplyError;
12
+ exports.orientationApplyError = orientationApplyError;
12
13
  exports.panesApplyError = panesApplyError;
13
14
  exports.pixelExactApplyError = pixelExactApplyError;
14
15
  exports.parseClick = parseClick;
@@ -17,6 +18,7 @@ exports.parseControlStatus = parseControlStatus;
17
18
  const node_crypto_1 = require("node:crypto");
18
19
  const node_path_1 = require("node:path");
19
20
  const ipcPayloads_1 = require("./ipcPayloads");
21
+ const calibration_1 = require("./calibration");
20
22
  const presets_1 = require("./presets");
21
23
  /**
22
24
  * The agent-control protocol shared by the main-process control server
@@ -50,6 +52,7 @@ exports.CONTROL_COMMANDS = [
50
52
  'setProfile',
51
53
  'setViewMode',
52
54
  'setPanes',
55
+ 'setOrientation',
53
56
  'captureVisible',
54
57
  // v0.5 drive controls (spec §14 "Drive controls").
55
58
  'scroll',
@@ -153,6 +156,13 @@ function profileApplyError(id) {
153
156
  function viewModeApplyError(v) {
154
157
  return v === '1:1' || v === 'fit' ? null : `setViewMode payload must be { mode: '1:1' | 'fit' }`;
155
158
  }
159
+ /**
160
+ * Validates a `setOrientation` payload. Unlike a preset id there is no table to
161
+ * miss, so the message just names the two words rather than listing anything.
162
+ */
163
+ function orientationApplyError(v) {
164
+ return (0, presets_1.isOrientation)(v) ? null : `setOrientation payload must be { orientation: 'portrait' | 'landscape' }`;
165
+ }
156
166
  function panesApplyError(v) {
157
167
  return v === 'both' || v === 'target' ? null : `setPanes payload must be { panes: 'both' | 'target' }`;
158
168
  }
@@ -214,6 +224,19 @@ function parseHighlight(raw) {
214
224
  durationMs: Math.min(Math.max(Math.round(d), exports.HIGHLIGHT_DURATION_MIN_MS), exports.HIGHLIGHT_DURATION_MAX_MS),
215
225
  };
216
226
  }
227
+ /**
228
+ * The shape to report when the app did not name one. Dimensions settle it
229
+ * outright when they are there. Without them the app predates rotation, which
230
+ * means it is showing the preset unrotated — so the preset's own natural shape
231
+ * is exact rather than a guess. Only a custom screen on such an app falls all
232
+ * the way through to the flag.
233
+ */
234
+ function inferScreenShape(cssWidth, cssHeight, presetId, orientation) {
235
+ if (cssWidth > 0 && cssHeight > 0)
236
+ return (0, calibration_1.screenShape)(cssWidth, cssHeight);
237
+ const preset = presets_1.SCREEN_PRESETS.find(p => p.id === presetId);
238
+ return preset ? (0, calibration_1.screenShape)(preset.width, preset.height) : orientation;
239
+ }
217
240
  /** Validates a control server `status` response on the client side. */
218
241
  function parseControlStatus(raw) {
219
242
  if (!isRecord(raw))
@@ -245,5 +268,40 @@ function parseControlStatus(raw) {
245
268
  const tabIndex = raw.tabIndex ?? 0;
246
269
  if (typeof tabIndex !== 'number' || !Number.isInteger(tabIndex) || tabIndex < 0)
247
270
  return null;
248
- return { version, url, presetId, profileId, viewMode, panes, mode, tabId, tabIndex };
271
+ // And the same skew again, one release later still. The npm MCP server
272
+ // routinely talks to a DMG app older than itself, and an app that predates
273
+ // rotation shows every screen unrotated — which is exactly what the default
274
+ // says. Returning null instead would take out drive and live snap wholesale
275
+ // against every app older than this feature, which is the bug this repo has
276
+ // already been bitten by twice.
277
+ const orientation = raw.orientation ?? presets_1.DEFAULT_ORIENTATION;
278
+ if (!(0, presets_1.isOrientation)(orientation))
279
+ return null;
280
+ // And once more for the dimensions and the derived shape. `0` means "the app
281
+ // did not say", which is the truthful answer for one that predates them —
282
+ // inventing a size would be worse than admitting the gap.
283
+ const cssWidth = raw.cssWidth ?? 0;
284
+ if (typeof cssWidth !== 'number' || !Number.isFinite(cssWidth) || cssWidth < 0)
285
+ return null;
286
+ const cssHeight = raw.cssHeight ?? 0;
287
+ if (typeof cssHeight !== 'number' || !Number.isFinite(cssHeight) || cssHeight < 0)
288
+ return null;
289
+ const reported = raw.screenShape;
290
+ if (reported !== undefined && !(0, presets_1.isOrientation)(reported))
291
+ return null;
292
+ return {
293
+ version,
294
+ url,
295
+ presetId,
296
+ profileId,
297
+ viewMode,
298
+ panes,
299
+ orientation,
300
+ mode,
301
+ tabId,
302
+ tabIndex,
303
+ cssWidth,
304
+ cssHeight,
305
+ screenShape: reported ?? inferScreenShape(cssWidth, cssHeight, presetId, orientation),
306
+ };
249
307
  }
@@ -5,6 +5,7 @@ exports.parseRect = parseRect;
5
5
  exports.parseInputEvent = parseInputEvent;
6
6
  exports.parseDeviceScaleFactor = parseDeviceScaleFactor;
7
7
  exports.parseSettings = parseSettings;
8
+ exports.parseOrientation = parseOrientation;
8
9
  exports.parseMode = parseMode;
9
10
  exports.parseTabId = parseTabId;
10
11
  exports.parseUiState = parseUiState;
@@ -142,6 +143,15 @@ function parseSettings(raw) {
142
143
  return null;
143
144
  return { hostDiagonalInches, hostNits, agentControl, updateCheck, lastUpdateCheck, recordHistory, split, maxTabs };
144
145
  }
146
+ /**
147
+ * An orientation off the wire. Refused rather than defaulted, unlike the
148
+ * absent-field handling inside `parseUiState`: a caller that names an
149
+ * orientation and gets a different one back is worse served than one told its
150
+ * value was not a word this app knows.
151
+ */
152
+ function parseOrientation(raw) {
153
+ return (0, presets_1.isOrientation)(raw) ? raw : null;
154
+ }
145
155
  function parseMode(raw) {
146
156
  return raw === 'url' || raw === 'image' ? raw : null;
147
157
  }
@@ -197,12 +207,17 @@ function parseUiState(raw) {
197
207
  const panes = raw.panes ?? 'both';
198
208
  if (panes !== 'both' && panes !== 'target')
199
209
  return null;
210
+ // Same shape again for the orientation, and for the same reason.
211
+ const orientation = raw.orientation ?? presets_1.DEFAULT_ORIENTATION;
212
+ if (!(0, presets_1.isOrientation)(orientation))
213
+ return null;
200
214
  return {
201
215
  tabId,
202
216
  presetId,
203
217
  profileId,
204
218
  viewMode,
205
219
  panes,
220
+ orientation,
206
221
  mode,
207
222
  targetBounds: parseRect(raw.targetBounds),
208
223
  canvasBounds: parseRect(raw.canvasBounds),
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PANEL_PROFILES = exports.SCREEN_PRESETS = exports.DEFAULT_SETTINGS = exports.MAX_TABS_MAX = exports.MAX_TABS_MIN = exports.SPLIT_MAX = exports.SPLIT_MIN = exports.MAX_VIEWPORT = void 0;
3
+ exports.PANEL_PROFILES = exports.SCREEN_PRESETS = exports.DEFAULT_ORIENTATION = exports.DEFAULT_SETTINGS = exports.MAX_TABS_MAX = exports.MAX_TABS_MIN = exports.SPLIT_MAX = exports.SPLIT_MIN = exports.MAX_VIEWPORT = void 0;
4
+ exports.isOrientation = isOrientation;
4
5
  exports.findPreset = findPreset;
5
6
  exports.findProfile = findProfile;
6
7
  exports.MAX_VIEWPORT = 4096;
@@ -22,6 +23,15 @@ exports.DEFAULT_SETTINGS = {
22
23
  split: 0.5,
23
24
  maxTabs: 12,
24
25
  };
26
+ /**
27
+ * Every tab opens unrotated. `'portrait'` means "the preset as stored", so this
28
+ * is a no-op against the table below rather than a shape anyone has to honour.
29
+ */
30
+ exports.DEFAULT_ORIENTATION = 'portrait';
31
+ /** Type guard for anything off the wire, off disk, or off an agent's payload. */
32
+ function isOrientation(v) {
33
+ return v === 'portrait' || v === 'landscape';
34
+ }
25
35
  exports.SCREEN_PRESETS = [
26
36
  // Laptops — ordered largest to smallest panel, then the denser 1080p outlier.
27
37
  { id: 'laptop-768', label: '1366×768 15.6"', width: 1366, height: 768, diagonalInches: 15.6, deviceScaleFactor: 1, group: 'laptop' },