Free, open-source planning poker for agile teams. No sign-up required.
Features • Tech Stack • Getting Started • Architecture • Contributing • License
Parallax is a real-time planning poker tool that lets agile teams estimate stories together without accounts, passwords, or friction. Create a board, share the link, and start voting — all powered by WebSocket-based live sync.
- Real-Time Voting — See who has voted instantly via WebSocket presence sync. No polling, no page refreshes.
- Multiple Estimation Decks — Fibonacci, Modified Fibonacci, T-Shirt Sizes, Powers of 2, or build your own custom deck.
- Issue Management — Add stories manually, paste Jira links (auto-parsed), or bulk import multiple issues at once.
- Facilitator Controls — Start/reveal/reset votes, manage participants, save estimates, change deck type mid-session.
- Spectator Mode — Stakeholders can watch the session without influencing estimates.
- Built-in Timer — Configurable countdown timer synced across all participants to keep sessions moving.
- No Sign-Up Required — Zero accounts, zero data collection, zero friction. Device-based identity only.
- Auto-Cleanup — Sessions automatically expire after 24 hours of inactivity.
- Re-voting — Change your vote at any time during voting, or re-estimate previously estimated issues.
- Responsive — Works on desktop, tablet, and mobile with an adaptive layout.
| Layer | Technology |
|---|---|
| Framework | Nuxt 4 (Vue 3) |
| Database & Realtime | Supabase (Postgres + Realtime WebSockets) |
| Styling | Tailwind CSS v4 via @tailwindcss/vite |
| Deployment | Vercel (serverless) |
| Language | TypeScript (end-to-end) |
- Node.js 18+
- A Supabase project (free tier works)
git clone https://github.com/whoisarjen/Parallax.git
cd Parallax
npm installCopy the example env file and fill in your Supabase credentials:
cp .env.example .env| Variable | Description |
|---|---|
NUXT_PUBLIC_SUPABASE_URL |
Your Supabase project URL |
NUXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anonymous/public key |
NUXT_SUPABASE_SERVICE_KEY |
Supabase service role key (server-side only) |
NUXT_CRON_SECRET |
Secret for the cleanup cron endpoint |
Open supabase/migrations/001_initial_schema.sql and execute it in your Supabase project's SQL Editor. This creates:
- 4 tables (
boards,participants,issues,votes) - Row Level Security policies (vote hiding until reveal)
- Realtime publication for live sync
- Auto-updating timestamps
In your Supabase Dashboard, go to Database > Replication and ensure the supabase_realtime publication includes boards, participants, and issues. The migration handles this, but verify it's active.
npm run devThe app will be available at http://localhost:3000.
Create Board → Share Link → Team Joins → Vote → Reveal → Discuss → Save Estimate
- Create — Set a session name, choose an estimation deck, enter your display name
- Share — Share the board URL or invite code with your team
- Join — Teammates enter their display name and join (no account needed)
- Vote — Facilitator starts voting on an issue; everyone selects a card independently
- Reveal — Facilitator reveals all votes simultaneously with stats (average, median, mode, agreement %)
- Save — Discuss outliers, re-vote if needed, then save the final estimate
┌─────────────┐ HTTP (initial load) ┌──────────────┐ service_role ┌──────────┐
│ Browser │ ───────────────────────────> │ Nuxt Server │ ──────────────────> │ Supabase │
│ (Vue SPA) │ │ (API routes)│ │ Postgres │
│ │ <─────────────────────────── │ │ <────────────────── │ │
│ │ WebSocket (all updates) │ │ │ │
│ │ <══════════════════════════> │ │ │ │
│ │ Supabase Realtime │ │ │ │
└─────────────┘ └──────────────┘ └──────────┘
- Initial load — Single HTTP fetch for board + participants + issues
- All subsequent updates — Pure WebSocket via Supabase Realtime (zero HTTP polling)
- Three realtime channels:
- Presence — Online/offline status for each participant
- Broadcast — Ephemeral events (vote submitted, votes revealed, timer sync)
- Postgres Changes — DB row changes pushed to all subscribers (new participants, issue updates, etc.)
| Concern | Approach |
|---|---|
| Identity | Device UUID stored in cookie + localStorage (no auth required) |
| Facilitator auth | Server-generated token, SHA-256 hash stored in DB |
| Vote hiding | Row Level Security — votes only readable when voting_state = 'revealed' |
| API writes | All mutations go through Nuxt server routes using service_role key (bypasses RLS) |
| Input validation | Server-side sanitization on all user inputs (display names, issue titles, Jira URLs) |
Parallax/
├── app/
│ ├── assets/css/ # Tailwind CSS v4 theme & utilities
│ ├── components/
│ │ ├── board/ # Board UI (voting area, cards, participants, issues, timer)
│ │ ├── common/ # Shared UI (header, footer, dialogs, toast)
│ │ └── landing/ # Landing page sections
│ ├── composables/ # Vue composables (realtime, voting, timer, identity, etc.)
│ ├── layouts/ # Default + board layouts
│ ├── pages/ # index, my-boards, board/[id]
│ ├── types/ # TypeScript interfaces
│ └── utils/ # Board codes, deck configs, vote stats
├── server/
│ ├── api/boards/ # REST API (CRUD, voting, facilitator actions)
│ ├── api/cron/ # Expired board cleanup
│ └── utils/ # Server Supabase client, input validation
├── shared/ # Constants shared between client & server
├── supabase/migrations/ # Database schema (single migration file)
└── public/ # Static assets (logo, favicon)
All configurable limits live in a single file (shared/constants.ts) so they can be changed in one place:
| Constant | Default | Description |
|---|---|---|
BOARD_EXPIRY_HOURS |
24 |
Hours before an inactive board expires |
HARD_DELETE_AFTER_DAYS |
7 |
Days after soft-delete before permanent removal |
MAX_BOARDS_PER_DEVICE |
10 |
Maximum boards a single device can create |
MAX_PARTICIPANTS_PER_BOARD |
10 |
Maximum participants per board |
Note: The database migration uses a default
INTERVAL '24 hours'forexpires_at. If you changeBOARD_EXPIRY_HOURS, also update the migration or runALTER TABLE boards ALTER COLUMN expires_at SET DEFAULT (now() + INTERVAL '<new value> hours');.
- Push your repo to GitHub
- Import the repository in Vercel
- Add all environment variables in project settings
- Deploy
The vercel.json includes a cron job that runs every 6 hours to clean up expired boards.
Contributions are welcome! Here's how:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Run
npm run buildto verify everything compiles - Commit and push:
git push origin feature/my-feature - Open a Pull Request
- Emoji reactions on vote reveal
- Export estimates to CSV
- Jira/Linear integration for syncing issues
- Custom color themes
- Sound effects on vote reveal
- Analytics dashboard for estimation trends
