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
+15 -3
View File
@@ -5,7 +5,9 @@
import type { DocState, PanelState } from '$lib/doc.svelte';
import { t } from '$lib/i18n/index.svelte';
import { relationOf, titleOf } from '$lib/labels';
import { answerPanel } from '$lib/objections';
import type { BasketState } from '$lib/picks.svelte';
import { session } from '$lib/session.svelte';
import type { Lang } from '$lib/types';
import type { Line } from '$lib/vote';
import BallotPanel from './BallotPanel.svelte';
@@ -77,7 +79,7 @@
<div class="bar">
<div class="tabs" role="tablist" bind:this={tablist}>
{#each tabs as [key, label] (key)}
<button role="tab" type="button" aria-selected={panel.tab === key} class:on={panel.tab === key} onclick={() => onchange({ ...panel, tab: key, prefill: null })}>{label}</button>
<button role="tab" type="button" aria-selected={panel.tab === key} class:on={panel.tab === key} onclick={() => onchange({ ...panel, tab: key, prefill: null, objectionId: null, edit: false })}>{label}</button>
{/each}
</div>
<button class="close" type="button" onclick={onclose} aria-label={t('panel.close')}><Icon name="close" /></button>
@@ -91,8 +93,13 @@
{doc}
proposalId={voteId}
{lang}
edit={!!panel.edit}
onvote={(id) => onvote(id, true)}
onpropose={(prefill) => onchange({ ...panel, tab: 'propose', prefill, draftId: null })}
onpropose={(prefill) => onchange({ ...panel, tab: 'propose', prefill, draftId: null, edit: false })}
onanswer={(o) => {
const next = answerPanel(o, session.me?.id ?? null);
if (next) onchange(next);
}}
/>
{:else if panel.tab === 'proposals' && unit}
<ul class="versions">
@@ -103,7 +110,7 @@
<span class="text">
<span class="eyebrow">{v.status === 'in_force' ? t('panel.inforce') : t('panel.proposal')}</span>
<span class="title">{titleOf(v, unit)}</span>
<span class="faint small">{v.status === 'in_force' ? (v.seedMinimal ? t('doc.seed') : '') : relationOf(v, unit)} · {t('tally.short', { for: v.tally.for, against: v.tally.against })}{#if v.myVote && !v.myVoteStale} · {t('basket.mine', { choice: t(`vote.choice.${v.myVote}`) })}{/if}</span>
<span class="faint small">{v.status === 'in_force' ? (v.seedMinimal ? t('doc.seed') : '') : relationOf(v, unit)} · {t('tally.short', { for: v.tally.for, against: v.tally.against })}{#if v.myVote && !v.myVoteStale} · {t('basket.mine', { choice: t(`vote.choice.${v.myVote}`) })}{/if}{#if v.blocked} · <b class="held">{t('deep.held')}</b>{/if}</span>
</span>
</button>
</li>
@@ -116,6 +123,8 @@
unitId={panel.unitId}
draftId={panel.draftId ?? null}
prefill={panel.prefill ?? null}
from={panel.objectionId ? (panel.proposalId ?? null) : null}
objectionId={panel.objectionId ?? null}
ondone={(r) =>
r.proposalId && r.submitted
? onvote(r.proposalId, true)
@@ -223,6 +232,9 @@
.small {
font-size: 0.82rem;
}
.held {
color: var(--ochre);
}
@keyframes enter {
from {
transform: translateX(16px);