forked from yvv/decision
v3 : le vote déplié sous l'article, le panier, la signature par le portefeuille Ğ1
- Le vote se déplie sous l'article, façon accordéon, l'URL pour état (?vote=) :
niveau d'eau, totaux, seuil et voix manquantes, ton vote et où il en est
(enregistré, inscrit sur la chaîne, en attente, à reconfirmer, non compté),
inertie et formule. Le reste d'une version (coquilles, traductions,
signalements, historique) passe dans l'onglet « Détails » du panneau.
- Le panier : article par article, « tout pour / tout contre » par section ou
pour tout le document, jamais pour un article à plusieurs versions ; un seul
geste (une écriture, ou une transaction).
- La signature : le portefeuille de la page (injectedWeb3), polkadot-api chargé
au moment de signer ; sans portefeuille, Ğecko par lien et QR ; la page
attend la confirmation. Un bulletin que le portefeuille n'a pas signé est
lâché aussitôt (DELETE /api/ballots/{id}).
- Pastilles d'ambiance : flèches dans le groupe, anneau de focus pointillé à
la couleur d'accent de la page (3:1 au moins), distinct du choix.
- e2e : chaîne simulée (indexeur et nœud), faux portefeuille ; C21–C24, et
C4, C5, C6, C11, C17, C19, C20, M1 mis à jour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// The basket of a vote (Yvv, 2026-10-30: « Je peux voter tout d'un coup, ou une
|
||||
// section entière, ou un article avec son titre, ou séparément »). One chooses
|
||||
// article by article; « tout pour » or « tout contre » fills a section or the
|
||||
// whole document — but never an article whose versions compete: there, one
|
||||
// chooses one's version. Then one gesture sends it all: one write on the
|
||||
// server, or one transaction on the Ğ1 chain.
|
||||
|
||||
import type { Choice, Proposal, Unit } from './types';
|
||||
|
||||
/** A choice in the basket: for, against, or withdrawing one's vote. Absent: nothing. */
|
||||
export type Picks = Record<string, Choice>;
|
||||
|
||||
/** An article as the basket shows it. */
|
||||
export interface Slot {
|
||||
unit: Unit;
|
||||
/** The versions in competition: the text in force, the open texts and removals. */
|
||||
versions: Proposal[];
|
||||
/** Open changes of its inertia: another question, never filled by a grouped gesture. */
|
||||
inertia: Proposal[];
|
||||
}
|
||||
|
||||
export interface Section {
|
||||
/** The section of the seed (introduction, fondamental…), or null. */
|
||||
key: string | null;
|
||||
slots: Slot[];
|
||||
}
|
||||
|
||||
/** The articles of the document with something to vote, by section, in document order. */
|
||||
export function sections(units: Unit[]): Section[] {
|
||||
const out: Section[] = [];
|
||||
for (const unit of units) {
|
||||
const open = unit.proposals.filter((p) => p.status === 'open');
|
||||
const versions = [
|
||||
...(unit.inForce?.status === 'in_force' ? [unit.inForce] : []),
|
||||
...open.filter((p) => p.kind !== 'inertia'),
|
||||
];
|
||||
const inertia = open.filter((p) => p.kind === 'inertia');
|
||||
if (!versions.length && !inertia.length) continue;
|
||||
const last = out.at(-1);
|
||||
const slot = { unit, versions, inertia };
|
||||
if (last && last.key === unit.section) last.slots.push(slot);
|
||||
else out.push({ key: unit.section, slots: [slot] });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** « Tout pour » / « tout contre » over some articles: only those with a single version. */
|
||||
export function fill(slots: Slot[], choice: 'for' | 'against', picks: Picks): Picks {
|
||||
const next = { ...picks };
|
||||
for (const s of slots) {
|
||||
const [only] = s.versions;
|
||||
if (only && s.versions.length === 1) next[only.id] = choice;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/** Articles of several versions where no version is chosen yet: the interface shows them. */
|
||||
export function waiting(slots: Slot[], picks: Picks): Slot[] {
|
||||
return slots.filter((s) => s.versions.length > 1 && !s.versions.some((p) => p.id in picks));
|
||||
}
|
||||
|
||||
/** Empties the basket of these articles only. */
|
||||
export function clearScope(slots: Slot[], picks: Picks): Picks {
|
||||
const ids = new Set(slots.flatMap((s) => [...s.versions, ...s.inertia].map((p) => p.id)));
|
||||
return Object.fromEntries(Object.entries(picks).filter(([id]) => !ids.has(id)));
|
||||
}
|
||||
|
||||
/** What is sent: one line per version still in the document; withdrawing is a null choice. */
|
||||
export function lines(picks: Picks, known: Set<string>): { proposalId: string; choice: 'for' | 'against' | null }[] {
|
||||
return Object.entries(picks)
|
||||
.filter(([id]) => known.has(id))
|
||||
.map(([proposalId, c]) => ({ proposalId, choice: c === 'none' ? null : c }));
|
||||
}
|
||||
|
||||
export function counts(picks: Picks): { for: number; against: number; none: number; total: number } {
|
||||
const all = Object.values(picks);
|
||||
return {
|
||||
for: all.filter((c) => c === 'for').length,
|
||||
against: all.filter((c) => c === 'against').length,
|
||||
none: all.filter((c) => c === 'none').length,
|
||||
total: all.length,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user