
# Navigation côté client

> Passer d'une page à l'autre sans rechargement — clientSideNavigation remplace la zone de contenu au clic sur un lien de la barre latérale et garde à l'écran les données déjà chargées, et trackNavigation garde juste la marque de l'entrée courante après le remplacement.

Par défaut, chaque lien d'une app Sovrium est un chargement de page ordinaire : le navigateur récupère un nouveau document, et tout ce qui est sur la page — tableaux, tableaux kanban, leurs lignes déjà chargées — repart de zéro. C'est simple et toujours correct. Ces deux options échangent un peu de cette simplicité contre une navigation qui paraît instantanée.

## `clientSideNavigation`

`clientSideNavigation: true` sur une `sidebar` transforme le clic sur un lien interne à l'app en remplacement du contenu. Sovrium ne récupère que l'élément `<main id="main-content">` de la destination et remplace l'élément courant sur place ; le document lui-même n'est jamais rechargé.

```yaml
pages:
  - name: deals
    path: /deals
    components:
      - type: sidebar
        clientSideNavigation: true
        groups:
          - label: Sales
            items:
              - { label: Deals, href: /deals }
              - { label: Pipeline, href: /pipeline }
```

Déclare-la sur la barre latérale de chaque page qui doit y participer. Une page dont la barre latérale ne la définit pas se charge en entier quand on y arrive, et ses propres liens sont des chargements de page ordinaires.

**Ce qui survit au remplacement.** Le document, le runtime client et les données qu'il a déjà chargées. Un tableau ou un kanban déjà vu affiche ses lignes immédiatement au retour — sans squelette de chargement — et les rafraîchit en arrière-plan. Le titre de la page suit la destination, tout comme les boutons précédent et suivant du navigateur : chacun remplace à nouveau la zone que désigne la barre d'adresse.

**La barre latérale vient avec le contenu.** Elle se trouve dans `<main id="main-content">`, donc le serveur la rend à nouveau pour la destination, avec la bonne entrée portant `aria-current="page"`. Tu n'as pas besoin de `trackNavigation` en plus.

**Ce qui n'est jamais intercepté.** Un clic avec une touche de modification, un clic du milieu, un lien avec `target="_blank"` ou `download`, un lien vers une autre origine, un lien vers `/_admin`, `/api/` ou `/assets/`, un lien vers une ancre de la page courante, et tout lien portant l'attribut `data-no-spa`. Chacun se comporte exactement comme sans l'option. Utilise `data-no-spa` sur un lien qui doit toujours charger un document neuf.

**Les pages toujours chargées en entier.** Une destination qui déclare `scripts`, définit `presence: true` ou utilise un `layout.sidebar` lié à un enregistrement est chargée comme un document complet, parce que chacun de ces éléments vit en dehors de la zone remplacée. C'est aussi le cas quand la destination répond par une erreur ou une redirection (vers la connexion, par exemple), ou quand la page que l'on quitte a elle-même un `layout.sidebar` lié à un enregistrement ou `presence: true`. On arrive quand même à destination ; c'est simplement un chargement de page.

**Les écouteurs continuent de fonctionner.** Après chaque remplacement, `sovrium:navigated` est émis sur `document` — le même événement que celui décrit plus bas sous `trackNavigation` —, donc un script qui l'écoute voit les navigations côté client comme n'importe quelle autre.

**Une différence connue.** La barre latérale est rendue à nouveau avec chaque destination, donc sa propre position de défilement revient en haut à chaque navigation. Dans une longue barre latérale, on garde sa place dans le contenu, pas dans la liste.

## `trackNavigation`

`aria-current="page"` est résolu sur le serveur, ce qui est juste et suffisant pour une app dont chaque navigation est un chargement de page. Une app qui remplace sa zone de contenu sur place laisse la barre latérale montée, et la marque du serveur figée sur la page que l'on a déjà quittée — l'élément qui répond à « où suis-je » devient alors celui qui se trompe.

`trackNavigation: true` recalcule la marque côté client après une navigation dans le même document. L'option est à activer explicitement parce qu'elle coûte un îlot client, et qu'une app qui ne fait que des chargements de page complets n'y gagne rien.

La marque se déplace sur deux signaux. **`popstate`** — les boutons précédent et suivant du navigateur — ne demande rien à ton app. L'autre est **`sovrium:navigated`**, l'événement qu'annonce un mécanisme de remplacement interne à l'app. Mets d'abord à jour `window.location`, puis émets un `CustomEvent` sur `document` :

```ts
history.pushState({}, '', '/products/widgets')
document.dispatchEvent(
  new CustomEvent('sovrium:navigated', { detail: { path: '/products/widgets' } })
)
```

Cet ordre est le contrat, pas une convention : la barre latérale lit elle-même `window.location`, donc `detail.path` n'est qu'informatif, et émettre l'événement avant la mise à jour de l'adresse recalcule la marque sur la page que l'on quitte.

`activeMatch` est réévalué côté client selon la même règle que côté serveur, donc une entrée `prefix` garde sa marque lors d'une navigation vers une sous-page. Un volet dont la section devient courante s'ouvre avec elle — mais n'est jamais rouvert après avoir été replié volontairement.
