Define the core data structures for polls, options, votes, and their sync behavior across peers. Polls are owned by the creating user and stored on their device (and cached by participants).
Design
Poll Schema
interfacePoll{id: string;// uuid
ownerId: string;// creator's public key
title: string;description: string;anonymous: boolean;// set at creation, immutable
status:'draft'|'open'|'closed';visibility:'private'|'link'|'public';// who can view via URL
createdAt: number;closedAt?: number;options: Option[];votes: Vote[];roles: RoleAssignment[];}interfaceOption{id: string;text: string;addedBy: string;// user ID
addedAt: number;}interfaceVote{optionId: string;voterId: string|null;// null if poll.anonymous === true
timestamp: number;signature: string;// proves vote authenticity
}interfaceRoleAssignment{userId: string;role:'viewer'|'participant'|'moderator';}
Anonymity
When anonymous: true, votes store voterId: null. The owner's device may transiently see the sender's peer ID during live submission, but the identity is not persisted. Anonymity means: hidden from other participants and public snapshots. It is not cryptographic anonymity from the poll owner.
When anonymous: false, voterId is the voter's public key
This flag is set at poll creation and cannot be changed after any votes are cast
Visibility
private: Only users with assigned roles can access. Poll link requires role assignment.
link: Anyone with the poll link can view as a viewer. No directory listing.
public: Poll snapshot is published to the server. Discoverable via direct link (no directory listing yet — can be added with future discovery feature).
Roles & Permissions
Action
Viewer
Participant
Moderator
Owner
View poll & results
✅
✅
✅
✅
Add options
❌
✅
✅
✅
Vote
❌
✅
✅
✅
Add/remove users
❌
❌
✅
✅
Start/stop poll
❌
❌
✅
✅
Delete poll
❌
❌
❌
✅
Owner is implicit (poll.ownerId === userId); not stored in roles[]
RoleAssignment entries in poll.roles[] grant viewer, participant, or moderator access
Users without a role assignment who connect via link get viewer by default
Permission checks happen both client-side (UI gating) and owner-side on message receipt (owner validates before applying any mutation)
Role changes are broadcast to all connected peers
Sync Strategy
Owner is the source of truth
On connect: owner sends full poll snapshot
Changes (new vote, new option, role change) are sent as incremental messages
Participants cache poll locally for offline viewing
Plan
Define TypeScript interfaces for Poll, Option, Vote, RoleAssignment
Create poll store (Svelte store + IndexedDB)
Implement poll CRUD operations locally
Implement sync: snapshot on connect, incremental updates
Enforce anonymity invariant (voterId null when anonymous)