Fusion du lot U3b : le tableau des cinq crans se lit en entier dans le panneau des réglages, sur bureau (1360, 1024) comme sur téléphone, en fr, en, es — les cellules resserrées et les en-têtes repliés à toutes les largeurs (le panneau fait 27rem sur tout bureau) ; C44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 23:00:27 +01:00
co-authored by Claude Opus 5.5
5 changed files with 104 additions and 15 deletions
+2 -2
View File
@@ -106,7 +106,7 @@ web/src/
# · g/[group]/decisions · g/[group]/mandats ·
# g/[group]/protocoles (à venir) · acces (trustinWallet) · messages (la boîte) ;
# /groupes, /decisions… redirigent
web/e2e/ # Playwright C1–C43 + M1, M2 ; serve.sh = serveur jetable /tmp/ld-e2e sur :8092 et
web/e2e/ # Playwright C1–C44 + M1, M2 ; serve.sh = serveur jetable /tmp/ld-e2e sur :8092 et
# chain-mock.mjs (nœud et indexeur Ğ1 simulés) ; faux portefeuille dans helpers.ts
docker/ # app.Dockerfile · app-compose.yml (sonic)
.woodpecker/ # app.yml · maquette.yml
@@ -128,7 +128,7 @@ maquette/ # v2 intacte (frontend Nuxt, backend FastAPI, docker, docs-v
```bash
export PATH=$HOME/.cargo/bin:$PATH
cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace
cd web && npm run check && npm test && npm run build && npm run e2e # e2e : 45 contrôles (47 tests, C25 et C35 en deux)
cd web && npm run check && npm test && npm run build && npm run e2e # e2e : 46 contrôles (48 tests, C25 et C35 en deux)
LD_DEV_LOGIN=1 cargo run -p ld-server # :8002, sert web/build
cd web && npm run dev # :3002 à chaud
@@ -60,6 +60,7 @@ pour une personne, ils coûtent 0,1 Ğ1 réels : c'est un acte « paye » d'Yvv.
| C41 | Qui n'est pas encore invité (lot U3 ; Yvv, 03/11) | sans session : `/`, puis « Se connecter » ; puis en anglais et en espagnol | à l'accueil, à la place du bouton de connexion Ğ1 (pas encore active) : « Pour rentrer dans un groupe de décisions, il faut pour l'instant demander un lien d'invitation à Chiara ou à Yvv. Ce prototype n'est pas encore câblé sur la chaîne. », puis « Tu as reçu un lien d'invitation ? Ouvre-le simplement : il te connecte. » ; sur « Se connecter », le même mot une seule fois, et la ligne Ğ1 ne dit plus « En attendant, on entre avec un lien d'invitation » ; en, es : le même mot traduit |
| C42 | Une poignée tirée sur plusieurs lignes (lot U3, défaut vu par T4) | groupe d'essai : « Réordonner » les engagements fondamentaux ; tirer la poignée du dernier article jusqu'en haut, lentement ; puis flèche bas ; « Annuler » | l'article arrive en place 1, sans revenir en arrière en route ; « « … » en place 1 sur 4. » est dit, le focus est sur lui ; la flèche le descend en place 2, dit aussi |
| C43 | Les informations de fonctionnement là où elles servent (lot U3, ADR 0014) | Monnaie libre : le vote de la Raison d'être, « La formule du seuil », « Réglages de la formule » ; groupe d'essai : « Réordonner » les engagements fondamentaux, puis « N ordre(s) proposé(s) » et « Le vote » de l'ordre en vigueur → « Autres versions » | sous les boutons : « Le vote reste ouvert, sans échéance : tu peux changer ou retirer ton vote à tout moment, le dernier fait foi. » ; la formule : T′ (« votants retenus : 12, T ou Q s'il en manque »), G « déduit de la participation habituelle du groupe », « arrondi à l'entier au-dessus », « Sous le quorum Q de l'article… autant de « pour » que si Q personnes avaient voté » ; les réglages : « Part exigée : celle que le cran demande au-delà du quorum de l'article, à la participation habituelle. », « Elle ne dépend pas du quorum d'un article. », la phrase de G, jamais « d'habitude » ; en réordonnant : « S'il entre en vigueur, seuls les articles de cette section changent de place ; aucun texte ne change. » ; le vote d'un ordre proposé à côté de l'ordre en vigueur : la marge à dépasser, et le texte remplacé qui redevient une proposition ; « Autres versions » : « Si plusieurs versions atteignent leur seuil, celle qui a la marge la plus large entre en vigueur. » (l'inventaire : `U3-informations.md`) |
| C44 | Les réglages de la formule se lisent en entier (lot U3b) | Monnaie libre : « Réglages de la formule » en tête du document, puis depuis le vote d'Unicité du compte ; sur un bureau (1360 × 900, puis 1024 × 768) et sur un téléphone ; en FR, EN, ES | le tableau « Les cinq crans » tient dans le panneau : ses six colonnes, « Voix pour » et ses cinq chiffres compris, sans que le tableau, le panneau ou la page défilent de côté ; les en-têtes peuvent passer sur deux lignes (« Part / exigée », « Voix / pour »), le nom du cran aussi, un chiffre jamais ; depuis le vote, « cet article » sur la ligne du cran 2 ; dans les quatre ambiances |
## Au-delà de la liste
@@ -106,3 +107,4 @@ page, la langue et la taille de l'écran. Les retours se traitent dans trustinWa
| 2026-11-03 | automate | local, lot T3c sur `42c803e` (après la relecture de `e7ab82f` : « à la participation habituelle » à la place de « comme d'habitude » dans F1.2 et N1.1, « le quorum de l'article », « la part exigée », « repose sur moins de 5 propositions », « ramenée » ; aucun contrôle changé) | C1–C38, M1, M2 | 40 / 40 verts (42 tests), du premier coup (19 h 51 - 19 h 55, sous le verrou `/tmp/ld-e2e.lock`) |
| 2026-11-03 | automate | local, lot T4 sur `3996f6f` (les textes d'Yvv du 03/11 pour I1, E1, E2 ; E12 « Responsabilité civique » entre E1 et E2 ; formule, inertie et ordonnancement sortis du document, ADR 0014 : C3, C9, C17, C18, C25, C31, C35, C38 repris ; C35 glisse d'une place) | C1–C38, M1, M2 | 40 / 40 verts (42 tests) à la boucle finale (21 h 47 - 21 h 50, sous le verrou `/tmp/ld-e2e.lock`) ; avant, 39 / 42 (C18, C31, C35 lisaient l'ancien texte d'I1 ou trois articles), puis 41 / 42 (C35 : un glisser de deux places réordonne mais n'est pas annoncé, signalé pour U3) |
| 2026-11-03 | automate | local, lot U3 sur `d15d46e` (l'interface de l'Acte d'engagement : le chapitre « Info ouverture doc de réf. », la séparation, plus les titres « Introduction » et « Engagements fondamentaux », C39 ; « Proposition d'amorce » en fr, en, es, C40 ; le mot pour qui n'est pas invité, C41 ; une poignée tirée sur plusieurs lignes, annoncée et suivie par le focus, C42 ; les informations de fonctionnement là où elles servent, C43 ; C1, C3, C20, C27, C35, C38 suivent les mots) | C1–C43, M1, M2 | 45 / 45 verts (47 tests), du premier coup à la boucle finale (22 h 32 - 22 h 37, sous le verrou `/tmp/ld-e2e.lock`) ; avant le code, C39 à C43 vus rouges (5 / 5) sur `590ccd7` |
| 2026-11-03 | automate | local, lot U3b sur `a6f5e22` (le tableau des cinq crans se lit en entier dans le panneau des réglages, sur bureau comme sur téléphone, en fr, en, es, C44) | C1–C44, M1, M2 | 46 / 46 verts (48 tests), du premier coup à la boucle finale (22 h 55 - 22 h 59, sous le verrou `/tmp/ld-e2e.lock`) ; avant le code, C44 vu rouge sur l'interface de `a47aed0` : 12 cas sur 18 (bureau 1360 et 1024, en tête et depuis une version, trois langues : le tableau défile, 406 à 501 px pour 394), le téléphone vert |
@@ -219,3 +219,8 @@ trouver leur place dans l'ux ». Tranché ainsi (inventaire : `U3-informations.m
- **Réordonner au glisser** : la ligne sous le pointeur se lit à sa place dans la mise en page, pas là
où l'animation la montre : tirée sur plusieurs lignes, la poignée ne revient plus en arrière, et
la nouvelle place est dite.
- **Le panneau des réglages se lit en entier** (lot U3b) : le tableau des cinq crans vit dans le
panneau de droite, 27rem sur tout bureau, à peine plus large que la feuille d'un téléphone ; il en
prend donc les règles partout (cellules resserrées, en-têtes et nom du cran sur deux lignes au
besoin, un chiffre jamais coupé) : « Voix pour » ne se cache plus derrière un défilement de côté,
en français, en anglais, en espagnol (C44 ; captures : `/tmp/ld-u3b-shots/`).
+86 -1
View File
@@ -1,4 +1,4 @@
import { expect, test, type Page } from '@playwright/test';
import { devices, expect, test, type Page } from '@playwright/test';
import { DOC, seedsInForce, shot, signIn, TEST_DOC } from './helpers';
// Lot U3 (Yvv, 2026-11-03, vers 20 h 38 et avant 20 h 44): the rules of operation leave the votes
@@ -255,3 +255,88 @@ test('C43 — les informations de fonctionnement là où elles servent : le vote
await expect(inForce).toContainText('Si plusieurs versions atteignent leur seuil, celle qui a la marge la plus large entre en vigueur.');
await shot(page, 'c43-3-ordre-rival');
});
/**
* What of the table of the five levels is not seen whole in the settings panel: a header or a
* cell that leaves the table or the panel, a text that leaves its cell; the table, the panel or
* the page scrolled sideways to reach it. Empty when every column reads in full.
*/
async function hiddenInSettings(page: Page): Promise<string[]> {
return page.evaluate(() => {
const panel = document.querySelector('aside.panel');
const scroll = panel?.querySelector('.settings .scroll');
const table = scroll?.querySelector('table');
const body = panel?.querySelector('.body');
if (!panel || !scroll || !table || !body) return ['pas de tableau dans le panneau'];
const faults: string[] = [];
for (const [el, what] of [
[scroll, 'le tableau'],
[body, 'le panneau'],
[document.documentElement, 'la page'],
] as const) {
if (el.scrollWidth > el.clientWidth + 1) faults.push(`${what} défile de côté (${el.scrollWidth} > ${el.clientWidth})`);
}
const p = panel.getBoundingClientRect();
const s = scroll.getBoundingClientRect();
const left = Math.max(p.left, s.left, 0);
const right = Math.min(p.right, s.right, document.documentElement.clientWidth);
const cols = table.querySelectorAll('thead th').length;
if (cols !== 6) faults.push(`${cols} colonnes au lieu de 6`);
for (const cell of table.querySelectorAll('th, td')) {
const name = (cell.textContent ?? '').replace(/\s+/g, ' ').trim();
const c = cell.getBoundingClientRect();
const range = document.createRange();
range.selectNodeContents(cell);
const r = range.getBoundingClientRect();
if (c.left < left - 0.5 || c.right > right + 0.5)
faults.push(`« ${name} » : sa cellule sort du panneau (${Math.round(c.left)}–${Math.round(c.right)} hors de ${Math.round(left)}–${Math.round(right)})`);
if (r.left < c.left - 0.5 || r.right > c.right + 0.5) faults.push(`« ${name} » : son texte sort de sa cellule`);
}
return faults;
});
}
test('C44 — les réglages de la formule se lisent en entier : chaque colonne des cinq crans et chaque en-tête dans le panneau, sur bureau et sur téléphone', async ({ page, browser }) => {
// Lot U3b (03/11): on a desktop, the panel cut the table on the right, « Voix pour » out of sight.
await signIn(page, 'Chiara');
const g = await (await page.request.get('/api/groups/monnaie-libre')).json();
const d = await (await page.request.get(`/api/docs/${g.docs[0].id}`)).json();
const e1 = d.units.find((u: { code: string }) => u.code === 'E1');
const version: string = e1.proposals.find((p: { seedProposal?: boolean }) => p.seedProposal).id;
const phone = await browser.newContext({ ...devices['Pixel 7'], locale: 'fr-FR' });
const screens: { name: string; tag: string; page: Page; size?: { width: number; height: number } }[] = [
{ name: 'bureau 1360 × 900', tag: 'bureau-1360', page, size: { width: 1360, height: 900 } },
{ name: 'bureau 1024 × 768', tag: 'bureau-1024', page, size: { width: 1024, height: 768 } },
{ name: 'téléphone (Pixel 7)', tag: 'telephone', page: await phone.newPage() },
];
for (const screen of screens) {
const p = screen.page;
if (screen.size) await p.setViewportSize(screen.size);
if (p !== page) await signIn(p, 'Chiara');
// From the head of the document (the usual turnout), then from a version (« cet article » in its
// row); in each language, whose headers are longer in Spanish (« Proporción exigida », « Votos a favor »).
for (const [lang, last, current] of [
['fr', 'Voix pour', 'cet article'],
['en', 'Votes for', 'this article'],
['es', 'Votos a favor', 'este artículo'],
]) {
for (const from of ['tete', 'version']) {
await p.goto(from === 'tete' ? `${DOC}?onglet=reglages` : `${DOC}?onglet=reglages&version=${version}`);
if ((await p.evaluate(() => localStorage.getItem('ld:lang'))) !== lang) {
await p.evaluate((l) => localStorage.setItem('ld:lang', l), lang);
await p.reload();
}
const panel = p.locator('aside.panel');
await expect(panel.locator('table tbody tr')).toHaveCount(5);
if (from === 'version') await expect(panel.locator('tr.current th')).toContainText(current);
await expect(panel.locator('thead th').last()).toHaveText(last);
await panel.locator('table').scrollIntoViewIfNeeded();
// Soft: every screen is looked at, and the report names each one where the table is cut.
expect.soft(await hiddenInSettings(p), `${screen.name}, ${lang}, ${from === 'tete' ? 'en tête du document' : 'depuis une version'}`).toEqual([]);
if (lang === 'fr') await shot(p, `c44-${screen.tag}-${from}`);
}
}
await p.evaluate(() => localStorage.setItem('ld:lang', 'fr'));
}
await phone.close();
});
+9 -12
View File
@@ -148,12 +148,20 @@
font-size: 0.88rem;
font-variant-numeric: tabular-nums;
}
/* The table lives in the side panel, 27rem at most whatever the screen (DocPanel), about as
narrow as a phone's sheet: on a desktop as on a phone, the cells sit close and the headers
and the level's name wrap, so that the six columns hold, in each language; a figure never
wraps (lot U3b). */
th,
td {
padding: 0.45rem 0.55rem;
padding: 0.4rem 0.3rem;
text-align: right;
white-space: nowrap;
}
thead th,
th[scope='row'] {
white-space: normal;
}
thead th {
font-size: 0.74rem;
font-weight: 800;
@@ -193,15 +201,4 @@
.tag {
font-size: 0.68rem;
}
/* On a phone, six columns hold in the sheet: the headers and the level's name wrap. */
@media (max-width: 480px) {
th,
td {
padding: 0.4rem 0.3rem;
}
thead th,
th[scope='row'] {
white-space: normal;
}
}
</style>