Files
427e7578-d7bf-49c8-aee9-2dd…/MVP_PLAN.md
2026-03-18 21:28:06 +01:00

7.0 KiB
Raw Permalink Blame History

Minimal MVP Plan: P2P Poll App

Recommendation

Build the MVP with:

  • Vanilla JS or TypeScript
  • Vite for local dev/build
  • Yjs for shared state
  • y-webrtc for browser-to-browser sync
  • y-indexeddb for local persistence/offline recovery

This is the best fit for a small poll app because the data model is tiny, concurrent edits are possible, and Yjs already solves merge/conflict handling while y-webrtc gives a direct P2P transport with only signaling infrastructure.

Why This Stack

  • Best balance of simplicity and correctness
  • Clients connect directly over WebRTC
  • No custom conflict-resolution logic needed
  • Easy to add offline persistence
  • Good fit for a single shared document/room

PeerJS

  • Very simple transport layer
  • But you must design and maintain your own replication and merge rules
  • Fine for demos with a host-authoritative peer, weaker for true collaborative editing

GUN

  • Fast to prototype realtime shared state
  • But official tutorials commonly use a GUN peer/relay server for shared data
  • For this app, its data model is less explicit than a CRDT and gives less control over vote semantics

Automerge

  • Very capable CRDT and local-first model
  • But heavier than needed for a single small poll
  • Better choice if the project is expected to evolve into a richer collaborative app

MVP Scope

Ship exactly one shared poll per room.

Included:

  • Join a poll by opening a shared URL
  • Add a new option
  • Vote for one option
  • Change your vote
  • Realtime updates across peers
  • Local persistence in the browser
  • Basic connection status UI

Excluded:

  • User accounts
  • Multiple polls per room
  • Option deletion/editing
  • Authentication/authorization
  • Rich presence
  • Advanced discovery
  • Production-grade TURN/signaling deployment

MVP UX

Keep the interface intentionally small:

  • Poll title at the top
  • Option list with vote counts
  • One button per option to cast vote
  • Small form to add an option
  • Status line showing connecting, connected, or offline
  • Small label showing which option you voted for

Join model:

  • Room ID in the URL, e.g. /?room=poll-demo
  • Users share the URL manually

Identity model:

  • Generate a local userId once and store it in localStorage
  • Optional local display name, also stored locally

Shared Data Model

Use a single Yjs document per room.

Suggested structure:

  • poll as a Y.Map
  • poll.title as a string
  • poll.options as a Y.Map<optionId, Y.Map>
  • poll.votes as a Y.Map<userId, optionId>

Each option record:

  • id
  • label
  • createdBy
  • createdAt

Important design choice:

Do not store vote counters as mutable shared numbers for the MVP. Instead, derive counts from votes.

Why:

  • A user changing vote becomes a single write: votes[userId] = optionId
  • No double-counting logic
  • Concurrent writes are easier to reason about
  • Rendering counts from votes is trivial at this scale

Sync Model

Networking

  • Use WebrtcProvider(roomId, ydoc)
  • Start with the default public signaling servers for local development
  • If needed, swap to a self-hosted signaling server later without changing the app model

Persistence

  • Use IndexeddbPersistence(roomId, ydoc)
  • This preserves state across reloads and helps when reconnecting after temporary disconnects

Conflict behavior

  • Concurrent option additions merge naturally
  • Concurrent vote changes resolve at the per-user key level
  • Tally is recalculated from the merged vote map

Suggested Project Structure

src/
  main.ts
  app.ts
  state.ts
  sync.ts
  render.ts
  identity.ts
  styles.css
index.html

Responsibilities:

  • sync.ts: create Yjs doc, WebRTC provider, IndexedDB provider
  • state.ts: shared structures, add option, cast vote, selectors
  • identity.ts: local userId and optional name
  • render.ts: DOM updates
  • app.ts: wire events to state and rendering

Implementation Plan

Phase 1: App shell

  • Create Vite app with vanilla TS or JS
  • Add a minimal single-page UI
  • Parse room from the URL
  • Generate and persist userId

Success check:

  • App loads
  • Room ID appears in UI
  • User can type in the form

Phase 2: Shared state

  • Add Yjs document
  • Create root shared maps
  • Seed default poll title if missing
  • Observe document updates and re-render UI

Success check:

  • State exists in one browser tab
  • Refresh keeps local state when IndexedDB is enabled

Phase 3: P2P sync

  • Add y-webrtc
  • Join room based on URL room ID
  • Show connection status from provider events

Success check:

  • Two browsers on different tabs/devices see the same options
  • New options appear in near real time

Phase 4: Voting logic

  • Implement castVote(optionId) as votes.set(userId, optionId)
  • Derive tallies from votes
  • Highlight the local users current vote

Success check:

  • Votes update live across peers
  • Changing vote updates counts correctly

Phase 5: Basic hardening

  • Trim/validate option labels
  • Prevent empty options
  • Ignore duplicate labels case-insensitively for MVP
  • Show simple offline/connecting text

Success check:

  • Basic misuse does not break the UI
  • Reconnect restores updates

Minimal API Surface

These functions are enough for the first build:

  • getRoomId(): string
  • getUserId(): string
  • initSync(roomId): AppSync
  • ensurePollInitialized()
  • addOption(label: string)
  • castVote(optionId: string)
  • getViewModel(): PollViewModel
  • render(viewModel)

Risks And MVP Mitigations

NAT / WebRTC connectivity

Risk:

  • Some peer pairs may fail to connect in restrictive networks

Mitigation:

  • Accept this for MVP
  • Keep signaling configurable
  • Add TURN only if testing shows frequent failures

Small public signaling dependency

Risk:

  • Public signaling is fine for demos, not ideal for production

Mitigation:

  • Treat it as replaceable infrastructure
  • Self-host later if the prototype is kept

Duplicate or messy options

Risk:

  • Users may add near-duplicate entries

Mitigation:

  • Normalize labels
  • Prevent exact duplicates for MVP

No trust/auth model

Risk:

  • Any participant in the room can add options and vote

Mitigation:

  • Accept for MVP
  • Frame rooms as small trusted groups

Estimated MVP Size

For one developer:

  • Initial prototype: 0.5 to 1 day
  • Polished MVP with basic resilience: 1 to 2 days

Rough code size:

  • 250 to 500 lines plus styles

Nice Next Steps After MVP

  • Copy-link button
  • Participant list / presence
  • Vote limit modes: single-choice or multi-choice
  • Option editing/deletion
  • QR code for room join
  • Self-hosted signaling server
  • PWA packaging for better offline behavior

Sources