v3 : modules d'ordre et de titre — l'interface, restes mineurs (L11, C25–C27)

Chaque section du lecteur finit par l'ordre de ses articles (inertie 2) : ses
articles numérotés, son vote déplié, « Proposer un autre ordre » — une liste où
l'on clique un article puis sa place, ses flèches, ou le clavier. Le titre du
document (inertie 4) se vote et se propose sous le titre, « traduction à
revoir » quand il a changé. Les deux sont dans la liste des propositions, le
panier et la chaîne Ğ1 ; un nouvel article se propose dans une section, à sa fin.
La liste du groupe compte les propositions ouvertes des modules.

Restes mineurs : la participation ramenée se dit en deux temps (la mesure, puis
ce que le groupe demande), les lignes « habituelle » des réglages resserrées, le
sélecteur d'inertie se parcourt aux flèches (le cran actuel atteignable, jamais
choisi), la marque « seed a minima » porte le vrai nombre de votes.

Contrôles : C25 (ordre, dont la chaîne et le téléphone), C26 (titre), C27
(participation ramenée) ; C3, C17 (41 modules), C20 mis à jour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 05:35:02 +01:00
co-authored by Claude Opus 5.5
parent 3831ddb1bf
commit 785580c78f
31 changed files with 1480 additions and 136 deletions
+29 -10
View File
@@ -5,8 +5,8 @@
// marks — and each unfolds its own vote.
import type { Snippet } from 'svelte';
import { plain } from '$lib/markdown';
import { inertiaBadge, relationOf, titleOf } from '$lib/labels';
import { i18n, t, type Key } from '$lib/i18n/index.svelte';
import { inertiaBadge, moduleName, orderNote, relationOf, sectionLabel, titleOf } from '$lib/labels';
import { i18n, t } from '$lib/i18n/index.svelte';
import type { Proposal, Unit } from '$lib/types';
import { excerpts, type Article } from '$lib/versions';
import Icon from './Icon.svelte';
@@ -18,7 +18,9 @@
ontoggle,
fold,
onsection,
nameOf = (id) => id,
}: {
/** Articles and modules (ADR 0009) with open proposals, in document order. */
articles: Article[];
/** The version whose vote is unfolded. */
vote: string | null;
@@ -26,10 +28,16 @@
fold: Snippet<[Unit, Proposal]>;
/** Opens the basket on a section; absent when this session does not vote here. */
onsection?: (section: string) => void;
/** The title of an article, for the orders proposed. */
nameOf?: (unitId: string) => string;
} = $props();
const SECTIONS = ['introduction', 'fondamental', 'technique', 'conclusion', 'annexe', 'formule', 'inertie', 'ordonnancement'];
const sectionLabel = (s: string | null) => (s && SECTIONS.includes(s) ? t(`section.${s}` as Key) : null);
/** What a line shows of a version: the start of its text, or the order it proposes. */
function excerptOf(p: Proposal): string {
if (p.kind === 'text') return plain(p.body);
if (p.kind === 'order') return (p.order?.ids ?? []).map(nameOf).join(' · ');
return '';
}
// Unfolded by hand, or because the version whose vote is unfolded is inside.
let unfolded = $state<Record<string, boolean>>({});
@@ -38,6 +46,7 @@
/** The title of the article: its best text version, or what the best proposal is. */
function headline(a: Article): string {
if (a.unit.kind) return moduleName(a.unit);
const text = a.versions.find((p) => p.kind === 'text');
const best = a.versions[0];
return text ? text.title : best ? titleOf(best, a.unit) : a.unit.code;
@@ -45,7 +54,7 @@
/** What tells a version from its siblings: its variant, its author, or its rank of arrival. */
function labelOf(p: Proposal, a: Article): string {
if (p.kind !== 'text') return titleOf(p, a.unit);
if (p.kind === 'inertia' || p.kind === 'remove') return titleOf(p, a.unit);
if (p.label) return t('deep.variant', { label: p.label });
if (p.author?.name) return t('panel.by', { name: p.author.name });
const arrival = a.unit.proposals.filter((x) => x.status === 'open').findIndex((x) => x.id === p.id);
@@ -56,10 +65,18 @@
function toldApart(a: Article): Map<string, string> {
const textual = a.versions.filter((p) => p.kind === 'text');
const told = excerpts(textual.map((p) => plain(p.body)));
return new Map(textual.map((p, k) => [p.id, told[k] ?? plain(p.body)]));
return new Map([
...a.versions.map((p): [string, string] => [p.id, excerptOf(p)]),
...textual.map((p, k): [string, string] => [p.id, told[k] ?? plain(p.body)]),
]);
}
</script>
{#snippet module(p: Proposal)}
{#if p.kind === 'title'}<span class="faint">{t('module.title.name')}</span>{/if}
{#each orderNote(p) as note (note)}<span class="faint">{note}</span>{/each}
{/snippet}
{#snippet marks(p: Proposal)}
{#if p.evaluation?.wellRated}<span class="tag water">{t('deep.wellrated')}</span>{/if}
{#if p.blocked}<span class="tag ochre">{t('deep.blocked')}</span>{/if}
@@ -86,11 +103,12 @@
<WaterLevel tally={best.tally} evaluation={best.evaluation} blocked={best.blocked} />
<span class="main">
<span class="title">{titleOf(best, unit)}</span>
{#if best.kind === 'text'}<span class="excerpt">{plain(best.body)}</span>{/if}
{#if excerptOf(best)}<span class="excerpt">{excerptOf(best)}</span>{/if}
<span class="meta">
<span>{t('tally.short', { for: best.tally.for, against: best.tally.against })}</span>
{#if relationOf(best, unit)}<span class="faint">{relationOf(best, unit)}</span>{/if}
{#if unit.inertia !== 3}<span class="faint">{inertiaBadge(unit.inertia)}</span>{/if}
{@render module(best)}
{@render marks(best)}
</span>
</span>
@@ -106,7 +124,7 @@
<WaterLevel tally={best.tally} evaluation={best.evaluation} blocked={best.blocked} />
<span class="main">
<span class="title">{title}</span>
{#if best.kind === 'text'}<span class="excerpt">{plain(best.body)}</span>{/if}
{#if excerptOf(best)}<span class="excerpt">{excerptOf(best)}</span>{/if}
<span class="meta">
<span class="count">{i18n.plural('deep.versions', a.versions.length)}</span>
{#if a.leads}<span>{t('deep.lead', { label: labelOf(best, a) })}</span>{/if}
@@ -126,10 +144,11 @@
<button type="button" class="version" class:current={vote === p.id} aria-expanded={vote === p.id} aria-controls="vote-{p.id}" onclick={() => ontoggle(unit, p)}>
<WaterLevel tally={p.tally} evaluation={p.evaluation} blocked={p.blocked} />
<span class="main">
<span class="label">{labelOf(p, a)}{#if p.kind === 'text' && p.title !== title}<span class="other"> · {p.title}</span>{/if}</span>
{#if p.kind === 'text'}<span class="excerpt">{told.get(p.id)}</span>{/if}
<span class="label">{labelOf(p, a)}{#if (p.kind === 'text' || p.kind === 'title') && p.title !== title}<span class="other"> · {p.title}</span>{/if}</span>
{#if told.get(p.id)}<span class="excerpt">{told.get(p.id)}</span>{/if}
<span class="meta">
<span>{t('tally.short', { for: p.tally.for, against: p.tally.against })}</span>
{#each orderNote(p) as note (note)}<span class="faint">{note}</span>{/each}
{@render marks(p)}
</span>
</span>