Files
PocketVeto/frontend/app/how-it-works/page.tsx
Jack Levy 4c86a5b9ca feat: PocketVeto v1.0.0 — initial public release
Self-hosted US Congress monitoring platform with AI policy briefs,
bill/member/topic follows, ntfy + RSS + email notifications,
alignment scoring, collections, and draft-letter generator.

Authored by: Jack Levy
2026-03-15 01:35:01 -04:00

354 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import {
BarChart2,
Bell,
Bookmark,
Calendar,
Clock,
FileText,
Filter,
Heart,
HelpCircle,
ListChecks,
Mail,
MessageSquare,
Rss,
Shield,
Share2,
StickyNote,
TrendingUp,
Users,
Zap,
} from "lucide-react";
function Section({ id, title, icon: Icon, children }: {
id: string;
title: string;
icon: React.ElementType;
children: React.ReactNode;
}) {
return (
<section id={id} className="bg-card border border-border rounded-lg p-6 space-y-4 scroll-mt-6">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Icon className="w-5 h-5 text-primary" />
{title}
</h2>
{children}
</section>
);
}
function Item({ icon: Icon, color, title, children }: {
icon: React.ElementType;
color: string;
title: string;
children: React.ReactNode;
}) {
return (
<div className="flex gap-3">
<div className={`mt-0.5 shrink-0 w-7 h-7 rounded-full flex items-center justify-center ${color}`}>
<Icon className="w-3.5 h-3.5" />
</div>
<div>
<p className="text-sm font-medium">{title}</p>
<p className="text-xs text-muted-foreground mt-0.5 leading-relaxed">{children}</p>
</div>
</div>
);
}
export default function HowItWorksPage() {
return (
<div className="max-w-2xl mx-auto space-y-6">
<div>
<h1 className="text-2xl font-bold flex items-center gap-2">
<HelpCircle className="w-5 h-5" /> How it works
</h1>
<p className="text-muted-foreground text-sm mt-1">
A quick guide to PocketVeto&apos;s features.
</p>
{/* Jump links */}
<div className="flex flex-wrap gap-2 mt-3">
{[
{ href: "#follow", label: "Following" },
{ href: "#collections", label: "Collections" },
{ href: "#notifications", label: "Notifications" },
{ href: "#briefs", label: "AI Briefs" },
{ href: "#votes", label: "Votes" },
{ href: "#alignment", label: "Alignment" },
{ href: "#notes", label: "Notes" },
{ href: "#bills", label: "Bills" },
{ href: "#members-topics", label: "Members & Topics" },
{ href: "#dashboard", label: "Dashboard" },
].map(({ href, label }) => (
<a
key={href}
href={href}
className="text-xs px-2.5 py-1 bg-muted rounded-full hover:bg-accent transition-colors"
>
{label}
</a>
))}
</div>
</div>
{/* Following */}
<Section id="follow" title="Following bills" icon={Heart}>
<p className="text-sm text-muted-foreground">
Follow any bill to track it. PocketVeto checks for changes and notifies you through your
configured channels. Three modes let you tune the signal to your interest level each
with its own independent set of alert filters.
</p>
<div className="space-y-3">
<Item icon={Heart} color="bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400" title="Follow">
The standard mode. Default alerts: new bill text, amendments filed, chamber votes,
presidential action, and committee reports.
</Item>
<Item icon={Shield} color="bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400" title="Pocket Veto">
For bills you oppose and only want to hear about if they gain real traction. Default
alerts: chamber votes and presidential action only no noise from early committee or
document activity.
</Item>
<Item icon={Zap} color="bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" title="Pocket Boost">
For bills you actively support. Default alerts: everything new text, amendments,
votes, presidential action, committee reports, calendar placement, procedural moves,
and committee referrals. Also adds &ldquo;Find Your Rep&rdquo; action buttons to push
notifications.
</Item>
</div>
<div className="rounded-md bg-muted/60 px-4 py-3 space-y-1.5">
<p className="text-xs font-medium flex items-center gap-1.5">
<Filter className="w-3.5 h-3.5" /> Adjusting alert filters
</p>
<p className="text-xs text-muted-foreground leading-relaxed">
The defaults above are starting points. In{" "}
<Link href="/notifications" className="text-primary hover:underline">Notifications Alert Filters</Link>,
each mode has its own tab with eight independently toggleable alert types. For example,
a Follow bill where you don&apos;t care about committee reports uncheck it and only
that mode is affected. Hit <strong>Load defaults</strong> on any tab to revert to the
preset above.
</p>
</div>
<p className="text-xs text-muted-foreground">
You can also follow <strong>members</strong> and <strong>topics</strong>.
When a followed member sponsors a bill, or a new bill matches a followed topic, you&apos;ll
receive a <em>Discovery</em> alert. These have their own independent filter set in{" "}
<Link href="/notifications" className="text-primary hover:underline">Notifications Alert Filters Discovery</Link>.
By default, all followed members and topics trigger notifications you can mute individual
ones without unfollowing them.
</p>
</Section>
{/* Collections */}
<Section id="collections" title="Collections" icon={Bookmark}>
<p className="text-sm text-muted-foreground">
A collection is a named, curated group of bills like a playlist for legislation. Use
collections to track a policy area, build a watchlist for an advocacy campaign, or share
research with colleagues.
</p>
<div className="space-y-3">
<Item icon={Bookmark} color="bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400" title="Creating a collection">
Give it a name (e.g. &ldquo;Healthcare Watch&rdquo;) and add bills from any bill detail
page using the bookmark icon next to the Follow button.
</Item>
<Item icon={Share2} color="bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-400" title="Sharing">
Every collection has a unique share link. Anyone with the link can view the collection
no account required.
</Item>
</div>
<p className="text-xs text-muted-foreground">
<strong>Public vs. private:</strong> Both have share links. Marking a collection public
signals it may appear in a future public directory; private collections are invisible to
anyone without your link.
</p>
</Section>
{/* Notifications */}
<Section id="notifications" title="Notifications" icon={Bell}>
<p className="text-sm text-muted-foreground">
PocketVeto delivers alerts through three independent channels use any combination.
</p>
<div className="space-y-3">
<Item icon={Bell} color="bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400" title="Push via ntfy">
<a href="https://ntfy.sh" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">ntfy</a>
{" "}is a free, open-source push notification service. Configure a topic URL in{" "}
<Link href="/notifications" className="text-primary hover:underline">Notifications</Link>{" "}
and receive real-time alerts on any device with the ntfy app.
</Item>
<Item icon={Mail} color="bg-indigo-100 text-indigo-600 dark:bg-indigo-900/30 dark:text-indigo-400" title="Email">
Receive alerts as plain-text emails. Add your address in{" "}
<Link href="/notifications" className="text-primary hover:underline">Notifications Email</Link>.
Every email includes a one-click unsubscribe link, and your address is never used for
anything other than bill alerts.
</Item>
<Item icon={Rss} color="bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400" title="RSS feed">
A private, tokenized RSS feed of all your bill alerts. Subscribe in any RSS reader
(Feedly, NetNewsWire, etc.). Always real-time, completely independent of the other channels.
</Item>
<Item icon={Clock} color="bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400" title="Quiet hours">
Pause push and email notifications during set hours (e.g. 10 PM 8 AM). Events that
arrive during quiet hours are queued and sent as a batch when the window ends.
</Item>
<Item icon={Calendar} color="bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" title="Digest mode">
Instead of one alert per event, receive a single bundled summary on a daily or weekly
schedule. Your RSS feed is always real-time regardless of this setting.
</Item>
<Item icon={MessageSquare} color="bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-400" title="Discovery alerts">
Member and topic follows generate Discovery alerts separate from the bills you follow
directly. In{" "}
<Link href="/notifications" className="text-primary hover:underline">Alert Filters Discovery</Link>,
you can enable or disable these independently, tune which event types trigger them, and
mute specific members or topics without unfollowing them. Each notification includes a
&ldquo;why&rdquo; line so you always know which follow triggered it.
</Item>
</div>
</Section>
{/* AI Briefs */}
<Section id="briefs" title="AI Briefs" icon={FileText}>
<p className="text-sm text-muted-foreground">
For bills with published official text, PocketVeto generates a plain-English AI brief
automatically no action needed on your part.
</p>
<div className="space-y-3">
<Item icon={FileText} color="bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400" title="What's in a brief">
A plain-English summary, key policy points with references to specific bill sections
(§ chips you can expand to see the quoted source text), and a risks section that flags
potential unintended consequences or contested provisions.
</Item>
<Item icon={Share2} color="bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-400" title="Sharing a brief">
Click the share icon in the brief panel to copy a public link. Anyone can read the
brief at that URL no login required.
</Item>
<Item icon={Zap} color="bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" title="Draft a letter">
Use the Draft Letter panel in the Analysis tab to generate a personalised letter to
your representative based on the brief&apos;s key points.
</Item>
</div>
<p className="text-xs text-muted-foreground">
Briefs are only generated for bills where GovInfo has published official text. Bills
without text show a &ldquo;No text&rdquo; badge on their card. When a bill is amended,
a new &ldquo;What Changed&rdquo; brief is generated automatically alongside the original.
</p>
</Section>
{/* Votes */}
<Section id="votes" title="Roll-call votes" icon={ListChecks}>
<p className="text-sm text-muted-foreground">
The <strong>Votes</strong> tab on any bill page shows every recorded roll-call vote for
that bill, fetched directly from official House and Senate XML sources.
</p>
<div className="space-y-3">
<Item icon={ListChecks} color="bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400" title="Vote breakdown">
Each vote shows the result, chamber, roll number, date, and a visual Yea/Nay bar with
exact counts.
</Item>
<Item icon={Users} color="bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-400" title="Followed member positions">
If any of your followed members voted on the bill, their individual Yea/Nay positions
are surfaced directly in the vote row no need to dig through the full member list.
</Item>
</div>
</Section>
{/* Alignment */}
<Section id="alignment" title="Representation Alignment" icon={BarChart2}>
<p className="text-sm text-muted-foreground">
The <Link href="/alignment" className="text-primary hover:underline">Alignment</Link> page
shows how often your followed members vote in line with your stated bill positions.
</p>
<div className="space-y-3">
<Item icon={Zap} color="bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" title="How it's calculated">
For every bill you follow with Pocket Boost or Pocket Veto, PocketVeto checks how each
of your followed members voted. A Yea on a boosted bill counts as aligned; a Nay on a
vetoed bill counts as aligned. Not Voting and Present are excluded.
</Item>
<Item icon={BarChart2} color="bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400" title="Alignment score">
Each followed member gets an alignment percentage based on all overlapping votes. Members
are ranked from most to least aligned with your positions.
</Item>
</div>
<p className="text-xs text-muted-foreground">
Alignment only appears for members who have actually voted on bills you&apos;ve stanced.
Follow more members and stake positions on more bills to build a fuller picture.
</p>
</Section>
{/* Notes */}
<Section id="notes" title="Notes" icon={StickyNote}>
<p className="text-sm text-muted-foreground">
Add a personal note to any bill visible only to you. Find it in the{" "}
<strong>Notes</strong> tab on any bill detail page.
</p>
<div className="space-y-3">
<Item icon={StickyNote} color="bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" title="Pinning">
Pin a note to float it above the tab bar so it&apos;s always visible when you open the
bill, regardless of which tab you&apos;re on.
</Item>
</div>
</Section>
{/* Bills */}
<Section id="bills" title="Browsing bills" icon={FileText}>
<p className="text-sm text-muted-foreground">
The <Link href="/bills" className="text-primary hover:underline">Bills</Link> page lists
all tracked legislation. Use the filters to narrow your search.
</p>
<div className="space-y-2 text-xs text-muted-foreground">
<p><strong className="text-foreground">Search</strong> matches bill ID, title, and short title.</p>
<p><strong className="text-foreground">Chamber</strong> House or Senate.</p>
<p><strong className="text-foreground">Topic</strong> AI-tagged policy area (healthcare, defense, etc.).</p>
<p><strong className="text-foreground">Has text</strong> show only bills with published official text. On by default.</p>
</div>
<p className="text-xs text-muted-foreground">
Each bill page is organised into four tabs: <strong>Analysis</strong> (AI brief + draft
letter), <strong>Timeline</strong> (action history), <strong>Votes</strong> (roll-call
records), and <strong>Notes</strong> (your personal note).
Topic tags appear just below the tab bar click any tag to jump to that filtered view.
</p>
</Section>
{/* Members & Topics */}
<Section id="members-topics" title="Members & Topics" icon={Users}>
<p className="text-sm text-muted-foreground">
Browse and follow legislators and policy topics independently of specific bills.
</p>
<div className="space-y-3">
<Item icon={Users} color="bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400" title="Members">
The <Link href="/members" className="text-primary hover:underline">Members</Link> page
lists all current members of Congress. Each member page shows their sponsored bills,
news coverage, voting trend, and once enough votes are recorded
an <strong>effectiveness score</strong> ranking how often their sponsored bills advance.
</Item>
<Item icon={Filter} color="bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-400" title="Topics">
The <Link href="/topics" className="text-primary hover:underline">Topics</Link> page
lists all AI-tagged policy areas. Following a topic sends you a Discovery alert whenever
a new bill is tagged with it useful for staying on top of a policy area without
tracking individual bills.
</Item>
</div>
</Section>
{/* Dashboard */}
<Section id="dashboard" title="Dashboard" icon={TrendingUp}>
<p className="text-sm text-muted-foreground">
The <Link href="/" className="text-primary hover:underline">Dashboard</Link> is your
personalised home view, split into two areas.
</p>
<div className="space-y-3">
<Item icon={Heart} color="bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400" title="Your feed">
Bills from your follows directly followed bills, bills sponsored by followed members,
and bills matching followed topics sorted by latest activity.
</Item>
<Item icon={TrendingUp} color="bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" title="Trending">
The top 10 bills by composite trend score, calculated nightly from news article volume
(NewsAPI + Google News) and Google Trends interest. A bill climbing here is getting real
public attention regardless of whether you follow it.
</Item>
</div>
</Section>
</div>
);
}