← Portfolio

Technical Documentation

Sound On Paper

Put sound into paper — a privacy-first, QR-based audio sharing system built for artists who want to share sound as an intimate physical object

Next.js 15 TypeScript Postgres Prisma Web Crypto API MinIO/R2 Resend TOTP MFA

Private beta live  ·  All 10 milestones implemented · 125 unit tests passing · 12 focused Playwright scenarios


Sound on Paper — paper tapes

The Idea

I watch my younger students reaching for analog. They're hunting down digicams, shooting film, buying cassettes. There's something they're after — something physical, personal, and real — that purely digital music sharing has removed. I've wanted to restore a physical dimension to sound for a long time. Not nostalgia exactly, but the feeling that music can be an object you hold, pass to someone, and give as a gift. Sound on Paper is built around that idea. A printed card with a song inside it. Snail mail for sound. The QR code generators already exist — this is the ecosystem, the experience, the thing that's been missing. A companion physical player is in development — the goal is a complete platform, not just a web app.

Control the paper = control the song.

What It Should Feel Like

It should feel like

  • a message passed by hand
  • a small art object
  • a secret song container
  • a minimalist music player
  • a physical-world sound object

It should not feel like

  • a promo landing page
  • a streaming smart link
  • a social profile
  • a CRM or marketing dashboard
  • a generic QR generator

Privacy Architecture

The privacy model is architectural. The server hosts encrypted sound objects and is deliberately designed so it cannot listen to them.

Client-side encryption

Audio, artwork, title, and message are encrypted in the browser before upload using AES-GCM via the Web Crypto API. The server stores only encrypted files, encrypted metadata, non-sensitive routing metadata, and aggregate event records. It never receives plaintext audio, titles, messages, or artwork.

URL fragment key

The QR code points to a URL shaped like:

https://soundonpaper.com/p/quiet-river-17#k=PUBLIC_SHARE_KEY

The slug quiet-river-17 is sent to the server. The fragment — everything after # — is never sent in normal HTTP requests. The browser uses it locally to decrypt the Piece. The server can serve the encrypted manifest without ever knowing the decryption key.

Dynamic QR

A printed QR code keeps working even when the creator changes the audio behind it. Each Piece has a stable public share key embedded in the QR at generation time. New audio versions are encrypted so the same printed paper decrypts the current assigned content. The creator's encrypted key set retains the Piece key so updates remain possible indefinitely. A printed QR can be updated to play different audio at any time without reprinting — the paper stays the same.

Key management

On account creation, a creator master key is generated in the browser. A recovery phrase — shown exactly once — can unlock it. Only encrypted key material is stored server-side. Trusted-device keys are stored as non-extractable CryptoKey objects in IndexedDB. If a creator loses their recovery phrase and has no trusted device, the company cannot recover their encrypted content. This is explained during setup in plain language without making the product feel like a security bunker.

Practical limits

The user-facing privacy promise is intimate and private — not magically impossible to copy. Client-side encryption prevents the company from reading stored content by default. It does not prevent a listener from recording audio from their device, sharing the QR link, or submitting the key during an abuse report. The product is honest about this distinction.


The Listener Experience

The listener page shows almost nothing by default: a large play button, an optional progress bar, optional title, optional short message, optional artwork. No creator identity, no promotional links, no social mechanics, no engagement bait.

A quiet response control — present like a small folded corner of paper — can reveal lightweight actions: a heart and a report. It is secondary to listening in every sense. Six themes — Plain Paper, Blank, Night Paper, Signal, Blue Hour, Red Room — provide restrained visual expression that feels like paper stocks, not decorative skins.

Playback pipeline


The Creator Experience

The creator side now opens with a derived three-step path: add audio, make a Piece, then print and test the QR. Each step links directly to the relevant tool and updates from the creator's actual library state, replacing a dashboard that previously showed counts without helping someone decide what to do next.


Build Status

The project is built in testable milestones — not because AI can't accelerate coding, but because the product has high-risk promises that must be validated incrementally. Each milestone leaves behind a working slice that is tested before more complexity is added.

0 Repo and App Foundation Complete Next.js 15, TypeScript strict, Tailwind v4, Prisma, Vitest, Playwright
1 Auth and Private Key Setup Complete Auth.js v5, PBKDF2 key derivation, IndexedDB non-extractable device keys, recovery phrase flow
2 Encrypted Audio Upload Playwright accepted AES-GCM browser encryption, presigned S3 upload, browser recording, MP3/M4A validation, 3 min/50 MB limits
3 Piece Creation and Dynamic QR Playwright accepted Encrypted manifest, stable share keys, SVG/PNG QR export, word-word-NN slugs
4 Listener Playback Playwright accepted Browser-side decryption, one-tap playback, graceful errors for missing/invalid/wrong key
5 Themes and Listener Response Playwright accepted Six themes, heart toggle, report flow with key-sharing confirmation
6 Privacy-Friendly Analytics Playwright accepted Scan, play, completion events — no raw IP, no fingerprinting, no third-party scripts
7 Reporting, Copyright, Enforcement Playwright accepted Abuse reports, DMCA/counter-notice intake, searchable admin queues, linked records, enforcement logging
8 Access Controls Playwright accepted Expiration, play limits, Pass-required Pieces, one-time and expiring Passes
9 Billing and Entitlements Implemented Stripe integration and plan enforcement are complete; checkout is intentionally disabled during the free private beta
10 Print and Physical Artifact Tools Playwright accepted Sheet (A4), Card (85×54 mm), and Sticker (60×60 mm) print layouts, PDF export, optional URL beneath QR

The current verification set contains 125 passing Vitest unit tests and 12 focused Playwright browser scenarios across setup, encrypted audio, browser recording, Pieces, listener playback, administration, access controls, printing, and private-beta security. GitHub Actions runs Node 22 installation, Prisma generation, lint, type checking, unit tests, and a production build. Stripe checkout remains outside the beta acceptance path because production billing is deliberately disabled.

In summary: Auth.js v5 with PBKDF2 key derivation and IndexedDB non-extractable trusted-device keys; encrypted upload and recording to S3-compatible object storage; dynamic QR generation with stable fragment share keys; browser-side decryption with one-tap playback; six listener themes; privacy-friendly analytics; abuse reporting and linked admin moderation queues; expiration, play limits, and Pass-gated listening; optional billing and plan enforcement; and printable PDF, card, and sticker layouts for physical QR artifacts.


Private Beta Readiness

Sound on Paper is now running as a free, invitation-only concierge beta. New creators request access, receive a 24-hour single-use invitation by email, and begin on the Free plan. Artist access is granted manually so the beta can focus on whether people can make and physically scan a Piece without turning onboarding into a checkout funnel.


Tech Stack

Framework Next.js 15 App Router
Language TypeScript (strict mode)
Styling Tailwind CSS v4
Database Postgres · Prisma ORM
Object storage MinIO (local) · Cloudflare R2 / S3-compatible (production)
Browser encryption Web Crypto API · AES-GCM · PBKDF2
Trusted device keys IndexedDB · non-extractable AES-KW CryptoKey objects
Auth Auth.js v5 credentials · invite-only signup · administrator TOTP MFA
Billing Stripe checkout, portal, and webhook · disabled mode for complimentary beta access
Email Resend · verified sender · server-only invitation and password recovery adapter
QR generation SVG and PNG export · error correction level H for sticker layout
Unit tests Vitest · 125 passing
Acceptance tests Playwright · 12 focused browser scenarios against local Postgres and MinIO
Runtime target Node 22 (.nvmrc + package.json engines)

Architecture

Route structure

app/
  (auth)/         signup, login, forgot/reset password, setup, unlock
  (creator)/      dashboard, audio, pieces, print, settings
  (listener)/p/   [slug] — public listener route
  (legal)/        copyright, privacy, terms, DMCA, counter-notice
  admin/          users, beta, reports, copyright, enforcement
  admin-mfa/      authenticator enrollment
  api/            auth, key-vault, audio, pieces, public, admin, legal

Source structure

src/
  crypto/
    browser-crypto.ts      key generation, wrapping, unwrapping
    key-derivation.ts      PBKDF2 derivation from recovery phrase
    manifest-crypto.ts     Piece manifest encrypt/decrypt
    audio-crypto.ts        AES-GCM audio encrypt/decrypt
    device-key-store.ts    IndexedDB non-extractable CryptoKey storage
  lib/
    access-control.ts      expiry, play-limit, pass validation helpers
    billing.ts             Stripe customer, checkout, portal, webhook
    entitlements.ts        plan limits and enforcement
    events.ts              privacy-friendly event ingestion
    pass-code.ts           WORD-NN pass code generation
    pass-grant.ts          short-lived signed grant after Pass redemption
    email.ts               server-only invitation and password recovery email
    rate-limit.ts          durable HMAC-keyed Postgres rate limiting
    admin-mfa.ts           encrypted TOTP enrollment and recovery-code handling
    qr.ts                  QR SVG generation for listener URLs and print
    slug.ts                word-word-NN slug generation
    storage.ts             S3-compatible object storage abstraction
    themes.ts              listener theme token definitions
    validation.ts          locked audio constraints (MP3/M4A, 50 MB, 3 min)

Prisma models


Security Decisions


Moderation and Safety

The privacy model creates a real tension with safety. The product does not pretend otherwise. The principle: no surveillance, clear safety boundaries, human review only when someone with access reports abuse.

When a listener files a report, the flow explains that submitting it shares the full Piece URL — including the fragment key — with moderation. This is an explicit, informed action. Moderators can decrypt and review reported Pieces, disable Piece routes, record copyright notices, and log enforcement actions against accounts. DMCA notice and counter-notice intake is built into the legal pages. Repeat-infringer tracking is modeled in the data schema.


Product Guardrails