# Page Search Console dédiée (`/gsc`) — Design

- **Date** : 2026-07-23
- **Décisions de cadrage (Bastien)** : périmètre **gestion + santé** (pas de dataviz — les données GSC restent consommées dans Recherche/Planning/Overview) ; la section GSC de Réglages → Intégrations est **supprimée et remplacée par un lien** ; entrée de navigation **top-level** (route `/gsc`).
- **Contexte** : la connexion/gestion GSC vit aujourd'hui dans `/settings/integrations` (`src/components/gsc-integrations.tsx`), la santé du worker Rust de snapshots (`gsc_snapshot_runs`) n'est visible nulle part, et le CTA « Connecter » de `keyword-modal-sources.tsx` pointe vers la page settings. Demande d'origine (2026-07-22) : sortir Search Console de la carte de complétude et lui donner sa propre page.

## Architecture

- **Route** : `src/app/(app)/gsc/page.tsx` (RSC, pattern des pages dashboard : `DashboardHeader` + Breadcrumb, fetch serveur initial, client component par card quand il y a de l'interaction).
- **Navigation** : nouveau groupe top-level « Search Console » dans `navMainConfig` (`src/components/app-sidebar.tsx`), route `/gsc`, clé i18n `Nav.gsc` ajoutée dans `messages/*`.
- **Data fetching** (parallèle dans le RSC) :
  - `getGscStatusAction()` — existant (`src/lib/gsc/actions.ts`), scope-aware (`connected`, `missingScope`, `propertiesError`, `properties[]`, `sites[]`, `canManage`, `canLink`, `isOwner`).
  - `getGscHealthAction()` — **nouvelle** action : `{ syncBySite: Array<{siteId, lastGscSyncAt: Date|null}>, snapshotRuns: Array<{id, startedAt, finishedAt|null, sitesOk, sitesFailed, rowsUpserted, error|null}>, isSuperAdmin: boolean }`. Dernière sync TS par site de l'org (même source que `getKeywordsSyncStatus`, servie à tous les membres) + les **7 derniers** runs de `gsc_snapshot_runs` (ORDER BY startedAt DESC LIMIT 7). **Gating des runs : super-admin plateforme uniquement** (`isCurrentUserSuperAdmin`, `src/lib/auth/super-admin.ts`) — décision Bastien 2026-07-23, raison : `gsc_snapshot_runs` est une table **GLOBALE cross-tenant** (non scopée org), un owner/admin d'org n'y verrait que du bruit d'autres tenants. Un non super-admin reçoit `snapshotRuns: []` et `isSuperAdmin: false`. Le flag `isSuperAdmin` pilote le rendu de la Card 3.

## Composants (nouveau dossier `src/components/gsc/`)

`gsc-integrations.tsx` est éclaté et **supprimé** à la fin.

- **Card 1 — Connexion** (`gsc-connection-card.tsx`, client) : mêmes états que l'existant — connecté / `missingScope` (bouton « Reconnecter Google Search Console ») / non connecté + `canLink` (bouton « Connecter ») / member lecture seule. Le `linkSocial` garde provider google + scope `webmasters.readonly`, mais `callbackURL: "/gsc"`. Mention du rôle porteur de la liaison (copie owner vs générique, comme aujourd'hui).
- **Card 2 — Propriétés par site** (`gsc-properties-card.tsx`, client) : mapping actuel (Select par site → `setSiteGscPropertyAction`, item « Non mappé », erreurs `role="alert"`) enrichi par ligne : date de dernière sync (`syncBySite`), bouton « Synchroniser » (→ `syncGscKeywordsAction(siteId)`, résultat inline « X requêtes, Y matchés, Z créés », erreurs affichées), désactivé si pas de propriété mappée. `propertiesError` → message d'erreur clair à la place du Select. Visible par tous, actionnable si `canManage && connected`.
- **Card 3 — Santé des snapshots quotidiens** (`gsc-snapshot-health-card.tsx`, présentationnel) : table des runs (`snapshotRuns`) — date, statut dérivé (ok si `sitesFailed === 0 && !error`, partiel si `sitesOk > 0 && sitesFailed > 0`, échec sinon), sites ok/échoués, lignes upsertées, message d'erreur tronqué. État vide : « Aucun run de snapshot pour l'instant (le worker tourne chaque nuit). » Le mapping run → statut est une fonction pure exportée (testable). **Rendue UNIQUEMENT pour un super-admin plateforme** (`health.isSuperAdmin`) — la table `gsc_snapshot_runs` est globale cross-tenant (décision Bastien 2026-07-23, cf. gating de `getGscHealthAction` ci-dessus), donc pas de sens pour un owner/admin d'org.

## Migrations des surfaces existantes

- `/settings/integrations` : la card GSC est remplacée par une card courte « Google Search Console » avec un lien « Gérer Search Console → » vers `/gsc` (SE-Ranking ne bouge pas). Le fetch `getGscStatusAction` disparaît de cette page.
- `keyword-modal-sources.tsx` : le CTA « Connecter » (GSC uniquement) pointe vers `/gsc` au lieu de `/settings/integrations`.
- `src/components/gsc-integrations.tsx` : supprimé une fois les cards en place (grep final : plus aucun import).

## Erreurs

Toutes les erreurs visibles (pattern existant `role="alert"`) : `propertiesError`, échec de `setSiteGscPropertyAction`, échec de `syncGscKeywordsAction`. Aucun échec avalé silencieusement (leçon du bug scope du 2026-07-23).

## Tests (bun:test, patterns du repo)

- `getGscHealthAction` : org sans sites, org sans runs, runs multiples (ordre + limite 7), run en échec (error non null).
- Fonction pure statut de run : ok / partiel / échec / run sans `finishedAt` (en cours).
- Les tests existants (`actions.test.ts`, `scope.test.ts`) couvrent déjà la connexion — pas de doublon.
- Non-régression : `bun test src/lib/gsc/` + typecheck.

## Hors périmètre (explicite)

- Dataviz GSC (requêtes/pages/tendances) — future page distincte sous le même groupe sidebar si le besoin arrive.
- Page GSC par site ou par propriété.
- Modification du worker Rust ou du broker de token.
- Import GSC (`import-gsc-dialog.tsx`) — inchangé, reste sur les pages keywords.
