How rvQR works
Two devices, a screen and a camera, and no network in between. One device turns a file into a stream of QR codes; the other watches, collects the frames, checks the hash, and stores the result. That is the whole idea — everything below is detail.
Try it in 60 seconds
-
Load the demo container
On the Vault tab, tap ruvnet demo .rvf. It is a real RVF container holding 24 vectors of 16 dimensions, and it fits in five QR frames.
-
Send it
Tap the artifact, then Send this. Frames start cycling immediately. Leave that screen showing.
-
Receive it on the other device
Open this same page on a second device, go to Receive, tap Start camera and point it at the first screen. No camera? Photograph the screen and drop the picture into the image box instead.
-
Watch it land
The bar fills and the grid lights up as frames arrive, in whatever order they happen to be caught. When the last one lands, the bytes are hashed and compared with the manifest. Match, and it is stored. Mismatch, and the whole transfer is thrown away.
-
Open what you received
Tap the new artifact. For an RVF container you get the real segment table, and then a search box: randomise a query vector and see the nearest ids ranked by distance. A vector database, delivered by camera.
Sending well
Two settings decide whether the other device can keep up.
Chunk size is how many bytes ride in each frame. Bigger chunks mean fewer frames but a denser symbol. 512 bytes suits a phone with native scanning; drop to 256 if the receiving device is struggling or is falling back to rvQR's own decoder.
Speed is frames per second. Faster finishes sooner but gives the camera less time on each frame. Five is comfortable; drop to two or three in poor light.
Hold both devices steady, fill the camera with the QR code, and avoid glare — the same things that make any QR scan work.
Three ways to receive
Camera. The fastest route. Uses the browser's native barcode reader where there is one, and rvQR's own decoder where there is not.
A picture. Photograph or screenshot the sending screen and drop the image in. Every frame visible in the picture is read at once. This works in every browser, and it is the answer when the page is embedded somewhere that blocks camera access.
Pasted text. If you can copy a frame's contents as text, paste it in. Mostly useful for debugging, and it drives exactly the same receiving logic as the other two.
Reading the progress display
The ring on the sending side shows where the sender is in its loop. It is not the receiver's progress — the sender has no idea what the receiver has.
The bar and grid on the receiving side are the real progress. Each cell is one frame, lit once that frame has been read. Gaps fill in as the sender loops around again. On a very large transfer, one cell stands for several frames.
The counters show duplicates (harmless and expected) and rejected frames (unreadable, or belonging to a different transfer).
Inspecting what you got
RVF containers are parsed by the real RVF WebAssembly microkernel — the same one published as @ruvector/rvf-wasm. You get the header, every segment with its type and size, a CRC fingerprint per segment, and for containers with vectors, the count, the dimensionality and a working nearest-neighbour search.
WASM modules are compiled but never instantiated, which is enough to list their exports and imports without running a single instruction.
Where a check cannot be made honestly, rvQR says so instead of showing a green tick. Some entry points in the published kernel do less than their names suggest, and the detail view spells out which.
Disposal
Any stored artifact can be armed to destroy itself: burn after export deletes it the moment you download it, and a timed expiry deletes it once the clock runs out. Both are set on the artifact's detail sheet, behind a confirmation, and a running countdown is shown on the artifact wherever one exists.
This is a real deletion, not a display: the record leaves IndexedDB, and the vault also sweeps expired records every time it is read, so one that ran out while the app was closed is gone before it can be listed.
What it cannot do is more important than what it can. It deletes this app's copy on this device. It cannot recall a file you already exported or a copy another device received, and browser storage is not secure erasure — the bytes may survive on disk until the operating system reuses that space.
What is actually guaranteed
Integrity verified · Authenticity not established
Integrity: yes. Nothing enters the vault unless the reassembled bytes hash to exactly the SHA-256 in the manifest frame. One wrong bit discards the entire transfer.
Authenticity: no. The manifest travels in the same unauthenticated stream as the data, so anyone who can put a screen in front of your camera can send you a perfectly valid transfer of anything at all. Treat what arrives like a download from a stranger. Signed manifests are on the roadmap.
Execution: never. Receiving a file does not run it, and rvQR has no mechanism that would.
Browser reality
Sending works anywhere with a canvas. For receiving, Chrome and Edge — and Safari 17 and later — have a native barcode reader that rvQR uses when it is there. Everywhere else, including Firefox and older Safari, rvQR falls back to its own decoder, which is dependable on smaller symbols and wants a steadier hand on the largest ones. The image and paste routes always work.
What this is
Move files between devices with a screen and a camera. rvQR carries RVF containers and WASM artifacts: one screen shows the file as QR frames, one camera reads them back. No network, no cable, no account — and you can watch the whole transfer happen.
Security model
Integrity verified · Authenticity not established
Transport is not trust. Receiving a file grants it no authority at all.
- Nothing executes on arrival. Received bytes are inert data in local storage.
- WASM inspection is compile-only. The vault calls
WebAssembly.compile to read export names. It never instantiates a module, never provides imports, and never calls a function.
- Integrity is always verified. A transfer is accepted only if the reassembled bytes hash to exactly the SHA-256 in the manifest frame. A mismatch discards the whole transfer — there is no partial acceptance.
- Integrity is not authenticity. The hash proves the bytes arrived intact, not who sent them — the manifest travels in the same unauthenticated stream as the payload. Anyone who can put a screen in front of your camera can produce a perfectly valid transfer. Treat a received artifact like a download from a stranger. Signed manifests via rvf-crypto are roadmap.
- No trust gate yet. Received artifacts are marked received in the vault, but that tag is a label rather than an enforcement point: nothing here blocks exporting one. A real quarantine gate is roadmap, not shipped.
- Activation stays separate. Turning an artifact into something that runs is a privileged operation outside this app.
Disposal and its limits
Artifacts can be armed to delete themselves — on first export, or on a timer. The deletion is real and happens in the vault: the record is removed from IndexedDB, and every read of the vault sweeps anything already past its moment.
- Scope is this device. It deletes this app's copy, and nothing beyond it.
- No recall. A file you already downloaded, or one another device already received, is outside this app's reach forever.
- Not secure erasure. Deleting an IndexedDB record does not overwrite the underlying storage. The bytes may remain recoverable on disk until the operating system reuses that space.
- The clock needs the app. A timed expiry is enforced while this page is open, and again the next time you open it. It does not run while the app is closed, so a record can outlive its moment on disk — it simply never gets listed or opened again.
Arming is always confirmed before it takes effect, and destroying immediately is a separate, confirmed action.
Under the hood
- Frame
- A compact JSON header plus a base64url payload, encoded as one QR symbol in byte mode.
- Manifest
- Frame 0 carries the name, byte size, full SHA-256 and chunk size.
- Receiver
- Collects frames in any order, ignores duplicates and foreign transfers, then verifies before storing.
Browser support
Sending works anywhere with a canvas. Receiving needs the native BarcodeDetector API — Chrome and Edge on Android and desktop, and Safari 17 and later. On older iOS Safari this device can still send; use the paste-a-frame fallback or a supported browser to receive.