Add date-ranked-choice question type (Doodle-style scheduling) #1
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
A new question type for feedback forms that lets the author offer a list of date/time options and asks each participant to rate how well each option would fit them (Doodle-with-ratings style).
User-facing behaviour (participant)
On
/f/<slug>, this question renders as a list of date/time options. For each option, the participant picks a rating from a small scale:(Exact scale + labels TBD — could also be 3-point: works / maybe / no. Resolve before implementation.)
UI ideas:
scalequestion)Mobile: column-stack.
Author-facing behaviour (admin)
In the FormBuilder (
src/lib/components/FormBuilder.svelte):date_ranked_choiceJSON shape draft (under
FeedbackQuestionSchema):Answer shape:
Results / aggregation
Admin Results tab (
Results.svelte+src/lib/server/results.ts) shows for each option:Export (CSV/JSON) includes the per-option ratings under the question id.
Open design questions (resolve before coding)
live_results_enabledflag governs whether participants can see the aggregate after submitting. Reuse, no new flag.Scope
src/lib/schemas.ts— extendFeedbackQuestionSchemadiscriminated union withdate_ranked_choicevariant; add answer shape to submission schemasrc/lib/components/FormBuilder.svelte— new question-type editor + option listsrc/routes/f/[slug]/+page.svelte— renderer for the new question typesrc/lib/server/results.ts— aggregation logic (averages, distributions, sort)src/lib/components/Results.svelte— display block for date-ranked aggregatessrc/routes/api/admin/feedback/[id]/export/+server.ts— include in CSV/JSON exportOut of scope (later)
Acceptance
date_ranked_choicequestions in the visual builder/f/<slug>rates each option, submits, sees thank-you (or live results if enabled)How to work
Branch off main when you grab this. Probably one PR / one branch with multiple commits (schema, builder, renderer, results, export). Smoke on the live site after merge.
Design questions resolved (m + paul/head, 2026-05-06):
scale: { min: 1, max: 5, min_label: '...', max_label: '...' })<input type="datetime-local">(browser handles conversion).allow_partial: Defaulttrue. Participants can leave options blank. Ifrequired: trueis also set on the question, blank = blank for that option but the question itself counts as answered iff at least one option got a rating (TBD by implementer — pick the sensible default and document).Implementation can proceed. Ready for hire.
Done — date_ranked_choice ready for review
Branch:
mai/hermes/date-ranked-choice(compare)Locked-in design (from your 2026-05-06 comment)
allow_partial: truedefaultWhat landed
Schema —
src/lib/schemas.tsdate_ranked_choicevariant in theFeedbackQuestionSchemadiscriminated union:{ options: [{ id, start, end?, label? }, …], scale?: { min_label?, max_label? }, allow_partial? }. Times are ISO 8601 with offset; option ids are 1-64 chars[A-Za-z0-9_-], must be unique; 2-50 options per question.Record<optId, 1|2|3|4|5|null>shape for the per-option rating map.Aggregation —
src/lib/server/results.tscount,mean, andhistogram[1..5]from the rating maps. Question-levelcount= submissions that rated at least one option.results.test.ts(5 cases) wired intobun test.Export —
src/routes/api/admin/feedback/[id]/export/+server.tsdate_ranked_choicequestion into one column per option, named<qid>[<optid>]. Empty cell when an option was skipped or unrated.Author UI —
src/lib/components/FormBuilder.svelteDate ranked choicein the type picker. Per-option editor: datetime-local Start (required), datetime-local End (optional), free-text label. Below the list: optional rating-1 / rating-5 label fields, and anAllow participants to skip individual optionstoggle (default on). Local↔UTC helpers keep storage in UTC.Participant UI —
src/routes/f/[slug]/+page.svelte(date · optional label)on the left, 1-5 button group + skip—button on the right. Mobile stacks. Required check: "at least one rated" forrequired: true; "all rated" whenallow_partial: false. Dates render in viewer's local TZ viaIntl.DateTimeFormat.Results UI —
src/lib/components/Results.svelteVerification
bun run check— 0 errors. 32 warnings (26 pre-existing + 6 a11y label-has-associated-control on new FormBuilder labels — same pattern as the existing builder labels, so consistent with codebase style. Worth a follow-up sweep if you want zero a11y warnings).bun run test— 25 pass, 0 fail (5 new aggregation tests).bun run build— succeeds.Commits
91098e0schema: date_ranked_choice question type439b030server: aggregation + export + tests5ef08e5UI: builder + participant renderer + results displayParallel safety
Did not touch
src/routes/admin/feedback/new/+page.svelte(cronus). Rebased onto currentmainafter cronus'smai/cronus/builder-on-newlanded — no conflicts.Smoke-test path (post-merge)
/admin/feedback/new→ add a question, switch type toDate ranked choice, add 2-3 datetime options, save./f/<slug>in another browser, rate the options, submit./admin/feedback/<id>→ Results tab shows the ranked list with per-option histograms.<qid>[<optid>].Out of scope per brief: calendar invites, deadlines/auto-close, two-pass voting, cross-respondent heatmap.