forked from yvv/decision
Merge branch 'chantier/acte-certification' into lot/u-lecteur
This commit is contained in:
@@ -78,6 +78,7 @@
|
||||
{/snippet}
|
||||
|
||||
{#snippet marks(p: Proposal)}
|
||||
{#if p.seedProposal}<span class="tag water" title={t('doc.seedProposal.long')}>{t('doc.seedProposal')}</span>{/if}
|
||||
{#if p.evaluation?.wellRated}<span class="tag water">{t('deep.wellrated')}</span>{/if}
|
||||
{#if p.blocked}<span class="tag ochre">{t('deep.held')}</span>{/if}
|
||||
{#if p.myVoteStale}<span class="tag ochre">{t('deep.stale')}</span>{/if}
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
<span class="text">
|
||||
<span class="eyebrow">{v.status === 'in_force' ? t('panel.inforce') : t('panel.proposal')}</span>
|
||||
<span class="title">{nameOfVersion(v)}</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 class="faint small">{v.status === 'in_force' ? (v.seedMinimal ? t('doc.seed') : '') : v.seedProposal ? t('doc.seedProposal') : 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>
|
||||
|
||||
@@ -33,12 +33,18 @@
|
||||
<details class="formula">
|
||||
<summary>{t('formula.title')}</summary>
|
||||
<p class="muted">{t('formula.intro')}</p>
|
||||
<!-- ADR 0013: judged on T′ = max(T, Q) voters; below the quorum, the voters missing count as against. -->
|
||||
<math display="block" class="math">
|
||||
<mrow>
|
||||
<mi>R</mi><mo>=</mo><mi>C</mi><mo>+</mo><msup><mi>B</mi><mi>W</mi></msup><mo>+</mo>
|
||||
<msup><mi>T</mi><mo>′</mo></msup><mo>=</mo><mi>max</mi><mo>(</mo><mi>T</mi><mo>,</mo><mi>Q</mi><mo>)</mo>
|
||||
</mrow>
|
||||
</math>
|
||||
<math display="block" class="math">
|
||||
<mrow>
|
||||
<mi>R</mi><mo>=</mo><msup><mi>B</mi><mi>W</mi></msup><mo>+</mo>
|
||||
<mrow><mo>(</mo><mi>M</mi><mo>+</mo><mo>(</mo><mn>1</mn><mo>−</mo><mi>M</mi><mo>)</mo><mo>(</mo><mn>1</mn><mo>−</mo>
|
||||
<msup><mrow><mo>(</mo><mfrac><mi>T</mi><mi>W</mi></mfrac><mo>)</mo></mrow><mi>G</mi></msup><mo>)</mo><mo>)</mo></mrow>
|
||||
<mo>·</mo><mi>max</mi><mo>(</mo><mn>0</mn><mo>,</mo><mi>T</mi><mo>−</mo><mi>C</mi><mo>)</mo>
|
||||
<msup><mrow><mo>(</mo><mfrac><msup><mi>T</mi><mo>′</mo></msup><mi>W</mi></mfrac><mo>)</mo></mrow><mi>G</mi></msup><mo>)</mo><mo>)</mo></mrow>
|
||||
<mo>·</mo><msup><mi>T</mi><mo>′</mo></msup>
|
||||
</mrow>
|
||||
</math>
|
||||
<ul class="values">
|
||||
@@ -49,7 +55,7 @@
|
||||
<li>{t('formula.G', { n: i18n.number(Math.round(p.gradient * 1000) / 1000) })}</li>
|
||||
{/if}
|
||||
<li>{t('formula.B', { n: i18n.number(base) })}</li>
|
||||
<li>{t('formula.C', { n: i18n.number(floor) })}</li>
|
||||
<li>{t('formula.Q', { n: i18n.number(evaluation?.quorum ?? p?.quorum ?? floor) })}</li>
|
||||
{#if evaluation}<li class="r">{t('formula.R', { n: evaluation.required })}</li>{/if}
|
||||
</ul>
|
||||
<p class="faint">{t('formula.reading')}</p>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
// The settings of the formula, read only: W and its source, C, B, the usual
|
||||
// The settings of the formula, read only: W and its source, B, the usual
|
||||
// turnout of the group (median or reference) and the five levels on its
|
||||
// scale. From the head of the document, the *for* votes asked are those of
|
||||
// the usual turnout; from a version, those of its own turnout.
|
||||
// scale, each with its quorum (ADR 0013). From the head of the document, the
|
||||
// *for* votes asked are those of the usual turnout; from a version, those of
|
||||
// its own turnout, on its article's quorum.
|
||||
import { api } from '$lib/api';
|
||||
import type { DocState } from '$lib/doc.svelte';
|
||||
import { i18n, t } from '$lib/i18n/index.svelte';
|
||||
@@ -50,7 +51,6 @@
|
||||
|
||||
<ul class="values">
|
||||
<li>{t('formula.W', { n: i18n.number(view.doc.electorate), source: t(view.doc.electorateSource === 'wot' ? 'formula.W.wot' : 'formula.W.group') })}</li>
|
||||
<li>{t('formula.C', { n: i18n.number(view.doc.voteFloor) })}</li>
|
||||
<li>{t('formula.B', { n: i18n.number(view.doc.formula.base) })}</li>
|
||||
</ul>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
{#if p.source === 'median'}
|
||||
{i18n.plural('settings.usual.median', shown?.voters ?? p.voters, { electorate: i18n.number(view.doc.electorate), pct: pct(shown?.ratio ?? p.ratio), sample: p.sample })}
|
||||
{:else}
|
||||
{i18n.plural('settings.usual.reference', shown?.voters ?? p.voters, { electorate: i18n.number(view.doc.electorate), pct: pct(shown?.ratio ?? p.ratio), min: p.minSample, sample: p.sample })}
|
||||
{i18n.plural('settings.usual.reference', shown?.voters ?? p.voters, { electorate: i18n.number(view.doc.electorate), pct: pct(shown?.ratio ?? p.ratio), q: i18n.number(view.doc.voteFloor), min: p.minSample, sample: p.sample })}
|
||||
{/if}
|
||||
</p>
|
||||
{#if p.bounded}<p class="faint small">{i18n.plural('settings.usual.bounded', p.voters, { lo: pct(p.bounds[0]), hi: pct(p.bounds[1]), pct: pct(p.ratio) })}</p>{/if}
|
||||
@@ -76,6 +76,7 @@
|
||||
<th scope="col">M</th>
|
||||
<th scope="col">G</th>
|
||||
<th scope="col">{t('settings.col.share')}</th>
|
||||
<th scope="col">{t('settings.col.quorum')}</th>
|
||||
<th scope="col">{t('settings.col.required')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -92,6 +93,7 @@
|
||||
<td>{whole(l.majorityPct)}</td>
|
||||
<td>{gradient(l.gradient)}</td>
|
||||
<td>{whole(l.sharePct)}</td>
|
||||
<td>{l.quorum === undefined ? '—' : i18n.number(l.quorum)}</td>
|
||||
<td class="required">{l.required === null ? '—' : i18n.number(l.required)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -99,6 +101,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<p class="faint small">{i18n.plural('settings.share.note', p.voters)}</p>
|
||||
<p class="faint small">{t('settings.quorum.note')}</p>
|
||||
<p class="faint small">{i18n.plural(version ? 'settings.required.version' : 'settings.required.usual', cast)}</p>
|
||||
<p class="faint small">{t('settings.reading')}</p>
|
||||
</section>
|
||||
@@ -194,4 +197,15 @@
|
||||
.tag {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
/* On a phone, six columns hold in the sheet: the headers and the level's name wrap. */
|
||||
@media (max-width: 480px) {
|
||||
th,
|
||||
td {
|
||||
padding: 0.4rem 0.3rem;
|
||||
}
|
||||
thead th,
|
||||
th[scope='row'] {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -238,6 +238,7 @@
|
||||
<h3>{p.kind === 'text' || p.kind === 'title' ? text.title : titleOf(p, unit)}</h3>
|
||||
<p class="meta faint">
|
||||
{#if p.seedMinimal}<span class="tag water" title={t('doc.seed.long')}>{t('doc.seed')}</span>
|
||||
{:else if p.seedProposal}<span class="tag water" title={t('doc.seedProposal.long')}>{t('doc.seedProposal')}</span>
|
||||
{:else if p.confirmedAt}<span class="tag moss">{t('doc.confirmed')}</span>
|
||||
{:else if p.enteredAt && p.status === 'in_force'}<span class="tag moss">{t('doc.adopted', { date: i18n.date(p.enteredAt) })}</span>{/if}
|
||||
{#if p.blocked}<span class="tag ochre">{t('deep.held')}</span>{/if}
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
<div class="level">
|
||||
<WaterLevel tally={p.tally} evaluation={p.evaluation} blocked={p.blocked} size="large" />
|
||||
<div class="numbers">
|
||||
<p class="tally">{t('tally.short', { for: p.tally.for, against: p.tally.against })}</p>
|
||||
<p class="tally">{t('tally.short', { for: p.tally.for, against: p.tally.against })}{#if p.tallySimulated?.for || p.tallySimulated?.against} <span class="faint small">{i18n.plural('vote.tally.simulated', p.tallySimulated.for + p.tallySimulated.against)}</span>{/if}</p>
|
||||
{#if p.evaluation}
|
||||
<p class="muted">{t('tally.required', { n: p.evaluation.required })}</p>
|
||||
<p class="state">{p.evaluation.reached ? t('tally.reached') : i18n.plural('tally.missing', p.evaluation.missing)}</p>
|
||||
@@ -186,8 +186,8 @@
|
||||
{/if}
|
||||
{#if p.myVoteStale}
|
||||
<p class="warn">{t('vote.stale')}</p>
|
||||
{:else if p.myVote}
|
||||
<p class="voted {p.myVote}">{t(p.myVote === 'for' ? 'vote.mine.for' : 'vote.mine.against')} · <span class="how">{t(p.myVoteChain ? 'vote.mine.chain' : 'vote.mine.saved')}</span></p>
|
||||
{:else if p.myVote && p.myVoteState !== 'pending'}
|
||||
<p class="voted {p.myVote}">{t(p.myVote === 'for' ? 'vote.mine.for' : 'vote.mine.against')} · <span class="how">{t(p.myVoteChain ? 'vote.mine.chain' : p.myVoteState === 'simulated' ? 'vote.mine.simulated' : 'vote.mine.saved')}</span></p>
|
||||
{:else if ballot?.status !== 'pending'}
|
||||
<p class="muted">{t('vote.mine.none')}</p>
|
||||
{/if}
|
||||
@@ -215,6 +215,8 @@
|
||||
{#if chain && access.chain}
|
||||
<p class="faint small">{t('vote.chain.note', { amount: g1Amount(access.chain.amount) })}</p>
|
||||
<p class="faint small">{t('vote.chain.counts')}</p>
|
||||
{:else if access.simulated}
|
||||
<p class="faint small">{t('vote.simulated.note')}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
<p class="faint small">{t('vote.private')}</p>
|
||||
|
||||
+25
-10
@@ -30,7 +30,8 @@ export const en: Messages = {
|
||||
'nav.menu': 'Menu',
|
||||
'nav.close': 'Close',
|
||||
|
||||
'home.title': 'Making collective decisions possible',
|
||||
'home.title.line1': 'Making it possible',
|
||||
'home.title.line2': 'to decide together',
|
||||
'home.leitmotiv.context': 'context and framing',
|
||||
'home.leitmotiv.process': 'processes and methods',
|
||||
'home.leitmotiv.tools': 'a toolbox',
|
||||
@@ -122,6 +123,8 @@ export const en: Messages = {
|
||||
'doc.seed.long': 'Came into force without a vote, so as not to start from a blank page. It remains open for voting, like any text.',
|
||||
'doc.seed.votes.one': 'seed a minima · {n} vote',
|
||||
'doc.seed.votes.other': 'seed a minima · {n} votes',
|
||||
'doc.seedProposal': 'Seed proposal',
|
||||
'doc.seedProposal.long': 'A starting text put forward to start the vote. It is not in force: it comes into force through the vote, like any proposal.',
|
||||
'doc.confirmed': 'confirmed by vote',
|
||||
'doc.adopted': 'adopted on {date}',
|
||||
'doc.action.vote': 'Voting',
|
||||
@@ -134,7 +137,7 @@ export const en: Messages = {
|
||||
'doc.publish.pending': 'Publication pending',
|
||||
|
||||
'deep.title': 'Open proposals',
|
||||
'deep.note': 'Not in force yet. Each one moves up as soon as it reaches its threshold: at least {floor} votes in favor, then the majority its inertia requires.',
|
||||
'deep.note': 'Not in force yet. Each one moves up as soon as it reaches its threshold: the share of votes in favour its inertia requires, counted on at least its quorum of voters.',
|
||||
'deep.new': 'Propose a new article',
|
||||
'deep.drafts': 'Your drafts',
|
||||
'deep.drafts.note': 'Not submitted to vote. Only you can see them.',
|
||||
@@ -252,6 +255,10 @@ export const en: Messages = {
|
||||
'vote.mine.chain': 'recorded on the Ğ1 chain',
|
||||
'vote.mine.saved': 'saved',
|
||||
'vote.mine.pending': 'Waiting for the Ğ1 chain: {choice}.',
|
||||
'vote.mine.simulated': 'simulated',
|
||||
'vote.simulated.note': 'The Ğ1 chain is not open yet: your vote is simulated. It counts as a vote, marked “simulated”, until it is erased when the chain opens.',
|
||||
'vote.tally.simulated.one': 'of which {n} simulated',
|
||||
'vote.tally.simulated.other': 'of which {n} simulated',
|
||||
'vote.mine.refused': 'Your last vote was not counted: {reason}',
|
||||
'vote.mine.expired': 'Your last vote was not seen on the Ğ1 chain in time: it does not count.',
|
||||
'vote.mine.receipts': 'Your ballots and receipts',
|
||||
@@ -266,6 +273,7 @@ export const en: Messages = {
|
||||
'ballot.reason.not_member': 'the wallet that signed is not that of a member of the Ğ1 web of trust.',
|
||||
'ballot.reason.no_identity': 'the wallet that signed is linked to no Ğ1 identity.',
|
||||
'ballot.reason.transfer': 'the transfer to the vote account is missing, or its amount is too low.',
|
||||
'ballot.reason.objection': 'an objection was holding this version back when the Ğ1 chain confirmed your vote; your earlier vote stands.',
|
||||
'basket.title': 'Voting basket',
|
||||
'basket.intro': 'Choose article by article, a whole section or the whole document: it all goes in a single gesture.',
|
||||
'basket.doc': 'The whole document',
|
||||
@@ -333,6 +341,7 @@ export const en: Messages = {
|
||||
'ballots.lines.other': '{n} votes',
|
||||
'ballots.status.pending': 'Waiting for the Ğ1 chain',
|
||||
'ballots.status.confirmed': 'Counted for the Ğ1 identity “{name}”',
|
||||
'ballots.status.simulated': 'Simulated: counted for “{name}” until the Ğ1 chain opens',
|
||||
'ballots.status.refused': 'Not counted: {reason}',
|
||||
'ballots.status.expired': 'Not seen on the Ğ1 chain in time: it does not count',
|
||||
'ballots.block': 'block {n}',
|
||||
@@ -372,17 +381,17 @@ export const en: Messages = {
|
||||
'formula.M': 'M — majority at full turnout: {n}%',
|
||||
'formula.G': 'G — gradient: {n}',
|
||||
'formula.B': 'B — base: {n}',
|
||||
'formula.C': 'C — minimum votes in favor: {n}',
|
||||
'formula.Q': 'Q — quorum: {n}',
|
||||
'formula.R': 'R — current threshold: {n} votes in favor',
|
||||
'formula.reading': 'At low turnout, near-unanimity is needed; when everyone votes, majority M is enough. Nothing passes with fewer than C votes in favor.',
|
||||
'formula.reading': 'At low turnout, near-unanimity is needed; when everyone votes, majority M is enough. Below the quorum Q, the missing voters count as against.',
|
||||
'common.pct': '{n}%',
|
||||
'settings.title': 'Formula settings',
|
||||
'settings.intro': 'To change a text, the group asks for a number of votes in favour that depends on its electorate, its usual turnout and the inertia of the article.',
|
||||
'settings.usual': 'Usual turnout',
|
||||
'settings.usual.median.one': '{n} voter out of {electorate} ({pct}): the median of the {sample} proposals voted on over the past year.',
|
||||
'settings.usual.median.other': '{n} voters out of {electorate} ({pct}): the median of the {sample} proposals voted on over the past year.',
|
||||
'settings.usual.reference.one': '{n} voter out of {electorate} ({pct}), twice C: the reference turnout, until the group has voted on {min} proposals ({sample} so far).',
|
||||
'settings.usual.reference.other': '{n} voters out of {electorate} ({pct}), twice C: the reference turnout, until the group has voted on {min} proposals ({sample} so far).',
|
||||
'settings.usual.reference.one': '{n} voter out of {electorate} ({pct}), twice {q}, the reference quorum: the reference turnout, until the group has voted on {min} proposals ({sample} so far).',
|
||||
'settings.usual.reference.other': '{n} voters out of {electorate} ({pct}), twice {q}, the reference quorum: the reference turnout, until the group has voted on {min} proposals ({sample} so far).',
|
||||
'settings.usual.bounded.one': 'Kept between {lo} and {hi} of the electorate: the group asks for its votes in favour as if {n} person voted ({pct}).',
|
||||
'settings.usual.bounded.other': 'Kept between {lo} and {hi} of the electorate: the group asks for its votes in favour as if {n} people voted ({pct}).',
|
||||
'settings.usual.when': 'Measured once a day, last on {date}.',
|
||||
@@ -390,15 +399,17 @@ export const en: Messages = {
|
||||
'settings.levels': 'The five levels',
|
||||
'settings.col.level': 'Level',
|
||||
'settings.col.share': 'Share asked',
|
||||
'settings.col.quorum': 'Quorum',
|
||||
'settings.col.required': 'Votes for',
|
||||
'settings.current': 'this article',
|
||||
'settings.share.note.one': 'Share asked: the votes in favour asked beyond C, at {n} voter as usual.',
|
||||
'settings.share.note.other': 'Share asked: the votes in favour asked beyond C, at {n} voters as usual.',
|
||||
'settings.share.note.one': 'Share asked: the share of votes in favour asked, at {n} voter as usual.',
|
||||
'settings.share.note.other': 'Share asked: the share of votes in favour asked, at {n} voters as usual.',
|
||||
'settings.quorum.note': "Quorum: below it, the missing voters count as against. It is the level's; the author of an article may set another one before the first vote.",
|
||||
'settings.required.version.one': "Votes for: at {n} voter, this version's turnout.",
|
||||
'settings.required.version.other': "Votes for: at {n} voters, this version's turnout.",
|
||||
'settings.required.usual.one': 'Votes for: at {n} voter, as usual.',
|
||||
'settings.required.usual.other': 'Votes for: at {n} voters, as usual.',
|
||||
'settings.reading': 'At each level, the group asks for its share of votes in favour beyond C when it votes as usual; the more voters, the closer it comes to M. Nothing passes below C votes in favour.',
|
||||
'settings.reading': 'At each level, the group asks for its share of votes in favour when it votes as usual; the more voters, the closer it comes to M. Below the quorum of the level, the missing voters count as against.',
|
||||
|
||||
'history.title': 'Corrections',
|
||||
'history.initial': 'initial text',
|
||||
@@ -470,7 +481,7 @@ export const en: Messages = {
|
||||
'objection.respond.contredit': 'Reword',
|
||||
'objection.linked': 'This proposal answers an objection: once submitted, the person who raised it lifts it or reiterates it.',
|
||||
'messages.title': 'Messages',
|
||||
'messages.intro': 'What waits for a gesture from you: an objection to answer, a typo fix to validate. Each message carries its deadline; a message dealt with asks nothing more.',
|
||||
'messages.intro': 'What waits for a gesture from you: an objection to answer, a typo fix to validate. Each message carries its deadline; a message dealt with asks nothing more. You also read here what became of a vote that was not counted.',
|
||||
'messages.none': 'No messages for now.',
|
||||
'messages.readAll': 'Mark all as read',
|
||||
'messages.read': 'Mark as read',
|
||||
@@ -479,12 +490,16 @@ export const en: Messages = {
|
||||
'messages.open': 'See it in the text',
|
||||
'messages.topic.objection': 'Objection',
|
||||
'messages.topic.typo': 'Typo',
|
||||
'messages.topic.ballot': 'Vote',
|
||||
'messages.objection_raised.redit': '{name} raises an objection: your version “{held}” repeats “{other}”. Until it is lifted, nobody can vote for it any more. To answer, change the article it repeats.',
|
||||
'messages.objection_raised.contredit': '{name} raises an objection: your version “{held}” contradicts “{other}”. Until it is lifted, nobody can vote for it any more. To answer, reword it.',
|
||||
'messages.objection_reiterated': '{name} reiterates their objection: nobody can vote for “{held}” yet. Answer again.',
|
||||
'messages.objection_answered': 'Your objection on “{held}” has been answered. Lift it if the answer suits you, or reiterate it. With no answer from you, it is lifted.',
|
||||
'messages.typo_pending.author': '{name} suggests a typo fix in your version “{title}”.',
|
||||
'messages.typo_pending.admin': '{name} suggests a typo fix in “{title}”, a seed text: an administrator validates it.',
|
||||
'messages.ballot_expired': 'Your vote of {date} ({articles}) was not seen on the Ğ1 chain within 24 hours: it is set aside. You can vote again.',
|
||||
'messages.ballot_refused': 'Your vote of {date} ({articles}) was not counted: {reason} You can vote again.',
|
||||
'messages.ballot_objection': 'Your “for” on {articles} is set aside: an objection was holding this version back when the Ğ1 chain confirmed your vote. Your earlier vote stands.',
|
||||
'messages.respond': 'Answer',
|
||||
'messages.outcome.answered': 'answered',
|
||||
'messages.outcome.reiterated': 'objection reiterated',
|
||||
|
||||
+25
-10
@@ -30,7 +30,8 @@ export const es: Messages = {
|
||||
'nav.menu': 'Menú',
|
||||
'nav.close': 'Cerrar',
|
||||
|
||||
'home.title': 'Hacer posible la decisión colectiva',
|
||||
'home.title.line1': 'Hacer posible',
|
||||
'home.title.line2': 'la decisión colectiva',
|
||||
'home.leitmotiv.context': 'contexto y encuadre',
|
||||
'home.leitmotiv.process': 'procesos y modalidades',
|
||||
'home.leitmotiv.tools': 'caja de herramientas',
|
||||
@@ -122,6 +123,8 @@ export const es: Messages = {
|
||||
'doc.seed.long': 'Entró en vigor sin votación, para no partir de una página en blanco. Sigue en votación, como todo texto.',
|
||||
'doc.seed.votes.one': 'seed a minima · {n} voto',
|
||||
'doc.seed.votes.other': 'seed a minima · {n} votos',
|
||||
'doc.seedProposal': 'Propuesta seed',
|
||||
'doc.seedProposal.long': 'Texto de partida destacado para lanzar la votación. No está en vigor: entra en vigor por la votación, como toda propuesta.',
|
||||
'doc.confirmed': 'confirmado por la votación',
|
||||
'doc.adopted': 'adoptado el {date}',
|
||||
'doc.action.vote': 'Votación',
|
||||
@@ -134,7 +137,7 @@ export const es: Messages = {
|
||||
'doc.publish.pending': 'Publicación pendiente',
|
||||
|
||||
'deep.title': 'Propuestas en curso',
|
||||
'deep.note': 'Aún no están en vigor. Cada una sube en cuanto alcanza su umbral: al menos {floor} votos a favor y, después, la mayoría que exige su inercia.',
|
||||
'deep.note': 'Aún no están en vigor. Cada una sube en cuanto alcanza su umbral: la proporción de votos a favor que exige su inercia, contada sobre al menos su quórum de votantes.',
|
||||
'deep.new': 'Proponer un artículo nuevo',
|
||||
'deep.drafts': 'Tus borradores',
|
||||
'deep.drafts.note': 'No sometidos a votación. Solo tú los ves.',
|
||||
@@ -252,6 +255,10 @@ export const es: Messages = {
|
||||
'vote.mine.chain': 'inscrito en la cadena Ğ1',
|
||||
'vote.mine.saved': 'registrado',
|
||||
'vote.mine.pending': 'Esperando a la cadena Ğ1: {choice}.',
|
||||
'vote.mine.simulated': 'simulado',
|
||||
'vote.simulated.note': 'La cadena Ğ1 aún no está abierta: tu voto es simulado. Cuenta como un voto, marcado «simulado», hasta que se borre al abrirse la cadena.',
|
||||
'vote.tally.simulated.one': 'de ellos {n} simulado',
|
||||
'vote.tally.simulated.other': 'de ellos {n} simulados',
|
||||
'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',
|
||||
@@ -266,6 +273,7 @@ export const es: Messages = {
|
||||
'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.',
|
||||
'ballot.reason.objection': 'una objeción retenía esta versión cuando la cadena Ğ1 confirmó tu voto; tu voto anterior se mantiene.',
|
||||
'basket.title': 'Cesta de votación',
|
||||
'basket.intro': 'Elige artículo por artículo, una sección entera o todo el documento: todo sale en un solo gesto.',
|
||||
'basket.doc': 'Todo el documento',
|
||||
@@ -333,6 +341,7 @@ export const es: Messages = {
|
||||
'ballots.lines.other': '{n} votos',
|
||||
'ballots.status.pending': 'Esperando a la cadena Ğ1',
|
||||
'ballots.status.confirmed': 'Contado para la identidad Ğ1 «{name}»',
|
||||
'ballots.status.simulated': 'Simulado: contado para «{name}» hasta que se abra la cadena Ğ1',
|
||||
'ballots.status.refused': 'No contado: {reason}',
|
||||
'ballots.status.expired': 'No visto a tiempo en la cadena Ğ1: no cuenta',
|
||||
'ballots.block': 'bloque {n}',
|
||||
@@ -372,17 +381,17 @@ export const es: Messages = {
|
||||
'formula.M': 'M — mayoría con participación plena: {n} %',
|
||||
'formula.G': 'G — gradiente: {n}',
|
||||
'formula.B': 'B — base: {n}',
|
||||
'formula.C': 'C — mínimo de votos a favor: {n}',
|
||||
'formula.Q': 'Q — quórum: {n}',
|
||||
'formula.R': 'R — umbral actual: {n} votos a favor',
|
||||
'formula.reading': 'Con poca participación, hace falta casi la unanimidad; cuando vota todo el mundo, basta la mayoría M. Nada pasa con menos de C votos a favor.',
|
||||
'formula.reading': 'Con poca participación, hace falta casi la unanimidad; cuando vota todo el mundo, basta la mayoría M. Por debajo del quórum Q, los votantes que faltan cuentan como «no».',
|
||||
'common.pct': '{n} %',
|
||||
'settings.title': 'Ajustes de la fórmula',
|
||||
'settings.intro': 'Para cambiar un texto, el grupo pide un número de votos a favor que depende de su electorado, de su participación habitual y de la inercia del artículo.',
|
||||
'settings.usual': 'Participación habitual',
|
||||
'settings.usual.median.one': '{n} votante de {electorate} ({pct}): la mediana de las {sample} propuestas votadas en el último año.',
|
||||
'settings.usual.median.other': '{n} votantes de {electorate} ({pct}): la mediana de las {sample} propuestas votadas en el último año.',
|
||||
'settings.usual.reference.one': '{n} votante de {electorate} ({pct}), dos veces C: la participación de referencia, hasta que el grupo haya votado {min} propuestas ({sample} por ahora).',
|
||||
'settings.usual.reference.other': '{n} votantes de {electorate} ({pct}), dos veces C: la participación de referencia, hasta que el grupo haya votado {min} propuestas ({sample} por ahora).',
|
||||
'settings.usual.reference.one': '{n} votante de {electorate} ({pct}), dos veces {q}, el quórum de referencia: la participación de referencia, hasta que el grupo haya votado {min} propuestas ({sample} por ahora).',
|
||||
'settings.usual.reference.other': '{n} votantes de {electorate} ({pct}), dos veces {q}, el quórum de referencia: la participación de referencia, hasta que el grupo haya votado {min} propuestas ({sample} por ahora).',
|
||||
'settings.usual.bounded.one': 'Mantenida entre el {lo} y el {hi} del electorado: el grupo pide sus votos a favor como si votara {n} persona ({pct}).',
|
||||
'settings.usual.bounded.other': 'Mantenida entre el {lo} y el {hi} del electorado: el grupo pide sus votos a favor como si votaran {n} personas ({pct}).',
|
||||
'settings.usual.when': 'Medida una vez al día, la última el {date}.',
|
||||
@@ -390,15 +399,17 @@ export const es: Messages = {
|
||||
'settings.levels': 'Los cinco niveles',
|
||||
'settings.col.level': 'Nivel',
|
||||
'settings.col.share': 'Proporción exigida',
|
||||
'settings.col.quorum': 'Quórum',
|
||||
'settings.col.required': 'Votos a favor',
|
||||
'settings.current': 'este artículo',
|
||||
'settings.share.note.one': 'Proporción exigida: los votos a favor pedidos más allá de C, con {n} votante como de costumbre.',
|
||||
'settings.share.note.other': 'Proporción exigida: los votos a favor pedidos más allá de C, con {n} votantes como de costumbre.',
|
||||
'settings.share.note.one': 'Proporción exigida: la parte de votos a favor pedida, con {n} votante como de costumbre.',
|
||||
'settings.share.note.other': 'Proporción exigida: la parte de votos a favor pedida, con {n} votantes como de costumbre.',
|
||||
'settings.quorum.note': 'Quórum: por debajo, los votantes que faltan cuentan como «no». Es el del nivel; el autor de un artículo puede fijar otro antes del primer voto.',
|
||||
'settings.required.version.one': 'Votos a favor: con {n} votante, la participación de esta versión.',
|
||||
'settings.required.version.other': 'Votos a favor: con {n} votantes, la participación de esta versión.',
|
||||
'settings.required.usual.one': 'Votos a favor: con {n} votante, como de costumbre.',
|
||||
'settings.required.usual.other': 'Votos a favor: con {n} votantes, como de costumbre.',
|
||||
'settings.reading': 'En cada nivel, el grupo pide su parte de votos a favor más allá de C cuando vota como de costumbre; cuantos más votantes, más se acerca a M. Nada pasa con menos de C votos a favor.',
|
||||
'settings.reading': 'En cada nivel, el grupo pide su parte de votos a favor cuando vota como de costumbre; cuantos más votantes, más se acerca a M. Por debajo del quórum del nivel, los votantes que faltan cuentan como «no».',
|
||||
|
||||
'history.title': 'Correcciones',
|
||||
'history.initial': 'texto inicial',
|
||||
@@ -470,7 +481,7 @@ export const es: Messages = {
|
||||
'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.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. Aquí lees también qué fue de un voto que no se contó.',
|
||||
'messages.none': 'Ningún mensaje por ahora.',
|
||||
'messages.readAll': 'Marcar todo como leído',
|
||||
'messages.read': 'Marcar como leído',
|
||||
@@ -479,12 +490,16 @@ export const es: Messages = {
|
||||
'messages.open': 'Verlo en el texto',
|
||||
'messages.topic.objection': 'Objeción',
|
||||
'messages.topic.typo': 'Errata',
|
||||
'messages.topic.ballot': 'Voto',
|
||||
'messages.objection_raised.redit': '{name} hace una objeción: tu versión «{held}» repite «{other}». Hasta que se levante, nadie puede votar a favor. Para responder, modifica el artículo que repite.',
|
||||
'messages.objection_raised.contredit': '{name} hace una objeción: tu versión «{held}» contradice «{other}». Hasta que se levante, nadie puede votar a favor. Para responder, reformúlala.',
|
||||
'messages.objection_reiterated': '{name} reitera su objeción: nadie puede votar aún a favor de «{held}». 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.ballot_expired': 'Tu voto del {date} ({articles}) no se vio en la cadena Ğ1 en 24 horas: queda descartado. Puedes votar de nuevo.',
|
||||
'messages.ballot_refused': 'Tu voto del {date} ({articles}) no se contó: {reason} Puedes votar de nuevo.',
|
||||
'messages.ballot_objection': 'Tu «a favor» sobre {articles} queda descartado: una objeción retenía esta versión cuando la cadena Ğ1 confirmó tu voto. Tu voto anterior se mantiene.',
|
||||
'messages.respond': 'Responder',
|
||||
'messages.outcome.answered': 'respuesta dada',
|
||||
'messages.outcome.reiterated': 'objeción reiterada',
|
||||
|
||||
+26
-10
@@ -33,7 +33,9 @@ export const fr = {
|
||||
'nav.menu': 'Menu',
|
||||
'nav.close': 'Fermer',
|
||||
|
||||
'home.title': 'Rendre possible la décision collective',
|
||||
// The title on two lines, the break after « possible » (Yvv, 2026-11-02): one key per line.
|
||||
'home.title.line1': 'Rendre possible',
|
||||
'home.title.line2': 'la décision collective',
|
||||
'home.leitmotiv.context': 'contexte et cadrage',
|
||||
'home.leitmotiv.process': 'processus et modalités',
|
||||
'home.leitmotiv.tools': 'boîte à outils',
|
||||
@@ -125,6 +127,8 @@ export const fr = {
|
||||
'doc.seed.long': "Entré en vigueur sans vote, pour ne pas partir d'une page blanche. Il reste sous vote, comme tout texte.",
|
||||
'doc.seed.votes.one': 'seed a minima · {n} vote',
|
||||
'doc.seed.votes.other': 'seed a minima · {n} votes',
|
||||
'doc.seedProposal': 'Proposition seed',
|
||||
'doc.seedProposal.long': "Texte de départ mis en avant pour lancer le vote. Il n'est pas en vigueur : il y entre par le vote, comme toute proposition.",
|
||||
'doc.confirmed': 'confirmé par le vote',
|
||||
'doc.adopted': 'adopté le {date}',
|
||||
'doc.action.vote': 'Le vote',
|
||||
@@ -137,7 +141,7 @@ export const fr = {
|
||||
'doc.publish.pending': 'Publication en attente',
|
||||
|
||||
'deep.title': 'Propositions en cours',
|
||||
'deep.note': "Pas encore en vigueur. Chacune remonte dès qu'elle atteint son seuil : au moins {floor} voix pour, puis la majorité que demande son inertie.",
|
||||
'deep.note': "Pas encore en vigueur. Chacune remonte dès qu'elle atteint son seuil : la part de voix pour que demande son inertie, comptée sur au moins son quorum de votants.",
|
||||
'deep.new': 'Proposer un nouvel article',
|
||||
'deep.drafts': 'Tes brouillons',
|
||||
'deep.drafts.note': 'Non soumis au vote. Toi seul les vois.',
|
||||
@@ -255,6 +259,10 @@ export const fr = {
|
||||
'vote.mine.chain': 'inscrit sur la chaîne Ğ1',
|
||||
'vote.mine.saved': 'enregistré',
|
||||
'vote.mine.pending': 'En attente de la chaîne Ğ1 : {choice}.',
|
||||
'vote.mine.simulated': 'simulé',
|
||||
'vote.simulated.note': "La chaîne Ğ1 n'est pas encore ouverte : ton vote est simulé. Il compte comme un vote, marqué « simulé », jusqu'à son effacement à l'ouverture de la chaîne.",
|
||||
'vote.tally.simulated.one': 'dont {n} simulé',
|
||||
'vote.tally.simulated.other': 'dont {n} simulés',
|
||||
'vote.mine.refused': "Ton dernier vote n'a pas été compté : {reason}",
|
||||
'vote.mine.expired': "Ton dernier vote n'a pas été vu sur la chaîne Ğ1 à temps : il ne compte pas.",
|
||||
'vote.mine.receipts': 'Tes bulletins et reçus',
|
||||
@@ -269,6 +277,7 @@ export const fr = {
|
||||
'ballot.reason.not_member': "le portefeuille qui a signé n'est pas celui d'un membre de la toile de confiance Ğ1.",
|
||||
'ballot.reason.no_identity': "le portefeuille qui a signé n'est rattaché à aucune identité Ğ1.",
|
||||
'ballot.reason.transfer': 'le virement vers le compte des votes manque, ou son montant est trop faible.',
|
||||
'ballot.reason.objection': "une objection retenait cette version quand la chaîne Ğ1 a confirmé ton vote ; ton vote d'avant reste.",
|
||||
'basket.title': 'Panier de vote',
|
||||
'basket.intro': 'Choisis article par article, une section entière ou tout le document : tout part en un seul geste.',
|
||||
'basket.doc': 'Tout le document',
|
||||
@@ -336,6 +345,7 @@ export const fr = {
|
||||
'ballots.lines.other': '{n} votes',
|
||||
'ballots.status.pending': 'En attente de la chaîne Ğ1',
|
||||
'ballots.status.confirmed': "Compté pour l'identité Ğ1 « {name} »",
|
||||
'ballots.status.simulated': "Simulé : compté pour « {name} » jusqu'à l'ouverture de la chaîne Ğ1",
|
||||
'ballots.status.refused': 'Pas compté : {reason}',
|
||||
'ballots.status.expired': 'Pas vu sur la chaîne Ğ1 à temps : il ne compte pas',
|
||||
'ballots.block': 'bloc {n}',
|
||||
@@ -375,17 +385,17 @@ export const fr = {
|
||||
'formula.M': 'M — majorité à pleine participation : {n} %',
|
||||
'formula.G': 'G — gradient : {n}',
|
||||
'formula.B': 'B — base : {n}',
|
||||
'formula.C': 'C — minimum de voix pour : {n}',
|
||||
'formula.Q': 'Q — quorum : {n}',
|
||||
'formula.R': 'R — seuil actuel : {n} voix pour',
|
||||
'formula.reading': "À faible participation, il faut presque l'unanimité ; quand tout le monde vote, la majorité M suffit. Rien ne passe sous C voix pour.",
|
||||
'formula.reading': "À faible participation, il faut presque l'unanimité ; quand tout le monde vote, la majorité M suffit. Sous le quorum Q, les votants qui manquent comptent comme « non ».",
|
||||
'common.pct': '{n} %',
|
||||
'settings.title': 'Réglages de la formule',
|
||||
'settings.intro': "Pour changer un texte, le groupe demande un nombre de voix pour qui dépend de son électorat, de sa participation habituelle et de l'inertie de l'article.",
|
||||
'settings.usual': 'Participation habituelle',
|
||||
'settings.usual.median.one': '{n} votant sur {electorate} ({pct}) : la médiane des {sample} propositions votées depuis un an.',
|
||||
'settings.usual.median.other': '{n} votants sur {electorate} ({pct}) : la médiane des {sample} propositions votées depuis un an.',
|
||||
'settings.usual.reference.one': "{n} votant sur {electorate} ({pct}), deux fois C : la participation de référence, tant que le groupe n'a pas voté sur {min} propositions ({sample} pour l'instant).",
|
||||
'settings.usual.reference.other': "{n} votants sur {electorate} ({pct}), deux fois C : la participation de référence, tant que le groupe n'a pas voté sur {min} propositions ({sample} pour l'instant).",
|
||||
'settings.usual.reference.one': "{n} votant sur {electorate} ({pct}), deux fois {q}, le quorum de référence : la participation de référence, tant que le groupe n'a pas voté sur {min} propositions ({sample} pour l'instant).",
|
||||
'settings.usual.reference.other': "{n} votants sur {electorate} ({pct}), deux fois {q}, le quorum de référence : la participation de référence, tant que le groupe n'a pas voté sur {min} propositions ({sample} pour l'instant).",
|
||||
'settings.usual.bounded.one': "Ramenée entre {lo} et {hi} de l'électorat : le groupe demande ses voix pour comme si {n} personne votait ({pct}).",
|
||||
'settings.usual.bounded.other': "Ramenée entre {lo} et {hi} de l'électorat : le groupe demande ses voix pour comme si {n} personnes votaient ({pct}).",
|
||||
'settings.usual.when': 'Mesurée une fois par jour, la dernière fois le {date}.',
|
||||
@@ -393,15 +403,17 @@ export const fr = {
|
||||
'settings.levels': 'Les cinq crans',
|
||||
'settings.col.level': 'Cran',
|
||||
'settings.col.share': 'Part exigée',
|
||||
'settings.col.quorum': 'Quorum',
|
||||
'settings.col.required': 'Voix pour',
|
||||
'settings.current': 'cet article',
|
||||
'settings.share.note.one': "Part exigée : les « pour » demandés au-delà de C, à {n} votant comme d'habitude.",
|
||||
'settings.share.note.other': "Part exigée : les « pour » demandés au-delà de C, à {n} votants comme d'habitude.",
|
||||
'settings.share.note.one': "Part exigée : la part de « pour » demandée, à {n} votant comme d'habitude.",
|
||||
'settings.share.note.other': "Part exigée : la part de « pour » demandée, à {n} votants comme d'habitude.",
|
||||
'settings.quorum.note': "Quorum : en dessous, les votants qui manquent comptent comme « non ». C'est celui du cran ; l'auteur d'un article peut en fixer un autre avant le premier vote.",
|
||||
'settings.required.version.one': 'Voix pour : à {n} votant, la participation de cette version.',
|
||||
'settings.required.version.other': 'Voix pour : à {n} votants, la participation de cette version.',
|
||||
'settings.required.usual.one': "Voix pour : à {n} votant, comme d'habitude.",
|
||||
'settings.required.usual.other': "Voix pour : à {n} votants, comme d'habitude.",
|
||||
'settings.reading': "À chaque cran, le groupe demande sa part de « pour » au-delà de C quand il vote comme d'habitude ; plus il y a de votants, plus elle se rapproche de M. Rien ne passe sous C voix pour.",
|
||||
'settings.reading': "À chaque cran, le groupe demande sa part de « pour » quand il vote comme d'habitude ; plus il y a de votants, plus elle se rapproche de M. Sous le quorum du cran, les votants qui manquent comptent comme « non ».",
|
||||
|
||||
'history.title': 'Corrections',
|
||||
'history.initial': 'texte initial',
|
||||
@@ -473,7 +485,7 @@ export const fr = {
|
||||
'objection.respond.contredit': 'Reformuler',
|
||||
'objection.linked': "Cette proposition répond à une objection : une fois soumise, la personne qui l'a faite la lève ou la réitère.",
|
||||
'messages.title': 'Messages',
|
||||
'messages.intro': 'Ce qui attend un geste de toi : une objection à laquelle répondre, une correction de coquille à valider. Chaque message porte son échéance ; un message traité ne demande plus rien.',
|
||||
'messages.intro': "Ce qui attend un geste de toi : une objection à laquelle répondre, une correction de coquille à valider. Chaque message porte son échéance ; un message traité ne demande plus rien. Tu y lis aussi ce qu'il advient d'un vote qui n'a pas été compté.",
|
||||
'messages.none': "Aucun message pour l'instant.",
|
||||
'messages.readAll': 'Tout marquer comme lu',
|
||||
'messages.read': 'Marquer comme lu',
|
||||
@@ -482,12 +494,16 @@ export const fr = {
|
||||
'messages.open': 'Voir dans le texte',
|
||||
'messages.topic.objection': 'Objection',
|
||||
'messages.topic.typo': 'Coquille',
|
||||
'messages.topic.ballot': 'Vote',
|
||||
'messages.objection_raised.redit': "{name} fait une objection : ta version « {held} » redit « {other} ». Jusqu'à sa levée, personne ne peut plus voter pour elle. Pour répondre, modifie l'article visé.",
|
||||
'messages.objection_raised.contredit': "{name} fait une objection : ta version « {held} » contredit « {other} ». Jusqu'à sa levée, personne ne peut plus voter pour elle. Pour répondre, reformule-la.",
|
||||
'messages.objection_reiterated': '{name} réitère son objection : personne ne peut encore voter pour « {held} ». Réponds à nouveau.',
|
||||
'messages.objection_answered': 'Ton objection sur « {held} » a reçu une réponse. Lève-la si elle te convient, ou réitère-la. Sans réponse de ta part, elle est levée.',
|
||||
'messages.typo_pending.author': '{name} propose une correction de coquille dans ta version « {title} ».',
|
||||
'messages.typo_pending.admin': '{name} propose une correction de coquille dans « {title} », un texte du seed : un administrateur la valide.',
|
||||
'messages.ballot_expired': "Ton vote du {date} ({articles}) n'a pas été vu sur la chaîne Ğ1 en 24 heures : il est écarté. Tu peux voter à nouveau.",
|
||||
'messages.ballot_refused': "Ton vote du {date} ({articles}) n'a pas été compté : {reason} Tu peux voter à nouveau.",
|
||||
'messages.ballot_objection': "Ton « pour » sur {articles} est écarté : une objection retenait cette version quand la chaîne Ğ1 a confirmé ton vote. Ton vote d'avant reste.",
|
||||
'messages.respond': 'Répondre',
|
||||
'messages.outcome.answered': 'réponse donnée',
|
||||
'messages.outcome.reiterated': 'objection réitérée',
|
||||
|
||||
+70
-8
@@ -4,17 +4,19 @@ export type LangMap = Partial<Record<Lang, string>>;
|
||||
export type Inertia = 1 | 2 | 3 | 4 | 5;
|
||||
export const INERTIA_LEVELS: Inertia[] = [1, 2, 3, 4, 5];
|
||||
|
||||
/** One level on the group's scale: M and G of the formula, s_k, and the *for* votes asked at `cast` votes. */
|
||||
/** One level on the group's scale: M and G of the formula, s_k, its quorum, and the *for* votes asked at `cast` votes. */
|
||||
export interface LevelInfo {
|
||||
level: Inertia;
|
||||
majorityPct: number;
|
||||
gradient: number;
|
||||
sharePct: number;
|
||||
/** Q — the quorum of the level (ADR 0013); from a version, its article's own if it has one. */
|
||||
quorum?: number;
|
||||
cast: number;
|
||||
required: number | null;
|
||||
}
|
||||
|
||||
/** p̃ — the usual turnout of the group, measured once a day (or the reference 2C/W). */
|
||||
/** p̃ — the usual turnout of the group, measured once a day (or the reference 2Q₀/W, Q₀ = `voteFloor`). */
|
||||
export interface ParticipationInfo {
|
||||
ratio: number;
|
||||
source: 'median' | 'reference';
|
||||
@@ -60,11 +62,17 @@ export interface Tally {
|
||||
|
||||
export interface Evaluation {
|
||||
cast: number;
|
||||
/** Q — the quorum it is judged on (ADR 0013): below it, the voters missing count as against. */
|
||||
quorum?: number;
|
||||
required: number;
|
||||
reached: boolean;
|
||||
missing: number;
|
||||
requiredRatio: number;
|
||||
wellRated: boolean;
|
||||
/** The total of *for* that passes with the *against* cast now. */
|
||||
yesNeeded?: number;
|
||||
/** The total of *against* that holds the *for* cast now in check (0: the quorum holds them). */
|
||||
noNeeded?: number;
|
||||
}
|
||||
|
||||
export interface Translation {
|
||||
@@ -107,7 +115,16 @@ export interface Proposal {
|
||||
enteredAt: string | null;
|
||||
confirmedAt: string | null;
|
||||
seedMinimal: boolean;
|
||||
/** A seed proposal (ADR 0011): open, put forward by the seed to start the vote, not in force. */
|
||||
seedProposal?: boolean;
|
||||
/** The totals shown: recorded, pending and simulated votes, each intention in place of
|
||||
* the vote its session sees as its own (ADR 0012). */
|
||||
tally: Tally;
|
||||
/** The part of `tally` still without proof (an intention pending on the chain). */
|
||||
tallyPending: Tally;
|
||||
/** The part of `tally` that comes from the simulation (ADR 0012). */
|
||||
tallySimulated: Tally;
|
||||
/** On the totals shown (`tally`), the quorum included (ADR 0013). */
|
||||
evaluation: Evaluation | null;
|
||||
votedAtInertia: Inertia;
|
||||
/** Held back by a standing objection: the latest arrival of the two (never a text in force). */
|
||||
@@ -116,11 +133,17 @@ export interface Proposal {
|
||||
votable: boolean;
|
||||
/** Standing objections around this version. */
|
||||
objections: number;
|
||||
/** What the person sees as her vote now: her latest gesture, an intention pending included. */
|
||||
myVote: 'for' | 'against' | null;
|
||||
/** Where « ton vote » stands (ADR 0012): `recorded` — proven by the chain, or a vote of
|
||||
* the server in a managed group; `pending` — an intention waiting for the chain;
|
||||
* `simulated` — a vote of the simulation, while the chain is closed. */
|
||||
myVoteState: 'recorded' | 'pending' | 'simulated' | null;
|
||||
myVoteStale: boolean;
|
||||
/** « Ton vote » was cast on the Ğ1 chain (confirmed). */
|
||||
myVoteChain: boolean;
|
||||
/** A ballot of this session not counted yet: pending on the chain, or refused, or let go. */
|
||||
/** A ballot of this session not counted yet: pending on the chain, or refused, or let go —
|
||||
* or its *for* set aside by an objection (`refused`, reason `objection`, ADR 0012). */
|
||||
myBallot: MyBallot | null;
|
||||
translations: Partial<Record<Lang, Translation>>;
|
||||
/** An order version: the order it gives its section now. */
|
||||
@@ -144,7 +167,7 @@ export type Choice = 'for' | 'against' | 'none';
|
||||
export interface MyBallot {
|
||||
id: string;
|
||||
status: 'pending' | 'refused' | 'expired';
|
||||
reason: 'not_member' | 'no_identity' | 'transfer' | null;
|
||||
reason: 'not_member' | 'no_identity' | 'transfer' | 'objection' | null;
|
||||
choice: Choice;
|
||||
}
|
||||
|
||||
@@ -164,8 +187,12 @@ export interface ChainSettings {
|
||||
|
||||
/** How this session votes in the group of the document, and whether it may now. */
|
||||
export interface VoteAccess {
|
||||
/** `server` too while the chain is closed: the vote is then simulated (`simulated`). */
|
||||
way: 'server' | 'chain';
|
||||
open: boolean;
|
||||
/** The chain is closed: the vote leaves by a write of the server, marked as simulated,
|
||||
* and counts until an act « supprime » sets it aside (ADR 0012). */
|
||||
simulated: boolean;
|
||||
reason: 'members_only' | 'chain_off' | null;
|
||||
chain: ChainSettings | null;
|
||||
/**
|
||||
@@ -188,6 +215,8 @@ export interface Ballot {
|
||||
voter: string | null;
|
||||
entered: Entered[];
|
||||
expiresAt: string;
|
||||
/** `simulated`: a ballot of the simulation, proven by the server, never on the chain. */
|
||||
mode: 'chain' | 'simulated';
|
||||
}
|
||||
|
||||
/** A ballot of the person's history (`GET /api/ballots`): never anyone else's. */
|
||||
@@ -202,6 +231,7 @@ export interface BallotEntry {
|
||||
lines: number;
|
||||
createdAt: string;
|
||||
settledAt: string | null;
|
||||
mode: Ballot['mode'];
|
||||
}
|
||||
|
||||
export interface Unit {
|
||||
@@ -219,6 +249,12 @@ export interface Unit {
|
||||
members?: string[];
|
||||
/** An order of fewer than two articles: nothing to decide, not shown. */
|
||||
moot?: boolean;
|
||||
/** Its own quorum, set by the author of its first version before the first vote; `null`: its level's (ADR 0013). */
|
||||
quorum?: number | null;
|
||||
/** The quorum of its level. */
|
||||
quorumDefault?: number;
|
||||
/** This session may set its quorum now (`PUT /units/{id}/quorum`). */
|
||||
canSetQuorum?: boolean;
|
||||
}
|
||||
|
||||
/** What a document decides besides its articles: its title, the order of each section. */
|
||||
@@ -228,12 +264,16 @@ export interface Modules {
|
||||
}
|
||||
|
||||
export interface Publication {
|
||||
state: 'none' | 'pending' | 'done';
|
||||
/** `suspended`: no article in force yet, the queue waits for the first text (ADR 0011). */
|
||||
state: 'none' | 'pending' | 'suspended' | 'done';
|
||||
configured: boolean;
|
||||
since?: string;
|
||||
doneAt?: string | null;
|
||||
commit?: string | null;
|
||||
attempts?: number;
|
||||
/** Why it is suspended: no article in force yet (`text`, ADR 0011), or something in force
|
||||
* entered by simulated votes that the proven votes do not carry yet (`proven`, ADR 0012). */
|
||||
waitsFor?: 'text' | 'proven' | null;
|
||||
}
|
||||
|
||||
export interface DocView {
|
||||
@@ -248,7 +288,10 @@ export interface DocView {
|
||||
title: LangMap;
|
||||
summary: LangMap;
|
||||
sourceLang: Lang;
|
||||
/** Q₀ — the reference quorum (the C of before): the reference turnout is 2Q₀/W. */
|
||||
voteFloor: number;
|
||||
/** The quorum of each level, 1 to 5 (ADR 0013). */
|
||||
quorums?: number[];
|
||||
electorate: number;
|
||||
electorateSource: 'wot' | 'group';
|
||||
formula: { base: number };
|
||||
@@ -342,11 +385,29 @@ export interface TypoFix {
|
||||
version?: { id: string; title: string; status: Proposal['status']; unitId: string; code: string | null };
|
||||
}
|
||||
|
||||
/** A message of one's box: about an objection or a typo fix, with its gestures now. */
|
||||
/** A ballot set aside, as a message tells it (ADR 0012): line by line, what became of it. */
|
||||
export interface BallotNotice {
|
||||
id: string;
|
||||
status: Ballot['status'];
|
||||
reason: MyBallot['reason'];
|
||||
mode: Ballot['mode'];
|
||||
createdAt: string;
|
||||
settledAt: string | null;
|
||||
lines: { proposalId: string; unitId: string; code: string; title: string; choice: Choice; outcome: 'counted' | 'superseded' | 'objection' | null }[];
|
||||
}
|
||||
|
||||
/** A message of one's box: about an objection, a typo fix or a ballot set aside, with its gestures now. */
|
||||
export interface Message {
|
||||
id: string;
|
||||
topic: 'objection' | 'typo';
|
||||
kind: 'objection_raised' | 'objection_answered' | 'objection_reiterated' | 'typo_pending';
|
||||
topic: 'objection' | 'typo' | 'ballot';
|
||||
kind:
|
||||
| 'objection_raised'
|
||||
| 'objection_answered'
|
||||
| 'objection_reiterated'
|
||||
| 'typo_pending'
|
||||
| 'ballot_expired'
|
||||
| 'ballot_refused'
|
||||
| 'ballot_objection';
|
||||
cycle: number;
|
||||
createdAt: string;
|
||||
dueAt: string | null;
|
||||
@@ -358,4 +419,5 @@ export interface Message {
|
||||
doc: { id: string; groupSlug: string; slug: string; title: LangMap };
|
||||
objection?: Objection;
|
||||
typo?: TypoFix;
|
||||
ballot?: BallotNotice;
|
||||
}
|
||||
|
||||
+121
-39
@@ -1,17 +1,28 @@
|
||||
<script lang="ts">
|
||||
// The welcome (Yvv, 2026-11-02): the keystone arch, large enough to shelter the three points of
|
||||
// the leitmotiv, the toolbox first; then the title, centred, broken after « possible ».
|
||||
import sign from '$lib/assets/cle-de-voute.svg?raw';
|
||||
import GroupChoice from '$lib/components/GroupChoice.svelte';
|
||||
import { t } from '$lib/i18n/index.svelte';
|
||||
import { session } from '$lib/session.svelte';
|
||||
|
||||
const LEITMOTIV = ['home.leitmotiv.tools', 'home.leitmotiv.context', 'home.leitmotiv.process'] as const;
|
||||
</script>
|
||||
|
||||
<div class="home">
|
||||
<header class="intro">
|
||||
<h1>{t('home.title')}</h1>
|
||||
<ul class="leitmotiv">
|
||||
<li>{t('home.leitmotiv.context')}</li>
|
||||
<li>{t('home.leitmotiv.process')}</li>
|
||||
<li>{t('home.leitmotiv.tools')}</li>
|
||||
</ul>
|
||||
<div class="vault">
|
||||
<span class="sign" aria-hidden="true">{@html sign}</span>
|
||||
<ul class="leitmotiv">
|
||||
{#each LEITMOTIV as key, i (key)}
|
||||
<li style:--i={i}>{t(key)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<h1>
|
||||
<span class="line">{t('home.title.line1')}</span>
|
||||
<span class="line">{t('home.title.line2')}</span>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
{#if session.me}
|
||||
@@ -28,56 +39,127 @@
|
||||
.home {
|
||||
max-width: 44rem;
|
||||
margin: 0 auto;
|
||||
padding: clamp(3rem, 10vh, 6.5rem) clamp(1.2rem, 5vw, 3.5rem) 5rem;
|
||||
padding: clamp(2rem, 7vh, 4.5rem) clamp(1.2rem, 5vw, 3.5rem) 5rem;
|
||||
display: grid;
|
||||
gap: clamp(2.4rem, 6vh, 3.6rem);
|
||||
}
|
||||
.intro h1 {
|
||||
font-family: var(--font-text);
|
||||
font-weight: 500;
|
||||
font-size: clamp(2.1rem, 1.4rem + 3vw, 3.4rem);
|
||||
letter-spacing: -0.015em;
|
||||
line-height: 1.08;
|
||||
font-variation-settings: 'opsz' 72;
|
||||
max-width: 15ch;
|
||||
text-wrap: balance;
|
||||
|
||||
/* The arch, drawn by the sign of the logo (stones in the ink of the mood, the keystone in
|
||||
`--cle`), never tilted. Its box is the stone itself: 92 units wide (the base slab, x from
|
||||
−46 to 46) and 59 high (y from −40, the top of the keystone, to 19, under the slab); the SVG's
|
||||
own 3-unit margin spills out above and below (clipped on the sides, where it is empty). On a
|
||||
phone the arch reaches into the side padding, 0.75rem short of the screen's edges, so that the
|
||||
opening keeps as much room as it can. */
|
||||
.vault {
|
||||
--w: min(34rem, 100vw - 1.5rem);
|
||||
container-type: inline-size;
|
||||
position: relative;
|
||||
width: var(--w);
|
||||
margin-inline: calc(50% - var(--w) / 2);
|
||||
aspect-ratio: 92 / 59;
|
||||
overflow-x: clip;
|
||||
color: var(--ink);
|
||||
}
|
||||
/* The leitmotiv: three lines, each set on a small stone of the keystone's colour. */
|
||||
.sign {
|
||||
position: absolute;
|
||||
inset: calc(-300% / 59) calc(-300% / 92);
|
||||
}
|
||||
.sign :global(svg) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/* The light under the vault: the opening (x from −18.5 to 18.5, from the keystone down to the
|
||||
slab), its top a half circle, washed with the keystone's colour. */
|
||||
.vault::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: calc(2150% / 59) calc(2750% / 92) calc(340% / 59);
|
||||
border-radius: 50% 50% 0 0 / 54.25% 54.25% 0 0;
|
||||
background: radial-gradient(
|
||||
ellipse 75% 62% at 50% 60%,
|
||||
color-mix(in srgb, var(--cle) 12%, transparent),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
/* The three points, sheltered: centred in the opening (x from −19 to 19; a band from y = −19,
|
||||
under the keystone, to 19, centred on the springing line y = 0), one line each. The type
|
||||
follows the arch (cqi): the longest line of the three languages, 10.5 em (es), holds in the
|
||||
38 units of the opening with room to spare. On a phone the italic is small (12px at 360):
|
||||
its advances are kept exact, not rounded to the pixel, or the letters drift apart
|
||||
(« pr ocessus ») and the word spaces close up. */
|
||||
.leitmotiv {
|
||||
list-style: none;
|
||||
margin: 1.4rem 0 0;
|
||||
position: absolute;
|
||||
inset: calc(2100% / 59) calc(2700% / 92) 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
align-content: center;
|
||||
justify-items: center;
|
||||
gap: 0.2em;
|
||||
font-family: var(--font-text);
|
||||
font-style: italic;
|
||||
font-size: clamp(1.12rem, 1rem + 0.55vw, 1.4rem);
|
||||
line-height: 1.45;
|
||||
font-size: 3.55cqi;
|
||||
line-height: 1.3;
|
||||
color: var(--ink-soft);
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
text-rendering: geometricPrecision;
|
||||
}
|
||||
.leitmotiv li {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.7rem;
|
||||
|
||||
.intro h1 {
|
||||
margin-top: clamp(1.4rem, 4vh, 2.4rem);
|
||||
font-family: var(--font-text);
|
||||
font-weight: 500;
|
||||
font-size: min(3.3rem, 8.6vw);
|
||||
letter-spacing: -0.015em;
|
||||
line-height: 1.1;
|
||||
font-variation-settings: 'opsz' 72;
|
||||
text-align: center;
|
||||
}
|
||||
.leitmotiv li::before {
|
||||
content: '';
|
||||
flex: none;
|
||||
width: 0.62em;
|
||||
height: 0.34em;
|
||||
border-radius: 0.1em;
|
||||
background: var(--cle);
|
||||
opacity: 0.75;
|
||||
translate: 0 -0.12em;
|
||||
.line {
|
||||
display: block;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.enter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem 1.2rem;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
.enter .btn {
|
||||
min-height: 2.75rem;
|
||||
padding-inline: 1.4rem;
|
||||
}
|
||||
.enter p {
|
||||
font-size: 0.9rem;
|
||||
max-width: 34ch;
|
||||
}
|
||||
|
||||
/* The keystone sets the arch, then the three points come under it. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.sign :global(path[fill^='var(--cle']) {
|
||||
transform-box: fill-box;
|
||||
animation: settle 0.7s var(--ease) 0.1s backwards;
|
||||
}
|
||||
.leitmotiv li {
|
||||
animation: appear 0.6s var(--ease) backwards;
|
||||
animation-delay: calc(0.55s + var(--i) * 0.12s);
|
||||
}
|
||||
}
|
||||
@keyframes settle {
|
||||
from {
|
||||
transform: translateY(-40%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes appear {
|
||||
from {
|
||||
transform: translateY(0.4em);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
|
||||
function standing(b: BallotEntry): string {
|
||||
if (b.status === 'pending') return t('ballots.status.pending');
|
||||
// A vote of the simulation (ADR 0012): counted for the session, never on the chain.
|
||||
if (b.status === 'confirmed' && b.mode === 'simulated') return t('ballots.status.simulated', { name: b.voter ?? '' });
|
||||
if (b.status === 'confirmed') return t('ballots.status.confirmed', { name: b.voter ?? '' });
|
||||
if (b.status === 'refused') return t('ballots.status.refused', { reason: t(`ballot.reason.${b.reason ?? 'transfer'}`) });
|
||||
return t('ballots.status.expired');
|
||||
@@ -62,7 +64,7 @@
|
||||
{#each data.value.ballots as b (b.id)}
|
||||
<li class="card block ballot">
|
||||
<p class="small faint">{i18n.date(b.createdAt)} · {titleOf(b.doc.title)} · {i18n.plural('ballots.lines', b.lines)}</p>
|
||||
<p class="state {b.status}">
|
||||
<p class="state {b.status}" class:simulated={b.mode === 'simulated'}>
|
||||
{standing(b)}{#if b.block !== null}<span class="faint"> · {t('ballots.block', { n: i18n.number(b.block) })}</span>{/if}
|
||||
</p>
|
||||
<code class="comment">{b.comment}</code>
|
||||
@@ -159,7 +161,8 @@
|
||||
.state.confirmed {
|
||||
color: var(--moss);
|
||||
}
|
||||
.state.pending {
|
||||
.state.pending,
|
||||
.state.confirmed.simulated {
|
||||
color: var(--water);
|
||||
}
|
||||
.state.refused,
|
||||
|
||||
@@ -192,6 +192,8 @@
|
||||
|
||||
<section class="text" aria-label={t('doc.view.inforce')}>
|
||||
{#if doc.mode === 'inforce' || !doc.preview}
|
||||
<!-- No article in force (the seed proposals are not, ADR 0011): said once, above the orders. -->
|
||||
{#if !doc.inForce.length}<p class="muted">{t('doc.empty')}</p>{/if}
|
||||
{#each doc.inForceBySection as s, i (`${i}:${s.section}`)}
|
||||
{#each s.units as u (u.id)}
|
||||
{#if u.inForce}
|
||||
@@ -224,8 +226,6 @@
|
||||
{#snippet fold()}{@render foldOf(o, op)}{/snippet}
|
||||
</OrderModule>
|
||||
{/if}
|
||||
{:else}
|
||||
<p class="muted">{t('doc.empty')}</p>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each doc.preview as pu (pu.unitId)}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// The box « Messages » of trustinWallet, in the application (2026-10-31, lot F,
|
||||
// ADR 0010): what waits for a gesture of the person — an objection to answer,
|
||||
// to lift or to reiterate, a typo fix to validate or refuse —, each with its
|
||||
// deadline. A message dealt with offers no gesture any more. No mail.
|
||||
// deadline. A message dealt with offers no gesture any more. No mail. A ballot set
|
||||
// aside (ADR 0012) is told here too, without gesture: one votes again in the text.
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { api } from '$lib/api';
|
||||
@@ -44,10 +45,27 @@
|
||||
await reload();
|
||||
}
|
||||
|
||||
/** The lines of a ballot a message is about: those set aside by an objection, or all. */
|
||||
function linesOf(m: Message) {
|
||||
const lines = m.ballot?.lines ?? [];
|
||||
const held = lines.filter((l) => l.outcome === 'objection');
|
||||
return m.kind === 'ballot_objection' && held.length ? held : lines;
|
||||
}
|
||||
|
||||
/** The text of a message, in the language of the reader, from its subject. */
|
||||
function textOf(m: Message): string {
|
||||
const o = m.objection;
|
||||
const fix = m.typo;
|
||||
const b = m.ballot;
|
||||
if (b) {
|
||||
const articles = linesOf(m)
|
||||
.map((l) => l.code)
|
||||
.join(', ');
|
||||
const date = i18n.date(b.createdAt);
|
||||
if (m.kind === 'ballot_expired') return t('messages.ballot_expired', { date, articles });
|
||||
if (m.kind === 'ballot_refused') return t('messages.ballot_refused', { date, articles, reason: t(`ballot.reason.${b.reason ?? 'transfer'}`) });
|
||||
return t('messages.ballot_objection', { articles });
|
||||
}
|
||||
if (o) {
|
||||
const held = o.heldVersion?.title ?? '';
|
||||
if (m.kind === 'objection_raised') return t(`messages.objection_raised.${o.kind}`, { name: o.objector ?? '', held, other: o.other.title ?? '' });
|
||||
@@ -73,6 +91,8 @@
|
||||
function hrefOf(m: Message): string {
|
||||
const v = m.objection?.heldVersion ?? m.typo?.version ?? null;
|
||||
const base = `/g/${m.doc.groupSlug}/docs/${m.doc.slug}`;
|
||||
const line = linesOf(m)[0];
|
||||
if (line) return `${base}?${panelSearch({ tab: 'details', unitId: line.unitId, proposalId: line.proposalId }, null)}`;
|
||||
if (!v) return base;
|
||||
return `${base}?${panelSearch({ tab: 'details', unitId: v.unitId ?? null, proposalId: v.id }, null)}`;
|
||||
}
|
||||
@@ -149,7 +169,7 @@
|
||||
<p class="text">{textOf(m)}</p>
|
||||
{#if m.objection?.reason && m.kind !== 'objection_answered'}<p class="reason">{m.objection.reason}</p>{/if}
|
||||
{#if m.typo}<DiffText parts={condense(m.typo.diff)} />{/if}
|
||||
{#if m.done}
|
||||
{#if m.done && m.topic !== 'ballot'}
|
||||
<p class="outcome">{outcomeOf(m)}</p>
|
||||
{:else if m.dueAt}
|
||||
<p class="due">{t('messages.due', { date: i18n.date(m.dueAt) })}</p>
|
||||
|
||||
Reference in New Issue
Block a user