Fusion de la tête du chantier (924a40d : lots B2 et F) dans le lot D

Les deux côtés gardés partout. Aux coutures :
- une objection (redit / contredit) porte sur deux articles, jamais sur un module ;
  un ordre ne se corrige pas, ni par son auteur ni par un autre ; une coquille du
  titre suit le chemin de F (l'auteur du titre, un administrateur pour le titre
  de départ, ou 3 jours), et documents.title suit à l'application ;
- votable() couvre les modules ; leurs totaux passent par shown_tally, à l'écran
  et dans document.json ;
- « Modules à voter » ne compte plus une version retenue par une objection (test
  vu rouge) ; la liste du groupe la compte toujours parmi les propositions en cours ;
- le panier : une fonction pour les articles et les modules, avec les versions
  retenues de F ; un nouvel article se propose dans une section, à sa fin, et peut
  répondre à une objection ;
- migrations 0007 (modules) puis 0008 (objections) ;
- C25 et C26 supportent un groupe d'essai où une voix suffit (dès 16 membres).

Boucle : fmt, clippy -D warnings, cargo test (227), svelte-check 0, vitest 55,
build, e2e 35 / 35 (C1–C33 et M1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 09:41:30 +01:00
co-authored by Claude Opus 5.5
82 changed files with 8285 additions and 677 deletions
+24
View File
@@ -19,6 +19,30 @@ export interface PanelState {
/** The basket, opened on one section. */
section?: string | null;
prefill?: { title: string; body: string } | null;
/** A new proposal that answers this objection (ADR 0010). */
objectionId?: string | null;
/** The details of one's own version, its edit form open: to reword it. */
edit?: boolean;
}
/** The tabs as the URL names them. */
export const TABS: Record<string, Tab> = { panier: 'basket', propositions: 'proposals', proposer: 'propose', details: 'details', reglages: 'settings' };
export const TAB_NAMES: Record<Tab, string> = { basket: 'panier', proposals: 'propositions', propose: 'proposer', details: 'details', settings: 'reglages' };
/** The query of a reader's URL: the unfolded vote and the panel. */
export function panelSearch(panel: PanelState | null, vote: string | null): string {
const q = new URLSearchParams();
if (vote) q.set('vote', vote);
if (panel) {
q.set('onglet', TAB_NAMES[panel.tab]);
if (panel.unitId) q.set('article', panel.unitId);
if (panel.proposalId) q.set('version', panel.proposalId);
if (panel.draftId) q.set('brouillon', panel.draftId);
if (panel.section) q.set('section', panel.section);
if (panel.objectionId) q.set('objection', panel.objectionId);
if (panel.edit) q.set('modifier', '1');
}
return q.toString();
}
export interface Shown {