dsh-inner-browser-use 0.11.44 → 0.13.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/docs/VERIFY.md CHANGED
@@ -1,435 +1,98 @@
1
1
  # Verify it works
2
2
 
3
- Ten minutes, one page, no debugger. Each step names what a failure looks like.
3
+ Five minutes, one page, no debugger. Each step names what a failure looks like.
4
4
 
5
5
  ## Before you start
6
6
 
7
- - The plugin is installed and enabled in the profile (`0.11.30` or later).
8
- - The app window was reloaded **after** the install: the page half only loads on
9
- a page load. A host-only reload is not enough.
10
- - A chat is open, own workspace or not.
7
+ - The plugin is installed and enabled, `0.12.1` or later (`package.json`).
8
+ - The GUI page was reloaded after the install: the page half loads on a page load.
9
+ - Chrome sits at one of the paths in
10
+ [COMPATIBILITY.md](COMPATIBILITY.md#where-chrome-is-looked-for).
11
+ - The app answers at `http://127.0.0.1:60930`.
11
12
 
12
- ## 1. The page half is alive
13
+ ## 1. The plugin answers
13
14
 
14
15
  ```bash
15
- curl -s http://127.0.0.1:<port>/dsh-inner-browser-use/state
16
+ node scripts/doctor.mjs
16
17
  ```
17
18
 
18
- Expected: `{"connected":[],"pending":0,"client":{…"tabType":true…}}`.
19
-
20
- - `client: null`: the page half never reported in after this host started. Reopen the app.
21
- - `"tabType":false`: the page loaded but the tab type did not register; the log
22
- line `phase:"idle"` in the proof log is the same fact.
23
- - `client.fresh: false`: a page reported in and then stopped, or the app has been
24
- idle past a minute. The page half beats every 20 s.
25
- - `client` present, `connected:[]`: correct before any tab is open. Seats are
26
- open tabs, not the page.
27
-
28
- ## 2. A tool opens its own page
29
-
30
- Ask the model: `用 browser_view 看看当前页面`. Nothing was clicked.
31
-
32
- Expected: the call returns the page text plus numbered controls, and the
33
- **内置浏览器** tab shows the same page.
34
-
35
- - `No 内置浏览器 tab answered.`: no page answered within `openWaitMs`. Check
36
- the proof log for `phase:"resident-open"` and then `phase:"native-address"`; a
37
- `phase:"native-missing"` instead means the tab was placed but took no address,
38
- and the next call places it again.
39
-
40
- ## 3. Drive a page
41
-
42
- ```text
43
- browser_navigate { "url": "https://example.com" }
44
- browser_view {}
45
- browser_click { "ref": 1 }
46
- browser_type { "ref": 2, "text": "hello", "submit": true }
47
- browser_script { "code": "return document.title" }
48
- browser_screenshot{}
49
- browser_download { "url": "https://example.com/report.pdf" }
50
- browser_upload { "ref": 3, "file": "/absolute/path/note.txt" }
51
- ```
52
-
53
- Expected: the pointer moves and ripples in the page; `click`/`type`/`upload`
54
- each return a fresh `view`; the screenshot returns a path under
55
- `.dsh-inner-browser-use/` in the chat's working directory.
56
-
57
- Measured in the app on 2026-10-25, nothing clicked, one chat on screen:
58
-
59
- | call | came back |
60
- | --- | --- |
61
- | `browser_navigate` | `Example Domain`, one control |
62
- | `browser_view` | the page text and `#1 [link] Learn more` |
63
- | `browser_script` | the selection it set |
64
- | `browser_selection` | that selection, 917 characters |
65
- | `browser_copy` | `Copied 917 characters` |
66
- | `browser_click` | text changed to `clicked` |
67
- | `browser_type` | `#1 [input] n = hello harness` |
68
- | `browser_download` | `.dsh-inner-browser-use/downloads/report.pdf` and its size |
69
- | `browser_upload` | `#2 [input] f = C:\fakepath\dshib-upload.txt` |
70
-
71
- ## 4. The tab does not disturb other panes
72
-
73
- Open the guide pane (`开始`) and the files pane: both are unaffected by this
74
- plugin. The plugin registers one tab type, one pane key and one title key, and no
75
- guide card. `test/client.test.mjs` guards the last part.
76
-
77
- ## 5. Read the proof log
78
-
79
- `DSH_INNER_BROWSER_PROOF` (default `/tmp/dsh-inner-browser-use.log`) records one
80
- JSON line per lifecycle event: `downlink-open`, `uplink`, `watch-open`.
81
-
82
- - `watch-open` with no `wanted` lines and a failing tool: the page never received
83
- the ask; check step 1.
84
- - `uplink` frames with `"ok":false`: the page answered and refused; the error
85
- string is the answer.
86
-
87
- ## Exit codes
88
-
89
- `npm test` runs both halves: 19 host tests over a real socket, 6 client tests
90
- that load the page module with a fake module loader and a fake `EventSource`.
91
- `node ~/.dsh/skills/ui-restraint/scripts/ui-copy-lint.mjs .` must print
92
- `ui-copy-lint: clean.`
93
-
94
- ## A capture needs that chat's page to be drawn
95
-
96
- Reading, clicking and typing drive the page itself, so they work while you are
97
- looking at another chat. Pixels are different: they come from a surface the app
98
- is drawing. A view sitting behind another conversation's view is not drawn — the
99
- guest keeps a real size (`575x1015`) and `capturePage` never answers at all, so
100
- the tool tries twice for a short time and then reports what it got, rather than
101
- waiting fifteen seconds. `browser_script` still reads that page.
102
-
103
- Two things were measured here, and both say the same: the app draws only the pane
104
- it is showing. `capturePage` on the covered guest fails with
105
- `UnknownVizError` — a surface with no frame — and a guest the plugin appends to
106
- the page itself is never attached at all: every accessor throws
107
- `The WebView must be attached to the DOM and the dom-ready event emitted`.
108
- Rendering the page out of sight is therefore not something the page half can do,
109
- so the tool reports the chat to open and stops. The frame
110
- `why:"capture-covered"` records the page it was asked to draw.
111
-
112
- The proof log carries the reason in the frame next to it:
113
-
114
- ```bash
115
- tail -5 /tmp/dsh-inner-browser-use.log # look for "why":"shot-geometry"
116
- ```
117
-
118
- - `columnBefore` / `columnAfter` — the right column's size. `0x…` means the
119
- column is closed; the tool tried to open it and this is what it got.
120
- - `body` — the page body's own size. Two zeros with a wide column mean the tab
121
- exists but is not the one the pane is showing.
122
- - `active` — which view the pane had in front.
123
- - `rooms` — the visible session containers and their sizes.
124
-
125
- ## A chat that is not on screen
126
-
127
- Pixels come from a painted surface. A page behind another chat's tab has a size
128
- but no surface, so the capture comes back empty; the tool then says which tab to
129
- bring forward instead of guessing. A second guest would be no help here: it would
130
- load the address again and photograph the top of a page whose scroll position and
131
- form state are not the ones the chat is reading, so the image would not be of the
132
- page the answer describes. Read the content instead — `browser_script` returns the
133
- same text, and `browser_view` returns the same controls.
134
-
135
- `browser_screenshot` with `offscreen: true` never opens the right column: it takes
136
- the image only if a surface already exists and otherwise says so at once.
137
-
138
- The proof log records what the capture found:
139
-
140
- ```bash
141
- tail -3 /tmp/dsh-inner-browser-use.log # "why":"capture-covered" carries href and error
142
- ```
143
-
144
- A call for a chat the person is not reading never takes the column. If that chat
145
- has no page yet, one is mounted out of sight and answers from there; only if that
146
- is impossible does the call say which chat to open. The proof log shows the two
147
- halves:
148
-
149
- ```bash
150
- grep -m1 '"phase":"wanted"' /tmp/dsh-inner-browser-use.log # panes, screen
151
- grep -m1 'resident-open' /tmp/dsh-inner-browser-use.log # the page made out of sight
152
- ```
153
-
154
- At most three pages are kept out of sight, and the oldest goes first, so a long
155
- session cannot pile them up.
156
-
157
- ## What has been run in the app
158
-
159
- Each line was run against the live pane on 2026-10-25 and is what the tool
160
- returned, not what the code was meant to do.
161
-
162
- | Tool | The page | Result |
163
- | --- | --- | --- |
164
- | `browser_navigate` | example.com | title `Example Domain` |
165
- | `browser_view` | the same page | one control, the link's text |
166
- | `browser_script` | the same page | added a button, a form, and an output to the page |
167
- | `browser_click` | the added button at 60,140 (120x40) | its label changed to `clicked`, its title to `probe clicked`, and the reply named the point: `at 120,160` |
168
- | the pointer | read from the page after a click | `translate3d(116px, 158px, 0px)`, opacity `1`, `z-index: 2147483647` — the click point less the pointer's hotspot |
169
- | `browser_type` | the added text field | the field held the text that was typed |
170
- | `browser_download` | a page with a "Download" link | the file is in `<chat folder>/.dsh-inner-browser-use/downloads/` and the tool returns that path |
171
- | `browser_upload` | the added file field | the field held the chosen file's name |
172
- | a link the page saves itself | a page whose own script saves a file | the reply names the file under the chat's folder; `POST /land` without the page's header answers 403 |
173
- | `browser_selection` | not yet run in the app | the host half that carries this tool loads on the next start; the bridge is covered by `test/host.test.mjs` |
174
- | `browser_copy` | not yet run in the app | same |
175
- | the page is remounted | the page went blank | the next call reopened it; frame `why:"restore"` |
176
- | `browser_screenshot` | a page the app is not drawing | said which tab to open; frame `why:"capture-covered"` |
177
-
178
- A capture of a page the app *is* drawing returned a 74 KB PNG of that page; the
179
- same call on a covered page has no surface to read, which is the boundary
180
- described above.
181
-
182
- ## Both halves reload without restarting the app
183
-
184
- A change is verifiable without asking anyone to restart anything. The two halves
185
- load at different moments, and both get there on their own:
186
-
187
- - `index.js` (the host half) is re-imported as soon as the file changes. A route
188
- added or removed answers at once: `curl -s -o /dev/null -w '%{http_code}'
189
- http://127.0.0.1:49631/dsh-inner-browser-use/<route>` flips from 404 to 200 on
190
- the next request, with no app restart.
191
- - `client.js` (the page half) replaces itself on its own: a loaded copy asks
192
- `/state` every four seconds and reloads when the served build differs from its
193
- own. A copy loaded before that self-check existed cannot replace itself, so the
194
- window's refresh control is the one step that is not automatic; after it, the
195
- page half keeps itself current. To prove which copy is loaded, read the hash
196
- the page reports instead of guessing: `curl -s
197
- http://127.0.0.1:49631/dsh-inner-browser-use/state | python3 -c "import json,sys;
198
- print(json.load(sys.stdin)['client'])"` — `native: true` means the page runs a
199
- copy that drives the product's own browser.
200
-
201
- So: no restart to check a behaviour, and no restart to see the result of one.
202
-
203
- To skip the wait, a reloader plugin mounted in the same app can be asked directly.
204
- `dsh-unlag` answers on `POST /dsh-unlag/reload` with the header `x-dsh-unlag: 1`;
205
- `GET /dsh-unlag/state` reports its two switches. It only acts while no turn is
206
- running, so a request made from inside a turn answers `{"status":"busy"}` — leave
207
- a waiter behind that asks again once the turn ends, and the next check runs on
208
- the new code.
209
-
210
- ## The panel is taken only for the chat the person is looking at
211
-
212
- A tool call can arrive for a chat whose page is not open. Taking the right panel
213
- then would put an empty column in front of the chat the person is reading, so the
214
- page half first asks which chat is on screen and answers only that one.
215
-
216
- The answer comes from an occupant this plugin puts in the conversation header:
217
- it renders nothing, measures the nearest header container that has a size, and
218
- watches whether that container is on screen. Its beats name the chat:
219
-
220
- ```
221
- grep -o '"phase":"seen","session":"[^"]*"' /tmp/dsh-inner-browser-use.log | tail -1
222
- ```
223
-
224
- Switch chats in the app and the named chat follows, one line per switch. When the
225
- call is for another chat, its tab is placed in that chat's strip and the call
226
- answers that the tab is there — the panel is left alone:
227
-
228
- ```
229
- grep -o '"step":"other-chat"' /tmp/dsh-inner-browser-use.log | tail -1
230
- ```
231
-
232
- A frame naming no chat at all, when no header reports, keeps the older behaviour:
233
- the tab is placed and the panel may open, then closes itself after three seconds
234
- unless a page of ours mounted in it (`"step":"selfheal"`).
235
-
236
- ## A chat that is not on screen
237
-
238
- Pixels come from a painted surface. A page behind another chat's tab has a size
239
- but no surface, so the capture comes back empty; the tool then says which tab to
240
- bring forward instead of guessing. A second guest would be no help here: it would
241
- load the address again and photograph the top of a page whose scroll position and
242
- form state are not the ones the chat is reading, so the image would not be of the
243
- page the answer describes. Read the content instead — `browser_script` returns the
244
- same text, and `browser_view` returns the same controls.
245
-
246
- `browser_screenshot` with `offscreen: true` never opens the right column: it takes
247
- the image only if a surface already exists and otherwise says so at once.
248
-
249
- The proof log records what the capture found:
19
+ One line per fact: the address, the browser (`not running` before the first
20
+ call), the profile name and directory, whether it exists, the page, viewers,
21
+ frames, pointer and the control endpoint. It only reads.
250
22
 
251
23
  ```bash
252
- tail -3 /tmp/dsh-inner-browser-use.log # "why":"capture-covered" carries href and error
24
+ curl -s http://127.0.0.1:60930/dsh-inner-browser-use/sidecar/state
253
25
  ```
254
26
 
255
- A call for a chat the person is not reading never takes the column. If that chat
256
- has no page yet, one is mounted out of sight and answers from there; only if that
257
- is impossible does the call say which chat to open. The proof log shows the two
258
- halves:
259
-
260
- ```bash
261
- grep -m1 '"phase":"wanted"' /tmp/dsh-inner-browser-use.log # panes, screen
262
- grep -m1 'resident-open' /tmp/dsh-inner-browser-use.log # the page made out of sight
263
- ```
264
-
265
- At most three pages are kept out of sight, and the oldest goes first, so a long
266
- session cannot pile them up.
267
-
268
- ## What has been run in the app
269
-
270
- Each line was run against the live pane on 2026-10-25 and is what the tool
271
- returned, not what the code was meant to do.
27
+ Expected before any tool call: `"alive":false`, `"profile":"default"`,
28
+ `"viewers":0`, `"error":""`.
272
29
 
273
- | Tool | The page | Result |
274
- | --- | --- | --- |
275
- | `browser_navigate` | example.com | title `Example Domain` |
276
- | `browser_view` | the same page | one control, the link's text |
277
- | `browser_script` | the same page | added a button, a form, and an output to the page |
278
- | `browser_click` | the added button at 60,140 (120x40) | its label changed to `clicked`, its title to `probe clicked`, and the reply named the point: `at 120,160` |
279
- | the pointer | read from the page after a click | `translate3d(116px, 158px, 0px)`, opacity `1`, `z-index: 2147483647` — the click point less the pointer's hotspot |
280
- | `browser_type` | the added text field | the field held the text that was typed |
281
- | `browser_download` | a page with a "Download" link | the file is in `<chat folder>/.dsh-inner-browser-use/downloads/` and the tool returns that path |
282
- | `browser_upload` | the added file field | the field held the chosen file's name |
283
- | a link the page saves itself | a page whose own script saves a file | the reply names the file under the chat's folder; `POST /land` without the page's header answers 403 |
284
- | `browser_selection` | not yet run in the app | the host half that carries this tool loads on the next start; the bridge is covered by `test/host.test.mjs` |
285
- | `browser_copy` | not yet run in the app | same |
286
- | the page is remounted | the page went blank | the next call reopened it; frame `why:"restore"` |
287
- | `browser_screenshot` | a page the app is not drawing | said which tab to open; frame `why:"capture-covered"` |
30
+ - `not answering under /dsh-inner-browser-use`: the plugin is not loaded, or the
31
+ app moved: set `DSH_BRIDGE` to its address.
32
+ - A 404 there: the plugin did not load, or an older build is on disk.
288
33
 
289
- A capture of a page the app *is* drawing returned a 74 KB PNG of that page; the
290
- same call on a covered page has no surface to read, which is the boundary
291
- described above.
292
-
293
- ## Both halves reload without restarting the app
294
-
295
- A change is verifiable without asking anyone to restart anything. The two halves
296
- load at different moments, and both get there on their own:
297
-
298
- - `index.js` (the host half) is re-imported as soon as the file changes. A route
299
- added or removed answers at once: `curl -s -o /dev/null -w '%{http_code}'
300
- http://127.0.0.1:49631/dsh-inner-browser-use/<route>` flips from 404 to 200 on
301
- the next request, with no app restart.
302
- - `client.js` (the page half) replaces itself on its own: a loaded copy asks
303
- `/state` every four seconds and reloads when the served build differs from its
304
- own. A copy loaded before that self-check existed cannot replace itself, so the
305
- window's refresh control is the one step that is not automatic; after it, the
306
- page half keeps itself current. To prove which copy is loaded, read the hash
307
- the page reports instead of guessing: `curl -s
308
- http://127.0.0.1:49631/dsh-inner-browser-use/state | python3 -c "import json,sys;
309
- print(json.load(sys.stdin)['client'])"` — `native: true` means the page runs a
310
- copy that drives the product's own browser.
311
-
312
- So: no restart to check a behaviour, and no restart to see the result of one.
313
-
314
- To skip the wait, a reloader plugin mounted in the same app can be asked directly.
315
- `dsh-unlag` answers on `POST /dsh-unlag/reload` with the header `x-dsh-unlag: 1`;
316
- `GET /dsh-unlag/state` reports its two switches. It only acts while no turn is
317
- running, so a request made from inside a turn answers `{"status":"busy"}` — leave
318
- a waiter behind that asks again once the turn ends, and the next check runs on
319
- the new code.
320
-
321
- ## The panel is taken only for the chat the person is looking at
322
-
323
- A tool call can arrive for a chat whose page is not open. Taking the right panel
324
- then would put an empty column in front of the chat the person is reading, so the
325
- page half first asks which chat is on screen and answers only that one.
326
-
327
- The answer comes from an occupant this plugin puts in the conversation header:
328
- it renders nothing, measures the nearest header container that has a size, and
329
- watches whether that container is on screen. Its beats name the chat:
330
-
331
- ```
332
- grep -o '"phase":"seen","session":"[^"]*"' /tmp/dsh-inner-browser-use.log | tail -1
333
- ```
334
-
335
- Switch chats in the app and the named chat follows, one line per switch. When the
336
- call is for another chat, its tab is placed in that chat's strip and the call
337
- answers that the tab is there — the panel is left alone:
338
-
339
- ```
340
- grep -o '"step":"other-chat"' /tmp/dsh-inner-browser-use.log | tail -1
341
- ```
342
-
343
- A frame naming no chat at all, when no header reports, keeps the older behaviour:
344
- the tab is placed and the panel may open, then closes itself after three seconds
345
- unless a page of ours mounted in it (`"step":"selfheal"`).
346
-
347
- ## Where a login lives
348
-
349
- A pane leases the app's own sidebar guest, and the shell decides which storage
350
- partition that guest gets. The shipped code reserves it like this:
351
-
352
- ```
353
- partition = `dsh-sidebar-browser-${randomUUID()}`
354
- // "Reserve one guest in a workspace's process-lifetime partition."
355
- ```
356
-
357
- So a login is shared by every chat of the same project and survives closing the
358
- tab or the chat. The partition itself is named fresh each run, so the guest keeps
359
- nothing on disk; the page half therefore files what the *page* can read under
360
- `~/.dsh/inner-browser-use/cookies/<hash>.json`, mode 0600, keyed by the workspace
361
- key, and writes it back when that origin is opened again:
362
-
363
- ```console
364
- $ ls -l ~/.dsh/inner-browser-use/cookies/
365
- -rw-------@ 1 apple staff 811 Oct 25 21:26 7b5c760580d2febdb4d5f50a.json
366
- $ python3 -c "import json,glob;print([(c['name'],c['url']) for c in json.load(open(glob.glob('/Users/apple/.dsh/inner-browser-use/cookies/*.json')[0]))])"
367
- [('i18next', 'http://127.0.0.1:8765'), ('sid', 'http://127.0.0.1:8765'), ('__ac_nonce', 'https://www.douyin.com'), ...]
368
- ```
369
-
370
- That file was produced by the app: one guest had been to `http://127.0.0.1:8765`,
371
- another to `https://www.douyin.com`, and each save replaced only its own origin, so
372
- both sites sit in the same workspace file. A save runs on `did-stop-loading` and
373
- every 30 s while the guest is alive; a restore runs once the page it belongs to has
374
- loaded, and the page is loaded once more only when a cookie was actually written,
375
- because a cookie set from the page cannot affect a document already open. The log
376
- shows `{"why":"cookies-restored"}` or `{"why":"signed-in-again"}` when that happens.
377
-
378
- Two limits stay. Cookies marked `HttpOnly` are invisible to the page, so a login
379
- that lives only in those needs the password again after the app quits. And the
380
- store holds only what the page could read when it was saved, so a workspace that
381
- has not opened a site yet has no file; `node scripts/doctor.mjs` prints
382
- `sign-in kept per workspace` either way.
383
-
384
- ## What a running app showed
385
-
386
- Three flows were driven against the app itself, each one visible in its own output:
387
-
388
- - **The arrow is in the image.** A click answered `at 101,167`, and the saved
389
- image showed the pointer over that control — the capture puts the arrow back
390
- even when the action is older than the arrow's own timeout.
391
- - **A download lands in the chat's folder and arrives in the chat.** Following a
392
- file link answered `Saved <chat folder>/.dsh-inner-browser-use/downloads/report.txt
393
- (21 bytes)` and printed the file's contents in the same answer.
394
- - **The page and its cookies stay.** A guest reopened with `"why":"reopen"` on the
395
- page it had been on, and a cookie set before a full navigation was still there
396
- after it.
397
- - **A file the chat made goes back to the page.** A file input took the caller's
398
- own saved image: `Attached <image>.png to #1`, and the page reported
399
- `<image>.png 17033 bytes`. So a page reaches the chat, the chat reaches the
400
- folder, and the folder reaches the page again without anyone moving it by hand.
401
-
402
- ## A chat that is not on screen
403
-
404
- The column shows one chat, so the page for any other chat does not exist while it
405
- is off screen. A call made from such a chat answers at once:
406
-
407
- ```
408
- This chat is not the one on screen. Open it, then call again.
409
- ```
410
-
411
- That answer is the guarantee, not a failure: bringing the column to the caller's
412
- chat would take the screen from whoever is reading. Switch to the caller's chat
413
- and the same call opens its page in the column it already belongs to.
414
-
415
- That answer comes from the page's own next heartbeat, not from a timeout:
416
-
417
- ```
418
- curl -s http://127.0.0.1:<port>/dsh-inner-browser-use/state | grep -o '"pending":[0-9]*'
419
- grep -o '"step":"other-chat"' /tmp/dsh-inner-browser-use.log | tail -1
420
- ```
421
-
422
- ## Catching up on a published build
34
+ ## 2. A tool opens its own page
423
35
 
424
- The page half compares the build the app is serving with the build it is running
425
- and reloads itself once when they differ, so a plugin update does not need the
426
- window closed by hand. Two facts are worth reading off `/state` when it does not
427
- happen:
36
+ Ask the model `用 sidecar_open 打开 https://www.bing.com`, or call it yourself.
37
+ Expected: the result names the address and title, and this chat's right column
38
+ shows the picture in a tab called **后台浏览器** or **Background browser**.
39
+ `doctor.mjs` now reads `attached, headless`, `viewers 1`, frames climbing.
40
+
41
+ - `"alive":true` with `"viewers":0`: the page half has not mounted the mirror.
42
+ Reload the GUI page; a reload of the plugin alone does not rebuild the tab.
43
+ - `"alive":false` with a non-empty `"error"`: Chrome did not start. Check the
44
+ binary at a listed path, and on macOS that the launch carried
45
+ `--use-mock-keychain`.
46
+ - Frames flat while viewers is 1: the screencast stalled. `sidecar_stop`, then
47
+ `sidecar_open` again.
48
+ - `"pointer":null`: no tool input yet; it fills on the first click or move.
49
+
50
+ ## 3. Only the chat that asked
51
+
52
+ Switch to another chat: no mirror there, and the first chat keeps its own. A
53
+ call from the other chat drives the same browser; the tab stays put.
54
+
55
+ A mirror that opens in every chat means the page half is not comparing
56
+ `sidebarRight.mounted` and is running an older bundle: reload the GUI page.
57
+
58
+ ## 4. Drive it by hand
59
+
60
+ - **Hover** the search field: your cursor turns into an I-beam, the black arrow
61
+ does not move. An arrow that follows the mouse means the pointer paths are
62
+ crossed.
63
+ - **Type** Chinese with `sidecar_type { "text": "…", "submit": true }`: the
64
+ results page appears and the field holds characters, not boxes.
65
+ - **Backspace** with `sidecar_key { "key": "Backspace" }` in a filled field:
66
+ one character goes. Nothing happening means the key reached the document.
67
+ - **Screenshot** with `sidecar_shot {}`: an absolute path under
68
+ `~/.dsh-inner-browser-use/screenshots`. A black picture means the screencast
69
+ was not running when the frame was taken.
70
+
71
+ ## 5. Stop, then open again
72
+
73
+ `sidecar_stop` reads `"alive":false` and leaves the profile alone.
74
+ `sidecar_open https://www.bing.com` brings the same profile back, still signed
75
+ in from the first pass.
76
+
77
+ Signed out after the restart: Chrome came up on another profile, or was killed
78
+ instead of closed. The directory printed by `doctor.mjs` is the one that must
79
+ still hold the cookies.
80
+
81
+ ## 6. Upgrading from 0.11.x
82
+
83
+ - The `browser_*` tools are gone; asking for one is an unknown-tool error.
84
+ - `GET /dsh-inner-browser-use/state` answers 404; only the routes in
85
+ [ARCHITECTURE.md](ARCHITECTURE.md#routes) exist.
86
+ - The tab is called **后台浏览器** / **Background browser**; a tab still named
87
+ 内置浏览器 means the page half is stale: reload the GUI page.
88
+ - Guest-era logins are not migrated; sign in once in the new profile.
89
+
90
+ ## Breadth
91
+
92
+ `node scripts/breadth.mjs` drives a shadow-DOM component, a form (type, select,
93
+ checkbox, submit), a native dialog, an iframe (read, type, click), a click that
94
+ opens a tab, and a dead ref against local pages, and prints one JSON line per
95
+ check. Twenty-seven `true` lines is the bar; a `false` line names the check that
96
+ broke. Two of them cover a cross-site frame, which Chrome runs in its own
97
+ process.
428
98
 
429
- - `client.v` below `version`: the page is older than the files. One reload
430
- fixes it; a window that already used up its reloads is not retried, so close
431
- and reopen the window once.
432
- - `phase:"app-older"` in the proof log: the page is current but the app process
433
- started before the update. The app keeps serving the files it loaded, so the
434
- new host half arrives on the next app start; nothing on screen changes until
435
- then.