Uh oh!
There was an error while loading. Please reload this page.
Toronto Voter Survey - #52
Conversation
Greptile SummaryThe PR adds a York Factory-backed Toronto voter questionnaire, candidate-alignment results, and several candidate-answer views.
Confidence Score: 4/5The PR is not yet safe to merge because its survey load and submission flows still target York Factory routes that are not registered. The outstanding backend-route issue remains: opening the survey depends on an unavailable remote GET route and converts that failure into a not-found page, while submitting answers targets an unavailable POST route and returns an error. Files Needing Attention: src/lib/elections/survey.ts and src/app/api/elections/survey/route.ts
|
| Filename | Overview |
|---|---|
| src/lib/elections/survey.ts | Defines and fetches the remote questionnaire, but its York Factory GET dependency remains unavailable in the related backend repository. |
| src/app/api/elections/survey/route.ts | Adds the submission proxy, but forwards completed surveys to an endpoint not registered in the related backend repository. |
| src/app/toronto/vote/2026/survey/SurveyClient.tsx | Implements the multi-step voter questionnaire and transitions into candidate-alignment results. |
| src/lib/elections/alignment.ts | Adds candidate scoring and race-level alignment calculations. |
| src/app/api/elections/candidate-responses/route.ts | Adds a ward-scoped response proxy that joins respondent data with Toronto candidate rosters. |
| src/components/elections/SurveyGrid.tsx | Adds the candidate-by-question grid used to present mayoral questionnaire answers. |
Sequence Diagram
sequenceDiagram
participant V as Voter
participant T as Tradingpost
participant Y as York Factory
V->>T: Open survey page
T->>Y: "GET /elections/{election}/surveys/{survey}"
Y-->>T: Survey definition
T-->>V: Questionnaire
V->>T: Submit answers
T->>Y: "POST /elections/{election}/survey_responses"
Y-->>T: Region and submission result
T->>Y: GET candidate responses
Y-->>T: Candidate answers
T-->>V: Alignment results
Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile
| ): Promise<Survey> { | ||
| const { data } = await apiFetch<{ data: Survey }>( | ||
| `/elections/${electionSlug}/surveys/${surveySlug}`, | ||
| { revalidate: 300, tags: [`survey:${electionSlug}:${surveySlug}`] }, |
There was a problem hiding this comment.
Backend survey routes are missing
The survey page always requests GET /elections/{election}/surveys/{survey}, but the current York Factory election routes register only pledges, so the request returns 404 and loadSurvey turns the new page into a not-found response. The submission proxy likewise targets an unregistered POST /elections/{election}/survey_responses route, preventing responses from being recorded.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/elections/survey.ts
Line: 108-111
Comment:
**Backend survey routes are missing**
The survey page always requests `GET /elections/{election}/surveys/{survey}`, but the current York Factory election routes register only pledges, so the request returns 404 and `loadSurvey` turns the new page into a not-found response. The submission proxy likewise targets an unregistered `POST /elections/{election}/survey_responses` route, preventing responses from being recorded.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.3c283cd to
0e73123CompareThe questions lived here in surveyData.ts, which was a deliberate choice while this was the only thing rendering them: rewording a question needed no deploy on the API side. Candidates now answer surveys too, and a candidate questionnaire is authored in York Factory's CMS — a question set that only exists in the front end can't be rendered as an admin form. So the database became the source of truth and this follows it. The renderer is unchanged in spirit: it still derives step count, progress, validation and the submitted payload from whatever steps it's given, only now they arrive over the wire. Adding a question is a CMS edit with no deploy here at all, which is more than the old arrangement managed. Three things follow from the move: - The submitted slug and version come from the survey that was actually rendered rather than constants, so answers can't be filed under a question set the respondent never saw. - yesno options come from the API like any other choice list, so the values published results group by have one definition rather than a copy on each side. - Ward choices are resolved from the election's councillor races when the survey is served, so the list can't drift from the ward pages the way a frozen copy in this repo did. The page 404s when the definition can't be fetched and the ISR cache is cold. A half-rendered form is worse than an honest miss, and keeping a hard-coded fallback here is the drift this change exists to end. Depends on york_factory#100: production has no /surveys endpoint until that ships. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0e73123 to
eb1c18dCompareUh oh!
There was an error while loading. Please reload this page.
Builds out the 2026 municipal election section around the candidate questionnaire: voters answer the same questions the candidates did, then see where they line up with the field.
Rebased on
mainnow that the Toronto voter-guide/SEO pages (#74) have landed, so this PR is just the survey and candidate-answer work.What's here
Voter survey (
/toronto/vote/2026/survey)SurveyClientrenders the York Factory-hosted questionnaire rather than a local copy, so the questions voters answer are the same ones sent to candidates. Multi-step form, one question at a time, with staggered field transitions (.step-fieldinglobals.css).AlignmentResultsscores the voter against every candidate who wrote back (src/lib/elections/alignment.ts) and shows the closest matches per race.AgreementChart/AnswerChartshow how the field answered each question next to the voter's own pick./api/elections/survey(fetch + submit) and/api/elections/candidate-responses.Candidate answers
/toronto/vote/2026/mayor— the questionnaire read across the mayoral field as aSurveyGrid: candidates as columns, questions as rows, non-respondents marked as such./toronto/vote/2026/mayor/candidates— new. The mayoral roster on a page of its own: the field is fifty-odd people and it was eating half the landing page. Grouped answered-first (a name vs. a position is the most useful sort available), surname order within each group, withdrawn candidates kept in a group at the foot./toronto/vote/2026/issues— new. The whole field read issue-by-issue viaFieldSentiment, one card per question, for the city-wide "where do they converge and split" view that a grid can't hold at 32 respondents./toronto/vote/2026/wards/[ward]—WardDetailnow carries each race's questionnaire answers, charted withCandidateDotPlot(a dot per candidate on the option they picked) rather than a dial — shares are meaningless at two-to-six respondents, and the dial could only ever mark one position.Trilemma dial (
src/components/charts/trilemma/) ported in for the three-way tradeoff questions; its palette is also what the dot plots and answer charts colour from, so the pages read as one system.Shared pieces —
SurveyGrid,AnswerChart,AnswerOptionList,CandidateDotPlot,CandidateSurveyAnswers,SurveyCta,FieldSentiment, plussrc/lib/elections/{alignment,candidate-answers,candidate-responses,field-sentiment,survey,survey-answers,names,postal-code}.ts.ElectionLandingreworked around the new race entry points; postal-code normalization pulled out of the pledge route so the survey can share it.Notes
tsc --noEmitclean;pnpm lintreports only the four pre-existing warnings onbills/*andstate-of-the-nation/og-card.tsx.