Skip to content

Lobbying Explorer Redesign - #2318

Open
dasanchez wants to merge 42 commits into
codeforboston:mainfrom
hyphacoop:feat/90-lobbying-pages
Open

dasanchez wants to merge 42 commits into
codeforboston:mainfrom
hyphacoop:feat/90-lobbying-pages

Conversation

@dasanchez

@dasanchez dasanchez commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

The six Lobbying Explorer pages have been revised:

  • An overview with key figures and payments by session
  • Bills, Clients and Lobbyists lists with position bars and split lobbyist counts
  • Bill and Client pages with sortable, filterable tables whose rows expand to show each filer's position per report period.

The pages read the aggregates that the scraper precomputes, the "No client" placeholder is listed as such rather than as an organization, and lobbyist totals are exact. To keep page data light, the long lists embed only the current session and load others through cached API routes, and every page regenerates daily. The list pages read their rows from the field the stats run now writes them under to stay within Firestore's index entry limit.

Closes hyphacoop#89
Closes hyphacoop#90

What's in it

  • Share one keyboard-usable sortable header across the lobbying tables
  • Lobbying Explorer redesign: shared frontend groundwork hyphacoop/maple#89 Add the Lobbying Explorer redesign's shared activity helpers and components
  • Lobbying Explorer redesign: pages hyphacoop/maple#90 Rebuild the Lobbying Explorer pages to the redesign
  • Show every report period of the session in expanded activity rows
  • Use the scraper's precomputed aggregates in the Lobbying Explorer pages
  • List "No client" on the Lobbying Explorer pages, not as an organization
  • Label report periods the same way on every Lobbying Explorer page
  • Read the lobbying list rows from their rows field

Deploying

The redesigned pages read data that only exists once prod has the new scraper and a reparsed archive. So prod's data goes first, and this PR merges only after that. Dev already went through steps 1 and 2.

1. Release main to prod (scraper, indexes and rules)

  • Merge the "Deploy to PROD" PR from main.
  • What it ships: the scraper fixes and the rows index exemption in firestore.indexes.json, which deploy-prod.yml deploys. It also ships everything else merged to main since 10/15: conferences, the finance backfill, bill summaries and more.
  • The first scraper deploy to prod: prod doesn't have the "Deploy the lobbying scraper" workflow yet (Lobbying scraper: rebuild the image on merge #2309), so this release is the first time it runs for prod. It uses the prod environment's GCP_SERVICE_ACCOUNT_KEY and only updates the image of an existing maple-lobbying-scraper job.
  • Set up the job's resources: run lobbying-scraper/deploy.sh digital-testimony-prod from the prod checkout. It creates the job if it's missing, sets 2 GiB and 1 vCPU (the stats step runs out of memory at the 512 MiB default, and the workflow doesn't change memory), and sets up the weekly trigger. Then check that the job has ARCHIVE_RAW and no ARCHIVE_DIR (ingestion doc, "Deploying the Cloud Run Container").
  • Check: "Deploy the frontend and backend to the Firebase Production environment" and "Deploy the lobbying scraper" both pass on the prod push.

2. Backfill prod

These are the steps in the ingestion doc's "Backfilling Newly Parsed Fields", run with --project digital-testimony-prod. The rehearsal on dev is in hyphacoop#106 (see the implementation notes).

  1. Export the lobbying collections first (gcloud firestore export --collection-ids=…). The cleanup phase deletes docs, so this export is the only way to undo the backfill.
  2. refetch-errors: dry run, then --execute.
  3. write: expect "superseded" > 0 and superseded_without_current 0.
  4. registrations.
  5. cleanup for filings and for registrants. Read the dry runs before running --execute. Stale filings are expected only for superseded versions; anything else means doc ids changed, so stop.
  6. filing-periods: expect "updated" 0.
  7. Rebuild the stats with gcloud run jobs execute maple-lobbying-scraper --args=--mode,stats --region=us-central1 --project=digital-testimony-prod, once step 1's index deploy has finished.
  8. Spot checks: See the list in Deploy the Lobbying Explorer aggregates to dev and prod hyphacoop/maple#106. Each list doc (clientList_*, clientList_all, firmList_*) should have one rows field. Prod can be a little ahead of dev from newer filings.

The site that's live now should keep working on the backfilled data. The dev site has run main's current Lobbying Explorer on dev's backfilled data since hyphacoop#106, so check https://maple-dev.vercel.app/lobbying before starting.

3. Merge this PR into main

The dev site then shows the redesigned pages on dev's data. Check /lobbying and each list, Bill and Client page there, at phone width too.

4. Release main to prod again

That's the next "Deploy to PROD" PR. The lobbying pages are built at deploy and then regenerate daily, so they show prod's data from the first load. Check https://mapletestimony.org/lobbying and the same pages as in step 3.

Rollback: reverting this PR and releasing restores the old pages, which keep working on the backfilled data (see step 2). Undoing the data changes means importing the export from step 2.

🤖 Generated with Claude Code

dasanchez and others added 20 commits October 22, 2026 16:49
The Bills, Clients and Lobbyists lists and the bill page each had their own
SortTh. They now share components/lobbying/SortTh.tsx, along with the header
style. The header's label is an unstyled button, so it can be reached with Tab
and sorted with Enter or Space, and the sorted column has aria-sort. Every
sortable column shows an arrow (↕ until it is sorted), so headers no longer
change width when the sort changes; only the bill page did that before.

Verified: npx jest components/lobbying (SortTh.test.tsx: keyboard sort,
aria-sort, ↕ on unsorted columns); tsc --noEmit shows no errors in the files
changed; prettier --check; next lint adds no warnings.
Not verified: sorting on the four pages in a browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onents

The redesigned Bill and Client pages group filings by organization or by bill,
per half-year reporting period, and show a position bar and an expandable
"who lobbied" table. This adds those pieces, tested, for the page rebuild in
#90; no page uses them yet.

- activity.ts: normalizePosition, now the only one, follows the scraper's rule,
  so the precomputed counts and the pages agree. Existing pages now show
  "Monitor" filings as Neutral. periodOf takes the half-year from the filing,
  then its registrant, then falls back to the year. groupActivity builds the
  rows, with in-house status per client and a firm's agents on that client;
  orgPositionBreakdown counts each organization once, by its latest period.
- PositionBar: support/oppose/neutral in the MAPLE position colors, plus a new
  purple for "multiple positions", with counts on hover, in its label and in
  an optional legend.
- ActivityTable: rows open with a button to each filer's position per period
  and SoS filing links; it scrolls inside its box on narrow screens.
- fetchRegistrantsByIds fetches registrants in batches of 30. The
  lobbyingClients hooks, which read a collection nothing writes, are removed.

Closes #89

Verified: npx jest components/lobbying (37 tests, incl. mixed positions
across periods, a filer with no period data, an org in-house for one client
and a firm for another, opening a row by keyboard); tsc --noEmit shows no
errors in the files changed; prettier --check; next lint adds no warnings.
Not verified: PositionBar and ActivityTable in a browser or at phone width
(no page uses them until #90); fetchRegistrantsByIds against Firestore;
Monitor filings showing as Neutral on a live page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The six Lobbying Explorer pages now follow the redesign canvas's layout,
labels and interactions in MAPLE's colors.

- Overview: key figures per session, a payments-by-session chart whose bars
  pick the session, and the most-lobbied bills (overall for all sessions).
- Bills and Clients lists: the redesigned columns (organizations with
  position bars; bills, lobbyists split into in-house and firms with their
  agents, and reported paid).
- Bill and Client pages: a summary and an expandable organization or bill
  table, sortable and filtered by position.
- Lobbyists list: the shared table style and a count.

Every session select opens on, and marks, the session in progress.

To keep pages light, the Overview and the Client page are prerendered with
their data. The Clients list embeds only the latest session and loads others
through a CDN-cached API route; all regenerate daily. A Client page reads
only the selected session's filings. Firestore's "client is offline" error
reads as the server being unavailable.

Closes #90

Verified: npx jest components/lobbying (59 passed), tsc --noEmit (only the
4 typesense errors from local node_modules), next lint, prettier --check.
In the browser, against a full local scrape in the emulator: H4746's 105
organizations and 250 filings, the Greater Boston Chamber of Commerce's
194th summary, the 194th's 5,650 bills, sorting both ways, session and
position filters, picking a session from the chart, keyboard expand and
row clicks, fixed column widths through re-sorts, and phone width. That
also covers the checks 5e14038, #89 and 06b1902 left open
(the sortable headers, PositionBar and ActivityTable in a browser and at
phone width, and the Clients page showing inHouseLobbyists and hiredFirms).

Not verified: next build and static regeneration (the local build stops on
the typesense type error); the pages on dev or prod, which need the #106
deploy; Firefox's session select width (checked in Chromium only); All
sessions for the largest clients' pages (the emulator rejects the ~25 MB
response).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Positions are now blue for support, orange for oppose and grey for neutral,
as on the redesign canvas, in place of MAPLE's green, orange and navy. One
table in chartTheme holds each position's bar, text, chip and square
colors. Position bars, legends, the Bill page's counts, the chips in the
tables and the lobbying card on MAPLE's bill pages all read from it. Text
colors are all 4.5:1 or better on their backgrounds. The canvas's neutral
grey is 2.1:1 as a bar segment; each segment's count is also in its hover
text and the bar's label.

Verified: npx jest components/lobbying, tsc --noEmit (only the 4 local
typesense errors), next lint, prettier --check. In the browser on the full
local scrape: the Overview's top bills, H4746's summary bar and counts,
and the chips in its organization table.

Not verified: the lobbying card on MAPLE's bill pages in a browser (the
local bill pages 404 without bill data).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An expanded organization (Bill page) or bill (Client page) now lists every
report period of its session that has ended, newest first, for each filer.
Each period is a lettered square in the position's color (S, O, N), or an
empty dashed square when the filer didn't file that period, as on the
redesign canvas. The periods used to be only those the row's own filers
filed in. A new period's lane stays empty until its reports come in.
sessionPeriods and sessionPeriodsWith in activity.ts build the list, and
each period carries a short label for the captions.

Verified: npx jest components/lobbying (new tests for the period list and
the empty squares), tsc --noEmit (only the 4 local typesense errors), next
lint, prettier --check. In the browser on the full local scrape: H4746's
ACLU and Abbott rows show Jan–Jun 26, Jul–Dec 25 and Jan–Jun 25 with
filled and empty squares.

Not verified: a Client page's All sessions view, where each bill row
shows its own session's periods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ganizations only

- Banner: the Lobbying Explorer's beta banner is one centered sentence with
  the email inline, as on the canvas. The shared Banner is a flex row,
  which broke it into three lines; the lobbying banner now uses Bootstrap's
  theme classes on a paragraph, and the other banners are unchanged.
- Bill page title: it stays the page's h1 at h3 size, since a bill's title
  can run long. The Client page's name keeps the full h1 size.
- Bills list: only the Organizations column sorts.

Verified: tsc --noEmit (only the 4 local typesense errors), next lint,
prettier --check. In the browser on the full local scrape: the banner on
one line at 1280px and wrapping at 390px; H4001's title at 30px (24px on a
phone); the Bills list sorting by organizations both ways with a plain
Bill header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pages now read what compute_stats precomputes on fix/88 instead of
working it out in the browser:

- The Bills list and the Overview's most-lobbied bills draw each bar from
  every organization counted once (orgLatest*), with a "multiple" segment,
  so they match the Bill page.
- The Bill and Client pages leave out the "No client" placeholder and H0/S0
  rows, by the scraper's own rules.
- Lobbyist totals from the 189th session on are exact: individuals plus
  hired firms that named no agents.
- The Client page takes its in-house filers from its summary.
- The Clients list reads one precomputed doc per session.
- The Lobbyists list now loads per session like the other lists. It
  embeds only the current session and loads others through a new cached
  /api/lobbying/firms/[session] route, so its page data drops from
  2.29 MB to 514 kB.

Along the way, every lobbying data hook reports a result only for its
current arguments, the three list API routes share one handler, and bill
rows carry only the fields the lists show.

Verified: npx jest components/lobbying (65 passed), tsc --noEmit (only the
4 local typesense errors), next lint, prettier --check. In the browser on
the full local scrape rebuilt with fix/88: H4746 8/23/67/7 and H4001
100/5/266/13 on the Bill page, Bills list and Overview alike, with no "No
client" rows; H1 first in the 194th; Abbott Laboratories "1 in-house · 1
firm (3 agents)"; the Greater Boston Chamber of Commerce's 39 bills, 6
in-house lobbyists and its own SoS page; Lobbyists 2,164 (194th), 2,009
(191st) and 8,146 (all); session switches show loading, then the new
rows. That covers the check 369d878 left open (the #90 pages reading the
new fields).

Not verified: next build and static regeneration (the local build stops
on the typesense type error); the CDN caching of the new clients and
firms routes; the pages on dev or prod, which need the #106 stats run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scraper lists the portal's "No client" again, so readers can see
lobbyists who reported no client, but still leaves it out of client and
organization counts. The pages now do the same:

- The Bill page keeps its "No client" row, tagged "Not an organization",
  and leaves it out of the organization count and the position bar, so
  they still match the stored counts. Its filings and filers still count.
- The Clients list sorts it in with the rest, tagged, with only its
  people count under Lobbyists, and leaves it out of "N clients".
- Its own Client page says it isn't an organization in place of the
  registration line and SoS link, and drops the in-house clause.
- Filers of "No client" show no in-house or firm label, and the
  Lobbyist page tags it in a lobbyist's client list.

The legacy total-salary row is still dropped everywhere.

Verified: npx jest components/lobbying (66 passed), tsc --noEmit (only
the 4 local typesense errors), next lint, prettier --check. In the
browser on the local scrape rebuilt with d5d8fab: H4576 (194th) shows 1
organization and an oppose-only bar, plus the "No client" row (Gregory P
Vasil, Jackie McCarthy); H4746 still 8/23/67/7 and H1 413 organizations;
the 194th Clients list still 1,810 clients, with "No client" at 48 bills,
23 lobbyists and $81,725; its Client page and Jackie McCarthy's page as
described.

Not verified: the pages on dev or prod, which need the #106 stats run to
write the placeholder flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in the redesign, an expanded row's filer box now sits under the Most
recent position, Who lobbied and Filings columns, not across the whole
row. Each filer's period squares line up with the position chips, and
its name, kind and filing links with the Who lobbied text. The position
column widens from 180 to 300 px, the redesign's minimum, so a session's
three period squares fit on one line.

Verified: in the browser on the local scrape, on H4746 (194th) and
Abbott Laboratories' Client page: squares at 453 px under chips at
452 px, filer names at 752 px under the Who lobbied text at 752 px;
prettier --check.

Not verified: phone width, where the table now scrolls from 840 px
instead of 720 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in fix/88's merge of upstream main: filings record their
reporting period (codeforboston#2308), the scraper deploy workflow (codeforboston#2309) and the
slimmer beta banner (codeforboston#2304).

The redesigned Bill and Client pages stay as they are: upstream's period
sort changed the old filings tables they replaced. The Lobbyist page
takes upstream's period sort, and its filings table labels rows by
period. The Lobbying Explorer uses upstream's slim beta banner in place
of the redesign's.

Verified: tsc --noEmit (only the 4 local typesense errors), jest
components/lobbying (70 passed), next lint, prettier --check. In the
browser on the local scrape: the slim banner; Jackie McCarthy's
Lobbyist page labeling rows "Jan–Jun 2026"; the H4746 Bill page loading
its organizations; no page errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Period labels came from three places: the filings tables' formatter
("Jan–Jun 2025"), the activity rows' own month strings, and the Lobbyist
page's date ranges ("1/1/2026 – 6/30/2026"), so that page labeled the
same period two ways. All of them now use the filings tables' formatter,
which gains a short-year form ("Jan–Jun 26") for the period squares.

Verified: npx jest components/lobbying (71 passed), tsc --noEmit (only
the 4 local typesense errors), next lint. In the browser on the local
scrape: Jackie McCarthy's Lobbyist page reads "Jan–Jun 2026: $9,542" and
the like with no date ranges left; H4746's period squares read
"JAN–JUN 26" with "Jul–Dec 2025: Support" on hover; no page errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in fix/88's code review fixes: the stats size check before
writing, the settled-year rule for the weekly summary cache, and
amendment chains in the migration. No frontend changes.

Verified: the scraper suite (194 passed), tsc --noEmit (only the 4 local
typesense errors), jest components/lobbying (71 passed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Firestore indexes every field by default and allows 40,000 index entries
per document, a limit the emulator doesn't enforce. The per-session
Lobbyists and Clients list docs held one indexed field per row, so the
dev stats run failed writing firmList_192, and firmList_193 and
clientList_all would have failed after it.

The list docs now hold their rows under one "rows" map, which
firestore.indexes.json exempts from indexing; nothing queries these
docs, each is read whole by id. The stats run's pre-write check now
estimates each document's index entries next to its size, stops before
writing anything if one passes the limit, and logs each doc's count.
The filing counts and inHouseFilers docs, which the current pages read,
keep their shape: the largest is about 10,600 entries.

Readers of these docs must read rows from "rows"; only the unmerged
redesigned list pages do.

Verified: pytest in lobbying-scraper (200 passed), including the
estimate, the stop before writing, and a check that the exempt field is
exempt in firestore.indexes.json. On a copy of the local emulator data,
the estimate puts firmList_192 (42,979), firmList_193 (40,266) and
clientList_all (55,722) over the limit and every clientList_{gc} under,
matching what dev wrote and refused. A full stats rebuild against that
copy wrote every list doc under "rows" with no errors.

Not verified: the stats run on dev once the exemption is deployed; that
Firestore applies the exemption to the rows' subfields as documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stats step ran out of memory on dev at Cloud Run's 512 MiB default.
deploy.sh now creates and updates the job with 2 GiB and 1 vCPU, about
three times the measured peak, so a recreated job or the prod job don't
fall back to the default.

Verified: a full local stats rebuild (592,149 filings) peaked at 688 MiB
resident memory.

Not verified: deploy.sh against a project; the job's peak memory on
Cloud Run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stats run now writes the Lobbyists and Clients list docs as
{rows: {normalized name: row}}, so their rows stay out of Firestore's
index entry limit. fetchFirmList and fetchClientList read the rows from
there; every list page and list API route goes through them.

Verified: against the local emulator rebuilt with the merged scraper,
the firms and clients API routes return 2,164 (194), 2,241 (192), 1,811
(194) and 5,572 (all) rows, matching the stats log, and the Lobbyists
and Clients pages render with their current session's rows.

Not verified: the pages in a browser or at phone width; against dev,
which has the flat list docs until its stats run succeeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
maple-dev Ready Ready Preview Nov 2, 2026 7:44pm UTC

Request Review

dasanchez and others added 12 commits October 30, 2026 09:19
The 2014-2018 layout (also used by recent individual disclosures that report
no activity-level compensation) rebuilds each client's amount from its
"Total amount paid by client" panel and dropped any client at $0. A
lobbyist's $0 client then vanished from "Clients represented". The legacy
2009-2013 path had the same filter.

A client with a recorded $0 now keeps its compensation entry. Fixture totals
are unchanged; the 2011 and 2016 fixtures gain their $0 clients (3 and 1).

Verified: scraper tests pass; the new test fails without the fix. Parsing
Adam Romanow's live Jul-Dec 2025 disclosure took the Hybrid path and dropped
Massachusetts Brewers Guild before the change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The overview's per-session client count came from filings, so a client named
on a disclosure's compensation but with no activity rows was in the Clients
list and not in the count (1,759 against 1,810 on dev for the 194th). Both now
come from the client summaries, less the "No client" placeholder.

Verified: scraper tests pass; the new test fails without the change. After a
rebuild from the local archive into a fresh emulator, every session from 184
to 194 has stats clients equal to its list rows minus the placeholder, and
clientList_all matches totalClients.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An Executive activity has no bill number; the portal's identifier cell names
the agency (for example "Office of the Governor"), but the filing doc dropped
it, leaving the row with only a title. Filing docs now carry `agency` for
Executive items and null for bills. Doc ids are unchanged, so existing docs
are overwritten in place by a rewrite from the archive.

Closes #121

Verified: scraper tests pass; the new test fails without the change. In the
local archive rebuild, Adam Romanow's 5 Executive filings carry agency and no
bill row does. His 2025 rows read "Office of the Governor"; his 2024 rows read
"Other", the portal's own text.

Not verified: dev. Its docs stay as they were until the migration's write
phase and a stats run are done there.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lobbyist's activity table showed "—" under Bills for items with no bill,
such as Executive items like a chapter of the Acts, even when a position was
reported on them. The cell now names the item, "Executive" (with the agency
when the portal names one, e.g. "Executive · Office of the Governor") or
"Other", with its title beneath. The portal's literal "Other" agency is not
shown. On a lobbyist's page these rows now sort after the year's bills
instead of first.

Verified: yarn test:website; in yarn dev:local against an emulator rebuilt
from the archive, Adam Romanow's page at 1280 and 375 px shows his 2025 rows
as "Executive · Office of the Governor" with the title, his 2024 rows as
"Executive" with the title, each after the year's bills.

Not verified: on dev, where filings carry no agency until the migration's
write phase runs; rows show "Executive" and the title until then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each row counted as a "filing" is one activity on a disclosure (a bill or
an Executive item with a position), so a lobbyist with 5 disclosures showed
"87 filings". Counts, column headings and empty-table text now say
"activities", the portal's own name for these rows; "filing" and
"disclosure" keep meaning a whole report. Only the wording changes: keys and
code identifiers stay. Drops billCard.filingCount_*, which nothing used.

Verified: yarn test:website; in yarn dev:local, Adam Romanow's page reads
"87 activities", the Lobbyists list heads its column "Activities", and a
bill page reads "…in 867 activities by 299 lobbyists and firms."

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A note under the list says what "—" and "0" mean in Clients represented and
Activities, and a "—" cell carries a short tooltip.

Verified: in yarn dev:local at 1280 px the note shows under the list.

Not verified: the note's meaning in a session view. That view still shows
"—" for a lobbyist who filed empty disclosures that session (e.g. Brian
Delburn in the 194th), where the note says "0"; a per-session flag from the
scraper's stats fixes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session's Lobbyists list showed "—" for anyone with no clients and no
activity that session, including a lobbyist who filed disclosures of their
own that reported nothing. The list can't then tell "registered, but an
entity files for them" from "filed, but reported no clients". Each row of a
session's list now carries filedInSession: whether the lobbyist or entity
filed a disclosure of its own that session.

Verified: scraper tests pass; the new test fails without the change. After
a stats rerun on the emulator rebuilt from the archive, Brian Delburn's 194th
row has filedInSession true with 0 clients and 0 activities; rows without it
drop from 921 to 486 in the 194th (642 to 117 in the 184th), and no row has
clients or activities without it.

Not verified: dev, until its stats run after deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session's Lobbyists list showed "—" for anyone with no clients and no
activities that session, including a lobbyist who filed disclosures of
their own reporting nothing, which the note under the list says shows as
0. The list now reads the stats' filedInSession flag: filed that session,
the counts show (0 when empty); registered only, "—". Rows written before
the flag fall back to the old rule. In All sessions, one that filed but
never reported an activity shows 0 activities rather than "—". The note
now says "in the sessions shown".

Closes #122

Verified: yarn test:website; in yarn dev:local against the emulator with
the new stats, the 194th view shows Brian Delburn as 0 / 0 (was — / —) at
1280 and 375 px, and a registered-only lobbyist (Alexander Furey) keeps —.

Not verified: dev, which keeps the old rule until its stats run writes
filedInSession; the All sessions view in a browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A client page's "reported paid" covers the session, but its SoS
registration link opens one year's registration page, so 2U Inc. showed
$21,250 for the 194th (2025 and 2026) and a link to a page listing $10,000
(2026 only). The link now reads "2026 SoS registration", from the client's
last registered year; without one it keeps the plain label.

Verified: yarn test:website; in the emulator data the linked page's year
equals the last registered year for all 5,762 clients with a link; in
yarn dev:local, 2U's page reads "2026 SoS registration" at 1280 and 375 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Mephistic Mephistic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM - I just want to keep an eye on our Firestore usage metrics as we promote this more

I'll hold off merging this until Monday morning - then we can co-ordinate rolling this out to PROD

The Overview's Key figures heading read "Key figures · 194th General
Court (2025–26)". The session stays in the heading and its years move to
a second, lighter line in full ("2025–2026"). For All sessions the line
gives the span of sessions with data ("2005–2026").

Verified: screenshots of the panel at 1280 and 375 px for the 194th, and
at 1280 px for All sessions, against yarn dev; yarn test:website passes;
check-types shows only the 4 known errors; no new lint warnings.
Not verified: other sessions in the select, beyond the shared helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Overview's payments chart named each bar by its session ("194th").
It now gives the session's years ("2025–26"); the tooltip keeps the full
"194th General Court (2025–26)". At phone width the chart labels every
third bar, which is fine as it is.

Verified: screenshots of the chart at 1280 and 375 px against yarn dev;
check-types shows only the 4 known errors; prettier passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each segment of the position bar carried its count in a title
attribute, which the browser shows only after about a second. The
segments now use react-bootstrap's tooltip, which shows at once (its
default delay is 0), above the segment. Screen readers still read every
count from the bar's label. This covers the bar on the Overview's top
bills, the Bills list and the bill page; the bill card on MAPLE's bill
pages draws its own bar and keeps its title.

Verified: yarn test:website passes; check-types shows only the 4 known
errors; prettier passes.
Not verified: the tooltip in a browser, on any page; tapping a segment
on a phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The years under the Key figures heading ("2025–2026") were lighter and
gray. They now take the heading's weight, spacing and color, and keep
only their own line.

Verified: screenshot of the panel at 1280 px against yarn dev;
check-types shows only the 4 known errors; prettier passes. In the same
session, hovering a position bar segment on the Overview showed its
tooltip ("Support: 133 organizations") in about 70 ms, which 5112f0a
lists as not verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Bills list's panel said "select a column heading to sort", but only
the Organizations column sorts. The panel now gives just the bill count.
The Clients list keeps the hint, so its string stays.

Verified: on yarn dev, the Bills panel heading reads "5,660 bills with
reported lobbying" with no hint; check-types shows only the 4 known
errors; prettier passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The boxed bar holding the session select and the search box on the
Bills, Clients and Lobbyists pages was as wide as its filters, so it
ended short of the list's panel. A boxed bar now spans the list and its
search box takes the extra room (wrapping to its own line on phones).
The Overview's bar, with only the session select, keeps fitting its
filters (boxed="fit").

Verified: on yarn dev, the bar's and the list panel's edges match on all
three pages at 1280 and 375 px (82-1198 and 12-363), and the Overview's
bar still fits its select; check-types shows only the 4 known errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lobbyist's page kept the older look: a compact Bootstrap table on the
gray background whose Period column was cut off, and a right column of
plain lists, 21c's clients alone running past 100 lines.

The Lobbied bills table now sits in a panel with the activity count and
its pagination, in the Lobbyists list's table style (a style of this
page's own; the bill card's shared table is unchanged). It scrolls
inside its panel on phones. Each right-column group is its own panel.
Clients collapse to their name and total, expanding to what each paid
by period; Registrations and Disclosures show the latest three, then
Show all / Show fewer.

NonBillActivity moves to its own file, and the shared filings table
drops the bill and amount columns its one caller, the bill card, never
showed. The ▸/▾ button style is shared with the activity tables.

Verified: on yarn dev, 21c at 1280 and 375 px (table fits its panel at
1280 and scrolls at 375; a client expands to its periods and collapses;
Registrations shows 3, all 12, then 3); Aaron Agulnek's panels; Aaron
Michael Vega's files-through-employer note inside the Bills panel;
yarn test:website passes; check-types shows only the 4 known errors.
Not verified: the bill card on MAPLE's bill pages in a browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – maple-dev — 8bc66a56 Deployed Nov 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lobbying Explorer redesign: pages Lobbying Explorer redesign: shared frontend groundwork

2 participants