From 181cb9378629602b53fcc78231066001e20de513 Mon Sep 17 00:00:00 2001 From: violet <158512193+fastfadingviolets@users.noreply.github.com> Date: Wed, 8 Apr 2026 02:44:44 -0400 Subject: [PATCH 1/5] feat: add firestore indexes & rule for ballot questions feature (#2100) --- firestore.indexes.json | 58 ++++++++++++++++++++++++++++++++++++++++++ firestore.rules | 4 +++ 2 files changed, 62 insertions(+) diff --git a/firestore.indexes.json b/firestore.indexes.json index cdf966cf6..427795d41 100644 --- a/firestore.indexes.json +++ b/firestore.indexes.json @@ -18,6 +18,28 @@ } ] }, + { + "collectionGroup": "archivedTestimony", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "billId", + "order": "ASCENDING" + }, + { + "fieldPath": "court", + "order": "ASCENDING" + }, + { + "fieldPath": "ballotQuestionId", + "order": "ASCENDING" + }, + { + "fieldPath": "version", + "order": "DESCENDING" + } + ] + }, { "collectionGroup": "bills", "queryScope": "COLLECTION", @@ -727,6 +749,31 @@ "order": "ASCENDING" } ] + }, + { + "collectionGroup": "ballotQuestions", + "queryScope": "COLLECTION", + "fields": [ + { "fieldPath": "electionYear", "order": "ASCENDING" }, + { "fieldPath": "ballotStatus", "order": "ASCENDING" } + ] + }, + { + "collectionGroup": "publishedTestimony", + "queryScope": "COLLECTION_GROUP", + "fields": [ + { "fieldPath": "ballotQuestionId", "order": "ASCENDING" }, + { "fieldPath": "publishedAt", "order": "DESCENDING" } + ] + }, + { + "collectionGroup": "publishedTestimony", + "queryScope": "COLLECTION", + "fields": [ + { "fieldPath": "billId", "order": "ASCENDING" }, + { "fieldPath": "court", "order": "ASCENDING" }, + { "fieldPath": "ballotQuestionId", "order": "ASCENDING" } + ] } ], "fieldOverrides": [ @@ -865,6 +912,17 @@ "queryScope": "COLLECTION_GROUP" } ] + }, + { + "collectionGroup": "publishedTestimony", + "fieldPath": "ballotQuestionId", + "ttl": false, + "indexes": [ + { + "order": "ASCENDING", + "queryScope": "COLLECTION_GROUP" + } + ] } ] } diff --git a/firestore.rules b/firestore.rules index 978809c0f..a95586279 100644 --- a/firestore.rules +++ b/firestore.rules @@ -99,6 +99,10 @@ service cloud.firestore { allow read, write: if request.auth.token.get("role", "user") == "admin" } } + match /ballotQuestions/{id} { + allow read: if true; + allow write: if false; + } match /transcriptions/{tid} { // public, read-only allow read: if true From 3f48c2b65ff89eddfc43b1ffb6b59a735f58ef1f Mon Sep 17 00:00:00 2001 From: Janice Lichtman Date: Tue, 14 Apr 2026 19:50:28 -0400 Subject: [PATCH 2/5] fixing race condition on phone number authentication (#2091) --- components/EditProfilePage/PhoneVerificationModal.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/EditProfilePage/PhoneVerificationModal.tsx b/components/EditProfilePage/PhoneVerificationModal.tsx index 718dbc299..a08c78370 100644 --- a/components/EditProfilePage/PhoneVerificationModal.tsx +++ b/components/EditProfilePage/PhoneVerificationModal.tsx @@ -130,6 +130,10 @@ export default function PhoneVerificationModal({ setVerifying(true) try { await confirmationResult.confirm(code.trim()) + + // Force fresh token to ensure context.auth is populated + await auth.currentUser?.getIdToken(true) + if (completePhoneVerification.execute) { await completePhoneVerification.execute() } From 797099dbd9d6d2c3c69f3a0164115b7ff8020da2 Mon Sep 17 00:00:00 2001 From: Sam DeMarrais Date: Tue, 14 Apr 2026 19:59:52 -0400 Subject: [PATCH 3/5] Add multi video support to frontend (#2094) * Added multiple video handling to frontend * Fixed a reference-keeping bug * Fixed minor text error * Apply timestamp fix --- components/hearing/HearingDetails.tsx | 193 ++++++++++++++++++++------ components/hearing/HearingSidebar.tsx | 50 +++---- components/hearing/Transcriptions.tsx | 81 ++++++----- components/hearing/hearing.ts | 50 ++++++- public/locales/en/hearing.json | 4 +- 5 files changed, 270 insertions(+), 108 deletions(-) diff --git a/components/hearing/HearingDetails.tsx b/components/hearing/HearingDetails.tsx index e6e482d34..1e7c4b1d7 100644 --- a/components/hearing/HearingDetails.tsx +++ b/components/hearing/HearingDetails.tsx @@ -2,7 +2,8 @@ import { useRouter } from "next/router" import { Trans, useTranslation } from "next-i18next" import { useEffect, useRef, useState } from "react" import styled from "styled-components" -import { Col, Container, Image, Row } from "../bootstrap" +import { ButtonGroup } from "react-bootstrap" +import { Col, Container, Image, Row, Button } from "../bootstrap" import * as links from "../links" import { committeeURL, External } from "../links" import { @@ -14,8 +15,10 @@ import { HearingSidebar } from "./HearingSidebar" import { HearingData, Paragraph, + TranscriptData, convertToString, - fetchTranscriptionData + fetchTranscriptionData, + toVTT } from "./hearing" import { Transcriptions } from "./Transcriptions" @@ -39,6 +42,34 @@ const VideoParent = styled.div` overflow: hidden; ` +const VideoButton = styled(Button)` + border: none; + background: transparent; + color: ${({ $active }) => ($active ? "#212529" : "#6c757d")}; + font-weight: ${({ $active }) => ($active ? "600" : "500")}; + padding: 0.75rem 1rem; + border-radius: 0; + position: relative; + transition: all 0.25s ease-in-out; + + &:hover { + color: #212529; + background-color: rgba(0, 0, 0, 0.03); + } + + &::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + width: ${({ $active }) => ($active ? "100%" : "0%")}; + height: 2px; + background-color: #212529; + transition: all 0.3s ease-in-out; + transform: translateX(-50%); + } +` + export const HearingDetails = ({ hearingData: { billsInAgenda, @@ -48,21 +79,97 @@ export const HearingDetails = ({ generalCourtNumber, hearingDate, hearingId, - videoTranscriptionId, - videoURL + videos } }: { hearingData: HearingData }) => { const { t } = useTranslation(["common", "hearing"]) const router = useRouter() + const previousActive = useRef(null) + const routerReady = useRef(false) + const [activeVideo, setActiveVideo] = useState(0) + const [transcripts, setTranscripts] = useState< + (TranscriptData | null)[] | null + >(null) - const [transcriptData, setTranscriptData] = useState(null) - const [videoLoaded, setVideoLoaded] = useState(false) + // Important this occurs before router check; otherwise time will be improperly removed on first render + useEffect(() => { + if ( + previousActive.current === null || + previousActive.current === activeVideo + ) + return + previousActive.current = activeVideo + if (activeVideo !== 0) { + router.replace( + { + pathname: router.pathname, + query: { + hearingId: hearingId, + v: activeVideo + 1 + } + }, + undefined, + { shallow: true } + ) + } else { + router.replace( + { + pathname: router.pathname, + query: { + hearingId: hearingId + } + }, + undefined, + { shallow: true } + ) + } + }, [activeVideo]) - const handleVideoLoad = () => { - setVideoLoaded(true) - } + // Runs once + useEffect(() => { + if (!router.isReady || routerReady.current) return + routerReady.current = true + + const query = router.query.v + if (typeof query !== "string") { + previousActive.current = activeVideo + return + } + const n = parseInt(query, 10) + if (!isNaN(n) && n >= 1 && n <= videos.length) { + setActiveVideo(n - 1) + previousActive.current = n - 1 + } + }, [router.isReady]) + + useEffect(() => { + ;(async function () { + const transcripts = await Promise.all( + videos.map(v => + v.transcriptionId ? fetchTranscriptionData(v.transcriptionId) : null + ) + ) + const result = transcripts.map((t, index) => { + if (!t) return null + const filename = + transcripts.length == 1 + ? `hearing-${hearingId}` + : `hearing-${hearingId}-${index + 1}` + const vtt = toVTT(t) + const blob = new Blob([vtt], { type: "text/vtt" }) + + return { + title: videos[index].title, + transcript: t, + blob: blob, + filename: filename + } + }) + setTranscripts(result) + })() + }, [videos]) const videoRef = useRef(null) function setCurTimeVideo(value: number) { @@ -78,14 +185,6 @@ export const HearingDetails = ({ } }, [router.query.t, videoRef.current]) - useEffect(() => { - ;(async function () { - if (!videoTranscriptionId || transcriptData !== null) return - const docList = await fetchTranscriptionData(videoTranscriptionId) - setTranscriptData(docList) - })() - }, [videoTranscriptionId]) - return ( @@ -94,7 +193,7 @@ export const HearingDetails = ({ - {transcriptData ? ( + {videos.length ? ( {/* ButtonContainer contrains clickable area of link so that it doesn't exceed the button and strech invisibly across the width of the page */} @@ -128,7 +227,7 @@ export const HearingDetails = ({ - {transcriptData ? ( + {transcripts !== null && transcripts.length > 0 ? ( )} - {videoURL ? ( - - - + {videos.length > 1 ? ( + + {videos.map((video, index) => ( + setActiveVideo(index)} + > + {video.title} + + ))} + + ) : ( +
+ )} + + {videos.length > 0 ? ( + <> + + + + ) : ( - {transcriptData - ? t("no_video_on_file", { ns: "hearing" }) - : t("no_video_or_transcript", { ns: "hearing" })} + {t("no_video_or_transcript", { ns: "hearing" })} )} - {transcriptData ? ( + {transcripts && transcripts.length > 0 ? ( - ) : videoURL ? ( + ) : videos.length > 0 ? ( -
{t("no_transcript_on_file", { ns: "hearing" })}
+
{t("transcript_loading", { ns: "hearing" })}
) : null}
diff --git a/components/hearing/HearingSidebar.tsx b/components/hearing/HearingSidebar.tsx index a95b8a5a1..e57ffded3 100644 --- a/components/hearing/HearingSidebar.tsx +++ b/components/hearing/HearingSidebar.tsx @@ -9,7 +9,7 @@ import { firestore } from "../firebase" import * as links from "../links" import { billSiteURL, Internal } from "../links" import { LabeledIcon } from "../shared" -import { Paragraph, formatVTTTimestamp } from "./hearing" +import { Paragraph, TranscriptData, formatVTTTimestamp } from "./hearing" type Bill = { BillNumber: string @@ -114,19 +114,19 @@ const SidebarSubbody = styled.div` ` export const HearingSidebar = ({ + activeVideo, billsInAgenda, committeeCode, generalCourtNumber, hearingDate, - hearingId, - transcriptData + transcripts }: { + activeVideo: number billsInAgenda: any[] | null committeeCode: string | null generalCourtNumber: string | null hearingDate: string | null - hearingId: string - transcriptData: Paragraph[] | null + transcripts: (TranscriptData | null)[] | null }) => { const { t } = useTranslation(["common", "hearing"]) @@ -186,35 +186,14 @@ export const HearingSidebar = ({ }, [committeeCode, generalCourtNumber]) useEffect(() => { - setDownloadName(`hearing-${hearingId}.vtt`) - }, [hearingId]) - - useEffect(() => { - if (!transcriptData) return - const vttLines = ["WEBVTT", ""] - - transcriptData.forEach((paragraph, index) => { - const cueNumber = index + 1 - const startTime = formatVTTTimestamp(paragraph.start) - const endTime = formatVTTTimestamp(paragraph.end) - - vttLines.push( - String(cueNumber), - `${startTime} --> ${endTime}`, - paragraph.text, - "" - ) - }) - - const vtt = vttLines.join("\n") - const blob = new Blob([vtt], { type: "text/vtt" }) - const url = URL.createObjectURL(blob) + if (!transcripts || !transcripts[activeVideo]) return + setDownloadName(transcripts[activeVideo]!.filename) + const url = URL.createObjectURL(transcripts[activeVideo]!.blob) setDownloadURL(url) - return () => { URL.revokeObjectURL(url) } - }, [transcriptData]) + }, [activeVideo, transcripts]) useEffect(() => { committeeCode && generalCourtNumber ? committeeData() : null @@ -245,14 +224,21 @@ export const HearingSidebar = ({ ) : ( <> )} - {downloadURL !== "" ? ( + {downloadURL !== "" && + transcripts !== null && + transcripts[activeVideo] !== null ? ( ) : ( diff --git a/components/hearing/Transcriptions.tsx b/components/hearing/Transcriptions.tsx index b451c0917..54a61e905 100644 --- a/components/hearing/Transcriptions.tsx +++ b/components/hearing/Transcriptions.tsx @@ -10,6 +10,7 @@ import styled from "styled-components" import { Col, Container, Row } from "../bootstrap" import { Paragraph, + TranscriptData, convertToString, formatMilliseconds, formatTotalSeconds @@ -28,6 +29,10 @@ const ClearButton = styled(FontAwesomeIcon)` cursor: pointer; ` +const LegalContainer = styled(Container)` + background-color: white; +` + const ResultNumText = styled.div` position: absolute; right: 4rem; @@ -135,16 +140,16 @@ const TranscriptRow = styled(Row)` const TranscriptRowActive = styled(Row)`` export const Transcriptions = ({ + activeVideo, hearingId, - transcriptData, + transcripts, setCurTimeVideo, - videoLoaded, videoRef }: { + activeVideo: number hearingId: string - transcriptData: Paragraph[] + transcripts: (TranscriptData | null)[] setCurTimeVideo: any - videoLoaded: boolean videoRef: any }) => { const { t } = useTranslation(["common", "hearing"]) @@ -188,32 +193,36 @@ export const Transcriptions = ({ } useEffect(() => { + if (!transcripts[activeVideo]) return + setHighlightedId(-1) + containerRef.current.scrollTop = 0 + setSearchTerm("") + }, [activeVideo]) + + useEffect(() => { + if (!transcripts[activeVideo]) return setFilteredData( - transcriptData.filter(el => + transcripts[activeVideo]!.transcript.filter(el => el.text.toLowerCase().includes(searchTerm.toLowerCase()) ) ) - }, [transcriptData, searchTerm]) + }, [activeVideo, searchTerm]) const router = useRouter() const startTime = router.query.t - const resultString: string = convertToString(startTime) - - let currentIndex = transcriptData.findIndex( - element => parseInt(resultString, 10) < element.end / 1000 - ) // Set the initial scroll target when we have a startTime and transcripts useEffect(() => { - if ( - startTime && - transcriptData.length > 0 && - currentIndex !== -1 && - !hasScrolledToInitial.current - ) { + const resultString: string = convertToString(startTime) + const currentIndex = transcripts[activeVideo] + ? transcripts[activeVideo]!.transcript.findIndex( + element => parseInt(resultString, 10) < element.end / 1000 + ) + : -1 + if (startTime && currentIndex !== -1 && !hasScrolledToInitial.current) { setInitialScrollTarget(currentIndex) } - }, [startTime, transcriptData, currentIndex]) + }, [startTime, transcripts]) // Scroll to the initial target when the ref becomes available useEffect(() => { @@ -230,12 +239,13 @@ export const Transcriptions = ({ }, [initialScrollTarget, transcriptRefs.current.size, searchTerm]) useEffect(() => { + if (!transcripts[activeVideo]) return const handleTimeUpdate = () => { - videoLoaded - ? (currentIndex = transcriptData.findIndex( - element => videoRef.current.currentTime < element.end / 1000 - )) - : null + if (videoRef.current.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) + return + const currentIndex = filteredData.findIndex( + paragraph => videoRef.current.currentTime < paragraph.end / 1000 + ) if (containerRef.current && currentIndex !== highlightedId) { setHighlightedId(currentIndex) if (currentIndex !== -1 && !searchTerm) { @@ -245,18 +255,16 @@ export const Transcriptions = ({ } const videoElement = videoRef.current - videoLoaded - ? videoElement.addEventListener("timeupdate", handleTimeUpdate) - : null + if (!videoElement) return + + videoElement.addEventListener("timeupdate", handleTimeUpdate) return () => { - videoLoaded - ? videoElement.removeEventListener("timeupdate", handleTimeUpdate) - : null + videoElement.removeEventListener("timeupdate", handleTimeUpdate) } - }, [highlightedId, transcriptData, videoLoaded, videoRef, searchTerm]) + }, [highlightedId, activeVideo, filteredData, videoRef]) - return ( + return transcripts[activeVideo] ? ( <> ( + ) : ( + +
{t("no_transcript_on_file", { ns: "hearing" })}
+
) } // forwardRef must be updated for React 19 migration const TranscriptItem = forwardRef(function TranscriptItem( { + activeVideo, element, hearingId, highlightedId, @@ -325,6 +339,7 @@ const TranscriptItem = forwardRef(function TranscriptItem( setCurTimeVideo, searchTerm }: { + activeVideo: number element: Paragraph hearingId: string highlightedId: number @@ -396,7 +411,9 @@ const TranscriptItem = forwardRef(function TranscriptItem( { @@ -54,6 +66,18 @@ export async function fetchHearingData( ? DateTime.fromISO(maybeDate, { zone: "America/New_York" }).toISO() : null + const videos = docData.videos + ? docData.videos + : docData.videoURL + ? [ + { + title: `Hearing ${hearingId}`, + url: docData.videoURL, + transcriptionId: docData.videoTranscriptionId ?? null + } + ] + : [] + return { billsInAgenda: docData.content?.HearingAgendas[0]?.DocumentsInAgenda ?? null, @@ -64,8 +88,7 @@ export async function fetchHearingData( docData.content?.HearingHost?.GeneralCourtNumber ?? null, hearingDate: hearingDate, hearingId: hearingId, - videoTranscriptionId: docData.videoTranscriptionId ?? null, - videoURL: docData.videoURL ?? null + videos: videos } } @@ -128,3 +151,22 @@ export function formatVTTTimestamp(ms: number): string { return `${formattedHours}:${formattedMinutes}:${formattedSeconds}.${formattedMilliseconds}` } + +export function toVTT(transcriptData: Paragraph[]): string { + const vttLines = ["WEBVTT", ""] + + transcriptData.forEach((paragraph, index) => { + const cueNumber = index + 1 + const startTime = formatVTTTimestamp(paragraph.start) + const endTime = formatVTTTimestamp(paragraph.end) + + vttLines.push( + String(cueNumber), + `${startTime} --> ${endTime}`, + paragraph.text, + "" + ) + }) + + return vttLines.join("\n") +} diff --git a/public/locales/en/hearing.json b/public/locales/en/hearing.json index b16347b7f..bfd82a69c 100644 --- a/public/locales/en/hearing.json +++ b/public/locales/en/hearing.json @@ -4,6 +4,7 @@ "chairs": "Chairs", "committee_members": "Committee members", "download_transcript": "Download transcript", + "download_transcript_x": "Download transcript for {{title}}", "hearing_details": "Hearing details", "house_chair": "House Chair", "member": "Member", @@ -11,7 +12,7 @@ "no": "No", "no_record": "No Record", "no_results_found": "No Search Results for ”{{searchTerm}}”", - "no_transcript_on_file": "This hearing does not yet have a transcription on file", + "no_transcript_on_file": "This video does not yet have a transcription on file", "no_video_on_file": "This hearing does not yet have a video on file", "no_video_or_transcript": "This hearing does not yet have a video or transcript on file", "no_vote": "No Vote Recorded", @@ -26,6 +27,7 @@ "see_all": "See all", "see_less": "See less", "senate_chair": "Senate Chair", + "transcript_loading": "Loading transcript for this hearing...", "video_and_transcription_feature_callout": "Hearing Video + Transcription", "view_bill": "View Bill Details", "view_votes": "View Committee Votes", From 6b70152bbbf3c5610edb72ed1f8e448dd325c70b Mon Sep 17 00:00:00 2001 From: Andre Coullard <119697079+ACoullard@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:03:25 -0400 Subject: [PATCH 4/5] MAPLE In The News Page (#2102) * get basic page and translations * css changes to nav groups * add in main components * add in badges and tweak styling * remove placeholder testing news items * add loading state * prettier * remove unused import and add read more button to translation file * add sorting to news items * formatting --- components/InTheNews/InTheNews.tsx | 209 +++++++++++++++++++++++++++++ components/InTheNews/NewsCard.tsx | 68 ++++++++++ components/Navbar.tsx | 3 + components/NavbarComponents.tsx | 21 +++ components/db/news.ts | 11 +- pages/about/in-the-news.tsx | 17 +++ public/locales/en/common.json | 2 + public/locales/en/inTheNews.json | 13 ++ styles/bootstrap.scss | 6 + styles/globals.css | 16 +++ 10 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 components/InTheNews/InTheNews.tsx create mode 100644 components/InTheNews/NewsCard.tsx create mode 100644 pages/about/in-the-news.tsx create mode 100644 public/locales/en/inTheNews.json diff --git a/components/InTheNews/InTheNews.tsx b/components/InTheNews/InTheNews.tsx new file mode 100644 index 000000000..fc6f91b82 --- /dev/null +++ b/components/InTheNews/InTheNews.tsx @@ -0,0 +1,209 @@ +import { useState } from "react" +import { Col, Row, Container, Badge, Spinner } from "../bootstrap" +import Tab from "react-bootstrap/Tab" +import Nav from "react-bootstrap/Nav" +import Dropdown from "react-bootstrap/Dropdown" +import { useMediaQuery } from "usehooks-ts" +import { useTranslation } from "next-i18next" +import { NewsCard } from "./NewsCard" +import { NewsType, NewsItem, useNews } from "components/db/news" + +type NewsFeedProps = { + type: NewsType + newsItems: NewsItem[] +} + +type TabCounts = { + media: number + awards: number + books: number +} + +const NewsFeed = ({ type, newsItems }: NewsFeedProps) => { + return ( +
+ {newsItems + .filter(item => item.type === type) + .map((item, index) => ( + + ))} +
+ ) +} + +export const InTheNews = () => { + const { t } = useTranslation("inTheNews") + const isMobile = useMediaQuery("(max-width: 768px)") + const { result: newsItems, loading } = useNews() + + const counts: TabCounts | null = newsItems + ? { + media: newsItems.filter(item => item.type === "article").length, + awards: newsItems.filter(item => item.type === "award").length, + books: newsItems.filter(item => item.type === "book").length + } + : null + + return ( + +

+ {t("title")} +

+
+ + {isMobile ? ( + + ) : ( + + )} + + + {loading ? ( +
+ + Loading... + +
+ ) : ( + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ )} + +
+
+
+
+ ) +} + +const TabGroup = ({ counts }: { counts: TabCounts | null }) => { + const { t } = useTranslation("inTheNews") + return ( + + + + + + + + + + + + ) +} + +const TabDropdown = ({ counts }: { counts: TabCounts | null }) => { + const { t } = useTranslation("inTheNews") + const [selectedTab, setSelectedTab] = useState("Media") + + const handleTabClick = (tabTitle: string) => { + setSelectedTab(tabTitle) + } + + return ( + + + + + {selectedTab} + + + handleTabClick("Media")} + > + {t("media.title")} + + + handleTabClick("Awards")} + > + {t("awards.title")} + + + handleTabClick("Books")} + > + {t("books.title")} + + + + + + ) +} diff --git a/components/InTheNews/NewsCard.tsx b/components/InTheNews/NewsCard.tsx new file mode 100644 index 000000000..cb545e354 --- /dev/null +++ b/components/InTheNews/NewsCard.tsx @@ -0,0 +1,68 @@ +import ArrowForward from "@mui/icons-material/ArrowForward" +import { useTranslation } from "next-i18next" +import { NewsItem } from "components/db" + +type NewsCardProps = { + newsItem: NewsItem +} + +export const NewsCard = ({ newsItem }: NewsCardProps) => { + const { t } = useTranslation("inTheNews") + return ( +
+
+
+

+ {new Date(newsItem.publishDate).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + timeZone: "UTC" + })} +

+
+

{newsItem.author}

+
+ +
{newsItem.description}
+
+ +
+ ) +} diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 029bcc35f..401ac8f17 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -16,6 +16,7 @@ import { NavbarLinkEffective, NavbarLinkFAQ, NavbarLinkGoals, + NavbarLinkInTheNews, NavbarLinkLogo, NavbarLinkNewsfeed, NavbarLinkProcess, @@ -92,6 +93,7 @@ const MobileNav: React.FC> = () => { + @@ -228,6 +230,7 @@ const DesktopNav: React.FC> = () => { +
diff --git a/components/NavbarComponents.tsx b/components/NavbarComponents.tsx index 458c56cb6..4395c134e 100644 --- a/components/NavbarComponents.tsx +++ b/components/NavbarComponents.tsx @@ -378,3 +378,24 @@ export const NavbarLinkWhyUse: React.FC< ) } + +export const NavbarLinkInTheNews: React.FC< + React.PropsWithChildren<{ + handleClick?: any + other?: any + }> +> = ({ handleClick, other }) => { + const isMobile = useMediaQuery("(max-width: 768px)") + const { t } = useTranslation(["common", "auth"]) + return ( + + + {t("navigation.inTheNews")} + + + ) +} diff --git a/components/db/news.ts b/components/db/news.ts index fb4dca211..8c8d587c2 100644 --- a/components/db/news.ts +++ b/components/db/news.ts @@ -1,4 +1,10 @@ -import { collection, getDocs, orderBy, Timestamp } from "firebase/firestore" +import { + collection, + getDocs, + orderBy, + query, + Timestamp +} from "firebase/firestore" import { useAsync } from "react-async-hook" import { firestore } from "../firebase" @@ -17,7 +23,8 @@ export type NewsItem = { export async function listNews(): Promise { const newsRef = collection(firestore, "news") - const result = await getDocs(newsRef) + const q = query(newsRef, orderBy("publishDate", "desc")) + const result = await getDocs(q) return result.docs.map(d => ({ id: d.id, ...d.data() } as NewsItem)) } diff --git a/pages/about/in-the-news.tsx b/pages/about/in-the-news.tsx new file mode 100644 index 000000000..1eff2c707 --- /dev/null +++ b/pages/about/in-the-news.tsx @@ -0,0 +1,17 @@ +import { createPage } from "../../components/page" +import { InTheNews } from "../../components/InTheNews/InTheNews" +import { createGetStaticTranslationProps } from "components/translations" + +export default createPage({ + titleI18nKey: "titles.in_the_news", + Page: () => { + return + } +}) + +export const getStaticProps = createGetStaticTranslationProps([ + "auth", + "common", + "footer", + "inTheNews" +]) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index a0a3d5fe2..8c6b7daf7 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -128,6 +128,7 @@ "aboutTestimony": "About Testimony", "viewProfile": "View Profile", "whyUseMaple": "Why Use MAPLE", + "inTheNews": "In The News", "login": "Login" }, "new_feature": "*NEW*", @@ -180,6 +181,7 @@ "legislative_process": "How To Have Impact Through Legislative Testimony", "submit_testimony": "Submit Testimony", "support_maple": "How to Support MAPLE", + "in_the_news": "In The News", "testimony": "Testimony", "policies": "Policies", "unsubscribe": "Unsubscribe", diff --git a/public/locales/en/inTheNews.json b/public/locales/en/inTheNews.json new file mode 100644 index 000000000..5d1cb3243 --- /dev/null +++ b/public/locales/en/inTheNews.json @@ -0,0 +1,13 @@ +{ + "title": "Media, Articles & Insights", + "readMoreButton": "READ MORE", + "media": { + "title": "Media" + }, + "awards": { + "title": "Awards" + }, + "books": { + "title": "Books" + } +} diff --git a/styles/bootstrap.scss b/styles/bootstrap.scss index d526ca4f8..9412d68b0 100644 --- a/styles/bootstrap.scss +++ b/styles/bootstrap.scss @@ -149,3 +149,9 @@ $utilities: ( .tracking-widest { letter-spacing: 0.1em; } + +.hover-underline { + &:hover { + text-decoration: underline !important; + } +} diff --git a/styles/globals.css b/styles/globals.css index f3a8f052e..61c2cac36 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -159,3 +159,19 @@ letter-spacing: 0.015em; padding: 7px, 8px, 7px, 8px; } + +/* Custom style in "In The News" Page */ +.in-the-news .nav-item .nav-link { + color: black; + font-weight: 600; + border-bottom: 2px solid; + border-color: #0000001a; +} +.in-the-news .nav-item .nav-link:hover { + color: #15276a; +} +.in-the-news .nav-item .nav-link.active { + color: #15276a; + border-bottom: 4px solid; + border-color: #15276a; +} From e39f298965e698384a927cbb4b1699f818b89666 Mon Sep 17 00:00:00 2001 From: violet <158512193+fastfadingviolets@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:53:14 -0400 Subject: [PATCH 5/5] Add ballot questions to maple (#2090) * Add a missing await in tests/integration/common * Add a data model, security rules, and indexes for ballot questions. * Add a script to ingest ballot questions from a directory of yaml files Also adds an integration test * Implement db methods to query ballot questions * Separate testimony for ballot questions vs bills. Also includes a clearer "ballotStatus" model as the ballot question progresses along the process. * Bring data model in line with figma * update frontend doc * Update ballot question docs for legislature testimony * Clarify ballot question testimony behavior at legislature stage * Add ballot question detail page frontend MVP * Add browse ballot questions page * Add bottom spacing to browse ballot questions page * Match browse ballot questions width to detail page * Use Bootstrap container widths for ballot question pages * Use Bootstrap container widths in browse ballot questions * Share ballot question types and stabilize testimony counts * Share ballot question types and stabilize testimony counts * Add ballotQuestionIds to publishTestimony * Add a backfill script for missing ballotquestionids on testimony * Validate ballotquestionid when publishing testimony * Render ballot-question testimony correctly in profile lists * Tighten ballot-question profile testimony copy * Fix ballot-question publish flow routing * Restore existing profile position copy * Tighten ballot-question testimony scope checks * Make useEditTestimony loading test less brittle * Remove duplicate ballot-question testimony heading * Move ballot question bill link to overview tab * Polish ballot-question testimony panel layout * Rename ballot question overview link * Avoid autosaving empty ballot-question drafts * Add a README for admin scripts & a test for package.json harness * fix firebase selection in local environments * 2026 ballot question yamls * add yamls, enable newlines in summary for ballot questions * add the old archivedTestimony back in for backcompat * Add fieldOverrides to publishedTestimony ballotQuestionId index * Fix: show commitee hearings in ballot question pages * add descriptions and details to select ballots * added legislature demo ballot question * Link ballot question hearings to MAPLE pages * Remove raw video fallback from ballot question hearing links * Organize ballot questions directory by year * add 2024 yamls * file 2024 questions * remove admin-generated data for 2026 * Split ballot-question testimony flow from bill flow * Add coverage for ballot-question publish flow * Keep recipient hook order stable * Strengthen ballot-question share redirect test * Clarify ballot-question share redirect cases * Refine ballot question phase links * Adjust ballot question testimony phase copy * Add ballot question browse filters * Redesign ballot question browse cards * Refine ballot question detail page design * Add shell accessibility landmarks * Make ballot question tabs accessible * Fix ballot question sync typecheck * Simplify ballot question sync type fix * Fix ballot question YAML summaries * Run Prettier for ballot question branch * Use repo Firebase CLI in integration tests * fix missing import & returns * polish ballot header * unstick ballot nav * polish overview icon * clarify header metadata * polish header actions * tighten hearing card * polish nav hover * fix ballot nav lint * Implement follow/notifications for ballot questions * address browse review * fix: make testimony notification flow use court data from testimony prevents extra fetch of a ballot question * refactor ballot tabs * fix: resolve CI failures for ballot questions PR * fix ballot question types * format ballot question files * Update summary header and add tooltip * Replace testimony with perspective in ballot questions * fix ballot question types * Improve nav button styling and spacing * format navbar components * format ctas panel * Simplify ballot question cards and update ballot lifecycle statuses * Enhance committee hearing card with icon and updated copy * format committee hearing * Refine BQ UX: collapsible search, emphasize question numbers, compact cards * Improve navbar accessibility and labels * fix navbar dropdown focus * fix repo checks * fix collapsible filter accessibility * Add title field to ballot questions and improve paragraph spacing in summary * refine ballot browse controls * align ballot status lifecycle copy * skip notifications when history empty * polish overview copy * fix navbar sign-out semantics * close mobile profile drawer on view profile * preserve dropdown active state * Fix invalid testimony locale quotes * Remove leftover merge marker * Restore client-side routing for navbar dropdown links * Fix profile banner overflow and nav link contrast * Lighten mobile navbar link contrast * Unify mobile nav item and dropdown colors * Constrain mobile navbar dropdown width * Tighten desktop navbar spacing at medium widths * Keep medium-width desktop nav links on one line * Reduce desktop navbar logo at medium widths * Reduce navbar logo at narrow medium widths * Fix navbar formatting * Fix ballot question formatting * Fix ballot question YAML status compatibility * 2026 BQ titles and statuses * 2024 BQ titles, status, numbers * doc changes for ballot statuses * Align ballot question tab test copy * Fix ballot question testimony follow behavior * Polish ballot question browse and perspective flow * typo * Fix ballot question hearing icon image * Capitalization nitpicks * Polish ballot question perspective copy * Apply prettier formatting Co-Authored-By: Claude Sonnet 4.6 * fix to use a translation string * Restore bill testimony links in ballot question perspectives * Restore ballot question stance wording * types in yamls * Finalize ballot question follow-up fixes * Apply prettier formatting * Trigger CI rerun * Fix ballot question testimony fixture * Remove dead collectionGroup fallback queries for BQ counts Counts are now stored on BallotQuestion docs with defaults of 0, so the hasStored/getStored helpers and live query fallbacks are unreachable. Read counts directly from the doc. * Guard count fields with ?? 0 for docs missing fields * feat: gate ballot questions behind feature flag --------- Co-authored-by: Lexa Michaelides <32111123+LexaMichaelides@users.noreply.github.com> Co-authored-by: vincent Co-authored-by: Lexa Co-authored-by: Claude Sonnet 4.6 --- .github/workflows/repo-checks.yml | 9 + ballotQuestions/2024/23-12.yaml | 31 + ballotQuestions/2024/23-13.yaml | 66 ++ ballotQuestions/2024/23-25.yaml | 59 ++ ballotQuestions/2024/23-26.yaml | 22 + ballotQuestions/2024/23-34.yaml | 14 + ballotQuestions/2026/25-03.yaml | 24 + ballotQuestions/2026/25-08.yaml | 42 ++ ballotQuestions/2026/25-10.yaml | 38 + ballotQuestions/2026/25-12.yaml | 25 + ballotQuestions/2026/25-14.yaml | 20 + ballotQuestions/2026/25-15.yaml | 53 ++ ballotQuestions/2026/25-17.yaml | 27 + ballotQuestions/2026/25-18.yaml | 22 + ballotQuestions/2026/25-21.yaml | 23 + ballotQuestions/2026/25-22.yaml | 18 + ballotQuestions/2026/25-37.yaml | 58 ++ components/CommentModal/Attachment.tsx | 12 +- components/EditProfilePage/FollowingTab.tsx | 75 +- components/Footer/Footer.tsx | 5 + components/Navbar.tsx | 107 ++- components/NavbarComponents.tsx | 305 +++++--- components/Newsfeed/Newsfeed.tsx | 40 +- components/Newsfeed/NotificationProps.tsx | 7 +- components/NewsfeedCard/NewsfeedCard.test.tsx | 51 ++ components/NewsfeedCard/NewsfeedCard.tsx | 15 + components/NewsfeedCard/NewsfeedCardBody.tsx | 35 + components/NewsfeedCard/NewsfeedCardTitle.tsx | 30 +- components/TestimonyCard/BillInfoHeader.tsx | 26 +- .../TestimonyCard/SortTestimonyDropDown.tsx | 11 +- components/TestimonyCard/Tabs.tsx | 85 ++- components/TestimonyCard/TestimonyItem.tsx | 69 +- .../TestimonyCard/ViewTestimony.test.tsx | 50 ++ components/TestimonyCard/ViewTestimony.tsx | 177 +++-- components/ViewAttachment.tsx | 20 +- components/auth/SignInWithButton.tsx | 25 +- .../BallotQuestionDetails.test.tsx | 48 ++ .../ballotquestions/BallotQuestionDetails.tsx | 81 +++ .../ballotquestions/BallotQuestionHeader.tsx | 218 ++++++ .../ballotquestions/BallotQuestionNav.tsx | 120 ++++ .../BallotQuestionTabButton.tsx | 54 ++ .../BallotQuestionTabLinks.test.tsx | 161 +++++ .../BrowseBallotQuestions.test.tsx | 195 +++++ .../ballotquestions/BrowseBallotQuestions.tsx | 671 ++++++++++++++++++ .../ballotquestions/CommitteeHearing.test.tsx | 46 ++ .../ballotquestions/CommitteeHearing.tsx | 82 +++ components/ballotquestions/DescriptionBox.tsx | 44 ++ components/ballotquestions/OverviewTab.tsx | 200 ++++++ components/ballotquestions/TestimoniesTab.tsx | 167 +++++ .../YourTestimonyPanel.test.tsx | 60 ++ .../ballotquestions/YourTestimonyPanel.tsx | 79 +++ components/ballotquestions/status.ts | 43 ++ components/ballotquestions/types.ts | 28 + components/bill/BillTestimonies.tsx | 4 +- components/db/api.ts | 25 +- components/db/index.ts | 1 + .../db/testimony/ballotQuestionScope.ts | 9 + components/db/testimony/resolveTestimony.ts | 54 +- components/db/testimony/types.ts | 3 +- .../db/testimony/useEditTestimony.test.ts | 96 ++- components/db/testimony/useEditTestimony.ts | 47 +- .../testimony/usePublishedTestimonyListing.ts | 35 +- components/featureFlags.ts | 13 +- components/firebase.ts | 6 +- components/layout.tsx | 23 +- components/links.tsx | 19 +- .../moderation/moderationComponents.test.tsx | 28 + components/publish/ChooseStance.tsx | 11 +- components/publish/KeepNote.tsx | 86 ++- components/publish/ProgressBar.tsx | 31 +- components/publish/PublishTestimony.tsx | 70 +- components/publish/QuickInfo.tsx | 26 +- components/publish/SelectRecipients.tsx | 12 +- components/publish/ShareTestimony.tsx | 29 +- components/publish/SubmitTestimonyForm.tsx | 36 +- components/publish/TestimonyPreview.tsx | 97 ++- components/publish/WriteTestimony.tsx | 38 +- components/publish/hooks/index.ts | 2 + components/publish/hooks/navigation.ts | 59 +- components/publish/hooks/resolveBill.ts | 11 +- components/publish/hooks/useFormSync.test.ts | 40 ++ components/publish/hooks/useFormSync.ts | 42 +- components/publish/hooks/usePublishCopy.ts | 20 + components/publish/hooks/usePublishMode.ts | 5 + .../publish/hooks/usePublishService.tsx | 17 +- components/publish/hooks/useTestimonyEmail.ts | 14 +- components/publish/mode.ts | 7 + .../publish/panel/EditTestimonyButton.tsx | 13 +- .../publish/panel/TestimonyFormPanel.tsx | 66 +- components/publish/panel/ThankYouModal.tsx | 9 +- components/publish/panel/YourTestimony.tsx | 106 ++- components/publish/panel/ctas.tsx | 176 +++-- components/publish/redux.test.ts | 147 ++++ components/publish/redux.ts | 57 +- components/shared/FollowButton.tsx | 26 +- components/shared/FollowingQueries.tsx | 32 +- components/shared/StyledSharedComponents.tsx | 2 +- .../TestimonyDetailPage/BillTitle.test.tsx | 45 ++ .../TestimonyDetailPage/BillTitle.tsx | 23 +- .../PolicyActions.test.tsx | 127 ++++ .../TestimonyDetailPage/PolicyActions.tsx | 110 ++- .../TestimonyDetailPage.tsx | 9 - .../testimonyDetailSlice.ts | 6 +- docs/ballot-questions-data-model.md | 253 +++++++ docs/ballot-questions-frontend.md | 278 ++++++++ functions/src/ballotQuestions/types.ts | 61 ++ functions/src/email/digestEmail.handlebars | 2 + .../ballotQuestions/ballotQuestion.handlebars | 31 + .../ballotQuestions.handlebars | 45 ++ functions/src/index.ts | 1 + .../deliverNotifications.test.ts | 304 ++++++++ .../src/notifications/deliverNotifications.ts | 66 +- functions/src/notifications/emailTypes.ts | 9 + functions/src/notifications/index.ts | 2 + ...teBallotQuestionNotificationEvents.test.ts | 120 ++++ ...opulateBallotQuestionNotificationEvents.ts | 57 ++ .../populateBillHistoryNotificationEvents.ts | 11 +- ...teTestimonySubmissionNotificationEvents.ts | 8 + .../src/notifications/publishNotifications.ts | 80 ++- functions/src/notifications/types.ts | 29 + functions/src/search/SearchIndexer.ts | 15 +- functions/src/subscriptions/types.ts | 5 + functions/src/testimony/deleteTestimony.ts | 21 + functions/src/testimony/publishTestimony.ts | 46 +- functions/src/testimony/search.ts | 1 + functions/src/testimony/types.ts | 3 +- .../testimony/updateTestimonyCounts.test.ts | 63 ++ .../src/testimony/updateTestimonyCounts.ts | 29 +- package.json | 6 +- pages/_document.tsx | 30 + pages/ballotQuestions/[id].tsx | 107 +++ pages/ballotQuestions/index.tsx | 98 +++ pages/submit-testimony.tsx | 79 ++- pages/testimony/[...testimonyDetail].tsx | 73 +- public/locales/en/common.json | 9 + public/locales/en/editProfile.json | 1 + public/locales/en/profile.json | 2 +- public/locales/en/search.json | 32 + public/locales/en/testimony.json | 75 ++ scripts/README.md | 203 ++++++ .../backfillBallotQuestionTestimonyCounts.ts | 62 ++ .../backfillTestimonyBallotQuestionId.ts | 42 ++ scripts/firebase-admin/sendTestEmail.ts | 4 +- scripts/firebase-admin/syncBallotQuestions.ts | 48 ++ .../NotificationDigestEmail.stories.tsx | 4 +- styles/globals.css | 200 ++++++ .../ballotQuestions/bq-test-fixture.yaml | 18 + .../backfillTestimonyBallotQuestionId.test.ts | 62 ++ .../ballotQuestionFollowing.test.ts | 79 +++ tests/integration/ballotQuestions.test.ts | 36 + tests/integration/testimony.test.ts | 123 +++- tests/system/testimony.test.ts | 32 +- yarn.lock | 105 +++ 153 files changed, 8343 insertions(+), 690 deletions(-) create mode 100644 ballotQuestions/2024/23-12.yaml create mode 100644 ballotQuestions/2024/23-13.yaml create mode 100644 ballotQuestions/2024/23-25.yaml create mode 100644 ballotQuestions/2024/23-26.yaml create mode 100644 ballotQuestions/2024/23-34.yaml create mode 100644 ballotQuestions/2026/25-03.yaml create mode 100644 ballotQuestions/2026/25-08.yaml create mode 100644 ballotQuestions/2026/25-10.yaml create mode 100644 ballotQuestions/2026/25-12.yaml create mode 100644 ballotQuestions/2026/25-14.yaml create mode 100644 ballotQuestions/2026/25-15.yaml create mode 100644 ballotQuestions/2026/25-17.yaml create mode 100644 ballotQuestions/2026/25-18.yaml create mode 100644 ballotQuestions/2026/25-21.yaml create mode 100644 ballotQuestions/2026/25-22.yaml create mode 100644 ballotQuestions/2026/25-37.yaml create mode 100644 components/NewsfeedCard/NewsfeedCard.test.tsx create mode 100644 components/TestimonyCard/ViewTestimony.test.tsx create mode 100644 components/ballotquestions/BallotQuestionDetails.test.tsx create mode 100644 components/ballotquestions/BallotQuestionDetails.tsx create mode 100644 components/ballotquestions/BallotQuestionHeader.tsx create mode 100644 components/ballotquestions/BallotQuestionNav.tsx create mode 100644 components/ballotquestions/BallotQuestionTabButton.tsx create mode 100644 components/ballotquestions/BallotQuestionTabLinks.test.tsx create mode 100644 components/ballotquestions/BrowseBallotQuestions.test.tsx create mode 100644 components/ballotquestions/BrowseBallotQuestions.tsx create mode 100644 components/ballotquestions/CommitteeHearing.test.tsx create mode 100644 components/ballotquestions/CommitteeHearing.tsx create mode 100644 components/ballotquestions/DescriptionBox.tsx create mode 100644 components/ballotquestions/OverviewTab.tsx create mode 100644 components/ballotquestions/TestimoniesTab.tsx create mode 100644 components/ballotquestions/YourTestimonyPanel.test.tsx create mode 100644 components/ballotquestions/YourTestimonyPanel.tsx create mode 100644 components/ballotquestions/status.ts create mode 100644 components/ballotquestions/types.ts create mode 100644 components/db/testimony/ballotQuestionScope.ts create mode 100644 components/publish/hooks/useFormSync.test.ts create mode 100644 components/publish/hooks/usePublishCopy.ts create mode 100644 components/publish/hooks/usePublishMode.ts create mode 100644 components/publish/mode.ts create mode 100644 components/publish/redux.test.ts create mode 100644 components/testimony/TestimonyDetailPage/BillTitle.test.tsx create mode 100644 components/testimony/TestimonyDetailPage/PolicyActions.test.tsx create mode 100644 docs/ballot-questions-data-model.md create mode 100644 docs/ballot-questions-frontend.md create mode 100644 functions/src/ballotQuestions/types.ts create mode 100644 functions/src/email/partials/ballotQuestions/ballotQuestion.handlebars create mode 100644 functions/src/email/partials/ballotQuestions/ballotQuestions.handlebars create mode 100644 functions/src/notifications/deliverNotifications.test.ts create mode 100644 functions/src/notifications/populateBallotQuestionNotificationEvents.test.ts create mode 100644 functions/src/notifications/populateBallotQuestionNotificationEvents.ts create mode 100644 functions/src/testimony/updateTestimonyCounts.test.ts create mode 100644 pages/_document.tsx create mode 100644 pages/ballotQuestions/[id].tsx create mode 100644 pages/ballotQuestions/index.tsx create mode 100644 scripts/README.md create mode 100644 scripts/firebase-admin/backfillBallotQuestionTestimonyCounts.ts create mode 100644 scripts/firebase-admin/backfillTestimonyBallotQuestionId.ts create mode 100644 scripts/firebase-admin/syncBallotQuestions.ts create mode 100644 tests/fixtures/ballotQuestions/bq-test-fixture.yaml create mode 100644 tests/integration/backfillTestimonyBallotQuestionId.test.ts create mode 100644 tests/integration/ballotQuestionFollowing.test.ts create mode 100644 tests/integration/ballotQuestions.test.ts diff --git a/.github/workflows/repo-checks.yml b/.github/workflows/repo-checks.yml index 07445efc0..85786ce0e 100644 --- a/.github/workflows/repo-checks.yml +++ b/.github/workflows/repo-checks.yml @@ -67,6 +67,13 @@ jobs: with: path: /home/runner/.cache/firebase/emulators key: ${{ runner.os }}-firebase-emulators-${{ hashFiles('~/.cache/firebase/emulators/**') }} + - name: Smoke Test Firebase Admin CLI + run: > + ./node_modules/.bin/firebase --project demo-dtp emulators:exec + --only auth,firestore + --import tests/integration/exportedTestData + "yarn firebase-admin run-script backfillTestimonyBallotQuestionId --env local" + - name: Run Integration Tests run: > yarn test:integration-ci @@ -74,3 +81,5 @@ jobs: tests/integration/auth.test.ts tests/integration/moderation.test.ts tests/integration/profile.test.ts + tests/integration/ballotQuestions.test.ts + tests/integration/backfillTestimonyBallotQuestionId.test.ts diff --git a/ballotQuestions/2024/23-12.yaml b/ballotQuestions/2024/23-12.yaml new file mode 100644 index 000000000..e7163e81a --- /dev/null +++ b/ballotQuestions/2024/23-12.yaml @@ -0,0 +1,31 @@ +# ballotQuestions/23-12.yaml +id: "23-12" +billId: "H4254" +title: "Minimum Wage for Tipped Workers" +court: 193 +electionYear: 2024 +type: initiative_statute +ballotStatus: rejected +ballotQuestionNumber: 5 +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would gradually increase the minimum hourly wage an + employer must pay a tipped worker, over the course of five years, on the following + schedule: - To 64% of the state minimum wage on January 1, 2025; - To 73% of + the state minimum wage on January 1, 2026; - To 82% of the state minimum wage + on January 1, 2027; - To 91% of the state minimum wage on January 1, 2028; and + - To 100% of the state minimum wage on January 1, 2029. The proposed law + would require employers to continue to pay tipped workers the difference between + the state minimum wage and the total amount a tipped worker receives in hourly + wages plus tips through the end of 2028. The proposed law would also permit + employers to calculate this difference over the entire weekly or bi-weekly payroll + period. The requirement to pay this difference would cease when the required + hourly wage for tipped workers would become 100% of the state minimum wage + on January 1, 2029. Under the proposed law, if an employer pays its workers an + hourly wage that is at least the state minimum wage, the employer would be + permitted to administer a “tip pool” that combines all the tips given by customers to + tipped workers and distributes them among all the workers, including non-tipped + workers. +pdfUrl: "https://malegislature.gov/Bills/193/H4254.pdf" diff --git a/ballotQuestions/2024/23-13.yaml b/ballotQuestions/2024/23-13.yaml new file mode 100644 index 000000000..e395fabe3 --- /dev/null +++ b/ballotQuestions/2024/23-13.yaml @@ -0,0 +1,66 @@ +# ballotQuestions/23-13.yaml +id: "23-13" +billId: "H4255" +title: "Limited Legalization and Regulation of Certain Natural Psychedelic Substances" +court: 193 +electionYear: 2024 +type: initiative_statute +ballotStatus: rejected +ballotQuestionNumber: 4 +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would allow persons aged 21 and older to grow, possess, and + use certain natural psychedelic substances in certain circumstances. The psychedelic + substances allowed would be two substances found in mushrooms (psilocybin and + psilocyn) and three substances found in plants (dimethyltryptamine, mescaline, and + ibogaine). These substances could be purchased at an approved location for use under the + supervision of a licensed facilitator. This proposed law would otherwise prohibit any retail + sale of natural psychedelic substances. This proposed law would also provide for the + regulation and taxation of these psychedelic substances. + + This proposed law would license and regulate facilities offering supervised use of + these psychedelic substances and provide for the taxation of proceeds from those facilities’ + sales of psychedelic substances. It would also allow persons aged 21 and older to grow + these psychedelic substances in a 12-foot by 12-foot area at their home and use these + psychedelic substances at their home. This proposed law would authorize persons aged 21 + or older to possess up to one gram of psilocybin, one gram of psilocyn, one gram of + dimethyltryptamine, 18 grams of mescaline, and 30 grams of ibogaine (“personal use + amount”), in addition to whatever they might grow at their home, and to give away up to + the personal use amount to a person aged 21 or over. + + This proposed law would create a Natural Psychedelic Substances Commission of + five members appointed by the Governor, Attorney General, and Treasurer which would + administer the law governing the use and distribution of these psychedelic substances. The + Commission would adopt regulations governing licensing qualifications, security, + recordkeeping, education and training, health and safety requirements, testing, and age + verification. This proposed law would also create a Natural Psychedelic Substances + Advisory Board of 20 members appointed by the Governor, Attorney General, and + Treasurer which would study and make recommendations to the Commission on the + regulation and taxation of these psychedelic substances. + + This proposed law would allow cities and towns to reasonably restrict the time, + place, and manner of the operation of licensed facilities offering psychedelic substances, + but cities and towns could not ban those facilities or their provision of these substances. + The proceeds of sales of psychedelic substances at licensed facilities would be + subject to the state sales tax and an additional excise tax of 15 percent. In addition, a city or + town could impose a separate tax of up to two percent. Revenue received from the + additional state excise tax, license application fees, and civil penalties for violations of this + proposed law would be deposited in a Natural Psychedelic Substances Regulation Fund + and would be used, subject to appropriation, for administration of this proposed law. + Using the psychedelic substances as permitted by this proposed law could not be a + basis to deny a person medical care or public assistance, impose discipline by a + professional licensing board, or enter adverse orders in child custody cases absent clear + and convincing evidence that the activities created an unreasonable danger to the safety of + a minor child. + + This proposed law would not affect existing laws regarding the operation of motor + vehicles while under the influence, or the ability of employers to enforce workplace + policies restricting the consumption of these psychedelic substances by employees. This + proposed law would allow property owners to prohibit the use, display, growing, + processing, or sale of these psychedelic substances on their premises. State and local governments could continue to restrict the possession and use of these psychedelic + substances in public buildings or at schools. + + This proposed law would take effect on December 15, 2024 +pdfUrl: "https://malegislature.gov/Bills/193/H4255.pdf" diff --git a/ballotQuestions/2024/23-25.yaml b/ballotQuestions/2024/23-25.yaml new file mode 100644 index 000000000..994a67a51 --- /dev/null +++ b/ballotQuestions/2024/23-25.yaml @@ -0,0 +1,59 @@ +# ballotQuestions/23-25.yaml +id: "23-25" +billId: "H4253" +title: "Unionization for Transportation Network Drivers" +court: 193 +electionYear: 2024 +type: initiative_statute +ballotStatus: accepted +ballotQuestionNumber: 3 +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + The proposed law would provide Transportation Network Drivers (“Drivers”) with the + right to form unions (“Driver Organizations”) to collectively bargain with Transportation + Network Companies (“Companies”)-which are companies that use a digital network to connect + riders to drivers for pre-arranged transportation-to create negotiated recommendations + concerning wages, benefits and terms and conditions of work. Drivers would not be required to + engage in any union activities. Companies would be allowed to form multi-Company + associations to represent them when negotiating with Driver Organizations. The state would + supervise the labor activities permitted by the proposed law and would have responsibility for + approving or disapproving the negotiated recommendations. The proposed law would define + certain activities by a Company or a Driver Organization to be unfair work practices. The + proposed law would establish a hearing process for the state Employment Relations Board + (“Board”) to follow when a Company or Driver Organization is charged with an unfair work + practice. The proposed law would permit the Board to take action, including awarding + compensation to adversely affected Drivers, if it found that an unfair work practice had been + committed. The proposed law would provide for an appeal of a Board decision to the state + Appeals Court. + + This proposed law also would establish a procedure for determining which Drivers are + Active Drivers, meaning that they completed more than the median number of rides in the + previous six months. The proposed law would establish procedures for the Board to determine + that a Driver Organization has signed authorizations from at least five percent of Active Drivers, + entitling the Driver Organization to a list of Active Drivers; to designate a Driver Organization + as the exclusive bargaining representative for all Drivers based on signed authorizations from at + least twenty-five percent of Active Drivers; to resolve disputes over exclusive bargaining status, + including through elections; and to decertify a Driver Organization from exclusive bargaining + status. A Driver Organization that has been designated the exclusive bargaining representative + would have the exclusive right to represent the Drivers and to receive voluntary membership + dues deductions. Once the Board determined that a Driver Organization was the exclusive + bargaining representative for all Drivers, the Companies would be required to bargain with that + Driver Organization concerning wages, benefits and terms and conditions of work. Once the + Driver Organization and Companies reached agreement on wages, benefits, and the terms and + conditions of work, that agreement would be voted upon by all Drivers who has completed at + least 100 trips the previous quarter. If approved by a majority of votes cast, the recommendations + would be submitted to the state Secretary of Labor for approval and if approved, would be + effective for three years. The proposed law would establish procedures for the mediation and + arbitration if the Driver Organization and Companies failed to reach agreement within a certain + period of time. An arbitrator would consider factors set forth in the proposed law, including + whether the wages of Drivers would be enough so that Drivers would not need to rely upon any + public benefits. The proposed law also sets out procedures for the Secretary of Labor’s review + and approval of recommendations negotiated by a Driver Organization and the Companies and + for judicial review of the Secretary’s decision. The proposed law states that neither its + provisions, an agreement nor a determination by the Secretary would be able to lessen labor + standards established by other laws. If there were any conflict between the proposed law and existing Massachusetts labor relations law, the proposed law would prevail. The Board would + make rules and regulations as appropriate to effectuate the proposed law. The proposed law + states that, if any of its parts were declared invalid, the other parts would stay in effect. +pdfUrl: "https://malegislature.gov/Bills/193/H4253.pdf" diff --git a/ballotQuestions/2024/23-26.yaml b/ballotQuestions/2024/23-26.yaml new file mode 100644 index 000000000..2f11b924e --- /dev/null +++ b/ballotQuestions/2024/23-26.yaml @@ -0,0 +1,22 @@ +# ballotQuestions/23-26.yaml +id: "23-26" +billId: "H4252" +title: "Elimination of MCAS as High School Graduation Requirement" +court: 193 +electionYear: 2024 +type: initiative_statute +ballotStatus: accepted +ballotQuestionNumber: 2 +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would eliminate the requirement that a student pass the Massachusetts + Comprehensive Assessment System (MCAS) tests (or other statewide or district-wide + assessments) in mathematics, science and technology, and English in order to receive a high + school diploma. Instead, in order for a student to receive a high school diploma, the proposed + law would require the student to complete coursework certified by the student’s district as + demonstrating mastery of the competencies contained in the state academic standards in + mathematics, science and technology, and English, as well as any additional areas determined by + the Board of Elementary and Secondary Education. +pdfUrl: "https://malegislature.gov/Bills/193/H4252.pdf" diff --git a/ballotQuestions/2024/23-34.yaml b/ballotQuestions/2024/23-34.yaml new file mode 100644 index 000000000..b44f46fb7 --- /dev/null +++ b/ballotQuestions/2024/23-34.yaml @@ -0,0 +1,14 @@ +# ballotQuestions/23-34.yaml +id: "23-34" +billId: "H4251" +title: "State Auditor’s Authority to Audit the Legislature" +court: 193 +electionYear: 2024 +type: initiative_statute +ballotStatus: accepted +ballotQuestionNumber: 1 +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: "This proposed law would specify that the State Auditor has the authority to audit the Legislature." +pdfUrl: "https://malegislature.gov/Bills/193/H4251.pdf" diff --git a/ballotQuestions/2026/25-03.yaml b/ballotQuestions/2026/25-03.yaml new file mode 100644 index 000000000..ac49665e7 --- /dev/null +++ b/ballotQuestions/2026/25-03.yaml @@ -0,0 +1,24 @@ +# ballotQuestions/25-03.yaml +id: "25-03" +billId: "H5000" +title: "To allow single-family homes on small lots in areas with adequate infrastructure" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would allow single-family homes to be built in a residentially zoned + area as long as the land on which it is to be constructed is at least 5,000 square feet, has at least + 50 feet of land bordering the street, road, or public way that it faces, and has access to public + sewer and water services. + + The proposed law would allow cities and towns to reasonably regulate certain aspects of + those single-family homes, including their height, distance from neighboring buildings, open + space, parking requirements, and whether they can be rented out on a short-term basis. The + proposed law would also allow the Executive Office of Housing and Livable Communities to + issue guidance or regulations to administer the proposed law. +pdfUrl: "https://malegislature.gov/Bills/194/H5000.pdf" diff --git a/ballotQuestions/2026/25-08.yaml b/ballotQuestions/2026/25-08.yaml new file mode 100644 index 000000000..cf8f4cba6 --- /dev/null +++ b/ballotQuestions/2026/25-08.yaml @@ -0,0 +1,42 @@ +# ballotQuestions/25-08.yaml +id: "25-08" +billId: "H5001" +title: "To election day registration" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would permit eligible individuals to register to vote or update their + voter registration address on Election Day. + + An individual who is eligible to vote could register to vote on Election Day by going to + the polling place in the precinct where they live during voting hours and presenting proof of + residency and signing a written oath. Proof of residency could be a valid photo identification, or + documentation showing the individual’s name and the address where the individual resides, such + as a current utility bill, bank statement, government check, residential lease, wireless telephone + statement, paycheck, current student fee statement or other document from a post-secondary + school, or another government document or correspondence. The written oath would require the + individual to certify that they are a citizen of the United States, are at least 18 years old, are not + legally prohibited from voting, and have not and will not vote in the same election at another + location. The oath would require the individual to acknowledge that providing false information + is a felony punishable by not more than 5 years imprisonment or a fine of not more than $10,000, + or both. + + If an individual did not present proof of residency, they would be allowed to cast a + provisional ballot, which would be counted only if the individual returned to provide the required + information before the close of polls for a municipal election; within two days after a state + primary; or within six days after a state election. + + Individuals who register to vote on Election Day would be registered to vote in future + elections as well as in the election taking place that day. + + Individuals who are already registered to vote would not be able to change their political + party affiliation on Election Day. + + The proposed law would take effect on January 1, 2028. +pdfUrl: "https://malegislature.gov/Bills/194/H5001.pdf" diff --git a/ballotQuestions/2026/25-10.yaml b/ballotQuestions/2026/25-10.yaml new file mode 100644 index 000000000..8e6c59998 --- /dev/null +++ b/ballotQuestions/2026/25-10.yaml @@ -0,0 +1,38 @@ +# ballotQuestions/25-10.yaml +id: "25-10" +billId: "H5002" +title: "To restore a sensible marijuana policy" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + The proposed law would change the type and amount of marijuana that may legally be + possessed in Massachusetts by repealing the laws that legalize, regulate, and tax the retail sale of + adult recreational use marijuana in Massachusetts. The proposed law would also permit persons + 21 years of age and older to possess 1 ounce or less of marijuana including no more than 5 grams + in the form of concentrate, and to gift or transfer to another person 21 years of age and older 1 + ounce or less of marijuana including no more than 5 grams in the form of concentrate. The + proposed law would also impose a civil penalty of $100 and forfeiture of the marijuana for the + possession of marijuana between the weight of 1 and 2 ounces. + + For persons 21 years of age and younger, the proposed law would make the possession of + 2 ounces or less of marijuana a civil infraction subject to a $100 fine, forfeiture of the marijuana, + completion of a drug awareness program and community service, and notification to their parents + or legal guardian of the offense and penalties. + + The proposed law would allow currently licensed adult recreational marijuana businesses to apply + on an expedited basis to become a licensed medical marijuana dispensary and to sell their remaining + inventory of adult recreational marijuana to medical marijuana dispensaries. The proposed law would + retain the Cannabis Control Commission but modify its authority so it would regulate only the medical + marijuana market. + + The proposed law states that, if any of its parts were declared invalid, the other parts would stay + in effect. + + The proposed law would take effect on January 1, 2028. +pdfUrl: "https://malegislature.gov/Bills/194/H5002.pdf" diff --git a/ballotQuestions/2026/25-12.yaml b/ballotQuestions/2026/25-12.yaml new file mode 100644 index 000000000..1453443ef --- /dev/null +++ b/ballotQuestions/2026/25-12.yaml @@ -0,0 +1,25 @@ +# ballotQuestions/25-12.yaml +id: "25-12" +billId: "H5003" +title: "To implement all-party state primaries" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would eliminate political party primaries for state elections and + instead establish a system where there would be a single, all-party primary in which all + candidates, regardless of their party affiliation, would be listed on one ballot, and voters could + vote for any candidate on the ballot. The two candidates receiving the most votes in the primary + would advance to the general election ballot. + + This proposed law would require candidates for governor and lieutenant governor to run + and be listed jointly on the ballot in the primary. + + This proposed law would provide political party status to any group whose candidates for + any statewide office received at least 3% of the ballots cast in the state primary. +pdfUrl: "https://malegislature.gov/Bills/194/H5003.pdf" diff --git a/ballotQuestions/2026/25-14.yaml b/ballotQuestions/2026/25-14.yaml new file mode 100644 index 000000000..e22a3b2b6 --- /dev/null +++ b/ballotQuestions/2026/25-14.yaml @@ -0,0 +1,20 @@ +# ballotQuestions/25-14.yaml +id: "25-14" +billId: "H5004" +title: "To improve access to public records" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would make most records held by the Legislature and the Office of the + Governor public records under the Massachusetts Public Records Law. This proposed law would + exempt documents related to the development of public policy and communications between + legislators and their constituents, if those communications are reasonably related to a + constituent’s request for assistance in obtaining government-provided benefits or services or + interacting with a government agency. +pdfUrl: "https://malegislature.gov/Bills/194/H5004.pdf" diff --git a/ballotQuestions/2026/25-15.yaml b/ballotQuestions/2026/25-15.yaml new file mode 100644 index 000000000..4ae8eb31c --- /dev/null +++ b/ballotQuestions/2026/25-15.yaml @@ -0,0 +1,53 @@ +# ballotQuestions/25-15.yaml +id: "25-15" +billId: "H5005" +title: "To protect water and nature" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would establish a Nature for All Fund that, subject to appropriation by + the Legislature, would receive 50% of state taxes collected from the sale and use of sporting + goods, recreational vehicles, and golf courses for the first year of its operation. After July 1, + 2028, the Nature for All Fund would begin receiving, subject to appropriation by the Legislature, + 100% of state taxes collected on the sale and use of sporting goods, recreational vehicles, and + golf courses. The sales tax revenue received by the Nature for All Fund would exclude sales tax + revenue transferred to the Massachusetts Bay Transportation Authority State and Local + Contribution Fund and the School Modernization and Reconstruction Trust Fund. The proposed + law would allow the state Executive Office of Energy and Environmental Affairs to spend the + money in the Nature for All Fund for natural resource conservation. + + The proposed law would allow public and private donations to the Nature for All Fund. + The proposed law would prevent the state comptroller from transferring surplus funds in the + Nature for All Fund at the end of the fiscal year. It would also allow state agencies, + municipalities, public charities involved in natural resource conservation, tribal governments, + and other regional public entities to receive money from the Nature for All Fund. + Natural resource conservation would include the conservation or restoration of land to + protect drinking water, streams, rivers, lakes, coasts, farms, forests, connectivity between open + spaces, and lands and natural resources of indigenous cultural significance. Natural resource + conservation would also include the creation, improvement, and management of parks, trails, + greenspaces or outdoor recreation access. + + The proposed law would establish a 15-member Nature for All Board that consists of five + state officials and ten members of the public appointed by the Governor. The proposed law + would require the ten members of the public to include representatives of underserved + communities and indigenous peoples and at least one person with expertise or experience in + natural resource conservation. The proposed law would allow the state Executive Office of + Energy and Environmental Affairs to spend money from the Nature for All Fund to hire staff to + manage the fund. The proposed law would also require the Nature for All Board to establish + rules about how the money in the Nature for All Fund should be spent, including rules regarding + alignment with environmental justice principles, access to and restoration of lands and natural + resources of indigenous cultural significance, promotion of affordable housing development, and + other matters regarding spending and bond issuance. + + The proposed law would require the state Executive Office of Energy and Environmental + Affairs to submit an annual report to various state committees regarding the funds spent to buy or + improve land in cities and towns containing environmental justice populations. + + The proposed law would take effect on July 1, 2027. +pdfUrl: "https://malegislature.gov/Bills/194/H5005.pdf" diff --git a/ballotQuestions/2026/25-17.yaml b/ballotQuestions/2026/25-17.yaml new file mode 100644 index 000000000..c441e9e3b --- /dev/null +++ b/ballotQuestions/2026/25-17.yaml @@ -0,0 +1,27 @@ +# ballotQuestions/25-17.yaml +id: "25-17" +billId: "H5006" +title: "To limiting state tax collection growth and returning surpluses to taxpayers" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would change the limit on how much revenue the state can collect in a + given year. The proposal would limit state revenue in a given year to the net amount of state + revenue from the year before, increased by a rate equal to the average growth of wages and + salaries in Massachusetts over the most recent three years. If revenue collected by the state in a + given year exceeds the limit, the excess amount would be refunded to taxpayers the following + year. The proposed law would include all revenue from the surtax on incomes over $1 million + when calculating the revenue limit and when determining whether state revenue exceeds the + limit. + + The provisions of the proposed law would all be effective as of July 1, 2027. + + The proposed law states that, if any of its parts were declared invalid, the other parts + would stay in effect. +pdfUrl: "https://malegislature.gov/Bills/194/H5006.pdf" diff --git a/ballotQuestions/2026/25-18.yaml b/ballotQuestions/2026/25-18.yaml new file mode 100644 index 000000000..7bb26ea8f --- /dev/null +++ b/ballotQuestions/2026/25-18.yaml @@ -0,0 +1,22 @@ +# ballotQuestions/25-18.yaml +id: "25-18" +billId: "H5007" +title: "To reducing the state personal income tax rate from 5% to 4%" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would, over a period of three years, lower the tax rates on (1) personal + taxable income consisting of interest and dividends, and (2) personal taxable income other than + interest, dividends or capital gain income, such as wages and salaries. Both tax rates were 5.00% + for tax year 2024. The proposed law would set both tax rates at 4.67% for tax year 2027, 4.33% + for tax year 2028, and 4.00% beginning in tax year 2029. + + The proposed law states that, if any of its parts were declared invalid, the other parts + would stay in effect. +pdfUrl: "https://malegislature.gov/Bills/194/H5007.pdf" diff --git a/ballotQuestions/2026/25-21.yaml b/ballotQuestions/2026/25-21.yaml new file mode 100644 index 000000000..dcc6ff5c2 --- /dev/null +++ b/ballotQuestions/2026/25-21.yaml @@ -0,0 +1,23 @@ +# ballotQuestions/25-21.yaml +id: "25-21" +billId: "H5008" +title: "To protect tenants by limiting rent increases" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would limit the annual rent increase for residential units in + Massachusetts to the annual increase in the Consumer Price Index for a 12-month period, or 5%, + whichever is lower. The law would not apply to units in owner-occupied buildings with four or + fewer units; units that are subject to regulation by a public authority; units rented to transient + guests for periods of less than 14 days; units operated for educational, religious, or non-profit + purposes; and units that received their residential certificate of occupancy within the last 10 + years. The rent in place for a unit as of January 31, 2026, would serve as the base rent for the + annual rent increase limit. A violation of this law would be a violation of the state consumer + protection law. +pdfUrl: "https://malegislature.gov/Bills/194/H5008.pdf" diff --git a/ballotQuestions/2026/25-22.yaml b/ballotQuestions/2026/25-22.yaml new file mode 100644 index 000000000..a69b13e9d --- /dev/null +++ b/ballotQuestions/2026/25-22.yaml @@ -0,0 +1,18 @@ +# ballotQuestions/25-22.yaml +id: "25-22" +billId: "H5009" +title: "To labor relations policies for committee for public counsel services employees" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would specify that employees of the Committee for Public Counsel + Services (“CPCS”) are permitted to engage in collective bargaining with their employer. It would + also require CPCS, after executing a collective bargaining agreement, to request the + appropriation necessary to fund such agreement from the Governor. +pdfUrl: "https://malegislature.gov/Bills/194/H5009.pdf" diff --git a/ballotQuestions/2026/25-37.yaml b/ballotQuestions/2026/25-37.yaml new file mode 100644 index 000000000..5abb2c8fd --- /dev/null +++ b/ballotQuestions/2026/25-37.yaml @@ -0,0 +1,58 @@ +# ballotQuestions/25-37.yaml +id: "25-37" +billId: "H5010" +title: "To reform and regulate legislative stipends" +court: 194 +electionYear: 2026 +type: initiative_statute +ballotStatus: expectedOnBallot +ballotQuestionNumber: null +relatedBillIds: [] +description: null +atAGlance: null +fullSummary: |- + This proposed law would change the method for calculating stipends paid to certain state + legislators on top of their base salaries. + + Under the proposed law, legislators would receive stipends, subject to appropriation, + based on their leadership positions and/or committee membership. The Senate President and + Speaker of the House (Group 1) would receive a stipend of up to 75% of their base salaries. The + floor leaders of the two largest parties in each house of the legislature and the chairs of each + house’s ways and means committee (Group 2) would receive a stipend of up to 50% of their base + salaries. The assistant and second assistant floor leaders of the two largest parties in each house, + the third assistant floor leaders of the minority party in each house, and the vice chairs and + ranking minority members of each house’s ways and means committee (Group 3) and the chairs + of eligible committees (Group 4) would receive a stipend of up to 33% of their base salaries. + Legislators who are not in Groups 1-4 who are members of an eligible committee would receive + a stipend of up to 20% of their base salaries. A committee would be “eligible” under the + proposed law if it was established by the joint rules of the House and Senate and had more than + 50 bills referred to it before March 1 of the first year of the legislative session. + + The proposed law would provide a further 20% stipend to three categories of senators: (1) + any senator in Group 2 or Group 3 who is a member of one or more eligible committees, (2) any + senator in Group 4 who is a member of more than one eligible committee, or (3) any senator not + in Groups 1-4 who is a member of more than four eligible committees. + + Under the proposed law, no senator could receive a stipend for more than two positions, + and no representative could receive a stipend for more than one position. + + This proposed law would also establish various terms and conditions for the payment of + legislative compensation. A Group 4 leader would receive 50% of the leader’s stipend in + biweekly paychecks; the leader would receive the other 50% in the last paycheck of the year if + the leader’s eligible committee had achieved compliance that year. Under the proposed law, + “compliance” would mean that, on or before the first Monday in December (in the first year of + the legislative session) or on or before the last Friday in May (in the second year of the session), + an eligible committee had (1) held a public hearing and public mark-up session on each bill + referred to it before a specified cutoff date and (2) approved all of its reports by a majority vote + at a public meeting with a quorum present. A Group 1-3 leader would receive 50% of the + leader’s stipend in biweekly paychecks; the leader would receive the other 50%, multiplied by + the percentage of eligible committees achieving compliance in that year, in the last paycheck of + the year. The proposed law would require the House and Senate clerks to jointly certify + compliance and to calculate the compliance percentage each legislative year. Except as otherwise + provided, legislators would receive their compensation on a biweekly basis. + + Legislators who served in a qualifying position for less than the full biennial session + would receive prorated stipends. + + The proposed law would take effect on January 6, 2027. +pdfUrl: "https://malegislature.gov/Bills/194/H5010.pdf" diff --git a/components/CommentModal/Attachment.tsx b/components/CommentModal/Attachment.tsx index f65d4905c..1593ccaed 100644 --- a/components/CommentModal/Attachment.tsx +++ b/components/CommentModal/Attachment.tsx @@ -9,11 +9,13 @@ import { useTranslation } from "next-i18next" export function Attachment({ attachment, className, - confirmRemove = false + confirmRemove = false, + label }: { attachment: UseDraftTestimonyAttachment className?: string confirmRemove?: boolean + label?: string }) { const { upload, error, id } = attachment const [key, setKey] = useState(0), @@ -35,7 +37,7 @@ export function Attachment({ return ( -