LS / MTLLuke Sidney
Lighting the room0%
10th floor · Quartier Latin · golden hour
Montreal, QC·lukesidney.me·Fresh 2 · DenoNo cookies · No forms
Plate01

Front page.What it is

Live

IAN

A live room that puts a finished deck on every client's phone, in step with the presenter.

In A Nutshell. It shows decks; it never makes them.

Riso illustration: a laptop at the head of a meeting table and five phones around it, each showing the same slide, linked by dotted lines, with a QR card in the middle.
Plate02

Set in type.The stack

What it runs on

A web app for the room, and a small sync server beside it.

Interface

  • Next.js16.3the room, the console, the deck list
  • React19.2
  • uqr0.1QR codes for the room

Live

  • Sync serverserver-sent events to every phone, on Railway
  • Upstash Redisa 50-event history and the viewer count

Data

  • Neon Postgresdecks and rooms
  • Drizzle0.45queries
  • Cloudflare R2slides, on pre-signed uploads
  • Better Auth1.7magic-link sign-in

Tooling

  • Bun1.4
  • Biome2.5lint and format
  • Vercelthe web app
Plate03

On the press.How it works

5 steps, in order

From a deck made elsewhere to a room full of phones.

  1. The deck arrives made

    CCC builds the slides and reserves a deck. Each slide image goes straight to R2 on a pre-signed upload.

  2. Every slide is checked

    Completion reads the first 24 bytes of each PNG and checks its size. One wrong slide refuses the whole deck.

  3. Open the room

    The launcher shows a QR code. Clients scan it; nobody signs up.

  4. Present

    The console moves and every phone follows. A phone that drops reconnects and catches up from the last 50 events.

  5. End the meeting

    One button closes the room on every phone. Nothing stays behind for the client.

Plate04

Pulled proof.The code

Refusing a bad slide

app/api/decks/[id]/complete/route.ts · lines 61–77real source
for (const s of manifest.slides) { const key = `decks/${id}/slides/${pad(s.n)}.png`; const header = await getObjectRange(key, 0, 23); if (!header) { return NextResponse.json( { error: `Missing object: ${key}` }, { status: 409 }, ); } const size = readPngSize(header); if (!size || size.width !== s.width || size.height !== s.height) { return NextResponse.json( { error: `PNG size mismatch: ${key}` }, { status: 422 }, ); } }

24 bytes per slide is enough to read a PNG's size, so the check never downloads the deck.

Plate05

The reel.The walkthrough

Watch it run

Software demo coming soonAbout 90 seconds, over the shoulder: the QR scan, two phones following the console, End meeting closing both.
Plate06

Corrections.What it fixes

The problems it solves

  • Decks emailed as PDFs nobody opens.

    The deck is on their phone while you talk.

  • Screen share: lag, and your desktop on show.

    Full-resolution slides on each phone. Nothing leaves your screen.

  • A shared link that changes when the deck changes.

    A pushed version never changes a link already given out.

  • Viewers asked to make an account.

    Scan and watch.

Plate07

Editor's letter.Why I built it

Why it exists

My slide pipeline worked, and its output had nowhere to go but a file.

IAN is that endpoint. I built it as internal infrastructure first and a product second: the decks come from my own system, and the room only has to show them well.

Riso illustration: a slide pipeline ending in a room of phones instead of a file.
Plate08

Against the rack.Better than average

Against the usual tools

Against Zoom or Meet screen share for showing a deck, and DocSend, Pitch and Gamma for sharing one. Their side was read from each vendor's own pages on 2026-09-27.

WhatThe market averageThis app
JoiningA link sent ahead, often by email or calendar invite.Scan a QR code on the presenter's screen.vendor's page
What viewers see on a callA video feed of your screen, limited by bandwidth.The slide itself, full resolution, on their own screen.vendor's page
After a dropped connectionGamma's follow link: the viewer clicks to sync back up.The phone catches up on its own from the last 50 events.vendor's page
After the meetingA deck link stays open until you turn it off or it expires.End meeting closes the room on every phone at once.vendor's page
Plate09

The imprint.How long it took

24 commits in 20 days

commits
24
days with a commit
6
days, first commit to latest
20
2026-09-01commits per day · busiest: 92026-09-20
  1. First commit
  2. All six features landed: the core took four days
  3. Latest commit

Measured: git log of the app's repo, 2026-09-27.

Plate10

Colophon.Where it sits in the ledger

Remote▲ ▼ section · ◀ ▶ project · OK opens