← back to Sdcc Journalists Viewer
Google sign-in (allowlisted) + live at sdccjournalists.agentabrams.com
ca6312d7da4f5c306ac9870c2e477cccb2ddbc5a · 2026-07-23 10:11:08 -0700 · Steve Abrams
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Files touched
M data/journalists.jsonM public/index.htmlM server.js
Diff
commit ca6312d7da4f5c306ac9870c2e477cccb2ddbc5a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 23 10:11:08 2026 -0700
Google sign-in (allowlisted) + live at sdccjournalists.agentabrams.com
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
data/journalists.json | 2 +-
public/index.html | 110 +++++++++++++++++++++++++++++++++++++++-
server.js | 136 +++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 238 insertions(+), 10 deletions(-)
diff --git a/data/journalists.json b/data/journalists.json
index 48d9b03..4d80b78 100644
--- a/data/journalists.json
+++ b/data/journalists.json
@@ -1 +1 @@
-{"generated_at": "2026-07-23T10:02:27.562450-07:00", "drive_pull_ok": true, "source": "gdrive:SDCC/sdcc-journalists-media-contacts.xlsx", "count": 38, "rows": [{"name": "Jillian Berman", "outlet_on_file": "MarketWatch", "current_outlet_verified": "MarketWatch", "medium": "Print/Digital", "beat": "Student Debt", "email": "", "email_source": "not found", "phone": "", "twitter": "@jaborman", "location": "", "latest_article": "https://muckrack.com/jillian-berman/articles", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "69", "last_tracked_date": "2026-02-19", "first_contact": "2026-06-24", "initial_email": "Open in Gmail", "last_contact": "2026-06-24", "last_email": "Open in Gmail", "contact_source": "gmail correspondence (name match)", "notes": "Actively publishing July 2026 on OBBB student-loan caps; MarketWatch blocks crawlers \u2014 byline page given. Author of 'Sunk Cost' (2025).", "_links": {"latest_article": "https://muckrack.com/jillian-berman/articles", "initial_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19ef9d11379f7fd5", "last_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19ef9d11379f7fd5"}}, {"name": "Jessica Priest", "outlet_on_file": "The Texas Tribune", "current_outlet_verified": "The Texas Tribune (with Open Campus)", "medium": "Print/Digital", "beat": "Higher education", "email": "jessica.priest@texastribune.org", "email_source": "SDCC database", "phone": "512-716-8600", "twitter": "", "location": "TX", "latest_article": "Student success should mean more state money for universities, Texas agency suggests", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "2026-03-19", "initial_email": "Open in Gmail", "last_contact": "2026-06-03", "last_email": "Open in Gmail", "contact_source": "gmail correspondence (email match)", "notes": "Email on Tribune staff page.", "_links": {"latest_article": "https://www.texastribune.org/2026/07/22/texas-universities-performanced-based-funding-recommendation/", "initial_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19d093759a42a8d7", "last_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19e90f44246291fd"}}, {"name": "Michael Stratford", "outlet_on_file": "Politico", "current_outlet_verified": "Politico (moved to economy team \u2014 banking/financial regulation, Treasury, student debt)", "medium": "Print/Digital", "beat": "Education Policy", "email": "mstratford@politico.com", "email_source": "publicly listed (web-verified)", "phone": "", "twitter": "@maboratford", "location": "", "latest_article": "Trump administration explores selling off parts of the $1.6 trillion federal student loan portfolio", "article_date": "2025-10", "link_type": "author_page", "articles_tracked_10yr": "73", "last_tracked_date": "2026-01-19", "first_contact": "2026-04-06", "initial_email": "Open in Gmail", "last_contact": "2026-04-06", "last_email": "Open in Gmail", "contact_source": "gmail correspondence (email match)", "notes": "Email self-posted on X ('Add me to your press lists'). Politico direct URLs blocked; Oct 2025 loan-portfolio-sale scoop widely cited.", "_links": {"latest_article": "https://muckrack.com/mstratford/articles", "initial_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19d635ef313effc8", "last_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19d635ef313effc8"}}, {"name": "Katie Lobosco", "outlet_on_file": "CNN", "current_outlet_verified": "Tax Notes (LEFT CNN ~early 2025)", "medium": "TV", "beat": "Politics & Policy", "email": "", "email_source": "not found", "phone": "", "twitter": "@KaborLobosco", "location": "", "latest_article": "Latest Republican Tax Proposals Stall Ahead of Midterms", "article_date": "2026-07-13", "link_type": "article", "articles_tracked_10yr": "81", "last_tracked_date": "2026-02-27", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Now Capitol Hill tax-policy reporter \u2014 no longer student-loan beat.", "_links": {"latest_article": "https://www.taxnotes.com/featured-news/latest-republican-tax-proposals-stall-ahead-midterms/2026/07/13/7wdd3"}}, {"name": "Danielle Douglas-Gabriel", "outlet_on_file": "The Washington Post", "current_outlet_verified": "The Washington Post (education finance)", "medium": "Print/Digital", "beat": "Higher Education", "email": "", "email_source": "not found", "phone": "", "twitter": "@DaniDougGabriel", "location": "", "latest_article": "Americans say four-year colleges cost too much", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "80", "last_tracked_date": "2026-02-14", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Running a July 2026 paying-for-college reader series. Email not verifiable.", "_links": {"latest_article": "https://www.washingtonpost.com/education/2026/07/22/americans-say-four-year-colleges-cost-too-much/"}}, {"name": "Stacy Cowley", "outlet_on_file": "The New York Times", "current_outlet_verified": "The New York Times", "medium": "Print/Digital", "beat": "Consumer Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@staborwley", "location": "", "latest_article": "Trump Wants to Create More Banks. Many Firms Are Heeding His Call", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "80", "last_tracked_date": "2026-02-10", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "NYT URLs not retrievable \u2014 byline aggregator given. No public email.", "_links": {"latest_article": "https://muckrack.com/stacycowley/articles"}}, {"name": "Cory Turner", "outlet_on_file": "NPR", "current_outlet_verified": "NPR (Ed team correspondent/senior editor)", "medium": "Radio", "beat": "Education Policy", "email": "", "email_source": "not found", "phone": "", "twitter": "@caborturner", "location": "", "latest_article": "Under a new federal rule, colleges must leave grads better off or lose financial aid", "article_date": "2026-06-30", "link_type": "article", "articles_tracked_10yr": "75", "last_tracked_date": "2026-02-15", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "No public email.", "_links": {"latest_article": "https://www.npr.org/2026/06/30/nx-s1-5835631/turner-camhi-do-no-harm-college-loans"}}, {"name": "Tara Siegel Bernard", "outlet_on_file": "The New York Times", "current_outlet_verified": "The New York Times", "medium": "Print/Digital", "beat": "Personal Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@bernaborara", "location": "", "latest_article": "A Longer Life Can Lead to Financial Concerns, and More Questions", "article_date": "2026-04-29", "link_type": "author_page", "articles_tracked_10yr": "75", "last_tracked_date": "2026-02-16", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Direct NYT URL not retrievable \u2014 byline aggregator given. No public email.", "_links": {"latest_article": "https://muckrack.com/tarasbernard/articles"}}, {"name": "Zack Friedman", "outlet_on_file": "Forbes", "current_outlet_verified": "Mentor (mentormoney.com), founder/CEO \u2014 Forbes byline inactive since Aug 2022", "medium": "Print/Digital", "beat": "Student Loans", "email": "", "email_source": "not found", "phone": "", "twitter": "@zackfriedman", "location": "", "latest_article": "Biden Cancels Student Loans And Extends Student Loan Payment Pause", "article_date": "2022-08-24", "link_type": "article", "articles_tracked_10yr": "75", "last_tracked_date": "2026-02-23", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "CAUTION: no 2025-2026 bylines \u2014 consider deprioritizing; Forbes student-loan coverage now under Forbes Advisor staff.", "_links": {"latest_article": "https://www.forbes.com/sites/zackfriedman/2022/08/24/biden-cancels-student-loans-and-extends-student-loan-payment-pause/"}}, {"name": "Tariq Habash", "outlet_on_file": "The Century Foundation", "current_outlet_verified": "A New Policy (founder) / Student Borrower Protection Center senior fellow \u2014 no longer at The Century Foundation", "medium": "Print/Digital", "beat": "Education Equity", "email": "", "email_source": "not found", "phone": "", "twitter": "@TariqHabash", "location": "", "latest_article": "Banning Foreign Students Is the Opposite of 'Making America Great'", "article_date": "2025-06-18", "link_type": "article", "articles_tracked_10yr": "73", "last_tracked_date": "2026-02-17", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Op-ed in The Nation. TCF \u2192 SBPC \u2192 ED policy advisor (resigned Jan 2024) \u2192 A New Policy. No public email.", "_links": {"latest_article": "https://www.thenation.com/article/society/banning-foreign-students-is-the-opposite-of-making-america-great/"}}, {"name": "Chris Quintana", "outlet_on_file": "USA Today", "current_outlet_verified": "USA Today (investigative, higher ed)", "medium": "Print/Digital", "beat": "Higher Education", "email": "", "email_source": "not found", "phone": "", "twitter": "@ChrisQuintana", "location": "", "latest_article": "Next generation of nurses at risk with proposed student loan caps", "article_date": "2026-01-16", "link_type": "article", "articles_tracked_10yr": "69", "last_tracked_date": "2026-02-08", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "AOL syndication (USA Today blocks crawlers); has more recent Feb 2026 bylines without verifiable URLs.", "_links": {"latest_article": "https://www.aol.com/articles/next-generation-nurses-risk-proposed-213531292.html"}}, {"name": "Ryan Lane", "outlet_on_file": "NerdWallet", "current_outlet_verified": "NerdWallet (managing editor; moved toward small-business but retains student-loan bylines)", "medium": "Print/Digital", "beat": "Student Loans", "email": "", "email_source": "not found", "phone": "", "twitter": "@aborlane", "location": "", "latest_article": "3 Ways to Lower Your Student Loan Interest Rate", "article_date": "2026-06-18", "link_type": "article", "articles_tracked_10yr": "69", "last_tracked_date": "2026-02-12", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Date = last-updated (evergreen model). No public email.", "_links": {"latest_article": "https://www.nerdwallet.com/student-loans/learn/how-to-lower-student-loan-interest-rate"}}, {"name": "Aarthi Swaminathan", "outlet_on_file": "Yahoo Finance", "current_outlet_verified": "MarketWatch", "medium": "Print/Digital", "beat": "Economy & Policy", "email": "aarthi@marketwatch.com", "email_source": "publicly listed (web-verified)", "phone": "", "twitter": "@aaborwami", "location": "", "latest_article": "10 U.S. metro areas where builders are slashing prices on new homes", "article_date": "2026-07-17", "link_type": "article", "articles_tracked_10yr": "67", "last_tracked_date": "2025-12-22", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Moved from Yahoo Finance to MarketWatch (now housing/real-estate beat). URL is Yahoo syndication of her MarketWatch piece. Email self-published in her X post soliciting sources.", "_links": {"latest_article": "https://finance.yahoo.com/real-estate/articles/10-u-metro-areas-where-001000782.html"}}, {"name": "Adam Minsky", "outlet_on_file": "Forbes", "current_outlet_verified": "Forbes (senior contributor)", "medium": "Print/Digital", "beat": "Student Loan Law", "email": "", "email_source": "not found", "phone": "", "twitter": "@AdamSMinsky", "location": "", "latest_article": "Education Department Must Wipe Out Student Loans For 500,000 Borrowers Under Settlement, Says Court", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "67", "last_tracked_date": "2026-02-27", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Publishes multiple times weekly on student-loan law; no public email found.", "_links": {"latest_article": "https://www.forbes.com/sites/adamminsky/2026/07/21/education-department-must-wipe-out-student-loans-for-500000-borrowers-under-settlement-says-court/"}}, {"name": "Annie Nova", "outlet_on_file": "CNBC", "current_outlet_verified": "CNBC", "medium": "TV", "beat": "Personal Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@annianova", "location": "", "latest_article": "Student loan borrowers on new RAP plan can lose key benefits if they pay even one day late", "article_date": "2026-07-12", "link_type": "article", "articles_tracked_10yr": "67", "last_tracked_date": "2026-02-02", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Active on RAP/SAVE transition coverage. No public email found.", "_links": {"latest_article": "https://www.cnbc.com/2026/07/12/student-loans-rap-late-payment.html"}}, {"name": "Ayelet Sheffey", "outlet_on_file": "Business Insider", "current_outlet_verified": "Business Insider (senior economic policy reporter)", "medium": "Print/Digital", "beat": "Policy & Economy", "email": "", "email_source": "not found", "phone": "", "twitter": "@ayaborffey", "location": "", "latest_article": "Latest Trump administration email pushes SAVE student-loan borrowers to find a new repayment plan \u2014 now", "article_date": "2026-07", "link_type": "article", "articles_tracked_10yr": "65", "last_tracked_date": "2026-02-17", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "AOL syndication of her BI piece (BI blocks crawlers). No public email found.", "_links": {"latest_article": "https://www.aol.com/articles/latest-trump-administration-email-pushes-185705250.html"}}, {"name": "Jessica Dickler", "outlet_on_file": "CNBC", "current_outlet_verified": "CNBC", "medium": "TV", "beat": "Personal Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@jaborickler", "location": "", "latest_article": "BlackRock, Carhartt, Ford and Google launch skilled trades workforce initiative", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "62", "last_tracked_date": "2026-02-22", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "No public email found.", "_links": {"latest_article": "https://www.cnbc.com/2026/07/21/blackrock-carhartt-ford-google-skilled-trades.html"}}, {"name": "Laura Beamer", "outlet_on_file": "Jain Family Institute", "current_outlet_verified": "Jain Family Institute", "medium": "Print/Digital", "beat": "Student Debt Research", "email": "", "email_source": "not found", "phone": "", "twitter": "@laboramer", "location": "", "latest_article": "America's Student Loans Were Never Going to Be Repaid (NYT interactive, with Marshall Steinbaum)", "article_date": "2023-07-13", "link_type": "author_page", "articles_tracked_10yr": "62", "last_tracked_date": "2026-01-10", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Researcher, not reporter \u2014 no 2025-2026 bylines found; latest major byline July 2023.", "_links": {"latest_article": "https://jainfamilyinstitute.org/our-team/laura-beamer/"}}, {"name": "Charlie Eaton", "outlet_on_file": "UC Merced / Academic", "current_outlet_verified": "UC Merced (Assoc. Professor; co-founder HERE Lab)", "medium": "Print/Digital", "beat": "Higher Ed Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@CharlieDEaton", "location": "", "latest_article": "White Flight in Public Higher Education? Racial Avoidance of Hispanic-Serving Institutions (Sociology of Education)", "article_date": "2025", "link_type": "article", "articles_tracked_10yr": "57", "last_tracked_date": "2025-11-12", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Academic, not journalist \u2014 latest is a 2025 peer-reviewed article. Faculty-page email not verifiable.", "_links": {"latest_article": "https://journals.sagepub.com/doi/10.1177/00380407251352786"}}, {"name": "Adam Echelman", "outlet_on_file": "CalMatters", "current_outlet_verified": "CalMatters (with Open Campus)", "medium": "Print/Digital", "beat": "Higher education; workforce/job training; community colleges; student data privacy", "email": "aechelman@calmatters.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "CA", "latest_article": "A new law holds colleges accountable if their grads don't earn more than $36,000 a year", "article_date": "2026-07-16", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-16", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on CalMatters author page.", "_links": {"latest_article": "https://calmatters.org/education/higher-education/2026/07/college-graduate-earnings-california/"}}, {"name": "Aleksandra Appleton", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Indiana", "medium": "Print/Digital", "beat": "Indiana education policy; K-12 statewide", "email": "aappleton@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "IN", "latest_article": "IPEC chooses charter school policy leader to guide its work", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-02-02", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page.", "_links": {"latest_article": "https://www.chalkbeat.org/indiana/2026/07/22/karega-rausch-named-ipec-executive-director/"}}, {"name": "Carolyn Jones", "outlet_on_file": "CalMatters", "current_outlet_verified": "CalMatters", "medium": "Print/Digital", "beat": "K-12; special education; school funding; inequity/student achievement; policy", "email": "carolyn@calmatters.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "CA", "latest_article": "California reading scores are up, thanks to literacy investments. Can the state do more?", "article_date": "2026-07-20", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-11", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on CalMatters author page.", "_links": {"latest_article": "https://calmatters.org/education/k-12-education/2026/07/literacy-california/"}}, {"name": "Emma Epperly", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News", "medium": "Print/Digital", "beat": "Idaho K-12; North Idaho regional; special education", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "ID", "latest_article": "Idaho's average per-pupil spending edges up", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-24", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "No email displayed on author page.", "_links": {"latest_article": "https://www.idahoednews.org/top-news/average-idaho-per-pupil-spending-edges-up/"}}, {"name": "Hannah Dellinger", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Detroit", "medium": "Print/Digital", "beat": "Detroit schools; absenteeism; district operations", "email": "hdellinger@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "Detroit MI", "latest_article": "A crowded field: 35 are in the race for 5 seats on the Detroit school board", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-02-26", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page.", "_links": {"latest_article": "https://www.chalkbeat.org/detroit/2026/07/22/dpscd-candidates-running-for-the-detroit-school-board-in-nov-3-election/"}}, {"name": "Howard Blume", "outlet_on_file": "Los Angeles Times", "current_outlet_verified": "Los Angeles Times", "medium": "Print/Digital", "beat": "Education (K-12 and district policy)", "email": "howard.blume@latimes.com", "email_source": "publicly listed (web-verified)", "phone": "", "twitter": "", "location": "CA", "latest_article": "Falling enrollment. Budget uncertainty. LAUSD warns of layoffs and cuts", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-24", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "LAT blocks crawlers \u2014 byline page given. Email surfaced in public journalist listings.", "_links": {"latest_article": "https://muckrack.com/howard-blume/articles"}}, {"name": "Jaweed Kaleem", "outlet_on_file": "Los Angeles Times", "current_outlet_verified": "Los Angeles Times", "medium": "Print/Digital", "beat": "University of California; higher education; campus activism; free speech; religion; race; politics", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "CA", "latest_article": "UC asks California Supreme Court to block release of Trump's UCLA settlement offer", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-19", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "LAT blocks crawlers \u2014 byline page given. Covering UC/higher-ed incl. $1.2B DOJ-UCLA settlement fight.", "_links": {"latest_article": "https://muckrack.com/jaweedkaleem/articles"}}, {"name": "Kevin Richert", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News", "medium": "Print/Digital", "beat": "Education politics; education policy; higher education", "email": "krichert@idahoednews.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "ID", "latest_article": "How the feds' healthcare grants could affect Idaho education: Three examples", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-03", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Byline, date, email verified on article page.", "_links": {"latest_article": "https://www.idahoednews.org/top-news/how-the-feds-healthcare-grants-could-affect-idaho-education-three-examples/"}}, {"name": "Melanie Asmar", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Colorado (Bureau Chief)", "medium": "Print/Digital", "beat": "Denver Public Schools; school board governance; K-12 policy", "email": "masmar@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "CO", "latest_article": "Free school supplies, haircuts, vaccines, and more. Check out these 2026 back-to-school events", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-01-21", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page.", "_links": {"latest_article": "https://www.chalkbeat.org/colorado/2026/07/22/free-back-to-school-supplies-backpacks-haircuts-in-denver-metro-area/"}}, {"name": "Mikhail Zinshteyn", "outlet_on_file": "CalMatters", "current_outlet_verified": "CalMatters", "medium": "Print/Digital", "beat": "Higher education; state legislation; financial aid; labor issues; campus housing; college affordability", "email": "mikhail@calmatters.org", "email_source": "SDCC database", "phone": "", "twitter": "mzinshteyn", "location": "CA", "latest_article": "White House admits it used keywords to kill billions worth of California research grants", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-11", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on CalMatters author page.", "_links": {"latest_article": "https://calmatters.org/education/higher-education/2026/07/research-grants-california-2/"}}, {"name": "Ryan Suppe", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News (senior reporter, K-12 policy)", "medium": "Print/Digital", "beat": "Education policy; K-12 focus", "email": "ryan@idahoednews.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "ID", "latest_article": "Former Nampa school board chair resigns", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-02", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on IEN author page; LinkedIn confirms current role.", "_links": {"latest_article": "https://www.idahoednews.org/news/former-nampa-school-board-chairwoman-resigns/"}}, {"name": "Sean Dolan", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News", "medium": "Print/Digital", "beat": "Education state policy; local district governance", "email": "sean@idahoednews.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "ID", "latest_article": "Schools face soaring laptop costs as AI data centers squeeze supply", "article_date": "2026-07-20", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-25", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "BoiseDev syndication of his IEN piece. Email on IEN author page.", "_links": {"latest_article": "https://boisedev.com/news/2026/07/20/schools-face-soaring-laptop-costs-as-ai-data-centers-squeeze-supply/"}}, {"name": "Melissa Brown", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Tennessee (Bureau Chief)", "medium": "Print/Digital", "beat": "Tennessee education policy; school choice/vouchers; immigration impacts on schools", "email": "mbrown@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "TN", "latest_article": "Tennessee considers easing teacher licensure testing requirements", "article_date": "2026-07-08", "link_type": "article", "articles_tracked_10yr": "2", "last_tracked_date": "2026-03-10", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page. Leading Memphis-Shelby state-takeover coverage.", "_links": {"latest_article": "https://www.chalkbeat.org/tennessee/2026/07/08/will-tennessee-drop-edtpa-requirement-for-teacher-licensure/"}}, {"name": "Reema Amin", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Chicago", "medium": "Print/Digital", "beat": "Chicago Public Schools", "email": "ramin@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "Chicago IL", "latest_article": "Chicago Public Schools lays off 162 central and citywide employees", "article_date": "2026-07-13", "link_type": "article", "articles_tracked_10yr": "1", "last_tracked_date": "2026-03-20", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email publicly listed by Chalkbeat.", "_links": {"latest_article": "https://www.chalkbeat.org/chicago/2026/07/13/cps-lays-off-162-central-and-citywide-employees/"}}, {"name": "Jaden Edison", "outlet_on_file": "The Texas Tribune", "current_outlet_verified": "The Texas Tribune", "medium": "Print/Digital", "beat": "Public education", "email": "jaden.edison@texastribune.org", "email_source": "SDCC database", "phone": "512-716-8600", "twitter": "", "location": "TX", "latest_article": "Texas' longstanding 'Robin Hood' school finance system will face a new legal challenge", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Tribune staff page.", "_links": {"latest_article": "https://www.texastribune.org/2026/07/22/west-texas-midland-district-lawsuit-school-finance-robin-hood/"}}, {"name": "jillian", "outlet_on_file": "", "current_outlet_verified": "", "medium": "Print/Digital", "beat": "General", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "", "latest_article": "", "article_date": "", "link_type": "", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "DATA-ENTRY STUB \u2014 no outlet on file", "_links": {}}, {"name": "marka", "outlet_on_file": "", "current_outlet_verified": "", "medium": "Print/Digital", "beat": "General", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "", "latest_article": "", "article_date": "", "link_type": "", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "DATA-ENTRY STUB \u2014 no outlet on file", "_links": {}}, {"name": "news day", "outlet_on_file": "", "current_outlet_verified": "", "medium": "Print/Digital", "beat": "General", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "", "latest_article": "", "article_date": "", "link_type": "", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "DATA-ENTRY STUB \u2014 no outlet on file", "_links": {}}, {"name": "Sneha Dey", "outlet_on_file": "The Texas Tribune", "current_outlet_verified": "The Texas Tribune", "medium": "Print/Digital", "beat": "Public education", "email": "sneha.dey@texastribune.org", "email_source": "SDCC database", "phone": "512-716-8600", "twitter": "", "location": "Austin TX", "latest_article": "Higher education agency prepares to cut 3% of spending as ordered by Texas leaders", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Beat now education-to-employment pathways, community colleges, higher ed. Email on Tribune author page.", "_links": {"latest_article": "https://www.texastribune.org/2026/07/22/texas-higher-education-spending-cuts/"}}]}
\ No newline at end of file
+{"generated_at": "2026-07-23T10:09:35.190298-07:00", "drive_pull_ok": true, "source": "gdrive:SDCC/sdcc-journalists-media-contacts.xlsx", "count": 38, "rows": [{"name": "Jillian Berman", "outlet_on_file": "MarketWatch", "current_outlet_verified": "MarketWatch", "medium": "Print/Digital", "beat": "Student Debt", "email": "", "email_source": "not found", "phone": "", "twitter": "@jaborman", "location": "", "latest_article": "https://muckrack.com/jillian-berman/articles", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "69", "last_tracked_date": "2026-02-19", "first_contact": "2026-06-24", "initial_email": "Open in Gmail", "last_contact": "2026-06-24", "last_email": "Open in Gmail", "contact_source": "gmail correspondence (name match)", "notes": "Actively publishing July 2026 on OBBB student-loan caps; MarketWatch blocks crawlers \u2014 byline page given. Author of 'Sunk Cost' (2025).", "_links": {"latest_article": "https://muckrack.com/jillian-berman/articles", "initial_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19ef9d11379f7fd5", "last_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19ef9d11379f7fd5"}}, {"name": "Jessica Priest", "outlet_on_file": "The Texas Tribune", "current_outlet_verified": "The Texas Tribune (with Open Campus)", "medium": "Print/Digital", "beat": "Higher education", "email": "jessica.priest@texastribune.org", "email_source": "SDCC database", "phone": "512-716-8600", "twitter": "", "location": "TX", "latest_article": "Student success should mean more state money for universities, Texas agency suggests", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "2026-03-19", "initial_email": "Open in Gmail", "last_contact": "2026-06-03", "last_email": "Open in Gmail", "contact_source": "gmail correspondence (email match)", "notes": "Email on Tribune staff page.", "_links": {"latest_article": "https://www.texastribune.org/2026/07/22/texas-universities-performanced-based-funding-recommendation/", "initial_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19d093759a42a8d7", "last_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19e90f44246291fd"}}, {"name": "Michael Stratford", "outlet_on_file": "Politico", "current_outlet_verified": "Politico (moved to economy team \u2014 banking/financial regulation, Treasury, student debt)", "medium": "Print/Digital", "beat": "Education Policy", "email": "mstratford@politico.com", "email_source": "publicly listed (web-verified)", "phone": "", "twitter": "@maboratford", "location": "", "latest_article": "Trump administration explores selling off parts of the $1.6 trillion federal student loan portfolio", "article_date": "2025-10", "link_type": "author_page", "articles_tracked_10yr": "73", "last_tracked_date": "2026-01-19", "first_contact": "2026-04-06", "initial_email": "Open in Gmail", "last_contact": "2026-04-06", "last_email": "Open in Gmail", "contact_source": "gmail correspondence (email match)", "notes": "Email self-posted on X ('Add me to your press lists'). Politico direct URLs blocked; Oct 2025 loan-portfolio-sale scoop widely cited.", "_links": {"latest_article": "https://muckrack.com/mstratford/articles", "initial_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19d635ef313effc8", "last_email": "https://mail.google.com/mail/u/natalia@studentdebtcrisis.org/#all/19d635ef313effc8"}}, {"name": "Katie Lobosco", "outlet_on_file": "CNN", "current_outlet_verified": "Tax Notes (LEFT CNN ~early 2025)", "medium": "TV", "beat": "Politics & Policy", "email": "", "email_source": "not found", "phone": "", "twitter": "@KaborLobosco", "location": "", "latest_article": "Latest Republican Tax Proposals Stall Ahead of Midterms", "article_date": "2026-07-13", "link_type": "article", "articles_tracked_10yr": "81", "last_tracked_date": "2026-02-27", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Now Capitol Hill tax-policy reporter \u2014 no longer student-loan beat.", "_links": {"latest_article": "https://www.taxnotes.com/featured-news/latest-republican-tax-proposals-stall-ahead-midterms/2026/07/13/7wdd3"}}, {"name": "Danielle Douglas-Gabriel", "outlet_on_file": "The Washington Post", "current_outlet_verified": "The Washington Post (education finance)", "medium": "Print/Digital", "beat": "Higher Education", "email": "", "email_source": "not found", "phone": "", "twitter": "@DaniDougGabriel", "location": "", "latest_article": "Americans say four-year colleges cost too much", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "80", "last_tracked_date": "2026-02-14", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Running a July 2026 paying-for-college reader series. Email not verifiable.", "_links": {"latest_article": "https://www.washingtonpost.com/education/2026/07/22/americans-say-four-year-colleges-cost-too-much/"}}, {"name": "Stacy Cowley", "outlet_on_file": "The New York Times", "current_outlet_verified": "The New York Times", "medium": "Print/Digital", "beat": "Consumer Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@staborwley", "location": "", "latest_article": "Trump Wants to Create More Banks. Many Firms Are Heeding His Call", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "80", "last_tracked_date": "2026-02-10", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "NYT URLs not retrievable \u2014 byline aggregator given. No public email.", "_links": {"latest_article": "https://muckrack.com/stacycowley/articles"}}, {"name": "Cory Turner", "outlet_on_file": "NPR", "current_outlet_verified": "NPR (Ed team correspondent/senior editor)", "medium": "Radio", "beat": "Education Policy", "email": "", "email_source": "not found", "phone": "", "twitter": "@caborturner", "location": "", "latest_article": "Under a new federal rule, colleges must leave grads better off or lose financial aid", "article_date": "2026-06-30", "link_type": "article", "articles_tracked_10yr": "75", "last_tracked_date": "2026-02-15", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "No public email.", "_links": {"latest_article": "https://www.npr.org/2026/06/30/nx-s1-5835631/turner-camhi-do-no-harm-college-loans"}}, {"name": "Tara Siegel Bernard", "outlet_on_file": "The New York Times", "current_outlet_verified": "The New York Times", "medium": "Print/Digital", "beat": "Personal Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@bernaborara", "location": "", "latest_article": "A Longer Life Can Lead to Financial Concerns, and More Questions", "article_date": "2026-04-29", "link_type": "author_page", "articles_tracked_10yr": "75", "last_tracked_date": "2026-02-16", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Direct NYT URL not retrievable \u2014 byline aggregator given. No public email.", "_links": {"latest_article": "https://muckrack.com/tarasbernard/articles"}}, {"name": "Zack Friedman", "outlet_on_file": "Forbes", "current_outlet_verified": "Mentor (mentormoney.com), founder/CEO \u2014 Forbes byline inactive since Aug 2022", "medium": "Print/Digital", "beat": "Student Loans", "email": "", "email_source": "not found", "phone": "", "twitter": "@zackfriedman", "location": "", "latest_article": "Biden Cancels Student Loans And Extends Student Loan Payment Pause", "article_date": "2022-08-24", "link_type": "article", "articles_tracked_10yr": "75", "last_tracked_date": "2026-02-23", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "CAUTION: no 2025-2026 bylines \u2014 consider deprioritizing; Forbes student-loan coverage now under Forbes Advisor staff.", "_links": {"latest_article": "https://www.forbes.com/sites/zackfriedman/2022/08/24/biden-cancels-student-loans-and-extends-student-loan-payment-pause/"}}, {"name": "Tariq Habash", "outlet_on_file": "The Century Foundation", "current_outlet_verified": "A New Policy (founder) / Student Borrower Protection Center senior fellow \u2014 no longer at The Century Foundation", "medium": "Print/Digital", "beat": "Education Equity", "email": "", "email_source": "not found", "phone": "", "twitter": "@TariqHabash", "location": "", "latest_article": "Banning Foreign Students Is the Opposite of 'Making America Great'", "article_date": "2025-06-18", "link_type": "article", "articles_tracked_10yr": "73", "last_tracked_date": "2026-02-17", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Op-ed in The Nation. TCF \u2192 SBPC \u2192 ED policy advisor (resigned Jan 2024) \u2192 A New Policy. No public email.", "_links": {"latest_article": "https://www.thenation.com/article/society/banning-foreign-students-is-the-opposite-of-making-america-great/"}}, {"name": "Chris Quintana", "outlet_on_file": "USA Today", "current_outlet_verified": "USA Today (investigative, higher ed)", "medium": "Print/Digital", "beat": "Higher Education", "email": "", "email_source": "not found", "phone": "", "twitter": "@ChrisQuintana", "location": "", "latest_article": "Next generation of nurses at risk with proposed student loan caps", "article_date": "2026-01-16", "link_type": "article", "articles_tracked_10yr": "69", "last_tracked_date": "2026-02-08", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "AOL syndication (USA Today blocks crawlers); has more recent Feb 2026 bylines without verifiable URLs.", "_links": {"latest_article": "https://www.aol.com/articles/next-generation-nurses-risk-proposed-213531292.html"}}, {"name": "Ryan Lane", "outlet_on_file": "NerdWallet", "current_outlet_verified": "NerdWallet (managing editor; moved toward small-business but retains student-loan bylines)", "medium": "Print/Digital", "beat": "Student Loans", "email": "", "email_source": "not found", "phone": "", "twitter": "@aborlane", "location": "", "latest_article": "3 Ways to Lower Your Student Loan Interest Rate", "article_date": "2026-06-18", "link_type": "article", "articles_tracked_10yr": "69", "last_tracked_date": "2026-02-12", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Date = last-updated (evergreen model). No public email.", "_links": {"latest_article": "https://www.nerdwallet.com/student-loans/learn/how-to-lower-student-loan-interest-rate"}}, {"name": "Aarthi Swaminathan", "outlet_on_file": "Yahoo Finance", "current_outlet_verified": "MarketWatch", "medium": "Print/Digital", "beat": "Economy & Policy", "email": "aarthi@marketwatch.com", "email_source": "publicly listed (web-verified)", "phone": "", "twitter": "@aaborwami", "location": "", "latest_article": "10 U.S. metro areas where builders are slashing prices on new homes", "article_date": "2026-07-17", "link_type": "article", "articles_tracked_10yr": "67", "last_tracked_date": "2025-12-22", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Moved from Yahoo Finance to MarketWatch (now housing/real-estate beat). URL is Yahoo syndication of her MarketWatch piece. Email self-published in her X post soliciting sources.", "_links": {"latest_article": "https://finance.yahoo.com/real-estate/articles/10-u-metro-areas-where-001000782.html"}}, {"name": "Adam Minsky", "outlet_on_file": "Forbes", "current_outlet_verified": "Forbes (senior contributor)", "medium": "Print/Digital", "beat": "Student Loan Law", "email": "", "email_source": "not found", "phone": "", "twitter": "@AdamSMinsky", "location": "", "latest_article": "Education Department Must Wipe Out Student Loans For 500,000 Borrowers Under Settlement, Says Court", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "67", "last_tracked_date": "2026-02-27", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Publishes multiple times weekly on student-loan law; no public email found.", "_links": {"latest_article": "https://www.forbes.com/sites/adamminsky/2026/07/21/education-department-must-wipe-out-student-loans-for-500000-borrowers-under-settlement-says-court/"}}, {"name": "Annie Nova", "outlet_on_file": "CNBC", "current_outlet_verified": "CNBC", "medium": "TV", "beat": "Personal Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@annianova", "location": "", "latest_article": "Student loan borrowers on new RAP plan can lose key benefits if they pay even one day late", "article_date": "2026-07-12", "link_type": "article", "articles_tracked_10yr": "67", "last_tracked_date": "2026-02-02", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Active on RAP/SAVE transition coverage. No public email found.", "_links": {"latest_article": "https://www.cnbc.com/2026/07/12/student-loans-rap-late-payment.html"}}, {"name": "Ayelet Sheffey", "outlet_on_file": "Business Insider", "current_outlet_verified": "Business Insider (senior economic policy reporter)", "medium": "Print/Digital", "beat": "Policy & Economy", "email": "", "email_source": "not found", "phone": "", "twitter": "@ayaborffey", "location": "", "latest_article": "Latest Trump administration email pushes SAVE student-loan borrowers to find a new repayment plan \u2014 now", "article_date": "2026-07", "link_type": "article", "articles_tracked_10yr": "65", "last_tracked_date": "2026-02-17", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "AOL syndication of her BI piece (BI blocks crawlers). No public email found.", "_links": {"latest_article": "https://www.aol.com/articles/latest-trump-administration-email-pushes-185705250.html"}}, {"name": "Jessica Dickler", "outlet_on_file": "CNBC", "current_outlet_verified": "CNBC", "medium": "TV", "beat": "Personal Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@jaborickler", "location": "", "latest_article": "BlackRock, Carhartt, Ford and Google launch skilled trades workforce initiative", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "62", "last_tracked_date": "2026-02-22", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "No public email found.", "_links": {"latest_article": "https://www.cnbc.com/2026/07/21/blackrock-carhartt-ford-google-skilled-trades.html"}}, {"name": "Laura Beamer", "outlet_on_file": "Jain Family Institute", "current_outlet_verified": "Jain Family Institute", "medium": "Print/Digital", "beat": "Student Debt Research", "email": "", "email_source": "not found", "phone": "", "twitter": "@laboramer", "location": "", "latest_article": "America's Student Loans Were Never Going to Be Repaid (NYT interactive, with Marshall Steinbaum)", "article_date": "2023-07-13", "link_type": "author_page", "articles_tracked_10yr": "62", "last_tracked_date": "2026-01-10", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Researcher, not reporter \u2014 no 2025-2026 bylines found; latest major byline July 2023.", "_links": {"latest_article": "https://jainfamilyinstitute.org/our-team/laura-beamer/"}}, {"name": "Charlie Eaton", "outlet_on_file": "UC Merced / Academic", "current_outlet_verified": "UC Merced (Assoc. Professor; co-founder HERE Lab)", "medium": "Print/Digital", "beat": "Higher Ed Finance", "email": "", "email_source": "not found", "phone": "", "twitter": "@CharlieDEaton", "location": "", "latest_article": "White Flight in Public Higher Education? Racial Avoidance of Hispanic-Serving Institutions (Sociology of Education)", "article_date": "2025", "link_type": "article", "articles_tracked_10yr": "57", "last_tracked_date": "2025-11-12", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Academic, not journalist \u2014 latest is a 2025 peer-reviewed article. Faculty-page email not verifiable.", "_links": {"latest_article": "https://journals.sagepub.com/doi/10.1177/00380407251352786"}}, {"name": "Adam Echelman", "outlet_on_file": "CalMatters", "current_outlet_verified": "CalMatters (with Open Campus)", "medium": "Print/Digital", "beat": "Higher education; workforce/job training; community colleges; student data privacy", "email": "aechelman@calmatters.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "CA", "latest_article": "A new law holds colleges accountable if their grads don't earn more than $36,000 a year", "article_date": "2026-07-16", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-16", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on CalMatters author page.", "_links": {"latest_article": "https://calmatters.org/education/higher-education/2026/07/college-graduate-earnings-california/"}}, {"name": "Aleksandra Appleton", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Indiana", "medium": "Print/Digital", "beat": "Indiana education policy; K-12 statewide", "email": "aappleton@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "IN", "latest_article": "IPEC chooses charter school policy leader to guide its work", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-02-02", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page.", "_links": {"latest_article": "https://www.chalkbeat.org/indiana/2026/07/22/karega-rausch-named-ipec-executive-director/"}}, {"name": "Carolyn Jones", "outlet_on_file": "CalMatters", "current_outlet_verified": "CalMatters", "medium": "Print/Digital", "beat": "K-12; special education; school funding; inequity/student achievement; policy", "email": "carolyn@calmatters.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "CA", "latest_article": "California reading scores are up, thanks to literacy investments. Can the state do more?", "article_date": "2026-07-20", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-11", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on CalMatters author page.", "_links": {"latest_article": "https://calmatters.org/education/k-12-education/2026/07/literacy-california/"}}, {"name": "Emma Epperly", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News", "medium": "Print/Digital", "beat": "Idaho K-12; North Idaho regional; special education", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "ID", "latest_article": "Idaho's average per-pupil spending edges up", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-24", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "No email displayed on author page.", "_links": {"latest_article": "https://www.idahoednews.org/top-news/average-idaho-per-pupil-spending-edges-up/"}}, {"name": "Hannah Dellinger", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Detroit", "medium": "Print/Digital", "beat": "Detroit schools; absenteeism; district operations", "email": "hdellinger@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "Detroit MI", "latest_article": "A crowded field: 35 are in the race for 5 seats on the Detroit school board", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-02-26", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page.", "_links": {"latest_article": "https://www.chalkbeat.org/detroit/2026/07/22/dpscd-candidates-running-for-the-detroit-school-board-in-nov-3-election/"}}, {"name": "Howard Blume", "outlet_on_file": "Los Angeles Times", "current_outlet_verified": "Los Angeles Times", "medium": "Print/Digital", "beat": "Education (K-12 and district policy)", "email": "howard.blume@latimes.com", "email_source": "publicly listed (web-verified)", "phone": "", "twitter": "", "location": "CA", "latest_article": "Falling enrollment. Budget uncertainty. LAUSD warns of layoffs and cuts", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-24", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "LAT blocks crawlers \u2014 byline page given. Email surfaced in public journalist listings.", "_links": {"latest_article": "https://muckrack.com/howard-blume/articles"}}, {"name": "Jaweed Kaleem", "outlet_on_file": "Los Angeles Times", "current_outlet_verified": "Los Angeles Times", "medium": "Print/Digital", "beat": "University of California; higher education; campus activism; free speech; religion; race; politics", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "CA", "latest_article": "UC asks California Supreme Court to block release of Trump's UCLA settlement offer", "article_date": "2026-07", "link_type": "author_page", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-19", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "LAT blocks crawlers \u2014 byline page given. Covering UC/higher-ed incl. $1.2B DOJ-UCLA settlement fight.", "_links": {"latest_article": "https://muckrack.com/jaweedkaleem/articles"}}, {"name": "Kevin Richert", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News", "medium": "Print/Digital", "beat": "Education politics; education policy; higher education", "email": "krichert@idahoednews.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "ID", "latest_article": "How the feds' healthcare grants could affect Idaho education: Three examples", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-03", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Byline, date, email verified on article page.", "_links": {"latest_article": "https://www.idahoednews.org/top-news/how-the-feds-healthcare-grants-could-affect-idaho-education-three-examples/"}}, {"name": "Melanie Asmar", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Colorado (Bureau Chief)", "medium": "Print/Digital", "beat": "Denver Public Schools; school board governance; K-12 policy", "email": "masmar@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "CO", "latest_article": "Free school supplies, haircuts, vaccines, and more. Check out these 2026 back-to-school events", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-01-21", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page.", "_links": {"latest_article": "https://www.chalkbeat.org/colorado/2026/07/22/free-back-to-school-supplies-backpacks-haircuts-in-denver-metro-area/"}}, {"name": "Mikhail Zinshteyn", "outlet_on_file": "CalMatters", "current_outlet_verified": "CalMatters", "medium": "Print/Digital", "beat": "Higher education; state legislation; financial aid; labor issues; campus housing; college affordability", "email": "mikhail@calmatters.org", "email_source": "SDCC database", "phone": "", "twitter": "mzinshteyn", "location": "CA", "latest_article": "White House admits it used keywords to kill billions worth of California research grants", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-11", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on CalMatters author page.", "_links": {"latest_article": "https://calmatters.org/education/higher-education/2026/07/research-grants-california-2/"}}, {"name": "Ryan Suppe", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News (senior reporter, K-12 policy)", "medium": "Print/Digital", "beat": "Education policy; K-12 focus", "email": "ryan@idahoednews.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "ID", "latest_article": "Former Nampa school board chair resigns", "article_date": "2026-07-21", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-02", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on IEN author page; LinkedIn confirms current role.", "_links": {"latest_article": "https://www.idahoednews.org/news/former-nampa-school-board-chairwoman-resigns/"}}, {"name": "Sean Dolan", "outlet_on_file": "Idaho Education News", "current_outlet_verified": "Idaho Education News", "medium": "Print/Digital", "beat": "Education state policy; local district governance", "email": "sean@idahoednews.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "ID", "latest_article": "Schools face soaring laptop costs as AI data centers squeeze supply", "article_date": "2026-07-20", "link_type": "article", "articles_tracked_10yr": "3", "last_tracked_date": "2026-03-25", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "BoiseDev syndication of his IEN piece. Email on IEN author page.", "_links": {"latest_article": "https://boisedev.com/news/2026/07/20/schools-face-soaring-laptop-costs-as-ai-data-centers-squeeze-supply/"}}, {"name": "Melissa Brown", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Tennessee (Bureau Chief)", "medium": "Print/Digital", "beat": "Tennessee education policy; school choice/vouchers; immigration impacts on schools", "email": "mbrown@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "TN", "latest_article": "Tennessee considers easing teacher licensure testing requirements", "article_date": "2026-07-08", "link_type": "article", "articles_tracked_10yr": "2", "last_tracked_date": "2026-03-10", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Chalkbeat author page. Leading Memphis-Shelby state-takeover coverage.", "_links": {"latest_article": "https://www.chalkbeat.org/tennessee/2026/07/08/will-tennessee-drop-edtpa-requirement-for-teacher-licensure/"}}, {"name": "Reema Amin", "outlet_on_file": "Chalkbeat", "current_outlet_verified": "Chalkbeat Chicago", "medium": "Print/Digital", "beat": "Chicago Public Schools", "email": "ramin@chalkbeat.org", "email_source": "SDCC database", "phone": "", "twitter": "", "location": "Chicago IL", "latest_article": "Chicago Public Schools lays off 162 central and citywide employees", "article_date": "2026-07-13", "link_type": "article", "articles_tracked_10yr": "1", "last_tracked_date": "2026-03-20", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email publicly listed by Chalkbeat.", "_links": {"latest_article": "https://www.chalkbeat.org/chicago/2026/07/13/cps-lays-off-162-central-and-citywide-employees/"}}, {"name": "Jaden Edison", "outlet_on_file": "The Texas Tribune", "current_outlet_verified": "The Texas Tribune", "medium": "Print/Digital", "beat": "Public education", "email": "jaden.edison@texastribune.org", "email_source": "SDCC database", "phone": "512-716-8600", "twitter": "", "location": "TX", "latest_article": "Texas' longstanding 'Robin Hood' school finance system will face a new legal challenge", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Email on Tribune staff page.", "_links": {"latest_article": "https://www.texastribune.org/2026/07/22/west-texas-midland-district-lawsuit-school-finance-robin-hood/"}}, {"name": "jillian", "outlet_on_file": "", "current_outlet_verified": "", "medium": "Print/Digital", "beat": "General", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "", "latest_article": "", "article_date": "", "link_type": "", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "DATA-ENTRY STUB \u2014 no outlet on file", "_links": {}}, {"name": "marka", "outlet_on_file": "", "current_outlet_verified": "", "medium": "Print/Digital", "beat": "General", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "", "latest_article": "", "article_date": "", "link_type": "", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "DATA-ENTRY STUB \u2014 no outlet on file", "_links": {}}, {"name": "news day", "outlet_on_file": "", "current_outlet_verified": "", "medium": "Print/Digital", "beat": "General", "email": "", "email_source": "not found", "phone": "", "twitter": "", "location": "", "latest_article": "", "article_date": "", "link_type": "", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "DATA-ENTRY STUB \u2014 no outlet on file", "_links": {}}, {"name": "Sneha Dey", "outlet_on_file": "The Texas Tribune", "current_outlet_verified": "The Texas Tribune", "medium": "Print/Digital", "beat": "Public education", "email": "sneha.dey@texastribune.org", "email_source": "SDCC database", "phone": "512-716-8600", "twitter": "", "location": "Austin TX", "latest_article": "Higher education agency prepares to cut 3% of spending as ordered by Texas leaders", "article_date": "2026-07-22", "link_type": "article", "articles_tracked_10yr": "0", "last_tracked_date": "", "first_contact": "", "initial_email": "", "last_contact": "", "last_email": "", "contact_source": "no contact on record", "notes": "Beat now education-to-employment pathways, community colleges, higher ed. Email on Tribune author page.", "_links": {"latest_article": "https://www.texastribune.org/2026/07/22/texas-higher-education-spending-cuts/"}}]}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index feea6f3..c225040 100644
--- a/public/index.html
+++ b/public/index.html
@@ -44,6 +44,22 @@ main { flex:1; padding:16px 20px; }
.article a { color:var(--text); font-weight:600; }
.notes { color:var(--muted); font-size:calc(11.5px*var(--scale)); font-style:italic; }
.empty { color:var(--muted); padding:40px; text-align:center; }
+/* Cards ⇄ List view toggle */
+.viewtoggle { display:inline-flex; border:1px solid var(--border); border-radius:8px; overflow:hidden; }
+.viewtoggle button { border:0; border-radius:0; background:var(--el); padding:6px 12px; }
+.viewtoggle button.active { background:var(--pri); color:#08110c; font-weight:600; }
+.viewtoggle button+button { border-left:1px solid var(--border); }
+/* List (table) view */
+.table-wrap { overflow-x:auto; border:1px solid var(--border); border-radius:12px; }
+table.list { border-collapse:collapse; width:100%; font-size:calc(13px*var(--scale)); }
+table.list th, table.list td { text-align:left; padding:calc(7px*var(--scale)) 12px; border-bottom:1px solid var(--border); white-space:nowrap; vertical-align:top; }
+table.list thead th { position:sticky; top:60px; background:var(--el); color:var(--sub); font-size:calc(11.5px*var(--scale)); text-transform:uppercase; letter-spacing:.04em; cursor:pointer; user-select:none; z-index:2; }
+table.list thead th:hover { color:var(--text); }
+table.list thead th.sorted { color:var(--pri); }
+table.list thead th .arr { font-size:10px; margin-left:3px; }
+table.list tbody tr:hover { background:var(--surface); }
+table.list td.notes-cell { white-space:normal; max-width:340px; color:var(--muted); font-style:italic; }
+table.list td.num { text-align:right; font-variant-numeric:tabular-nums; }
@media (max-width:900px){ .layout{flex-direction:column} aside{width:100%;position:static;max-height:none;flex:auto} :root{--cols:1!important} }
</style>
</head>
@@ -70,6 +86,10 @@ main { flex:1; padding:16px 20px; }
</aside>
<main>
<div class="controls">
+ <span class="viewtoggle" id="viewtoggle">
+ <button type="button" data-view="cards">▦ Cards</button>
+ <button type="button" data-view="list">☰ List</button>
+ </span>
<select id="sort">
<option value="contact">Last contact (newest)</option>
<option value="articles">Articles tracked</option>
@@ -80,6 +100,7 @@ main { flex:1; padding:16px 20px; }
<span class="density">Density <input type="range" id="density" min="1" max="5" step="1" value="3"></span>
</div>
<div class="grid" id="grid"></div>
+ <div class="table-wrap" id="tablewrap" style="display:none"></div>
<div class="empty" id="empty" style="display:none">No journalists match.</div>
</main>
</div>
@@ -94,6 +115,25 @@ const FIELDS = [
const LS = (k,v)=> v===undefined ? localStorage.getItem("sdccjv_"+k) : localStorage.setItem("sdccjv_"+k,v);
let DATA = [], META = {};
+// List-view columns — [key, label, type]. Every one is a clickable sort header.
+const COLUMNS = [
+ ["name","Name","text"],
+ ["current_outlet_verified","Outlet","text"],
+ ["medium","Medium","text"],
+ ["beat","Beat","text"],
+ ["email","Email","text"],
+ ["phone","Phone","text"],
+ ["twitter","X","text"],
+ ["location","Location","text"],
+ ["article_date","Latest article","date"],
+ ["articles_tracked_10yr","Tracked","num"],
+ ["date_of_last_contact","Last contact","date"],
+ ["date_of_first_contact","First contact","date"],
+ ["notes","Notes","text"],
+];
+let VIEW = LS("view")||"cards";
+let TSORT = { col: LS("tcol")||"name", dir: +(LS("tdir")||1) };
+
const vis = {};
FIELDS.forEach(([k,,def])=>{ const s=LS("f_"+k); vis[k] = s===null?def:s==="1"; });
@@ -156,16 +196,19 @@ function card(r){
return el(`<div class="card">${parts.join("")}</div>`);
}
-function render(){
+function filteredRows(){
const q=(document.getElementById("q").value||"").toLowerCase();
const fE=document.getElementById("fEmail").checked, fC=document.getElementById("fContact").checked, fA=document.getElementById("fActive").checked;
- let rows = DATA.filter(r=>{
+ return DATA.filter(r=>{
if(q && !JSON.stringify(r).toLowerCase().includes(q)) return false;
if(fE && !r.email) return false;
if(fC && !r.date_of_last_contact) return false;
if(fA && /stub|deprioritiz|not a (journalist|reporter)|inactive/i.test(r.notes||"")) return false;
return true;
});
+}
+
+function renderCards(rows){
const s=document.getElementById("sort").value;
const by=(f,dir=1)=>(a,b)=>((a[f]||"")<(b[f]||"")?-1:1)*dir;
if(s==="name") rows.sort(by("name"));
@@ -175,6 +218,59 @@ function render(){
if(s==="contact") rows.sort((a,b)=>((b.date_of_last_contact||"")+(+b.articles_tracked_10yr||0)).localeCompare((a.date_of_last_contact||"")+(+a.articles_tracked_10yr||0)));
const grid=document.getElementById("grid"); grid.innerHTML="";
rows.forEach(r=>grid.appendChild(card(r)));
+}
+
+// One cell of the list view — key cells become clickable (email, phone, X, article, gmail threads).
+function cellHTML(r,col){
+ const L=r._links||{}, k=col[0], v=r[k];
+ if(k==="email") return v?`<a href="mailto:${esc(v)}" title="${esc(r.email_source||"")}">${esc(v)}</a>`:"";
+ if(k==="phone") return v?`<a href="tel:${esc(v.replace(/[^0-9+]/g,""))}">${esc(v)}</a>`:"";
+ if(k==="twitter"){ const url=linkX(v,r.name,r.current_outlet_verified||r.outlet_on_file);
+ return v?`<a href="${url}" target="_blank" rel="noopener noreferrer">${esc(v)}</a>`:`<a href="${url}" target="_blank" rel="noopener noreferrer" class="kv">find</a>`; }
+ if(k==="article_date"){ const url=L.latest_article||r.latest_article, t=esc(v||(url?"↗":""));
+ return url?`<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${t}</a>`:t; }
+ if(k==="date_of_last_contact") return v?(L.last_email?`<a href="${esc(L.last_email)}" target="_blank" rel="noopener noreferrer">${esc(v)}</a>`:esc(v)):"";
+ if(k==="date_of_first_contact") return v?(L.initial_email?`<a href="${esc(L.initial_email)}" target="_blank" rel="noopener noreferrer">${esc(v)}</a>`:esc(v)):"";
+ return esc(v||"");
+}
+
+function renderTable(rows){
+ const col = COLUMNS.find(c=>c[0]===TSORT.col) || COLUMNS[0];
+ const isEmpty = r => col[2]==="num" ? false : !((r[col[0]]||"").toString().trim());
+ rows.sort((a,b)=>{
+ const ae=isEmpty(a), be=isEmpty(b);
+ if(ae&&!be) return 1; if(be&&!ae) return -1; if(ae&&be) return 0; // blanks always sink
+ let d;
+ if(col[2]==="num") d=(+a[col[0]]||0)-(+b[col[0]]||0);
+ else { const av=(a[col[0]]||"").toString().toLowerCase(), bv=(b[col[0]]||"").toString().toLowerCase(); d=av<bv?-1:av>bv?1:0; }
+ return d*TSORT.dir;
+ });
+ const head = COLUMNS.map(c=>{
+ const active=c[0]===col[0];
+ return `<th data-col="${c[0]}" class="${active?"sorted":""}" title="Sort by ${esc(c[1])}">${esc(c[1])}${active?`<span class="arr">${TSORT.dir>0?"▲":"▼"}</span>`:""}</th>`;
+ }).join("");
+ const body = rows.map(r=>"<tr>"+COLUMNS.map(c=>{
+ const cls=c[0]==="notes"?"notes-cell":(c[2]==="num"?"num":"");
+ const title=c[0]==="notes"?` title="${esc(r.notes||"")}"`:"";
+ return `<td class="${cls}"${title}>${cellHTML(r,c)}</td>`;
+ }).join("")+"</tr>").join("");
+ const wrap=document.getElementById("tablewrap");
+ wrap.innerHTML=`<table class="list"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`;
+ wrap.querySelectorAll("thead th").forEach(h=>h.onclick=()=>{
+ const c=h.getAttribute("data-col");
+ if(TSORT.col===c){ TSORT.dir=-TSORT.dir; } // same col → flip direction
+ else { TSORT.col=c; const t=(COLUMNS.find(x=>x[0]===c)||[])[2]; TSORT.dir=(t==="num"||t==="date")?-1:1; } // num/date default desc
+ LS("tcol",TSORT.col); LS("tdir",TSORT.dir); render();
+ });
+}
+
+function render(){
+ const rows = filteredRows();
+ const list = VIEW==="list";
+ if(list) renderTable(rows); else renderCards(rows);
+ document.getElementById("grid").style.display = list?"none":"grid";
+ document.getElementById("tablewrap").style.display = list?"block":"none";
+ document.getElementById("sort").style.display = list?"none":""; // headers drive sorting in list mode
document.getElementById("empty").style.display = rows.length?"none":"block";
document.getElementById("count").textContent = `${rows.length} of ${DATA.length} journalists`;
}
@@ -209,6 +305,16 @@ document.getElementById("refreshBtn").onclick=async()=>{
try{ await fetch("/api/refresh",{method:"POST"}); await load(); } finally { b.disabled=false; b.textContent="⟳ Refresh from Drive"; }
};
+// view toggle (Cards ⇄ List) — persists in localStorage
+document.querySelectorAll("#viewtoggle button").forEach(b=>{
+ b.classList.toggle("active", b.dataset.view===VIEW);
+ b.onclick=()=>{
+ VIEW=b.dataset.view; LS("view",VIEW);
+ document.querySelectorAll("#viewtoggle button").forEach(x=>x.classList.toggle("active",x.dataset.view===VIEW));
+ render();
+ };
+});
+
// restore persisted controls
const sv=LS("sort"); if(sv) document.getElementById("sort").value=sv;
const dv=LS("density")||3; document.getElementById("density").value=dv;
diff --git a/server.js b/server.js
index dfb53a0..9d880b1 100644
--- a/server.js
+++ b/server.js
@@ -1,20 +1,41 @@
#!/usr/bin/env node
/**
* SDCC Journalists Viewer — live view over the Drive spreadsheet.
- * Basic auth admin/DW2024! (unauth /healthz for the fleet keepalive).
+ * Auth: Google Sign-In (allowlisted domains/emails) with basic-auth fallback.
+ * Unauth /healthz for the fleet keepalive.
* Data source of truth: gdrive:SDCC/sdcc-journalists-media-contacts.xlsx,
* re-pulled by scripts/refresh.py on boot, every 5 min, and via POST /api/refresh.
*/
const http = require('http');
+const https = require('https');
const fs = require('fs');
const path = require('path');
+const crypto = require('crypto');
const { execFile } = require('child_process');
+// .env loader (no deps)
+const envPath = path.join(__dirname, '.env');
+if (fs.existsSync(envPath)) {
+ for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
+ const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
+ if (m && !(m[1] in process.env)) process.env[m[1]] = m[2];
+ }
+}
+
const PORT = parseInt(process.env.PORT || '9878', 10);
const AUTH = 'Basic ' + Buffer.from(process.env.BASIC_AUTH || 'admin:DW2024!').toString('base64');
+const CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
+const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
+const BASE_URL = process.env.BASE_URL || `http://127.0.0.1:${PORT}`;
+const COOKIE_SECRET = process.env.COOKIE_SECRET || 'dev-secret-change-me';
+const ALLOWED_DOMAINS = (process.env.ALLOWED_DOMAINS || 'studentdebtcrisis.org').toLowerCase().split(',').filter(Boolean);
+const ALLOWED_EMAILS = (process.env.ALLOWED_EMAILS || 'steveabramsdesigns@gmail.com,steve@designerwallcoverings.com,browntwn@gmail.com')
+ .toLowerCase().split(',').filter(Boolean);
+
const DATA_JSON = path.join(__dirname, 'data', 'journalists.json');
const REFRESH = path.join(__dirname, 'scripts', 'refresh.py');
+/* ─── Drive refresh loop ─────────────────────────────────────────────── */
let refreshing = false;
function refresh(cb) {
if (refreshing) return cb && cb(new Error('refresh already running'));
@@ -29,16 +50,117 @@ function refresh(cb) {
refresh();
setInterval(() => refresh(), 5 * 60 * 1000);
+/* ─── Session helpers ────────────────────────────────────────────────── */
+function sign(email) {
+ const exp = Date.now() + 30 * 24 * 3600 * 1000; // 30 days
+ const body = Buffer.from(JSON.stringify({ e: email, x: exp })).toString('base64url');
+ const sig = crypto.createHmac('sha256', COOKIE_SECRET).update(body).digest('base64url');
+ return `${body}.${sig}`;
+}
+function verifySession(cookieHeader) {
+ const m = /(?:^|;\s*)sdccjv_sess=([^;]+)/.exec(cookieHeader || '');
+ if (!m) return null;
+ const [body, sig] = m[1].split('.');
+ if (!body || !sig) return null;
+ const want = crypto.createHmac('sha256', COOKIE_SECRET).update(body).digest('base64url');
+ if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want))) return null;
+ try {
+ const { e, x } = JSON.parse(Buffer.from(body, 'base64url').toString());
+ if (Date.now() > x) return null;
+ return e;
+ } catch { return null; }
+}
+function emailAllowed(email) {
+ email = (email || '').toLowerCase();
+ if (ALLOWED_EMAILS.includes(email)) return true;
+ const dom = email.split('@')[1] || '';
+ return ALLOWED_DOMAINS.includes(dom);
+}
+
+/* ─── Google OAuth (authorization-code flow) ─────────────────────────── */
+function postForm(url, form) {
+ return new Promise((resolve, reject) => {
+ const data = new URLSearchParams(form).toString();
+ const u = new URL(url);
+ const req = https.request({ hostname: u.hostname, path: u.pathname, method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(data) } },
+ res => { let b = ''; res.on('data', c => b += c); res.on('end', () => resolve(JSON.parse(b))); });
+ req.on('error', reject); req.write(data); req.end();
+ });
+}
+
+const LOGIN_HTML = (msg) => `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>SDCC Media Contacts — Sign in</title><style>body{margin:0;background:#0C0F1A;color:#f0f0f5;font:15px/1.5 -apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh}
+.box{background:#131829;border:1px solid #2A3048;border-radius:14px;padding:40px;text-align:center;max-width:380px}
+h1{font-size:18px;margin:0 0 6px}p{color:#a1a1b5;font-size:13px}
+a.g{display:inline-flex;align-items:center;gap:10px;background:#fff;color:#1f1f1f;border-radius:8px;padding:10px 22px;font-weight:600;text-decoration:none;margin-top:14px}
+.err{color:#f43f5e;font-size:13px;margin-top:12px}</style></head><body><div class="box">
+<h1>SDCC Media Contacts</h1><p>Sign in with your Gmail / Google Workspace account to use the journalist data.</p>
+<a class="g" href="/auth/google"><svg width="18" height="18" viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.5 0 6.6 1.2 9 3.5l6.7-6.7C35.7 2.5 30.2 0 24 0 14.6 0 6.5 5.4 2.6 13.2l7.8 6.1C12.3 13.2 17.7 9.5 24 9.5z"/><path fill="#4285F4" d="M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.5-4.8 7.2l7.5 5.8c4.4-4.1 7.1-10.1 7.1-17.5z"/><path fill="#FBBC05" d="M10.4 28.7A14.4 14.4 0 0 1 9.5 24c0-1.6.3-3.2.8-4.7l-7.8-6.1A24 24 0 0 0 0 24c0 3.9.9 7.5 2.6 10.8l7.8-6.1z"/><path fill="#34A853" d="M24 48c6.2 0 11.7-2 15.5-5.6l-7.5-5.8c-2.1 1.4-4.8 2.3-8 2.3-6.3 0-11.7-3.7-13.6-9l-7.8 6.1C6.5 42.6 14.6 48 24 48z"/></svg>Sign in with Google</a>
+${msg ? `<div class="err">${msg}</div>` : ''}</div></body></html>`;
+
+/* ─── Server ─────────────────────────────────────────────────────────── */
const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml' };
-const server = http.createServer((req, res) => {
- const url = req.url.split('?')[0];
+const server = http.createServer(async (req, res) => {
+ const u = new URL(req.url, BASE_URL);
+ const url = u.pathname;
if (url === '/healthz') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end('{"ok":true}'); }
- if (req.headers.authorization !== AUTH) {
- res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="SDCC"' });
- return res.end('auth required');
+ if (url === '/auth/google') {
+ if (!CLIENT_ID) { res.writeHead(500); return res.end('Google OAuth not configured'); }
+ const p = new URLSearchParams({
+ client_id: CLIENT_ID, redirect_uri: `${BASE_URL}/auth/google/callback`,
+ response_type: 'code', scope: 'openid email profile', prompt: 'select_account',
+ });
+ res.writeHead(302, { Location: `https://accounts.google.com/o/oauth2/v2/auth?${p}` });
+ return res.end();
+ }
+
+ if (url === '/auth/google/callback') {
+ try {
+ const tok = await postForm('https://oauth2.googleapis.com/token', {
+ code: u.searchParams.get('code'), client_id: CLIENT_ID, client_secret: CLIENT_SECRET,
+ redirect_uri: `${BASE_URL}/auth/google/callback`, grant_type: 'authorization_code',
+ });
+ if (!tok.id_token) throw new Error(tok.error_description || tok.error || 'no id_token');
+ const claims = JSON.parse(Buffer.from(tok.id_token.split('.')[1], 'base64url').toString());
+ if (!claims.email || claims.email_verified === false) throw new Error('email not verified');
+ if (!emailAllowed(claims.email)) {
+ res.writeHead(403, { 'Content-Type': 'text/html' });
+ return res.end(LOGIN_HTML(`${claims.email} isn’t on the SDCC access list. Ask Steve to add you.`));
+ }
+ console.log('[auth] signed in:', claims.email);
+ res.writeHead(302, {
+ 'Set-Cookie': `sdccjv_sess=${sign(claims.email)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${30 * 24 * 3600}${BASE_URL.startsWith('https') ? '; Secure' : ''}`,
+ Location: '/',
+ });
+ return res.end();
+ } catch (e) {
+ console.error('[auth]', e.message);
+ res.writeHead(500, { 'Content-Type': 'text/html' });
+ return res.end(LOGIN_HTML('Sign-in failed: ' + e.message));
+ }
+ }
+
+ if (url === '/logout') {
+ res.writeHead(302, { 'Set-Cookie': 'sdccjv_sess=; Path=/; Max-Age=0', Location: '/login' });
+ return res.end();
+ }
+ if (url === '/login') { res.writeHead(200, { 'Content-Type': 'text/html' }); return res.end(LOGIN_HTML('')); }
+
+ // auth gate: Google session OR basic-auth fallback
+ const sessEmail = verifySession(req.headers.cookie);
+ if (!sessEmail && req.headers.authorization !== AUTH) {
+ if (url.startsWith('/api/')) { res.writeHead(401, { 'Content-Type': 'application/json' }); return res.end('{"error":"sign in"}'); }
+ res.writeHead(302, { Location: '/login' });
+ return res.end();
+ }
+
+ if (url === '/api/me') {
+ res.writeHead(200, { 'Content-Type': 'application/json' });
+ return res.end(JSON.stringify({ email: sessEmail || 'basic-auth' }));
}
if (url === '/api/data') {
@@ -68,4 +190,4 @@ const server = http.createServer((req, res) => {
});
});
-server.listen(PORT, () => console.log(`sdcc-journalists-viewer on http://127.0.0.1:${PORT}`));
+server.listen(PORT, () => console.log(`sdcc-journalists-viewer on ${BASE_URL} (port ${PORT})`));
← 00fd144 SDCC journalists viewer: Drive-synced spreadsheet viewer w/
·
back to Sdcc Journalists Viewer
·
auto-save: 2026-07-23T10:20:16 (1 files) — data/journalists. 31ae406 →