Files
platform/docs/sendoff.md
T
pastilhasandClaude Fable 5 8b4a5b3b0b docs: SendOff — files land in the user's tree, a push says where
Spec from the mobile side, v2. One endpoint (POST /api/sendoff/send — the one
sanctioned door into another user's filesystem, sender identity from the token),
a push category whose route deep-links the Files apps, and a minimal devices
registry for the send-to pickers. The mobile half is already shipped against
this and degrades silently until it exists.

Lives here rather than in COMMS because it is a spec to implement, not a
message: the repo is where it can be corrected as the implementation teaches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 02:02:38 +01:00

4.0 KiB

SendOff — files land in the user's own tree, and a push says where

From: mobile · Date: 2026-08-23 · v2, replacing this morning's spec entirely. The v1 design (staging area, transfer/target/file tables, ack lifecycle, TTL cleanup) is withdrawn — Andre's observation kills it: every user already has a filesystem on the server, a file browser on the web, and a Files app on the phone. SendOff is not a new storage system; it is a folder convention, one endpoint, and a push.

The convention

~/Downloads/sendoff/from-<username>/   ← a file another USER sent you
~/Downloads/sendoff/<machine>/         ← a file you sent yourself, aimed at a named device

That is the whole data model. Nothing to ack, nothing to expire, nothing to clean up — the files are ordinary files in the recipient's tree, visible in every file UI that already exists, managed like anything else they own.

The endpoint

POST /api/sendoff/send
  multipart: any number of `file` parts + a `meta` part:
    { "toUser": "<username>" }        — cross-user: writes into THEIR tree, from-<sender>/
    { "machine": "<device name>" }    — self: writes into own tree, <machine>/
  → { path: "Downloads/sendoff/…", files: [{ name, size }] }

Server-side because of the cross-user case: /api/file-browser/upload correctly writes only the caller's own tree, and that must not change. This endpoint is the one sanctioned door into another user's filesystem, and it writes ONLY under Downloads/sendoff/from-<sender>/ — the sender's identity comes from their token, never from the payload, so the folder name cannot lie about who sent it. Filenames sanitised, collisions suffixed (1) — never overwritten; a second send of the same name is a second file.

Whether user A may send to user B at all is a platform policy decision (everyone on the server? a grant?) — flagging it rather than deciding it here.

The push

On write, push to the recipient's devices through the existing pipeline:

{ "officer": { "c": "officer/core:sendoff.landed", "path": "Downloads/sendoff/from-andre" } }
alert: "photo.jpg from andre" / "3 files from omega"

The push catalogue's route for the category:

officer://files?path={path}

Mobile opens routes from the catalogue verbatim (openNotificationRoute), and Officer now parses officer://files?path=… into the Files app at that folder. The web can deep-link its file browser the same way.

For a machine-targeted send, push only to that machine's device if it can be identified; to all the user's devices otherwise — the path in the folder name already says who it was for.

Devices

The machine name needs a picker on the sending side, so a minimal registry survives from v1:

devices: id (client uuid), user_id, name, platform, last_seen_at
POST /api/devices/register { id, name, platform }   (upsert; called on every app start)
GET  /api/devices → { devices }                     (the "send to my…" picker)
PATCH /api/devices/:id { name } · DELETE /api/devices/:id

No link to transfers — nothing references a device row except the picker and, best-effort, push targeting.

Web UI

One send form: files + target (one of my machines / another user), POST, done. Received files need no UI at all — they are in the file browser already.

What mobile already does (shipped against this)

  • Registers the device on start (silent failure until the endpoint exists).
  • Parses officer://files?path=… and opens the Files app at that path — this is the piece that makes the push tap land somewhere.
  • SendOff screen: the device's name (the folder senders will aim at), and an explanation of where arrivals land. Sending FROM the phone comes later, likely through the existing share sheet.

Out of scope

Receiving in the browser (already exists — it is the file browser); cross-user permission model (platform decision); phone-side arbitrary-file sending (needs a document picker; the share sheet is the natural door and is a later step).