v3 : modules d'ordre et de titre — moteur, données, serveur (L11)

L'ordre des articles de chaque section (cran 2) et le titre du document
(cran 4) deviennent des modules : des unités à part, dont les versions
sont des propositions votées comme les articles (dépliées, panier, chaîne).

- moteur : ld-engine/src/order.rs (qui est dans l'ordre, ordre proposé,
  complété à la fin quand un article entre ou sort, places redistribuées,
  nouvel article en fin de section) ; pending : un ordre de moins de deux
  articles n'est jamais à voter ;
- données : migration 0007 (units.kind, module_amendments) ; le seed
  déclare ses modules ; module_amend.rs les ajoute une fois, gardé et
  journalisé, aux documents nés sans eux, sans toucher articles ni votes ;
- serveur : modules.rs, order.rs, doc_title.rs (portes pour proposer,
  entrée en vigueur : les articles changent de place, le document change
  de titre, en et es passent « à revoir ») ; publication des modules ;
- reste mineur : la médiane compte les statuts open, in_force, applied,
  et eux seuls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 02:08:44 +01:00
co-authored by Claude Opus 5.5
parent 1a35d0f521
commit 3831ddb1bf
21 changed files with 2249 additions and 115 deletions
+13 -4
View File
@@ -133,9 +133,11 @@ pub async fn decisions(
Ok(Json(json!({ "canVote": can_vote, "docs": out })))
}
/// The articles of a document as `me` sees them: its version in force (adopted
/// by a vote, or a seed a minima), its 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 modules of a document as `me` sees them — its articles, its title, the
/// 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.
async fn modules(state: &AppState, doc_id: &str, me: &str) -> AppResult<Vec<Module>> {
#[derive(sqlx::FromRow)]
struct VersionRow {
@@ -144,6 +146,13 @@ async fn modules(state: &AppState, doc_id: &str, me: &str) -> AppResult<Vec<Modu
adopted: bool,
voted: bool,
}
let st = docs::load_state(state, doc_id).await?;
let moot: HashSet<String> = st
.modules(crate::modules::ModuleKind::Order)
.into_iter()
.filter(|u| ld_engine::order::is_moot(&st.members(u.section.as_deref().unwrap_or_default())))
.map(|u| u.id.clone())
.collect();
let rows = sqlx::query_as::<_, VersionRow>(
"SELECT u.id AS unit_id,
IFNULL(p.id = u.in_force_id, 0) AS in_force,
@@ -164,7 +173,7 @@ async fn modules(state: &AppState, doc_id: &str, me: &str) -> AppResult<Vec<Modu
let mut current: Option<String> = None;
for r in rows {
if current.as_deref() != Some(r.unit_id.as_str()) {
out.push(Module::default());
out.push(Module { moot: moot.contains(&r.unit_id), ..Module::default() });
current = Some(r.unit_id);
}
if let Some(m) = out.last_mut() {