Repository navigation
Conversation
Keep Share your input beside the conference title, greyed, once the reader has filed
* Show the reader's own input first in Public Input The reader's filing appeared twice: in a Your Input section above Public Input and again in the feed. The section goes. The feed now takes the reader's entry out of the list and puts it first, outside the filters and the five-per-page count, so no filter or page hides it. It is marked with a soft brand background and a "You" chip. Posting still jumps to the card, which now carries the #your-input anchor. The highlight is built from existing tokens as a placeholder; the client's designer has yet to see it. Closes hyphacoop#125 Verified: jest components/conference passes, including new PublicInput cases for the reader's card (first, once, on page 3, under an excluding filter, alone). On the local emulator, posting landed on the card in Public Input, with one copy, first on page 2 and no Your Input section; a 375px-wide frame had no horizontal overflow. ESLint is clean on the changed source files. Not verified: yarn check-types, which fails repo-wide here on styled-components and JSX types, lines this change did not touch included. The highlight against the client's designer. A filter that excludes the reader, in the browser. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Tidy Public Input after the reader's card moved in The scroll-margin calc was written out twice in PublicInput, once for the section and once for the reader's card, so the two could drift and park a jump at different offsets. They now share one fragment. Two comments in the tabs code still described the removed Your Input section. Verified: jest on PublicInput and ConferenceTabs passes; Prettier is clean. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
… ones (#2345) * Take the Sources list out of "What needs to be resolved?" The section no longer ends in a numbered Sources list, and the points no longer carry superscript marks referring to it. This matches the client's mockup, which parks its Sources. The page stops loading sources: the view types and the loader drop them, along with the two locale strings. Each point's authored source stays in the YAML and on the conference document, so authors keep recording where a point came from. Closes hyphacoop#124 Verified: eslint and prettier are clean on the touched files; the conference jest suites pass, including a test that no Sources label or superscript renders. Not verified: the section laid beside the pinned mockup at desktop and phone widths. yarn check-types fails across the repo in files this change does not touch, so it did not confirm the change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Show the conference gist as a short paragraph The gist is now a few sentences rather than one line, so it sits at the answer cards' text size with a readable line length, and the feedback invitation moves to its own fainter line beneath it. The gist comment and the authoring doc say "short paragraph", and that a source is optional. Verified: eslint on the component, prettier, conference jest suites. Not verified: the page rendered, at desktop or phone width. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Write the partner's open and settled questions into nine committees Each committee's gist, differences and similarities now come from the media partner's 11/4 questions draft, replacing the lorem ipsum on Phone-Free Schools and the empty lists elsewhere. Sources, notes and section citations for settled points are left out. Chambers whose bill doesn't touch a point are left out so the page shows "Not in the bill". Two settled rows that were not matches are dropped. The other seven committees are untouched. Verified: all sixteen files pass the Conference runtype with no unknown fields; prettier; row counts match the draft. Not verified: the pages rendered; the draft is marked for editorial review and flags readings a lawyer should check, such as the nuclear description; not synced to any database. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Follow the conference report's history when guessing status A conference report is a bill of its own, and the legislature records the enactment and the governor's signature on that bill's history, not on the pair it reconciles. findConferences read only the pair's history, so every reported conference stopped at reportFiled even once it became law. It now reads the report bill's history too, drafts the report as reportBillId, and prints the chapter of the acts it became. Verified: against malegislature.gov for the five reported conferences: each history's "Reported by H5xxx" names its report bill, and each report bill's history ends in "Signed by the Governor, Chapter N of the Acts of 2026". Not verified: the script was not run end to end against dev. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Mark conferences we don't cover, and the report that resolved enacted ones We're trialling the conference pages with some committees. A conference's YAML can now say `covered: false`: it stays listed and linked, its name is muted with a "Not covered" label in the rail, picker, explorer table and phone list, and its page opens with a notice that we're trialling with a limited set and this isn't one of them. It takes no input: the shared takesTestimony gate now requires coverage, so the page, the testimony page's actions and publishTestimony all refuse it. The landing opens on the first covered conference. Mass Ready and BRIGHT, the borrowing bills, are marked not covered. A conference's YAML can also name its report bill (`reportBillId`); its page then says "Resolved by H.5350", linked to that bill. The five reported conferences are enacted, so their status moves from reportFiled to enacted, each with its report bill. The sync refuses a report bill on an appointed conference or one that is a bill of the pair, and warns when the report bill hasn't been fetched. Public Input and its tab now show only on covered conferences that take input, or that got some before closing, read from the conference's testimony count so a closed conference without input never shows it. Both fields default (covered true, no report), so YAML without them syncs unchanged; website readers don't validate documents, so a document synced before these fields reads as covered. Closes hyphacoop#123 Verified: type-check; components/conference jest (62), test:website (159) and functions tests (145) pass; the conference sync integration suite (13) and the uncovered-conference refusal in testimony.test.ts pass on throwaway emulators; all 16 YAMLs validate against the runtype; on a seeded local stack every conference page and H5350 return 200 with the expected covered, reportBillId and testimony count props, and the landing opens on a covered conference. Not verified: the greyed rows, notices and hidden Public Input laid beside the pinned mockup at desktop and phone widths; five cosign tally tests in testimony.test.ts fail locally when the whole file runs, as they do on the base commit without this change, and pass alone and in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Seed report bills and the sample conference's counts locally seedLocal fetched each conference's two bills but not its report bill, so a "Resolved by" link led to a missing bill page on a seeded emulator. It now fetches the report bill too. It also writes its sample testimony straight to Firestore, skipping publishTestimony, which keeps a conference's testimony counts, so the sample conference showed no input in its count. It now sets the counts with the same updateTestimonyCounts the publish path uses. Verified: on the local stack the seed fetched all five report bills (the sync then warned about none); a hand-written version of the count update set the sample conference's count to 7; the final form type-checks. Not verified: the final updateTestimonyCounts form was not re-run against the emulator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…2319) * Say why a cosign was refused, and refuse a legislator's testimony The cosign panel tells a reader that the input they are cosigning has been removed, or that the conference has stopped taking input. Both refusals are failed-precondition, and a gone parent shares not-found with a gone draft, so the error code cannot tell them apart. fail() now takes details, and publish sends { refusal: "cosignParentGone" | "conferenceClosed" } with those two refusals. The rules for what can be cosigned move to a module with no dependencies, so the website offers Cosign by the rule that publishing keeps: an individual's or an organization's testimony, never a legislator's, a cosign, or one's own. A legislator's testimony was cosignable before. The draft's cosign shape lives there too, so the website's draft type can share it. Verified: yarn test:integration-ci on the six suites CI runs (97 pass, including the new legislator refusal and the gone-parent refusal's details); yarn --cwd functions jest src (138); functions tsc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop a draft or publication deleted elsewhere from the edit hook The testimony edit hook ignored a watched draft or publication that stopped existing, so a page kept what another tab, or now a cosign's cascade, had deleted: the conference panel reopened as an edit of a gone publication, asking for an edit reason, and saves failed against the deleted draft. A snapshot that the server, not the local cache, reports gone now goes through the hook's own delete and discard. Verified: on the local emulators, deleting the signed-in user's cosign and draft behind the open conference page, then filing plain input from Share without an edit reason, which posted. Not verified: the bill and ballot question pages, which share the hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cosign from a Public Input card, to the client's mockup A reader can cosign an individual's or an organization's input on a conference from its card, or from the card opened whole. The panel fills the window with the input being signed beside it: the reader says they have read it, then adds an optional note or says they are adding none, and lands in Your Input, which names what they cosigned. Editing a posted cosign changes its note only, with a reason. A signed-out reader goes to sign in; Cosign is offered only while the reader has no input of their own here, posted or drafted; and the two refusals the server words are shown in the panel. The share-with-the-committee choice in the mockup is left for its own issue, since nothing reads it yet. The letter sits in the well our bill text uses, lighter than the mockup's grey. Both forms share one draft hook held by the page, and its autosave, so switching between them never files two drafts, and a quick post no longer saves twice. Below lg the panel is now as wide as the window, where a narrow phone used to cut off the plain form's edge. Closes hyphacoop#97 Verified: on the local emulators (seedLocal), beside the pinned mockup at about 1280px and 390px: the card's Cosign, both steps including the unticked Continue and the empty-note ask, filing with and without a note (the parent's tally went from 3 to 4), the landing, editing a cosign, deleting the parent and closing the conference mid-cosign, a signed-out Cosign opening sign-in, and plain Share, Review and Post unchanged; npx jest components/conference (new tests pass; load.test.ts fails as it does on main); tsc, prettier and lint. Not verified: the edit-cosign view, which the mockup does not draw, by the designer; widths other than about 1280px and 390px; the dev site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Seed cosigns on the local sample conference The local seed adds four cosigns of its sample testimony, with and without notes, from cosigners with districts, so the cosign panel and Your Input can be laid beside the mockup with a parent that already has a count. The functions emulator's trigger keeps each parent's tally, as it would on dev. Every seeded testimony now carries firstPublishedAt, as publishing sets it. Verified: yarn firebase-admin run-script seedLocal --env local on fresh emulators, after which the Massachusetts Medical Society sample carried cosignCount 3 from 2 Senate districts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Size a Public Input card's portrait, marks and chip down on phones The client's mockup brings the card's furniture down with its type: the portrait to 34px under 767px and 26px under 450px, the account marks to 14px and 13px, and the position chip to 10px with less padding under 450px. Ours kept the desktop sizes at every width. The sizing sits on the card's own portrait rather than the shared disc, so the cosign panel's portraits keep theirs. Verified: computed sizes in a 390px frame beside the mockup at k-g-k/maple-conference@19bd101: portrait 26px with 10px letters, marks 13px, chip 10px with 6px padding; desktop unchanged at 40px. Not verified: the 767px tier, between phone and desktop widths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Group cosigns under what they cosign in Public Input A cosigned card now carries the mockup's band ("N constituents have cosigned this", "including you" with a way to your own input, and the Cosign button) and its floor (the count, the Senate and House districts cosigners come from, and the last-cosigned date). Its cosigns are listed under it, a tier quieter than the card, newest first, three shown and the rest behind "Show N more cosigns". This departs from the mockup's flat feed, where each cosign is a card at its own date and noteless ones trail at the end: a card now ranks at its latest cosign, so cosigning brings it back up, and its cosigns go with it. Filters and paging take a card and its cosigns as one unit. Cosigns without a note are listed only with "Show cosigns without content" ticked, and counted either way. The local seed gains two more noted cosigns so a card shows the cap. Closes hyphacoop#98 Verified: band and floor beside the mockup at k-g-k/maple-conference@19bd101 by computed style at desktop, and in a 390px frame; on local emulators, cosigning a card as the seeded test user brought it to the top with "including you", its cosign listed under it, and View input landed on Your Input; the noteless setting, the expander and the badge count in the browser; components/conference jest (the grouped feed's ordering, units, filters, paging, cap, noteless setting and districts). Not verified: the client's review of the grouped list, which is our design; the 767px tier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the last-cosigned date on a wide card in a narrow window At 900px and under, the floor hid its "Last cosigned" date so the card's foot line could carry it, but only a narrow card has a foot line. A card 600px or wider in such a window (no rail, ~700px) showed the date nowhere. Entry now hands the floor the card's width, as it does its cosigns, and the floor hides the date only on a narrow card. Verified: npx jest components/conference passes apart from load.test.ts, which fails on main from a stale fixture. Not verified: the date by eye on a wide card in a narrow window, or at phone width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep conference drafts saving through failures and apart from cosigns A failed save left the Post or Cosign button disabled for good and a timed autosave stuck on "Saving". A post pressed while the edit hook was still loading the draft had its save dropped and waited forever; it now saves again once the draft settles. A flush made while a write is in flight no longer starts a second one, so a quick Agree then Cosign can't add two drafts. Delete Draft comes back when the fields return to what is kept. The compose form, which stays mounted behind a closed panel, no longer takes a cosign draft for its own: it does not seed from one, and its draft check refuses one. Before, it could publish a cosign draft as a cosign, or once refusing them, autosave a cosign's note as the reader's own input after every page load. A cosign posted without words reopens with "no words" only when no note has been drafted since, so that note is not hidden and wiped. Signing out clears the page's record of the draft, so a signed-out visitor sees Cosign. Verified: npx jest components/conference passes apart from load.test.ts (stale fixture, fails on main); InputForm.test fails without the seeding fix. On local emulators, a cosign draft with a note survived a reload as a cosign. yarn check-types clean in the changed files. Not verified: failed saves and the dropped-save post by hand; sign-out with a draft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Warn before a cosign replaces a note drafted for another The reader has one draft on a conference. With a note drafted cosigning one input, agreeing to cosign another wrote over that note without a word. Cosign stays on every card, but the agreeing step now says that continuing deletes the note on the other cosign, naming it when it is still in the feed. Verified: npx jest components/conference passes apart from load.test.ts (stale fixture, fails on main), with tests for warning and for staying quiet over this parent's draft or one without words. On local emulators: drafted a note cosigning Fight for the Future, reloaded, pressed Cosign on Massachusetts Medical Society, and the warning named Fight for the Future. Not verified: the warning at phone width; the wording with the client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the district counts from a cosigned card's floor The floor read "N cosigned · X Senate · Y House"; the last two counted the distinct districts the cosigners live in, which confused and weren't needed. It now shows the cosign count and the last-cosigned date. The tally still writes the two district counts; nothing reads them. Closes hyphacoop#127 Verified: npx jest components/conference/input passes, including a card that carries district counts showing neither label. Not verified: the floor by eye at desktop and phone widths. Dropping the district counts from the tally, the spec's FR-1 and #92's brief waits on the data-model decision. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Fix the React 19 type errors that failed the Vercel build The cosign commit passed a RefObject<HTMLTextAreaElement | null> to a parameter typed RefObject<HTMLElement>, called useRef with no argument, and left an onChange parameter untyped where styled(Field) drops the event type. next build type-checks these; the repo's CI does not, so only the Vercel deploy failed. Verified: yarn check-types is clean outside the untracked scripts/firebase-admin/_*.ts; npx jest components/conference/input (36 pass); prettier clean. Not verified: a full yarn build:dev, which still stops on an untracked scratch script that Vercel never sees; the Vercel deploy itself. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Put the reader's own cosign first, under the card it cosigns A reader whose input is a cosign now has the card they cosigned pinned first in Public Input, outside filters and paging, with their cosign first in the group under it, tinted and marked "You" as their own card is. Their cosign shows even without a note and past the three-row cap, and it carries the your-input anchor, so posting and the band's "View input" link land on it. A cosign whose card is not in the feed still stands alone, as before. Closes hyphacoop#129 Verified: components/conference/input and ConferenceDetails jest suites pass; prettier clean; check-types clean outside scripts/. Not verified: the row beside #125's own card at desktop and 390px; the post-and-land jump in a browser; yarn build:dev. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Share one keyboard-usable sortable header across the lobbying tables 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> * Add the Lobbying Explorer redesign's shared activity helpers and components 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 hyphacoop#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> * Rebuild the Lobbying Explorer pages to the redesign 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 hyphacoop#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, hyphacoop#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> * Use the redesign's position colors in the Lobbying Explorer 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> * Show every report period of the session in expanded activity rows 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> * Match the redesign's banner and Bill page title, and sort bills by organizations 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> * Use the scraper's precomputed aggregates in the Lobbying Explorer pages 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> * List "No client" on the Lobbying Explorer pages, not as an organization 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> * Line up expanded activity rows with the table's columns 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> * Label report periods the same way on every Lobbying Explorer page 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> * Keep the lobbying list docs under Firestore's index entry limit 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> * Give the lobbying scraper job enough memory for the stats step 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> * Read the lobbying list rows from their rows field 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> * Fetch each summary page once per scraper run process_disclosure re-fetched the registrant's summary page for every disclosure, so a registrant with N disclosures cost 1 + 2N requests. It now takes the parsed summary from its caller. The backfill passes the one it already holds; the weekly run passes the one it fetched live, or, for a prior year served from the summary cache, fetches it once when the first unprocessed disclosure needs it (none when nothing is new). Closes hyphacoop#101 Verified: backfill from the local archive into empty emulators, old vs new code, one worker: 2016 (20 registrants) summary fetches 59 -> 20, 2011 (1,820 registrants) 4,517 -> 1,820, disclosure fetches unchanged; all 1,064 and 6,749 documents identical apart from fetchedAt and the order of the clients list, which also varies between two runs of the old code. Live weekly run, 2026 limit 5: summary GETs 10 -> 5, 152 documents identical. Scraper unit tests pass, including new per-registrant fetch counts. Also with 8 workers, full 2011 from the archive: two runs of the old code and one of the new write 6,749 identical documents (clients list order ignored), summary fetches 4,592 / 4,586 -> 1,820. Not verified: the weekly prior-year cache-hit path against the live portal (unit tests only). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Center the key figure numbers between their row lines Each key figure row lined its number up with the first line of its label, so a wrapped label, or the Lobbyists row's line about firms, left far more space below the number than above it. The row now centers the number, its label and the info button, and the number's line box hugs its digits. Closes hyphacoop#120 Verified: against dev data in Chromium at 1280 and 375 px, the space above and below each number went from 16/33, 16/18, 17/65 and 14/33 px to 20/20, 15/15, 37/36 and 18/19 px; an opened definition still lines up under its label at both widths. Not verified: the All sessions view, where the numbers are wider; Firefox and Safari. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep a lobbying client who was paid nothing 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> * Count each session's clients from the client list 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> * Record the agency on Executive lobbying filings 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 hyphacoop#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> * Show non-bill lobbying activity instead of a dash 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> * Call lobbying rows activities, not filings 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> * Explain "—" and "0" in the Lobbyists list 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> * Mark who filed a disclosure in each session on the Lobbyists lists 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> * Show a session's counts for anyone who filed in it 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 hyphacoop#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> * Label a client's SoS registration link with its year 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> * Put the session's years on their own line in Key figures 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> * Label the payments chart's bars with their years 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> * Show a position bar segment's count as soon as it is hovered 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> * Set Key figures' years in the heading's own font 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> * Drop the sort hint from the Bills list 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> * Line the list pages' filter bar up with the list below it 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> * Restyle a lobbyist's page like the other lobbying pages 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> * Run a filer's position history oldest to newest, captioned by season An expanded row on the Bill and Client pages lists each filer's report periods from left to right, oldest first, captioned "25-Spring" and "25-Fall" (shown in capitals by the caption style) instead of "Jan–Jun 25". The filer's SoS links follow the same order and name ("25-Spring SoS filing"); a square's tooltip keeps the full "Jan–Jun 2025: Support". sessionPeriods and sessionPeriodsWith now return oldest first, since this list is their only user, and halfOfPeriod gives the year and half that the caption is built from. The compact "Jan–Jun 26" label is gone, with filingPeriodLabel's shortYear option. Verified: jest components/lobbying (72 passed) and check-types (only the 4 earlier errors), on the tree with the next commit's layout too; on yarn dev, H4746 and Abbott Laboratories show 25-SPRING, 25-FALL, 26-SPRING left to right and links in that order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give each period in a filer's history the same width Every period takes at least the width of "26-SPRING" or two squares, so Spring and Fall line up, and the "Most recent position" column is sized from them to fit a session's four periods on one line even when a filer reported two positions in a period; the filer side narrows to match. The SoS links sit two a line, so a year's Spring and Fall filings pair up. Verified: on yarn dev, Abbott Laboratories in the 193rd at 1280px: every period 64px on one line, BCB Government Relations with Oppose and Neutral in 23-Spring and 23-Fall; Maria Cahill's four links on two lines. Not verified: a period with three positions, which widens its cell; at phone width the table still scrolls sideways, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…elopment (so the Dev environment UX more closely matches prod). (#2350)
Mephistic
requested review from
alexjball,
kiminkim724,
mertbagt,
mvictor55,
nesanders,
sashamaryl and
timblais
as code owners
November 6, 2026 17:13
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changelog
Extra Deploy Steps
None.