add proposal

This commit is contained in:
xstraven
2026-03-18 21:28:06 +01:00
parent 42594951f3
commit 2a39d594c7
15 changed files with 2585 additions and 1 deletions

310
MVP_PLAN.md Normal file
View File

@@ -0,0 +1,310 @@
# 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
### Recommended: Yjs + y-webrtc
- 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
### Not recommended for the first MVP
#### 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
```text
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
- PeerJS getting started: https://peerjs.com/client/getting-started
- Yjs collaborative editor guide: https://docs.yjs.dev/getting-started/a-collaborative-editor
- Yjs offline support: https://docs.yjs.dev/getting-started/allowing-offline-editing
- y-webrtc README: https://github.com/yjs/y-webrtc
- GUN tutorial showing shared data via a GUN peer: https://gun.eco/converse
- Automerge network sync: https://automerge.org/docs/tutorial/network-sync/