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.
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.
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.
Size
Drop files under a size floor, or over a ceiling you set.
Cost: none, it's just the file metadata.
Date
Keep only today, this week, everything, or one specific day.
Cost: none.
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.