Repository navigation
Add public input to the conference committee page - #2303
fastfadingviolets wants to merge 12 commits into
Conversation
/conferences now lands on a hero (what a conference committee is, its five steps) above the first conference in the rail's order. Every conference address gets the same hero above it and lands at the conference's top; only the landing opens on the hero. The explorer moves to /conferences/explorer, titled Conference Explorer and reached from the hero; a court with no conferences yet sends /conferences there. The Conferences nav item is active across all of /conferences. Scroll snap rests the reader on the whole hero or the conference's top, and only near the top, since a snapping window eases every scroll further down. Where the conference starts is measured by a ResizeObserver on what sits above the hero, as the body is a fixed-height box. Rail clicks land at the conference's top. Back within the app leaves the reader where the browser put them, the hero included; a reload lands at the conference's top, since Layout renders nothing until the client mounts and Chrome has nothing to restore against. Back from a bill to deep in a conference comes back near the top: Chrome restores while the shorter bill page is showing, as on every MAPLE page. The page has one h1: the hero's title on the landing, the conference's on an address, visually hidden below md where the picker shows the name. Step headings sit a level under the hero's title. The hero is built on the shared PageColumn and reads its steps from the locale. Closes #81 Verified: jest components/conference 16/16, tsc, eslint and prettier. In Chrome on dev:local: the landing opens on the hero with its steps, sources and explorer link; See the committees, a rail click (from 900px into the landing too) and a direct address land at the conference's top; Back from a bill to the hero stays at 0; reload mid-conference lands at the conference's top; the explorer's tab title; an unknown address 404s. In 390px frames: stacked steps, the picker, no horizontal overflow, the h1 present but clipped; --conference-nav matches the header. Not verified: a screen reader on a real phone; in a browser, the empty-court redirect, the snap reach after a resize and the reduced-motion jump; the hero's padding at phone width on PageColumn. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page column now spaces its header and the sections under it with a flex gap instead of a margin on the header, so a section added below the committee needs no wrapper of its own to sit 1.5rem from the one above. The page looks the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conference page ends with the mockup's Bill Text section: the Senate and House texts in a recessed well, one sheet each, with a version picker for Senate, House or both. Both is the default; below md it is the Senate, as in the mockup. Each sheet scrolls on its own and shows one paragraph per line the legislature broke. The texts are fetched in the browser, not with the page: an omnibus pair is half a megabyte (energy affordability is 533KB), and the page props would carry it on every view. Both are fetched once by the section, so switching version shows a text without fetching it again. A spinner holds each sheet while it loads, and a bill with no stored text gets a sentence and a link to its PDF. Text the scraper extracted from a PDF is shown as stored, line numbers and all; cleaning it up is its own change. Closes #79 Verified: on the emulator with real scraped bills, data privacy shows S2619 beside H5479 and community resilience shows S3064 beside H5518's PDF-extracted text, each sheet scrolling on its own; the picker shows one sheet and back; in a 390px frame it opens on the Senate with no sideways scroll; the spinner shows in both sheets before energy affordability's texts arrive; the gaps between sections are still 24px. Component tests cover both sheets, one chamber picked with the other still fetched, the spinner and the PDF link; tsc, eslint and prettier are clean. Not verified: the PDF link in a browser (no local bill lacks text); a real phone, only a narrow frame; whether a phone flashes both sheets before switching to the Senate on hydration; the picker after the fetch-once change in a browser, only in tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conference page's title now holds under the navbar with the mockup's tab bar beneath it: Committee and Bill Text, each tab scrolling to its section, the section being read underlined. Testimony adds Public Input between them. The bar was built with testimony's Public Input; it moves here so the bill text PR carries the page's frame and testimony builds on it. A bar of one section is left out, as the mockup leaves it out. A tab lights once its section's top has come up to where a jump parks it, measured on every scroll and whenever the column the sections sit in changes size, since a section moves without a scroll when one above it appears. The observed band it replaces was never reached by the page's last section, so Bill Text could not light. The bands pin under the navbar's bottom edge, tracked as the page scrolls, rather than the stylesheet's 4.5rem. That is shorter or taller than the desktop bar with the window, and the desktop bar lets go and scrolls away about a window down, being sticky only inside the layout's one-window box. On a phone the bar is not sticky, so the bands pin at the top. A tab scrolls its section to where the navbar will be on arrival: the browser's own jump takes the margin from before the scroll and lands a navbar's height low once the bar has gone. The learn page's rail tracks the same edge. A jump also turns off the hero's snap, which is on near the conference's top and would snap the landing too, stopping a jump to Bill Text short; the hero turns it back on near the top. Verified: on the emulator, data privacy's two tabs jump to their sections and land 24px under the bar as in the mockup; the lit tab follows the scroll, and Bill Text lights at the bottom of the page; the sheets still scroll on their own; the title sits flush under the desktop navbar, follows it up as it lets go (stretched page: 79, 59, 39px with the bar) and rests at the top once it has gone, where a jump to Bill Text and back lands 24px under the tabs; in a 390px frame it pins at the top with no sideways scroll. Under the hero, See the committees lands on the conference with the title pinned at the top, and Bill Text, Committee and Bill Text again each land 24px under the tabs and light (Bill Text stopped 175px short before the snap was turned off). The bar was laid beside the mockup at 19bd101. Component tests cover the tabs' targets, the lit tab following the sections on a scroll and on a resize with no scroll (failing without the resize), and a bar of one; tsc, eslint and prettier are clean. Not verified: a real phone, only a narrow frame; crossing the navbar's breakpoint by resizing; keyboard focus on the tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tab click replaced the history entry's state with null, which is Next's router state: Back to that entry from another conference rewrote the URL and left the other conference on screen. The click now keeps the state, with the hash added to the path Next restores on Back, which would drop it otherwise. The pinned navbar edge no longer remeasures on scrolls inside the conference column, such as a bill text's sheet, which cannot move the navbar; the tabs already ignored them. Verified: on the emulator, opening Health Worker Safety from the rail, clicking Bill Text, opening PETS Act from the rail and pressing the browser's Back shows Health Worker Safety again at #text, the tab click adding no history entry; jest components/conference 24/24, the new tab-click test failing without the fix; tsc, eslint and prettier clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testimony on a committee of conference answers a different question than testimony on a bill: which version the committee should report, not whether the bill should pass. Position gains passNothing, passSomething, passSenateVersion and passHouseVersion. A draft with a conferenceId must take one of them; every other draft keeps the three bill positions. It follows ballot questions: one Testimony type scoped by an optional conferenceId, with counts kept on the conference document. The testimony is filed against the conference's canonical bill but kept off it: not in its counts or latest testimony, not in testimony search, and not sent to the bill's followers. The author's followers still see it. A user can now hold a bill testimony and a conference testimony on one bill, so publish, delete, drafts and the version archive match the scope in memory instead of by query. That avoids stamping conferenceId: null on existing documents. Excluding conference testimony from search changes the indexed collection's filter, so each environment rebuilds the testimony collection on its next deploy. One composite index is added for the conference listing. Rolling back once conference testimony exists leaves older functions unable to publish or delete it. This is the server side only. Nothing under components/ or pages/ changes, so there is no way to file conference testimony from the site yet. Verified: functions and site tsc; functions unit tests for testimony and notifications (32); the testimony, ballot question, moderation, conferences and count backfill integration suites (59 tests), run on the branch tip where the functions are unchanged from this commit. Not verified: that testimony search leaves conference testimony out (no Typesense locally); publishing on dev. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This should go in after #2299 |
Conference testimony is filed against the conference's canonical bill, so it shares that bill's id with the bill's own testimony. The site now keeps the two apart the way the server does. The client gets its own ConferencePosition, ConferenceTestimony and conference draft types, so the bill and ballot-question displays keep their three positions and none of them change. Listings for a bill, a profile and recent testimony skip documents with a conferenceId. Draft and publication lookups match the conference scope with the functions' matchesTestimonyScope, so a conference draft cannot collide with the bill's draft. useEditConferenceTestimony drafts and publishes on a conference, and useConferenceTestimony lists what has been filed on one. Verified: site tsc is clean. The scope match is the functions' matchesTestimonyScope, whose unit test passes. On the emulator a conference testimony was drafted, posted and republished through the real publish function, and its draft carried the conferenceId. Not verified: a bill page that has testimony, in the browser, since S2581 has no bill document locally. The listing hooks have no unit test of their own: their jest suites already fail at the base commit (see the crumb on #69). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ient's mockup The conference page gets the client's Public Input feed and Add Your Input panel, built from k-g-k/maple-conference at 19bd101 (the "/" routes, which use its BUILD flag preset). The title carries a "Share your input" button, and Public Input joins the section tabs between Committee and Bill Text. Public Input lists what has been filed as cards, narrowed by account type and position, with a Following filter that appears once the reader follows someone, five to a page, and a modal that opens an entry whole. Each card has the kebab with Follow and Report. The panel writes on the existing draft and publish hooks: autosaved drafts, a review step, and posting, which puts the panel away and scrolls to a "Your Input" section. Editing reopens the panel on the posted text; because the server requires a reason for every republish, the review step shows one Reason for Edit field only then, the one control the mockup does not show. The page is keyed by conference so moving to another starts clean. Left out on purpose: the mockup's weekly-email checkbox (nothing on the server backs it) and its Share action (it has no destination). The followers' digest email and newsfeed card are unchanged and still assume three positions. Closes #69 Verified: site tsc and eslint are clean. jest passes for components/conference (18, including a test that moves between conferences and failed before the page was keyed). The feed card, filter row, and the compose panel and footer were measured against the running mockup and match, bar the differences below. Posting, editing, republishing, following (including from the card menu, which then offers Unfollow), the entry modal and filters were exercised in the browser on the emulator. The server commits below it pass the testimony, ballot question, moderation, conferences and backfill integration suites (59). Not verified: phone width. The review step and the Your Input section were compared by eye only. The feed's headings and filter row do not pin as the mockup's do. The mockup labels the reader's new entry "Just now" and animates the "on the record" line; ours shows the date. The account-type tooltip is a native title. Avatars are initials. The panel sits under our 72px navbar where the mockup's is 60px. The report modal and the unverified-email path were not exercised. The translation files the page loads have no test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Publishing, and republishing, testimony on a conference that is reportFiled, enacted or dissolved now fails with "not accepting testimony". Deleting stays allowed so people can still take their testimony down. Unlike the ballot question phase, which only the client enforces, this is checked in the publish transaction too, so calling the function directly cannot bypass it. On the site, a closed conference keeps its Public Input but offers no way to write: the title's Share your input button and the Edit item in the author's own card menu only show while it is appointed. The testimony detail page sent conference testimony to the bill's form; it now links to the conference page while the conference is appointed and offers no add or edit after. Closes #78 Verified: the integration tests added here (publish refused for each closed status, republish refused after the status flips, delete allowed on a closed conference) pass. jest covers the closed page (no Share button, Public Input still there), the card menu without Edit on a closed conference, and the detail page's conference link and its absence once reported; the menu test fails with the gate taken out. Site tsc is clean. Not verified: a reportFiled conference in the browser on the emulator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce data yarn emulators:start loads tests/integration/exportedTestData, which predates conferences: no conferences, no court-194 members or bills, and test@example.com unverified. Restarting the emulators, or running the integration suite, wipes whatever was added by hand. seedLocal reads conferences/194/*.yaml, queues scraper batches so the functions emulator fetches the real bills and members they name, waits for them, runs syncConferences, verifies test@example.com, and writes seven sample testimonies on one conference. Local only. Verified: on an emulator started clean from the export it filled in 32 bills, 65 members and 16 conferences in about 106 seconds, and every conference page then showed its six conferees. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ewsfeed Conference testimony reaches the author's followers, and both places they see it treated it as bill testimony: the digest email drew a broken position image and linked the bill, and the newsfeed card linked the bill and the bill's testimony page. The digest gives a conference item the conference id and its stance in words, taken from the site's wording (a test holds the two equal). Each of the six slots in the user partial sends it through a new conferenceItem partial that links the conference. The newsfeed card says the author gave input to the conference committee, shows the stance chip from the conference page, and links the conference's Public Input. Bill and ballot-question items are drawn as before. Verified: functions jest src (123), including buildDigestData and a render of the user partial for conference, bill and sixth-slot items; site tsc; NewsfeedCard jest for a conference and a bill card. On the emulator: the test user followed an author from the conference page, a new conference and a new bill testimony by that author both reached the follower's feed, /newsfeed showed both cards with the right links, and the digest built from the emulator rendered the conference item with its stance and conference link beside the bill item's image. Bill rows of the user partial render the same as on main apart from whitespace between tags. Not verified: violet has not looked at the email or the newsfeed in a browser herself, nor has anyone seen the conference stance cell, now 100px wide and wrapping, in an email client. NewsfeedCard's ballot-question test still fails, as it does on main (the test env has ballotQuestions off). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testimony from before conference testimony has no conferenceId, and Firestore cannot select on a missing field, so every read that kept bill testimony apart from conference testimony fetched the whole bill and filtered in memory. backfillTestimonyScope stamps null on whichever of ballotQuestionId and conferenceId a published, archived or draft testimony lacks (the ballot-question backfill skipped drafts, and this replaces it, in CI too), with --dry-run. Drafts are now written with conferenceId: null. Publish, delete, the counts backfill, the website's draft and publication lookup, the bill's listing and a testimony's version history then filter both fields in the query, with their limits back: publish, delete and the counts backfill are main's code plus one filter, and matchesTestimonyScope goes. The version history fixes a conference filing listing its author's bill versions on the canonical bill, and the reverse. Unscoped listings keep the in-memory filter. Two composite indexes and a conferenceId override are added; the bill listings rely on index merging, as the ballotQuestionId filter already did. The integration test data is migrated with the script. Run per environment, dev then prod: indexes first, then the backfill, then the functions and hosting, then the backfill again for what the old code wrote in between. Stamping after the new code is live hides legacy testimony from its bill and lets a republish duplicate it. Closes #82 Verified: site tsc; functions jest 120/120; yarn test:integration-ci tests/integration components/db, 102 pass, including the backfill on legacy docs and its dry run, republishing backfilled legacy testimony in place beside a conference filing, and delete skipping a newer conference filing for the bill's latest (fails without the filter); the script's dry run, run and second dry run against the exported test data (25 published, 133 archived, 38 drafts, then none left). Not verified: that the indexes serve every query, which the emulator does not check, so only dev shows it; the migration on dev or prod data; the full yarn test:integration-ci, which aborts partway at this commit's parent too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fadd435 to
8500e3f
Compare
Mephistic
left a comment
There was a problem hiding this comment.
Love this - just a few small questions, nothing that I consider a blocker to getting this PR merged.
|
|
||
| await syncConferences(ctx) | ||
|
|
||
| const user = await auth.getUserByEmail("test@example.com") |
There was a problem hiding this comment.
OOC, if we're relying on fetching an existing test user, why not just set that user to have emailVerified: true in the auth export (or add a new email-verified@example.com test user if we're worried about conflicts with other tests)?
|
|
||
| const want = [ | ||
| ...[...bills].map(id => `generalCourts/${COURT}/bills/${id}`), | ||
| ...[...members].map(id => `generalCourts/${COURT}/members/${id}`) |
There was a problem hiding this comment.
Should this also generate the MemberSearchIndex now that it's powering parts of the Conference feature? IIRC the job that generates the index is a scheduled function (that will never run in the emulators unless explicitly triggered).
| }) | ||
| }) | ||
|
|
||
| it("moves only the total for a position without a field in the set", () => { |
There was a problem hiding this comment.
Just double-checking to make sure I'm caught up on the desired UX: if a bill receives 100 "Public Inputs" and no actual testimony, we expect it to display on its Bill Details page as having 100 Testimonies (with 0 endorse/neutral/oppose counts and no testimony on the page)?
| items.push( | ||
| <PolicyActionItem | ||
| key="add-testimony" | ||
| billName={`${isUser ? "Edit" : "Add"} Testimony for ${conferenceName( |
There was a problem hiding this comment.
Just double-checking: for a conference, should this be called "Public Input" instead of "Testimony"? Not sure what nomenclature we settled on here.
| attachmentId: Maybe(String), | ||
| editReason: Maybe(String), | ||
| ballotQuestionId: Maybe(String) | ||
| ballotQuestionId: Maybe(String), |
There was a problem hiding this comment.
Just to clarify: are ballotQuestionId and conferenceId mutually exclusive? If so, should we enforce that constraint in the type definition?
| /** The mockup's theme.css values, for the colours the site has no token for. | ||
| * The page's own surfaces and brand come from the --maple tokens, as in | ||
| * ../styles.ts. */ | ||
| export const ink = { |
There was a problem hiding this comment.
@k-g-k 100% not a blocker, but do you know if the goal here to have these tokens be part of the re-branded color palate (or are they unique to the Conference feature)? Just want to make sure we're keeping an eye out for UI consistency here.
|
|
||
| /** The floating surface under a control. `$right` hangs it from the right | ||
| * edge; widths and padding are set by whoever extends it. */ | ||
| export const Menu = styled.div<{ $right?: boolean }>` |
There was a problem hiding this comment.
OOC, what does this custom popover give us that the Popover from react-bootstrap doesn't?

People can now file testimony on a committee of conference with a four-way stance on which version should pass, read what others filed in Public Input, and add their own from the Add Your Input panel, while that testimony stays out of the canonical bill's page, counts, search and followers, and reaches the author's followers' digest and newsfeed as conference input. Existing testimony is backfilled with an explicit null conferenceId so bill testimony is filtered by query rather than in memory; the backfill has to run in each environment before and again after the new functions and hosting go live, as the last commit describes.
Closes hyphacoop#69
Closes hyphacoop#82
Closes hyphacoop#78
What's in it
To Deploy
Everything except for the backfill step is automated. The safest way to deploy with no downtime is:
🤖 Generated with Claude Code