About
Ever since knocking doors for Bernie in 2019, I've been obsessed with the potential of the electoral left. I built this site in my spare time in an attempt to see, in one place, all the badass candidates who are fueled by small donors and volunteer armies, who often refuse PAC money, and who are willing to stand up to the donor class and fight for a future where our democracy actually works for the majority of its people. I would love to work on this more.
If you want to see more orgs added to the list and more data scraped and sorted, please throw some coin in the cup so I can work on this more. And please, contact me at unboughtdems [at] riseup.net. Let me know what orgs / people you want added next and if you're interested in collaborating!
Changelog
July 17, 2026
Features
- Renamed the site from SmallDonorArmy to Unbought Dems and changed the canonical domain to unboughtdems.org across the masthead, site shell, metadata, methodology, CNAME, and social-sharing card.
- Added each winning candidate’s rounded vote percentage to the bottom of the Winners’ Circle card, opposite the result badge.
- Matched Winners’ Circle card backgrounds to the “Movement Is on the Ballot” card.
- Moved the election date to the upper-right corner of each card.
- Added interactive odds, wk, 2wk, and mo controls beneath the front-page Polymarket badge.
- Added official Polymarket CLOB price-history retrieval to the normal Polymarket refresh, with one-week, two-week, and one-month movement calculated for the featured candidate.
- Changed the odds badge to show a spaced green upward or red downward arrow with the point change for the selected window.
- Preserved the last good historical data when Polymarket’s history endpoint is temporarily unavailable.
Fixes
- Removed the star and arrow icons from Winners’ Circle cards.
- Anchored Winners’ Circle card footers to the bottom so result badges and vote percentages align across each row.
- Removed the receipts section from the bottom of the front page.
- Removed “The slate’s shared platform” from the “What We Want” panel and restored standard spacing above the footer.
- Improved the history selectors with larger, bold black text and an underline only on the active view.
- Prevented the Polymarket tooltip from opening when hovering or focusing the history selectors.
- Added mobile spacing below the odds selectors and shifted the AIPAC Tracker badge 15px to the right.
Older updates
July 10, 2026 · Site updates
Workflow
- Race intake now supports a sourced pull quote (Quote Text / Attribution / Source URL) that appears on the front-page candidate panel.
- Candidate portraits: drop a photo at public/portraits/<slug>.<ext> and set profile.photoUrl; missing files fall back to initials.
Features
- Added MI-01 · Democratic Primary.
- Front page: nav links (The Map Room, About) in the masthead; movement record reads "advanced" for primary wins, "won" reserved for the general.
- Candidate panels: Polymarket odds shown as a starburst sticker, an AIPAC Tracker seal, a sourced quote blurb, a $0 corporate-PAC flag, and the featured-endorser logo.
- Candidate portraits render with a blue duotone treatment on a deep-blue backdrop.
- Persistent note clarifies that every featured candidate is endorsed by Justice Democrats, DSA, Indivisible, or Bernie Sanders — the movement organizations we track.
- Back-page header restyled to match the front page; wider content width across the site.
Fixes
- Endorsement logos now signal a curated subset ("Featured Endorser") rather than implying a candidate's full endorsement list.
- Fixed the El-Sayed campaign links (were placeholder URLs).
July 9, 2026 · Site updates
Features
- Added PA-03 · Democratic Primary (completed primary with results).
- Added TX-30 · Democratic Primary (completed primary with results).
July 8, 2026 · Schema consistency and scripts cleanup
Workflow
- Added a schema consistency normalizer for candidate and race JSON files.
- Cleaned the public scripts directory so retired workflow scripts can be removed while keeping active refresh, intake, snapshot, audit, organize, and schema helpers.
- Kept the cleanup as drop-in only; no live candidate or race files are deleted by this scripts patch.
Features
- Preserved lightweight embedded contenders as valid race data without requiring empty FEC, finance, or metadata sections.
- Added normalization support for completed result dates, partial PAC objects, old candidate schema records, and YouTube video IDs.
Fixes
- Relaxed the audit rules so they validate the agreed compact schema instead of forcing every contender into a full candidate-like shape.
- Prevented partial contender PAC objects from failing audit after refresh.
- Kept existing changelog history intact while adding today's script and schema cleanup notes.
July 5, 2026 · Site updates
Features
- Added PA-12 · Democratic Primary (completed primary with results).
- Added WA-07 · Democratic Primary.
- Added MN-05 · Democratic Primary.
- Added MI-12 · Democratic Primary.
- Added IL-03 · Democratic Primary (completed primary with results).
July 4, 2026 · Race intake workflow, refresh fixes, and Greg Casar validation
Workflow
- Rebuilt the race intake workbook around the normalized Candidates, Races, Opponents, and Results sheets.
- Added required Stage values to Opponents and Results so Primary and General elections can be imported independently.
- Changed completed-election results to one row per candidate for each election stage.
- Added completed-General handling so a General card can contain its own winner, percentages, result date, reporting percentage, and official source.
- Made General-card creation automatic when the featured candidate won or advanced in the Primary and at least one General opponent is present.
- Added support for multiple General opponents.
- Reduced LLM Info to Race Reference, Candidate Name, and Seat Context.
- Standardized the canonical intake files as race-intake-workbook.xlsx and race-intake-workbook.md.
- Kept generated intake JSON in data-entry/race-intake/generated/ as intermediate debug and audit output.
Features
- Added exact-header validation so stale or incompatible workbook schemas fail before import.
- Added official-source requirements for election dates, ballot status, results, FEC IDs, endorsements, social links, Polymarket markets, and YouTube videos.
- Kept fundraising totals and donor percentages out of the workbook because they are supplied by refresh scripts.
- Separated YouTube Channel URL from Featured YouTube Video URL.
- Required an actual video URL for featured video rendering.
- Added explicit validation for Primary and General result rows.
- Preserved Campaign Commitments, featured-video titles, social links, and source metadata through import.
- Kept Endorsements as a multi-value field using semicolon-separated values.
- Limited featured endorsers to:
- Justice Democrats
- DSA National
- Indivisible
- Bernie Sanders
- Removed Working Families Party and Our Revolution from the featured-endorser list.
- Kept AIPAC Tracker Endorsed as a separate badge field.
- Prevented AIPAC Tracker status from being mixed into featured endorsements.
- Fixed new-candidate serialization so imported candidates retain their display name and full metadata instead of falling back to the slug.
- Fixed candidate metadata hydration across both Primary and General race cards.
- Added independent result grouping by Race Reference and Stage.
- Added one Featured result and multiple Opponent results per election stage.
- Added multiple-General-opponent support.
- Added completed-General card creation.
- Preserved endorsements, AIPAC Tracker status, campaign commitments, social links, campaign website, YouTube channel, featured video, and video title.
- Fixed General Polymarket storage so the market is written to the correct General race signal field.
- Updated Support button behavior to fall back to the campaign website when needed.
- Added Greg Casar as the first complete validation case for the revised workflow.
- Fixed candidate display-name rendering on cards and map tooltips.
- Corrected the Primary result:
- Greg Casar: 80.75%
- Esther Amalia de Jesus Fleharty: 19.25%
- Added General opponents:
- Lauren B. Peña
- James Kinney
- Added and preserved opponent FEC candidate and committee IDs.
- Added the TX-37 General Polymarket market.
- Added Greg Casar’s campaign website, X account, Bluesky account, FEC IDs, Justice Democrats endorsement, and AIPAC Tracker badge.
- Added a verified featured YouTube video and title.
- Confirmed that both Primary and General cards use the full candidate record.
- Added support for district-level party-outcome markets that do not contain the candidate’s name in the market title.
- Mapped General market probabilities to the featured candidate.
- Added General odds display to the General race card.
- Added the current odds sentence to generated card blurbs.
- Updated candidate-scoped refresh behavior so Polymarket can be refreshed for a specific candidate.
- Updated candidate-scoped refreshes to include every attached Primary and General opponent.
- Fixed participant lookup so embedded opponents receive FEC refresh data.
- Added opponent refresh support for:
- total receipts
- donor-size data
- PAC receipts
- outside spending
- Confirmed npm run refresh:candidate -- <candidate-slug> as the supported candidate refresh command.
- Added --force support to the existing candidate refresh command instead of maintaining a separate refresh:candidate:force script.
- Updated npm run refresh so the displayed menu matches the scripts that actually exist in package.json.
- Standardized the main refresh commands:
- npm run refresh:fast
- npm run refresh:full
- npm run refresh:force
- npm run refresh:candidate -- <candidate-slug>
- npm run refresh:daily
- npm run refresh:weekly
- Removed stale references to deleted commands, including:
- refresh:fec:totals
- refresh:fec:all-money
- refresh:candidate:force
- Updated imported cards to use the existing lightweight YouTube player.
- Load only the YouTube thumbnail on initial page load.
- Create the iframe only after the user clicks Play.
- Preserved lazy loading and avoided loading YouTube embeds before interaction.
- Tested the Greg Casar Primary and General intake flow.
- Tested completed-General result handling.
- Confirmed correct candidate names, result ownership, opponents, Polymarket market mapping, and video metadata.
- Ran the site audit with zero reported errors and warnings during completed validation passes.
- Confirmed successful production builds after the intake and rendering fixes.
July 3, 2026 · Homepage filter styling
Features
- Updated the endorser selector with a more compact height, a single border, and a muted mustard background that fits the site’s warm palette.
- Applied the same mustard background to the Primary / General segmented toggle for a more unified filter treatment.
Fixes
- Removed the extra shadowed border effect from the endorser selector.
July 2, 2026 · Compact data schemas and embedded-contender refresh
Workflow
- Standardized the public workbook commands as intake:new for new races and intake:complete for completed races and general-card creation.
- Added compact candidate and race schemas with compatibility readers so existing UI code can continue working during migration.
- Added npm run validate:races to check contender identity, FEC references, completed-race results, percentages, signals, and race sources.
- Updated the intake LLM instructions to require official state or local government election-tabulation sources for result percentages and winners.
- Added a shared participant resolver/writer so refresh scripts use standalone files for featured candidates and embedded race records for contenders.
Features
- Reduced candidate JSON files to the readable profile, featuredEndorsements, otherEndorsements, fec, finance, and metadata sections.
- Reduced race JSON files to race identity, featured candidate, embedded contenders, one result block, signals, and directly related source fields.
- Embedded primary and general-election contenders inside race JSONs instead of creating standalone contender candidate files.
- Limited live candidate and race directories to records actually displayed on the site, with unused records moved to z_archive.
Fixes
- Fixed all FEC refresh paths to read and write embedded contender IDs, totals, donor-size buckets, PAC receipts, outside spending, and provenance.
- Fixed force-refresh and refresh deduplication so contender-only slugs and contenders appearing in multiple races update correctly.
- Removed duplicate result structures, unused display blocks, the generic race links wrapper, and obsolete OpenSecrets links.
- Restored missing contender data including Christy Peterson’s FEC candidate and committee IDs.
- Removed unverified hand-entered donor tiers and other legacy candidate fields that were not rendered or used by refresh logic.
- Made archive cleanup scripts compatible with the version of Bash included with macOS.
July 1, 2026 · Colorado completion workflow and data cleanup
Workflow
- Completed the CO-01 and Colorado Senate primary workbook using the unified intake process.
- Added clearer intake:new and intake:complete command aliases while retaining the underlying workbook importer.
- Expanded completion previews to show winners, candidate percentages, result sources, general-election dates, and newly created general opponents before writing.
- Updated the changelog helper so a missing src/data/changelog.json file is initialized automatically.
Features
- Created the CO-01 general-election card after Melat Kiros advanced from the Democratic primary.
- Added human-readable ordering to candidate and race JSON files and documented the active data model.
- Consolidated project documentation around the current intake, data, refresh, deployment, and troubleshooting workflows.
- Reorganized all live race JSON files into a compact, human-readable schema.
- Reduced contender records to identity, FEC, finance, and provenance fields used by the site.
- Removed duplicated race titles, candidate lists, comparison-card row definitions, and legacy intake metadata from stored JSON.
- Added race-schema compatibility helpers so existing display, refresh, audit, snapshot, and intake workflows continue to work.
- Kept npm run intake:new and npm run intake:complete as the public workbook commands.
- Consolidated stored election outcomes into a single result block.
- Removed legacy signals.raceResult duplication from all live race JSONs and writers.
- Removed the generic links object and the unused OpenSecrets race link.
- Moved race verification URLs to the defined profile.sourceUrl field.
- Updated race schema adapters, card rendering, validation, intake, refresh, and organizer paths to preserve the final structure.
Fixes
- Fixed existing-race completion so it updates current candidate and race records instead of being treated as a new intake.
- Fixed completion previews that previously showed only file counts and hid the actual result and general-opponent data.
- Removed standalone opponent records and other non-displayed candidate and race files from live data directories.
- Fixed the cleanup criteria so only featured candidates receive standalone candidate JSON files.
June 27, 2026 · Workbook intake, embedded opponents, and uncontested primaries
Workflow
- Added one XLSX-based intake workflow for upcoming primaries, completed primaries, results, and general-card creation.
- Added LLM instructions for researching and filling the workbook directly with verified sources; candidate and opponent slugs are generated automatically.
- Archived the retired JSON intake and separate results workflows under data-entry/templates/archive/ so the active templates folder stays workbook-only.
- Added and tested batch intake using Ro Khanna in CA-17 and Ayanna Pressley in MA-07.
Features
- Moved comparison-only challengers into race-level opponents records while keeping full candidate JSON files for featured candidates.
- Added Primary Contest Status with contested, uncontested, and unknown values.
- Officially uncontested primaries can now appear in the race feed and map, display an Uncontested primary label, and render single-candidate campaign-finance data.
- Reduced the homepage hero to one slide for now.
- Comparison-only opponent records now live with the race that uses them, reducing stale standalone candidate files and simplifying quick opponent corrections.
- The workbook importer remains preview-first; use --write only after reviewing the generated race reports.
Fixes
- Fixed general-election cards so refreshed FEC totals and donor-size data display when comparison rows are missing or an opponent has no FEC data.
- Updated race completion, intake, FEC refresh, audits, snapshots, outside-spending lookup, homepage scope, map filtering, and race-card rendering for embedded opponents and uncontested races.
- Standardized uncontested-race checks on the shared contestStatus field across the feed, map, and card renderer.
- Removed the two-candidate requirement from verified uncontested races while retaining warnings and hidden cards for incomplete contested or unknown races.
June 24, 2026 · Site updates
Workflow
- Replaced the separate interactive result command and CSV importer with one CSV-driven race:complete workflow: npm run race:complete -- <results.csv>.
- Moved filled result files to data-entry/results/ and filled race-intake JSON to data-entry/race-intake/; templates/ now contains only reusable blank templates and instructions.
- Updated candidate intake commands and validation to use data-entry/race-intake/<race-slug>.json.
- race:complete now writes supplied opponent FEC identifiers into new opponent candidate files and fills missing identifiers on existing opponent records without overwriting populated values.
Features
- General-race slugs and titles are now generated consistently from structured office data, including ny-10-general / NY-10 for House races and me-senate-general / Maine U.S. Senate for Senate races.
- The race-completion template now accepts opponent party, FEC candidate ID, and FEC committee ID so newly seeded general-election opponents can be refreshed from FEC data immediately.
- Removed automatically derived general-race key and title columns from the blank result-completion CSV/XLSX templates.
- Renamed the reusable files to race-results-template.csv, race-results-template.xlsx, race-results-instructions.md, and race-intake-template.md.
- The general-election Polymarket URL remains an explicit completion-template field because the odds refresh cannot reliably infer the correct market without a seeded URL.
Fixes
- The About-page changelog now exposes the most recent entry containing changes, derives a readable date label when one is missing, and no longer presents an empty current-day entry instead of the latest update.
- Made race:complete safe to rerun: completed primary results are always written, existing general-election cards are merged and updated instead of causing the batch to abort, and existing editorial content is preserved.
- Updated race:complete to auto-detect comma-, tab-, and semicolon-delimited result files and to reject Excel workbooks with a clear export-to-CSV message.
June 21, 2026 · Site updates
Features
- Replaced the filter-responsive scoreboard with one compact, cycle-wide summary showing total candidates tracked, elected count and rate, primary advancement results, and general-election results.
- Moved the Primary / General selector into the map card as a compact segmented control so the map is the single place for choosing race stage.
- Placed the compact Primary / General segmented control on the same line as the 2026 Races heading on wider screens.
- Restored the boxed Primary and General election scorecard sections with larger, grouped figures while keeping so-far rates and active counts.
- Made the All Congress / House / Senate control local to the scoreboard and restored independent House / Senate filters inside the map.
- Placed the live candidates-tracked count beside the Scoreboard label, updating with the scoreboard-only House / Senate selector.
- Added an Active-races card-list filter for All, Close races, Favored, and Underdog, using the featured candidate’s odds for the currently selected Primary or General stage.
- The scoreboard and map office filters are intentionally independent; the organization selector remains shared across both.
- Active-race odds categories use Close: 40%–60%, Favored: above 60%, and Underdog: below 40%; races without odds appear only under All.
Fixes
- Removed the redundant Primary Candidates / General Candidates stage heading and the small Show, Office, and Month filter labels.
- Tightened the space above the race cards while preserving the existing Active / Completed, office, and relevant-month filters.
- Changed scoreboard percentages to so-far rates based on decided races, added active-primary counts, and suppressed zero elected/zero-percent headline metrics.
- Simplified scoreboard typography so counts, labels, and percentages use a more consistent scale and sit on the same line.
- Moved the Primary / General segmented control directly beside the 2026 Races title instead of aligning it to the far right, with additional spacing below the row.
- Added a full-width divider beneath the 2026 Races heading and Primary / General control so the map heading reads as a distinct section header.
- Grouped the map status, office, and month filters into segmented controls so each related set reads as one choice instead of a field of equal pills.
- Removed the duplicate candidates-tracked line inside the scoreboard; the count now appears only once beside the Scoreboard label.
- Restored organization-filter responsiveness to the scoreboard while keeping its All Congress / House / Senate selector independent from the map office filter.
- Collapsed the scoreboard by default and added an explicit Show / Hide disclosure so its separate office filter does not compete visually with the map controls.
- Limited the race-odds filter to Active views and made it available for both Primary and General races, using only the odds for the selected stage.
June 19, 2026 · Site updates
Workflow
- Updated race:complete so called races record only the public result essentials: outcome, candidate vote percentages, reporting percentage, date called, and an optional source URL.
- Added npm run normalize:race-fec as a repeatable cleanup/check for active race files that accidentally contain hardcoded FEC display values.
- Clarified the FEC source-of-truth workflow: active race cards read live FEC money from candidate JSON, while completed/frozen race cards may preserve explicit race-time snapshots.
- Documented that forced FEC refreshes (--force, --force-full, or --no-preflight) bypass preflight skips when donor-size, PAC, or outside-spending sections need a full repull.
Features
- Recorded results for OK-05 · Democratic Primary.
- Active race cards now surface public FEC profile links beside candidate names in the FEC comparison tables when a candidate or committee ID is available.
- Candidate records now store constructed FEC candidate and committee URLs under dataSources.fec when FEC IDs are present.
- Donation-size comparisons now render partial data: candidates with measured donor buckets show their percentages, while candidates without usable FEC bucket rows show an unavailable state instead of hiding the entire table.
- Active race cards with the same election date now sort by the featured candidate’s first name within each state, so Alexandria Ocasio-Cortez appears first among New York’s June 23 races.
- Candidate Comparison now shows the latest FEC refresh date once at the top of the expanded section, beside a prominent OpenSecrets race link.
- Added compact tap/click info popovers defining Donation Sizes, PAC and Outside Spending, and Total Raised without adding permanent text clutter to each card.
- Added an OpenSecrets election-page URL to every public race card and an audit warning for missing or malformed links.
- The FEC public URLs are deterministic links built from the saved FEC candidate and committee IDs; the API pull supplies the IDs and the site constructs the profile links.
- A candidate can still show total raised while donor-size buckets are unavailable if the FEC by-size endpoint returns no usable rows for that candidate.
Fixes
- Simplified every completed-race result badge to remove internal review notes and operational language; badges now show only Won/Lost/Advanced, candidate vote shares, percentage counted, date called, and source when available.
- Removed stale active-race FEC display overrides so newly refreshed candidate totals, including NY-14, are not masked by race JSON values.
- Kept completed/frozen race-card money snapshots protected, so primary-era comparisons do not silently change when the same candidate advances to a general election.
- Updated the race-card PAC/outside-spending table so it is no longer blocked merely because one candidate lacks donor-size bucket data.
- Total-raised comparisons now render when only one real candidate has usable FEC data, instead of requiring two complete comparison rows.
- Clarified campaign-finance scope in the interface: total raised excludes outside spending, donor-size percentages cover individual contributions, and PAC/outside figures are limited to specific FEC-reported activity.
June 15, 2026 · Site updates
Workflow
- Added npm run add:batch so multiple sourced intake JSON files can be dry-run, written, or updated in one terminal command while preserving the one-race-per-intake format.
- Cleaned up root documentation by consolidating duplicate README and changelog notes while keeping refresh-alert.md in place.
- Moved the audit report into docs/ and kept the original audit text intact.
- Added npm run normalize:race-fec to remove stale active race-card FEC display fields while leaving completed/frozen race snapshots intact.
Features
- Active race state sections now sort by soonest upcoming election date instead of alphabetical state order, while still clustering cards by state.
- Removed the computed Next races line from the map area now that active race sections are ordered by upcoming election date.
- Removed the duplicate Active Primary Races heading from below the map; the active cards now start directly with the state sections.
- Added support for an optional above-the-fold race takeaway block, displayed above the main blurb only when a race sets takeaway.visible to true and provides takeaway title/body copy.
- Updated New York 14th Congressional District · Democratic Primary from intake (candidates, results, or links).
- Updated HowTo Add Candidates with a batch add/update checklist for importing several candidates or races at once.
- cycleLabel remains manually maintained for now. Existing hidden takeaway text was preserved but is not displayed unless explicitly enabled, so this cleanup does not add new visible card clutter by default.
- Candidate JSON is now the live FEC source of truth for active race cards; race JSON should only carry historical money values as completed/frozen snapshots.
Fixes
- Standardized active race-card FEC display so live totals resolve from candidate JSON instead of hardcoded race-card amountDisplay/totalDisplay fields.
- Reordered candidate JSON files and race JSON files to better match the site card flow without changing the parsed data schema.
- Kept strict JSON formatting with no comments, so existing Astro imports and refresh scripts continue to parse the files normally.
June 13, 2026 · Site updates
Workflow
- Race intake now handles general elections more completely (a Polymarket link for the general card and an explicit opponent party, since some general elections are between two Democrats).
- Every race carries a link to a trusted polling index, so the polling area always points somewhere current even when there is no fresh poll.
- Added data-quality checks for the new endorsement signals: a candidate marked as Track AIPAC endorsed or DSA endorsed must carry a source link, or the build flags it.
Features
- Added an AIPAC Tracker Endorsed badge to candidates listed by Track AIPAC (Citizens Against AIPAC Corruption), verified against their public endorsements list, with a link to the source. 19 candidates currently qualify.
- Simplified the DSA signal to a single DSA icon on a candidate's endorsement line, covering candidates endorsed by DSA at either the national or local-chapter level. Each DSA endorsement now links to DSA's canonical endorsement pages.
- Added a methodology note on the About page (above the changelog) listing where each kind of data on the site comes from.
- Explored, then set aside, a larger “commitments matrix” idea: a per-candidate grid of pledges (No Fossil Fuel Money, Green New Deal, and others) alongside the donor-share figure. The principle was sound — only show a commitment when it has a single, canonical, current source of truth — but on inspection the available pledge lists did not meet that bar for this cycle (no current, candidate-specific master list we could rely on), so the matrix would have required ongoing manual editorial upkeep and risked showing claims we could not cleanly verify. The one signal that did meet the bar, the Track AIPAC endorsement list, shipped on its own as a sourced badge. The durable takeaway — separate computed FEC figures from sourced external claims, and require a real link for every claim — is now baked into the site’s data-quality checks.
Fixes
- Removed a duplicate DSA badge: DSA candidates previously showed both an icon and a separate pill. Now they show one DSA icon, like every other endorsing organization.
- Recorded campaign-finance data for newly added races against current FEC filings.
June 12, 2026 · Site updates
Workflow
- Race intake now handles general elections fully: a Polymarket link for the general card and an explicit opponent party (California top-two generals can be Democrat vs. Democrat).
- New intake rule for polling: every race carries a link to a trusted polling index (RealClearPolling or FiveThirtyEight), so the polling cell always points somewhere current even when no fresh poll exists.
Features
- The General view shows a notice that general-election cards are still being filled in.
Fixes
- Removed the per-card fundraising trend line: FEC money is reported quarterly, so a daily line sat flat and unreadable. Daily data snapshots continue behind the scenes for future trend features.
June 11, 2026 · Site updates
Workflow
- Updating existing races from intake now works end to end: missing candidate facts (FEC IDs, bios, links, endorsements) and race results are filled in without overwriting anything already recorded, and the preview lists exactly what will be skipped without an update flag.
- The intake tool warns when a sourced FEC candidate ID conflicts with the one on file — catching stale automatic matches before they hide real money.
- For completed races, printed next steps now use the per-candidate force refresh, which correctly bypasses the election-day data freeze.
- Daily data snapshots: one small file per day records every candidate’s totals and live market odds, with a backfill tool that reconstructs past days from history. Groundwork for trend and “what moved” features.
- The changelog now updates itself: adding a race or recording results appends an entry automatically.
Features
- Scoreboard label “Running” renamed to “Candidates” — it counts every tracked candidate in the cycle, including decided races.
- The methodology page now states who qualifies for a card: a current-cycle endorsement from one of the featured endorsers.
- Total Raised bars fall back to verified candidate records when a race’s stored figures are empty, so retroactively added races show real money.
Fixes
- Browser-tab and search titles no longer repeat the site name, and the site description is now “Progressive economic populists in one place.”
- README cleanup: removed stale paths and an outdated manual step.
June 10, 2026 · Site updates
Workflow
- New guided workflow for adding races: npm run add:prompt prints an LLM research template, add:race turns the returned JSON into race and candidate files with a reviewable preview, and add:menu prints the whole six-step process.
- Split the automated data pulls into a fast daily refresh (Mon–Sat) and a deep weekly refresh (Sundays), both ending in a data audit that blocks publishing if anything looks wrong.
- New npm run audit checks every data file against rules learned from real bugs: stub template values, negative money, mismatched district links, stale dates, and missing results.
Features
- Race cards now always display verified FEC data: donor-size tables and narratives read from pipeline-verified candidate records first, and races without measured data say so honestly instead of showing placeholder numbers.
- Added TX-30: Rev. Frederick Douglass Haynes III's completed primary (won with 72.6%) and his general-election matchup.
- The 'Next races' line above the map is now computed from listed election dates instead of hand-written text.
- Race cards can be deep-linked: a /#race-... link opens the page with that card expanded.
Fixes
- Removed placeholder donor numbers that had been displaying as if they were FEC figures across more than a dozen races.
- Fixed a negative total-raised display, names like 'Jr.' being treated as surnames, completed races showing '100% chance of winning', inconsistent date formats, and the doubled page title.
June 9, 2026 · Site updates
Workflow
- Renamed refresh scripts so each command says what it pulls: donor-size buckets, PAC/committee receipts, outside spending, all FEC money, Polymarket, or all public data.
- Updated the refresh documentation and GitHub Actions workflow to use the explicit all-public-data refresh command.
Features
- Reworked expanded race cards into three collapsible sections: Race Signals, Candidate Comparisons, and More Campaign Info.
- Grouped Donation Sizes, PAC and Outside Spending, and Total Raised inside Candidate Comparisons.
Fixes
- Updated donor blurbs to lead with donation-size comparisons and point readers to expanded PAC and outside-spending details.
- Kept PAC/outside spending details out of the collapsed card summary so the race cards are easier to scan.
June 7, 2026 · Site updates
Workflow
- Added batch race-result imports from CSV, with dry-run previews before site files change.
- Cleaned up race completion prompts for called, pending, advanced, and lost races.
- Simplified refresh commands around daily, deep, money, odds, IDs, and one-race updates.
Features
- Added Active / Completed map mode so the map remains useful after races finish.
- Added Bernie Sanders as a featured endorser and seeded federal Bernie-backed race cards.
- Simplified the Signals card to focus on the next upcoming races only.
Fixes
- Fixed endorsement icon merging so adding Bernie does not hide existing featured endorsers.
- Kept card icons scoped to featured endorsers only: Justice Dems, DSA, Indivisible, and Bernie Sanders.
- Fixed mobile display issues around light mode and endorsement icon alignment.
- Removed duplicate count-status text from result pills.
- Adjusted the scoreboard so primary counts use active running races and general labels use Won / Win rate.
- Updated the Bernie endorsement icon to use a light-blue bird badge.
June 6, 2026 · Site updates
Workflow
- Moved the site onto the custom domain setup and cleaned up the GitHub Pages path assumptions.
- Added script documentation for maintaining refreshes, race completion, and imports.
Features
- Added Buy Me a Coffee support buttons on the home carousel and About page.
- Added social/news icons beside candidate support links.
- Added a ballot-box favicon and social sharing preview image.
Fixes
- Fixed public asset paths for the custom domain, including favicon and endorsement icons.
- Updated social sharing text to: Find unbought candidates and support them!
June 5, 2026 · Site updates
Workflow
- Defined the completed-race workflow: freeze automated data, manually confirm results, then move races to Completed.
- Added support for pending counts so uncalled races can show Results Pending without leaving the active workflow.
Features
- Renamed the primary dashboard to Scoreboard.
- Added completed-race filters for Advanced and Lost outcomes.
- Updated Graham Platner's primary card date and kept it active.
- src/components/RaceCard.astro — the race card, extracted from
- src/utils/raceDisplay.ts — the ~40 pure display helpers moved out of
- src/utils/names.ts — surname handling shared by all prose generators.
- src/pages/index.astro — 1,911 → 365 lines. Now does only data
- src/utils/donorNarrative.ts — the critical fix. Donor-tier
- src/components/RaceMap.astro — the hardcoded (and already-stale)
- src/layouts/BaseLayout.astro — no more "SmallDonorArmy ·
- src/styles/global.css — one appended rule for the new
- 16 race JSONs — removed the copy-pasted template donor values
- 10 candidate JSONs — removed the same template tiers where they had
- 25 cards still show "As of May 23, 2026" on empty polling cells — that date
- The npm run audit validator, scaffolder (new:candidate / new:race),
Fixes
- Removed the footer from the About page.
- Aligned carousel arrows with the map's burnt-orange color.
June 2, 2026 · Fix FEC totals refresh validation
Features
- Fixed totals refresh argument order so new FEC totals are validated correctly instead of validating stale JSON values.
- Treat missing FEC PAC/committee total fields as zero, preventing valid totals refreshes from failing with pacTotal undefined.
- Changed sharp total-raised drops versus previous JSON from hard skips into visible warnings so fresh FEC totals can still update while remaining reviewable.
- Removed the visible per-card outside-money drill-down section for now because it was too noisy/sloppy for launch.
- Kept the underlying outside-spending summary data available for candidate-card blurbs and future polished money sections.
- Added Graham Platner campaign video (53bZ_95nDjk).
- Added Nida Allam campaign video (zY3VCEfq7qQ).
- Kept latest FEC PAC/outside-spending refresh fixes intact.
- Fixed Schedule E outside-spending refresh so negative amendment/refund rows cannot create invalid negative displayed support/oppose totals; totals are now netted and clamped at zero for display.
- Added more robust FEC candidate ID lookup aliases/search normalization for candidates whose public names include titles or alternate filing names, including Dr. Adam Hamawy and Peggy Flanagan.
- Kept refresh scope limited to listed public race-card candidates.
June 1, 2026 · Fix FEC PAC refresh scope and reporting
Features
- Changed PAC breakdown refresh to use FEC Schedule A Form 3 line 11C, which is the candidate-committee line for PAC/other political committee contributions.
- Stopped rejecting PAC breakdowns solely because a classified Schedule A pull exceeds a stale cached /totals PAC value; totals can be refreshed separately.
- Made FEC refresh output clearer by listing the active scope, step meanings, and frozen/skipped records without noisy per-candidate frozen lines.
- Added YouTube campaign videos for Cori Bush, Dr. Adam Hamawy, Brad Lander, Claire Valdez, and Darializa Avila Chevalier.
- Checked listed public race-card candidate video coverage; Graham Platner and Analilia Mejía are still missing videos, along with several other listed candidates.
- Added a shared site-scope helper that defines which races/candidates are listed on the public dashboard.
- Updated homepage, endorsement helpers, Polymarket refresh, freeze logic, and FEC refresh candidate selection to use the same listed race-card scope.
- Fixed FEC refreshes pulling candidates from single-candidate/reference race files, such as AOC, when those races are not visible competitive cards.
- Improved refresh output so it states the active scope in plain language.
- Fixed candidate-scoped refreshes so --candidate= is case-insensitive and normalizes spaces/underscores to slugs, for example Randy-Villegas now matches randy-villegas.
- Fixed candidate-scoped refreshes so a zero-match candidate filter is reported as a real command error instead of silently finishing with Done. 0 problem(s).
- Kept FEC refreshes featured-candidate-only by default.
- Updated the homepage intro paragraph to the new “another battle” framing.
- Changed the map heading to “2026 Races” and kept it at the smaller map-card title size.
- Refreshed available Polymarket and polling values across the tracked races where current public snippets/pages were available.
- Added a completed-race badge for NJ-11 noting Analilia Mejía won the April 16 special election.
- Left May 19 primary races uncalled; no winner was added where a reliable result was not yet available.
- Changed the map card heading from “2026 Race Directory” to “Join the fight. Find candidates to support!”
- Removed the duplicate race-list heading below the map.
- Replaced the polling “stub” pill with an “As of [date]” badge when a poll date is available.
- Ran npm run build successfully.
- Retitled the component UI from 2026 Race Map to 2026 Race Directory.
- Kept the existing map architecture and inline no-framework script.
- Moved the core filter row above the month row.
- Made the core taxonomy filters fixed instead of data-derived:
- Election: All · Primary · General
- Level: All · Federal · State
- Office: All · Senate · House
- Kept month pills dynamic from actual electionDate values.
- Added a result count: Showing X races in Y states.
- Added an empty state when no races match the selected filters.
- Added URL state for filters, for example:
- /?election=primary&level=federal&office=senate&month=8
- Added browser back/forward support for filter changes.
- Updated filtering so the same selected filters control:
- active/dimmed states on the map
- active/dimmed small-state tray buttons
- visible/hidden campaign cards in the feed below
- Updated state click behavior so it scrolls to the first currently matching campaign card in that state.
- Removed the fallback behavior where clicking a dimmed state could still jump to a non-matching race.
- Normalized lowerChamber → house and upperChamber → senate internally so the fixed Office filter stays simple.
- Replaced the hand-maintained active/reference race imports with import.meta.glob('../data/races/*.json').
- Active campaign cards are now generated from race JSON where status: "active".
- Reference comparisons are now generated from race JSON where status: "reference".
- Active races sort by priority, highest first.
- Added race metadata attributes to each homepage campaign card:
- data-race-card
- data-state
- data-month
- data-election-type
- data-office-level
- data-office-type
- data-status
- data-priority
- Added small normalization helpers so the campaign feed and map use the same election/level/office/month values.
- Left the existing featured-race card design, donor narrative, signals grid, CTAs, and reference comparison section intact.
- Added status: "active".
- Added priority: 30.
- Existing race classification remains:
- electionDate: "2026-11-03"
- electionType: "general"
- officeLevel: "federal"
- officeType: "senate"
- Added priority: 20.
- electionDate: "2026-08-04"
- electionType: "primary"
- Added priority: 10.
- electionDate: "2026-08-11"
- Added status: "reference".
- Added priority: 0.
- This keeps reference comparisons distinct from active campaign entries.
- Added styles for the race-directory result count.
- Added styles for the empty-filter state.
- Added a [hidden] rule so filtered-out campaign cards disappear cleanly.
- Kept the existing visual system, card styling, map colors, and pill styling.
- Filters update the map, the campaign feed, the result count, and the URL.
- Month pills sit below the Election / Level / Office row.
- State and House options always appear, even when the current dataset has no matching races yet.
- Clicking a state scrolls to the first visible matching campaign card for that state.
- Clicking a state does not filter the feed by that state.
- If the current filters exclude a state, clicking that dimmed state does nothing because there is no visible matching feed entry.
- electionType: primary, general
- officeLevel: federal, state
- officeType: senate, house
- Legacy aliases still normalize correctly:
- upperChamber → senate
- lowerChamber → house
- Ran npm install because the clean source zip excludes node_modules.
- Added active PA-03 Democratic primary race.
- Race metadata:
- state: "pa"
- electionDate: "2026-05-19"
- officeType: "house"
- status: "active"
- priority: 85
- Featured candidate: chris-rabb.
- Candidates:
- chris-rabb
- sharif-street
- ala-stanford
- shaun-griffith
- Added homepage signals for polling/news/market context.
- Added all comparison cards:
- Donor profile
- Total raised
- PAC posture
- Super PAC outlook
- Geographic base
- Added race takeaway.
- Added candidate profile page data.
- Added homepage/campaign card copy and links.
- Added individual donor, PAC, and super PAC sections.
- FEC summary source baseline:
- Total receipts: $1,000,978.48
- Individual contributions: $921,637.13
- Other committee contributions: $43,950.00
- Total receipts: $1,010,683.61
- Individual contributions: $886,308.48
- Other committee contributions: $121,650.00
- Total receipts: $756,544.28
- Individual contributions: $474,353.48
- Other committee contributions: $32,000.00
- Candidate loan: $250,000.00
- Added outside-spending note for reported 314 Action Fund support.
- FEC committee summary source baseline:
- Total receipts: $9,090.00
- Individual contributions: $6,990.00
- Candidate contributions: $2,100.00
- Other committee contributions: $0.00
- Added Pennsylvania state outline so the PA-03 race intro and homepage card render a state silhouette.
- Extracted from the existing committed src/assets/us-map.svg path for Pennsylvania.
- Updated .compare-three-grid to use repeat(auto-fit, minmax(150px, 1fr)) instead of a fixed 3-column grid.
- This lets comparison cards render four-candidate races cleanly while preserving the existing card style.
- FEC total receipts, individual contributions, committee contributions, candidate loans, and cash-style summary numbers are based on FEC candidate/committee summary pages available during this build.
- Donor tier percentages are first-pass hand estimates. The full itemized donor-tier processing pipeline has not been run.
- PAC industry categorization has not been run. The PAC card uses direct other committee contributions totals only.
- Stanford outside-spending language is based on contemporaneous reporting and should be verified against FEC independent-expenditure filings before treating it as final production data.
- Candidate videos are left as placeholders unless a YouTube ID is added later.
- Ran npm run build successfully after adding the PA-03 race and candidate files.
- Replaced the two active-race CTAs with one combined link:
- Meet [candidate] + compare the money →
- The combined CTA links to /race/<slug>/, not the candidate-only page.
- Imports and renders CampaignSection for the race’s featuredCandidate.
- Adds a compact editorial label above the candidate card:
- Why we're watching [candidate]
- Adds the heading:
- Compare the Candidates
- Keeps the donor contrast lede immediately above the comparison cards.
- Changed endorsements from a vertical list to a comma-separated inline line.
- This reduces vertical height on the race page while preserving endorsement content.
- Added race-page styles for the featured candidate block and Compare the Candidates heading.
- Updated CTA comments to reflect the new single-path homepage flow.
- Updated endorsement styles for compact inline rendering.
- The race page now carries both sides of the FTM value proposition:
- the editorial candidate lift, and
- the neutral money comparison.
- The comparison remains visually separated by the Compare the Candidates heading, so the page does not collapse into a generic campaign profile.
- Ran npm install because the source zip excludes node_modules.
- Updated the site/nav title from “Follow the Money” to “Candidates With Receipts.”
- Updated the homepage hero title to “Candidates With Receipts.”
- Added the homepage tagline: “Candidates you should fuck with — and the money trail to prove it.”
- Replaced the homepage description with the new billionaires / Epstein class / working- and middle-class donor framing.
- Updated the default metadata description and footer brand reference to match the new name.
- Added TN-09 Democratic primary as an active federal House race.
- Featured candidate: Justin J. Pearson.
- Comparison candidate: Steve Cohen.
- Added race metadata for the Race Directory:
- state: "tn"
- electionDate: "2026-08-06"
- priority: 75
- Added Polymarket signal for TN-09 Democratic primary.
- Added first-pass comparison cards:
- Corporate PAC posture
- Added full candidate profile for Justin J. Pearson.
- Added official campaign video ID: O8P-XRIRDFw.
- Added campaign positioning, endorsements, action URLs, FEC summary totals, donor profile estimates, PAC posture, and outside-spending caveat.
- Added comparison candidate profile for Steve Cohen.
- Added FEC summary totals, donor profile estimates, PAC posture, cash-on-hand note, and outside-spending caveat.
- Added Tennessee state outline for homepage/race-page display.
- Replaced placeholder/Kalshi Polymarket signal with the requested Polymarket race market:
- https://polymarket.com/event/pa-03-democratic-primary-winner
- Updated visible odds signal:
- Street 54%
- Rabb 41%
- Stanford low single digits
- Set as-of date to May 13, 2026.
- Added official campaign video ID: 6Xki6idO4UY.
- Updated video label to “Official campaign video.”
- Tightened donor-count wording for the homepage-generated donor narrative.
- Added official campaign video ID: DPabbCgOb2A.
- Fixed the output path so newly extracted state outlines write to src/assets/states/ instead of the old ftm-site8/ working-directory path.
- Added topojson-client and us-atlas so the existing state-outline extraction script can run cleanly when adding new states.
- FEC summary pages for Pearson and Cohen current-cycle totals through March 31, 2026.
- Polymarket race pages for PA-03 and TN-09 Democratic primary odds.
- Pearson campaign site and public reporting for campaign positioning and endorsements.
- Tennessee Secretary of State / race-calendar sources for August 6, 2026 primary date.
- Donor-tier percentages and average-gift figures for TN-09 are first-pass estimates pending full itemized FEC processing.
- PAC industry categorization has not been run; the PAC card uses direct other committee contributions totals only.
- Leaders We Deserve support for Pearson is noted as documented outside support, but no pledged or expected dollar figure is counted as spent money.
- TN-09 polling is left empty/stubbed because no reliable public poll was added in this pass.
- Ran npm install.
- Updated the candidate-card donor narrative to use the FEC-grounded wording: FEC filings show [last name] raised [total], with [x]% of individual dollars coming from donors giving $2,000 or less.
- Changed the PAC/committee sentence to the clearer public-facing language: [x]% of the campaign's reported fundraising came from PACs and other committees.
- Kept the outside-spending sentence as reported-dollar language and continued suppressing missing-data fields instead of making unsupported claims.
- Added npm run refresh:fec:spending:candidate -- --candidate=slug for one-candidate PAC/committee breakdown and outside-spending refreshes.
- Added the new candidate-scoped spending command to the npm run refresh command menu, including an Angela Gonzales-Torres example.
- Renamed visible site branding from SeatbySeat.Vote to SmallDonorArmy where surfaced in the layout, metadata, homepage title, footer, docs, and package name.
- Updated npm run refresh:fec:spending:candidate -- --candidate=slug to run the FEC ID resolution step before PAC/committee and outside-spending pulls, so a candidate without cached FEC IDs does not immediately fail.
- Quieted the global freeze-summary output for candidate-scoped spending refreshes to reduce command noise.
- Updated the refresh command menu to clarify that the candidate-scoped spending command resolves missing FEC IDs first.
- Changed FEC refresh loops so they operate only on race-card featuredCandidate records by default, preventing PAC/outside-spending refreshes from walking opponent or reference candidate JSON files.
- Added a guarded --include-all-candidates escape hatch for deliberate audits, but normal refresh commands remain featured-only.
- Updated refresh:fec:totals, refresh:fec:donors, refresh:fec:pacs, and refresh:fec:outside-spending so they resolve missing FEC IDs before running their specific pull, reducing missing fecCommitteeId / missing fecCandidateId skip noise.
- Updated the npm run refresh menu to document the featured-only scope and candidate-scoped usage.
- Updated the FEC outside-spending refresh so the public race-card opposing total includes both direct Schedule E opposition to the featured candidate and Schedule E support for listed opponents in the same race.
- Kept refresh targets limited to listed public race-card featured candidates; opponent FEC IDs are resolved only in memory as race context and are not written to opponent JSON.
- Added structured fields for direct opposition and opponent-support components so the aggregate can be audited later.
- Clarified refresh command text so outside explains that support for listed opponents is counted as race-opposing outside spending.
- Added shared site-scope helpers for completed-race status and default primary-phase visibility.
- Server-rendered non-primary race cards now start hidden on the default Primary view, preventing general-election cards like Graham Platner's Maine general race from appearing in the primary list before client filters run.
- Tightened completed-race detection to treat status: completed, electionResult.status: completed, and electionResult.status: called as completed for the active/completed split.
- Updated candidate-card Polymarket blurb logic so a general-election card does not borrow a stale primary market; odds sentences now come from the market for the visible race phase and are suppressed when the featured candidate is not a named market outcome.
May 30, 2026 · Completed-race navigation and result badges
Features
- Added a small View completed races ↓ link in the 2026 Races map header.
- Added a floating back to top button that appears after scrolling.
- Updated completed-race badges with larger visual treatments:
- 👑 Won for candidates who won/advanced.
- ✊ Good fight for completed races where the highlighted candidate did not win.
- Kept the top nav clean; completed-race access remains contextual inside the race map area.
- Fixed the feed data split so completed races no longer render inside the Active Races section.
- Added a dedicated Completed Races archive section below the active race feed.
- Fixed winner/loss badge logic so the crown only appears when the featured candidate actually won.
- Updated losing-result copy to Good Fight (lost).
- Ran npm run build successfully.
May 14, 2026 · Homepage intro block
Features
- Reworked the homepage hero from one long manifesto paragraph into a structured intro.
- Added the new trust/follow-the-money tagline under the site title.
- Added three compact idea cards: Unrig elections, Bypass the oligarchs, and Back the unbought.
- Added the Shirley Chisholm unbought and unbossed closing line below the cards.
- Kept the race directory, map filters, and signal cards unchanged.
- Replaced the prior hero CTA with the tighter line: Follow the money. See who’s funded by people, not oligarchs. Then help them win.
- Added a short intro that connects broader election reform fights to this site’s direct-funding lever.
- Reworked the three homepage cards so they are site-specific: Check the receipts, Compare the money, and Help them win.
- Clarified that the site surfaces data to evaluate independence claims rather than acting as a simple endorsement page.
- Added first-pass race-directory entries for Indivisible-backed candidates not already represented on the map: Brad Lander (NY-10), Julie Gonzales (CO-Sen), Analilia Mejia (NJ-11), Dr. Jasmine Clark (GA-13), and Randy Villegas (CA-22).
- Added Indivisible endorsement tags to existing Graham Platner and Peggy Flanagan candidate records so the endorser filter catches them.
- These new entries are intentionally first-pass shells: the map/filter/feed are live, but donor-tier calculations, opponent comparison cards, polling, and Polymarket fields are pending.
- Upgraded Indivisible-backed shell entries into usable race packages with opponent context, comparison cards, polling/market signals where available, and candidate profile data.
- Added opponent/benchmark candidates: Dan Goldman, John Hickenlooper, Everton Blair Jr., Jasmeet Bains, David Valadao, and Joe Hathaway.
- Added/updated Polymarket signals for NY-10, CO Senate, GA-13, CA-22, and NJ-11 where public markets were available.
- Added available public polling/result signals and clearly marked missing or partial polling as stubbed.
- FEC receipt totals and committee/PAC totals are from FEC summary pages; donor tier percentages remain hand-estimated pending itemized FEC processing.
- Fixed the endorsement filter bug where selecting an org filtered the map but hid every homepage race card.
- Added normalized endorsement metadata to each homepage race card so the feed and map use the same filtering inputs.
- Limited the Endorsed by dropdown to four top-level org filters: Working Families Party, Justice Dems, DSA National, and Indivisible.
- Kept Any as the default/no-filter state, so endorsed and unendorsed campaigns remain visible until an org is selected.
- Updated homepage race cards to match the approved dashboard-card mockup.
- Removed the donor tier bar/legend from homepage race cards.
- Added a compact Candidate Comparison table below the signal cards.
- Put donor-range definitions directly under the table headers: Small + Mid = under $1K; Near-cap = $5K–legal max.
- Applied the same header-range treatment to the race comparison donor table and removed the separate tier-breakdown chart/key from that card.
- Removed the candidate roster line under each homepage race eyebrow.
- Applied the approved mockup text hierarchy to homepage dashboard cards.
- Kept the race eyebrow in the red/coral FTM treatment.
- Enlarged the candidate name treatment so it carries the card like the mockup.
- Kept the Candidate Comparison title in the serif display treatment.
- Capped homepage candidate names at 65px max while keeping the same serif display treatment.
- Added compact endorsement badges to the right of each featured candidate name.
- Wrapped the donor profile text and the three signal cards in a native accordion so they can be hidden without hiding the Candidate Comparison table.
- Renamed the site/navigation/homepage title to “Progressive Movement Map.”
- Updated the homepage tagline to “Find the people-powered candidates that inspire you and help them win.”
- Removed the longer homepage intro paragraph and three proof cards.
- Added uploaded endorsement logos for Justice Dems, DSA National, Our Revolution, Sunrise, and Indivisible next to candidate names when those endorsements are present; unsupported groups still fall back to a compact text badge.
- Added Our Revolution and Sunrise to endorsement normalization so their logos can render from candidate JSON endorsement text.
- Updated donor comparison table headers to state the federal near-cap range as $5K–$7K federal cap.
- Updated state outline rendering to use black 1px non-scaling strokes and proportional state dimensions inside a consistent 96px card slot.
- Reverted state outline sizing from real-world relative proportions to same-size card icons with black 1px outlines.
- Increased endorsement logo badges and removed text fallback badges next to candidate names; only orgs with uploaded icons render there.
- Made Donor Profile visible by default, added a closed “More Campaign Info” accordion with campaign video and website link, and kept Race Signals open by default.
- Removed the “Meet candidate + compare the money” CTA from homepage cards; News Coverage remains as the external link.
- Added a secondary “Show total raised” reveal inside Candidate Comparison instead of creating another top-level accordion.
- Moved endorsement logos into a dedicated Endorsed by: row below candidate names.
- Made Candidate Comparison a closed-by-default accordion using the same header scale as the other dashboard sections.
- Removed the nested Show total raised reveal; total-raised bars now appear inside Candidate Comparison when that section is opened.
- Reduced the vertical gap between the homepage title/tagline and the map.
- Added hover labels to endorser logo icons and made the logos larger.
- Moved More Campaign Info below Candidate Comparison.
- Matched the Total raised bar section spacing/typography to the candidate comparison table and gave its header the same background treatment.
- Added a closing action block to homepage candidate cards.
- The block reads: “These are people-powered campaigns. Help them win.”
- Added direct Donate and Volunteer buttons using each featured candidate’s homepage.donateUrl and homepage.volunteerUrl.
- Moved the News Coverage link below the Donate/Volunteer action.
- Kept campaign links/data-driven URLs in candidate JSON as the source of truth.
- Added a plain paragraph under the homepage tagline.
- Renamed the race directory heading to “Join the fight. Find candidates to support!” and increased its size.
- Removed the closing action header text while keeping Donate/Volunteer buttons.
- Added a thin divider below the action buttons.
- Bolded Race Signals card headers.
- Increased embedded campaign video width.
- Matched total-raised candidate-name sizing to the candidate comparison table.
- Removed the homepage title/tagline block from the top of the page, leaving only the plain intro paragraph.
- Simplified polling cards to show header + as-of date, one result line, and a single source link.
- Polling links now use clearer labels such as “View RealClearPolling Average” when the source is RCP.
- Sorted active homepage candidate cards alphabetically by the featured candidate’s first name.
- Added DSA National’s remaining federal campaign from the uploaded endorsement CSV: Oliver Larkin.
- Added oliver-larkin.json and jared-moskowitz.json candidate records.
- Added a South Florida House Democratic primary race record for Larkin’s current redistricting-shifted campaign context.
- Added Florida state outline asset.
- Added DSA National to Chris Rabb’s endorsement list so the DSA filter catches the federal DSA National-backed races.
- Added first-pass FEC summary totals, donor-comparison estimates, committee/PAC posture, race signals where available, and notes about Florida redistricting uncertainty.
- Donor-tier percentages remain hand-estimated pending itemized FEC processing.
- Updated the top homepage copy so it says the site focuses on another lever: candidates and removed the word “highlighting.”
- Changed the DSA endorsement/org label to DSA National in the filter, icon tooltips, and normalized endorsement display.
- Removed the Level filter from the map controls because the current build is federal-only.
- Updated More Campaign Info so it shows a larger YouTube campaign video followed by an endorsers list.
- Removed the campaign website link from More Campaign Info.
- Ran npm run build successfully.
- Added remaining federal Justice Democrats-backed candidates from the official Justice Democrats candidates page.
- Included both upcoming challengers/open-seat candidates and current Congressmembers.
- Sorted homepage cards alphabetically by candidate first name already via the homepage data sort.
- Entered FEC summary totals where readily available from The Green Papers/FEC summary references; donor-tier percentages remain first-pass estimates pending itemized FEC processing.
- Marked completed/ended races where current public sources showed a primary result, primary winner, or withdrawn campaign.
- Added a first-pass Movement Scorecard showing tracked endorsed candidates currently in office, up for re-election, new candidates, won/lost outcomes, and net House/Senate changes.
- Added organization-level scorecard rows for Justice Dems, DSA National, Working Families Party, Indivisible, Our Revolution, and Sunrise when represented in the tracked federal dataset.
- Race Signals now render clear fallback cards when polling, primary markets, or general-election markets are unavailable instead of leaving blank space.
- Single-candidate races now show a visible “No listed primary competition” badge and signal fallback text.
- Added support for separate race-stage cards for the same candidate/campaign.
- Split Graham Platner into a live Maine U.S. Senate general-election card and a completed Maine Democratic primary card.
- In the default All view, completed primary cards collapse into a compact historical header so they do not compete with active race cards.
- Selecting the Primary filter still filters out general elections and shows completed primary cards as normal dashboard cards.
- Updated Maine general-election metadata to use the November 3, 2026 general-election date rather than the June primary date.
- Added a minimal Janet Mills candidate profile so the completed Maine primary comparison can render.
- Added completed-primary race signals that emphasize the result rather than treating the race as a live forecast.
- Fixed the default All view so completed primary race-stage cards are already collapsed on first page load, not only after clicking the All filter.
- Added/normalized race-signal checked dates across tracked races so fallback signal cards are timestamped instead of appearing stale or blank.
- Added an available Polymarket signal for NY-14/AOC and kept unavailable polling/market data as explicit fallback states rather than invented numbers.
- Adjusted completed-result pill spacing so it sits between the endorsement row and donor-profile text instead of crowding either one.
- Re-audited every active/completed federal race card for Race Signals coverage.
- Ensured every race has a lastChecked date and polling fallback state so the dashboard renders a complete signal row even when no public polling exists.
- Added newly found Polymarket signals for CA-11, NY-07, MO-01, NY-14, CA-07, and FL-23/FL-25-related primary context where current public markets were available.
- Added general-election Polymarket outlooks where found for CA-11, MO-01, and NY-14.
- Preserved campaign-info formatting for candidates without a YouTube video by rendering a consistent campaign-video placeholder inside More Campaign Info.
- Added a formal “Site audit protocol” to the design methodology.
- Defined that an audit must check race status for every candidate, not only refresh visible signals.
- Added the rule that completed primaries should be preserved as completed race-stage cards and, when the candidate advances, paired with a separate active general-election card.
- Added requirements for signal fallbacks, campaign-video placeholders, endorsement normalization, scorecard refreshes, sorting/filter sanity checks, and changelog updates.
- Renamed public brand references from Progressive Movement Map / Follow the Money to SeatbySeat.Vote where visible in layout, docs, README, and metadata.
- Added the npm run audit validation script idea to TODO and documented the full site-audit protocol in the public methodology and canonical methodology doc.
- Moved the Movement Scorecard above the race map in the homepage layout.
- Removed dead internal candidate/race links from homepage comparison output where the corresponding dynamic routes are not present in this build.
- Removed the old Reference Comparisons homepage section to avoid dead /race/<slug> links until those pages are restored.
- Cleaned federal-only map code by removing stale office-level label plumbing from the map display model.
May 13, 2026 · Race page profile card trim
Features
- Removed the descriptive/positioning paragraph from the featured candidate profile card on race comparison pages.
- Kept the video, action buttons, and compact comma-separated endorsements intact.
- Left standalone candidate profile pages unchanged, so they can still use the fuller candidate introduction template.
- Moved news coverage out of the three-card homepage signal grid.
- Added a smaller News coverage link below the main “Meet [candidate] + compare the money” CTA.
- Added a new third signal card: General election outlook.
- Added signals.generalElectionOutlook to all active races:
- PA-03 House Election Winner — Democratic Party 94%, Republican Party 6%.
- TN-09 House Election Winner — Republican Party 81%, Democratic Party 14%.
- Michigan Senate Election Winner — Democrat 73%, Republican 27%.
- Minnesota Senate Election Winner — Democrat 92%, Republican 8%.
- Maine Senate Election Winner — Democrat 79%, Republican 22%.
- Renamed the candidate-level Polymarket signal label on the homepage to Primary odds when the market type is primary.
- Left race comparison pages unchanged.
- Polymarket PA-03 House Election Winner, TN-09 House Election Winner, Michigan Senate Election Winner, Minnesota Senate Election Winner, and Maine Senate Election Winner pages, checked May 13, 2026.
- Added explicit Polymarket source badges to the homepage Primary odds / Race odds card and the General election outlook card.
- Updated homepage market link text from View market to View Polymarket.
- Refreshed Polymarket signal data across active races:
- PA-03 Democratic primary: Street 52%, Rabb 50%.
- PA-03 general election outlook: Democratic Party 94%, Republican Party 6%.
- TN-09 Democratic primary: Pearson 52%, Cohen 30%.
- TN-09 general election outlook: Republican Party 82%, Democratic Party 15%.
- MN Senate Democratic primary: Flanagan 81%, Craig 17%.
- MN Senate general election outlook: Democrat 92%, Republican 7%.
- MI Senate general election outlook remains Democrat 73%, Republican 27%; primary odds as-of date refreshed.
- ME Senate general election outlook refreshed to Democrat 78%, Republican 23%.
- Refreshed polling cards where current public numbers were available:
- Michigan now uses the RealClearPolling Democratic primary average.
- Minnesota now uses the May 4 Public Policy Polling topline.
- Maine now uses the RealClearPolling average plus the March Emerson poll.
- PA-03 keeps the only public numeric poll found, with a note that newer private polling was reported without numbers.
- TN-09 remains blank for polling because no public numeric primary poll was found.
- Added a receipt/dollar SVG favicon at public/favicon.svg and linked it from the base layout.
- Renamed the homepage General election outlook card to General odds.
- Standardized the homepage signal card label row: icons and the Primary odds, Polling, and General odds labels now render in black.
- Reduced visual noise in the signal-card body: leader/result text, percentages, secondary lines, and notes now use the same muted size and color instead of mixed green, serif, bold, small, and italic treatments.
- Preserved the small Polymarket source badge so readers still know where market odds come from.
- Removed redundant Polymarket primary market and generic-market note lines from signal card bodies.
- Changed market-card top-right badges to show freshness, e.g. As of May 13, 2026, while keeping the source in the View Polymarket link.
- Replaced the old polling footer (1 poll · most recent ...) with a linked source/date line, e.g. RealClearPolling average May 13, 2026:.
- Kept non-RCP polling source lines data-driven so stubbed/private-poll races are not mislabeled as RCP averages.