4 éléments · 2 stores · 5 sections · 15 entrées

Le frame, pas les pages

Cette page ne dessine rien de la coquille : elle déclare les quatre balises et laisse /commun/coquille.js les rendre. Les 17 pages de l'application font exactement la même chose, puis n'écrivent que leur <main>.

Le menu vient de RAIL, RAIL_BAS, DOCK, RAIL_DE et SECTIONS, recopiés de specs/mockups/assets/mockup-nav.js. Aucune page ne contient de lien de navigation.

Rail 1 — 64 px

<ui-rail page="impayes">

page
— allume le raccourci (ici Impayés).
actif
— force l'entrée allumée.
marque
— le texte du logo (défaut : Marki v2 — ADTI).
logo
— le fichier du logo (défaut : /commun/marki-logo.png).
utilisateur / email
— la session, sinon les attributs.
deconnexion
— la route de sortie (défaut : /login/).

Rail 2 — 220 px

<ui-rail-nav page="impayes">

page
— l'entrée active (une fiche n'en allume aucune).
actif
— force l'entrée active.
titre
— l'en-tête (défaut : ADTI · Recouvrement).
monogramme
— la pastille (défaut : AD).
base
— préfixe des liens (../../ dans une spec).

Les messages

Deux stores, un x-for par type. Un message passe par le store, jamais par un setTimeout de page.

Le volet latéral

Le chrome vient du frame ; le corps vient de la page. Fermeture par Échap, par le fond ou par × ; le focus part sur × et revient au bouton d'ouverture.

La modale

Même principe : le voile, le panneau, l'en-tête, la croix et le pied viennent du frame ; la page n'écrit que le corps et ses boutons. Échap ne ferme que la couche du dessus.

Les quatre états

Une zone sans données ne s'écrit plus : elle se nomme. Le frame dessine, la page ne fournit que ses mots.

chargement · squelette (3 lignes)

chargement · cartes (5) et attente

vide · avec une action

erreur · bloc avec « Réessayer », puis bandeau

permission — aucun écran dans les maquettes (0/20), le type existe pour le jour où un rôle apparaîtra :

Ce que la console prouve

document.querySelectorAll('ui-rail').length;                                  // 1
document.querySelectorAll('ui-rail [data-rail]').length;                       // 5  (4 raccourcis + paramètres)
document.querySelectorAll('ui-rail [aria-current="page"]').length;            // 1  (impayes)
document.querySelectorAll('ui-rail-nav h2').length;                           // 5  sections
document.querySelectorAll('ui-rail-nav [data-espace]').length;                // 15 entrées
document.querySelector('ui-rail-nav [aria-current="page"]').dataset.espace;   // "impayes"
document.querySelectorAll('ui-rail [data-coquille="dock"] [data-dock]').length; // 6
document.querySelector('ui-rail [data-coquille-sortie]').getAttribute('href');  // "/login/"
document.querySelectorAll('ui-notification template[x-for]').length;          // 4
document.querySelectorAll('ui-toasts template[x-for]').length;                // 4
Alpine.store('notifications').items.length;                                   // 0 au départ

Le corps du volet appartient à la page

Son contenu est déclaré dans data-volet-corps, qui ne s'exécute pas tant que le volet est fermé — comme le template x-if des maquettes.

Un second bloc, pour vérifier l'espacement space-y-4 du corps.

Le corps de la modale appartient à la page, comme celui du volet. Il ne s'exécute pas tant que la modale est fermée.

#modale-titre et #btn-modale-close sont les deux ids du frame.

Aucune croix : le focus part sur le panneau. Aucun pied : la rangée n'est pas rendue.