Skip to content

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 12 Aug 19:50
· 5 commits to main since this release
bfcf5e4

Compatibility: 0.5.0 does not interoperate with 0.4.x, in either direction. Update both ends. On decimen.app a reload is enough (fully close and reopen an installed PWA). Standalone files from earlier releases keep working with each other, but must both be re-downloaded to talk to a 0.5.0 peer. A 0.5.0 receiver names the mismatch on screen; a 0.4.x receiver pointed at a 0.5.0 sender shows nothing — it predates the version field — so if the sender is current and the receiving phone seems blind, update the receiver.

Protocol: wire format v3

  • The frame header (shared/protocol.ts) grows 20 to 22 bytes and finally carries its own version: a fixed two-byte magic pair (0xD1 0xC3), a version byte, and a flags byte, ahead of the fields v2 carried. v1 and v2 each spent a format break on a magic bump and bought no version field with it; a mismatched receiver returned null and showed nothing, so "the sender is too old" looked exactly like bad lighting. Survivable for a PWA that reconverges on a service-worker refresh within days; not survivable once store binaries update on the user's schedule. From v3 on, the next break is a number, not a break.
  • A receiver that meets a Decimen format it cannot read says so: "That screen is sending an older Decimen format — update the sending device" / "a newer format — update this app to receive it." Non-Decimen codes stay silent — the camera decodes every QR in view, and a shop window earns no advice. Malformed frames stay silent too, being indistinguishable from a bad read. Two magic bytes are what make the advice trustworthy: gated on one, roughly 1 binary QR payload in 256 would tell a user to update a device that has never run Decimen, and that message latches on screen until a real frame clears it.
  • The flags byte ships pre-split: 0x0F must-understand (an unknown bit is a loud reject), 0xF0 safe-to-ignore (parsed straight through). The split cannot be added later — a receiver already taught "every unknown bit is fatal" can only be corrected by another format break — so the rule ships with the first versioned build even though nothing sets a bit yet. FLAG_ENCRYPTED (0x01) is reserved now, so encrypted payloads later cost a flag bit rather than wire v4.
  • Stream identity includes the critical half of the flags byte (a change there is a genuinely different stream) and excludes the ignorable half — a mid-stream flip of an ignorable bit must not reset the decoder and discard every block recovered so far.
  • 0x0C and 0x0D stay reserved as magic-1 values forever: they are how a v3 receiver names a v1/v2 sender instead of shrugging at it.

Conformance

  • docs/technical/versioning.md — the wire contract, independent of any one implementation: magic, version, flag semantics, what a receiver owes the user when it cannot decode, and the release compatibility table.
  • docs/technical/golden-vectors.md — the bytes a second implementation is held to: the canonical 22-byte frame, classification vectors (which mutations speak and which stay silent), and the stream-identity rules. A diff to any byte on that page is a wire-format change and gets reviewed as one.
  • tests/transfer.test.ts — cross-layer harness: 300 KB of incompressible data through container, fountain, and framed wire and back, over deterministic ~15% frame loss, asserting full recovery, hash agreement, byte-exactness, and under 1.3x overhead. It is the only test that catches a header field read from the wrong offset — packFrame and parseFrame agreeing with each other is not the same as either agreeing with the wire — and the conformance bar a native decoder is held to.

Privacy: auto-show

  • Show received files automatically (Receive settings, on by default). Turned off, a landed image, video, audio file, or text snippet waits behind a Show button instead of putting itself on a screen someone may be holding up in a room. Saving and Copy are unaffected either way — the file arrived and verified regardless; only the room is blind to it.
  • Off also means nothing is written to the media cache until Show: the Cache API staging exists so the in-page player can seek, and a file never opened never needed it.
  • Clear Decimen cache now appears only when something is actually cached — the old unconditional offer implied Decimen had kept a file it hadn't. It is also reachable from the text-snippet end screen, so receiving text after a video no longer strands the video in the cache.
  • This is the one preference Decimen persists between sessions (a single localStorage key). A privacy choice that forgets itself on reload is no choice at all — the point is that the next thing to arrive stays covered.

decimen-codec 0.2.0

  • The vendored decoder is bumped to decimen-codec v0.2.0, sha256-identical to its GitHub release, self-identifying via banner and version()/build() exports.
  • The 0.2.0 work is native-facing: a C ABI for iOS/Android consumers (caller-owned buffers, clean error codes, the position quad reported even on failed decodes so tracking can re-aim), the engine core split from the embind layer, and a native CMake target built in CI. The WASM decode paths are unchanged and parity-gated; web behavior is identical.

Site

  • Support links, hosted builds only: footer, home card, and a post-transfer line. The standalone artifacts never solicit — the module is swapped out at build time, so the code is not even parsed there.
  • The OG card's speed claim is tokenized from benchmarks/records.json at build time, so the social card can never lag the published table.
  • favicon.ico, robots.txt, sitemap.xml, canonical URLs on every page.