Otobong Okoko
Back to lab
AI ProductStatus: Live

Tyck

A stage timer for conferences and services that you run by talking to it. Type or say "add five minutes to the keynote", or photograph a printed schedule and get a working run-sheet.

View project (opens in a new tab)

Tech Stack

  • SvelteKit 2
  • Svelte 5
  • TypeScript
  • Tailwind 4
  • WebSockets
  • Postgres
  • Redis
  • Claude API
  • Stripe

Problem

Whoever runs the clock at a live event is usually doing three other jobs. Stage timers expect that person to stop, find the right control and click through a form while a speaker is running over. The schedule itself tends to arrive as a printed sheet or a photo in a group chat, so someone retypes it by hand before the event starts.

I wanted a timer where changing the plan takes one sentence, and where the schedule you were handed becomes the run-sheet without retyping.

It began as a favour. I volunteer as media lead for a local community, and we were paying for a subscription tool just to manage speaker time on stage. The first version was a single HTML page on Cloudflare Pages that did only that. Tyck is what it grew into.

Approach

Tyck splits into two views. The controller runs the event: an agenda of segments with skip and restore, plus and minus time, an estimated end time and an on-time status. The viewer is a full-screen countdown for the stage, opened from a link or QR code with no sign-in, so a volunteer can put it on a confidence monitor in seconds. The controller can also push a message to the speaker at three levels of urgency.

Plain-language commands go to Claude with a system prompt that returns JSON. The server never trusts that output directly. It checks the action against a whitelist of fifteen commands and treats anything else as unknown, clamps every duration to between one second and 24 hours, and clamps the confidence score before anything touches a timer. Photo import uses the same pattern with Claude's vision input: photograph a schedule, review the extracted segments, save it as a template. Voice input runs on the browser's Web Speech API.

The timer is server-authoritative. One process owns the clock and broadcasts full state every second, so a late-joining screen is correct straight away and two controllers cannot drift apart. Rooms live in memory, which raised a real question: what happens when the server restarts in the middle of a service? A controller that still holds the true state replays it to the server, with a guard against restore loops. If the connection drops, the client falls back to a local timer and elects a leader across open tabs so only one of them drives.

It began on PartyKit and Supabase. I later moved it to a VPS and replaced PartyKit with a 324-line Node WebSocket server that speaks the same JSON protocol, so the client code did not change. Socket access uses short-lived HMAC tickets that encode the caller's role, which means the WebSocket server never has to query the database or believe a role the client claims.

Walkthrough

The controller: live countdown preview, agenda with skip and restore, estimated end time, and the AI Assistant tab for plain-language commands.
Four ways to build a run-sheet. Photo import is the recommended path because most schedules arrive as a picture.

Key Learnings

  • Treat model output as untrusted input. The whitelist and the clamps are what make a language interface safe to point at a live event.
  • A clock that many screens share needs exactly one owner. Server authority with a full-state broadcast removed a whole class of sync bugs that merging client state would have created.
  • In-memory rooms are fine if you design the restart path. Letting a trusted client re-seed the server was simpler than persisting every tick.
  • Keeping the wire protocol stable made the move off PartyKit a server swap instead of a rewrite.
  • The overtime display freezes at 61 minutes, measured in wall-clock time so it survives a restore. Live events always find the edge cases.