Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
9668ece
feat(desktop): put Virtual Displays in both assistants' sidebars
theogravity Oct 26, 2026
6f63f5e
fix(web): say why previews are off, and offer only runnable display p…
theogravity Oct 26, 2026
81c813d
chore: add the changeset for the display rail entry
theogravity Oct 26, 2026
f046b30
fix(preview): say which gate refused a display, and keep the children…
theogravity Oct 27, 2026
5e32956
fix(web): put a virtual-display launch refusal at its checkbox
theogravity Oct 27, 2026
582453d
chore: cover the refusal placement in the display changeset
theogravity Oct 27, 2026
fd743b0
fix(desktop): the helper's codesign requirement was read as a filename
theogravity Oct 27, 2026
4f50c70
chore: note the display helper settings fix
theogravity Oct 27, 2026
2361e35
fix(desktop): read the helper's CDHash at the verbosity that prints it
theogravity Oct 27, 2026
bf81bcd
fix(helper): the consent store trapped the helper before its first wi…
theogravity Oct 27, 2026
a06623a
fix(desktop): staging the helper could no longer stop rebuilding itself
theogravity Oct 27, 2026
0b9ae8a
fix(preview): log what the helper actually answered when a screen is …
theogravity Oct 27, 2026
239b7d6
fix(preview): log the helper's raw answer at the readiness gate too
theogravity Oct 27, 2026
a9650e0
feat(web): badge the preview icon with what there is to open
theogravity Oct 27, 2026
2e2b673
feat(helper): style the panel like the app, say who is watching, and …
theogravity Oct 27, 2026
bd328eb
fix(helper): never leave the person at the Mac without a cursor
theogravity Oct 27, 2026
65078d1
test(helper): put the second screen's test point inside its real bounds
theogravity Oct 27, 2026
82555ec
fix(helper): lay the panel out as a settings card, not a report
theogravity Oct 27, 2026
14773cb
refactor(design): one palette file, imported by every surface
theogravity Oct 27, 2026
d5b935e
feat(helper): render the display helper's panel on the design system
theogravity Oct 27, 2026
361c35b
fix(helper): read the web view's arguments on the main actor
theogravity Oct 27, 2026
5fe323d
fix(helper): ship the panel as one document that fetches nothing
theogravity Oct 27, 2026
84abd8a
fix(helper): bring the panel up to the design system
theogravity Oct 27, 2026
7e818c6
fix(helper): give the bar one idiom and let the page own the width
theogravity Oct 27, 2026
6639d57
fix(helper): let the permission row say what still needs fixing
theogravity Oct 27, 2026
eb1daac
fix(helper): name the status menu and keep Minimize current
theogravity Oct 27, 2026
7d5538e
docs(changeset): describe the panel as it shipped
theogravity Oct 27, 2026
4a0461a
fix(helper): read the login item as the fact it is
theogravity Oct 27, 2026
ab5eaa0
feat(helper): give the login item its own section
theogravity Oct 27, 2026
1218a14
fix(helper): build the panel from the dashboard's own primitives
theogravity Oct 27, 2026
c1d3b50
docs(changeset): name the components the panel now composes
theogravity Oct 27, 2026
8b3a212
fix(helper): explain what quitting does, and stop it revoking consent
theogravity Oct 27, 2026
6ace2b6
feat(helper): let the status menu enable the helper too
theogravity Oct 27, 2026
e2ce283
fix(helper): make sign-in its own choice, and read it honestly
theogravity Oct 27, 2026
860aaee
feat(helper): keep a way into System Settings on the granted rows
theogravity Oct 27, 2026
8b2a8d9
fix(helper,preview): clear the review round's majors and minors
theogravity Oct 27, 2026
b7a8934
fix(helper,web): close the second review round's minors
theogravity Oct 27, 2026
7e46860
fix(helper): use the current activation call in the quit alert
theogravity Oct 27, 2026
68592c1
docs(security): describe the consent split the helper actually ships
theogravity Oct 27, 2026
c3df870
fix(helper): tell the quit truth, and keep the unregister door open
theogravity Oct 27, 2026
14771bf
fix(helper): answer the sign-in question with consent in hand, and re…
theogravity Oct 27, 2026
47dcb49
docs(changeset): keep the true half of the codesign escape story
theogravity Oct 27, 2026
d8849e6
fix(web,helper,preview): close the fifth review round's minors
theogravity Oct 27, 2026
fec31bd
fix(web,docs): count only what the reader could open, and name the re…
theogravity Oct 27, 2026
1360b22
fix(web,helper): finish the OS gate, and make the palette copy provable
theogravity Oct 27, 2026
2ff40df
fix(web,helper): let the list and the value answer the same question
theogravity Oct 27, 2026
832e83f
fix(web,docs): one reading of an unanswered OS, and the consent doors…
theogravity Oct 27, 2026
527d57b
fix(server,web): a dead pane stream says so and recovers; workspace h…
theogravity Oct 27, 2026
dbaa5b6
docs: cover the display feature's new doors, and photograph them
theogravity Oct 27, 2026
80061be
chore(website): keep the shipped screenshots from their own capture
theogravity Oct 27, 2026
ff8a8d4
fix(web,docs,e2e): tell the truth about the badge, the picker, and th…
theogravity Oct 27, 2026
4681067
fix(web,docs): one tested predicate for the refusal's owner, and it s…
theogravity Oct 27, 2026
2164a31
fix(server,node): a cursor read that answers nothing is null, not (0,0)
theogravity Oct 27, 2026
dbe5bbe
fix(web,e2e): one source for the branch test, and no real addresses i…
theogravity Oct 27, 2026
5b440d7
fix(web,helper): the consent note names its own act, and one answer p…
theogravity Oct 27, 2026
64586b7
fix(web,helper): a real cross-tab write lifts the page-load memory
theogravity Oct 27, 2026
66d3a76
fix(web): a viewer whose grid moved under its replay re-attaches
theogravity Oct 27, 2026
483f620
fix(web): a grid adoption that settles after the check still resyncs
theogravity Oct 27, 2026
6be7039
fix(web,node,server,helper): a refused provider stays named, and an O…
theogravity Oct 27, 2026
1f37124
fix(server,node,helper,web): a resume that fails stops the helper, it…
theogravity Oct 27, 2026
b2cae90
fix(helper,node,web,docs): keep the Swift call the macOS lane has pro…
theogravity Oct 27, 2026
b3aa3d8
docs(docs,e2e): re-shoot the dashboard figures against the current tree
theogravity Oct 27, 2026
55b386e
fix(helper): the third activation call, and a cursor heal that runs e…
theogravity Oct 27, 2026
2168801
Pane terminals self-heal: dead streams, garbled grids, dead writers (…
theogravity Oct 27, 2026
438b819
test(web): put the attach timings back, and stop four comments claimi…
theogravity Oct 27, 2026
7ae7a7e
fix(web,mcp,server,helper): a viewer's badge stops promising save, an…
theogravity Oct 27, 2026
0bff230
fix(docs,helper,e2e): state the cursor guard the right way round, and…
theogravity Oct 27, 2026
84bac94
fix(mcp,docs,helper,e2e): report a provider's enabled state the way c…
theogravity Oct 27, 2026
a0f6cd2
fix(web,helper,docs): give the save half one home, and sweep the pane…
theogravity Oct 27, 2026
9c41e3c
fix(helper,web,mcp,docs): close the split-key route to the consent note
theogravity Oct 27, 2026
a1b3aed
docs: add CODEX.md for Codex sessions
theogravity Oct 27, 2026
af37aee
Revert "docs: add CODEX.md for Codex sessions"
theogravity Oct 27, 2026
3a36c4e
fix(helper,web,docs): make the consent copy unreachable, not pattern-…
theogravity Oct 27, 2026
b049a10
test(helper): pin the invariant the consent guard rests on
theogravity Oct 27, 2026
054bcf9
test(helper,mcp): close the copy surface instead of listing names to …
theogravity Oct 27, 2026
e9600c9
test(helper): let the closed key set see non-enumerable copy
theogravity Oct 27, 2026
d395b11
fix(helper,mcp): sweep the page the webview loads, and pin every gate…
theogravity Oct 27, 2026
0088234
test(helper): drop the pattern guards the last four rounds kept faili…
theogravity Oct 27, 2026
228ff20
test(helper,mcp): sweep by enumeration, and stop claiming a promise t…
theogravity Oct 27, 2026
6916cdd
test(helper,web): audit the sweep against the whole package, decode w…
theogravity Oct 27, 2026
6f1e6da
fix(web): point the BetterDisplay row at the sidebar door, and announ…
theogravity Oct 27, 2026
82e92db
refactor(web): give the launch refusal one announced home
theogravity Oct 27, 2026
dfc513a
test(desktop): pin the two orders the helper install transaction neve…
theogravity Oct 27, 2026
93927be
test(helper): pin the rescue-before-bind order and the consent a fail…
theogravity Oct 27, 2026
cb6822d
refactor(web): let a launch dialog carry its refusal with its content
theogravity Oct 28, 2026
b7c9011
refactor(web): route the empty-state refusal through the same line
theogravity Oct 28, 2026
2053042
fix: harden display helper launch and repair review regressions
theogravity Oct 28, 2026
71183be
test: allow isolated preview ports during browser verification
theogravity Oct 28, 2026
0f5d2ed
fix: capture helper weakly in deferred menu refresh
theogravity Oct 28, 2026
f3e7fa8
fix: preserve native terminal selection in copy mode
theogravity Oct 28, 2026
53bdb99
docs: explain review fixes and illustrate terminal copying
theogravity Oct 28, 2026
6612114
fix: keep generated setup credentials out of process arguments
theogravity Oct 28, 2026
8a9b325
fix: harden SSH setup transport and bound resize memory
theogravity Oct 28, 2026
dd701fe
test: await preview menu effects before closing the view
theogravity Oct 28, 2026
bfa2d5d
test: read enrollment URL from curl stdin configuration
theogravity Oct 28, 2026
c1cca83
docs: link security disclosure to current node protocol
theogravity Oct 28, 2026
603084f
fix: revoke live terminal access when subshell shares change
theogravity Oct 28, 2026
ff96c98
test: read settled workspace IDs from the browser location
theogravity Oct 28, 2026
b317c29
fix: bound terminal input work and discard disconnected retries
theogravity Oct 28, 2026
11ce743
fix(previews): proxy app resources and remember personal settings
theogravity Oct 28, 2026
f98afbc
test(previews): use portable process launcher for browser checks
theogravity Oct 28, 2026
343986b
fix(previews): link setup guide for individual disabled features
theogravity Oct 28, 2026
f486416
feat(website): showcase virtual displays and macOS setup
theogravity Oct 28, 2026
75d4772
refactor(website): keep virtual display setup in linked guides
theogravity Oct 28, 2026
141630a
Organize preview docs and clarify display helper setup
theogravity Oct 28, 2026
f851ddb
Show a loaded preview and consolidate preview guides
theogravity Oct 28, 2026
c62a8a3
Enlarge preview screenshots and clarify badge examples
theogravity Oct 28, 2026
188b978
Use readable landscape preview screenshots
theogravity Oct 28, 2026
ccd6580
fix: address PR 465 review findings and CI regressions
theogravity Oct 28, 2026
967b9fe
Keep local review handoff report out of source control
theogravity Oct 28, 2026
964b265
docs: clarify native checks and preview schema properties
theogravity Oct 28, 2026
ce015b4
test: use supported exact-name matching in workspace dialog
theogravity Oct 28, 2026
7975bdd
fix: isolate terminal input budgets and clean successful helper updates
theogravity Oct 28, 2026
9e98a20
fix(server): reclaim a stuck input retry before closing a live viewer
theogravity Oct 28, 2026
d203a26
test(server): pin the node byte cap and the viewer-id hold fallback
theogravity Oct 28, 2026
695451e
fix(e2e): clear the previous run's test file before the agent is aske…
theogravity Oct 28, 2026
a76c5df
test(server): compare the re-fire assertion against the hold's own drain
theogravity Oct 28, 2026
4129db4
Refresh remaining demo clips with neutral paths and readable phone text
theogravity Oct 28, 2026
57f69b8
Restore the deployed mobile demo and original player sizing
theogravity Oct 28, 2026
6b4d0c3
Keep phone demo size consistent across website layouts
theogravity Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .changeset/attach-cursor-honest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/server": patch
"@internal/node": patch
---

A pane viewer could open already garbled, and stay garbled for its whole life: the replay's cursor read accepts a blank answer as row zero. tmux occasionally prints `":"` for a pane caught mid-redraw, and `Number("")` is 0, so that moment became a confident "the cursor is top-left", the replay ended sixteen-odd rows off the pane, and every later frame that repositions relative to it (a TUI repainting its pinned spinner block) landed on the transcript instead. The read now answers the pane's real position or nothing at all, and "nothing" takes the known degrade: a replay without the restore rather than a wrong one. The attach journal line gained the matching witness, `cursor=x,y|none`, so the next "fixed elements overlap the output" report reads the base off the log instead of guessing. Also: the hand-run attach probes were repaired (stale dist paths, and `test-setup.ts` no longer loads outside the runner, so they get their own preload) and `probe-join.ts` joins a pane the way production does, overlap and all.
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@internal/backend-errors",
"@internal/brand",
"@internal/e2e",
"@internal/helper-panel",
"@internal/mcp-core",
"@internal/mobile",
"@internal/node-admin",
Expand Down
8 changes: 8 additions & 0 deletions .changeset/copy-mode-toast-and-node-enabled.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@internal/server": patch
"@internal/node": patch
---

A pane's **Enable text copying** toggle now asks the preference what the device actually holds before deciding what the press did. The header menu and the touch bar share one per-device set, and each used to flip its own React copy of it, so a press that followed another window's change could "turn copying off" on a pane that was already off and say so out loud. In a private window, where the device stores nothing at all, the two surfaces disagreed the other way: the one you clicked remembered your click while the other re-read an empty store. Both now take the answer the preference module holds, which is also what a refused write leaves the page with.

`get_node` also holds its answer about a display provider to the same rule the launch path applies, mirroring all four of the plane's gates: the node switch, the provider catalog, the owner's enabled list, and whether that provider runs on this node's operating system, with the protocol's own default rather than a provider name typed into the tool. The last one used to be inferred from `status`, which is the node's own report rather than the plane's verdict, so a node answering optimistically about a platform it cannot serve had the tool recommending a launch the server refuses. `enabled` is now the plane's answer and `status` stays the node's, and when the node cannot be asked the tool says so and promises nothing else. The other three were already enforced on the way in, so nothing a user could click read wrong before this; what changes is that a row arriving by some other route, or one a later release shrinks out of the catalog, can no longer be reported ready here and then refused by the launch it recommended.
6 changes: 6 additions & 0 deletions .changeset/display-helper-copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@internal/desktop-client": patch
"@internal/desktop-server": patch
---

Clarify virtual display setup and helper consent, permissions, sign-in, and quit messages. Keep shared-desktop warnings visible and explain when viewers disconnect while subshells keep running. Bump the bundled helper to 1.0.1 so its update is identified consistently across both desktop apps.
7 changes: 7 additions & 0 deletions .changeset/frozen-tail-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@internal/server": patch
---

A pane terminal could freeze while the pane itself kept running: when the node's byte stream to the browser died, every device watching that subshell kept seeing the same stale screen, typing echoed nothing, and the state lasted until the server restarted. The dashboard builds one output pump per subshell and hands it to every later viewer, and a pump that went silent (a refused tail start, a relay that stopped delivering) never said so. Now it does: the server logs the failure, drops that pane's viewers with the retry close, and the re-attach that follows builds a fresh pump at the current end of the log. A shared pump also gets a watchdog: if the pane's log grows while the stream has delivered nothing for 25 seconds, the pump is declared dead the same way, and a genuinely quiet pane is left alone because the verdict compares the log's size against what the pump actually delivered.

The workspace header's actions menu now offers the **Diagnostics** toggle for the focused pane, the same per-device HUD the subshell page has had since Wave C. It floats over the dock terminal the header stands above and reports the socket state, reconnects, viewers, input echo timings, and when the last pane byte arrived, which is what you want on screen while a terminal is misbehaving.
5 changes: 5 additions & 0 deletions .changeset/grid-resync-on-settle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@internal/server": patch
---

A terminal no longer stays garbled after the pane it watches resizes. The reported shape: a TUI with a block that repaints constantly (a spinner, a pinned status line) would start drawing that block over the transcript, "where new output is mixed in", and keep doing it until the tab was closed. The window is the resize itself: bytes the pane emitted while changing its grid addressed a size the viewer never rendered, and every later frame the app positions relative to them lands a constant offset off. Only the attach can lay down a true base (absolute capture, cursor restore, confirmed geometry), so when a viewer adopts a grid other than the one its replay was painted into, and pane bytes arrived in between, it now silently re-attaches and heals. Once per observed change, debounced so a window drag settles before the replay, and gapped so a bouncing grid cannot turn the self-heal into a reconnect storm. The client never asks the server for a size, only for fresh truth.
9 changes: 9 additions & 0 deletions .changeset/helper-review-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@internal/desktop-client": patch
"@internal/desktop-server": patch
"@internal/server": patch
---

Verify the installed display helper before opening it even when an update is blocked by another running helper. Preserve executable permissions when staging the helper, harden its bundled page's HTML escaping and native message boundary, and keep its state and menu updates consistent.

Recover terminal grid changes that happen during the reconnect cooldown. Let native text selection reach the terminal rows in copy mode, and prevent input focus from clearing a new selection.
5 changes: 5 additions & 0 deletions .changeset/pipe-writer-rearm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@internal/node": patch
---

A node re-arms its own pane recorder now. Each pane's bytes reach the dashboard through a small writer process tmux pipes the pane into; if something kills just that writer (an OOM pass is the usual culprit) while the pane itself keeps running, the log stops growing, and every viewer sees a frozen terminal with a live pane behind it. tmux's own pipe flag cannot arbitrate (it has measured both on and off for a dead writer), so the exit watcher, which already touches every supervised pane, asks the honest question on that same beat: if the pane's screen moved and its log did not, repeatedly, for a little over a minute, the writer is dead, and the watcher re-arms the pipe on the same log file the long way: the form that cannot be talked out of it by tmux believing the pipe is already on. A quiet pane and a healthy writer both cost nothing beyond a periodic look, and the re-arm waits out three strikes so no single race spends one.
7 changes: 7 additions & 0 deletions .changeset/pr465-review-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@internal/docs": patch
---

Explain display-helper updates and signature failures, touch-device text copying, and terminal display recovery after resizing. Add screenshots of the copy controls and selected text, and show where to open helper settings.

Explain private setup-key delivery, the CLI capability check, coordinated server and node upgrades, and the remaining shell-history and local-account trust limits.
11 changes: 11 additions & 0 deletions .changeset/pr465-review-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@internal/server": patch
"@internal/node": patch
"@internal/desktop-server": patch
"@internal/desktop-client": patch
"@internal/docs": patch
---

Keep preview redirects inside their proxy, preserve malformed app scripts for browser diagnostics, and bound concurrent rewriting by decoded bytes. Fix preview preferences on display-only nodes, reconnect grid tracking, launch-dialog cleanup, and node-settings links. Recover stalled capture writers between output bursts and rescue the Mac pointer after capture loss. Keep helper confirmations stable across state updates and clear them when settings reopen. Verify shared palette imports and refresh the preview documentation screenshots.

Reserve terminal input capacity per node so an offline machine cannot consume the fleet's budget, give a keystroke on a live pane the capacity a stuck retry was holding before disconnecting anyone, and drop held input immediately during forced viewer recovery. Prune obsolete helper staging copies after a successful install while preserving recovery data on failure. Dismiss the helper's quit confirmation when Confirm is pressed, even where the native bridge is absent.
5 changes: 5 additions & 0 deletions .changeset/preview-compatibility-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@internal/docs": patch
---

Document personal preview settings, common URL rewriting, and browser compatibility limits. Explain live sharing revocation, terminal input bounds, private installer credentials, and Display Helper verification. Give Web previews & screens its own sidebar section with shared prerequisites and dedicated app, browser, Linux, macOS, and agent guides. Consolidate platform setup and preserve existing links. Start the overview with a loaded website preview and consolidate repeated prerequisites, access rules, and provider lifecycle explanations.
8 changes: 8 additions & 0 deletions .changeset/preview-proxy-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
---

Fix web preview asset paths and common development-server requests, including Next.js and Vite hot reload. Add personal proxy settings remembered for each app on a node, with controls for URL rewriting, the Host header, and redirects.

Show the setup guide when either web previews or virtual displays is disabled, including when no views are discovered.
5 changes: 5 additions & 0 deletions .changeset/setup-command-credentials.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@internal/server": patch
---

Keep setup keys out of process arguments in dashboard-generated install and setup commands. Explain the current CLI requirement and the remaining shell-history and local-account trust limits.
8 changes: 8 additions & 0 deletions .changeset/ssh-bulletin-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@internal/server": patch
"@internal/node": patch
"@internal/desktop-server": patch
"@internal/desktop-client": patch
---

Keep SSH setup keys out of subprocess arguments, require stdin-capable connecting nodes, and refuse older downloaded node binaries before passing credentials. Bound retained terminal resize work during sustained updates and document the SSH security bulletin review.
7 changes: 7 additions & 0 deletions .changeset/terminal-input-budgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
"@internal/docs": patch
---

Bound terminal WebSocket input by frame count and UTF-8 payload bytes per connection and across the server, including dispatched writes and retries. Close connections that exceed the limits and prevent late failures from recreating retry queues after disconnect. Document the limits and recovery behavior.
7 changes: 7 additions & 0 deletions .changeset/terminal-share-revocation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@internal/server": patch
"@internal/desktop-server": patch
"@internal/docs": patch
---

Revoke cached terminal access when subshell shares change, including connections still attaching and queued input retries. Recipients reconnect through the current access checks; the owner's terminal stays connected. Document the cutoff and the limits for commands already dispatched.
28 changes: 28 additions & 0 deletions .changeset/virtual-display-rail-entry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
"@internal/desktop-client": minor
"@internal/desktop-server": minor
"@internal/server": patch
"@internal/node": patch
"@internal/docs": patch
---

Add a **Virtual Displays** section to both desktop assistants' sidebar, shown only where a display helper exists, so the helper is reachable from the window a person is already looking at instead of only from the macOS menu bar. The screen carries the steps the menu item never showed (enable the helper, grant Screen Recording and Accessibility to Subshell Display Helper, and what to do when macOS asks for a restart) above one button that opens the helper's own settings panel and a link to the setup guide. Document the sidebar as the way in.

In the web dashboard, a node that cannot serve previews now says so in gold when the reason is that both of its preview switches are off, which an owner can fix, with a link to the guide that says how; an offline or outdated node stays muted, because no setting fixes that. A launch form, a preset, and the provider list no longer offer a virtual display provider the selected machine's operating system cannot run, and once the server knows the machine's OS it refuses one before the subshell row, its token, or a launch command exists, so a Mac is not asked to start Xvfb. A machine whose OS has not answered is not refused on that basis. Both shapes of no-answer count (no value at all, and an agent on a platform it cannot classify reporting the protocol's own `unknown`), because neither is a verdict: the list keeps what it held and the saved choice stays. Filtering on a non-answer would empty the provider card of a node that enrolled and never reported, which is the owner's only way to enable anything there, and would hide what a preset is about to launch. It is not a promise that such a machine can run the provider: the node answers that itself and refuses Xvfb on any non-Linux platform with `preview-display-unsupported`, so the server's check is a fast no rather than the only no. A provider the node has not enabled now stays named in its picker instead of hiding behind a trigger reading "Node default", since the line beside that row tells you to choose another provider and a choice you cannot see is not one you can change.

A launch refused over its virtual display now reports the problem beside the **Virtual display** checkbox it belongs to, not under Save as preset; switch the checkbox off and the line moves back to the form's own message, so the reason is never both invisible and orphaned. A refusal the server raises itself now carries the node's own word for the same failure, so it lands in the same place whoever noticed it. On the machine, the display allocation says which readiness gate stayed shut (the Xvfb socket, the VNC port, or the listener's owner), logs what Xvfb and x11vnc printed before cleaning up, and gives a cold box 10 seconds instead of 5.

The preview icon in a pane's header now wears a count when the pane has things you could open but have not saved: discovered app ports with no Saved services row, plus the screen and the browser watch when the pane offers them. It is the picker's own data, so the number and the list cannot disagree, and "you could" is read as the reader's own access: someone holding view sees the port and screen rows but cannot act on them, each disabled control saying why, so their badge counts only the browser watch and never numbers a control that answers "needs edit access".

The desktop apps' **Open Display Helper settings** control works: the helper's signature check handed `codesign` a bare requirement string, which it read as a file name, so the panel failed to open on every press. (The module is macOS-only, so no Linux lane even builds it, and the macOS lanes that do only fail at runtime on a machine with a real bundle: nothing in CI opens the helper's panel, so no lane ever pressed the path that checks a signature or reads a preference.) Pressing it then opened nothing either: the helper read its own preferences through a `UserDefaults` suite named after its bundle identifier, which Foundation refuses, so the process trapped before any window existed. Both are fixed, and a helper that cannot find its panel now says so instead of showing an empty window.

The panel itself is now the same product as the rest of Subshell, built from the components a person already knows in the dashboard: a card plate with its border and radius, `Fact` rows (a strong muted label above a readable value) for the login item and the two macOS permissions, and a form-section heading with its trailing rule for each group. Gold marks what you can still fix and plain ink what is settled, red what macOS refused, a live line names how many people are watching this Mac's screens, help sits at the bottom left and the exits at the bottom right in one shape, and a disabled control's reason is drawn in gold rather than hidden where a person who cannot hover will never find it. It is a small local page bundled into the helper, so the design system's own checker guards it, and the palette it draws on moved to `packages/assistant/tokens.css`, which every CSS surface imports rather than copies: the dashboard, both assistants and the helper's panel now read one file, while the node's own dashboard and the mobile app still keep a hand-maintained copy of the same values. The menu bar's menu now says it belongs to the Subshell virtual display helper instead of leaving you to guess from an icon, its **Minimize helper** item follows whether the panel is actually on screen, and the consent item turns itself into **Enable helper** once you disable the helper, raising the panel so you can read what you agreed to. It used to offer only Disable, greyed out once consent was off, which left the settings window as the only way back to running.

**Quit helper** asks first, from the panel and from the menu bar's menu alike, and the question answers the part the button never said: watching stops now, your consent and the macOS permissions stay, and the helper
says what it will actually do next: it returns at sign-in only if you registered
it there, and the panel's **Stop at sign-in** button, not disabling, is what
takes it off that list. Quitting also stopped revoking your consent, which it had been doing silently along with unregistering the login item: an app's Close button destroying the very permission the panel exists to collect, so every reopen started from Disabled.

Enabling the helper no longer adds it to macOS's sign-in list, and disabling no longer takes it off. **Start at sign-in** is its own row with its own button, and the row says which of the three states macOS actually reports, registered, waiting for your approval, or not registered at all. Reading the last two as "approved" is what let a turned-off sign-in start keep claiming it starts at sign-in. The panel re-reads the machine every five seconds while it is open, in every run-loop mode and with App Nap held off, so a switch you flip in System Settings changes what you see without reopening anything.

Discarding the last virtual display on a Mac also hands the pointer back. A viewer driving that screen leaves the real cursor on a display that no longer exists, which looked like a vanished cursor to whoever was sitting at the machine. A virtual screen can outlive the helper, so the rescue runs on any helper start as well as on teardown: whoever opens the panel next finds their cursor back where they can see it.
12 changes: 12 additions & 0 deletions .changeset/website-virtual-displays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@internal/website": patch
"@internal/docs": patch
"@internal/server": patch
"@internal/desktop-server": patch
---

Add a virtual-display section to the website with a screenshot and Linux and macOS setup links. Refresh the display screenshot in the guide, and show SSH owner-only restrictions in gold like other node settings.

Refresh the website-preview recording with a neutral demo path.

Keep the phone demo at the same size when switching to the wider website layout.
Loading
Loading