Move files between devices with a screen and a camera.
RVF containers and WASM artifacts — offline, no cables, no accounts, nothing to install.
- Open
./artifacts/on two devices. - On the first: Vault → ruvnet demo .rvf (a real 2.3 KB RVF container, five QR frames), tap it, then Send this.
- On the second: Receive → Start camera, and point it at the first screen. No camera? Photograph the screen and drop the picture in — that works in every browser.
- Watch the grid fill. When the last frame lands the bytes are hashed, checked against the manifest, and stored.
- Tap what arrived: real segment table, 24 vectors of 16 dimensions, and a search box that ranks them by distance.
The written walkthrough is docs/tutorial.md; the same guide is built into the app under the Guide tab.
| Hosted | ruvnet.github.io/rvQR — the normal app. |
| One file | standalone.html — the whole app, both demo artifacts and the RVF microkernel inlined into a single ~450 KB page. Save it and open it from disk: it makes no network requests at all, so it keeps working on a machine that has never been online. Handy for the air-gapped side of a transfer. |
Receiving needs a camera, which browsers only grant on https:// or a local
file — both of the above qualify. The photo-upload and paste paths work
anywhere, including inside an embedded frame where camera access is refused.
rvQR is optical transfer of RVF cognitive containers and WASM artifacts. Open the same app on two devices. On the first device, load a file from your vault and tap Send—the app animates it as a stream of QR codes on your screen. On the second device, tap Receive and point the camera at the first screen. Watch the progress ring fill as the QR codes decode. When the last frame arrives, the file is verified and stored in your vault.
No internet connection needed. No pairing, no accounts, no setup. The data physically travels from one screen to another, and you can watch it happen.
Both ship in the repo, and both are real.
| Artifact | Size | Frames | What it shows |
|---|---|---|---|
ruvnet-demo.rvf |
2.3 KB | 5 | A genuine RVF container: 4 segments, 24 vectors of 16 dimensions. Transfers in about a second, then you can search it in the browser. |
rvf_wasm_bg.wasm |
40 KB | 82 | The RVF WebAssembly runtime itself, published as @ruvector/rvf-wasm@0.1.9. About 16 seconds at the defaults, and a worked example of compile-only module inspection. |
| Vault | Send | Receive |
|---|---|---|
![]() |
![]() |
![]() |
| Stored artifacts, typed and hashed | Frame 19 of 82, QR version 19 | Frames landing out of order, 56% in |
- Works fully offline. No WiFi, no cellular, no servers. Optical air-gap transfer at its literal best.
- Honest about speed. A single animated QR stream moves 2.5 KB/s at the defaults and 10 KB/s flat out. The 40 KB demo takes about 16 seconds; this is a channel for kilobytes and low megabytes, not for your photo library.
- Mobile-first. Designed for phone screens and phone cameras, but works on desktops too.
- Integrity verified. Every byte accepted into your vault is checked against the SHA-256 hash from the manifest. A single-bit error causes the entire transfer to be rejected and discarded.
- RVF-aware. Detects RVF containers (append-only segment streams with tail-discovered 4096-byte root manifest) and shows their type. Sends and receives them as-is.
- Real RVF parsing. Containers are parsed by the actual RVF WebAssembly microkernel — header, segment table, per-segment CRC, vector count and dimensionality — and then searched, with a working nearest-neighbour query over the vectors inside.
- Scans without a native decoder. Where the browser has no
BarcodeDetector(Firefox, older Safari), rvQR falls back to its own bundled QR decoder rather than giving up. - Decode from a picture. Photograph or screenshot the sending screen and drop the image in; every frame visible in it is read at once. No camera permission needed, works in any browser.
- WASM inspection. Compile-only analysis of WebAssembly modules—lists all exported names without instantiating or executing the code.
- Drag and drop. Import artifacts by file picker or drag them into the vault.
- Pause and restart. Control the send animation; skip ahead with the frame scrubber.
- Camera fallback. No BarcodeDetector? Paste a QR frame by hand as text.
The core send/receive loop and the artifact vault are working today. Below are the features that are still roadmap:
| Feature | Status | Notes |
|---|---|---|
| Single-QR-stream send/receive | ✅ Implemented | v1 protocol, deterministic frames, SHA-256 verification |
| Artifact vault (storage, import, export, WASM inspection) | ✅ Implemented | IndexedDB-backed, no server sync |
| RVF container parsing and vector search | ✅ Implemented | Real @ruvector/rvf-wasm microkernel; segment table, CRC fingerprints, nearest-neighbour query |
| Bundled QR decoder + image-upload receive | ✅ Implemented | Used where BarcodeDetector is missing; reads several frames from one picture |
| Erasure-coded frames | 🚧 Built, not yet wired in | A systematic GF(256) fountain code with RaptorQ's block structure — RaptorQ-structured, deliberately not RFC 6330 conformant, and it will not interoperate with a conformant codec. Any K+ε symbols reconstruct the object regardless of which arrive. Lives in artifacts/fountain.js; the transport still uses fixed indexed chunks. |
| Delta segment transfer | 🗺️ Roadmap | Receiver displays its root manifest; sender diffs and sends only the missing RVF segments. Moves ~100× less data for a 1 GB container with 1% changed — about 29 hours down to 18 minutes at this app's measured rate. |
| Signed manifest verification | 🗺️ Roadmap | Detached signatures via rvf-crypto; pinned key on receiver |
| BitChat session bootstrap | 🗺️ Roadmap | X25519 public-key exchange QR; HKDF-SHA256 session key derivation; encrypted optical payloads |
| Resume after browser termination | 🗺️ Roadmap | Persist transfer state; resume from last received frame |
See docs/protocol.md for the wire format and roadmap, docs/tutorial.md for the walkthrough, and docs/ecosystem.md for how the pieces fit together.
flowchart TD
A["File<br/><small>WASM · RVF container · anything</small>"] --> B["Manifest<br/><small>name · size · chunk</small>"]
B --> C["SHA-256<br/><small>hashed once, up front</small>"]
C --> D["QR frames<br/><small>JSON header + base64url chunk</small>"]
D -->|"animated on screen"| E["Camera<br/><small>BarcodeDetector</small>"]
E --> F["Reassembly<br/><small>any order, duplicates free</small>"]
F --> G{"Verify<br/>SHA-256"}
G -->|"match"| H["Vault<br/><small>IndexedDB, inert data</small>"]
G -->|"mismatch"| X["Discard<br/><small>whole transfer, no partial accept</small>"]
Transport is not trust. The optical channel moves bytes; it does not authorize execution.
- Integrity is mandatory. Every byte is verified against the manifest hash before storage. A single mismatch discards the entire transfer — there is no partial acceptance.
- Integrity is not authenticity. The hash proves the bytes arrived intact. It says nothing about who sent them, because 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 of anything they like. Treat a received artifact the way you would treat a file downloaded from a stranger — that is precisely what it is.
- WASM is never instantiated. Compile-only inspection lists exports and imports without executing any code.
- Nothing runs on arrival. Received bytes land in IndexedDB as inert data, tagged with where they came from and shown with a
receivedbadge in the vault. rvQR itself never executes an artifact, and nothing in the app turns one into something that runs. - But there is no trust gate yet. Received and imported artifacts share one store, and the origin tag is a label, not an enforcement point: nothing blocks you from exporting a received file. A real quarantine — signature verification against a pinned key, and an explicit acknowledgement before a received artifact can leave the vault — is roadmap, not shipped. See docs/protocol.md.
The protocol is minimal and deterministic. A frame is one QR code containing one UTF-8 JSON string.
Manifest frame (always sequence 0):
{"v":1,"t":"<8 hex, random transfer id>","h":"<first 8 hex of SHA-256>","i":0,"n":<total frames>,"m":{"name":"<file name>","size":<bytes>,"sha256":"<64 hex>","chunk":<bytes>}}Data frame (sequence 1 through n-1):
{"v":1,"t":"<same transfer id>","h":"<same 8 hex>","i":<sequence>,"n":<total>,"p":"<base64url payload>"}Frames may arrive in any order and duplicates are free. Unknown protocol versions, inconsistent hash prefixes and absurd frame counts are dropped. Frames belonging to a different transfer are ignored while the current one is still progressing, and adopted once it has visibly stalled — so a stray frame cannot hijack a live transfer, and a sender that restarts is picked up automatically rather than stonewalled. When the manifest has arrived and every sequence is present, the payloads are concatenated in order, the SHA-256 is verified, and only then is the artifact stored.
See docs/protocol.md for implementation detail and the roadmap (erasure-coded frames, delta transfer, BitChat, signed manifests), and ADR-001 for why v1 shipped with fixed indexed chunks.
rvQR is a transport layer for RuVector artifacts and RVF cognitive containers.
- RVF container format: Append-only segment streams with a 4096-byte root manifest discovered at the tail. Segment magic bytes
53 46 56 52("SFVR"), root manifest magic30 4D 56 52("0MVR"). See ADR-009 in the RuVector repository. - WASM runtime:
@ruvector/rvf-wasm0.1.9is the RVF WebAssembly runtime. The copy bundled here as the demo artifact is that exact binary — 40,989 bytes, which the app shows as 40.0 KB and npm advertises as 39 KB — carried as cargo rather than run.
The crates, packages and evaluation layer around all this are laid out below.
Three surfaces touch the same format, plus an evaluation layer. The deeper version — how each piece plugs into rvQR's send and receive paths — is in docs/ecosystem.md.
Web UI — artifacts/, this repository. The vault, the QR
sender, the camera receiver, compile-only WASM inspection. The only surface that
moves an artifact between two devices with no shared network.
Rust crates — in ruvnet/RuVector, under crates/rvf/:
| Crate | Role |
|---|---|
rvf-types |
Wire constants and header structs, including SEGMENT_MAGIC_BYTES and ROOT_MANIFEST_MAGIC_BYTES per ADR-009 |
rvf-wire |
Segment codec, tail scan (find_latest_manifest), golden byte vectors gated in CI |
rvf-runtime |
The store: open, ingest, query, copy-on-write derive, durable metadata |
rvf-crypto |
Signing, verification, witness chain — the roadmap signature layer |
npm packages:
| Package | Role |
|---|---|
@ruvector/rvf-wasm 0.1.9 |
The RVF WebAssembly runtime. rvQR's demo artifact is this binary, 40,989 bytes — carried, never loaded |
@ruvector/rvf |
The Node.js RVF store |
ruvector |
The full vector database; RVF is its portable format |
metaharness is the evaluation and governance layer. rvQR's acceptance bar — 100 transfers of 100 MB, zero incorrectly accepted files, recovery under 20% frame loss — is written to be run as metaharness-gated benchmarks, with correctness gates binary and throughput scored, so no amount of speed can offset a false accept. The receive path already produces what a witness record wants (manifest hash, computed hash, verdict, frame and duplicate counts), so transfers could be scored over attested outcomes rather than self-reported ones. To be clear: this is design intent, not shipped integration — there are no gate definitions or witness emission in this repository today. See docs/ecosystem.md for the sketch.
rvQR carries RVF containers but does not define the format. The decisions that do are mirrored, with provenance headers, under docs/adr/ — grouped by wire contract, cognitive containers and WASM, transfer and federation, and QR. Start with ADR-009, the normative wire contract this app's type detection implements, and ADR-034, which solves the single-symbol case rvQR complements by streaming.
Send works everywhere. Animating QR codes requires only Canvas, which all modern browsers support.
Receiving works everywhere, by one of three routes:
- Native scanning where the browser has
BarcodeDetector— Chrome and Edge, and Safari 17 and later. Fastest, and what rvQR uses when it is available. - The bundled decoder everywhere else, including Firefox and older Safari. It is dependable on smaller symbols and wants a sharper image on the densest ones: good to about version 16 on a blurry camera frame, and to version 40 on a sharp screenshot. Sending at a 256-byte chunk keeps every frame comfortably inside that range.
- A picture, or pasted text. Both work in any browser with no camera at all. The picture route is also the answer when rvQR is embedded in a page that does not grant camera access — a restriction set by the surrounding page, which the app detects and explains.
Open artifacts/test.html to run the self-tests in your browser. It exercises frame encoding, out-of-order and duplicate reassembly, hash-mismatch rejection, the QR encoder's structure, the decoder (encode → pixels → decode, including damaged symbols), and RVF parsing against the real demo container — no camera or second device needed — and renders two live QR codes you can scan with any reader to confirm the encoder produces real, readable symbols.
The same assertions run under Node:
node -e "const c=require('./artifacts/core.js'),q=require('./artifacts/vendor/qrcode.js'),t=require('./artifacts/tests.js');
const r=t.runAll(c,q); r.forEach(x=>console.log((x.ok?'ok ':'FAIL')+' '+x.name));
const s=t.summarize(r); console.log(s.passed+'/'+s.total+' passed'); process.exit(s.failed?1:0);"Contributions are welcome. Please open an issue or pull request on github.com/ruvnet/rvQR. For the RVF format itself, see the RuVector repository.
MIT License. Copyright (c) 2026 rUv. See LICENSE for details.


