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>
This commit is contained in:
Yvv
2026-10-31 04:21:20 +01:00
co-authored by Claude Opus 5.5
parent 3f1f968f16
commit 83f8ca0981
31 changed files with 1732 additions and 197 deletions
+12 -7
View File
@@ -405,8 +405,9 @@ pub async fn reiterate(
// What answers an objection
// ---------------------------------------------------------------------------
/// A version changed: the objections around it whose author it answers move on.
pub async fn on_change(state: &AppState, proposal_id: &str, substantive: bool) -> AppResult<()> {
/// A version changed (by `by`): the objections around it whose author it
/// answers move on; the messages of the one who answered are read.
pub async fn on_change(state: &AppState, proposal_id: &str, substantive: bool, by: &str) -> AppResult<()> {
for o in standing_around(state, proposal_id).await? {
let Some(side) = o.touches(proposal_id) else { continue };
let change = if substantive { Change::Revision(side) } else { Change::Typo(side) };
@@ -416,7 +417,9 @@ pub async fn on_change(state: &AppState, proposal_id: &str, substantive: bool) -
let due = state.clock.in_days(objection::ANSWER_DAYS);
if let Some(step) = objection::step(&o.phase(), &Event::Answered { due: &due }).map_err(refused)? {
let answer = if side == Touched::Target { "revision" } else { "other" };
store(state, &o, &step, proposal_id, Some((answer, proposal_id))).await?;
if store(state, &o, &step, proposal_id, Some((answer, proposal_id))).await? {
messages::read_subject(state, by, Topic::Objection, &o.id).await?;
}
}
}
Ok(())
@@ -443,15 +446,17 @@ pub async fn check_link(state: &AppState, user: &IdentityRow, objection_id: &str
Ok(())
}
/// A proposal linked to an objection was submitted: it answers the objection.
pub async fn on_linked(state: &AppState, objection_id: &str, proposal_id: &str) -> AppResult<()> {
/// A proposal linked to an objection was submitted (by `by`): it answers the objection.
pub async fn on_linked(state: &AppState, objection_id: &str, proposal_id: &str, by: &str) -> AppResult<()> {
let o = load(state, objection_id).await?;
if !objection::answers(Change::LinkedProposal) {
return Ok(());
}
let due = state.clock.in_days(objection::ANSWER_DAYS);
if let Some(step) = objection::step(&o.phase(), &Event::Answered { due: &due }).map_err(refused)? {
store(state, &o, &step, proposal_id, Some(("proposal", proposal_id))).await?;
if let Some(step) = objection::step(&o.phase(), &Event::Answered { due: &due }).map_err(refused)?
&& store(state, &o, &step, proposal_id, Some(("proposal", proposal_id))).await?
{
messages::read_subject(state, by, Topic::Objection, &o.id).await?;
}
Ok(())
}