+
{#each tabs as [key, label] (key)}
{/each}
@@ -57,6 +67,7 @@
{lang}
onopen={(id) => onchange({ ...panel, proposalId: id, tab: 'vote' })}
onpropose={(prefill) => onchange({ ...panel, tab: 'propose', prefill, draftId: null })}
+ onsettings={() => onchange({ ...panel, proposalId: voteId, tab: 'settings' })}
/>
{:else if panel.tab === 'proposals' && unit}
@@ -82,6 +93,8 @@
prefill={panel.prefill ?? null}
ondone={(r) => onchange(r.proposalId ? { unitId: doc.proposal(r.proposalId)?.unitId ?? panel.unitId, proposalId: r.proposalId, tab: 'vote' } : { ...panel, tab: 'proposals' })}
/>
+ {:else if panel.tab === 'settings'}
+
{/if}
diff --git a/web/src/lib/components/Formula.svelte b/web/src/lib/components/Formula.svelte
index b8d8b86..321d5b0 100644
--- a/web/src/lib/components/Formula.svelte
+++ b/web/src/lib/components/Formula.svelte
@@ -1,7 +1,8 @@
@@ -41,18 +45,23 @@
{t('formula.W', { n: i18n.number(electorate), source: t(source === 'wot' ? 'formula.W.wot' : 'formula.W.group') })}
{t('formula.T', { n: tally.for + tally.against })}
{#if p}
- {t('formula.M', { n: p.majorityPct })} — {t(`inertia.${inertia}`)}
- {t('formula.G', { n: p.gradient })}
+ {t('formula.M', { n: i18n.number(p.majorityPct) })} — {inertiaBadge(inertia)}
+ {t('formula.G', { n: i18n.number(Math.round(p.gradient * 1000) / 1000) })}
{/if}
- {t('formula.B', { n: base })}
- {t('formula.C', { n: floor })}
+ {t('formula.B', { n: i18n.number(base) })}
+ {t('formula.C', { n: i18n.number(floor) })}
{#if evaluation}{t('formula.R', { n: evaluation.required })}{/if}
{t('formula.reading')}
+{#if onsettings}
+
+{/if}
diff --git a/web/src/lib/components/FormulaSettings.svelte b/web/src/lib/components/FormulaSettings.svelte
new file mode 100644
index 0000000..178c713
--- /dev/null
+++ b/web/src/lib/components/FormulaSettings.svelte
@@ -0,0 +1,195 @@
+
+
+{#if view && p}
+
+ {t('settings.title')}
+ {t('settings.intro')}
+
+
+ - {t('formula.W', { n: i18n.number(view.doc.electorate), source: t(view.doc.electorateSource === 'wot' ? 'formula.W.wot' : 'formula.W.group') })}
+ - {t('formula.C', { n: i18n.number(view.doc.voteFloor) })}
+ - {t('formula.B', { n: i18n.number(view.doc.formula.base) })}
+
+
+
+
{t('settings.usual')}
+
+ {#if p.source === 'median'}
+ {i18n.plural('settings.usual.median', p.voters, { electorate: i18n.number(view.doc.electorate), pct: pct(p.ratio), sample: p.sample })}
+ {:else}
+ {i18n.plural('settings.usual.reference', p.voters, { electorate: i18n.number(view.doc.electorate), pct: pct(p.ratio), min: p.minSample, sample: p.sample })}
+ {/if}
+
+ {#if p.bounded}
{t('settings.usual.bounded', { lo: pct(p.bounds[0]), hi: pct(p.bounds[1]) })}
{/if}
+
{p.measuredAt ? t('settings.usual.when', { date: i18n.date(p.measuredAt) }) : t('settings.usual.never')}
+
+
+ {t('settings.levels')}
+
+ {i18n.plural('settings.share.note', p.voters)}
+ {i18n.plural(version ? 'settings.required.version' : 'settings.required.usual', cast)}
+ {t('settings.reading')}
+
+{/if}
+
+
diff --git a/web/src/lib/components/InertiaPicker.svelte b/web/src/lib/components/InertiaPicker.svelte
new file mode 100644
index 0000000..1ba35ce
--- /dev/null
+++ b/web/src/lib/components/InertiaPicker.svelte
@@ -0,0 +1,171 @@
+
+
+
+ {t('inertia.title')} · {inertiaLevel(unit.inertia)}
+ {t('inertia.explain')}
+ {#each unit.inertiaChanges as c (c.at + c.to)}
+ {t(`inertia.history.${c.cause}`, { from: level(c.from), to: level(c.to), date: i18n.date(c.at) })}
+ {/each}
+ {#each open as ip (ip.id)}
+
+ {/each}
+
+ {t('inertia.change')}
+
+ {#each INERTIA_LEVELS as k (k)}
+
+ {/each}
+
+
+ {t('inertia.change.note', { level: inertiaLevel(unit.inertia) })}
+
+ {#if error}{error}
{/if}
+
+
+
diff --git a/web/src/lib/components/VoteView.svelte b/web/src/lib/components/VoteView.svelte
index 2ea98c6..a3495f8 100644
--- a/web/src/lib/components/VoteView.svelte
+++ b/web/src/lib/components/VoteView.svelte
@@ -2,11 +2,12 @@
import { api, ApiError } from '$lib/api';
import { shown, type DocState } from '$lib/doc.svelte';
import { i18n, t, type Key } from '$lib/i18n/index.svelte';
- import { titleOf } from '$lib/labels';
+ import { inertiaBadge, titleOf } from '$lib/labels';
import { toasts } from '$lib/toast.svelte';
- import type { DiffPart, Entered, Inertia, Lang, Proposal } from '$lib/types';
+ import type { DiffPart, Entered, Inertia, Lang, LevelInfo, Proposal } from '$lib/types';
import DiffText from './DiffText.svelte';
import Formula from './Formula.svelte';
+ import InertiaPicker from './InertiaPicker.svelte';
import Prose from './Prose.svelte';
import WaterLevel from './WaterLevel.svelte';
@@ -16,12 +17,14 @@
lang,
onopen,
onpropose,
+ onsettings,
}: {
doc: DocState;
proposalId: string;
lang: Lang;
onopen: (proposalId: string) => void;
onpropose: (prefill: { title: string; body: string }) => void;
+ onsettings?: () => void;
} = $props();
interface Detail {
@@ -36,13 +39,13 @@
}[];
electorate: number;
voteFloor: number;
+ levels: LevelInfo[];
}
let detail = $state
(null);
let busy = $state(false);
let showDiff = $state(false);
let error = $state('');
- let inertiaTarget = $state('');
let typoOpen = $state(false);
let typoTitle = $state('');
let typoBody = $state('');
@@ -58,7 +61,6 @@
const source = $derived(view?.doc.sourceLang ?? 'fr');
const text = $derived(p ? shown(p, lang, source) : null);
const votable = $derived(p?.status === 'open' || p?.status === 'in_force');
- const inertiaOpen = $derived((unit?.proposals ?? []).filter((x) => x.kind === 'inertia' && x.status === 'open'));
const signalTargets = $derived(
(view?.units ?? []).flatMap((u) =>
[u.inForce, ...u.proposals]
@@ -122,21 +124,6 @@
}
}
- async function proposeInertia() {
- if (!unit || !inertiaTarget) return;
- busy = true;
- try {
- const r = await api.post<{ id: string }>(`/units/${unit.id}/inertia`, { target: inertiaTarget });
- inertiaTarget = '';
- await doc.reload();
- onopen(r.id);
- } catch (e) {
- error = message(e);
- } finally {
- busy = false;
- }
- }
-
async function saveTypo(e: SubmitEvent) {
e.preventDefault();
busy = true;
@@ -250,7 +237,7 @@
{t('tally.required', { n: p.evaluation.required })}
{p.evaluation.reached ? t('tally.reached') : i18n.plural('tally.missing', p.evaluation.missing)}
{/if}
- {t(`inertia.${p.votedAtInertia}`)}
+ {inertiaBadge(p.votedAtInertia)}