forked from yvv/decision
v3 : interface des objections, des coquilles validées et de la messagerie ; contrôles C28–C31
Une version retenue par une objection le dit sous l'article, à chacun selon son rôle : la personne qui vote voit qu'elle ne se vote plus (les votes faits restent) ; l'auteur, quoi faire (« Modifier l'article visé » pour une redite, « Reformuler » pour une contradiction) ; la personne qui objecte, lever ou réitérer avant l'échéance. Le panier la montre sans choix et ne l'envoie jamais. « Détails » : faire une objection (redit / contredit), les objections et où elles en sont, les coquilles proposées par d'autres avec leur diff resserré, valider / refuser / revenir, l'historique dit qui a validé. Une coquille se propose sur toute version ouverte ou en vigueur. Boîte « Messages » dans le menu, avec le nombre de non-lus (pastille sur le bouton du menu en mobile) : objet, texte, gestes, échéance ; un message traité n'a plus de geste. Répondre à une objection lit son message. Trilingue (fr, en, es). C6 suit la règle : la coquille d'un administrateur sur un texte du seed passe tout de suite ; C28–C31 couvrent le reste. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,49 @@ export async function shot(page: Page, name: string) {
|
||||
if (dir) await page.screenshot({ path: `${dir}/${name}.png` });
|
||||
}
|
||||
|
||||
const CSRF = { 'x-requested-with': 'libredecision' };
|
||||
|
||||
/** The document of the test group, as the server gives it. */
|
||||
export async function testDoc(page: Page): Promise<{
|
||||
id: string;
|
||||
units: { id: string; code: string; inForce: { id: string; title: string } | null; proposals: { id: string; title: string }[] }[];
|
||||
}> {
|
||||
const g = await (await page.request.get('/api/groups/essai')).json();
|
||||
const d = await (await page.request.get(`/api/docs/${g.docs[0].id}`)).json();
|
||||
return { id: d.doc.id, units: d.units };
|
||||
}
|
||||
|
||||
/** A new article proposed by the person of `page`, submitted to the vote; returns its id. */
|
||||
export async function propose(page: Page, title: string, body: string): Promise<string> {
|
||||
const doc = await testDoc(page);
|
||||
const r = await page.request.post(`/api/docs/${doc.id}/proposals`, {
|
||||
data: { kind: 'text', unitId: null, title, body, submit: true },
|
||||
headers: CSRF,
|
||||
});
|
||||
if (!r.ok()) throw new Error(`propose ${r.status()} ${await r.text()}`);
|
||||
return (await r.json()).id;
|
||||
}
|
||||
|
||||
/** Moves the server's clock forward (dev login only) and passes the deadlines at once. */
|
||||
export async function advanceClock(page: Page, seconds: number): Promise<{ lifted: number; applied: number; stale: number }> {
|
||||
const r = await page.request.post('/api/dev/clock', { data: { seconds }, headers: CSRF });
|
||||
if (!r.ok()) throw new Error(`clock ${r.status()}`);
|
||||
return r.json();
|
||||
}
|
||||
|
||||
/** Screenshots on a desktop and on a phone, when `LD_SHOTS` names a directory (never in the loop). */
|
||||
export async function shots(page: Page, name: string) {
|
||||
if (!process.env.LD_SHOTS) return;
|
||||
const size = page.viewportSize();
|
||||
await page.waitForTimeout(400);
|
||||
await shot(page, `${name}-bureau`);
|
||||
await page.setViewportSize({ width: 412, height: 915 });
|
||||
await page.waitForTimeout(200);
|
||||
await shot(page, `${name}-mobile`);
|
||||
if (size) await page.setViewportSize(size);
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
/** The control door of the simulated chain, found where the page finds its node. */
|
||||
export async function chain(page: Page, docPath = DOC) {
|
||||
const [, , group] = docPath.split('/');
|
||||
|
||||
Reference in New Issue
Block a user