diff --git a/.env.example b/.env.example index 0ea576a..cf23e35 100644 --- a/.env.example +++ b/.env.example @@ -73,3 +73,17 @@ GAP_CRITICAL_HOURS=24 # Default lookback window for analyses (days) LOOKBACK_DAYS=90 + +# ============================================================ +# Auto-detected incident pages (P3 — Viralité) +# ============================================================ +# Base URL of the Next.js front-end (the Python pipeline POSTs here +# to revalidate /incident/ ISR pages): +NEXTJS_BASE_URL=http://localhost:3000 +# Shared secret between FastAPI emitter and Next.js /api/revalidate. +# Generate with: python -c "import secrets;print(secrets.token_urlsafe(32))" +KRILL_REVALIDATE_SECRET= +# Internal secret for the pipeline → FastAPI POST /api/internal/incident +# (X-Internal header). Different from the revalidate secret so that an +# external client compromising the front-end cannot inject incidents. +KRILL_INTERNAL_SECRET= diff --git a/.gitignore b/.gitignore index c00b734..e15e357 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ web/.vercel web/.turbo web/next-env.d.ts web/*.tsbuildinfo + +# Superpowers brainstorming working directory (mockups, server state) +.superpowers/ diff --git a/README.md b/README.md index a6a8da4..649496d 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,15 @@ - **Multi-mission vessel tracker** — four CPWF campaigns share the same pipeline: krill (CCAMLR-licensed Antarctic trawlers), whaling (Kangei Maru + Icelandic Hvalur), Galápagos (Chinese DWF squid jiggers), West Africa (Chinese + Russian super-trawlers in Senegal/Mauritania/Guinea EEZs). Vessels are grouped by mission on the catalogue page with mission-coloured badges. - **Live AIS triage dashboard** — a Next.js investigator UI at `web/` that aggregates fleet positions, zone transitions, and severity alerts on a per-vessel basis. Home page surfaces CRITICAL and SUSPICIOUS vessels immediately; vessel detail shows a full-bleed MapLibre track with a configurable time window (24 h → 90 d). -- **Live alerting** — Discord and Slack webhooks fire on CRITICAL events (zone entry into MPA / IWC sanctuary / EEZ, severity escalation). Rich embeds carry a clickable deep link straight to the vessel detail page. Per-mission webhook routing supported (whaling alerts → separate channel from krill). +- **Bilingual campaign hubs** — `/[locale]/campagnes/` for each of the four CPWF campaigns: editorial intro, legal frame, fleet roster, season calendar, and source list. Hreflang fr↔en across the whole site. +- **Auto-generated incident pages** — every protected-zone entry by a tracked vessel triggers an ISR page at `/[locale]/incident/` with severity chip, headline, 7-day map trail centred on the event, campaign context, and sources. Detection → live page in under 5 s via a FastAPI → Next.js revalidate webhook. +- **Dynamic OG images** — `/api/og/incident/`, `/api/og/vessel/`, `/api/og/campaign/` render 1200×630 PNGs at the edge for Twitter/Bluesky/Mastodon/LinkedIn share previews. +- **Public contribution form** — `/[locale]/contribuer` accepts vessel sightings, transhipment tips, AIS anomalies, and free-form testimony, with optional photo upload (5 MB max). Discord webhook notifies admins of new submissions without leaking submitter PII. +- **Weekly digest route** — `/[locale]/digest` exposes the existing transhipment-review HTML reports under stable Next.js URLs, with optional ISO-week deep links (`/digest/2026-W17`). +- **Live alerting** — Discord and Slack webhooks fire on CRITICAL events (zone entry into MPA / IWC sanctuary / EEZ, severity escalation). Rich embeds carry a clickable deep link straight to the vessel detail page **and the auto-generated `/incident/`** when applicable. Per-mission webhook routing supported (whaling alerts → separate channel from krill). - **Multi-signal transhipment scorer** — for every GFW encounter event in a rolling window, six weighted signals (unauthorized reefer, AIS gap, IUU/sanctions hit, spatial anomaly, FOC flag, long duration) are evaluated and summed into a 0.00–0.90 score. Calibration frozen at **v0.4.2** — see [`calibration.md`](calibration.md) for the weight justifications and validation cases. - **Weekly HTML review report** — a self-contained three-section report (§1 CRITICAL, §2 FLAGGED, §3 transparency) that can be forwarded as an email attachment. Generated automatically every Monday by GitHub Actions and (in production) by `scripts/run_weekly_report.sh` on the deployment VM. -- **FastAPI backend with DuckDB persistence** — positions, zone-transition events, and vessel status land in a local DuckDB file. No external database required. A background SSE stream (`/api/stream`) pushes live updates to connected UIs. +- **FastAPI backend with DuckDB persistence** — positions, zone-transition events, vessel status, auto-incidents, and contributions land in a single DuckDB file. No external database required. A background SSE stream (`/api/stream`) pushes live updates to connected UIs. --- diff --git a/docs/superpowers/specs/2026-04-28-krill-refonte-design.md b/docs/superpowers/specs/2026-04-28-krill-refonte-design.md new file mode 100644 index 0000000..7e2a543 --- /dev/null +++ b/docs/superpowers/specs/2026-04-28-krill-refonte-design.md @@ -0,0 +1,226 @@ +# krill-watch · refonte unifiée — design spec + +> **Status** : draft validé · brainstorming 2026-04-28 +> **Owner** : @breaching +> **Spec ID** : 2026-04-28-krill-refonte +> **Approbation** : pending user review of this document +> **Companion artefacts** : `2026-04-28-krill-refonte/` (orchestration + phase plans + tasks) + +--- + +## 1. Contexte & motivation + +`krill-watch` exploite aujourd'hui **deux frontends parallèles** : un tracker dense (HTML/JS vanilla servi par FastAPI sur `127.0.0.1:8000`) et un site Next.js bilingue (`localhost:3000`). Il n'y a aucune synergie entre les deux : design systems divergents, navigation séparée, le tracker n'est pas mobile-friendly, les pages Next.js ne tirent pas parti des données live. + +L'utilisateur veut une refonte qui : +1. **Unifie** les deux apps en un seul produit cohérent (Next.js). +2. **Ajoute un historique satellite** des bateaux ("comme Google Maps") : timeline scrubber + trail cumulatif coloré. +3. **Sert simultanément** quatre audiences — activistes Sea Shepherd / CPWF (primaire), journalistes d'investigation, lanceurs d'alerte, et public général curieux. + +Décisions prises pendant le brainstorming : +- **Persona primaire** : activiste CPWF — toute friction UX se résout en sa faveur (mobile-first, partage, urgence visuelle, CTA "soutenir / signaler"). +- **Architecture** : app Next.js unifiée, le tracker legacy `frontend/app.js` est réécrit en composants React puis supprimé. +- **Historique satellite** : timeline scrubber + trail cumulatif coloré (sévérité = couleur). Pas d'imagerie Sentinel-2 réelle au MVP (gardée pour phase ultérieure). +- **Home** : hero hybride — phrase-choc tirée du live + 3 stats sévérité + 2 CTA + mini-map preview. +- **Atomes partageables** : pages incident, dossiers navires, hubs de campagne (les trois ont URL stable + OG image dédiée). Digest hebdo exposé en route depuis le HTML existant. +- **Scope** : refonte complète activiste, 5-7 semaines, 3 phases livrables (chacune utile en standalone). + +--- + +## 2. Vision & promesse + +> *Un seul tracker open-source qui rend visibles les flottes industrielles en zones protégées — vivant pour les activistes, citable pour les journalistes, accessible pour qui veut comprendre.* + +**Ton** : sobre + précis. L'activisme transparaît par le choix du sujet (et la sévérité visuelle des incidents), jamais par la rhétorique. Pas de "pillage", "scandale", "honte" — la donnée parle. + +**Bilingue FR/EN** maintenu en parité totale. Pas d'autres langues au MVP. + +**Mobile-first** sur les surfaces critiques (`/`, `/live`, `/incident/[slug]`). Le cockpit `/live` a un layout mobile *redessiné* (BottomSheet pour les filtres, drawer pour le navire sélectionné), pas un shrink desktop. + +--- + +## 3. Architecture cible + +### 3.1 Couches + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Next.js 15 App Router · port 3000 · TypeScript / React 19 │ +│ Tailwind 4 · MapLibre 5 · flag-icons · @vercel/og │ +└──────────────┬──────────────────────────────────────────────┘ + │ REST + SSE +┌──────────────┴──────────────────────────────────────────────┐ +│ FastAPI · port 8000 · Python 3.11+ │ +│ inchangé sauf : webhook revalidate + endpoint trail-historique +│ + endpoint contributions │ +└──────────────┬──────────────────────────────────────────────┘ + │ +┌──────────────┴──────────────────────────────────────────────┐ +│ DuckDB (data/krill_watch.duckdb) · GeoJSON config │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 3.2 Routes Next.js cibles + +| Route | Type | Phase | Description | +|---|---|---|---| +| `/[locale]` | static + ISR | 1 | Home (hero hybride, alerts strip, missions, méthode) | +| `/[locale]/live` | client | 1 | Tracker absorbé (cockpit) | +| `/[locale]/live/v/[slug]` | client | 1 | Deep-link vers un navire dans le cockpit | +| `/[locale]/vessels` | static | 1 | Liste flotte (existant — adapté DS) | +| `/[locale]/vessels/[slug]` | ISR | 2 | Dossier navire + trail + scrubber | +| `/[locale]/campagnes/[slug]` | ISR | 2 | 4 hubs de campagne | +| `/[locale]/incident/[slug]` | ISR | 3 | Pages incident auto-générées | +| `/[locale]/digest/[isoweek]` | static | 3 | Digest hebdo depuis HTML existant | +| `/[locale]/about` | static | — | Existe déjà | +| `/[locale]/contribuer` | client | 3 | Formulaire contribution | +| `/api/og/[type]/[slug]` | edge | 3 | OG images dynamiques | +| `/api/revalidate` | route | 3 | Webhook FastAPI → revalidate | +| `/api/contribute` | route | 3 | Endpoint formulaire contribution | + +### 3.3 Composants critiques mutualisés + +- **``** — composant map unique remplaçant `FleetMap` actuel, prend `mode` (`live | vessel | incident | campaign`) et configure layers/sources en conséquence. Déjà partiellement présent. +- **``** — layer GeoJSON line avec gradient de couleur selon sévérité. Phase 2. +- **``** — slider temporel avec keyboard a11y. Phase 2. +- **``** — strip d'alertes critiques, réutilisable home + campaign hub. +- **``** — copy-link + intents Twitter/Bluesky/Mastodon + download OG image. Phase 3. +- **``** — bouton "Soutenir CPWF" cohérent. Phase 3. +- **``, ``, ``, ``** — déjà existants, restent en place. + +### 3.4 Data layer + +- `web/lib/krill-watch.ts` (existe) — wrap REST + SSE. Étendu en phase 2 avec `getVesselTrail(slug, days)`. +- Génération statique pour pages stables (about, vessels, vessels/[slug] ISR, campagnes/[slug] ISR). +- `/live` et `/incident/[slug]` server-rendered (pour OG images correctes). + +--- + +## 4. Surfaces UX + +### 4.1 Home (`/`) + +**Above-the-fold** (hero hybride) : +- Tag mono : `● MAINTENANT · CCAMLR 48.1` (zone la plus chaude détectée) +- Headline : `11 chalutiers industriels en zone protégée antarctique.` (généré depuis l'API : nombre + zone + qualifieur) +- 3 stats par sévérité (CRITIQUE / SUSPECT / SURVEILLÉ) +- 2 CTA : "Voir la carte live →" (primary, krill orange) et "Comprendre la cause" (ghost) +- Mini-map preview à droite (180×120 desktop) avec dots clignotants + +**Below-the-fold** : LiveAlertsStrip (3 cartes alerte) → Grille 4 missions (cards cliquables vers `/campagnes/[slug]`) → Méthode 3 étapes → Footer. + +### 4.2 Cockpit (`/live`) + +**Desktop** : layout 3 colonnes — sidebar gauche (filtres + liste fleet collabsible) · map plein viewport · drawer droit (détails navire sélectionné, fermable). Toggle DARK / SATELLITE en flotte top-left de la map. + +**Mobile** : map plein écran. BottomSheet remontante pour filtres + liste. Drawer plein écran pour détails. FAB pour toggle satellite. + +URL state : `?severity=CRITICAL&zone=48.1&v=antarctic-endurance` (deep-linkable, copiable). + +### 4.3 Dossier navire (`/vessels/[slug]`) + +**Header** : drapeau (Flag SVG) + nom + sévérité badge + flag CPWF si friendly + IMO/MMSI mono. + +**Section map historique** : `` avec `` rendu en gradient color (vert → orange → rouge selon sévérité instantanée). En bas : `` avec marqueurs (entrées/sorties zones, AIS gaps). + +**Sections suivantes** : Tableau identité · Log événements · Incidents documentés (liens vers `/incident/[slug]` en phase 3) · Sources · `` + Embed code. + +### 4.4 Page incident (`/incident/[slug]`) + +**Above-the-fold** : map zoomée sur la violation + headline auto-générée (`{vessel} entered {zone} at {ts}`) + score transhipment (si applicable) + sources (3-5 liens externes). + +**Below-the-fold** : contexte campagne (hub link) · trail du navire 7 jours autour de l'incident · `` · ``. + +URL slug : `---` (ex: `2026-04-28-antarctic-endurance-zone-entered-48-1`). Idempotent — si réémis, met à jour la même page. Voir [P3-T01](./2026-04-28-krill-refonte/phase-3-viralite/P3-T01-revalidate-webhook.md) pour la fonction `makeIncidentSlug` partagée TS/Python. + +### 4.5 Hub campagne (`/campagnes/[slug]`) + +**Hero éditorial** : image satellite teaser (statique en MVP, Sentinel-2 plus tard) + 3 chiffres-clés + CTA Soutenir. + +**Sections** : Cadre légal & enjeu · Liste flotte filtrée par mission · Incidents récents (live) · Calendrier saison · Sources documentaires. + +Contenu éditorial : MD bilingue par mission (`web/data/campaigns/.{fr,en}.md`). + +--- + +## 5. Plan de phasage + +3 phases livrables. Chacune fournit de la valeur en standalone (`/live` à parité après phase 1, dossiers enrichis après phase 2, viralité après phase 3). + +| Phase | Durée | Tâches | Livrable principal | +|---|---|---|---| +| **1 · Fondation** | 10 jours | 8 tâches | `/live` à parité fonctionnelle dans Next.js, mobile-friendly. Hero hybride home. Legacy supprimé. | +| **2 · Historique & contenu** | 10 jours | 8 tâches | Trail + scrubber sur dossiers navires. 4 hubs de campagne. Glossaire enrichi. | +| **3 · Viralité & contribution** | 12 jours | 8 tâches | Pages incident auto-générées + OG dynamiques. Boutons partage. Formulaire contribution. Digest en route. | + +Détail dans `2026-04-28-krill-refonte/phase-N-*/README.md`. + +--- + +## 6. Risques & questions ouvertes + +### Risques techniques + +- **MapLibre + React StrictMode** : double-init en dev. Pattern ref + cleanup déjà en place dans `FleetMap.tsx` à conserver. +- **SSE dans Next.js App Router** : pas de support natif streaming SSE dans les Route Handlers pour l'instant. Solution : EventSource côté client (`'use client'`), fallback polling 30s. +- **OG images dynamiques** : `@vercel/og` (Satori) prend du SVG / éléments React simples. Une carte raster MapLibre ne s'y intègre pas. Solution prévue : pré-render côté serveur Python qui retourne PNG basique (tile statique + dot vessel + headline), ou mock SVG avec `` simplifié dans Satori. +- **Performance trail 30j × 23 navires** : 50k-100k points GeoJSON potentiels. Mitigation : downsample côté API (1 point / 15 min suffit visuellement), cluster côté client si > 5k. +- **Webhook revalidate FastAPI → Next.js** : nécessite un secret partagé + une URL publique. En dev local on peut utiliser ngrok ; en prod ça dépend de l'hébergement. + +### Questions ouvertes (à trancher pendant l'exécution) + +- **Hébergement Next.js** : Vercel (gratuit, OG natif) ou auto-hébergé (Docker compose avec FastAPI) ? *Décision recommandée : Vercel pour simplicité.* +- **Newsletter** : intégrée (Buttondown/Substack embed) ou simple lien externe ? *Décision recommandée : externe pour MVP.* +- **Storage formulaire contribution** : DuckDB (réutilise infra existante) ou GitHub Issues (auditabilité publique) ? *Décision recommandée : DuckDB + admin export.* +- **CTA Soutenir CPWF** : redirige vers `paulwatsonfoundation.org/donate` directement ou page intermédiaire krill-watch ? *Décision recommandée : redirige direct, ne pas créer un funnel.* + +--- + +## 7. Critères de succès globaux + +À la fin des 3 phases, le produit livré doit valider : + +1. **Synergie** : un seul header / footer / DS / locale switcher pour TOUT le produit. +2. **Mobile** : un activiste sur smartphone peut consulter `/live`, sélectionner un navire, et voir un incident détaillé sans frustration. +3. **Viralité** : partager une URL `/incident/` sur Twitter génère un preview avec OG image lisible (carte + headline + drapeau). +4. **Citabilité** : un journaliste peut citer `/vessels/` comme référence stable pour un article ; la page existait à la même URL il y a 6 mois. +5. **Pédagogie** : un random qui arrive sur `/` comprend en 30 secondes ce qu'il regarde et où il peut agir. +6. **Accessibilité** : Lighthouse a11y score ≥ 90 sur toutes les pages critiques. +7. **Performance** : Lighthouse perf ≥ 80 sur mobile, LCP < 2.5s, CLS < 0.1. + +--- + +## 8. Hors scope (explicite) + +- Imagerie Sentinel-2 réelle alignée AIS (gardée pour phase ultérieure post-MVP). +- Flux lanceur d'alerte chiffré bout-en-bout / Tor-friendly (gardé pour phase ultérieure). +- API publique versionnée avec auth journaliste. +- Widget embeddable `