Skip to content

Expose a sandbox port from the sandbox page - #1

Merged
vitramir merged 2 commits into
mainfrom
sandbox-ports
Aug 10, 2026
Merged

Expose a sandbox port from the sandbox page#1
vitramir merged 2 commits into
mainfrom
sandbox-ports

Conversation

@vitramir

Copy link
Copy Markdown
Contributor

Needs agynio/expose#37 — without it a browser cannot expose or remove a port at all.

Starting a dev server in a sandbox meant dropping into the shell, running a command, and reading the URL out of its output. Nothing on the page said what the sandbox was serving or where.

Shape

Ports live in a header chip beside the clocks, not a strip of their own. An address is derived from the sandbox, so every port shares one host — factoring it out collapses a row to its port, and the whole set then fits in the header. The terminal keeps the vertical space a second strip was taking.

The chip is present whenever the sandbox is running, reading "None" with nothing exposed. Hiding it until the first port would be cleaner and would leave nobody a way to find the feature.

Behind it, a popover: the host once, one row per port (link, copy, remove), the expose control, and the reachability notice.

The expose control is a ghost row

Two boxy controls in a popover of rows read as pasted in. At rest it matches the rows above it; clicking turns the row itself into an inline field that submits on Enter and cancels on Escape.

States worth reviewing

  • Re-exposing an existing port is not an error. The service returns the record it already has, so the editor closes and that port pulses rather than an error appearing for something that worked.
  • A refusal stays in the row with a retry, rather than becoming a toast that outlives the popover and detaches from what caused it.
  • The empty popover drops the host line and the notice — neither says anything until there is an address.
  • No live updates. Expose publishes no notification events, so a port exposed inside the shell surfaces on the next refetch: mount, the app's own mutations, or the workload changing.

57 tests, lint and typecheck clean. Deployed from source and checked in the browser.

Spec: agynio/architecture#179 (merged).

Starting a dev server in a sandbox meant dropping into the shell, running a
command, and reading the URL out of its output. Nothing on the page said what
the sandbox was serving or where.

A Ports strip sits above the terminal: a row per exposed port with its address
as a link, copy and remove beside it, and an input to expose another. Only while
there is a workload -- exposures belong to it, so a stopped sandbox has none.

Ports are keyed by workload rather than by sandbox, because a restart makes them
a different set even though the sandbox is the same.

The list is not live. Expose publishes no notification events, so a port exposed
from inside the shell surfaces on the next refetch -- mount, this component's own
mutations, or the workload changing.

parsePort refuses anything that is not purely digits rather than coercing: 3000x
is a typo, and exposing port 3000 for someone who meant something else gives
them no way to tell from the result.

The strip states the two things the page cannot otherwise tell you -- the address
resolves only on an enrolled device, and anyone else on the platform network can
reach it.
The strip repeated the full host on every row, which is redundant: an address is
derived from the sandbox, so every port shares one host. Factoring it out
collapses a row to its port, and the whole set then fits in the header beside
the clocks -- so the terminal keeps the vertical space a second strip was
taking.

The chip is present whenever the sandbox is running, reading "None" with nothing
exposed. Hiding it until the first port would be cleaner and would leave nobody
a way to find the feature.

Expose is a ghost row rather than an input and a button: two boxy controls in a
popover of rows read as pasted in. At rest it matches the rows above it; clicking
turns the row itself into an inline field that submits on Enter and cancels on
Escape.

Re-exposing an existing port is not an error. The service returns the record it
already has, so the editor closes and that port pulses instead of an error
appearing for something that worked.

A refusal stays in the row with a retry rather than becoming a toast, which
would outlive the popover and detach from what caused it. The empty popover
drops the host line and the reachability notice -- neither says anything until
there is an address.
@vitramir
vitramir merged commit da7d28a into main Aug 10, 2026
1 check passed
@vitramir
vitramir deleted the sandbox-ports branch August 10, 2026 05:15
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