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
+143 -19
View File
@@ -11,6 +11,9 @@ export const es: Messages = {
'nav.mandates': 'Mandatos',
'nav.protocols': 'Protocolos',
'nav.groups': 'Mis grupos',
'nav.messages': 'Mensajes',
'nav.messages.count.one': 'Mensajes, {n} sin leer',
'nav.messages.count.other': 'Mensajes, {n} sin leer',
'nav.docs.count.one': '{label}, {n} documento en curso',
'nav.docs.count.other': '{label}, {n} documentos en curso',
'nav.access': 'Accesos',
@@ -139,7 +142,7 @@ export const es: Messages = {
'deep.addition': 'artículo nuevo',
'deep.removal': 'retirada de «{title}»',
'deep.inertia': 'inercia a {level}',
'deep.blocked': 'bloqueada',
'deep.held': 'no votable: objeción',
'deep.mine': 'tu propuesta',
'deep.stale': 'por reconfirmar',
'deep.wellrated': 'bien valorada',
@@ -228,7 +231,14 @@ export const es: Messages = {
'vote.stale': 'El texto ha cambiado de fondo desde tu voto: tu voto deja de contar hasta que lo reconfirmes.',
'vote.reconfirm': 'Reconfirmar: {choice}',
'vote.private': 'Tu voto no se muestra a nadie: solo se ven los totales.',
'vote.blocked': 'Un aviso de contradicción la retiene: no puede entrar en vigor mientras se mantenga.',
'vote.held': 'Una objeción retiene esta versión: no se vota hasta que se levante. Los votos ya emitidos se mantienen.',
'vote.held.redit': 'Repite «{other}».',
'vote.held.contredit': 'Contradice «{other}».',
'vote.held.author.redit': 'Para que vuelva a votarse, modifica el artículo que repite. La persona que hizo la objeción la levanta entonces o la reitera; sin respuesta suya en 3 días, se levanta.',
'vote.held.author.contredit': 'Para que vuelva a votarse, reformúlala. La persona que hizo la objeción la levanta entonces o la reitera; sin respuesta suya en 3 días, se levanta.',
'vote.held.waiting': 'Llegó una respuesta: {name} levanta o reitera la objeción antes del {date}; sin respuesta, se levanta.',
'vote.held.mine': 'Hiciste esta objeción: levántala cuando la respuesta te convenga.',
'vote.held.mine.asked': 'Llegó una respuesta: levanta tu objeción si te convence, o reitérala, antes del {date}. Sin respuesta tuya, se levanta.',
'vote.entered': '«{title}» ha entrado en vigor.',
'vote.entered.remove': '«{title}» se ha retirado del texto en vigor.',
'vote.entered.inertia': 'La inercia de «{title}» ha cambiado.',
@@ -244,14 +254,15 @@ export const es: Messages = {
'vote.mine.pending': 'Esperando a la cadena Ğ1: {choice}.',
'vote.mine.refused': 'Tu último voto no se contó: {reason}',
'vote.mine.expired': 'Tu último voto no se vio a tiempo en la cadena Ğ1: no cuenta.',
'vote.mine.receipts': 'Tus papeletas y recibos',
'vote.choice.for': 'a favor',
'vote.choice.against': 'en contra',
'vote.choice.none': 'retirar tu voto',
'vote.chain.note': 'Tu voto sale de tu monedero Ğ1: {amount} Ğ1, en un solo gesto. La cadena muestra que has votado, nunca lo que has votado.',
'vote.chain.counts': 'Tu voz cuenta para la identidad Ğ1 del monedero que firma, si es miembro de la red de confianza.',
'vote.chain.off': 'La votación por la cadena Ğ1 aún no está abierta: falta crear la cuenta que recibe los votos. Puedes leer, proponer y señalar una contradicción.',
'vote.chain.off': 'La votación por la cadena Ğ1 aún no está abierta: falta crear la cuenta que recibe los votos. Puedes leer, proponer y hacer una objeción.',
'vote.members': 'Aquí solo votan los miembros de este grupo.',
'vote.details': 'Erratas, traducciones, señalamientos, historial',
'vote.details': 'Erratas, traducciones, objeciones, historial',
'ballot.reason.not_member': 'el monedero que firmó no es el de un miembro de la red de confianza Ğ1.',
'ballot.reason.no_identity': 'el monedero que firmó no está vinculado a ninguna identidad Ğ1.',
'ballot.reason.transfer': 'falta la transferencia a la cuenta de los votos, o su importe es demasiado bajo.',
@@ -278,6 +289,7 @@ export const es: Messages = {
'basket.sent.other': '{n} votos registrados.',
'basket.section': 'Votar esta sección',
'basket.mine': 'tu voto: {choice}',
'basket.held': 'no votable: una objeción la retiene',
'sign.title': 'Votar con tu monedero Ğ1',
'sign.summary.one': '{n} voto, en una transacción: {amount} Ğ1 a la cuenta de los votos, con tu papeleta sellada.',
'sign.summary.other': '{n} votos, en una transacción: {amount} Ğ1 a la cuenta de los votos, con tu papeleta sellada.',
@@ -305,6 +317,38 @@ export const es: Messages = {
'sign.close': 'Cerrar',
'sign.later': 'Puedes cerrar: la confirmación llegará sola, y tu voto aparecerá bajo el artículo.',
'sign.retry': 'Reintentar',
'sign.error.chain': 'La cadena Ğ1 rechazó la transacción: tu voto no salió. Vuelve a intentarlo cuando quieras.',
'sign.receipt': 'Descargar tu recibo',
'sign.history': 'Tus papeletas',
'sign.receipt.note': 'Tu recibo guarda tu papeleta y su sal: con él, compruebas por tu cuenta que la cadena lleva tu voto. Guárdalo para ti.',
'ballots.title': 'Tus papeletas de voto',
'ballots.intro': 'Cada voto por la cadena Ğ1 sale en una papeleta sellada: la cadena muestra que votaste, nunca lo que votaste. Tu recibo guarda la papeleta y su sal; con él, compruebas tu voto por tu cuenta. Nadie más lo recibe.',
'ballots.mine': 'Tus papeletas',
'ballots.signin': 'Conéctate para encontrar tus papeletas.',
'ballots.none': 'Aún no has enviado ninguna papeleta por la cadena Ğ1.',
'ballots.lines.one': '{n} voto',
'ballots.lines.other': '{n} votos',
'ballots.status.pending': 'Esperando a la cadena Ğ1',
'ballots.status.confirmed': 'Contado para la identidad Ğ1 «{name}»',
'ballots.status.refused': 'No contado: {reason}',
'ballots.status.expired': 'No visto a tiempo en la cadena Ğ1: no cuenta',
'ballots.block': 'bloque {n}',
'ballots.receipt': 'Descargar el recibo',
'ballots.verify.title': 'Comprobar un recibo',
'ballots.verify.intro': 'Elige el archivo de un recibo, o pega su contenido: la página recalcula el sello de la papeleta aquí mismo, sin pedir nada al servidor.',
'ballots.verify.file': 'Archivo del recibo',
'ballots.verify.paste': 'O pega el recibo',
'ballots.verify.go': 'Comprobar',
'ballots.verify.ok': 'El recibo es válido: sus votos y su sal dan exactamente este comentario.',
'ballots.verify.line': 'Versión {version}, revisión {revision}: {choice}',
'ballots.verify.line.label': '{label} — anotado en el recibo, fuera del sello',
'ballots.verify.where': 'Busca este comentario en el historial de tu monedero: es tu voto, inscrito en la cadena Ğ1.',
'ballots.verify.where.block': 'Busca este comentario en el historial de tu monedero, o en la cadena Ğ1 (el recibo indica el bloque {block}, fuera del sello): si figura allí, firmado por tu cuenta, es tu voto.',
'ballots.verify.bad': 'El recibo no es válido: sus votos y su sal no dan su comentario.',
'ballots.verify.computed': 'Recalculado:',
'ballots.verify.expected': 'En el recibo:',
'ballots.verify.unreadable': 'No es un recibo de voto legible.',
'inertia.title': 'Inercia',
'inertia.explain': 'Cuanto más alta es la inercia, más votos hacen falta para cambiar este texto cuando vota poca gente.',
@@ -359,27 +403,98 @@ export const es: Messages = {
'history.typo': 'errata corregida',
'history.substantive': 'cambio de fondo (votos por reconfirmar)',
'history.amendment': 'texto de partida sustituido',
'history.revert': 'corrección retirada por el autor',
'history.by': 'por {name}',
'history.typo.validated': 'validada por {name}',
'history.typo.deadline': 'aplicada sin respuesta en 3 días',
'typo.title': 'Corregir una errata',
'typo.note': 'Acentos, mayúsculas, erratas, palabras pegadas o separadas: la corrección se aplica sin votación, con su autor y su diff. La puntuación, las cifras, las fórmulas, las direcciones y todo cambio de sentido pasan por una propuesta.',
'typo.note': 'Acentos, mayúsculas, erratas, palabras pegadas o separadas: la corrección no se vota. En tu texto, se aplica enseguida. En el de otra persona, su autor la valida (un administrador para un texto del seed); sin respuesta en 3 días, se aplica. La puntuación, las cifras, las fórmulas, las direcciones y todo cambio de sentido pasan por una propuesta.',
'typo.save': 'Guardar la corrección',
'typo.done': 'Errata corregida, sin tocar los votos.',
'typo.substantive': 'Este cambio afecta al sentido: proponlo como modificación.',
'typo.asProposal': 'Convertirlo en propuesta',
'typo.pending.author': 'Tu corrección espera al autor del texto: se aplica cuando la valida, o sola el {date}.',
'typo.pending.admin': 'Tu corrección espera a un administrador, porque este texto viene del seed: se aplica cuando la valida, o sola el {date}.',
'typo.list': 'Erratas propuestas',
'typo.by': 'propuesta por {name}',
'typo.status.pending': 'en espera: se aplica sola el {date}',
'typo.status.applied': 'aplicada',
'typo.status.refused': 'rechazada',
'typo.status.stale': 'caducada: el texto cambió entretanto',
'typo.settled.by': 'por {name}',
'typo.settled.deadline': 'sin respuesta en 3 días',
'typo.validate': 'Validar',
'typo.refuse': 'Rechazar',
'typo.undo': 'Volver al texto anterior',
'typo.redo': 'Aplicar al final',
'typo.validated': 'Corrección aplicada.',
'typo.refused': 'Corrección rechazada.',
'signal.title': 'Contradicciones',
'signal.none': 'Ningún aviso.',
'signal.add': 'Señalar una contradicción',
'signal.target': 'Contradice',
'signal.reason': 'En qué se contradicen estos dos textos',
'signal.send': 'Señalar',
'signal.sent': 'Aviso registrado.',
'signal.note': 'Mientras un aviso se mantenga, estos dos textos no pueden estar en vigor a la vez. Su autor lo levanta; un cambio de fondo lo deja sin efecto.',
'signal.lift': 'Levantar',
'signal.lifted': 'levantado',
'signal.lifted.revision': 'sin efecto: el texto ha cambiado',
'signal.by': 'por {name}',
'objection.title': 'Objeciones',
'objection.none': 'Ninguna objeción.',
'objection.add': 'Hacer una objeción',
'objection.kind': 'Tu objeción',
'objection.kind.redit': 'Repite',
'objection.kind.contredit': 'Contradice',
'objection.kind.redit.hint': 'este texto repite otro artículo',
'objection.kind.contredit.hint': 'este texto dice lo contrario de otro artículo',
'objection.target': '¿Qué artículo?',
'objection.reason.redit': 'En qué dicen lo mismo estos dos textos',
'objection.reason.contredit': 'En qué se contradicen estos dos textos',
'objection.send': 'Enviar la objeción',
'objection.sent': 'Objeción registrada: la más reciente de las dos versiones no se vota hasta que se levante.',
'objection.note': 'Una objeción hace no votable la más reciente de las dos versiones hasta que se levante — nunca un texto en vigor. Su autor responde: para una repetición, modifica el artículo que repite; para una contradicción, reformula. Tú levantas entonces la objeción o la reiteras; sin respuesta tuya en 3 días, se levanta.',
'objection.says.redit': '«{held}» repite «{other}»',
'objection.says.contredit': '«{held}» contradice «{other}»',
'objection.by': 'objeción de {name}',
'objection.held.here': 'esta versión no se vota',
'objection.phase.author': 'en espera de la respuesta del autor',
'objection.phase.objector': 'respuesta recibida: levantada o reiterada antes del {date}, si no se levanta sola',
'objection.answer.revision': 'respuesta: el texto retenido se reformuló',
'objection.answer.other': 'respuesta: el otro texto cambió',
'objection.answer.proposal': 'respuesta: una propuesta vinculada',
'objection.lifted.objector': 'levantada por la persona que la hizo',
'objection.lifted.admin': 'levantada por un administrador',
'objection.lifted.deadline': 'levantada: 3 días sin continuación',
'objection.lifted.withdrawn': 'levantada: la propuesta se retiró',
'objection.lifted.revision': 'caída: el texto cambió',
'objection.lift': 'Levantar la objeción',
'objection.lift.admin': 'Levantar la objeción (administrador)',
'objection.reiterate': 'Reiterar la objeción',
'objection.done.lifted': 'Objeción levantada.',
'objection.done.reiterated': 'Objeción reiterada: se espera una nueva respuesta.',
'objection.respond.redit': 'Modificar el artículo que repite',
'objection.respond.contredit': 'Reformular',
'objection.linked': 'Esta propuesta responde a una objeción: una vez enviada, la persona que la hizo la levanta o la reitera.',
'messages.title': 'Mensajes',
'messages.intro': 'Lo que espera un gesto tuyo: una objeción que responder, una corrección de errata que validar. Cada mensaje lleva su plazo; un mensaje tratado ya no pide nada.',
'messages.none': 'Ningún mensaje por ahora.',
'messages.readAll': 'Marcar todo como leído',
'messages.read': 'Marcar como leído',
'messages.unread': 'sin leer',
'messages.due': 'plazo: {date}',
'messages.open': 'Verlo en el texto',
'messages.topic.objection': 'Objeción',
'messages.topic.typo': 'Errata',
'messages.objection_raised.redit': '{name} hace una objeción: tu versión «{held}» repite «{other}». No se vota hasta que se levante. Para responder, modifica el artículo que repite.',
'messages.objection_raised.contredit': '{name} hace una objeción: tu versión «{held}» contradice «{other}». No se vota hasta que se levante. Para responder, reformúlala.',
'messages.objection_reiterated': '{name} reitera su objeción: «{held}» sigue sin votarse. Responde de nuevo.',
'messages.objection_answered': 'Tu objeción sobre «{held}» recibió una respuesta. Levántala si te convence, o reitérala. Sin respuesta tuya, se levanta.',
'messages.typo_pending.author': '{name} propone una corrección de errata en tu versión «{title}».',
'messages.typo_pending.admin': '{name} propone una corrección de errata en «{title}», un texto del seed: un administrador la valida.',
'messages.respond': 'Responder',
'messages.outcome.answered': 'respuesta dada',
'messages.outcome.reiterated': 'objeción reiterada',
'messages.outcome.lifted': 'objeción levantada',
'messages.outcome.admin': 'objeción levantada por un administrador',
'messages.outcome.deadline.objection': 'objeción levantada: 3 días sin continuación',
'messages.outcome.deadline.typo': 'corrección aplicada: 3 días sin respuesta',
'messages.outcome.withdrawn': 'propuesta retirada',
'messages.outcome.applied': 'corrección aplicada',
'messages.outcome.refused': 'corrección rechazada',
'messages.outcome.stale': 'corrección caducada: el texto cambió',
'messages.outcome.done': 'tratado',
'propose.mode.replace': 'Modificar este texto',
'propose.mode.remove': 'Retirar este artículo',
@@ -490,7 +605,7 @@ export const es: Messages = {
'error.internal': 'Algo ha fallado por nuestra parte. Inténtalo de nuevo en un momento.',
'error.title': 'El título está vacío o es demasiado largo (120 caracteres como máximo).',
'error.body': 'El texto está vacío o es demasiado largo (4000 caracteres como máximo).',
'error.reason': 'Di en pocas palabras en qué se contradicen estos textos (de 10 a 600 caracteres).',
'error.reason': 'Di en pocas palabras en qué se basa tu objeción (de 10 a 600 caracteres).',
'error.substantive_not_author': 'Este cambio afecta al sentido: solo el autor de la propuesta puede hacerlo. Propón tu propia versión.',
'error.substantive_in_force': 'Este cambio afecta al sentido: proponlo como modificación.',
'error.same_unit': 'Estos dos textos se refieren al mismo artículo: de todos modos no pueden estar en vigor a la vez.',
@@ -509,7 +624,7 @@ export const es: Messages = {
'error.chain_vote_off': 'La votación por la cadena Ğ1 aún no está abierta.',
'error.chain_only': 'Aquí, el voto pasa por la cadena Ğ1, con tu monedero.',
'error.server_vote': 'Aquí se vota directamente, sin monedero.',
'error.too_many_ballots': 'Ya tienes muchos votos esperando a la cadena Ğ1: espera a que se confirmen.',
'error.too_many_ballots': 'Ya tienes muchas papeletas esperando, o abandonadas en la última hora: espera a que se confirmen, o vuelve a intentarlo un poco más tarde.',
'error.lines': 'Este voto ya no vale: recarga la página y vuelve a empezar.',
'error.choice': 'Esta elección no existe.',
'error.author': 'Esta cuenta no es una dirección Ğ1.',
@@ -523,6 +638,15 @@ export const es: Messages = {
'error.not_a_title': 'Este módulo no es el título del documento.',
'error.same_title': 'Ya es el título en vigor.',
'error.section': 'Esta sección no existe en este documento.',
'error.objection': 'Una objeción retiene esta versión: no se vota hasta que se levante.',
'error.kind': 'Di si este texto repite el otro o lo contradice.',
'error.other': 'Elige otro artículo, en vigor o propuesto.',
'error.both_in_force': 'Los dos textos están en vigor: una objeción siempre apunta a una versión que aún no lo está.',
'error.objection_exists': 'Ya tienes una objeción en curso sobre estos dos textos.',
'error.nothing_to_reiterate': 'Nada que reiterar: aún no ha llegado ninguna respuesta.',
'error.lifted': 'Esta objeción ya está levantada.',
'error.settled': 'Esta corrección ya está decidida.',
'error.link': 'Esta propuesta no puede responder a esta objeción: apunta a otro artículo, o la objeción está levantada.',
'error.retry': 'Reintentar',
'common.cancel': 'Cancelar',