diff --git a/CLAUDE.md b/CLAUDE.md
index 861d854..64f7b52 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -320,20 +320,39 @@ to signal it. `ViewLanguageTest` checks the whole static body for that reason
sentence it carries must have its translation. The rest of the text is born in JavaScript and
only shows at render time: that check is done at the browser, before pushing.
-**Renaming an identifier here needs a rendered check, not a compiler.** The template's code
-holds names that are *also* frozen JSON keys — `nom`, `valeur`, `racine`, `chemin`,
-`elagage` — read from and written to disk. A rename must therefore leave `.nom`, `nom:` and
-`"nom"` alone and touch only the bare identifier; that much a script can do. What it cannot
-see is the **object-literal shorthand**: `{...c, pkg, runs: [i]}` names a property by naming
-a variable, so renaming the variable renames the property, and the reader two hundred lines
-below still asks for the old one. Nothing fails at parse time, no test written in Java can
-reach it, and the view simply comes up empty. It happened on 29 August 2026, and it is the
-snapshot of the rendered strings that said so — not the 258 tests, which stayed green.
+**The identifiers are English; the keys are not, and that line is the whole difficulty.** The
+template's code holds names that are *also* frozen JSON keys — `nom`, `valeur`, `racine`,
+`chemin`, `elagage`, `coupes`, `ordre` — read from and written to disk, so a rename must
+leave `.nom`, `nom:` and `"nom"` alone and touch only the bare identifier. The page's own
+internals have no such constraint: the block loader's members are English (`load`, `store`,
+`touch`, `evict`, `pending`, `pinned`, `_queue`), because no file on disk names them —
+`XR.bloc` is the single name the generated `vue/*.js` know, and it stays.
+
+**Three traps, each of which shipped a defect.** A script that renames bare identifiers gets
+all three wrong unless it is told:
+
+- **The object-literal shorthand.** `{...c, pkg, runs: [i]}` names a property by naming a
+ variable. Renaming the variable renames the property (the view came up empty, 29 August);
+ *skipping* it leaves a reference to a variable that no longer exists (`noeuds is not
+ defined`, the same day). Neither is right: the shorthand must be **expanded** —
+ `noeuds: nodesByName` — which keeps the frozen name and points it at the new variable.
+- **A ternary's colon is not a key.** `? valeur : undefined` reads as `valeur:` to a regex,
+ so the rename skipped it and left a name that had been renamed away. It throws only after
+ a successful save through the served page — the one path the acceptance drives with `curl`
+ rather than a browser, so nothing saw it for a day.
+- **A frozen key inside a string.** Translating the identifiers turned `annotationPatch(R,
+ "nom", …)` into `"name"`, while every reader, the exporter and the save payload went on
+ asking for `nom`. A name typed in the page went into a field nobody read — and the page
+ showed it anyway, because it re-reads the input it just filled. `ViewContractTest` holds
+ that line now: a patched field outside `nom, description, etiquettes, elagage` fails the
+ build.
**The way to verify a change here is to render it.** Before touching the block, take a snapshot
of every string the page displays — both languages, a dozen states of the view — and take the
-same one after: the inversion itself was carried out that way, and the only differences it left
-were the ones intended. A reading of the diff proves nothing; the DOM does.
+same one after: the inversion itself was carried out that way, and so was the rename of the
+last eighty French names, which left **0 differences over 7 734 captured strings**. A reading
+of the diff proves nothing; the DOM does. And the DOM does not prove everything either — it
+never opens the save path, which is why the two defects above needed a test apiece.
## Conventions
diff --git a/orchestrator/src/main/resources/lab/xray/dashboard.html b/orchestrator/src/main/resources/lab/xray/dashboard.html
index 1adc29c..b7219f9 100644
--- a/orchestrator/src/main/resources/lab/xray/dashboard.html
+++ b/orchestrator/src/main/resources/lab/xray/dashboard.html
@@ -893,15 +893,15 @@
* ===================================================================================== */
const XR = {
cache: new Map(), // block name -> what it brought
- ordre: [], // from oldest to most recent
+ lruOrder: [], // from oldest to most recent
scripts: new Map(),
- attente: new Map(), // the same block asked for twice is loaded only once
- PLAFOND: 6,
- _recu: null,
+ pending: new Map(), // the same block asked for twice is loaded only once
+ CAP: 6,
+ _incoming: null,
/** Called by every line of a block while it loads. */
bloc(type, srcKey, val){
- if (XR._recu) XR._recu.push([type, srcKey, val]);
+ if (XR._incoming) XR._incoming.push([type, srcKey, val]);
},
/* Loads follow one another instead of overlapping, and that is not a detail: the
@@ -909,58 +909,58 @@
*
* The setting is deliberately in the page and not in the measurement: it changes in one
* click, with nothing to re-run, and the union recomputes on data already there. */
-let cumul = false;
-try { cumul = localStorage.getItem("runtime-xray.cumul") === "1"; } catch(e){}
-if (D.runs.length < 2) cumul = false;
+let cumulative = false;
+try { cumulative = localStorage.getItem("runtime-xray.cumul") === "1"; } catch(e){}
+if (D.runs.length < 2) cumulative = false;
/** The ticked runs, in the page's order. */
function tickedRuns(){
@@ -1086,7 +1086,7 @@
* same rule JaCoCo applies when merging two {@code .exec} files.
*/
function coverageForView(source){
- if (!cumul) return (R.coverage && R.coverage[source]) || {};
+ if (!cumulative) return (R.coverage && R.coverage[source]) || {};
// The union is read from an index computed at assembly: per line, two bit masks — who
// covered it entirely, who partially. Uniting a subset becomes a binary AND, one
// operation per line, instead of a search per line AND per run.
@@ -1095,8 +1095,8 @@
let maskBits = 0;
for (const i of tickedRuns()) maskBits |= (1 << i);
const out = {};
- for (const [nr, plein, partiel] of index){
- const p = plein & maskBits, q = partiel & maskBits;
+ for (const [nr, fully, partly] of index){
+ const p = fully & maskBits, q = partly & maskBits;
if (!p && !q) continue;
const bits = p | q, which = [];
for (let i = 0; i < D.runs.length; i++) if (bits & (1 << i)) which.push(i);
@@ -1107,7 +1107,7 @@
/** What was typed into the page wins over what the report already carried. */
function annotationOf(run){
const local = annotations[run.uuid] || {};
- const base = annotationEnregistree(run);
+ const base = savedAnnotation(run);
return {
nom: local.nom !== undefined ? local.nom : base.nom,
description: local.description !== undefined ? local.description : base.description,
@@ -1267,9 +1267,9 @@
/** A run's annotation as it is saved: the server first, otherwise the one the page
* carried when it was built. */
-function annotationEnregistree(run){
+function savedAnnotation(run){
const remote = serverAnnotations[run.uuid];
if (remote !== undefined) {
return typeof remote === "string"
@@ -1393,7 +1393,7 @@
* annotating two runs never cross, and on the same run the second is warned instead of
* overwriting the first.
*/
-function enregistrerAnnotations(bouton){
+function persistAnnotations(btn){
const run = R;
- const say = t => { bouton.textContent = t; };
+ const say = t => { btn.textContent = t; };
const a = annotationOf(run);
const tags = a.etiquettes && Object.keys(a.etiquettes).length ? a.etiquettes : undefined;
const el = pruningActive(run) ? pruningOf(run) : undefined;
@@ -1457,7 +1457,7 @@
/** Takes up a noms.json: the runs it names overwrite what we had, the others stay —
* importing is not starting over. */
-function importAnnotations(texte){
- const parsed = JSON.parse(texte);
+function importAnnotations(jsonText){
+ const parsed = JSON.parse(jsonText);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("this file carries no run record");
}
@@ -1503,16 +1503,16 @@