@nodefony/devkit 10.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +544 -0
- package/README.md +318 -0
- package/dist/_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.142.0/helpers/esm/decorateParam.js +8 -0
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.js +8 -0
- package/dist/_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateParam.js +8 -0
- package/dist/_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.147.0/helpers/esm/decorateParam.js +8 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
- package/dist/index.js +47 -0
- package/dist/nodefony/command/CardCommand.js +70 -0
- package/dist/nodefony/config/config.js +200 -0
- package/dist/nodefony/config/defineModuleConfig.js +36 -0
- package/dist/nodefony/controllers/DevkitController.js +60 -0
- package/dist/nodefony/controllers/McpController.js +223 -0
- package/dist/nodefony/controllers/OAuthMetadataController.js +89 -0
- package/dist/nodefony/interfaces/IDevkitService.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/DevkitService.js +198 -0
- package/dist/nodefony/src/card.js +2 -0
- package/dist/nodefony/src/errors/DevkitError.js +21 -0
- package/dist/nodefony/src/mcp/guard.js +51 -0
- package/dist/nodefony/src/mcp/protocol.js +127 -0
- package/dist/nodefony/src/mcp/server.js +133 -0
- package/dist/nodefony/src/mcp/tools.js +163 -0
- package/dist/types/index.d.ts +52 -0
- package/dist/types/nodefony/command/CardCommand.d.ts +33 -0
- package/dist/types/nodefony/config/config.d.ts +25 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +27 -0
- package/dist/types/nodefony/controllers/DevkitController.d.ts +37 -0
- package/dist/types/nodefony/controllers/McpController.d.ts +70 -0
- package/dist/types/nodefony/controllers/OAuthMetadataController.d.ts +39 -0
- package/dist/types/nodefony/interfaces/IDevkitService.d.ts +69 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/service/DevkitService.d.ts +143 -0
- package/dist/types/nodefony/src/card.d.ts +18 -0
- package/dist/types/nodefony/src/errors/DevkitError.d.ts +14 -0
- package/dist/types/nodefony/src/mcp/guard.d.ts +66 -0
- package/dist/types/nodefony/src/mcp/protocol.d.ts +139 -0
- package/dist/types/nodefony/src/mcp/server.d.ts +48 -0
- package/dist/types/nodefony/src/mcp/tools.d.ts +81 -0
- package/docs/index.md +358 -0
- package/package.json +77 -0
- package/skills/nodefony-add-crud/SKILL.md +199 -0
- package/skills/nodefony-add-realtime-channel/SKILL.md +95 -0
- package/skills/nodefony-add-service/SKILL.md +90 -0
- package/skills/nodefony-browser/SKILL.md +416 -0
- package/skills/nodefony-browser/references/socket.md +115 -0
- package/skills/nodefony-browser/references/sondes.md +175 -0
- package/skills/nodefony-browser/scripts/audit.mjs +169 -0
- package/skills/nodefony-browser/scripts/inspect.mjs +903 -0
- package/skills/nodefony-browser/scripts/lib/browser.mjs +357 -0
- package/skills/nodefony-browser/scripts/lib/probes.mjs +501 -0
- package/skills/nodefony-browser/scripts/lib/wcag.mjs +153 -0
- package/skills/nodefony-browser/scripts/socket.mjs +354 -0
- package/skills/nodefony-browser/scripts/watch.mjs +125 -0
- package/skills/nodefony-migrate-schema/SKILL.md +359 -0
- package/skills/nodefony-migrate-schema/references/verdicts.md +139 -0
- package/skills/nodefony-protect-route/SKILL.md +195 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodefony-add-realtime-channel
|
|
3
|
+
description: >
|
|
4
|
+
Ajoute un flux temps réel à une application Nodefony par la bonne couche — un
|
|
5
|
+
`RealtimeController` et ses décorateurs de canal — au lieu de recomposer un WebSocket à la main.
|
|
6
|
+
Porte la façon de fermer un canal à certains rôles, le piège du canal public par défaut, celui
|
|
7
|
+
du canal dont le nom est calculé, et la façade cliente à employer côté navigateur. À charger
|
|
8
|
+
AVANT d'écrire du code WebSocket, un canal, ou un abonnement client.
|
|
9
|
+
Déclencheurs : "flux temps réel", "websocket", "canal", "push", "notifications en direct",
|
|
10
|
+
"abonnement", "RealtimeController", "@RealtimeChannel", "socket client", "temps réel privé",
|
|
11
|
+
"réserver un canal à un rôle", "mon canal est public", "diffuser à plusieurs onglets".
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# add-realtime-channel — le temps réel par sa couche
|
|
15
|
+
|
|
16
|
+
> ⚖️ **La confiance n'exclut pas le contrôle.** Un canal sans politique est **public par
|
|
17
|
+
> construction** — c'est le comportement voulu du framework, pas un oubli. Ce qui n'est pas
|
|
18
|
+
> déclaré fermé est ouvert.
|
|
19
|
+
|
|
20
|
+
## Le geste
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx nodefony create controller Ops --kind realtime
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Produit une sous-classe de `RealtimeController` avec son canal décoré et une action, plus le
|
|
27
|
+
câblage. **N'écris pas de `new WebSocket(...)` ni de `ws.on(...)` côté serveur** : le bas niveau
|
|
28
|
+
existe, il est employé par le framework, et le reprendre à la main te prive du routage par canal,
|
|
29
|
+
de l'authentification partagée avec HTTP, et de la contre-pression.
|
|
30
|
+
|
|
31
|
+
## Fermer un canal
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
@RealtimeChannel("ops:alerts", { roles: ["ROLE_ADMIN"] })
|
|
35
|
+
export class OpsController extends RealtimeController {
|
|
36
|
+
@RealtimeAction("ops:snapshot", { roles: ["ROLE_ADMIN"] })
|
|
37
|
+
async snapshot() { … }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Deux niveaux, et ils sont indépendants : la **politique du canal** décide qui peut s'abonner, la
|
|
42
|
+
politique d'une **action** décide qui peut la déclencher. `{ authenticated: true }` suffit quand
|
|
43
|
+
aucun rôle particulier n'est requis.
|
|
44
|
+
|
|
45
|
+
## 🔴 Le piège du canal dont le nom est calculé
|
|
46
|
+
|
|
47
|
+
Une politique est indexée par le nom **EXACT** du canal. Un canal dont le nom se construit à
|
|
48
|
+
l'exécution (`` `room:${id}` ``) **n'est couvert par aucune politique déclarée** — il naît public,
|
|
49
|
+
et rien ne le signale.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
@RealtimeChannel("room:lobby", { authenticated: true }) // ✅ couvert
|
|
53
|
+
// `room:42` construit à la volée // 🔴 PAS couvert
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Tant qu'un canal à membres n'existe pas dans le framework, le geste montrable est le canal
|
|
57
|
+
**gardé** par rôle ou authentification, pas le canal dynamique.
|
|
58
|
+
|
|
59
|
+
## Côté client — la façade, pas le socket
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { RealtimeClient } from "nodefony/client";
|
|
63
|
+
|
|
64
|
+
const client = RealtimeClient.shared();
|
|
65
|
+
client.subscribe("ops:alerts", (payload) => { … });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
En React, les hooks du paquet client font la même chose avec le cycle de vie du composant.
|
|
69
|
+
**Importe depuis `nodefony/client`**, jamais depuis la racine `nodefony` : côté navigateur, elle
|
|
70
|
+
n'expose pas ces symboles et le compilateur refuse.
|
|
71
|
+
|
|
72
|
+
## Ne pas ouvrir plus que le canal demandé
|
|
73
|
+
|
|
74
|
+
Fermer « toute la zone `^/api` » pour protéger un canal emporte le reste de l'application avec
|
|
75
|
+
lui — y compris les démonstrations publiques posées par le scaffold. La politique se pose **sur le
|
|
76
|
+
canal**, pas sur l'espace HTTP qui l'entoure.
|
|
77
|
+
|
|
78
|
+
## Prouver
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm test
|
|
82
|
+
npx nodefony inspect routes --json # les canaux montés, tels que l'application les voit
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Puis, avec trois identités comme pour une route : un anonyme ne reçoit pas le flux réservé, un
|
|
86
|
+
compte sans le rôle non plus, l'administrateur oui — **et le canal public du scaffold répond
|
|
87
|
+
toujours** (s'il s'est fermé, une politique a débordé).
|
|
88
|
+
|
|
89
|
+
## Voisins
|
|
90
|
+
|
|
91
|
+
| Besoin | Skill |
|
|
92
|
+
| ------------------------------ | ------------------------ |
|
|
93
|
+
| Réserver une route HTTP | `nodefony-protect-route` |
|
|
94
|
+
| Une ressource complète stockée | `nodefony-add-crud` |
|
|
95
|
+
| Un service métier injectable | `nodefony-add-service` |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodefony-add-service
|
|
3
|
+
description: >
|
|
4
|
+
Crée un service injectable dans une application Nodefony par `nodefony create service`, et le
|
|
5
|
+
fait entrer dans le conteneur — la moitié qu'on oublie. Porte la distinction entre le nom de la
|
|
6
|
+
CLASSE et le nom de l'INSTANCE, les deux façons d'obtenir un service depuis un autre
|
|
7
|
+
(`@inject` au constructeur ou `container.get` à l'usage), et le défaut mesuré qu'un service
|
|
8
|
+
écrit à la main produit : une classe qui compile, dont les tests passent, et que le conteneur
|
|
9
|
+
ignore. À charger AVANT d'écrire une classe de service ou d'appeler un service depuis un autre.
|
|
10
|
+
Déclencheurs : "crée un service", "un service métier", "logique métier partagée", "injecter une
|
|
11
|
+
dépendance", "container.get", "@injectable", "@services", "appeler un service depuis un autre",
|
|
12
|
+
"mon service est undefined", "le conteneur ne trouve pas mon service".
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# add-service — un service que le conteneur connaît
|
|
16
|
+
|
|
17
|
+
> ⚖️ **La confiance n'exclut pas le contrôle.** Un service qui compile n'est pas un service
|
|
18
|
+
> enregistré. Le seul juge est l'application en marche.
|
|
19
|
+
|
|
20
|
+
## Le geste
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx nodefony create service Billing
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Produit la classe `@injectable()` `extends Service`, son interface, **et** l'inscrit dans le
|
|
27
|
+
`@services([…])` de la cible — en le créant s'il n'existe pas.
|
|
28
|
+
|
|
29
|
+
Pour qu'il en appelle un autre :
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx nodefony create service Invoice --inject Billing
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
La commande **refuse avant d'écrire** si le service visé n'existe pas, et liste alors ceux de la
|
|
36
|
+
cible. Elle refuse aussi de s'auto-injecter.
|
|
37
|
+
|
|
38
|
+
## Pourquoi ne pas l'écrire à la main — c'est mesuré
|
|
39
|
+
|
|
40
|
+
Lâché dans une application fraîche sans accès aux sources du framework, un agent produit une
|
|
41
|
+
classe à méthodes `static`. **Elle compile, elle marche, et elle reste invisible au conteneur.**
|
|
42
|
+
Le vérificateur le dit (`orphan-service`), mais seulement si on le lance :
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Billing porte @injectable mais n'est déclaré nulle part — sans @services([Billing])
|
|
46
|
+
sur le module, il n'entre pas dans l'ordre de démarrage, échappe au rapport de boot
|
|
47
|
+
et à l'introspection, et n'est construit qu'à la première requête qui le réclame
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Deux noms, et ils ne servent pas à la même chose
|
|
51
|
+
|
|
52
|
+
C'est le piège n°1, et il ne produit aucune erreur — juste un `undefined` :
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
@injectable() // ← nomme la CLASSE : @inject("BillingService")
|
|
56
|
+
export class BillingService extends Service {
|
|
57
|
+
constructor() {
|
|
58
|
+
super("billing", ...); // ← nomme l'INSTANCE : container.get("billing")
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Obtenir un service depuis un autre — deux voies, un choix
|
|
64
|
+
|
|
65
|
+
| Voie | Quand |
|
|
66
|
+
| ------------------------------ | ----------------------------------------------------------------------------- |
|
|
67
|
+
| `@inject("X")` au constructeur | **par défaut** — la dépendance est déclarée, l'ordre de démarrage la respecte |
|
|
68
|
+
| `container.get("x")` à l'usage | quand la dépendance est facultative, tardive, ou choisie à l'exécution |
|
|
69
|
+
|
|
70
|
+
`create service --inject` pose la première. Le second est visible partout dans les exemples, et
|
|
71
|
+
c'est pour cela qu'il est sur-employé : **déclarer vaut mieux que chercher.**
|
|
72
|
+
|
|
73
|
+
## Prouver
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx nodefony doctor # « porte @injectable mais n'est déclaré nulle part »
|
|
77
|
+
npx nodefony inspect services # ce que le conteneur porte VRAIMENT au démarrage
|
|
78
|
+
npm test
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`inspect services` est le seul juge : ni la compilation ni les tests ne voient un service absent
|
|
82
|
+
du conteneur — les tests l'instancient eux-mêmes.
|
|
83
|
+
|
|
84
|
+
## Voisins
|
|
85
|
+
|
|
86
|
+
| Besoin | Skill |
|
|
87
|
+
| ------------------------------ | ------------------------------- |
|
|
88
|
+
| Une ressource complète stockée | `nodefony-add-crud` |
|
|
89
|
+
| Réserver une route | `nodefony-protect-route` |
|
|
90
|
+
| Un flux temps réel | `nodefony-add-realtime-channel` |
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodefony-browser
|
|
3
|
+
description: >
|
|
4
|
+
Ouvre un écran de ton application dans un navigateur piloté pour le VOIR et surtout le MESURER —
|
|
5
|
+
contrastes et tailles réellement calculés par le moteur de rendu, audit d'accessibilité par
|
|
6
|
+
axe-core, erreurs de console, requêtes HTTP, frames WebSocket. Fonctionne sur ta machine
|
|
7
|
+
(Playwright) ou dans un conteneur jetable, au choix. Porte les sondes prêtes à l'emploi, le choix
|
|
8
|
+
du thème clair ou sombre — un défaut d'affichage n'existe souvent que dans l'un des deux —, les
|
|
9
|
+
contraintes de réseau qui font répondre `421` ou `401` à une application pourtant saine, et les
|
|
10
|
+
pièges qui font conclure FAUX : mesurer avant que l'écran soit peuplé, observer un bundle qui
|
|
11
|
+
n'est plus celui du code, prendre une condition d'arrêt qui réussit toujours. Sait aussi piloter
|
|
12
|
+
un socket temps réel de bout en bout : accueil, abonnement à un canal, action, latence médiane,
|
|
13
|
+
pont API, reconnexion. À charger AVANT de conclure quoi que ce soit sur un écran.
|
|
14
|
+
Déclencheurs : "regarde l'écran", "vérifie l'affichage", "est-ce que ça s'affiche ?",
|
|
15
|
+
"montre-moi la page", "lis la console", "y a-t-il des erreurs JS ?", "mesure le contraste",
|
|
16
|
+
"cette couleur est-elle lisible ?", "capture d'écran", "vérifie l'accessibilité",
|
|
17
|
+
"audit accessibilité", "audit WCAG", "en mode clair", "en mode sombre", "le thème sombre",
|
|
18
|
+
"la page est-elle rapide ?", "temps de chargement", "responsive ?",
|
|
19
|
+
"quelles requêtes fait la page ?", "le temps réel arrive-t-il jusqu'à l'écran ?",
|
|
20
|
+
"teste le socket", "mesure la latence du websocket", "le canal pousse-t-il ?",
|
|
21
|
+
"l'application démarre-t-elle vraiment ?".
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# see-screen — voir et MESURER un écran
|
|
25
|
+
|
|
26
|
+
> ⚖️ **La confiance n'exclut pas le contrôle.** Un `curl` prouve qu'une route répond ; il ne dit
|
|
27
|
+
> pas si l'écran se monte, s'alimente et ne crie pas dans la console.
|
|
28
|
+
|
|
29
|
+
## Le geste — sur ta machine
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm i -D playwright
|
|
33
|
+
npx playwright install chromium
|
|
34
|
+
node node_modules/@nodefony/devkit/skills/nodefony-browser/scripts/inspect.mjs /
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
C'est tout. Les sondes **constatent** où elles s'exécutent : sur ta machine elles visent
|
|
38
|
+
`https://127.0.0.1:5152` et déposent leurs captures dans `tmp/browser/`. Rien à configurer tant que
|
|
39
|
+
tu ne changes pas de port — et `NF_BROWSER_BASE` est là si tu le changes.
|
|
40
|
+
|
|
41
|
+
Playwright est un **pair optionnel** — quelques mégaoctets, pas cent : il serait déraisonnable
|
|
42
|
+
d'imposer un navigateur complet à qui n'a pas besoin de regarder un écran. Tant qu'il manque, les
|
|
43
|
+
sondes s'arrêtent en le disant, avec la commande exacte à taper — jamais sur un « module
|
|
44
|
+
introuvable » nu.
|
|
45
|
+
|
|
46
|
+
### Le navigateur : celui que tu as déjà, avant d'en télécharger un
|
|
47
|
+
|
|
48
|
+
Les sondes essaient, dans l'ordre : le **`chromium`** du pilote, puis **`chrome`**, puis
|
|
49
|
+
**`msedge`** — les deux derniers étant ceux DÉJÀ installés sur la machine. Sous Windows, Edge est
|
|
50
|
+
préinstallé : rien à télécharger. Le champ **`browserName`** de la sortie dit lequel a servi, parce
|
|
51
|
+
que deux mesures faites par des navigateurs différents ne se comparent pas.
|
|
52
|
+
|
|
53
|
+
Si aucun ne répond, la sonde s'arrête (code 69) en donnant la commande — le téléchargement se fait
|
|
54
|
+
**une fois par machine**, dans un cache utilisateur partagé par tous tes projets, jamais dans
|
|
55
|
+
`node_modules` :
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx playwright install chromium
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`NF_BROWSER_ENGINE=chrome` (ou `msedge`, `chromium`) impose un navigateur précis. Un choix explicite
|
|
62
|
+
n'est **jamais** complété par un repli : se rabattre en silence attribuerait la mesure au mauvais
|
|
63
|
+
moteur. ⚠️ À ne pas confondre avec `NF_BROWSER_CHANNEL`, qui désigne le canal d'un socket
|
|
64
|
+
applicatif — le mot « canal » sert aux deux dans des mondes différents.
|
|
65
|
+
|
|
66
|
+
## L'autre voie — en conteneur, et QUAND s'en servir
|
|
67
|
+
|
|
68
|
+
🔴 **Le conteneur est un DERNIER RECOURS, jamais le réflexe.** Playwright pilote un navigateur déjà
|
|
69
|
+
présent sur ta machine — il n'y a le plus souvent rien à installer ni à démarrer. Le conteneur ne se
|
|
70
|
+
justifie que par les trois lignes « conteneur » du tableau ci-dessous ; ailleurs, il rallonge tout
|
|
71
|
+
(copie des sondes à chaque modification, nom d'hôte particulier, ports publiés) pour un résultat
|
|
72
|
+
identique.
|
|
73
|
+
|
|
74
|
+
Le conteneur n'est pas « la bonne façon » : c'est un compromis, et il se choisit sur ce que tu es en
|
|
75
|
+
train de faire.
|
|
76
|
+
|
|
77
|
+
<!-- prettier-ignore -->
|
|
78
|
+
| Ce que tu fais | La voie | Pourquoi |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| Corriger un écran et vérifier ta correction | **locale** | la boucle est bien plus courte — pas de copie vers un conteneur entre deux essais |
|
|
81
|
+
| Regarder une page vite fait, lire la console | **locale** | une commande, rien à démarrer |
|
|
82
|
+
| **Comparer une mesure** dans le temps ou entre machines | **conteneur** | l'image est épinglée par empreinte : le même navigateur aujourd'hui et dans six mois. Un contraste ne bouge pas d'une version de navigateur à l'autre — un temps de rendu ou un Web Vital, si |
|
|
83
|
+
| **Intégration continue** | **conteneur** | un exécuteur sans interface graphique a déjà tout ; même décor qu'en local |
|
|
84
|
+
| Piloter une session authentifiée avec des identifiants **sensibles** | **conteneur** | le navigateur n'y voit ni ton disque ni ton réseau local |
|
|
85
|
+
| Tu ne veux **rien** installer, ou ta machine n'a pas les bibliothèques | **conteneur** | l'image porte navigateur, pilote et dépendances système |
|
|
86
|
+
|
|
87
|
+
Ce que le conteneur coûte, en revanche : le démarrer, recopier les sondes à chaque modification,
|
|
88
|
+
joindre ton application par un nom particulier (ci-dessous), et publier les ports. Sur une boucle de
|
|
89
|
+
correction, cela se paie à chaque tour.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
docker compose --profile browser up -d
|
|
93
|
+
docker cp node_modules/@nodefony/devkit/skills/nodefony-browser/scripts/. mon-app-browser:/app/see-screen
|
|
94
|
+
docker cp node_modules/axe-core/axe.min.js mon-app-browser:/app/see-screen/axe.min.js
|
|
95
|
+
docker exec mon-app-browser node /app/see-screen/inspect.mjs /
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Le conteneur s'appelle **`<nom-de-ton-app>-browser`** — fixé par le `compose.yaml`, rien à chercher.
|
|
99
|
+
|
|
100
|
+
> Le **`/.`** de la copie n'est pas décoratif : il copie le CONTENU du dossier. Sans lui, une
|
|
101
|
+
> seconde copie **imbrique** un dossier de plus au lieu de remplacer, et tu relances alors une
|
|
102
|
+
> version périmée des sondes en croyant les avoir mises à jour — sans le moindre message.
|
|
103
|
+
|
|
104
|
+
La troisième ligne emporte `axe-core`, qui vit dans les dépendances de ton projet et n'est donc pas
|
|
105
|
+
dans le dossier des sondes. Sans elle, la famille `axe` s'annonce **indisponible** et te donne cette
|
|
106
|
+
commande — elle ne rend jamais un verdict qu'elle n'a pas mesuré.
|
|
107
|
+
|
|
108
|
+
Ces commandes tiennent chacune sur **une ligne** et n'emploient ni substitution, ni tube, ni
|
|
109
|
+
continuation : elles passent telles quelles dans un terminal Linux, macOS, PowerShell ou `cmd.exe`.
|
|
110
|
+
|
|
111
|
+
> **Ne demande jamais à l'utilisateur de jouer la sonde** (« recharge et dis-moi ce que dit la
|
|
112
|
+
> console »). C'est le travail de cet outil, quelle que soit la voie choisie.
|
|
113
|
+
|
|
114
|
+
## Ce que la sonde rend, et qu'une capture ne dit pas
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
NF_BROWSER_LOGIN=/login NF_BROWSER_USER=admin NF_BROWSER_PASSWORD=secret NF_BROWSER_PROBES="bouton principal=button[type=submit],titre=h1" node node_modules/@nodefony/devkit/skills/nodefony-browser/scripts/inspect.mjs /tableau-de-bord "Chiffre d affaires"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
<details><summary>La même chose en conteneur (dernier recours)</summary>
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
docker exec -e NF_BROWSER_LOGIN=/login -e NF_BROWSER_USER=admin -e NF_BROWSER_PASSWORD=secret -e "NF_BROWSER_PROBES=bouton principal=button[type=submit],titre=h1" mon-app-browser node /app/see-screen/inspect.mjs /tableau-de-bord "Chiffre d affaires"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
</details>
|
|
127
|
+
|
|
128
|
+
Le troisième argument est un **texte discriminant** attendu avant toute mesure (voir les pièges).
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"url": "https://host.docker.internal:5152/tableau-de-bord",
|
|
133
|
+
"theme": "light",
|
|
134
|
+
"lang": "fr",
|
|
135
|
+
"title": "Mon application",
|
|
136
|
+
"scripts": ["/static/index-B7fK2p.js"],
|
|
137
|
+
"probes": [
|
|
138
|
+
{
|
|
139
|
+
"label": "bouton principal",
|
|
140
|
+
"text": "Enregistrer",
|
|
141
|
+
"color": "rgb(255, 255, 255)",
|
|
142
|
+
"background": "rgb(0, 87, 156)",
|
|
143
|
+
"contrast": 7.39,
|
|
144
|
+
"font": "16px",
|
|
145
|
+
"wcag": "AAA",
|
|
146
|
+
"size": "243×41"
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"consoleErrors": [],
|
|
150
|
+
"capture": "tmp/browser/tableau-de-bord-….png"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Le contraste est CALCULÉ, pas estimé** — luminances WCAG sur les couleurs que le moteur de rendu
|
|
155
|
+
applique vraiment, fond effectif obtenu en EMPILANT toutes les couches translucides jusqu'au premier
|
|
156
|
+
ancêtre opaque, puis en les composant. C'est ce qui sépare
|
|
157
|
+
« ça me paraît lisible » de « 7,39:1, donc AAA », et ce qui permet de valider une correction de
|
|
158
|
+
palette sans attendre un audit complet.
|
|
159
|
+
|
|
160
|
+
Le champ `wcag` tranche pour toi, parce que le seuil dépend de la **police** et non de la taille du
|
|
161
|
+
bloc : WCAG appelle « large » un texte d'au moins 24 px (ou 18,66 px en gras) et lui applique 3:1 au
|
|
162
|
+
lieu de 4,5:1. Un contraste rendu sans sa police laisse le lecteur choisir son seuil au hasard —
|
|
163
|
+
c'est-à-dire ne rien conclure.
|
|
164
|
+
|
|
165
|
+
**Un sélecteur par élément qui t'intéresse** : un contraste n'existe pas « pour une page », il existe
|
|
166
|
+
pour un élément contre son fond. La sonde ne connaît donc aucun sélecteur — les tiens viennent de
|
|
167
|
+
`NF_BROWSER_PROBES`, et c'est ce qui la garde utilisable quelle que soit ta bibliothèque de
|
|
168
|
+
composants.
|
|
169
|
+
|
|
170
|
+
Réglages par variables d'environnement : `NF_BROWSER_BASE`, `NF_BROWSER_PAGE`, `NF_BROWSER_EXPECT`,
|
|
171
|
+
`NF_BROWSER_LOGIN`, `NF_BROWSER_USER`, `NF_BROWSER_PASSWORD`, `NF_BROWSER_PROBES`
|
|
172
|
+
(`libellé=sélecteur`, séparés par des virgules), `NF_BROWSER_FAMILIES`, `NF_BROWSER_WIDTHS`,
|
|
173
|
+
`NF_BROWSER_SEUIL_LOURD`, `NF_BROWSER_SEUIL_LENT`, `NF_BROWSER_ACTIONS`, `NF_BROWSER_FULLPAGE`. Le détail vit dans l'en-tête de chaque script.
|
|
174
|
+
|
|
175
|
+
### Un écran qui n'existe qu'après un GESTE — `NF_BROWSER_ACTIONS`
|
|
176
|
+
|
|
177
|
+
Certaines pages ne sont pas un état mais un **parcours** : un formulaire qui ne déplie ses questions
|
|
178
|
+
qu'après un choix, un menu qui s'ouvre au survol, un panneau qui demande un second clic.
|
|
179
|
+
Photographier sans agir fait conclure « le champ n'y est pas » alors qu'on ne l'a jamais ouvert.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
NF_BROWSER_ACTIONS="Nouvelle commande|voir:Moyen de paiement" node node_modules/@nodefony/devkit/skills/nodefony-browser/scripts/inspect.mjs /commandes "Commandes"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Grammaire : `verbe:cible[=valeur]`, séquence séparée par `|`, verbe facultatif (`clic` par défaut),
|
|
186
|
+
cible cherchée d'abord comme **texte visible** puis comme sélecteur CSS. Verbes : `clic` · `double`
|
|
187
|
+
· `droit` (clic droit) · `survol` · `saisir:Nom=valeur` · `touche:Enter` · `voir` · `defiler:600`
|
|
188
|
+
· `attendre`.
|
|
189
|
+
|
|
190
|
+
- 🔴 **`voir` plutôt qu'un clic pour amener dans la vue** : sur un formulaire, le texte d'une
|
|
191
|
+
question est un `label` — cliquer dessus coche la case qu'il décrit, et tu observes alors un écran
|
|
192
|
+
que l'observation a modifié.
|
|
193
|
+
- ⚠️ **`defiler` n'est pas `NF_BROWSER_FULLPAGE=1`** : une application dont le contenu défile dans
|
|
194
|
+
un conteneur interne (toute interface à barre latérale fixe) ne grandit pas — la capture « page
|
|
195
|
+
entière » y rend exactement la fenêtre, et l'on croit que ce qui est plus bas n'existe pas.
|
|
196
|
+
- Une action dont la cible est introuvable **arrête la sonde** (code 65) en la nommant : une mesure
|
|
197
|
+
faite sur un écran qu'on n'a pas ouvert est pire qu'aucune mesure.
|
|
198
|
+
|
|
199
|
+
**`NF_BROWSER_LOGIN` n'a pas de défaut** : c'est le chemin du formulaire de connexion de **ton**
|
|
200
|
+
application. Il n'en existe pas d'universel, et deviner enverrait la sonde sur une page inexistante,
|
|
201
|
+
où elle mesurerait un écran d'erreur en croyant s'être authentifiée. Sans lui, un identifiant posé
|
|
202
|
+
fait s'arrêter la sonde avec un message — jamais une mesure fausse.
|
|
203
|
+
|
|
204
|
+
Les sondes de couleur cherchent tes sélecteurs, pas ceux d'une bibliothèque : le thème est lu sur le
|
|
205
|
+
`color-scheme` **calculé** (ce que le moteur applique) et sur `data-theme`. Si ton application marque
|
|
206
|
+
son thème autrement, sonde-le comme n'importe quel autre élément.
|
|
207
|
+
|
|
208
|
+
### Mesurer dans le thème que tu veux — pas seulement celui par défaut
|
|
209
|
+
|
|
210
|
+
**Un défaut d'affichage n'existe souvent que dans UN des deux thèmes.** Vécu : un libellé de menu
|
|
211
|
+
actif à **1,62:1** en clair — illisible — et impeccable en sombre, où la même variable de couleur
|
|
212
|
+
rend une nuance opposée. Tant qu'on ne regarde qu'un seul thème, la palette paraît saine.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
NF_BROWSER_COLOR_SCHEME=light NF_BROWSER_FAMILIES=axe node .../scripts/inspect.mjs /tableau-de-bord "Chiffre d affaires"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`NF_BROWSER_COLOR_SCHEME` émule `prefers-color-scheme` — la média query standard, comprise quelle
|
|
219
|
+
que soit ta trousse d'interface. Valeurs : `light`, `dark`, `no-preference` ; toute autre est
|
|
220
|
+
**refusée** (code 64), car l'accepter ferait mesurer le thème par défaut en croyant tenir l'autre.
|
|
221
|
+
|
|
222
|
+
Si ton application **mémorise** le choix de l'utilisateur, elle n'obéit plus à cette média query :
|
|
223
|
+
donne alors la clé de stockage, qui t'appartient — le code ne la devine pas.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
NF_BROWSER_STORAGE="ma-cle-de-theme=light" node .../scripts/inspect.mjs /
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Le champ `theme` de la sortie dit ce qui a été RÉELLEMENT appliqué. Vérifie-le : c'est ainsi qu'on
|
|
230
|
+
sait qu'on a mesuré le bon écran.
|
|
231
|
+
|
|
232
|
+
## Les familles de sondes — activables, jamais un mur de JSON
|
|
233
|
+
|
|
234
|
+
Le socle ci-dessus sort toujours. Le reste s'active par famille, chacune rendant un **verdict**
|
|
235
|
+
(`OK`/`ALERTE`) et des données bornées — comptes et 3 exemples, jamais l'inventaire :
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
NF_BROWSER_FAMILIES=axe,perf,reseau node .../scripts/inspect.mjs /tableau-de-bord "Chiffre d affaires"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| Famille | Question à laquelle elle répond |
|
|
242
|
+
| ------------ | -------------------------------------------------------------------------------------------------- |
|
|
243
|
+
| **`axe`** | **Audit WCAG complet par `axe-core` — une centaine de règles, dont le contraste de TOUT le texte** |
|
|
244
|
+
| `a11y` | Étiquettes, noms accessibles, hiérarchie des titres, cibles < 24 px, arbre d'accessibilité |
|
|
245
|
+
| `rendu` | Débordement horizontal, éléments hors viewport, polices RÉELLEMENT chargées |
|
|
246
|
+
| `reseau` | Requêtes, échecs, ressources lourdes et lentes, octets réellement transférés |
|
|
247
|
+
| `perf` | TTFB, FCP, LCP, CLS, tâches longues — verdict sur les seuils Web Vitals |
|
|
248
|
+
| `stockage` | Attributs des cookies et inventaire du Web Storage — **jamais les valeurs** |
|
|
249
|
+
| `responsive` | Le débordement horizontal rejoué à plusieurs largeurs (`NF_BROWSER_WIDTHS`) |
|
|
250
|
+
|
|
251
|
+
`NF_BROWSER_FAMILIES=toutes` active tout ; un nom inconnu est **refusé** (code 64), jamais ignoré.
|
|
252
|
+
Ce que chaque champ veut dire, comment lire un verdict, et **quand chaque famille se trompe** :
|
|
253
|
+
[`references/sondes.md`](references/sondes.md) — à lire avant de conclure sur un `ALERTE`.
|
|
254
|
+
|
|
255
|
+
> 🔴 **Pour l'accessibilité, prends `axe` — pas `a11y` seule, et n'écris JAMAIS ton propre calcul.**
|
|
256
|
+
> Les règles WCAG sont pleines de cas particuliers qu'on ne devine pas : canaux en 0–1 des
|
|
257
|
+
> couleurs CSS modernes, fonds semi-transparents à composer sur ce qu'il y a dessous, texte peint
|
|
258
|
+
> par une police en couleurs, éléments masqués aux seules techniques d'assistance. Une sonde écrite
|
|
259
|
+
> à la main les rate et produit des échecs inventés **qui noient les vrais** — mesuré : quarante et
|
|
260
|
+
> un faux positifs contre sept défauts réels, dont celui qu'on cherchait, invisible au milieu.
|
|
261
|
+
> `axe-core` est le moteur qu'embarque Lighthouse pour ce volet ; `a11y` reste utile pour ce qu'il
|
|
262
|
+
> ne fait pas — l'arbre d'accessibilité brut et les cibles trop petites.
|
|
263
|
+
>
|
|
264
|
+
> `axe` distingue trois choses, et la nuance compte : les **manquements** (avérés, ils font
|
|
265
|
+
> l'alerte), les cas **à vérifier** (le moteur refuse de trancher — un fond en image, par exemple —
|
|
266
|
+
> et ce n'est PAS un défaut), et les règles **conformes**. Chaque manquement rend jusqu'à cinq
|
|
267
|
+
> cibles distinctes plus le compte des autres : une même règle couvre des défauts à des endroits
|
|
268
|
+
> différents, qui ne se corrigent pas d'un seul geste.
|
|
269
|
+
|
|
270
|
+
## Auditer la page comme un moteur de recherche et un agent la voient — `audit.mjs`
|
|
271
|
+
|
|
272
|
+
Lighthouse complet, y compris **derrière une authentification** — ce que l'extension du navigateur
|
|
273
|
+
ne sait pas faire sur une application protégée.
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
npm i -D lighthouse
|
|
277
|
+
NF_BROWSER_LOGIN=/login NF_BROWSER_USER=admin NF_BROWSER_PASSWORD=secret node .../scripts/audit.mjs /tableau-de-bord
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Il rend les scores des cinq catégories — dont **`agentic-browsing`**, qui note ce qu'un agent
|
|
281
|
+
d'intelligence artificielle trouve en arrivant sur ta page : arbre d'accessibilité bien formé,
|
|
282
|
+
stabilité visuelle, annotations **WebMCP** de tes formulaires, outils déclarés, et présence d'un
|
|
283
|
+
`llms.txt`. Puis les audits ratés, **classés par poids** — ce qui coûte le plus à ta note, en
|
|
284
|
+
premier.
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"verdict": "ALERTE",
|
|
289
|
+
"decor": { "appareil": "desktop", "bridage": "simulate" },
|
|
290
|
+
"scores": {
|
|
291
|
+
"performance": 30,
|
|
292
|
+
"accessibility": 93,
|
|
293
|
+
"best-practices": 100,
|
|
294
|
+
"seo": 91,
|
|
295
|
+
"agentic-browsing": 96
|
|
296
|
+
},
|
|
297
|
+
"failedAudits": { "total": 22, "examples": [] },
|
|
298
|
+
"fullReport": "tmp/browser/lighthouse-….json"
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Le rapport COMPLET est déposé à côté : le résumé sert à décider, l'original à vérifier et à
|
|
303
|
+
comparer dans le temps. Tu peux l'ouvrir tel quel dans une visionneuse Lighthouse.
|
|
304
|
+
|
|
305
|
+
**Trois choses à savoir, sans quoi les chiffres trompent :**
|
|
306
|
+
|
|
307
|
+
- **Le `decor` fait partie de la mesure.** Un score de performance sans son appareil ne veut rien
|
|
308
|
+
dire. Le défaut est `desktop` ; `NF_BROWSER_FORMFACTOR=mobile` simule un téléphone bridé, et les
|
|
309
|
+
chiffres n'ont alors plus rien à voir.
|
|
310
|
+
- **Ne juge pas la performance d'un serveur de DÉVELOPPEMENT.** Modules servis un par un, sources
|
|
311
|
+
non minifiées, rechargement à chaud : la note s'effondre pour des raisons qui n'existent pas en
|
|
312
|
+
production. Cette catégorie ne se mesure que sur une version bâtie.
|
|
313
|
+
- **Un audit sans score n'a pas échoué** — il ne s'applique pas. Les audits WebMCP et `llms.txt`
|
|
314
|
+
sortent ainsi tant que tu ne les as pas mis en place ; c'est une indication, pas un reproche.
|
|
315
|
+
|
|
316
|
+
> **Comment l'authentification survit** alors que Lighthouse ouvre son propre onglet : le navigateur
|
|
317
|
+
> est lancé avec un profil PERSISTANT et un port de débogage ; on s'y connecte, puis Lighthouse s'y
|
|
318
|
+
> branche et hérite du profil. Et `disableStorageReset` est posé — sans lui, Lighthouse **vide le
|
|
319
|
+
> stockage** avant de mesurer, donc les témoins de session, et audite l'écran de connexion sans le
|
|
320
|
+
> dire.
|
|
321
|
+
|
|
322
|
+
## Observer ce qui se PASSE — `watch.mjs`
|
|
323
|
+
|
|
324
|
+
`inspect.mjs` photographie un instant ; celui-ci regarde le temps qui coule. C'est la seule façon de
|
|
325
|
+
voir une frame qui n'arrive pas, un canal qui pousse trop, une reconnexion en boucle — rien de tout
|
|
326
|
+
cela n'apparaît sur une capture.
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
docker exec mon-app-browser node /app/see-screen/watch.mjs /tableau-de-bord 7000
|
|
330
|
+
docker exec -e "NF_BROWSER_UNTIL=() => document.querySelectorAll('tbody tr').length >= 3" mon-app-browser node /app/see-screen/watch.mjs /articles 8000
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
La seconde s'arrête sur une **condition applicative** plutôt que sur une durée. Note les guillemets :
|
|
334
|
+
doubles à l'extérieur, simples à l'intérieur de l'expression — l'inverse ne survit pas à `cmd.exe`.
|
|
335
|
+
|
|
336
|
+
Il rend les **sockets et leurs frames horodatées dans les deux sens**, les réponses HTTP ≥ 400, les
|
|
337
|
+
erreurs de console et le verdict de la condition. Un controller temps réel se vérifie ainsi de bout
|
|
338
|
+
en bout : le message part-il, revient-il, et l'écran le reçoit-il ?
|
|
339
|
+
|
|
340
|
+
> 🔴 **Une condition d'arrêt se vérifie avec une condition IMPOSSIBLE.** Une chaîne passée à
|
|
341
|
+
> `waitForFunction` est évaluée comme une **expression** : `() => x` y **définit** une fonction sans
|
|
342
|
+
> jamais l'appeler, l'objet fonction est truthy, et l'attente réussit **toujours** — même sur une
|
|
343
|
+
> condition qui ne peut pas être vraie. La sonde invoque désormais les formes fonction ; le principe,
|
|
344
|
+
> lui, vaut pour toute attente que tu écriras : tant qu'elle n'a pas échoué une fois, elle ne
|
|
345
|
+
> discrimine rien.
|
|
346
|
+
|
|
347
|
+
## Piloter le socket de bout en bout — `socket.mjs`
|
|
348
|
+
|
|
349
|
+
`watch.mjs` regarde le trafic d'une page ; celui-ci **conduit** : il ouvre un socket temps réel
|
|
350
|
+
depuis la page (cookies et `Origin` réels), attend l'accueil, s'abonne à un canal, appelle une
|
|
351
|
+
action, mesure la latence médiane, rejoue une route par le pont API, ferme et se reconnecte — un
|
|
352
|
+
verdict par étape.
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
docker exec -e NF_BROWSER_API=/api/sante mon-app-browser node /app/see-screen/socket.mjs /chat/realtime
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Le chemin du endpoint est **requis** (1er argument ou `NF_BROWSER_SOCKET`) : c'est une route de ton
|
|
359
|
+
application, rien n'est deviné. `NF_BROWSER_CHANNEL` choisit le canal (défaut : le premier annoncé
|
|
360
|
+
par l'accueil) ; `NF_BROWSER_ACTION` une action RPC ; sans méthode corrélée la latence est
|
|
361
|
+
`NON MESURÉE` — jamais un zéro inventé, car la notification `ping` n'a pas de pong.
|
|
362
|
+
|
|
363
|
+
Le protocole du fil (les quatre formes de frame), la lecture de chaque verdict — dont
|
|
364
|
+
`SILENCIEUX`, qui n'est **pas** « cassé » — et les pièges :
|
|
365
|
+
[`references/socket.md`](references/socket.md).
|
|
366
|
+
|
|
367
|
+
## Trois contraintes de réseau — chacune imite un bug applicatif
|
|
368
|
+
|
|
369
|
+
<!-- prettier-ignore -->
|
|
370
|
+
| Contrainte | Ce qui arrive sinon |
|
|
371
|
+
| --- | --- |
|
|
372
|
+
| Joindre l'app par **`host.docker.internal`** | `localhost` désigne le CONTENEUR, pas ta machine. Et si tu as activé `domainCheck`, ajoute ce nom aux `trustedHosts` en développement : sinon la barrière répond **`421`** alors que le réseau passe. |
|
|
373
|
+
| Passer par **HTTPS** | Le cookie de session est `secure` : sur une origine `http://` non-`localhost`, le navigateur le **jette**, et tout revient en **`401`** — ce qui se lit à tort comme un login qui rate. |
|
|
374
|
+
| **Rien à poser** pour rendre Vite joignable | L'origine des assets se dérive du `Host` de ta requête : arriver par `host.docker.internal` suffit — l'allowlist Vite et le WebSocket du rechargement à chaud suivent le même nom, et ton poste reste servi sur `127.0.0.1` en même temps. |
|
|
375
|
+
|
|
376
|
+
Si la page annonce quand même `127.0.0.1` depuis le conteneur, c'est que le nom ne franchit pas
|
|
377
|
+
`trustedHosts`, ou qu'une `publicOrigin` explicite est configurée dans `nodefony.config.ts` — un
|
|
378
|
+
réglage durable gagne toujours sur une déduction.
|
|
379
|
+
|
|
380
|
+
## Pièges — chacun a déjà fait conclure faux
|
|
381
|
+
|
|
382
|
+
- **🔴 Mesurer trop tôt.** Une application se monte, PUIS demande ses données. Attendre un « réseau
|
|
383
|
+
calme » te fait mesurer un écran vide, avec des sondes absentes et des `401` encore en vol.
|
|
384
|
+
Attends un **texte discriminant de la page visée** — pas un texte présent aussi sur l'écran de
|
|
385
|
+
connexion (le nom de l'application aboutit dans les deux cas : il ne prouve rien).
|
|
386
|
+
- **🔴 Le bundle servi n'est pas toujours celui que tu as bâti.** À contrôler AVANT d'accuser ton
|
|
387
|
+
code, sinon tu débogues une génération précédente. Le champ **`scripts`** rendu par `inspect.mjs`
|
|
388
|
+
donne les fichiers réellement servis à la page : compare-les à ceux que désigne l'`index.html`
|
|
389
|
+
produit dans `dist/frontend/` de ton module. Deux valeurs différentes ⇒ rebâtis, **redémarre le
|
|
390
|
+
serveur** (le service d'assets lit son `index.html` au démarrage), puis redémarre le conteneur —
|
|
391
|
+
son cache HTTP survit à un simple rechargement.
|
|
392
|
+
- **Les erreurs de console d'un parcours de connexion ne sont pas des défauts.** Se connecter
|
|
393
|
+
produit des `401` sur la vérification d'identité ; ils disparaissent dès que l'état
|
|
394
|
+
d'authentification est réutilisé.
|
|
395
|
+
- **Une capture ne s'écrase pas.** Réutiliser un nom laisse l'ancienne image en place pendant que
|
|
396
|
+
l'appel répond « OK » : tu lis un écran périmé. Les sondes horodatent — ne le contourne pas.
|
|
397
|
+
- **Un état d'authentification sauvegardé peut être périmé** (session expirée, serveur redémarré).
|
|
398
|
+
Les sondes le constatent et refont le parcours plutôt que de mesurer l'écran de connexion.
|
|
399
|
+
- **Chaque compte a SON état sauvegardé** (le fichier porte l'identifiant). C'est ce qui permet
|
|
400
|
+
d'enchaîner deux sondes sous deux comptes — comparer ce que voit un administrateur et ce que voit
|
|
401
|
+
un utilisateur ordinaire — sans que la seconde reprenne la session de la première. Sans cela on
|
|
402
|
+
demande une mesure sous un compte et l'on obtient celle de l'autre, sans aucun message.
|
|
403
|
+
|
|
404
|
+
## L'autre voie : le serveur MCP du conteneur
|
|
405
|
+
|
|
406
|
+
La même image expose un serveur MCP (`http://127.0.0.1:3001/mcp`) auquel un agent se branche pour
|
|
407
|
+
**explorer** une page interactivement. Prends-le pour cela — et le pilotage direct ci-dessus pour
|
|
408
|
+
tout le reste : une commande, un JSON, un code de retour, quelques secondes. Le protocole
|
|
409
|
+
intermédiaire coûte plusieurs fois ce temps, ne rend pas de valeur exploitable par un script, et
|
|
410
|
+
sa session peut tomber sous toi.
|
|
411
|
+
|
|
412
|
+
## Ce que ce navigateur ne remplace pas
|
|
413
|
+
|
|
414
|
+
Le rechargement à chaud, l'animation et le rendu fin (polices, sous-pixel) se jugent dans un vrai
|
|
415
|
+
navigateur, sur ton poste. Celui-ci répond à « l'écran se monte-t-il, s'alimente-t-il, crie-t-il ? »
|
|
416
|
+
— et en tire des nombres.
|