#352 Validate Answer Values Against Question Options And Add Length Limits - #433
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
2 open — 2 🟠 Medium (see inline)
⚠️ Verceldeployment red — Neon branch quota, not a review finding. See .claude/docs/PIPELINE.md → Preview-database concurrency.
Revision — Cycle 1fixed R1-M1, R1-M2 · 980f464 |
cielbellerose
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · approved
0 open — clean
⚠️ Verceldeployment red — Neon branch quota, not a review finding. See .claude/docs/PIPELINE.md → Preview-database concurrency.
980f464 to
4bd58d3
Compare
Revision — human-directed rebaserebase: lib/constants.ts (comment merge — dropped stale multi-line comment dev already condensed), lib/utils.ts (comment merge — kept new All conflicts were dev's PR #418 ( |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 4 · approved
0 open — clean
Adds a shared getAnswerValueError validator (lib/constants.ts) enforcing choice membership, single-vs-multi value counts, and per-value/array length caps. Both answer-writing actions call it as the real backstop; both applicant editors call it inline so the client shows the same message before round-tripping to the server. Option count/length are capped via the existing validateOptions refinement so an admin can't author a choice that's unselectable by the new answer-side limits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Distinguish a re-thrown { error } from a genuine unexpected throw so
autosave catches never leak an authorization guard's raw message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6926b61 to
f7602f8
Compare
Revision — Cycle 4rebase-only cycle (already approved, clean) · onto origin/dev · f7602f8 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 5 · approved
0 open — clean
Closes #352
Summary
options, no "exactly one value" rule for single-value question types, and no length limits anywhere.getAnswerValueError(question, value)(lib/constants.ts), that both answer-writing server actions call as the real backstop, and both applicant editors call inline so the client shows the exact same message before ever hitting the network.validateOptionsrefinement so an admin can't author a choice that's unselectable by the new answer-side limits.Changes
lib/constants.ts— new constants (ANSWER_SHORT_MAX_LENGTH,ANSWER_LONG_MAX_LENGTH,ANSWER_OTHER_MAX_LENGTH,QUESTION_OPTION_MAX_LENGTH,QUESTION_MAX_OPTIONS,ANSWER_MAX_VALUES);getAnswerValueError()— exhaustive switch overQuestionTypeenforcing membership, value-count, and length rules; two new checks added tovalidateOptions(option count ≤QUESTION_MAX_OPTIONS, each option ≤QUESTION_OPTION_MAX_LENGTH).prisma/actions/applications.ts(createOrUpdateApplicationAnswer) — envelope schema capped with a pre-DB size guard (.max(ANSWER_LONG_MAX_LENGTH)per value,.max(ANSWER_MAX_VALUES)on the array); questionselectwidened tooptions/allowOther; calls the validator before any upsert.prisma/actions/profile.ts(updateGlobalAnswer) — same three changes.components/features/application-stepper.tsx— the Controller'srules.validatealso runs the validator so the inline message matches the server's.components/features/application-question.tsx—maxLengthon the short/long/"Other" inputs, a live counter forlong_answer, autosave blocked client-side when the value is invalid, and the failure toast now surfaces the action's own{ error }message instead of always the generic copy.components/features/profile-question.tsx— same treatment; generalizedformatError→validationErrorto cover every answer-validation message, not just format mismatches.components/ui/options-chip-editor.tsx—maxLengthon the option input andaddOptionrefuses pastQUESTION_MAX_OPTIONS, so the admin can't type into a rejected state.Deliberately a write-path-only change: no render path calls the new validator, so a stored value that no longer fits its question (e.g. an option an admin later deleted) still renders exactly as before — that mismatch UX is #354's job.
Testing plan
allowOtheron, add an "Other" text and confirm it saves alongside the options.short_answer/long_answer/single_choice, confirm normal answering and clearing (empty) both still save.createOrUpdateApplicationAnswerfrom the browser console with a two-element value on ashort_answerquestion, and with a choice value not inoptions: both come back{ error: … }and nothing is written./profile; messages are identical to the application flow.FormMessageunder Options on next edit).file_uploadquestion in both the profile and application flow.createOrUpdateApplicationAnswer/updateGlobalAnsweragainst another user's application/profile from the console; both remain scoped to the caller (unchangedrequireOwnership/user.idscoping).Automated checks
npm run prettier:checknpm run eslint:checknpm run tsc:checkNotes
applications.ts,profile.ts,application-question.tsx,profile-question.tsx— kept deliberately disjoint per the plan: this PR adds one validator call on the write path and touches no render/partition logic.ANSWER_MAX_VALUESis a pre-DB size guard, not a product rule — membership + uniqueness already bound a realmultiple_choiceanswer tooptions.length + 1.