Feat/seed registration questions - #237
Merged
Merged
Conversation
Gives the frontend a form to render before any of it is built. - H1 asks four questions (text, enum, text, bool), three of them mandatory. It is the only hackathon with `register` on, so it is where the join-and-answer flow is exercised: a newcomer reads the questions before joining and sends the answers along with Join. - H2 asks two, both mandatory, and everyone has answered — a closed form, the counterpart to H1's partly-filled one. - H3 asks nothing, which the frontend has to handle as its own case rather than as an empty form. - H4 asks three at cohort scale: 87 of its 102 participants answered, 234 answers in all. The gaps are the point. charles is waitlisted in H1 and has answered nothing, bob answered but skipped the optional `dietary`, and one in seven of H4 never answered at all — "has not filled it in" and "filled it in and left the optional parts blank" are different facts to an organizer, and only people who answered have rows. H4's answers draw from dataForGoodAnswerSeed, a random stream of its own, so the preference draw that follows them is byte-identical. The README documents two things that each cost an hour to find: - The backend must be restarted after seeding. The seed writes casbin rows into the policy table, but the running server loaded its policy at startup and never reloads, so every per-hackathon role the seed granted is invisible to it. The symptom misleads: H1's owner is refused her own hackathon, and ListParticipantAnswers silently returns only her own answers instead of the cohort's. A global admin is unaffected, which makes it look even more like a handler bug. - `Question.options` is a required column, so it is always set — an empty slice for the types that have no choices. Leaving it unset fails with "missing required field", which is not obvious from the call site.
A question arrives from the backend with a numeric type enum that lives under $lib/server and so cannot be imported by a component, which is why this comes in two halves: the kinds and their labels in $lib/utils/question, where a page can reach them, and the enum mapping plus form parsing in $lib/server, where they belong. `parseQuestionForm` checks a submission against the same rules the backend enforces — key shape, lengths, a fixed list needing at least two distinct options — so an organizer gets a sentence they can act on instead of a raw InvalidArgument, while the RPC stays the authority. 33 tests cover it.
A new page under Manage where an organizer adds, edits and deletes the questions their event asks at sign-up, plus the sidebar entry that leads to it. One question per row and one RPC per row, because the backend edits a single question at a time and has no whole-form save — so a failure spoils one row rather than the lot. The part worth knowing: once a question has been answered the backend refuses a type change, any options list, and even re-sending mandatory when it is already true, so a plain label fix would have failed had the form sent everything back. The action therefore reads the answer counts itself and sends only the fields still allowed, and the page locks those controls and says why; relaxing a required question to optional keeps working, since that is the one change the backend still accepts.
Adds /register/[id], where someone answers a hackathon's questions — through Join if they are signing up, SubmitAnswers if they are correcting an answer later. It sits outside /my/hackathon/[id]/ because that layout's hackathon.get refuses non-members and waitlisted callers, who are exactly the people who need it. The dashboard's Join now checks ListQuestions first and sends them here when the event asks anything. A blank answer is omitted rather than sent empty, so an unticked required tick-box reads as unanswered instead of as a quiet "no". Saving does not work yet: both backend write paths reject every answer at parse time (see TODO(backend: answer-upsert-sql)), and a waitlisted participant cannot save at all (TODO(backend: waitlisted-answers)). Both are reported to the user rather than worked around.
Manage Participants gains the read side of the registration form: a "3 of 4 answered" line in the header, and per person either a disclosure listing their answers question by question or a plain "Has not answered". Hidden entirely when the event asks nothing, which is a state the fixture covers. The disclosure sits below the card rather than inside it, because ParticipantCard takes only an `actions` snippet and widening a component three pages share for one of them is the wrong trade. The answered count is computed through the roster, not off the answer map: answers outlive RemoveParticipant, so counting them directly makes "12 of 10 answered" reachable. Both RPCs are swallowed on failure since this page exists to approve and remove people — with a note at the call site that ListParticipantAnswers silently narrows to the caller's own answers without hackathon write, so a stale casbin policy shows a roster where only the organizer appears to have answered.
sabinem
force-pushed
the
feat/seed-registration-questions
branch
from
August 25, 2026 08:42
686affb to
cdc2bcf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Custom registration forms
As an organizer, I can decide what my event asks people at sign-up —
free text, yes/no, or a fixed list of options — and see what everyone answered.
As a participant, I answer those questions when I join, and can come back
and correct them.
What's here
questions. A question people have already answered locks its type, options and
required flag, because the backend refuses to change those.
/register/[id]: the participant's form. Deliberately outside/my/hackathon/[id]/— that layout denies non-members and waitlisted people,who are exactly who needs this page.
something, and stays one click when it doesn't.
and not everyone has answered — so the gaps are visible.
Known gaps (backend, ticketed, not worked around)
time. The form reports this instead of pretending.
ListQuestionsneedshackathon:readagain after the revert inmodify llm changes, so a non-member can't reach the form.Both organizer surfaces work end to end. Neither participant surface can
complete until the first gap lands.