Files
politics-tracking/docs/contributing/frontend.md
T
Simon DiesenreiterandClaude Opus 5.5 9da5a1dc48 Politrack: track Austrian politicians from public sources
Initial import of Politrack, a self-hosted service that tracks Austrian
politicians from federal to municipal level - roles, votes, money flows,
statements, meetings, social media and legal proceedings - with every claim
linked to its sources.

- backend: FastAPI + Postgres/pgvector; adapter framework (fetch -> parse ->
  load -> enrich) with robots.txt/TDM-aware fetching, a raw-response archive
  for reparsing, and a claim/evidence model with corroboration
- sources: Parliament, Wikidata, EU Parliament/Commission, Council of
  Ministers, Rechnungshof party reports and donations, procurement, RTR media
  transparency, political ad libraries, WKStA, news feeds, Wikipedia edits,
  Bluesky, Mastodon, public Telegram channels, YouTube and X (official API)
- web: Next.js frontend in German and English with typed API client
- mcp: MCP server for AI agents and RAG
- docs: architecture, data model, sources, legal notes, Unraid deployment and
  contributor guides; Docker Compose setup for Unraid

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:47:14 +01:00

3.3 KiB
Raw Blame History

Extending the web app

Next.js 15 (App Router, React Server Components) with next-intl (German default, English). Pages fetch from the API on the server; there is almost no client-side JavaScript.

web/
  messages/de.json, en.json          all UI text (keys must exist in both)
  src/app/[locale]/<route>/page.tsx  pages (German route names: /personen, /parteien, /geld …)
  src/components/                    shared server components
  src/lib/api.ts                     typed API client (server-side only; reads the filter cookie)
  src/lib/api-types.ts               GENERATED from the backend's response models – do not edit
  src/lib/format.ts                  dates, money, localized names
  src/lib/prefs.ts                   the user's source/topic filter (cookie)

Types come from the backend

After any change to backend/politrack/api/schemas.py:

cd backend && politrack openapi --ts ../web/src/lib/api-types.ts
cd ../web && npm run typecheck

api.ts re-exports the generated types (Claim, MoneyFlow, …) and adds one function per endpoint. api.claims(), api.money(), api.social() … automatically apply the visitor's source filter (withPrefs), so every list respects "which sources do I trust".

Adding a page

  1. Create src/app/[locale]/<route>/page.tsx – an async server component:
    export default async function Page({params, searchParams}: Props) {
      const {locale} = await params;
      setRequestLocale(locale);
      const t = await getTranslations('MyPage');
      const rows = await api.something({...});
      return <>…</>;
    }
    
    Filters are plain <form method="get"> – shareable URLs, no client state.
  2. Add the texts to both messages/de.json and messages/en.json.
  3. Link it from the navigation (src/app/[locale]/layout.tsx) or the home page's "Entdecken" cards.
  4. Page modules may only export Next.js page fields (default, generateMetadata, …); keep helpers and constants module-local or in src/components.

Showing a new claim type

Claims render through ClaimList; type-specific details come from the registry in src/components/ClaimDetails.tsx:

const ambassador: Renderer = (claim, {t}) => (
  <p className="claim-detail small">{t('country')}: {String(claim.data.country ?? '')}</p>
);

const DETAILS: Partial<Record<ClaimType, Renderer[]>> = {
  …,
  career: [counterpart, ambassador],
};

Also add the label under Claims.type.<claim_type> in both message files, and (if it should appear) the type to the person page's TABS.

Non-negotiables in the UI

  • Every fact shows its sources (the "Quellen" disclosure under each claim, [source] links in tables). Don't render a claim without its evidence.
  • Presumption of innocence: needsInnocenceNotice() in ClaimList.tsx decides; never remove the notice for anything but an undisputed final conviction.
  • Connections are not accusations: pages that show networks or money of connected organisations carry the note (Network.note, Organizations.connectionNote).
  • Unverified social accounts are marked (Wikidata-only handles).

Styling

One global stylesheet (src/app/globals.css) with CSS variables for light/dark mode. Reuse the existing classes (claims, claim-detail, chip, tag, notice, grid-2, compact, num, muted, small) before adding new ones.