Scan to send · no app on the phone · files stay on your Wi-Fi

Phone → Computer Features

What it actually does

This page is the long version. Every mechanism here is described the way it works in the code — including the places where it degrades, gives up, or refuses to guess.

Computed locally

Hashes, metadata, date filtering — all on the phone or the computer. Nothing is sent anywhere else.

Written safely

Two-phase write, verify, then atomic rename. A failed transfer never leaves a half-written "real" file.

It says no

Where a thing isn't supported, it says so and gives you a next step. It doesn't pretend.

Entry

The QR code is drawn on your computer

It's just an address

The code contains a local URL like http://192.168.1.23:8787. It is rendered on your machine — no external QR service is contacted.

The same address is printed in large text underneath, so you can type it by hand if the camera won't focus.

It changes every time you start the app

The token inside rotates on each launch. A screenshot from yesterday won't get anyone in today.

⚠️ If you've set a password, the QR code carries no token at all — scanning it lands you on the pairing page, and you type the 6 digits like anyone else.

On the phone

Where you pick files

Direction is always phone → computer. Nothing here sends files the other way.

Three entries, always there

  • Photos — the camera roll, with thumbnails
  • Videos — same, but for clips
  • Any file — documents, zips, whatever the picker shows

One more, sometimes

Album folder — pick an entire directory at once.

⚠️ It only appears where the browser supports selecting a whole folder. On iOS Safari it's usually not there, because the browser itself doesn't offer the capability. The page detects this and hides the entry rather than showing a button that can't work.

How many entries you'll see

Three permanent entries — photos, videos, any file — plus 「album folder」 where the browser can do it. On an iPhone that's usually three. The page adapts to what your browser supports.

"Accept" in the file picker is a hint, not a correctness rule

The picker may suggest photos and videos. A file that doesn't match can still be sent — the filter is a convenience, not a gate. If something important is missing from the list, try the "any file" entry.

On the computer

Where files land, and what they're called

Target folders you choose

You add folders on the computer. Each one has a note (what shows on the phone) and a real path underneath it (to tell two folders with the same name apart).

⚠️ That real path is sent to the phone. This is a deliberate trade-off — see what the phone can see. The phone displays the path and never sends it back; only an opaque id is used to refer to a folder.

Batch note → directory name

One note for the whole batch. Files land under <target>/YYYY-MM-DD-note/. No note, no extra directory.

Note is for you — "birthday", "contract scans", "trip". It becomes part of the folder name so the directory is self-describing a year later.

Collisions: renamed, never overwritten

If photo.jpg already exists, the new one becomes photo (1).jpg, then (2), and so on. Nothing is ever written on top of an existing file.

Staging .samesend-tmp/ verify Checksum rename Final folder photo.jpg · photo (1).jpg Two-phase: write to staging → verify bytes → atomic move. The dashed box is the renamed duplicate.
Schematic. Drawn in SVG, not a screenshot. A crash mid-write leaves only a staging file, cleaned up on next launch.

The page you hold

What the phone page looks like

It's a plain web page. No framework, no external requests, no tracking — which is why it loads instantly on a slow network and works offline.

Three permanent entry points at the top, a date scope, a target folder, a batch note, and one big button. Everything else is progressive: the list, the progress bars, the preview overlay.

Schematic. Drawn in CSS, not a screenshot. ① Target folder shows two lines (note + real path) ② One note for the whole batch ③ Four entries (the dashed one simply doesn't appear on browsers that don't support it) ④ Date scope ⑤ Summary line: selected / to send / already on this computer / out of range ⑥ Per-row progress bar and status pill — "Already on this computer, skipped" is visually distinct from "Delivered" (dashed vs solid), so a skip is never dressed up as a save.

Before anything is sent

Three-stage funnel

When you pick files, nothing uploads yet. The list is filtered first, so you can see what's actually going over.

1

Size

Drop files under a size floor, or over a ceiling you set.

Cost: none, it's just the file metadata.

2

Date

Keep only today, this week, everything, or one specific day.

Cost: none.

3

Content fingerprint

Hash what's left, so already-transferred files can be skipped.

Cost: reading the file. Runs on the phone, in chunks.

The date filter uses file modification time, not capture time

The phone page has no access to EXIF. So "only today" means "files whose modified timestamp is today". A photo copied from somewhere else may carry the copy date. "I picked today and got nothing" is possible, and it isn't broken.

On arrival, the computer sorts by capture time where it can read it (JPEG / MP4 metadata) and falls back to modification time, then receive time, then an "unknown date" folder that is explicitly marked as a guess.

What "content fingerprint" means — and what it doesn't

Two algorithms, chosen by size:

  • Up to 64 MiB: a full sha256. That's a real cryptographic hash.
  • Over 64 MiB: sha256-sampled-v1 = first 4 MiB ‖ last 4 MiB ‖ little-endian uint64 length.

⚠️ The sampled one is not a full-content hash. Two large files that share a head and a tail will look identical. That's the price of being able to run on 4 GiB video at all — and we're telling you rather than letting you assume.

Big files

Chunked transfer, and what resume does and doesn't survive

When chunking kicks in

Anything over 16 MiB. Below that, one request, done.

Chunk size is clamp(ceil(size / 64), 8 MiB, 32 MiB). A 20 MiB clip becomes 3 chunks; a 2 GiB one becomes 64 (the 32 MiB ceiling caps it).

Chunks go one at a time, in order, not in parallel. That's a deliberate trade: it buys resume and keeps memory flat.

How resume works

The session id is the content fingerprint, so the computer knows which transfer this is without trusting anything the client says.

Bytes are appended in order into a staging file. If you retry after a drop, the server answers with the real offset it already has (a 409 that carries the number), and the phone continues from there.

Resume: what it crosses

It survives a network blip. It does not survive quitting the app.

  • ✅ Same app run, connection dropped. Hit retry. The transfer continues from the last confirmed byte. This is implemented and tested.
  • ⛔ You quit and reopened the app. Staging lives in a temporary directory that is cleared on the next launch. Resume state is gone; the transfer starts over. This is intentional — we don't want abandoned half-files accumulating.
  • ❓ Phone locked mid-transfer on iOS. The server side has a stall watchdog (60 seconds with no bytes → abort and report). Real-device confirmation is pending — we'll update this when we know.

Two-phase write

Staging, verify, then an atomic move. A crash leaves only staging files.

Strict order

Chunks must arrive in sequence. Out-of-order gets rejected with the real offset.

Built for 4 GiB scale

Not "we tested a 4 GiB file on a real phone" — built for that scale, in the design.

Look before you send

Thumbnails, preview, and what degrades

Tap to open big

Tap a photo to see it full size; tap a video to play it. Videos of any size can be previewed. The overlay holds its own blob: URL — it doesn't borrow the thumbnail's, which would break as the row scrolls away.

What might not show

  • HEIC usually degrades to an icon. The file still transfers; the thumbnail just isn't rendered.
  • Images over 32 MiB get no thumbnail. Note: "no thumbnail" and "not tappable" are two different checks.
  • Video codecs differ between a desktop browser and iOS Safari. Playing on one does not mean it plays on the other. Not verified on device

First run

Network cards and the one system prompt

Multiple network cards

Computers often have more than one address — Wi-Fi plus Ethernet, plus a VPN adapter or two. The app shows the network it's listening on and its address, so you can see which one the phone has to match.

If the phone is on Wi-Fi and the computer is on Ethernet, they're usually still on the same LAN — but a VPN or a guest network can split them.

One system prompt, once

Windows: a firewall dialog. Tick "Private networks" only.

macOS: a "local network" permission toggle. Refusing it means the phone will never connect — and the app looks perfectly fine, with a QR code on screen and no error.

Details in the two system gates.

On arrival

Album archiving

How photos and videos are organized once they're on the computer.

Two levels, both configurable

Level one: capture day, capture year-month, or file type. Level two depends on the first — e.g. day first, then split into Photos and Videos.

Changing the layout does not move files that already arrived. It only affects what comes after.

When capture time is missing

The chain is capture time → file modification time → receive time → "unknown date".

That last bucket is real and it is marked as a guess in the interface, so you can tell "we don't know when this was taken" from "this was taken on the 12th".

What is and isn't parsed

EXIF and container metadata are read for JPEG and MP4 common paths. HEIC files are classified but their metadata is not parsed — they fall back down the chain.