docs: overhaul documentation + add deployment guides

- ARCHITECTURE.md: update for v0.9.0/v0.9.3 — collections schema,
  collection_bills schema, alert_filters in notification_prefs,
  action_category in notification payload, migrations 0015/0016,
  /api/collections + /api/share endpoints, updated pages table,
  pipeline flow reflects categorize_action(), v0.9.0 and v0.9.3
  feature history entries
- ROADMAP.md: new file merging "MVP threshold" and "Feature Roadmap"
  docs into one clean shipped/upcoming/backlog structure with v1.0
  definition; removes stale design notes and duplicate entries
- DEPLOYING.md: new — prerequisites, .env setup, first run, admin
  account, domain/SSL with Caddy, useful commands
- UPDATING.md: new — SSH setup, manual deploy, deploy script, Gitea
  webhook + webhook listener, rollback procedure, env-only updates
- Delete: "MVP threshold this make v1 complete.md" and
  "PocketVeto — Feature Roadmap.md" (superseded by ROADMAP.md)
- how-it-works/page.tsx: accurate per-mode default alert sets,
  Alert Filters callout linking to Notifications settings
- notifications/page.tsx: Follow mode default includes amendment filed;
  Pocket Veto default excludes calendar placement

Authored-By: Jack Levy
This commit is contained in:
Jack Levy
2026-03-02 19:22:02 -05:00
parent a39ae4ccba
commit 676bf1b78d
8 changed files with 728 additions and 334 deletions

View File

@@ -312,7 +312,7 @@ News articles correlated to a specific member of Congress.
| email | varchar (unique) | |
| hashed_password | varchar | bcrypt |
| is_admin | bool | First registered user = true |
| notification_prefs | jsonb | ntfy topic URL, ntfy auth method/token/credentials, ntfy enabled, RSS token, quiet_hours_start/end (023 local), timezone (IANA name, e.g. `America/New_York`) |
| notification_prefs | jsonb | ntfy topic URL, ntfy auth method/token/credentials, ntfy enabled, RSS token, quiet_hours_start/end (023 local), timezone (IANA name, e.g. `America/New_York`), alert_filters (nested dict: `{neutral: {...}, pocket_veto: {...}, pocket_boost: {...}}` — 8 boolean keys per mode) |
| rss_token | varchar (nullable) | Unique token for personal RSS feed URL |
| created_at | timestamptz | |
@@ -418,11 +418,39 @@ Stores notification events for dispatching to user channels (ntfy, RSS).
| user_id | int (FK → users, CASCADE) | |
| bill_id | varchar (FK → bills, SET NULL) | nullable |
| event_type | varchar | `new_document`, `new_amendment`, `bill_updated`, `weekly_digest` |
| payload | jsonb | `{bill_title, bill_label, brief_summary, bill_url, milestone_tier}` |
| payload | jsonb | `{bill_title, bill_label, brief_summary, bill_url, action_category, milestone_tier}` |
| dispatched_at | timestamptz (nullable) | NULL = pending dispatch |
| created_at | timestamptz | |
`milestone_tier` in payload: `"progress"` (passed, signed, markup, conference, etc.) or `"referral"` (committee referral). Neutral follows silently skip referral-tier events; pocket_veto and pocket_boost receive them as early warnings.
`action_category` in payload (new events): one of `vote`, `presidential`, `committee_report`, `calendar`, `procedural`, `referral`. `milestone_tier` is retained for backwards compatibility (`"referral"` or `"progress"`). The dispatcher checks `notification_prefs.alert_filters[follow_mode][action_category]` to decide whether to send. `new_document` and `new_amendment` events are filtered by event type directly (not action_category).
---
### `collections`
Named, curated groups of bills. Shareable via UUID token.
| Column | Type | Notes |
|---|---|---|
| id | int (PK) | |
| user_id | int (FK → users, CASCADE) | |
| name | varchar | 1100 characters |
| slug | varchar | URL-safe version of name |
| is_public | bool | Signals inclusion in future public directory |
| share_token | uuid | Unique share URL token — read-only for non-owners |
| created_at | timestamptz | |
---
### `collection_bills`
Join table linking bills to collections.
| Column | Type | Notes |
|---|---|---|
| collection_id | int (FK → collections, CASCADE) | |
| bill_id | varchar (FK → bills, CASCADE) | |
| added_at | timestamptz | |
Unique constraint: `(collection_id, bill_id)`.
---
@@ -444,6 +472,8 @@ Stores notification events for dispatching to user channels (ntfy, RSS).
| `0012_dedupe_bill_actions_unique.py` | Unique constraint on `(bill_id, action_date, action_text)` for idempotent action ingestion |
| `0013_add_follow_mode.py` | `follow_mode` column on `follows` (`neutral` / `pocket_veto` / `pocket_boost`) |
| `0014_add_bill_notes.py` | `bill_notes` table with unique constraint `(user_id, bill_id)` |
| `0015_add_collections.py` | `collections` table (`id`, `user_id`, `name`, `slug`, `is_public`, `share_token` UUID, `created_at`) |
| `0016_add_collection_bills.py` | `collection_bills` join table (`collection_id` FK, `bill_id` FK, `added_at`); `share_token` UUID column on `bill_briefs` |
Migrations run automatically on API startup: `alembic upgrade head`.
@@ -533,6 +563,26 @@ Auth header: `Authorization: Bearer <jwt>`
| POST | `/test/follow-mode` | Required | Simulate a follow-mode notification to preview delivery behavior. |
| GET | `/history` | Required | Recent notification events (dispatched + pending). |
### `/api/collections`
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | `/` | Required | Current user's collections with bill count. |
| POST | `/` | Required | Create collection `{name, is_public}`. Generates slug + share_token. |
| GET | `/{id}` | Required | Collection detail with bills. |
| PUT | `/{id}` | Required | Update `{name, is_public}`. |
| DELETE | `/{id}` | Required | Delete collection (owner only). |
| POST | `/{id}/bills/{bill_id}` | Required | Add bill to collection. |
| DELETE | `/{id}/bills/{bill_id}` | Required | Remove bill from collection. |
| GET | `/share/{token}` | — | Public read-only view of a collection by share token. |
### `/api/share`
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | `/brief/{token}` | — | Public brief + bill data by share token (from `bill_briefs.share_token`). |
| GET | `/collection/{token}` | — | Public collection + bills by share token. |
### `/api/admin`
| Method | Path | Auth | Description |
@@ -610,12 +660,15 @@ Auth header: `Authorization: Bearer <jwt>`
has no sponsor data), upserts Member, sets bill.sponsor_id
↳ New bills → fetch_bill_documents.delay(bill_id)
↳ Updated bills → fetch_bill_documents.delay(bill_id) if changed
↳ Updated bills → emit bill_updated notification if action is a milestone:
- "progress" tier: passed/failed, signed/vetoed, enacted, markup, conference,
reported from committee, placed on calendar, cloture, roll call
→ all follow types (bill, sponsor, topic) receive notification
- "referral" tier: referred to committee
pocket_veto and pocket_boost only; neutral follows silently skip
↳ Updated bills → emit bill_updated notification if categorize_action() returns a category:
- vote: passed, failed, agreed to, roll call
- presidential: signed, vetoed, enacted, presented to the president
- committee_report: markup, ordered to be reported, ordered reported, reported by, discharged
- calendar: placed on
- procedural: cloture, conference
- referral: referred to
→ All three follow types (bill, sponsor, topic) receive notification.
Whether it is dispatched depends on the user's per-mode alert_filters in notification_prefs.
2. document_fetcher.fetch_bill_documents(bill_id)
↳ Gets text versions from Congress.gov (XML preferred, falls back to HTML/PDF)
@@ -755,14 +808,20 @@ class ReverseBrief:
|---|---|
| `/` | Dashboard — personalized feed + trending bills |
| `/bills` | Browse all bills with search, chamber/topic filters, pagination |
| `/bills/[id]` | Bill detail — brief with § citations, action timeline, news, trend chart |
| `/bills/[id]` | Bill detail — brief with § citations, action timeline, news, trend chart, draft letter, notes |
| `/members` | Browse members of Congress, filter by chamber/party/state |
| `/members/[id]` | Member profile — bio, contact info, leadership roles, service history, sponsored bills, public interest trend chart, recent news |
| `/following` | User's followed bills, members, and topics |
| `/following` | User's followed bills, members, and topics with accordion sections and topic filters |
| `/topics` | Browse and follow policy topics |
| `/collections` | User's collections (watchlists) — create, manage, share |
| `/collections/[id]` | Collection detail — bills in the collection, share link |
| `/notifications` | Notification settings — ntfy config, alert filters (per follow mode), quiet hours, digest mode, RSS |
| `/how-it-works` | Feature guide covering follow modes, collections, notifications, AI briefs, bill browsing |
| `/settings` | Admin panel (admin only) |
| `/login` | Email + password sign-in |
| `/register` | Account creation |
| `/share/brief/[token]` | Public shareable brief view — no sidebar, no auth required |
| `/share/collection/[token]` | Public shareable collection view — no sidebar, no auth required |
### Key Components
@@ -1100,6 +1159,39 @@ Nginx uses `resolver 127.0.0.11 valid=10s` (Docker's internal DNS) so upstream c
- Tailwind content scan extended to include `lib/` directory
- Nginx DNS resolver fix: prevents stale-IP 502s after container restarts
### v0.9.0 — Collections, Shareable Links & Notification Improvements
**Collections / Watchlists:**
- `collections` and `collection_bills` tables (migrations 0015/0016)
- Named, curated bill groups with public/private flag and UUID share token
- `CollectionPicker` popover on bill detail page — create or add to existing collections from the bookmark icon
- `/collections` page — list, create, manage collections
- `/collections/[id]` — collection detail with share link
- `GET /api/collections/share/{token}` — public read-only collection view
**Shareable Brief Links:**
- `share_token` UUID column on `bill_briefs` (migration 0016)
- Share button (Share2 icon) in `BriefPanel` copies a public link
- `/api/share/brief/{token}` and `/api/share/collection/{token}` — public router, no auth
- `/share/brief/[token]` and `/share/collection/[token]` — public frontend pages, no sidebar
- `AuthGuard` updated: `/collections` → AUTH_REQUIRED; `/share/` prefix → NO_SHELL (uses `.startsWith()`)
**Notification Improvements:**
- Quiet hours dispatch fix: events held during quiet window fire correctly on next run
- Digest mode: bundled ntfy summary on daily or weekly schedule instead of per-event pushes
- `send_notification_digest` Celery task with `digest_frequency: "daily" | "weekly"` setting
- Notification history panel on settings page split into direct follows vs topic follows
### v0.9.3 — Granular Per-Mode Alert Filters
- `categorize_action()` replaces `is_milestone_action()` / `is_referral_action()` — maps action text to one of 6 named categories: `vote`, `presidential`, `committee_report`, `calendar`, `procedural`, `referral`
- `action_category` stored in every `NotificationEvent.payload` going forward; `milestone_tier` retained for RSS/history backward compatibility
- `alert_filters` added to `notification_prefs` JSONB — nested dict: `{neutral: {...}, pocket_veto: {...}, pocket_boost: {...}}`, each with 8 boolean keys covering all event types (`new_document`, `new_amendment`, and the 6 action categories)
- Dispatcher `_should_dispatch()` checks `prefs["alert_filters"][follow_mode][key]` — replaces two hardcoded per-mode suppression blocks
- Notifications settings page: tabbed **Alert Filters** section (Follow / Pocket Veto / Pocket Boost tabs), each with 8 independent toggles, Milestones parent checkbox (indeterminate-aware), **Load defaults** revert button, and per-tab **Save** button
- How It Works page updated with accurate per-mode default alert sets and filter customization guidance
- No DB migration required — `alert_filters` stored in existing JSONB column
---
## Deployment