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.
- package/README.md +34 -0
- package/out/main/cli.js +1 -1
- package/out/main/index.js +741 -201
- package/out/main/{targetSource-nbfc-Fe6.js → targetSource-DlsHc-Ri.js} +43 -1
- package/out/mcp/server.js +21 -1
- package/out/preload/app.js +19 -16
- package/out/renderer/assets/{index-B61fbgqt.css → index-DvwDBeho.css} +107 -4
- package/out/renderer/assets/{index-BlbBg6lm.js → index-gYDbIgqJ.js} +380 -118
- package/out/renderer/index.html +2 -2
- package/out/shared/control.js +13 -1
- package/out/shared/ipcPayloads.js +29 -1
- package/out/shared/presets.js +6 -1
- package/out/shared/tabList.js +46 -0
- package/package.json +1 -1
- package/skills/obsrv-screens/SKILL.md +7 -0
package/out/renderer/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:" />
|
|
6
6
|
<title>Obsrv</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-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>
|
package/out/shared/control.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
package/out/shared/presets.js
CHANGED
|
@@ -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
|
@@ -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
|