Sit together. Do your own work. Bonfire is a shared focus timer: you light a temporary shared fire, invite a few people, and everyone sits around it on one timer.
- On the home page, pick a focus length (25, 30, 45, 60 or custom) and how many rounds come before the long rest, then start a bonfire. Or join a bonfire with a 6-character code or a shared link.
- Choose your name. Guests are offered a generated name such as "Sleepy Otter", kept per browser. The creator can also give the Bonfire an optional name, such as "Deep Work".
- You take a seat around the fire as a small illustrated character. Up to 6 people can gather.
- Everyone shares one timer: focus, short rest, long rest. A thin line under the time shows how much of the current phase has passed, and small marks show the round within the set.
- Step away when you are done. The Bonfire keeps going for everyone else, and you can come back with the link.
- The creator can End Bonfire for everyone. The link then shows that the fire has settled.
Bonfires are temporary. There are no feeds, followers, or public room lists.
- Accounts are optional. Guests can create and join Bonfires. Signing in (GitHub or Google) adds a persistent identity and a profile page with focus stats. A completed focus is credited to every signed-in person holding a seat when it completes, in any Bonfire.
- Pomodoros today. The room, Home and your profile show how many pomodoros you completed today, counted across every Bonfire and by your local day. Signed-in counts come from your own logs; for guests this browser keeps the count. The ending screen shows how many pomodoros that fire held.
- Six people maximum. The limit is enforced by the database, not just the UI.
- Settings. Defaults are 25 · 5 · 15 (focus, short rest, long rest in minutes) with a long rest every 4 rounds. Rests chosen on Home follow the focus length (5/15 up to 30 minutes, 10/20 up to 60). Exact durations, mode and the Bonfire name are changed in the room. The room shows its name (or "Sleepy Otter's fire" when unnamed) and configuration quietly at the top.
- Silent by default. No ambient sound or phase-end chime plays until you turn it on from the sound icon. Ambient sounds (rain, brown, pink, white noise) are generated with the Web Audio API and are local to each person.
- Sharing uses the link or the join code. Link previews read "Alex is inviting you to Deep Work", built only from values stored on the Bonfire; the preview image URL changes when the name or settings do. Rooms are marked
noindex.
| Mode | Who can start, pause, skip, and change durations |
|---|---|
| Focus (default) | Only the person who lit the Bonfire |
| Jam | Everyone holding a seat around the fire |
The creator switches modes in settings. Being present is never enough to control the timer: every command is authorized by the database, using the creator's token or a participant's seat credential.
Bonfire v2 is server-authoritative. Clients send commands; the database decides what happened; everyone renders what the database declared.
Client
→ PostgreSQL RPC (validate, authorize, transition, persist)
→ AFTER INSERT/UPDATE trigger → pg_net HTTP POST
→ Supabase Edge Function (bonfire-relay)
→ Supabase Realtime broadcast: state_update on channel bonfire:{id}
→ every client
- Timer. The timer is clock based. The database stores
time_leftandstarted_at, and each client computes the remaining time against the server's clock (offset measured once per page load from/api/time). There is no per-second server timer. When a client sees a phase reach zero it callscomplete_phase, and the database checks that the time has really run out before moving to the next phase; the client asks again if the phase is still at zero shortly after. - Consistency. Every change bumps
bonfires.version, and clients ignore states older than the one they hold, since relay broadcasts can arrive out of order. Clients re-read the Bonfire whenever the channel (re)subscribes and after a rejected command, so updates missed while loading or offline are caught up. - Commands are
SECURITY DEFINERPostgreSQL functions:create_bonfire,start_timer,pause_timer,skip_phase,complete_phase,change_settings,toggle_mode,end_bonfire,set_bonfire_details,resolve_join_code. Clients never write to thebonfirestable directly and never broadcast Bonfire state. - Seats.
join_bonfiregives each participant a credential and one of six seats, kept alive by a heartbeat (touch_bonfire_seat) and released byleave_bonfire. Seats are stable, so people keep their place when others arrive or leave. - Presence (Supabase Realtime Presence on the same channel) decides who is drawn around the fire. It is never used for authorization.
- Privacy. The creator's
initiator_tokenis not readable by clients. It is excluded from the column grants, from command results, and from the relay payload. Pomodoro logs are readable only by their owner and are written only bycomplete_phase.
The full design is in docs/bonfire-v2-architecture.md, and the product and UI rules are in docs/bonfire-product-spec.md.
- Next.js 14 (App Router), React 18, TypeScript
- Tailwind CSS plus plain CSS for the scene (fire, characters, and motion are CSS and inline SVG)
- Supabase: PostgreSQL, Auth, Realtime (broadcast and presence), Edge Functions,
pg_net @vercel/ogfor link preview images- Vitest and Testing Library
- Deployed on Vercel
app/
page.tsx Home
bonfire/[id]/ Bonfire room (server page + metadata)
api/bonfire/ Create and read Bonfires
api/og/ Link preview images
api/time/ Server clock for the countdown
api/cleanup/ Daily cron route (removes stale v1 sessions)
login/, auth/callback/ Optional sign-in
profile/[username]/ Personal focus stats
components/
bonfire/ Room: scene, characters, fire, timer, controls, menus
home/ Home and its illustrated objects
hooks/ useBonfire, useBonfireChannel, usePresence, useSeat, useCountdown, useToday, ...
lib/ Command wrappers, seats, characters, names, brand, timer, server clock, today count, audio
supabase/
migrations/ Database schema and RPCs
functions/bonfire-relay/ Edge Function that publishes state_update
docs/ Product spec and v2 architecture
The v1 routes (/session/[id], /explore, /api/session) are still in the repository as a fallback. They are not part of the Bonfire v2 flow.
Requirements: Node.js 20 and npm, a Supabase project, and the Supabase CLI (used through npx supabase).
-
Install
git clone https://github.com/MinitJain/bonfire.git cd bonfire npm install -
Environment
cp .env.local.example .env.local
Variable Used for NEXT_PUBLIC_SUPABASE_URLSupabase project URL NEXT_PUBLIC_SUPABASE_ANON_KEYSupabase anon key NEXT_PUBLIC_APP_URLShare links and preview images ( http://localhost:3000locally)SUPABASE_SERVICE_ROLE_KEYServer-only admin client for /api/cleanupCRON_SECRETAuthorizes the /api/cleanupcron requestNEXT_PUBLIC_GA_MEASUREMENT_IDOptional Google Analytics 4 ID -
Database
npx supabase link --project-ref <your-project-ref> npx supabase db push
This applies everything in
supabase/migrations/, including thepg_netandpgcryptoextensions. -
Realtime relay
Deploy the Edge Function. It uses the
SUPABASE_URLandSUPABASE_SERVICE_ROLE_KEYsecrets that Supabase provides to Edge Functions by default.npx supabase functions deploy bonfire-relay
The trigger finds the function through the
bonfire_relay_configtable. Migration022_set_relay_config.sqlfills it with the original project's values, so on your own project point it at your URL and anon key (in the SQL editor):update public.bonfire_relay_config set value = 'https://<your-project-ref>.supabase.co' where key = 'supabase_url'; update public.bonfire_relay_config set value = '<your-anon-key>' where key = 'supabase_anon_key';
Without this, commands still work but other clients will not receive live updates.
-
Sign-in (optional)
To enable accounts, turn on GitHub and/or Google under Supabase Authentication, Providers. Guests do not need this.
-
Run
npm run dev
Open http://localhost:3000.
| Command | What it does |
|---|---|
npm run dev |
Start the development server |
npm run build |
Production build |
npm run start |
Serve the production build |
npm run lint |
ESLint (next lint) |
npm run typecheck |
TypeScript (tsc --noEmit) |
npm test |
Run the Vitest suite once |
npm run test:watch |
Vitest in watch mode |
Before opening a pull request, run:
npm run typecheck
npm run lint
npm test
npm run buildUnit tests live in __tests__/ and cover the timer and countdown, state ordering, seat layout, character appearance, scene participants, command wrappers, room controls, number inputs, names, the home setup, the "today" count, silent-by-default audio, and invitation copy. CI (.github/workflows/ci.yml) runs lint, tests, typecheck, and build on pull requests to main and develop.
Read CLAUDE.md first. It describes the product boundaries, the visual direction, and the rule that UI work must not change the server-authoritative architecture. Please keep writing in the repository free of em dashes.