v3 : la Raison d'être remplace le Préambule (L7), amendement de seed gardé

- seed : I1 devient « Raison d'être », texte d'Yvv (fr), traduit en « Purpose » et
  « Razón de ser » ; apostrophes droites comme le reste du seed, insécables avant « ; » et
  dans les guillemets.
- seed_amend.rs : un amendement de seed remplace un texte de départ dans une base existante,
  décidé une fois par document et seulement si personne n'a touché l'article (seed a minima
  d'origine, révision 1, aucun vote, aucune correction, aucune traduction de membre, aucun
  signalement, empreinte attendue). Décision consignée (migration 0004, table
  seed_amendments), ligne d'historique « texte de départ remplacé » avec son diff,
  republication mise en file là où le document publie.
- Une coquille se mesure désormais aussi contre le texte posé par l'amendement.
- Tests : règle pure (3), scénarios d'API (base neuve, amendement appliqué et republié une
  seule fois, cinq refus), e2e C13 ; C3, C6 et l'API suivent le nouveau texte (C6 : les
  apostrophes typographiques d'Unicité du compte, « latransmission » ayant disparu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-30 09:47:17 +01:00
co-authored by Claude Opus 5.5
parent d400bfc92e
commit a5b92faa96
14 changed files with 801 additions and 37 deletions
+26 -8
View File
@@ -22,13 +22,13 @@ test('C2 — les groupes de la même clé ; pas l’Atelier du canal', async ({
await expect(page.getByText('Atelier du canal')).toHaveCount(0);
});
test('C3 — le texte en vigueur : préambule et deux engagements, seed a minima', async ({ page }) => {
test('C3 — le texte en vigueur : raison d’être et deux engagements, seed a minima', async ({ page }) => {
await signIn(page, 'Chiara');
await page.goto(DOC);
await expect(page.getByRole('heading', { level: 1 })).toHaveText("Acte d'engagement Certification");
const units = page.locator('section.text article.unit');
await expect(units).toHaveCount(3);
await expect(units.nth(0).locator('h2')).toHaveText('Préambule');
await expect(units.nth(0).locator('h2')).toHaveText("Raison d'être");
await expect(units.nth(1).locator('h2')).toHaveText('Unicité du compte');
await expect(units.nth(2).locator('h2')).toHaveText('Réciprocité');
await expect(page.getByText('seed a minima · 0 vote')).toHaveCount(3);
@@ -70,19 +70,20 @@ test('C5 — une proposition remonte en vigueur quand elle atteint son seuil (gr
test('C6 — une coquille se corrige sans vote', async ({ page }) => {
await signIn(page, 'Chiara');
await page.goto(TEST_DOC);
const preamble = page.locator('section.text article.unit').first();
await preamble.hover();
await preamble.getByRole('button', { name: 'Le vote' }).click();
const unicity = page.locator('section.text article.unit', { hasText: 'Unicité du compte' });
await unicity.hover();
await unicity.getByRole('button', { name: 'Le vote' }).click();
const panel = page.locator('aside.panel');
await panel.getByText('Corriger une coquille').click();
const body = panel.getByLabel('Texte', { exact: true });
await body.fill((await body.inputValue()).replace('latransmission', 'la transmission'));
// Typographic apostrophes: the letters and the signs stay, only their style changes.
await body.fill((await body.inputValue()).replaceAll("'", '’'));
await panel.getByRole('button', { name: 'Enregistrer la correction' }).click();
await expect(page.getByText('Coquille corrigée, sans toucher aux votes.')).toBeVisible();
await expect(preamble).toContainText('la transmission de ce document');
await expect(unicity).toContainText('Je m’engage sur l’honneur');
// A change of meaning is turned away.
await panel.getByText('Corriger une coquille').click();
await body.fill('Tout autre chose, qui change le sens du préambule.');
await body.fill('Tout autre chose, qui change le sens de cet engagement.');
await panel.getByRole('button', { name: 'Enregistrer la correction' }).click();
await expect(panel.getByText('Ce changement touche au sens : propose-le comme modification.')).toBeVisible();
});
@@ -187,3 +188,20 @@ test('C12 — lien d’équipe : chaque personne qui l’ouvre a sa propre ident
await expect(team.getByText('2 / 30 personnes', { exact: false })).toBeVisible();
await expect(yvv.getByText('venue par « Équipe de test »')).toHaveCount(2);
});
test('C13 — la Raison d’être ouvre le texte en vigueur, en trois langues', async ({ page }) => {
await signIn(page, 'Chiara');
await page.goto(DOC);
const first = page.locator('section.text article.unit').first();
await expect(first.locator('h2')).toHaveText("Raison d'être");
// The first sentence in italics, then two paragraphs.
const paragraphs = first.locator('.prose p');
await expect(paragraphs).toHaveCount(3);
await expect(paragraphs.nth(0).locator('em')).toContainText("Derrière une clé il n'y a qu'un seul être humain vivant");
await expect(paragraphs.nth(1)).toContainText("C'est le seul et unique propos de la certification");
await expect(paragraphs.nth(2)).toContainText('la première monnaie libre');
await page.getByRole('radiogroup', { name: 'Langue du texte' }).getByRole('radio', { name: 'EN' }).click();
await expect(first.locator('h2')).toHaveText('Purpose');
await page.getByRole('radiogroup', { name: 'Text language' }).getByRole('radio', { name: 'ES' }).click();
await expect(first.locator('h2')).toHaveText('Razón de ser');
});
+1 -1
View File
@@ -29,7 +29,7 @@
unit: { id: string; code: string; inertia: Inertia };
inForce: { id: string; title: string; body: string } | null;
diff: DiffPart[] | null;
history: { revision: number; kind: 'initial' | 'typo' | 'substantive'; author: string | null; authorInvited: boolean; at: string; diff: DiffPart[] | null }[];
history: { revision: number; kind: 'initial' | 'typo' | 'substantive' | 'amendment'; author: string | null; authorInvited: boolean; at: string; diff: DiffPart[] | null }[];
signals: {
id: string; reason: string; author: string; authorInvited: boolean; mine: boolean; at: string; liftedAt: string | null; liftedBy: string | null;
other: { id: string; title: string; status: string; code: string | null } | null;
+1
View File
@@ -208,6 +208,7 @@ export const en: Messages = {
'history.initial': 'initial text',
'history.typo': 'typo fixed',
'history.substantive': 'substantive change (votes to reconfirm)',
'history.amendment': 'starting text replaced',
'history.by': 'by {name}',
'typo.title': 'Fix a typo',
+1
View File
@@ -208,6 +208,7 @@ export const es: Messages = {
'history.initial': 'texto inicial',
'history.typo': 'errata corregida',
'history.substantive': 'cambio de fondo (votos por reconfirmar)',
'history.amendment': 'texto de partida sustituido',
'history.by': 'por {name}',
'typo.title': 'Corregir una errata',
+1
View File
@@ -211,6 +211,7 @@ export const fr = {
'history.initial': 'texte initial',
'history.typo': 'coquille corrigée',
'history.substantive': 'changement de fond (votes à reconfirmer)',
'history.amendment': 'texte de départ remplacé',
'history.by': 'par {name}',
'typo.title': 'Corriger une coquille',