This page provides an introduction to the spoker application, describing its purpose as a real-time planning poker tool, its target users, core capabilities, and high-level system architecture. For detailed information about specific subsystems:
Spoker is a real-time collaborative planning poker (scrum poker) application that enables distributed agile teams to estimate story points synchronously. The application solves the problem of geographically distributed teams needing a shared space for estimation sessions without requiring external project management tool integrations.
Distributed agile teams require:
Spoker implements a client-heavy architecture with Firebase as the data backend:
| Component | Technology | Role |
|---|---|---|
| Frontend | Next.js (Pages Router) + React 19 | UI rendering and user interactions |
| State Management | Zustand | Client-side state synchronization |
| Backend | Firebase Realtime Database | Data persistence and real-time sync |
| Authentication | Firebase Auth | Email/password and OAuth (Google, GitHub) |
| UI Library | Chakra UI | Component system and theming |
| Validation | Zod | Runtime type validation for forms |
Sources: package.json1-93 README.md121-143
Spoker defines three distinct user roles with different capabilities:
| Role | Description | Primary Actions |
|---|---|---|
| Owner | Room creator with full permissions | Create rooms, finish votes, configure settings, manage task queue |
| Participant | Team member who estimates tasks | Submit votes, view results when revealed |
| Observant | Observer who cannot vote | View votes and results, configure hide label |
The application targets:
Sources: README.md250-255 SPEC.md20-29
['0', '0.5', '1', '2', '3', '5', '8']Task Lifecycle States:
RoomInstance.task)| Room Type | Access Control | Implementation |
|---|---|---|
| Public | Anyone with room ID can join | RoomInstance.room.isPrivate = false |
| Private | Requires password | RoomInstance.room.password (client-side verification) |
Configurable emoji-based labels hide vote values until reveal:
Each label displays a different emoji instead of the numeric point value.
Sources: README.md17-33 SPEC.md9-31 src/lib/constants/hide-label.ts
Key Architectural Patterns:
src/lib/services/firebase/onValue() listeners automatically push Firebase changes to Zustand storesRouteWrapper enforces authentication and email verificationSources: README.md89-119 Diagram 1 and Diagram 3 from high-level system diagrams
| Tool | Purpose | Configuration |
|---|---|---|
| Biome | Linting and formatting (replaces ESLint + Prettier) | biome.json |
| TypeScript | Type safety | tsconfig.json |
| Turborepo | Build orchestration | Used in pnpm check:turbo |
| Husky | Git hooks for quality gates | .husky/ |
| Commitlint | Enforce conventional commits | commitlint.config.js |
| Service | Purpose | Integration Point |
|---|---|---|
| Sentry | Error tracking and session replay | `@sentry/nextjs` in sentry.client.config.ts |
| Umami | Self-hosted analytics | Embedded in src/pages/_document.tsx |
| Firebase App Check | Security (reCAPTCHA) | src/lib/services/firebase/appCheck.ts |
Sources: package.json35-61 package.json62-84 README.md135-143
spoker/
├── src/
│ ├── lib/ # Core application logic
│ │ ├── components/ # Reusable UI components
│ │ │ ├── spoker-logo/ # Brand logo component
│ │ │ └── auth/ # Auth-related components
│ │ ├── constants/ # Application constants
│ │ │ ├── routes/ # Route definitions (public/private/restricted)
│ │ │ ├── hide-label.ts # Vote hiding emoji options
│ │ │ └── allowed-values.ts # Validation constants
│ │ ├── hooks/ # Custom React hooks
│ │ ├── layout/ # Layout components
│ │ │ └── components/ # Header, Footer, RouteWrapper, Auth
│ │ ├── models/ # Zod validation schemas
│ │ ├── pages/ # Page-level components
│ │ │ ├── hall/ # Room creation/joining interface
│ │ │ ├── room/ # Main voting interface
│ │ │ │ ├── components/ # Room-specific UI
│ │ │ │ └── hooks/ # Room-specific hooks (listeners)
│ │ │ └── home/ # Landing page
│ │ ├── services/ # Firebase service layer
│ │ │ └── firebase/
│ │ │ ├── auth/ # Authentication operations
│ │ │ ├── room/ # Room CRUD operations
│ │ │ └── rules.ts # Security rules generator
│ │ ├── stores/ # Zustand state management
│ │ │ ├── auth.ts # Auth state
│ │ │ └── room.ts # Room state
│ │ ├── styles/ # Chakra UI theme configuration
│ │ ├── types/ # TypeScript definitions
│ │ │ ├── raw-db.ts # Firebase data structure types
│ │ │ ├── room.ts # Room-related types
│ │ │ └── user.ts # User types
│ │ └── utils/ # Utility functions
│ └── pages/ # Next.js pages (routing)
│ ├── _app.tsx # Global app wrapper
│ ├── _document.tsx # HTML document customization
│ ├── index.tsx # Home page route
│ ├── hall.tsx # Hall page route
│ ├── join/[id].tsx # Join room route
│ └── room/[id].tsx # Room page route
├── public/ # Static assets
├── tools/ # Build scripts
│ └── generate-rules.ts # Firebase rules generator
└── config files # next.config.ts, tsconfig.json, etc.
| Directory | Purpose | Key Files |
|---|---|---|
src/pages/ | Next.js routing layer | Thin wrappers importing from src/lib/pages/ |
src/lib/pages/ | Page components | Business logic and UI composition |
src/lib/services/firebase/ | Firebase operations | All database reads/writes |
src/lib/stores/ | Client state | Zustand stores synced with Firebase |
src/lib/types/ | Type definitions | raw-db.ts defines Firebase schema |
Sources: README.md89-119 AGENTS.md24-85
Key Data Flow Patterns:
Critical Functions:
updatePoint(): src/lib/services/firebase/room/update/point.tscheckAllParticipantVoted(): src/lib/utils/roomUtils.tsuseRoomListener(): src/lib/pages/room/hooks/use-room-listener.tsSources: AGENTS.md14-22 SPEC.md66-68 Diagram 3 from high-level system diagrams
The application uses Firebase Realtime Database (not Firestore) with onValue() listeners for automatic updates:
Characteristics:
useEffect cleanup functions| Aspect | Implementation |
|---|---|
| Business Logic | Runs client-side in React components and hooks |
| Validation | Client-side via Zod schemas + Firebase Security Rules |
| Security | Firebase Rules + Firebase Auth + App Check |
| State Management | Zustand stores (no server-side state) |
Trade-off: Simpler architecture and faster development at the cost of less control over business logic enforcement.
Authentication Flow:
RouteWrapper checks authentication state from useAuthStoreState()Sources: SPEC.md98-104 src/lib/layout/components/route-wrapper/ Diagram 4 from high-level system diagrams
The core data model is defined in src/lib/types/raw-db.ts:
Database Path: /rooms/$roomId/
Key Paths:
/rooms/$roomId/users/$uid: Individual user state/rooms/$roomId/task: Current task being voted on/rooms/$roomId/queue: Array of upcoming tasks/rooms/$roomId/completed: Array of finished tasks/rooms/$roomId/config: Room configurationSecurity: Rules generated from src/lib/services/firebase/rules.ts and deployed via pnpm generate-rules
Sources: src/lib/types/raw-db.ts1-35 SPEC.md253-304 Diagram 7 from high-level system diagrams
| Command | Purpose | Implementation |
|---|---|---|
pnpm dev | Development server | Next.js dev mode on port 3000 |
pnpm build:turbo | Production build | Turborepo orchestrated build |
pnpm check:turbo | Quality checks | Runs biome:check + type:check |
pnpm generate-rules | Firebase rules | Generates JSON from TypeScript rules |
Pre-commit hooks (.husky/pre-commit) enforce:
biome check on staged filestsc --noEmitRequired for Firebase integration:
Configuration Files:
~/lib/*)Sources: README.md144-205 package.json13-33 Diagram 6 from high-level system diagrams
Spoker is a real-time planning poker application built with a client-heavy architecture on Next.js and Firebase. The system prioritizes:
onValue() listenersThe codebase follows a layered architecture:
src/lib/pages/ and src/lib/components/src/lib/stores/src/lib/services/firebase/For more detailed information about specific subsystems, see the child pages under this section and the Architecture section.
Sources: README.md1-337 AGENTS.md1-450 SPEC.md1-367
Refresh this wiki