Fix CSV ballot export: escape fields and download directly via Blob#1428
Fix CSV ballot export: escape fields and download directly via Blob#1428jacksonloper wants to merge 2 commits into
Conversation
The CSV download rendered through a hidden <CSVLink> with
enclosingCharacter={''}, which disabled quoting entirely — any candidate
name, race title, or precinct containing a comma (or quote/newline)
shifted every subsequent column and corrupted the file. It also relied
on setting React state and synchronously clicking the hidden link, so a
click downloaded the previous render's data.
Build the CSV string directly with RFC-4180 field escaping (quote only
when needed) and download it via a Blob, the same approach as the JSON
export. Output is otherwise unchanged: same columns, same filename, and
a blank (not-scored) cell stays blank. Removes the now-unused react-csv
dependency.
Extracted from #1419.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Deploy Preview for bettervoting ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThe react-csv dependency was removed from packages/frontend/package.json. BallotDataExport.tsx was refactored to remove the useState/CSVLink-based CSV generation flow, replacing it with a new triggerDownload helper that creates a Blob, object URL, and temporary anchor element to initiate downloads. downloadCSV now synchronously builds CSV headers and rows, applies RFC-4180-style escaping via a new csvField function, and includes IRV/STV rank columns. downloadJson was simplified to use the shared triggerDownload helper. The rendered menu UI retains its "Download CSV" and "Download JSON" options without the hidden CSVLink element. Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The component rendered two separate MenuButtons — one for the loading state, one for the loaded state. The first Download click starts the ballots fetch and opens the loading menu; when the ballots arrive, that subtree unmounts and the open menu collapses, forcing a second click. Render one MenuButton and swap only its items, so the menu opened by the first click stays up and the CSV/JSON items appear in place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Description
Extracts the CSV bug fixes from #1419 (authored by @masiarek — the commit carries his authorship) without the new JSON v2 export format or the Raw/Official menu split, plus a fix for the Download menu collapsing on first open.
Bug 1 — corrupt CSV when a field contains a comma. The CSV download rendered through a hidden
<CSVLink>withenclosingCharacter={''}, which disabled quoting entirely: any candidate name, race title, or precinct containing a comma (or quote/newline) shifted every subsequent column. Verified end-to-end on the old code: a candidate namedSmith, Johnsplits into two header columns, so every later score sits under the wrong candidate. Fields are now escaped per RFC 4180 (quoted only when needed).Bug 2 — first "Download CSV" click downloads nothing. The old path set React state and then synchronously clicked the hidden link — which doesn't exist until the next render. Verified end-to-end: the first click produces no download, and a second click delivers whatever the first click staged. The CSV string is now built directly and downloaded via a Blob, the same approach the JSON download already used.
Bug 3 — the Download menu collapses while ballots load. The component rendered two separate MenuButtons (loading vs. loaded). The first click opens the loading menu and starts the ballots fetch; when the ballots arrive, that subtree unmounts and the open menu vanishes, forcing a second click on the button. Now a single MenuButton stays mounted and only its items swap, so the menu opened by the first click stays up and CSV/JSON appear in place. Net effect with Bug 2's fix: one click on Download, one click on the format, one correct file — previously it took four clicks and produced a corrupt file.
Output is otherwise unchanged: same columns, same filename, blanks (not-scored) stay blank, and the JSON download still emits the existing
{ Election, Ballots, Results }shape. Removes the now-unusedreact-csvdependency.Screenshots / Videos (frontend only)
No visible page change — the Download menu offers the same items (CSV / JSON); it now stays open while ballots load, and the downloaded CSV bytes differ when fields need escaping.
Related Issues
🤖 Generated with Claude Code