Files
decision/web/src/lib/versions.ts
T
YvvandClaude Opus 5.5 83f8ca0981 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>
2026-10-31 04:21:20 +01:00

81 lines
3.0 KiB
TypeScript

// The versions of an article, for the list of open proposals (Yvv, 2026-10-30:
// « Quand un article a plusieurs versions, faut pas ajouter un article dans la
// liste »): one line per article, its best version first, the others inside.
import type { Proposal, Unit } from './types';
export type Filter = 'all' | 'wellrated' | 'mine';
export interface Article {
unit: Unit;
/** The open proposals of the article, best first. */
versions: Proposal[];
/** The best version is strictly ahead of the next one (not a tie). */
leads: boolean;
}
/** How far a version has risen toward its threshold, from 0 to 1: its water level. */
export function level(p: Proposal): number {
const required = p.evaluation?.required ?? 0;
return required > 0 ? Math.min(1, p.tally.for / required) : 0;
}
/**
* Negative when `a` comes before `b`: the highest water, then more for, then fewer
* against. A version held back by an objection comes after the others: it is not
* votable, and cannot enter into force, until the objection is lifted.
*/
export function compare(a: Proposal, b: Proposal): number {
return (
Number(a.blocked) - Number(b.blocked) ||
level(b) - level(a) ||
b.tally.for - a.tally.for ||
a.tally.against - b.tally.against
);
}
/**
* Plain excerpts of sibling versions, each starting a few words before the versions
* part ways — two variants often share a long opening, and a one-line excerpt would
* show only that.
*/
export function excerpts(texts: string[]): string[] {
const LEAD = 3;
const words = texts.map((t) => t.split(/\s+/).filter(Boolean));
const [first] = words;
if (!first || words.length < 2) return texts;
let common = 0;
while (common < first.length && words.every((w) => w[common] === first[common])) common++;
const start = common - LEAD;
// A short common start reads well as it is.
if (start < 4) return texts;
return words.map((w) => `… ${w.slice(start).join(' ')}`);
}
export function passes(p: Proposal, filter: Filter): boolean {
if (filter === 'wellrated') return !!p.evaluation?.wellRated;
if (filter === 'mine') return p.mine;
return true;
}
/**
* One article per unit, in the order of the document; an article is kept when at
* least one of its versions passes the filter, and then shows all of them.
*/
export function articles(items: { unit: Unit; proposal: Proposal }[], filter: Filter = 'all'): Article[] {
const byUnit = new Map<string, { unit: Unit; versions: Proposal[] }>();
for (const { unit, proposal } of items) {
const entry = byUnit.get(unit.id) ?? { unit, versions: [] };
entry.versions.push(proposal);
byUnit.set(unit.id, entry);
}
return [...byUnit.values()]
.filter(({ versions }) => versions.some((p) => passes(p, filter)))
.map(({ unit, versions }) => {
// Array.prototype.sort is stable: equal versions keep the order of the document.
const ranked = [...versions].sort(compare);
const [best, next] = ranked;
return { unit, versions: ranked, leads: !!best && !!next && compare(best, next) < 0 };
});
}