A modern, secure, and auditable ranked choice voting application built with React, Bun, and SQLite.
- Instant Runoff Voting (IRV) for single-winner elections
- Secure token-based voting with unlinkable ballots
- Full transparency with published receipts and audit data
- Drag-and-drop ballot interface with accessibility support
- Real-time results with round-by-round visualization
- Deterministic tie-breaking for reproducible results
- React 18 + Vite + TypeScript
- TailwindCSS with neobrutalism design system
- Drag-and-drop voting interface with @dnd-kit
- TanStack Query for state management
- Bun runtime with Hono framework
- SQLite database with WAL mode
- Zod validation and JWT sessions
- Rate limiting and security middleware
- Pure TypeScript IRV implementation
- Deterministic tie-breaking with seeded PRNG
- Extensible for STV (multi-winner) support
- Bun runtime for the API
- Node.js 18+ for the frontend
- Windows, macOS, or Linux
-
Clone and setup
git clone <your-repo> cd tessera
-
Install counting package
cd packages/counting npm install npm run build cd ../..
-
Install and setup API
cd apps/api npm install cp .env.example .env # Edit .env if needed for your setup bun run init-db.js # Initialize database cd ../..
-
Install frontend (if needed)
cd apps/web npm install # This may take a while cp .env.example .env cd ../..
-
Start the API server
cd apps/api bun run dev
-
In another terminal, start the frontend
cd apps/web npm run dev
-
Open your browser
- Frontend: http://localhost:5173
- API: http://localhost:3000
- Create Account - Register at
/login
- Create Election - Set title, description, and voting parameters
- Add Candidates - Upload candidate info and photos
- Generate Tokens - Create secure voting tokens for distribution
- Open Voting - Make election live for voters
- Monitor Progress - Track votes and manage election
- Close & View Results - End voting and publish transparent results
- Visit Election - Go to
/e/election-slug
- Enter Token - Use your secure voting token
- Rank Candidates - Drag and drop to create your ranking
- Cast Vote - Submit your ballot
- Save Receipt - Keep your receipt hash for verification
- View Results -
/e/election-slug/results
- See round-by-round IRV counting - Check Receipts -
/e/election-slug/receipts
- Verify vote inclusion - Download Data - Get ballots JSON and audit information
- Replay Counting - Verify results with deterministic algorithm
- Unlinkable Ballots - Votes cannot be traced back to voters
- Receipt Verification - Voters can confirm their vote was counted
- Rate Limiting - Prevents abuse and spam
- Token-based Access - One-time use tokens prevent multiple voting
- Deterministic Counting - Results can be independently verified
tessera/
├── apps/
│ ├── api/ # Bun + Hono backend
│ └── web/ # React + Vite frontend
├── packages/
│ └── counting/ # IRV/STV algorithms
└── ...
# Install all dependencies
bun install
# Development (all apps)
bun run dev
# Build for production
bun run build
# Type checking
bun run typecheck
# Run tests
bun run test
The application uses SQLite with WAL mode for simplicity and reliability:
- Schema - See
apps/api/src/db/schema.sql
- Migrations - Run with
bun run migrate
- Backups - Copy the SQLite files
API (.env)
DB_PATH=./data/tessera.sqlite
SESSION_SECRET=your-super-secret-key
SITE_URL=https://yourdomain.com
NODE_ENV=production
Web (.env)
VITE_API_URL=https://api.yourdomain.com
-
Build applications
bun run build
-
Deploy API - Any Node.js/Bun hosting (Railway, Fly.io, etc.)
-
Deploy Frontend - Static hosting (Vercel, Netlify, etc.)
-
Set up database - Run migrations on production
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
MIT License - see LICENSE file for details
- Create an issue for bugs or feature requests
- Check the documentation in
/docs
(coming soon) - Review the specifications in
CLAUDE.md