khanhanh@sydney: ~/projects/lapse · cat lapse.md
$ cd .. back to ~/projects (esc)

case-study --deep-read

Lapse

A browser-native recording platform for capturing, processing, and sharing proof of work

project: Lapserole: solo engineer: product, architecture and deploymentstack: Next.js · Postgres SQL · Cloudflare · Clerktimeline: public beta · 2026status: live · source on github

Lapse is a browser-based screen recorder for builders who need to show work without sending a massive raw recording. The product flow is deliberately small: record a session, process it locally, upload it once, then share a private link that can be watched in the browser. This product was developed out of my own problem , when i want to post show case some of my coding work on instagram and need an easier way to make timelapse also basic processing its format.

Problem

The real problem was not "record the screen." The browser already exposes getDisplayMedia. The harder problem was making a recording workflow feel reliable after the user has just spent an hour capturing something important.

The app has to handle permission prompts, different display surfaces, optional microphone and camera, long recordings, memory pressure, local processing, upload progress, private sharing and viewer access. Any one of those can fail in a way that looks like the whole product is broken.

Architecture

The product is split into three main domains.

  • The recording engine owns capture, camera picture-in-picture, audio mixing, file-system fallback and local export processing.
  • The video domain owns upload reservations, Bunny video IDs, thumbnails, database finalization, visibility and sharing.
  • The access layer owns Clerk identity, private-share records, OTP sharing and signed playback URLs.

The database model reflects that split. videos is the canonical published asset. video_upload_reservations is the pre-publish state that connects a Bunny video ID to the user who is allowed to finalize it. video_shares is the private access grant, optionally tied to a Lapse user or an email OTP.

That separation mattered because upload and database writes cannot be treated as one transaction. A file may reach Bunny while the app still needs to validate ownership, thumbnail presence, expiry and quota before creating the final video row.

Key decisions

The first decision was to process as much as possible locally. Time-lapse export uses browser media APIs: a captured video is replayed at the selected speed, captured back into a stream and encoded through MediaRecorder. That keeps the server out of the slow media path and makes the product cheaper to operate.

The second decision was to treat uploads as reservations, not as blind file posts. A user reserves an upload, Bunny receives the media, then the app finalizes only if the reservation still belongs to the user, has not expired, has not already been consumed and has the required thumbnail data. The reservation table has unique indexes around Bunny IDs and finalized video IDs so retry behavior cannot create duplicate video rows.

The third decision was to support both memory and disk recording modes. Long recordings can become too large to keep safely in memory, so the recorder can write chunks through the File System Access API when the browser supports it. When the browser does not, it falls back to memory and warns near the cap.

Hard parts

Recording must start from the user's click path. Browsers are strict about media capture APIs, especially around getDisplayMedia, so the code keeps plan and settings data available synchronously before opening the native picker. If the app waits on network work at the wrong time, the browser may reject the capture even though the user clicked the right button.

Another hard part was camera composition. If the user records another app, the camera bubble can disappear from the captured surface. The workflow detects camera compositing support and offers a pop-out camera path for browsers that support document picture-in-picture.

The final hard part was failure recovery. Uploads, share emails and local media processing all fail differently. The app logs client-side processing errors, shows user-readable recovery messages and keeps the original recording URL available when an export transform fails.

What this demonstrates

Lapse shows full-stack product judgment around browser APIs, local media processing, auth boundaries, database consistency and external storage. It is not just a polished UI; it is a workflow where the unhappy paths matter because the user's recording is the product.

-- EOF · written by khanhanh, edited by nobody● open live ↗next: goat-food-and-tea.md →