#354 Preserve Stored Answers When A Question Type Or Options Change - #427
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 1 · needs revision
1 open — 1 ⚪ Nit (see inline)
3cc8e8b to
4a252c2
Compare
Revision — Cycle 1fixed R1-N1 · 4a252c2 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 2 · approved
0 open — clean
Pipeline EscalationHuman-directed rebase of Conflicting files
Why this isn't auto-resolvableEvery non-trivial hunk is both sides modifying the same function body / same prop type on overlapping lines, where accepting either side outright would silently drop the other feature's behavior (format validation or answer-shape preservation). Correctly composing them requires deciding shared type shape ( No commits were pushed; the branch is unchanged from its pre-rebase state (still |
Pipeline EscalationRebasing Note on sequencing: this PR was first rebased onto 1.
|
Introduces partitionAnswerValue/isAnswered (lib/utils.ts) so a stored
answer that no longer fits its question's current type/options is
split into the part still renderable ("fitted") and the rest
("orphaned"), instead of one silently overwriting the other on the
next blur. Both applicant editors (ApplicationQuestion, ProfileQuestion)
and the stepper's read-only card now render controls from `fitted` only,
surface `orphaned` via a new read-only AnswerMismatchNotice, and build
every write from `fitted` so a render-time mismatch can never become a
write. "Answered" is redefined via isAnswered everywhere required-answer
checks happen (submitApplication, getProfileCompleteness, the apply-page
gate) so client and server agree on what counts as answered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidating QuestionShape into AnswerQuestion dropped FORMAT_INPUT_TYPES and the type= prop, so email/phone/url/zip answers always rendered a plain text input. Relocated the map to lib/constants.ts alongside the other format constants so ProfileQuestion can adopt it later. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drops the ENGINEERING §7 citation from the partitionAnswerValue JSDoc and condenses every other comment this PR introduced (issue refs, narration, multi-paragraph JSDoc) to the one/two-line terse-fragment bar, without losing the invariants they document. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dbd41d0 to
9bd2e53
Compare
Revision — Cycle 4No new review findings to address (Cycle 4 was already approved); this cycle re-rebased onto · rebase: |
Condenses multi-line/duplicated comments this PR introduced to one-line terse fragments and drops a couple that naming already carried, without losing the invariants they documented. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes a cross-function reference comment in application-stepper.tsx and condenses the last two-line comment in profile.ts, completing the ENGINEERING §7 comment-discipline pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revision — Cycle 4comment-discipline cleanup (ENGINEERING §7), no review findings outstanding · 2b1e872 |
b-at-neu
left a comment
There was a problem hiding this comment.
Code Review — Cycle 5 · approved
0 open — clean
Closes #354
Summary
partitionAnswerValue(+isAnswered), that splits a stored answer into the part the question's current shape can render/round-trip (fitted) and the part it can't (orphaned). Both applicant editors now render fromfittedonly, showorphanedin a new read-only warning notice, and build every write fromfitted— so a truncated render can never become a write.isAnsweredeverywhere a required-answer check happens (submitApplication,getProfileCompleteness, the apply-page gate), so a mismatched required answer is treated as unanswered client- and server-side alike.Changes
lib/types.ts—AnswerQuestion(replaces the duplicate localQuestionShape/NarrowQuestiontypes) andAnswerPartition.lib/utils.ts—partitionAnswerValue()(exhaustiveswitchoverQuestionType,neverdefault) andisAnswered().components/features/answer-mismatch-notice.tsx— new read-only warning notice (chips formultiple_choice, plain text otherwise), used in three places.components/features/application-question.tsx— renders fromfitted, derives "Other" state fromfittedgated onallowOther, builds every write fromfitted, renders the notice, wiresaria-describedby/role="group".components/features/profile-question.tsx— same treatment for the edit view; the read-only view keeps showing the full stored value (safe — it's not writable) with the notice added above it.components/features/application-stepper.tsx— drops the localNarrowQuestiontype in favor ofAnswerQuestion; required-field validation and the read-onlymissingGlobalIdscheck useisAnswered;ReadOnlyQuestionCardrenders the notice.prisma/actions/applications.ts—submitApplication/reopenApplication's sharedfindMissingRequiredAnswersusesisAnsweredinstead of a bare non-empty check.prisma/data/profile.ts—getProfileCompletenessusesisAnswered; widened therequiredQuestionsselect to includetype/options/allowOther.app/(main)/(auth)/positions/[id]/apply/page.tsx— the profile-complete gate usesisAnsweredto matchsubmitApplication.Testing plan
Setup: as an admin, create a position question and a global profile question; as an applicant, answer them and start a draft application.
short_answer: the input shows the first value, the notice lists the other two, and blurring without typing preserves all three.single_choice: only a value still inoptionsstays selected; the rest appear in the notice./profileshows the notice, the completeness banner counts it missing, and/positions/<id>/applyshows the "Complete your profile first" gate until it is re-answered.allowOtherkeeps rendering a free-text answer as "Other" with no notice; turningallowOtheroff moves that text into the notice.aria-describedby).Automated checks
npm run prettier:checknpm run eslint:checknpm run tsc:checkNo test runner exists in this repo today (noted as a pre-existing gap in the plan);
partitionAnswerValueis deliberately pure so it becomes a trivial unit-test target once one exists.Notes
allowOther: true, a value whose option was removed keeps rendering as the applicant's "Other" text (not flagged) — nothing distinguishes the two cases in storage, and this is already the behavior Allow Admins to Enable an "Other" Free-Text Option on Choice Questions #322 established.AnswerField/useAnswerAutosaveextraction and unifying the two editors stays with Unify the Two Answer Editors #394; this PR only prepares the logic it will absorb.