Live polling for talks. Show a QR code, let the room answer on their phones with no login, then reveal results on the projector β with ranked-choice runoffs, word clouds, and breakdowns by who's in the audience.
Create a survey Β· no account needed
- Create β hit the homepage, name your survey. You get a secret admin link (bookmark it β it's the only way back).
- Build β add and edit questions as a private draft, then click Save questions to put new questions on deck. Mark ones like "What's your profession?" as Demographic so you can group everything else by them later.
- Share β put the fullscreen join slide (
/s/<slug>/present) on the projector. It shows a big QR code + short URL. - Release and collect β click Release to audience on each saved question when you reach it in the talk. Audience pages poll for newly released questions and present them one per screen. Each phone gets a cookie so answers are one-per-device and editable; no accounts.
- Reveal β flip Show results to audience. Open
/s/<slug>/resultson the projector; it live-updates every 3s. Use Group by to split every chart by a demographic question, or click a group chip to isolate it. - Co-create β participants suggest complete questions and upvote others from the intro or thank-you screen. In the admin Proposals tab, review a draft, edit it, then approve it to append a live question. Finished participants see an invitation to answer newly added questions rather than being moved away from the community queue.
Participants can propose any supported question type, including options, scale bounds, or matrix configuration. Pending proposals are public, sorted by upvotes (oldest first on ties), and refresh every 5 seconds while the page is visible. Each device can toggle one upvote per proposal; submitting a proposal does not automatically vote for it. Proposals do not require answering the survey first, and participants may upvote their own ideas.
The presenter sets required, demographic, and result-visibility flags during review. Approval is manual regardless of vote count, appends the edited question on deck, and removes its proposal from the pending queue. The presenter releases it separately when ready. Votes are not survey answers. There are no comments, participant edits, rejection actions, or automatic approvals.
Closing Accepting responses also closes proposals and voting, but leaves pending ideas readable. Admins may still approve while closed; participants can answer those questions when the survey reopens. Clearing responses keeps proposals and votes; deleting the survey removes them.
Proposal prompts are limited to 500 characters. Choice questions require 2β20 distinct nonempty options of up to 120 characters each. Scales use integer bounds between -100 and 100 and contain 2β21 steps. Matrix questions use a 2Γ2 grid, a nonempty subject label of up to 120 characters, four nonempty endpoint labels of up to 60 characters, and up to 100 optional comparison-point labels of 120 characters each. Submission and approval requests are limited to 16 KB.
| Type | Respondent sees | Results visualization |
|---|---|---|
| Multiple choice (one) | Tappable cards | Colored bar chart; grouped bars when faceted |
| Multiple choice (many) | Tappable cards | UpSet intersection plot with an option-total bar toggle; one plot per group when faceted |
| Scale / rating (e.g. 1β5, 0β10 NPS) | Number grid | Heat-colored histogram + animated mean |
| Ranked choice | Tap-to-rank list with reorder | Instant-runoff animation β step through elimination rounds, or switch to Borda points / first choices. Per-group winners when faceted |
| Word cloud | Single word/phrase | Sized, tilted word cloud |
| Free text | Textarea | Sticky-note wall |
| Emoji reaction | Emoji grid | Floating emoji bubbles scaled by count |
| 2Γ2 matrix | Drag/touch placement of a creator-labeled symbol anywhere in the plane, with tap and keyboard fallbacks, axis endpoints, and optional comparison points | Toggleable placement scatter plot and density heatmap with the same comparison points; one matrix per facet group |
- Save questions β persist question additions, edits, deletions, and reordering after reviewing the draft; newly saved questions remain on deck
- Release to audience / Move on deck (per question) β immediately control whether a saved question can be seen and answered without publishing other unsaved edits; moving a question back on deck preserves answers already collected
- Accepting responses β close the survey when you move on
- Show results to audience β survey-wide reveal toggle
- Hide results (per question) β keeps a question out of the audience view even when results are on
- Audience can group results β let respondents use the facet controls too (off by default, keeps the projector clean)
- Focus one question β projector-sized single-question view for pacing a talk
- Proposals β review the audience's most-upvoted question drafts, edit, and approve
- CSV export, clear responses, delete survey
Matrix questions have a creator-defined subject label for the symbol each
respondent places. The respondent can drag it with a mouse, touch, or stylus,
tap/click a destination, or use the arrow keys for precise movement. Answers are
stored as normalized coordinates measured from the top-left: x increases from
left to right and y increases from top to bottom. Merely viewing the centered
unplaced symbol does not answer the question; the existing Required setting
still controls whether it may be skipped.
Comparison points are optional read-only context, not additional answers. Each one appears in its cell as a symbol connected to a label, and question creators can drag the symbol and label independently to avoid visual overlap while keeping the full label inside its cell. Their relative placement is reused in the builder, respondent grid, scatter results, and density results. Each comparison point uses the same shape and color in the grid and retained legend; its legend entry includes a small highlighted grid showing its cell.
Results can switch between plotting every submitted placement and a 12Γ12
density heatmap. Exact overlapping scatter points receive small deterministic
offsets so repeated positions remain visible. CSV exports use stable values such
as x 0.750, y 0.250 from top-left. Existing { row, column } answers remain
compatible: they are interpreted at the center of the original quadrant and
canonicalized to continuous coordinates when submitted again.
Rendering mermaid diagram...
Security model: the admin key is a 28-char random token stored only as a
SHA-256 hash. Respondents are identified by an HttpOnly cookie; clearing it or
switching devices allows a second response or another proposal vote (acceptable
trade-off for a login-free live poll β a "welcome back" banner nudges returning
devices to edit instead). Public proposal payloads never expose device
identifiers. Votes are unique per device and proposal, not per verified person;
this is not strong abuse prevention.
Approval claims the pending proposal and appends its question on deck in a single SQLite write transaction. Regular builder edits stay local until Save questions is clicked. Release actions update only the persisted release flag, so they never publish unrelated draft edits. Saves compare their last persisted question snapshot to prevent stale tabs from overwriting an approval or release; conflicts retain local edits and offer a reload rather than silently losing questions.
Questions saved before progressive release was introduced are treated as released, preserving existing survey behavior. Public survey and results APIs omit on-deck questions, and crafted response requests cannot answer them. Admin results and CSV exports retain the full question set. If a released question is moved back on deck, its stored answers remain available if it is released again.
index.ts Hono server: shell routes, public + admin API, QR SVG
backend/db.ts SQLite schema, hashing, random ids
backend/surveys.ts CRUD, response upsert, answer sanitization
backend/aggregate.ts per-type aggregation, pick-many intersections, instant-runoff, facet bucketing
backend/proposals.ts proposal normalization, queries, approval workflow
backend/proposal-sql.ts proposal/vote schema and atomic mutation statements
backend/errors.ts expected API error types
shared/types.ts Question/answer/result types shared by client & server
shared/questions.ts shared question defaults and proposal validation
frontend/
root.tsx HTML shell
index.tsx React mount
lib/api.ts typed fetch helpers
lib/useProposals.ts abortable live proposal polling
components/App.tsx path router
pages/Home.tsx create a survey
pages/Admin.tsx build Β· share Β· results tabs
pages/Respond.tsx one-question-per-screen flow
lib/respondFlow.ts active-question reconciliation across live releases
pages/Results.tsx public live results
pages/Present.tsx fullscreen QR join slide
components/builder/ QuestionEditor and matrix configuration editor
components/proposals/ participant composer, proposal cards, admin review
components/respond/ QuestionInput, ranked drag/tap, and continuous matrix placement
components/charts/ Charts, matrix scatter/density results, ResultsView (polling, facets)
The tests use Deno's built-in runner and real in-memory SQLite through
node:sqlite; the test configuration redirects only the Val Town SQLite import.
No live survey data is used. The placeholder environment value lets the existing
Val Town asset utility initialize without real credentials.
VAL_TOWN_API_KEY=local-test-placeholder deno test --allow-import --allow-env=VAL_TOWN_API_KEY \ --config tests/deno.json tests/api_test.ts shared/questions_test.ts backend/aggregate_test.ts backend/proposal-sql_test.ts \ frontend/lib/questionDraft_test.ts frontend/components/MatrixCellReferences_test.ts \ frontend/components/respond/MatrixInput_test.ts frontend/components/charts/MatrixHeatmap_test.ts