tade — the away view, and the switches in front of it

blog /

The away view, and the switches in front of it

One page your own machine serves to a phone you paired at it, what it took to make acting safe, and every setting you have to find to turn any of it on.

The Tade project·9 min read·awaysafetysetup

Tade is a terminal program with no daemon, no account and no cloud component, and the thing people ask for anyway is to see it from the sofa. So there is one listener now, and it is the only one there has ever been: a page the window serves from your own machine to a device you paired by scanning a code in it.

It is built, tested, and not in the published package: tade-sh 0.1.1 was packed on 2 October and the v0.1.1 tag carries 1,146 files, not one of which is under packages/web/. Until a release carries it, the way to it is the source rows at the foot of Install.

This is the setup, in the order somebody actually does it, with the parts that have no command said plainly rather than left out.

1. Turn it on

tade web on          # loopback only: http://localhost:7654/
tade web status      # what is set, what is listening, who is paired
tade web off         # the setting, and every credential

tade web on writes one setting and tells you it takes effect the next time Tade starts, which is honest rather than half-done: the listener comes up with the window, and the epoch a connected phone holds is per server start, so turning it on mid-session would mean tearing down and rebuilding something somebody is looking at.

tade web off is two acts under one word. Off that left credentials behind is not off — a setting somebody turns back on next week would otherwise let every phone that was ever paired straight back in.

2. Decide who can reach it

away-on-the-network · paneldrawn by Tade
The same panel with the away view bound to every interface: what anybody on the same wifi can read, said above the code, with the way out in the same breath.

loopback and lan are a second decision and not a third option on the first one, because on and on the network being one control is how a laptop in a café ends up serving its control room to the café. The sentence above the code is the whole of what a person deciding needs:

On your network this is plain HTTP: anybody on the same wifi can read every page it serves and the session cookie with it. tailscale serve localhost:7654 gives a phone HTTPS while Tade keeps listening on this machine alone, which is a smaller surface than this one.

A LAN bind is also a read-only transport, and that is enforced rather than advised: a credential that crossed a network in the clear never buys an act, whatever is turned on later, and that is re-asked at the moment of every act rather than decided once at pairing.

If you use a proxy, its name goes in surfaces.web.trusted_hosts. No forwarded header is ever read. A deliberate proxy is trusted by being named by a person; a header is trusted by whoever sent it.

3. Pair a device, at the machine

away-pairing · paneldrawn by Tade
The away view in the window: the addresses it is bound to, a code to scan, and under it that an agent on this machine could pair itself, with Disconnect everything beside it.

There is one way in and it is the panel above: press its key for a code, scan it with the phone’s camera, and answer the question the window asks you.

tade web pair cannot mint a code and says so, with exit 1. A pairing ticket lives in the memory of the process whose server has to claim it, and there is no channel from a shell into the window. A typed URL cannot pair either — the ticket is in the code’s fragment — so somebody who types the address is told to scan. A six-digit typeable code is not built: it is a second kind of credential with its own threat model, and it is outstanding rather than decided against.

The ticket is burned on the claim, before anybody at the machine is asked. That is the one ordering where a replay, a concurrent attempt, a refusal, a deadline and a thrown error all find nothing.

The sentence under the code in that panel is the one nobody wants to write:

an agent on this machine could pair itself

0600 keeps the config and the device list from other people, and an agent is not another person — agents run as you, and nothing sandboxes them. What stands against it is that the panel lists every device there is, every act is journalled under the device that took it, and Disconnect everything needs no network at all.

4. Decide what a device may read

A paired device gets names, counts and states, and nothing anybody wrote. That is the default and it is not a degraded one: a page granted nothing still answers the question the whole surface exists for — is anything waiting for me.

Everything else is a grant, made at the machine, per device. Eleven of them: titles, intent, notes, findings, spend, reviews, accounts, talk, requests, material, workflows. Two devices with two scopes really are two projections built per device rather than one filtered afterwards, and the project half of a task id is the boundary — so no project is never read as every project.

The last two of those are the ones to think about, because they carry text a stranger wrote: requests is what each outside request calls itself, and material is the request as it arrived. Neither is scrubbed, for the reason a note is not — rewording somebody’s request is the same lie as rewording a note — and what stands instead is the grant, a budget, the heading that travels with the words, and a page that builds no markup at all: no innerHTML, no Markdown renderer, no unsafe-inline.

5. Decide what a device may do — and this is not a terminal step

Reading is the default. Acting is a third decision, and it is the one with no command-line writer at all.

# ~/.tade/config.yaml
surfaces:
  web:
    enabled: true
    acting: true                 # the eight verbs
    orchestrator: false          # talking to Tade: a fourth decision
    drafts: false                # saving a draft workflow: a fifth
    install: false               # a shell on the phone, and three keys under it
    trusted_hosts: ['studio.ts.net']

tade web on and tade web off write enabled, and --lan writes bind. The other ten keys in that group have no command at all: tade config reads (--json, --check) and does not write, there is no tade config set and no tade settings. They are set on the window’s own Settings page, by telling the orchestrator, or in a text editor. That is defensible — these are the keys that widen who can reach the control room, and the rule in this repository is that the thing granting authority is a person’s act — but Settings page or text editor is not something a setup document can put in a command block, so it is a paragraph instead.

With acting on, and then only for a device you widened in the away panel, and then only over this machine itself or an https name you named, there are eight verbs:

verb what it does
answer allow or deny what an agent is already waiting on
steer say something to an agent that is already running
queue pause, resume, reorder or let queued work start
park set a task aside, or pick it back up
note write something down about a task, word for word
context add a line to what a task’s agent is told
done mark work finished, with two presses and a literal confirm
intake approve a request a grant here already allowed

And nothing else. Not a setting, not a credential, not a command, not a new agent, not a push, not a merge, not a check overruled, not a review answered, not a workflow published, not a grant given to any device. Those are not flags that happen to be off: with acting off the acting routes are not in the route table at all, so a crafted call is the same 404 as a path nobody built, and the things in the paragraph above have no route in any configuration.

Three details are load-bearing:

  • steer cannot start an agent. It is a message into a conversation that is already running, and where no agent is, it is refused.
  • done needs two presses and a literal confirm: true in the body, and no other verb stands in for it.
  • A verb is a target plus the state it expects. An idempotency key is a fast path and never the guarantee: what makes a replayed request safe after a restart is that the act names what it assumed and the window re-checks it at the moment of acting.

Talking to Tade is a fourth switch and deliberately not implied by acting. A verb names a target and the state it expects; a message is free text to a model that holds tools. While Tade is answering one, its own tools are narrowed to reading that device’s own view of your work plus whatever that device was granted, and every other tool it has is refused in code — including the door that changes a setting, which is not reachable from a remote turn at all. That last part is what defeats the obvious attack: a message that names a setting you happened to name last week.

6. A shell on the phone, if you want one

Installing is a sixth switch, and the first whose effect is on somebody else’s hardware. Four things about it are worth knowing before turning it on, and none of them is discoverable by trying it.

It needs a secure origin. localhost is one; a private address on your own wifi is not, whatever it is bound to, and the browser refuses without explaining itself. tailscale serve localhost:7654, or a reverse proxy whose name is in trusted_hosts, is what gives a phone one — and the device pairs again there, because a session is bound to the exact host it was minted on and nothing is carried between origins.

What it may keep on its own disk is counts. How many are working, how many want you, how many are queued, and the moment they were true. No title, no intent, no note, no request, no name, and nothing can be acted on from it. It is still a copy on a phone: signing that device out clears it the next time that device reaches this machine, and what was already downloaded cannot be taken back.

A notification says how many and nothing else, unless you turn details on. Every one of them travels through the push service that phone’s browser chose — Apple’s, Google’s, Mozilla’s — which sees that something was sent to that subscription, when, and how big it was, and cannot read what it says. They are sent by the window on its own beat, so a closed window and a sleeping machine send nothing, and nothing is caught up: a window that comes back says what is true now rather than six things about last night.

Turning installing off is something the device has to be told. A 404 on a worker’s script does not remove it — the service worker specification was asked to unregister on 404 and 410 and decided against it in 2017, because a bad afternoon at a data centre would otherwise unregister everybody. So what is served instead is a worker that deletes every cache of Tade’s and removes itself, the next time that device reaches this machine. A device that never comes back keeps what it had, and what it had is the page: no project, no task, no note, no figure, and no way to read one without a session.

One thing this post will not tell you is that any of that has been read by a real phone. The served bytes are driven through install, upgrade, rollback and uninstall by a test, the committed icons are decoded, and the offline allow-list is asserted — and no notification has ever been delivered to a device from this repository. That is the difference between a tested surface and a field-tested one, and the second one is somebody’s afternoon with a phone rather than another test.

The thing that is true however many switches are on

away-pairing · paneldrawn by Tade
The panel again: on, on this machine alone, with every device already paired and what each one may read.

The away view answers only while the window is open. Close Tade and nothing is listening, which is also why closing Tade is harmless: there is no relay, no tunnel and no queue for later anywhere in it, and a bookmark tapped with Tade closed is the browser’s own connection error rather than an empty control room.

A sleeping laptop serves nothing, and the page never pretends otherwise. When a look fails, rows keep their state, their age freezes at the moment it was last true, nothing becomes zero, and the page says what it actually knows:

unreachable since 11:07 — the machine may be asleep, or Tade may be closed

Which is a third answer, and the reason it is worth having: an empty list and a machine that has gone quiet are two different things to do next.

If the thing you want from a phone is to approve the work that arrived while you were out, the door it arrived through is the other half of this.

blog← olderThe Tade project│2026-10-10│9 min│top