Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
201b3d1
feat(web): support hosted pairing links
juliusmarminge Apr 27, 2026
9033cb3
fix(web): keep hosted pairing to secure endpoints
juliusmarminge Apr 27, 2026
94acd22
docs(remote): document hosted pairing constraints
juliusmarminge Apr 27, 2026
9813692
fix(web): treat hosted app as static
juliusmarminge Apr 27, 2026
e227b78
feat(remote): add advertised endpoint registry
juliusmarminge Apr 27, 2026
19ef988
docs(remote): describe advertised endpoint selection
juliusmarminge Apr 27, 2026
6c4db9a
fix(web): hide endpoint rows when network access is disabled
juliusmarminge Apr 27, 2026
e11c88d
feat(web): make advertised endpoint defaults explicit
juliusmarminge Apr 27, 2026
88006a4
feat(remote): add advertised endpoint registry
juliusmarminge Apr 27, 2026
32be0a0
feat(desktop): add tailscale endpoint addon
juliusmarminge Apr 27, 2026
9d50f37
fix(web): dedupe advertised pairing helpers
juliusmarminge Apr 27, 2026
09f3726
docs(remote): document tailscale endpoint add-on
juliusmarminge Apr 27, 2026
f5624f6
fix(desktop): include port in tailscale https endpoint
juliusmarminge Apr 27, 2026
b124bf8
Add remote SSH environment launch support
juliusmarminge Apr 14, 2026
a6dbab6
Harden remote SSH environment recovery
juliusmarminge Apr 14, 2026
c349ef5
Respect desktop release channel for SSH bootstrap
juliusmarminge Apr 15, 2026
d2d82f5
Pin nightly SSH bootstrap to desktop version
juliusmarminge Apr 15, 2026
5ec3e43
Address SSH launch review feedback
juliusmarminge Apr 15, 2026
afb00cf
Use nightly remote T3 CLI in development
juliusmarminge Apr 15, 2026
5f14320
Improve SSH launch diagnostics
juliusmarminge Apr 15, 2026
30bdfb4
Tighten SSH setup dialog scrolling
juliusmarminge Apr 16, 2026
4b78804
Format settings and IPC imports
juliusmarminge Apr 17, 2026
f67c6dd
Extract desktop SSH bridge into dedicated class
juliusmarminge Apr 17, 2026
140ba2a
Extract SSH launch scripts
juliusmarminge Apr 17, 2026
d1cb067
Simplify ssh askpass to require cached secret
juliusmarminge Apr 17, 2026
b6faa16
Fix PR CI failures
juliusmarminge Apr 17, 2026
2fed370
docs(remote): document desktop ssh launch
juliusmarminge Apr 27, 2026
7f60bbf
Delay SSH tunnel disposal until pending setup settles
juliusmarminge Apr 28, 2026
1d6d98b
Add Tailscale Serve pairing support
juliusmarminge Apr 28, 2026
2ece612
Harden pairing and SSH prompt flows
juliusmarminge Apr 28, 2026
d586b5f
Handle malformed custom endpoints in pairing flow
juliusmarminge Apr 28, 2026
713e7ef
Update release smoke to include tailscale package
juliusmarminge Apr 28, 2026
83f7489
Extract SSH helpers into shared package
juliusmarminge Apr 28, 2026
8c487d8
Split SSH helpers into dedicated modules
juliusmarminge Apr 28, 2026
4a954dd
Gate pair route fallback to hosted static app
juliusmarminge Apr 28, 2026
6f0810b
Disable Tailscale Serve on server shutdown
juliusmarminge Apr 28, 2026
8117b65
Refactor SSH desktop pairing onto shared runtime
juliusmarminge Apr 29, 2026
7b50a39
Refine hosted pairing and SSH connection flows
juliusmarminge Apr 29, 2026
db06e1f
Add hosted SSH pairing state handling
juliusmarminge Apr 29, 2026
860d633
Log SSH tunnel lifecycle and surface bootstrap failures
juliusmarminge Apr 29, 2026
9ec9378
Improve SSH tunnel failure diagnostics
juliusmarminge Apr 30, 2026
b7f144b
Improve hosted pairing and reconnect flows
juliusmarminge Apr 30, 2026
cb6507e
Update settings panel tests for pairing and SSH flow
juliusmarminge Apr 30, 2026
ae941e6
Remove hardcoded dev remote server path
juliusmarminge Apr 30, 2026
7125fa1
Simplify hosted pairing and Tailscale serve cleanup
juliusmarminge Apr 30, 2026
7cbca67
Cancel stale saved environment reconnects
juliusmarminge Apr 30, 2026
5f3fd6a
Surface keybindings in server config updates
juliusmarminge Apr 30, 2026
d7eb1b8
Surface version mismatch warnings in composer and settings
juliusmarminge Apr 30, 2026
b177f59
Animate composer banner dismissals
juliusmarminge Apr 30, 2026
6747e7f
Handle missing hosted pairing config and disable tailscale serve
juliusmarminge May 1, 2026
46278bb
Address remaining PR review feedback
juliusmarminge May 1, 2026
27be1e1
Reuse live remote T3 server for SSH launch
juliusmarminge May 1, 2026
d5f5d82
Persist version mismatch dismissals per environment
juliusmarminge May 1, 2026
2a220f1
Format desktop main process entrypoint
juliusmarminge May 1, 2026
cbd4398
Improve SSH bootstrap diagnostics
juliusmarminge May 1, 2026
50bd5a1
Add chat header coverage for hosted pairing UI
juliusmarminge May 1, 2026
4e44973
Bootstrap SSH auth through live server
juliusmarminge May 1, 2026
d4712be
Revert "Bootstrap SSH auth through live server"
juliusmarminge May 1, 2026
dad2a07
Add SSH pairing diagnostics
juliusmarminge May 1, 2026
8710bdc
Fix SSH pairing diagnostics JSON parsing
juliusmarminge May 1, 2026
973f0cb
Use external SSH server auth store for pairing
juliusmarminge May 1, 2026
fb76060
Use default T3 home for SSH-launched servers
juliusmarminge May 1, 2026
e30876e
Trim SSH pairing diagnostics
juliusmarminge May 1, 2026
85f4df7
Address SSH and pairing review feedback
juliusmarminge May 1, 2026
2ab5de8
Retain thread subscriptions across reconnects
juliusmarminge May 1, 2026
ae22e8b
Handle stale tunnel and SSH rollback paths
juliusmarminge May 1, 2026
6bf71e2
Reconnect streams after browser resume
juliusmarminge May 1, 2026
36c2631
Add RPC request and heartbeat tracking
juliusmarminge May 1, 2026
74f7f0d
Refine mobile run context and banner alignment
juliusmarminge May 2, 2026
43b0d6d
Simplify mobile branch toolbar icon
juliusmarminge May 2, 2026
99c0544
Fix mobile environment icon spacing
juliusmarminge May 2, 2026
58277c6
Fix release smoke patched dependencies
juliusmarminge May 2, 2026
f5f8127
Fix web API compatibility after rebase
juliusmarminge May 2, 2026
4b42bf1
Merge branch 'main' into t3code/hosted-pairing-ui
juliusmarminge May 3, 2026
d6afc6d
Merge origin/main into t3code/hosted-pairing-ui
juliusmarminge May 4, 2026
86e1b67
Fix PR review comments
juliusmarminge May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .docs/remote-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Examples:

A known environment may or may not know the target `environmentId` before first successful connect.

In the hosted web app, known environments are browser-local. A hosted pairing URL can create the saved entry, but it does not give the hosted app a server-side control plane or a copy of the session state.

### AccessEndpoint

An `AccessEndpoint` is one concrete way to reach a known environment.
Expand All @@ -108,6 +110,67 @@ A single environment may have many endpoints:

The environment stays the same. Only the access path changes.

### AdvertisedEndpoint

An `AdvertisedEndpoint` is a server or desktop-authored candidate endpoint for an environment. It is how the backend tells the client which URLs may be useful for pairing and reconnecting.

`AdvertisedEndpoint` is deliberately narrower than the full access model:

- it describes a concrete HTTP and WebSocket base URL pair
- it can mark the endpoint as default, available, or unavailable
- it includes reachability hints such as loopback, LAN, private, public, or tunnel
- it includes compatibility hints such as whether the endpoint can be used from the hosted HTTPS app

Clients should treat advertised endpoints as hints, not as proof that a route works from the current device. The final connection attempt still decides whether the endpoint is reachable.

The UI presents one default advertised endpoint in the network-access summary and keeps the rest behind an expandable advanced list. The default controls pairing QR codes and primary copy actions. Users can override it, but that override is a UI preference, not backend configuration.

Persist the override by stable endpoint kind rather than raw URL whenever possible. For example, a LAN endpoint should be stored as the desktop LAN endpoint preference, not as `192.168.x.y`, because the address can change when the user switches networks. Provider endpoints should use provider-specific stable keys such as Tailscale IP or Tailscale MagicDNS HTTPS. Custom endpoints may fall back to their concrete identity.

When no user default is saved, endpoint selection should prefer:

1. endpoints compatible with the hosted HTTPS app
2. explicitly default endpoints
3. non-loopback endpoints
4. loopback endpoints only for same-machine clients

This keeps endpoint discovery centralized without making any one provider, such as Tailscale or a future tunnel service, part of the core environment model.

### Endpoint providers

Endpoint providers are add-ons that contribute advertised endpoints for the current environment.

The provider boundary is intentionally outside the core environment model:

- core owns `ExecutionEnvironment`, saved environments, pairing, and connection lifecycle
- providers discover or synthesize endpoints
- providers return normalized `AdvertisedEndpoint` records
- the UI and pairing logic select from those records without knowing provider-specific commands

The first provider is Tailscale. It can discover Tailnet IP and MagicDNS addresses from the local machine and publish them as additional endpoint candidates. Future providers, such as a hosted tunnel service, should plug into the same shape rather than adding a separate remote environment path.

Provider-specific confidence should remain a hint. A Tailscale endpoint still needs a successful browser or desktop connection before the client treats it as connected.

### Hosted pairing request

A hosted pairing request is a bootstrap URL for the static web app, not a transport.

Example:

```text
https://app.t3.codes/pair?host=https://backend.example.com:3773#token=PAIRCODE
```

The hosted app reads the `host` parameter and pairing token, exchanges the token directly with that backend, then saves the resulting environment record in browser local storage.

Important constraints:

- the hosted app does not proxy HTTP or WebSocket traffic
- the backend must still be reachable directly from the browser
- HTTPS pages can only connect to HTTPS/WSS backends
- HTTP LAN endpoints should keep using direct desktop or CLI pairing URLs
- the token belongs in the URL hash so it is not sent to the hosted app origin

### RepositoryIdentity

`RepositoryIdentity` remains a best-effort logical repo grouping mechanism across environments.
Expand Down Expand Up @@ -151,6 +214,8 @@ Benefits:
- no client-specific process management required
- best fit for hosted or self-managed remote T3 deployments

Browser security rules are part of this access method. A hosted HTTPS web client can connect to `wss://` backends, but it cannot connect to plain `ws://` or `http://` LAN backends because that would be mixed content.

### 2. Tunneled WebSocket access

Examples:
Expand All @@ -170,6 +235,8 @@ This is especially useful when:
- mobile must reach a desktop-hosted environment
- a machine should be reachable without exposing raw LAN or public ports

Tailscale-backed access sits here architecturally even though the current implementation is endpoint discovery rather than a T3-managed tunnel. It contributes private-network endpoints and lets the existing HTTP/WebSocket client path do the actual connection.

### 3. Desktop-managed SSH access

SSH is an access and launch helper, not a separate environment type.
Expand All @@ -185,6 +252,8 @@ After that, the renderer should still connect using an ordinary WebSocket URL ag

This keeps the renderer transport model consistent with every other access method.

The desktop main process owns the SSH bridge because it can spawn local SSH processes, manage askpass prompts, write temporary launch scripts, and clean up forwards. The renderer receives a saved environment record and connects through the forwarded URL; it should not need SSH-specific RPC paths for normal environment traffic.

## Launch methods

Launch methods answer a different question:
Expand Down Expand Up @@ -227,6 +296,15 @@ The recommended T3 flow is:
4. Desktop establishes local port forwarding.
5. Renderer connects to the forwarded WebSocket endpoint as a normal environment.

The saved environment should remember that it was created by desktop SSH launch only for reconnect and lifecycle UX. That metadata should not change the server protocol or the environment identity model.

Failure handling should be explicit:

- SSH authentication failure should surface before any environment is saved
- remote launch failure should include remote logs or the launcher command output when available
- forwarded-port failure should leave the saved environment disconnected rather than falling back to an unrelated endpoint
- reconnect should attempt to restore the SSH bridge before reconnecting the normal WebSocket client

### 3. Client-managed local publish

This is the inverse of remote launch: a local T3 server is already running, and the client publishes it through a tunnel.
Expand Down Expand Up @@ -267,6 +345,8 @@ T3 already supports a WebSocket auth token on the server. That should become a f

For publicly reachable environments, authenticated access should be treated as required.

Hosted pairing should be treated as a client-side convenience only. The hosted app must not receive pairing tokens through query parameters, must not store pairing state server-side, and must not imply that an HTTP backend is safe or reachable from an HTTPS browser context.

## Relationship to Zed

Zed is a useful reference implementation for managed remote launch and reconnect behavior.
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ apps/web/src/components/__screenshots__
__screenshots__/
.tanstack
squashfs-root/
.vercel
Loading
Loading