localhost routing
The daemon
tesser dev,use, andreviewstart tesserd in the background when it is not running.tesser daemon installmakes it a login service (seetesser daemon).- It listens on port 3000 for its own panel and status page, and on every port any of your boxes declares or depends on. A port means that port everywhere: nothing is redirected to another port of another box.
- It answers only requests addressed to a name that reaches this machine:
localhost, any*.localhostname, an IP address, the machine’s hostname or<hostname>.local, and its Tailscale name when Tailscale is running. Any otherHostgets a 403 that says to openlocalhost:<port>instead. A proxy of your own in front of tesserd must sendHost: localhost. - It answers your own machine and Tailscale peers, and refuses the rest of
the LAN unless started with
--allow-lan. - Your app is shown a
localhostaddress. A request tobox-<id>.localhost:<port>, the Tailscale name, the hostname or a LAN IP arrives withHost: localhost:<port>, andacme.box-<id>.localhostwithHost: acme.localhost:<port>. A browser’sOriginon any of these is rewritten the same way. A request to127.0.0.1keeps its ownHostandOrigin, except a WebSocket, which getslocalhost:<port>. - A write (any method but GET and HEAD) with no
Originand noSec-Fetch-Site, such as curl, sent to alocalhostname or a loopback IP, arrives withOriginset to itsHost. AnOriginfrom another site, ornull, passes through as sent.
Box addresses
- A box id such as
box_a1b2c3appears in its address asbox-a1b2c3, because a Content-Security-Policy does not accept a hostname with an underscore.box_a1b2c3.localhoststill reaches the same box. - Any
tessercommand that takes a box id also takes the box’s address, such asbox-a1b2c3.localhost:5173orhttp://box-a1b2c3.localhost:5173/. - Every port a box declares is served, such as
box-<id>.localhost:9229for a debugger. Opening a page on a port the box does not serve, such as 3000, sends the browser to the same path on the box’s own port. Any other request on that port answers 404. - The laptop serves the boxes it started, boxes opened from a share link, and
the org’s shared instances. A shared instance needs no share link: the
first request to its address adds it to the laptop, and opening it in a
browser wakes it.
tesser service lsshows each shared instance box. - A box the laptop cannot add, such as a teammate’s box nobody shared with
you, answers 404
NO_TARGETwith the reason, such as that the box does not exist or that you are not allowed to run on it. A browser shows the reason as the page heading. When the daemon cannot reach Tesser to ask, the request answers 503 and can be retried. - A request from another site’s page, such as an image on an unrelated site, never adds a box. Open the box’s address from the address bar first.
Dependency ports on a box address
- If a frontend’s manifest says
5000 = "api",box-<id>.localhost:5000is theapithat frontend talks to: its wire, the org’s shared instance, or your box ofapifrom the same worktree, whichever its routing table says now. - When that backend is one of your boxes, the request rides the tunnel the laptop already holds to it. Otherwise the daemon opens one through the frontend box’s loopback port.
- A dependency with nothing behind it answers 503 and names the
tesser wirecommand that fixes it.
A box page calling bare localhost
- A page on a box’s address stays with that box when it calls bare
localhost. Ifbox-<id>.localhost:3000fetcheshttp://localhost:8080, loads a script or image from it, or opens a websocket to it, the request goes wherebox-<id>.localhost:8080would. So a URL your app has baked in stays in that box’s worktree. - tesserd recognizes these requests by their
Originheader, orRefererwhen there is noOrigin. - A port outside that box’s declared and dependency ports goes to the focused box.
- Page loads on bare
localhostare never routed this way. Moving the focus does not reload these tabs or close their sockets.
From a box
- Code on any of your boxes can open
http://box-<id>.localhost:<port>for any instance box in the org and reach that box’s declared port, wired or not. This includes the org’s shared instances, so a shared box can call back into a dev box that handed it its address, such as for webhooks. - Workbenches declare no ports, so they have no address, and bare
localhost:<port>on a workbench is the workbench itself, which runs no server. A box holds addresses for at most 254 others. - A browser or curl on the box, which dials loopback with the name in
Host, is routed by name on a port some other box declares. - Nothing is bound on the box for this. Anything listening on the box wins its port, whether it started before or after: its own ports, its dependency ports, a database a test started. So from a browser on a box, a peer’s copy of a port this box listens on reaches this box.
- A port another box declares accepts a connection on this box’s loopback
even when nothing local listens, so a raw TCP probe (
nc -z,wait-on tcp:) cannot tell the port is free. Probe over HTTP. A port the target box does not declare is refused immediately. - The run command gets the box’s own address as
TESSER_BOX_URLand its id asTESSER_BOX_ID. <service>.localhost:<port>names whichever box is the org’s shared instance of that service now, so it survives a promote: the name moves to the new box and the old one drains. In-flight connections stay; a new connection after the promote lands on the new box. Use it when the other side should be the shared instance rather than one box, such as a webhook subscription that should survive deploys.
Focus
- Focusing a box focuses its whole dependency graph. Each box brings its declared ports and dependency ports. Shared dependencies are included even without a local target or SSH key for them: the daemon tunnels through one of your registered boxes.
- The last explicit focus wins for every port in its subtree. Unrelated ports stay where they were. Removing a focus reveals the one underneath.
- A missing binding fails with an explanation. It never falls back to an older focus.
- Shared nodes and cycles are visited once per focused root.
- Focus changes only the laptop’s routes. Dashboard A’s server process still calls the backend wired to A. To browser-test a backend-only branch B, run a dashboard B and wire it to backend B (see Wiring a dependency).
- A new
tesser devjoins the focus at the bottom, taking unclaimed ports. Re-running an existing box keeps its priority. Starting a service does not rewire its dependencies or launch other services.
tesser use
- The focus is saved even when the daemon is unavailable. The command then fails and says routing was not verified.
- It also fails when the focus is applied but its routes are unavailable. A ready route means the local tunnel accepts connections and the last known box status allows serving. It is not an application health check.
tesser use --jsonprints the same graph and status the panel reads.
TCP
HTTP on a port is routed by name. Anything else is passed through as bytes, so a database dependency is reachable from laptop tools on barelocalhost:5432, the same as from the box.
Cookies and sockets
localhostandbox-<id>.localhostare different sites to the browser, so aLaxor default cookie is not sent between them. A box’s address has its own cookies, so a tab there keeps a sign-in per box.- A
SameSite=Nonecookie set onlocalhostis sent with a box page’s calls to barelocalhost, and those calls reach the page’s box, not the focused one. - When the focus moves, the proxy closes the old box’s connections on the ports that moved, so a hot-reload socket stops feeding the page from the old branch. Pages on ports that did not move are not touched.
- A frontend that calls its API at a hardcoded
http://localhost:<port>and signs in with cookies should call its box’s address instead and setbox_address = true, or serve the API through the dev server’s proxy, so the page only talks to its own origin.
The panel
⌘⇧Uopens the panel. It lists the branches serving this port, each followed by its repository when the repository’s name differs from the branch’s. Hovering shows which branch you are on.- “Hide until tesserd restarts” takes the square off every page.
⌘⇧Ustill opens the panel, and expired AWS credentials still show. - Choosing a branch focuses the service serving this page and its whole dependency graph. Choosing the current branch again restores its graph after overrides.
- Ports that fail to connect are listed under the branches.
- A tab keeps the kind of address it was opened on. On a box address,
choosing a branch moves the tab to that branch’s box address on the same
path, and each box keeps its own cookies. On bare
localhost, the address stays and the page reloads onto the chosen branch. - A service with
box_address = truenever stays on barelocalhost: a page opened there moves to the box that port shows, on the same path. Only page loads move.