v3 : septième relecture — collages limités, titre jugé à part, découpage contrôlé
- Mots collés ou coupés : deux au plus, jamais sur une négation (« Si non » → « Sinon », « ne certifier » → « necertifier » sont du fond). - Titre et corps jugés chacun pour soi, puis ensemble : le titre n'absorbe plus le début du corps. - Caractères invisibles refusés aussi au découpage d'un brouillon et à la soumission. - Tabulation et retour chariot ne sont plus des espaces libres ; les lettres latines rares (ǃ, Ʒ…) font partie de la forme. - Le diff renvoyé après une coquille couvre le titre. Boucle : 62 tests Rust, svelte-check, 9 vitest, e2e 12/12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -316,15 +316,20 @@ pub async fn edit(
|
||||
}
|
||||
// Measured against the text that was voted, not the current one: successive
|
||||
// typo fixes are judged together and never add up to a change of meaning.
|
||||
// Title and body are judged apart (a title never absorbs the start of the
|
||||
// body) and together (two slips at most in all).
|
||||
let next = format!("{title}\n{body}");
|
||||
let kind = if next == format!("{}\n{}", p.title, p.body) {
|
||||
let kind = if title == p.title && body == p.body {
|
||||
EditKind::Unchanged
|
||||
} else {
|
||||
let (voted_title, voted_body) = voted_text(&state, &p).await?;
|
||||
match edit::classify(&format!("{voted_title}\n{voted_body}"), &next) {
|
||||
EditKind::Unchanged => EditKind::Typographic, // back to the voted text
|
||||
k => k,
|
||||
}
|
||||
let kinds = [
|
||||
edit::classify(&voted_title, title),
|
||||
edit::classify(&voted_body, body),
|
||||
edit::classify(&format!("{voted_title}\n{voted_body}"), &next),
|
||||
];
|
||||
// All unchanged: back to the voted text, itself a correction.
|
||||
if kinds.contains(&EditKind::Substantive) { EditKind::Substantive } else { EditKind::Typographic }
|
||||
};
|
||||
match kind {
|
||||
EditKind::Unchanged => Ok(Json(json!({ "edit": "unchanged" }))),
|
||||
@@ -363,7 +368,7 @@ pub async fn edit(
|
||||
if p.status == "in_force" {
|
||||
adoption::enqueue_publication(&state, &st.doc, "correction typographique").await?;
|
||||
}
|
||||
Ok(Json(json!({ "edit": "typo", "diff": edit::diff(&p.body, body) })))
|
||||
Ok(Json(json!({ "edit": "typo", "diff": edit::diff(&format!("{}\n{}", p.title, p.body), &next) })))
|
||||
}
|
||||
EditKind::Substantive => {
|
||||
if p.status == "in_force" {
|
||||
@@ -418,6 +423,9 @@ pub async fn submit(
|
||||
if p.status != "draft" {
|
||||
return Err(AppError::Conflict("not_draft"));
|
||||
}
|
||||
if edit::has_hidden_controls(&p.title) || edit::has_hidden_controls(&p.body) {
|
||||
return Err(AppError::Invalid("hidden_characters"));
|
||||
}
|
||||
let now = util::now();
|
||||
sqlx::query("UPDATE proposals SET status = 'open', submitted_at = ?, updated_at = ? WHERE id = ?")
|
||||
.bind(&now)
|
||||
@@ -484,6 +492,9 @@ pub async fn split(
|
||||
if parts.len() < 2 || parts.len() > 12 || parts.iter().any(|s| s.chars().count() > MAX_BODY) {
|
||||
return Err(AppError::Invalid("parts"));
|
||||
}
|
||||
if parts.iter().any(|s| edit::has_hidden_controls(s)) {
|
||||
return Err(AppError::Invalid("hidden_characters"));
|
||||
}
|
||||
sqlx::query("UPDATE proposals SET body = ?, updated_at = ? WHERE id = ?")
|
||||
.bind(parts[0])
|
||||
.bind(util::now())
|
||||
|
||||
Reference in New Issue
Block a user