getobsrv 0.11.0 → 0.12.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.
@@ -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-BlbBg6lm.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-B61fbgqt.css">
7
+ <script type="module" crossorigin src="./assets/index-gYDbIgqJ.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-DvwDBeho.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -233,5 +233,17 @@ function parseControlStatus(raw) {
233
233
  const panes = raw.panes ?? 'both';
234
234
  if (panes !== 'both' && panes !== 'target')
235
235
  return null;
236
- return { version, url, presetId, profileId, viewMode, panes, mode };
236
+ // The same skew, one release later. An app that predates tabs has exactly
237
+ // one session — it is the tab at index 0, and it has no id to name — so the
238
+ // defaults describe that app truthfully rather than papering over it. An
239
+ // agent polling one sees an unchanging `tabId`, which is correct: there is
240
+ // no other tab for the user to switch to. Returning null instead would take
241
+ // out drive and live snap wholesale against every app older than tabs.
242
+ const tabId = raw.tabId ?? '';
243
+ if (typeof tabId !== 'string')
244
+ return null;
245
+ const tabIndex = raw.tabIndex ?? 0;
246
+ if (typeof tabIndex !== 'number' || !Number.isInteger(tabIndex) || tabIndex < 0)
247
+ return null;
248
+ return { version, url, presetId, profileId, viewMode, panes, mode, tabId, tabIndex };
237
249
  }
@@ -6,6 +6,7 @@ exports.parseInputEvent = parseInputEvent;
6
6
  exports.parseDeviceScaleFactor = parseDeviceScaleFactor;
7
7
  exports.parseSettings = parseSettings;
8
8
  exports.parseMode = parseMode;
9
+ exports.parseTabId = parseTabId;
9
10
  exports.parseUiState = parseUiState;
10
11
  exports.parseScrollPos = parseScrollPos;
11
12
  exports.parseScrollRequest = parseScrollRequest;
@@ -132,13 +133,33 @@ function parseSettings(raw) {
132
133
  const split = raw.split ?? presets_1.DEFAULT_SETTINGS.split;
133
134
  if (!isFiniteNumber(split) || split < presets_1.SPLIT_MIN || split > presets_1.SPLIT_MAX)
134
135
  return null;
135
- return { hostDiagonalInches, hostNits, agentControl, updateCheck, lastUpdateCheck, recordHistory, split };
136
+ // Same shape for the tab cap: absent is the pre-tabs wire shape and means
137
+ // the default, and an out-of-band or fractional count is refused rather
138
+ // than clamped — the Settings input is a bounded integer field, so one
139
+ // arriving here is a bug rather than a hand-edited file.
140
+ const maxTabs = raw.maxTabs ?? presets_1.DEFAULT_SETTINGS.maxTabs;
141
+ if (!isFiniteNumber(maxTabs) || !Number.isInteger(maxTabs) || maxTabs < presets_1.MAX_TABS_MIN || maxTabs > presets_1.MAX_TABS_MAX)
142
+ return null;
143
+ return { hostDiagonalInches, hostNits, agentControl, updateCheck, lastUpdateCheck, recordHistory, split, maxTabs };
136
144
  }
137
145
  function parseMode(raw) {
138
146
  return raw === 'url' || raw === 'image' ? raw : null;
139
147
  }
140
148
  /** Longest preset/profile id the UI-state mirror will store. */
141
149
  const MAX_UI_ID = 64;
150
+ /**
151
+ * Longest tab id main will act on. Ids are minted main-side (`tab-N`), so a
152
+ * renderer message naming one is only ever echoing what it was told; the bound
153
+ * is there because the renderer is still the one sending it. An id no session
154
+ * carries resolves to nothing in the manager and the command is dropped, so
155
+ * shape is all this has to check.
156
+ */
157
+ const MAX_TAB_ID = 64;
158
+ function parseTabId(raw) {
159
+ if (typeof raw !== 'string' || raw.length === 0 || raw.length > MAX_TAB_ID)
160
+ return null;
161
+ return raw;
162
+ }
142
163
  /**
143
164
  * The renderer's UI-state report (`IPC.uiState`), mirrored main-side so the
144
165
  * agent-control server can answer `status` without a renderer round-trip.
@@ -156,6 +177,12 @@ function parseUiState(raw) {
156
177
  if (!isRecord(raw))
157
178
  return null;
158
179
  const { presetId, profileId, viewMode, mode } = raw;
180
+ // Required, unlike `panes` below: there is no sane default for "which tab
181
+ // this describes", and guessing would reintroduce exactly the misattribution
182
+ // the field exists to stop.
183
+ const tabId = parseTabId(raw.tabId);
184
+ if (tabId === null)
185
+ return null;
159
186
  if (typeof presetId !== 'string' || presetId.length === 0 || presetId.length > MAX_UI_ID)
160
187
  return null;
161
188
  if (typeof profileId !== 'string' || profileId.length === 0 || profileId.length > MAX_UI_ID)
@@ -171,6 +198,7 @@ function parseUiState(raw) {
171
198
  if (panes !== 'both' && panes !== 'target')
172
199
  return null;
173
200
  return {
201
+ tabId,
174
202
  presetId,
175
203
  profileId,
176
204
  viewMode,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PANEL_PROFILES = exports.SCREEN_PRESETS = exports.DEFAULT_SETTINGS = exports.SPLIT_MAX = exports.SPLIT_MIN = exports.MAX_VIEWPORT = void 0;
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;
4
4
  exports.findPreset = findPreset;
5
5
  exports.findProfile = findProfile;
6
6
  exports.MAX_VIEWPORT = 4096;
@@ -8,6 +8,10 @@ exports.MAX_VIEWPORT = 4096;
8
8
  * drag's 240px-a-side floor, which only the renderer knows the pixels for. */
9
9
  exports.SPLIT_MIN = 0.1;
10
10
  exports.SPLIT_MAX = 0.9;
11
+ /** The band `Settings.maxTabs` is held to. Below two there is nothing to tab
12
+ * between; above thirty-two the process count is a problem on any machine. */
13
+ exports.MAX_TABS_MIN = 2;
14
+ exports.MAX_TABS_MAX = 32;
11
15
  exports.DEFAULT_SETTINGS = {
12
16
  hostDiagonalInches: 27,
13
17
  hostNits: 500,
@@ -16,6 +20,7 @@ exports.DEFAULT_SETTINGS = {
16
20
  lastUpdateCheck: 0,
17
21
  recordHistory: true,
18
22
  split: 0.5,
23
+ maxTabs: 12,
19
24
  };
20
25
  exports.SCREEN_PRESETS = [
21
26
  // Laptops — ordered largest to smallest panel, then the denser 1080p outlier.
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.closeTab = closeTab;
4
+ exports.canAddTab = canAddTab;
5
+ exports.tabTitle = tabTitle;
6
+ /**
7
+ * Removes a tab and says which one should take focus. Closing the active tab
8
+ * moves right, because the tab that took its screen position is the one the
9
+ * eye is already on; at the end of the strip there is nothing to the right, so
10
+ * it moves left.
11
+ */
12
+ function closeTab(tabs, closeId, activeId) {
13
+ const index = tabs.findIndex(t => t.id === closeId);
14
+ if (index === -1)
15
+ return { tabs, activeId };
16
+ const next = tabs.filter(t => t.id !== closeId);
17
+ if (next.length === 0)
18
+ return { tabs: next, activeId: null };
19
+ if (closeId !== activeId)
20
+ return { tabs: next, activeId };
21
+ const neighbour = next[Math.min(index, next.length - 1)];
22
+ return { tabs: next, activeId: neighbour.id };
23
+ }
24
+ function canAddTab(count, max) {
25
+ return count < max;
26
+ }
27
+ /**
28
+ * What the strip shows. The port is kept deliberately: two local dev servers
29
+ * differ only by it, and a host-only label would render them identically.
30
+ */
31
+ function tabTitle(url, pageTitle) {
32
+ if (pageTitle.trim() !== '')
33
+ return pageTitle;
34
+ // `about:blank` is where every session starts and what an unused tab still
35
+ // holds, so it is the empty tab spelled in Chromium's words rather than an
36
+ // address anyone typed. It has no host, and falling through to the raw
37
+ // string labels a fresh tab `about:blank` — which the screenshot caught.
38
+ if (url.trim() === '' || url.trim() === 'about:blank')
39
+ return 'New tab';
40
+ try {
41
+ return new URL(url).host || url;
42
+ }
43
+ catch {
44
+ return url;
45
+ }
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getobsrv",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "See your site the way 1x screens see it",
5
5
  "main": "./out/main/index.js",
6
6
  "bin": {
@@ -61,6 +61,13 @@ when the PNG is taken. Reaching for `obsrv_snap` after a scroll works only
61
61
  when the app is already on that exact URL (it answers `navigated: false`);
62
62
  snapping a different URL is a fresh load and lands back at the top.
63
63
 
64
+ The app can hold several sessions open as tabs, and both tools act on the one
65
+ in **front** — resolved per command, so the user can move it under you. Each
66
+ result names the tab (`tabId`, `tabIndex`); if you drive over several calls and
67
+ the state has to hold, check that `tabId` did not change rather than assuming
68
+ it. You cannot name another tab, and you cannot open, close or switch tabs —
69
+ ask the user. An empty `tabId` is an app older than tabs, which has only one.
70
+
64
71
  ## The loop that catches real regressions
65
72
 
66
73
  1. Snap the dev URL across `--matrix laptop-768,android-65,1080p-24`, plus a