| @@ -29,12 +29,13 @@ export default function BillsPage({ | ||
| const congressRaw = asString(searchParams.congress); | ||
| const congressNum = parseInt(congressRaw ?? "", 10); | ||
| - | const bills = getBills({ | |
| + | const allBills = getBills({ | |
| policyArea, | ||
| q, | ||
| congress: Number.isFinite(congressNum) ? congressNum : undefined, | ||
| sort: "updated", | ||
| }); | ||
| + | const bills = allBills.slice(0, 100); // render cap; full count in eyebrow | |
| const policyAreas = getPolicyAreaLabels(); | ||
| const congresses = getBillCongresses(); | ||
| @@ -130,7 +131,7 @@ export default function BillsPage({ | ||
| <div className="mt-8"> | ||
| <SectionHeader | ||
| - | eyebrow={`${bills.length} bill${bills.length === 1 ? "" : "s"}`} | |
| + | eyebrow={`${allBills.length} bill${allBills.length === 1 ? "" : "s"} · showing ${bills.length}`} | |
| title={policyArea ? policyArea : "On record"} | ||
| /> | ||
| {bills.length === 0 ? ( |
| @@ -33,8 +33,14 @@ const ACTION_LABEL: Record<FeedItem["kind"], string> = { | ||
| apportionment: "Apportionments", | ||
| }; | ||
| + | // Render cap: the full ranked feed can be tens of thousands of rows; render | |
| + | // the top slice (it's already rank-ordered) so the page stays fast. Counts/ | |
| + | // stats below still use the full set. | |
| + | const FEED_RENDER_LIMIT = 150; | |
| + | ||
| export default async function RegisterPage() { | ||
| const items = getFeedItems(); | ||
| + | const shown = items.slice(0, FEED_RENDER_LIMIT); | |
| const awards = getAwards(); | ||
| const changes = getChangeEvents(12); | ||
| const topTopics = getCategories() | ||
| @@ -45,7 +51,7 @@ export default async function RegisterPage() { | ||
| return ( | ||
| <> | ||
| - | <RegisterHero seed={items} /> | |
| + | <RegisterHero seed={shown.slice(0, 12)} /> | |
| <div className="mx-auto w-full max-w-register px-4 py-12 sm:px-6"> | ||
| <SectionHeader | ||
| @@ -80,7 +86,7 @@ export default async function RegisterPage() { | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| - | {items.map((r) => ( | |
| + | {shown.map((r) => ( | |
| <tr | ||
| key={r.id} | ||
| className="border-b border-line font-mono text-[12px] transition-colors last:border-0 hover:bg-graphite/60" |
| @@ -16,6 +16,7 @@ export const dynamic = "force-dynamic"; | ||
| export default function VotesPage() { | ||
| const votes = getVotes(); | ||
| + | const shown = votes.slice(0, 100); // render cap; full count shown in eyebrow | |
| return ( | ||
| <div className="mx-auto w-full max-w-register px-4 py-10 sm:px-6"> | ||
| @@ -54,7 +55,7 @@ export default function VotesPage() { | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| - | {votes.map((v) => { | |
| + | {shown.map((v) => { | |
| const variant = classifyStatus(v.result); | ||
| return ( | ||
| <tr |
| @@ -16,16 +16,24 @@ export const DB_PATH = | ||
| let _db: Database.Database | null = null; | ||
| - | /** Single shared read-only connection. */ | |
| + | /** | |
| + | * Single shared connection. Opened read-WRITE (not readonly) on purpose: | |
| + | * the DB is in WAL mode (so ingest can write while we read without blocking), | |
| + | * and a read-only SQLite handle cannot operate on a WAL database because WAL | |
| + | * requires writing the shared-memory index. This layer only ever issues | |
| + | * SELECTs, so it behaves as a WAL *reader* - it never writes data. A | |
| + | * busy_timeout means a read waits briefly for a checkpoint instead of the | |
| + | * synchronous driver hanging the Node event loop. | |
| + | */ | |
| function db(): Database.Database { | ||
| if (!_db) { | ||
| _db = new Database(DB_PATH, { | ||
| - | readonly: true, | |
| + | readonly: false, | |
| fileMustExist: false, | ||
| }); | ||
| - | // Small perf wins; we only read. | |
| try { | ||
| _db.pragma("journal_mode = WAL"); | ||
| + | _db.pragma("busy_timeout = 4000"); | |
| } catch { | ||
| /* ignore */ | ||
| } |
| @@ -1043,7 +1043,7 @@ export function getMember(rawId: string): MemberDetail | null { | ||
| JOIN votes v ON v.id = vp.vote_id | ||
| WHERE vp.member_id = :mid | ||
| ORDER BY v.date DESC, v.id DESC | ||
| - | LIMIT 5000`, | |
| + | LIMIT 1500`, | |
| { mid: summary.numericId }, | ||
| (v) => ({ | ||
| voteId: `vote:${v.id}`, |