Rejog Stash

Rejog Stash: Your Stuff, Findable and Lendable

4 min read Updated July 23, 2026

Point your camera at a shelf; it catalogs what it sees. Then lend to a friend with one link, no app, no account.

The Problem

You own lots of things and can’t find the one you want, and you readily lend things to friends and invariably forget who had it last. I wanted the friction gone on both ends: catalog what you own, and enable a way to share it without losing track of where it ended up.

What I Built

A personal inventory app with peer-to-peer lending. Photograph a shelf and it becomes a searchable list, each item remembering where you last saw it. Lending is a one-time link, and the borrower needs no app and no account.

Three codebases - native iOS, native Android, and a Cloudflare Worker for the web side - that share no model and no code. What they share is a wire contract: one versioned, additive-only definition of every request and response, owned by the Worker and matched byte for byte by both platforms. iOS is the authority on how a feature is implemented, and Android ports but remains a native look-and-feel. The Worker is the authority on what the network looks like. When they disagree, the contract wins.

Tech Stack

Languages
  • Swift
  • Kotlin
  • TypeScript
  • JavaScript
  • HTML/CSS
  • SQL
  • Python
Runtime & frameworks
  • iOS SwiftUI
  • Android Jetpack Compose/AndroidX
  • Cloudflare Workers
  • static browser frontend
  • CameraX
Data & storage
  • iOS SwiftData + CloudKit/private DB + Keychain/iCloud Keychain
  • Android Room/SQLite + DataStore/EncryptedSharedPreferences
  • Cloudflare D1/KV/R2
Infra & deploy
  • XcodeGen/TestFlight/App Store Connect scripts
  • Gradle/R8/Firebase App Distribution/Play tracks
  • Wrangler/Workers Assets/custom domains
  • D1 migrations
AI / ML
  • iOS Vision/Core ML MobileCLIP/FoundationModels/OpenRouter relay
  • Android ML Kit/ONNX Runtime Mobile/MobileCLIP model download
  • Worker api-router relay to OpenRouter
Testing
  • XCTest/XCUITest
  • Android JUnit/Robolectric/Compose UI tests/MockWebServer
  • Vitest/node:sqlite
  • Playwright
  • Python device integration harnesses
Notable libraries
  • StoreKit 2
  • Play Billing
  • APNs/FCM/Web Push
  • Resend
  • osmdroid
  • Retrofit/OkHttp/kotlinx.serialization
  • Coil
  • ZXing

How It Works

Capture is camera-first. On iOS, Vision instance segmentation pulls every object out of one frame as its own item, running off the main actor under Swift 6 strict concurrency. Android does the same job with ML Kit subject segmentation off a CameraX feed. Naming routes through an owner-authenticated identify relay so no API key ever rides the wire, with an on-device model as fallback; on-device Foundation Models are where this wants to land, and that path already works against the iOS 27 beta. Search is semantic either way - embeddings plus a synonym map - so “coat” finds your windbreaker.

One Worker (D1, KV, R2) is the meeting point, serving four trust domains from a single codebase: an owner’s phone signing writes with a per-tenant HMAC key, an owner on the web console with a passkey, a borrower with no account authorized purely by holding an unguessable link, and an internal operator. The borrower is the interesting one - identity is possession of the secret, and nothing more.

The diagram below is the whole shape of it: what each client owns locally, what actually crosses the wire, and where Android’s leak guard cuts a path off on purpose.

Rejog Stash: one wire contract, three independent stores Three client stores sit at the top: the iOS app, persisting to SwiftData mirrored to a private CloudKit database, where item custody and condition are derived by folding an event ledger; the Android app, split into two physically separate Room databases, a syncable CatalogueDatabase for owner inventory and a device-local BorrowedDatabase that structurally never reaches a sync path; and the borrower web app, which holds no local store at all and authenticates purely by possessing an unguessable capability link. The iOS app and the Android catalogue database push HMAC-signed writes down to a single Cloudflare Worker at stash.rejog.net, which owns contract dot ts, version 2, additive and back-compatible only, where iOS is the product-behavior authority and the Worker is the wire-contract authority and Android conforms. The Android borrowed database has no connection down to the Worker at all, marked as no sync path. The Worker serves the borrower web app a capability link with no account required. Below the Worker sit its three storage bindings: D1, a relational system of record with 26 migrations across about 27 tables; KV, for hot token lookups, rate limits, idempotency, and WebAuthn challenges; and R2, two buckets, one for item photos deduped by content hash and one for the roughly 87 megabyte on-device MobileCLIP encoder Android downloads on first run. Rejog Stash one wire contract, three independent stores iOS App SwiftUI + SwiftData mirrored to private CloudKit custody/condition: folds over an event ledger, re-run on merge product-behavior authority Android App CatalogueDatabase owner inventory - syncable BorrowedDatabase device-local - never syncs Borrower Web no app, no account identity = holding an unguessable link no local store at all Cloudflare Worker - stash.rejog.net contract.ts v2 (~1,639 lines) - additive & back-compatible only iOS = product-behavior authority · Worker = wire-contract authority · Android conforms HMAC-signed writes catalogue only, conforms to v2 no sync path capability link, no account D1 (SQLite) relational system of record 26 migrations, ~27 tables KV tokens, rate limits, idempotency short-lived WebAuthn / pairing challenges R2 - two buckets PHOTOS - content-hash deduped MODELS - ~87MB encoder, first-run download
Three independent stores, one wire contract. iOS and Android each own their local state; Android's borrowed items are structurally cut off from the sync path; the Worker's contract.ts is the only thing all three must agree on.

Privacy is enforced in the code, not in a policy line. On Android, borrowed items live in a physically separate, device-local database that structurally cannot reach an upload path, pinned by a boundary test. Location is read only in the foreground, at the moment of capture, to stamp where an item was last seen - never in the background. Contact details cross between two people only once a loan is actually established, enforced server-side. The privacy policy is the public version of the same commitments.

What Made It Hard

Getting multi-item recognition to run on-device, fast, and off the main thread. Keeping three genuinely native clients honest against one contract with no shared codebase to lean on - the rule is additive and back-compatible only, so a field can be added but never repurposed or removed once it ships. And enforcing privacy structurally instead of as a promise, which costs more up front and is the only version that survives a refactor.

The Throughline

Take the tedium out of a real chore so the tool disappears and you just get the result. The hard engineering - the contract, the on-device vision, the structural privacy - is all in service of “point and done.”

Status

The borrower web app and backend are live at stash.rejog.net. The iOS and Android apps both run on real devices and are in pre-launch testing. Neither is in the App Store or Play Store yet. The whole system runs on a few dollars a month of edge infrastructure: cheap enough to keep paying out of pocket indefinitely, closer to a public utility than a startup.