Add How to Vote screen and remove inferred election deadlines - #292
Add How to Vote screen and remove inferred election deadlines#292ThatXliner wants to merge 2 commits into
Conversation
The Elections tab explains what is on the ballot but never explained how to cast one. How to Vote is the logistics half: which election, where you're registered, the ways you can vote, where to go, and who to ask. The screen renders only what an official source returned. Billion has no sourced deadline feed, so every deadline slot shows an explicit "not available" state and routes to the county rather than showing a date. That posture required deleting some existing behaviour. ElectionHero rendered "Registration closes" at electionDay-15 and "Ballots mailed" at electionDay-8; KeyDatesSection rendered "Registration Deadline" at -15 and "Early Voting Starts" at -29. None were sourced or hedged, and all were styled like the Election Day we actually get from Google Civic. Those offsets approximate one California cycle and are wrong elsewhere, so they're gone along with the shiftDays helper that existed to compute them. The Elections tab footer row also claimed "Verified on vote.gov" while pushing to an internal screen. Billion performs no such verification; the row now says what it does and opens How to Vote. Method availability distinguishes "the county hasn't published this yet" from "this isn't offered" — collapsing the two would tell voters a method doesn't exist when we simply don't know. Locations, loading, error, out-of-coverage, mail-only, Election Day, and post-election states are all handled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…l government Three review fixes. Steps are now a summary of an authority's published instructions rather than Billion's own advice, so they only render when we can name and link the page they summarize. `instructionsUrl` gates `steps` entirely: no source, no summary — the card becomes pure routing, without narrating why. The expanded card names the authority above the steps and promotes the official link from a footnote to a primary action. The registration row asked nothing and could answer nothing. It said "Billion can't confirm whether you're registered" and, wherever Civic omitted both registration URLs, rendered with no action at all — a prompt with no exit. It now asks the question the reader actually has and always resolves somewhere: county tool, else state, else vote.gov. The previous commit repointed the only link to /local-elections and stranded it. Local bills and upcoming meetings became unreachable, and `legistar.getMeetings` lost its last consumer. That screen is now "Your Local Government": the three sections How to Vote superseded are gone, and a card in Browse — under the jurisdiction row, where the reader is already asking which government they're looking at — is the way back in. Legistar serves San Jose, Santa Clara County, and Sunnyvale to everyone regardless of address, so "Your" is earned by an address match and the scope is stated plainly otherwise. A Sacramento reader is not told San Jose is theirs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Loaded-state screenshots (commit
|
|
Closing unmerged — this was an experiment against the How to Vote design, and Why it isn't merging. Everything voting-related in the screenshots above is That combination — fabricated values attributed to a real authority — is What replaces it. #272 is now an epic covering the design plus the data One piece should still land. #301 — removing the inferred Also worth keeping as reference:
The branch stays pushed. Nothing here is lost, it just shouldn't ship yet. |
Closes #272.
The Elections tab already explains what is on the ballot. It never explained how to turn a decision into a counted vote. This adds that half — and removes the invented deadlines currently standing in for it.
Design spec and mockups: https://claude.ai/code/artifact/010f959a-576a-4884-8c34-3648699589fb
Screenshots
Captured on an iPhone 17 Pro simulator (iOS 26.5) running this branch.
Note on the data: the
GOOGLE_CIVIC_API_KEYin.envcurrently returns403 — Civic Information API has not been used in project 963502945151 before or it is disabled, so live lookups fail locally. These shots run against a locally-seededcivic_api_cacherow shaped like a real Sacramento Countyvoterinforesponse. The seed is local-only and was removed afterwards; the deadline states below are genuine — they are what the screen renders whenever no deadline has been sourced, which today is always.1. Entry point on the Elections tab
Docked under the election hero, above the ballot tabs. Note the hero now shows only Election Day — the invented "Registration closes" and "Ballots mailed" rows are gone.
2. How to Vote — overview
Election, truncated address, a registration line that asserts nothing, and the four methods with icon+word availability chips.
3. A method expanded, with locations
Three steps, then the drop boxes with hours and Get Directions. Locations cap at three with "Show all {n}", matching
PollingPlacesSection.4. Vote by mail — the honest gap
The five-step checklist, then the unavailable state that stands in for every deadline Billion cannot source: "We won't guess at a date or a location." This is the state that replaces the fabricated
electionDay - 15dates.5. What to bring, and the official source footer
ID rules are deferred to the authority rather than restated. The named election body and its links are the last card in every state.
6. No address set
Both entry points still render — the section never silently disappears — and the destination stays useful with a national fallback.
The trust problem this also fixes
Two shipped components synthesized legal deadlines from arithmetic and rendered them with the same authority as real data:
ElectionHeroelectionDay − 15ElectionHeroelectionDay − 8KeyDatesSectionelectionDay − 15KeyDatesSectionelectionDay − 29None were sourced or labelled as estimates, and all were painted in the same amber/green semantics used for the Election Day we genuinely get from Google Civic. Those offsets approximate one California cycle and are wrong for most jurisdictions and years. They're removed, along with the
shiftDayshelper whose only callers they were.The Elections tab footer row also read "Find your polling place / Verified on vote.gov" while pushing to an internal screen. Billion performs no such verification — the row now describes what it does and opens How to Vote.
What ships
Entry point — a card docked directly under
ElectionHero, above the ballot tabs. The hero has just said which election and when, so "how do I vote in it" is the next sentence, and anyone who came only for logistics never scrolls a contest. Renders in every state, including with no address.The screen — election and Election Day, truncated registered address with Edit, the four ways to vote as a single-expand accordion, and a named election authority with working links as the last card in every state.
Honest gaps. Method availability distinguishes
unknown("the county hasn't published this yet") from unavailable ("an official source says it isn't offered"). Collapsing the two would tell a voter a method doesn't exist when we simply don't know. No deadline is displayed unless an official source supplied it; today that means the deadline slots render their "not available" variant and route to the county. Nothing on this screen asserts registration status.States handled — no address, loading, lookup error, out-of-coverage, mail-only, locations unpublished, Election Day, and post-election.
Notes for review
apps/expo/src/utils/voting.tsholds all the derivation logic and is pure — 20 tests cover it, including a guard that no generated step or subtitle can name a month.testscript to@acme/expo(tsx --test), matching the convention in@acme/api. The lockfile change is a 3-line importer entry;--frozen-lockfilepasses.packages/apinow exportsAdministrationRegion,AdministrationBody, andElectionOfficial— already-defined Civic types that weren't re-exported.alert,mail,inbox.pnpm format,lint,typecheck, andtestall pass across the monorepo (211 tests).Follow-ups, not in scope here
/local-electionsis titled "Where & How to Vote" and its polling-place and key-dates sections now overlap this screen. The clean split is How to Vote owning logistics and that screen keeping reps, local bills, and meetings — but it's a product call, not a refactor to slip into this PR.formatDate/monthDayin~/utils/datesdonew Date("2026-11-03"), which parses as UTC midnight and renders as Nov 2 in any timezone west of UTC — visible in the screenshots above. It predates this PR and affects every screen using those helpers, so I left it alone rather than change shared behaviour here, but it matters more on a deadline screen than elsewhere. Worth a follow-up.🤖 Generated with Claude Code