Skip to content

Show the pre-questionnaire ballot until the survey launches - #87

Merged
mikaalnaik merged 2 commits into
mainfrom
mikaal/old-toronto-election-ui
Sep 11, 2026
Merged

Show the pre-questionnaire ballot until the survey launches#87
mikaalnaik merged 2 commits into
mainfrom
mikaal/old-toronto-election-ui

Conversation

@mikaalnaik

Copy link
Copy Markdown
Contributor

Follow-up to #86. Hiding the candidates' answers left the pages that used to publish them standing — a questionnaire heading over a notice that there is nothing under it, a roster labelled by who wrote back with everyone in one half, "Candidate survey coming soon" in the place a reader came to read. Until launch we want the UI these pages had before the answers existed: the ballot.

Nothing is reverted. surveyClosed and questionnaireHidden stay exactly as they are and still gate the answers at the source; this is the presentation on top of them.

What each page shows now

Page Before this PR Now
Ward pages Questionnaire heading, withheld notice, flat roster, survey card Candidates — one card a candidate, name → their page, incumbent badged, bio
/mayor/candidates "Yet to respond" holding the whole ballot, "Answered us: 0" One flat ballot, surname order, 53 names
Candidate pages "Candidate survey coming soon" section Links, where they're running, and the bio they wrote us
Landing Explore grid promising "see how they answered" One card, the mayoral ballot; blurb says what the cards go to

The two pages that are only the answers

/issues is the whole field read question by question and /mayor is the mayoral field's answers — neither has a ballot-only version to write. Both files stay in the repo untouched and come down at the router instead (next.config.ts), each landing on the nearest ballot:

  • /toronto/vote/2026/issues/toronto/vote/2026
  • /toronto/vote/2026/mayor/toronto/vote/2026/mayor/candidates

Temporary 307s on purpose: a permanent redirect would sit in a reader's browser and skip the server after launch, when these are the pages we most want them to reach.

Launching

Delete the two redirect entries in next.config.ts, drop the two flags in the registry, and restore the questionnaire sections on the ward, mayoral-roster, candidate and landing pages — this commit is the diff to read backwards for that last part.

What stays

Everything built since the questionnaire: candidate profile pages, ward descriptions and Census profiles, the live countdown, the redesigned landing page. A candidate's own bio is still published, attributed as theirs — a self-description is not one of the positions being held back. ANSWERS_WITHHELD is kept for the two switched-off pages, now the only things referencing it.

Verification

  • tsc clean; eslint clean on every file touched (4 remaining warnings are pre-existing, in bills/ and state-of-the-nation/).
  • Checked the rendered HTML of the landing page, ward 13, /mayor/candidates and a candidate page against a dev server, plus both redirects returning 307 to the right targets.
  • npm run build prerenders all 570 pages and then fails on /tracker, which fetches localhost:3000/api/burndown/1 — pre-existing and unrelated.

One open question

/toronto/vote/survey-questions still returns 200. It's the question set rather than any answers, but nothing links to it now that the questionnaire pages are off — say the word if it should redirect too.

🤖 Generated with Claude Code

Hiding the answers left the pages that used to publish them standing: a
questionnaire heading over a notice that there is nothing under it, a
roster labelled by who wrote back with everyone in one half, and a
"Candidate survey coming soon" in the place a reader came to read. What
we want in the meantime is the UI these pages had before the answers
existed — the ballot — and that is what this does. Nothing is reverted;
the flags stay, and this is the presentation on top of them.

WHAT EACH PAGE SHOWS NOW

The ward pages are back to `Candidates`: one card a candidate, the name
a link to their own page, the incumbent badged, their bio under it. No
questionnaire rail, no roster split, no survey card, and no notice —
who is standing in a ward is a fact about the election, and the page is
complete without saying what else it is not printing yet.

/mayor/candidates is one flat ballot in surname order. The split was by
who wrote back, so with nothing to read back it was a scoreboard
reading nil-all and every word of it our doing. The stats row loses the
count of answers for the same reason.

A candidate's page keeps everything that is theirs — their links, where
they are running, and the bio they wrote us, still attributed — and
drops the questionnaire section under it.

The landing page's explore grid is one card, the mayoral ballot. Its
blurb opened "We put the same questions to every candidate on the
ballot. See how they answered", which is a promise the grid beneath it
cannot keep, so it now says what the cards actually go to. The closing
band was already the pledge.

THE TWO PAGES THAT ARE ONLY THE ANSWERS

/issues is the whole field read question by question and /mayor is the
mayoral field's answers; neither has anything left when those are gone,
so there is no ballot-only version of them to write. They stay in the
repo exactly as built and come down at the router instead, each landing
on the nearest ballot — /issues on the election page, /mayor on the
mayoral roster. Temporary 307s, not 308s: a permanent redirect would
sit in a reader's browser and skip the server after launch, when these
are the pages we most want them to reach.

WHAT STAYS

`surveyClosed` and `questionnaireHidden` are untouched and still gate
the answers at the source — the read proxy and `rosterSurvey` — so no
answer can reach the markup through a route this commit missed. The
bio still rides in on the same response and is still published, because
a self-description is not one of the positions being held back. Their
registry docs are rewritten to describe the ballot the pages now show
rather than the withheld notice they used to. `ANSWERS_WITHHELD` is
kept for the two pages above, which are the only things left that
reference it.

Everything built since the questionnaire is kept: the candidate pages,
the ward descriptions and Census profiles, the live countdown, the
redesigned landing page. This is the old reading of the ballot, not the
old site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR temporarily replaces questionnaire-oriented election presentation with ballot-focused views while candidate answers remain hidden.

  • Redirects the answer-only /issues and /mayor routes to the nearest ballot pages using temporary redirects.
  • Shows candidate cards on ward pages and a single surname-ordered mayoral roster.
  • Removes questionnaire sections and links from candidate profiles and the election landing page.
  • Keeps candidate-authored biographies visible while withholding policy answers.

Confidence Score: 5/5

The PR appears safe to merge from a correctness perspective, although the unresolved maintainability concern in WardDetail.tsx remains non-blocking.

No new defect was introduced after the previous review. The redirect explanation was simplified and its previous thread is resolved, but the ward questionnaire implementation remains deleted rather than conditionally selected, so restoring questionnaire answers at launch still requires reconstructing that presentation from the reverse diff.

Files Needing Attention: src/components/elections/WardDetail.tsx

Important Files Changed

Filename Overview
next.config.ts Adds temporary redirects for the two answer-only routes and simplifies the explanatory comment following the previous review.
src/components/elections/WardDetail.tsx Replaces the questionnaire-backed ward presentation with candidate cards; the earlier concern that restoring the flag alone cannot restore this view remains outstanding.
src/app/toronto/vote/2026/candidates/[candidate]/page.tsx Removes questionnaire choices from candidate profiles while retaining candidate-authored biographies and ballot navigation.
src/app/toronto/vote/2026/mayor/candidates/page.tsx Replaces questionnaire-response grouping with one surname-ordered mayoral ballot.
src/components/elections/ElectionLanding.tsx Changes the landing-page exploration card and copy from questionnaire discovery to mayoral-ballot discovery.
src/lib/elections/registry.ts Updates documentation for the existing questionnaire-hiding flag without changing its behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  L[Toronto election landing] --> M[Mayoral ballot]
  L --> W[Ward ballot]
  W --> C[Candidate profile]
  M --> C
  I[Answer-only issues route] -->|Temporary 307| L
  A[Answer-only mayor route] -->|Temporary 307| M
Loading

Reviews (2): Last reviewed commit: "Cut the redirect note to what a reader o..." | Re-trigger Greptile

Comment on lines +140 to +160
{councilRaces.map((race) => (
<div key={race.id}>
{showRaceHeadings && <RaceHeading race={race} />}
{race.candidates.length === 0 ? (
<EmptyRace
wardName={ward.name}
nominationCloseLabel={nominationCloseLabel}
/>
) : (
race.candidates.map((cand) => (
<CouncilCandidate
key={cand.key}
candidate={cand}
election={election.slug}
ward={ward.n}
wardName={ward.name}
/>
))
)}
</div>
))}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Temporary view is destructive

This change deletes the questionnaire view and its ward-page data fetch instead of selecting between the existing questionnaire and ballot views with questionnaireHidden. As a result, removing that flag cannot restore ward answers at launch; several deleted sections must be reconstructed from the reverse diff. Preserve both views and select the active one from the shared flag. The repository requires avoiding overly complicated changes, and that requirement must be satisfied before merging.

Context Used: Be suspicious of things that are overly complicated and could be simplified (source)

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/elections/WardDetail.tsx
Line: 140-160

Comment:
**Temporary view is destructive**

This change deletes the questionnaire view and its ward-page data fetch instead of selecting between the existing questionnaire and ballot views with `questionnaireHidden`. As a result, removing that flag cannot restore ward answers at launch; several deleted sections must be reconstructed from the reverse diff. Preserve both views and select the active one from the shared flag. The repository requires avoiding overly complicated changes, and that requirement must be satisfied before merging.

**Context Used:** Be suspicious of things that are overly complicated and could be simplified ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

**Knowledge Base Used:**
- [Municipal election engagement](https://app.greptile.com/buildcanada/-/custom-context/knowledge-base/buildcanada/tradingpost/-/docs/municipal-elections.md)
- [Toronto candidate and issue guides](https://app.greptile.com/buildcanada/-/custom-context/knowledge-base/buildcanada/tradingpost/-/docs/toronto-candidate-and-issue-guides.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not taking this one, deliberately.

Keeping both views and switching on questionnaireHidden is what the previous PR (#86) already did, and this PR exists because that state is the problem: two renderings of every ward, mayoral and candidate page in the tree at once, each needing the other kept working. The flag has been set for a day and there is no plan to unset it before launch, so the second branch is dead code we would be maintaining in components three regions share.

The restoration path is not a reverse diff by hand — it is git revert of this commit, which puts the questionnaire view and the ward-page fetch back exactly as they were. That is a smaller and safer operation than reading a conditional that has only ever run one way.

Worth noting the flag is not what this PR relies on for safety: questionnaireHidden stays in place and still gates the answers at the source (the read proxy and rosterSurvey), so no answer can reach the markup through a route this PR missed. What changed is only which UI the pages draw when there is nothing to draw.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s fair. Given the deliberate one-way rollout, removing the dead questionnaire rendering avoids maintaining duplicate page variants, and reverting this commit is a concrete restoration path that restores the previous implementation rather than depending on a flag flip. My original concern would apply only if launch were expected to switch views by changing questionnaireHidden; that is not the launch procedure here. Since source-level answer gating remains intact, I’m withdrawing this concern.

Comment thread next.config.ts Outdated
Seventeen lines for two redirects, most of them saying a second time
what the entries below say for themselves — which page goes where, and
that each page stays in the repo. What is left is the part that is not
in the code: why these two routes are off, and why the 307 is
deliberate rather than an oversight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mikaalnaik
mikaalnaik merged commit a23b6bc into main Sep 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant