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
+5 -3
View File
@@ -137,10 +137,12 @@ pub async fn decisions(
/// order of each section (ADR 0009): the version in force (adopted by a vote,
/// or a seed a minima), the open versions, and whether `me` holds a valid vote
/// on one of them. A change of inertia is not a version of the text; the order
/// of a section of fewer than two articles has nothing to decide.
/// of a section of fewer than two articles has nothing to decide; a version held
/// by an objection is nobody's to vote while it is held (ADR 0010).
async fn modules(state: &AppState, doc_id: &str, me: &str) -> AppResult<Vec<Module>> {
#[derive(sqlx::FromRow)]
struct VersionRow {
id: String,
unit_id: String,
in_force: bool,
adopted: bool,
@@ -154,7 +156,7 @@ async fn modules(state: &AppState, doc_id: &str, me: &str) -> AppResult<Vec<Modu
.map(|u| u.id.clone())
.collect();
let rows = sqlx::query_as::<_, VersionRow>(
"SELECT u.id AS unit_id,
"SELECT p.id AS id, u.id AS unit_id,
IFNULL(p.id = u.in_force_id, 0) AS in_force,
(p.entered_at IS NOT NULL OR p.confirmed_at IS NOT NULL OR p.origin <> 'seed') AS adopted,
EXISTS (SELECT 1 FROM votes v
@@ -179,7 +181,7 @@ async fn modules(state: &AppState, doc_id: &str, me: &str) -> AppResult<Vec<Modu
if let Some(m) = out.last_mut() {
if r.in_force {
m.in_force = Some(r.adopted);
} else {
} else if st.proposals.iter().any(|p| p.id == r.id && st.votable(p)) {
m.open += 1;
}
m.voted |= r.voted;