Skip to content

Say when a session's machine is offline, and keep its last screen - #168

Merged
Alexgodoroja merged 1 commit into
mainfrom
say-when-the-machine-is-offline
Sep 16, 2026
Merged

Alexgodoroja merged 1 commit into
mainfrom
say-when-the-machine-is-offline

Conversation

@Alexgodoroja

Copy link
Copy Markdown
Collaborator

The bug

A viewer can be perfectly connected to a session whose machine is not there, and be shown nothing at all.

Reported as "the terminal fails to appear, both in the app and in the standalone link". Reproduced on https://shell.online/s/2cMN…: the relay says {"exists":true,"status":"disconnected"}, two viewers are attached, the header dims a dot to "Offline", and the terminal area is a blank white rectangle with no explanation. The machine hosting it had rebooted; the session lives on the relay for the rest of its twelve hours, so every viewer since gets that page.

The relay has always reported the machine's state on every status message. Neither client acted on it:

  • Standalone page — showed the grey "Offline" dot and nothing else. It has notices for a missing session, an ended one and a full one; not for a machine that is away.
  • App panehandleControl read only status === "exited" and dropped the rest, so the pane stayed on connected and drew an empty terminal with no banner at all.

That is what a reboot, a closed lid, a flat battery or a lost network looks like from the browser today.

The fix

One module, shared/host-presence.ts, vendored into the app the way protocol.ts and terminal-grid.ts already are, so both surfaces say the same thing in the same words.

Relay (worker/index.ts)

  • Reports hostLastSeenAt and lastScreenAt alongside the status.
  • Keeps the last full screen a host sent, chunked under one storage prefix because a snapshot (up to 512 KB) is larger than a Durable Object value (128 KiB), and replays it to a viewer that arrives while the machine is away. For an encrypted session those bytes are ciphertext it cannot read: the opcode is authenticated as associated data, so the frame is stored and replayed untouched. It is deleted with the session.
  • A host only produces a screen when asked, so the alarm asks for one every five minutes on behalf of viewer 0, which no viewer ever is. That caps staleness for the cost of one snapshot per five minutes of a live session. Writes are throttled so sixteen people opening a link does not write sixteen screens.

Both viewers

  • Waiting for a machine that has never connected, or temporarily offline with when it was last seen and how old the screen below it is.
  • A kept screen is dimmed so it cannot be read as a live one.
  • It clears itself when the machine comes back.

Retention note

The relay now holds the last screen of each session, encrypted and unreadable by it, for as long as that session exists (12 hours, or 30 days for a persistent one). For a --no-e2ee session those bytes are plaintext, which is the tradeoff that flag already makes in flight.

Verification

Unit: tests/host-presence.test.ts (9) for the copy and the elapsed-time wording, and four cases in app/src/terminal/connection.test.ts for the status mapping. The connection tests fail on main and pass here.

End to end against a local relay and a real CLI:

  1. Live session renders, no notice.
  2. Host SIGKILLed — the open viewer keeps its screen, dimmed, under "Temporarily offline… last seen just now".
  3. A viewer arriving afterwards gets the kept screen and the notice, where it used to get a blank page.
  4. A restarted persistent host clears the notice and the terminal goes live again.
  5. A session whose machine never connected shows "Waiting for this machine".

Checked in both themes and in the app pane as well as the standalone page.

No CLI change, so no release is needed; cmd/ and internal/ are untouched.

A viewer could be perfectly connected to a session whose machine was not
there, and be shown nothing at all. The relay has always reported the
machine's state on every status message; neither client acted on it. The
standalone page dimmed a dot in the header to "Offline" and left the
terminal blank, and the app pane ignored every status except "exited", so
it stayed on "connected" and drew an empty screen with no banner. Both
read as a terminal that failed to load rather than a laptop that closed.

Reproduced with a session whose host was killed outright: it lives on the
relay for its remaining twelve hours, and every viewer since gets a blank
page. That is what a reboot, a lid, a dead battery or a lost network looks
like from the browser today.

Both surfaces now say it, in the same words, from one module:

- The relay reports hostLastSeenAt and lastScreenAt alongside the status.
- The relay keeps the last full screen a host sent, chunked under one
  storage prefix because a snapshot is larger than a Durable Object value,
  and replays it to a viewer that arrives while the machine is away. For an
  encrypted session those bytes are ciphertext it cannot read: the opcode
  is authenticated as associated data, so the frame is stored and replayed
  untouched. It goes with the session when that expires.
- A host only produces a screen when asked, so the alarm asks for one every
  five minutes on behalf of viewer 0, which no viewer is. That caps how
  stale a kept screen can be at the cost of one snapshot per five minutes
  of a live session. Writes are throttled so a room filling up does not
  write one per person arriving.
- The notice says which it is: waiting for a machine that has never
  connected, or temporarily offline with when it was last seen and how old
  the screen below it is. A kept screen is dimmed so it cannot be mistaken
  for a live one, and it all clears itself when the machine comes back.

Verified end to end against a local relay: a live session renders, a
SIGKILLed host turns the open viewer's screen into a dated kept screen with
the notice, a viewer arriving afterwards gets both where it used to get a
blank page, and a restarted persistent host clears the notice and goes live.
@Alexgodoroja
Alexgodoroja merged commit cffc4f6 into main Sep 16, 2026
15 checks passed
@Alexgodoroja
Alexgodoroja deleted the say-when-the-machine-is-offline branch September 16, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant