Lot U : le lecteur refait — l'ordre en une ligne au bout de sa section, poignées sur place (Reorder), le titre et l'ordre votés à leur place ; Propositions seed en tête quand rien n'est en vigueur ; Propositions en cours repliées par section ; panneau sans onglets ; oui pour passer · non pour bloquer ; quorum de l'auteur ; votes simulés dits simulés

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 07:59:24 +01:00
co-authored by Claude Opus 5.5
parent 70f7bb7247
commit 4cf4e47ee9
17 changed files with 1198 additions and 541 deletions
+40 -32
View File
@@ -1,13 +1,22 @@
<script lang="ts">
// An article as the text reads it: its title, its body, its state; two gestures —
// « Le vote » unfolds under it (its other versions inside), « Proposer une modification »
// opens the panel, where one writes (lot U, UX-lecteur.md). A seed proposal (ADR 0011),
// read at the head while nothing is in force, is marked so: never the bare body of a text
// in force.
import type { Snippet } from 'svelte';
import type { Shown } from '$lib/doc.svelte';
import { i18n, t } from '$lib/i18n/index.svelte';
import { needOf } from '$lib/labels';
import type { Proposal } from '$lib/types';
import Prose from './Prose.svelte';
import WaterLevel from './WaterLevel.svelte';
let {
text,
mark = 'unchanged',
seedMinimal = false,
seed = null,
enteredAt = null,
rivals = 0,
selected = false,
@@ -15,17 +24,17 @@
voteFor = null,
voteOpen = false,
votes = 0,
module = false,
proposeLabel,
meta,
content,
fold,
onaction,
}: {
text: Shown;
mark?: 'unchanged' | 'added' | 'replaced' | 'removed';
seedMinimal?: boolean;
/** A seed proposal at the head (ADR 0011): how far it is from passing, not in force. */
seed?: Proposal | null;
enteredAt?: string | null;
/** Its other versions open to the vote: said in its state, read in « Le vote ». */
rivals?: number;
selected?: boolean;
actions?: boolean;
@@ -34,51 +43,45 @@
voteOpen?: boolean;
/** The votes on the version in force, shown next to the seed mark. */
votes?: number;
/** A module (ADR 0009): quieter than an article, its own words to propose. */
module?: boolean;
proposeLabel?: string;
/** What the header adds after the title (a count, a level). */
meta?: Snippet;
/** In place of the text, for a module. */
content?: Snippet;
/** The vote, unfolded under the text. */
fold?: Snippet;
onaction: (action: 'vote' | 'proposals' | 'propose') => void;
onaction: (action: 'vote' | 'propose') => void;
} = $props();
</script>
<!-- A module is not an article of the text: a group named by its title. -->
<svelte:element
this={module ? 'div' : 'article'}
<article
class="unit {mark}"
class:module
class:selected
class:seed={!!seed}
class:unfolded={voteOpen}
role={module ? 'group' : undefined}
aria-label={module ? text.title : undefined}
>
<header>
{#if seed}<WaterLevel tally={seed.tally} evaluation={seed.evaluation} blocked={seed.blocked} />{/if}
<h2 class="eyebrow">{text.title}</h2>
{@render meta?.()}
{#if seed}<span class="tag water" title={t('doc.seedProposal.long')}>{t('doc.seedProposal')}</span>{/if}
{#if mark !== 'unchanged'}<span class="tag {mark === 'removed' ? 'ochre' : 'moss'}">{t(`doc.mark.${mark}`)}</span>{/if}
{#if text.stale}<span class="tag ochre">{t('doc.lang.stale')}</span>{:else if text.missing}<span class="tag">{t('doc.lang.missing')}</span>{/if}
</header>
{#if content}{@render content()}{:else}<Prose text={text.body} />{/if}
<Prose text={text.body} />
{#if actions}
<footer class="actions">
<span class="status faint">
{#if seedMinimal}<span title={t('doc.seed.long')}>{i18n.plural('doc.seed.votes', votes)}</span>
{#if seed}
{@const need = needOf(seed)}
{t('tally.short', { for: seed.tally.for, against: seed.tally.against })}{#if need} · <span title={need.title}>{need.short}</span>{/if}
{:else if seedMinimal}<span title={t('doc.seed.long')}>{i18n.plural('doc.seed.votes', votes)}</span>
{:else if enteredAt}{t('doc.adopted', { date: i18n.date(enteredAt) })}{/if}
{#if rivals > 0} · {i18n.plural('doc.others', rivals)}{/if}
</span>
<span class="links">
<button type="button" class:on={voteOpen} aria-expanded={voteOpen} aria-controls={voteFor ? `vote-${voteFor}` : undefined} onclick={() => onaction('vote')}>{t('doc.action.vote')}</button>
<button type="button" onclick={() => onaction('proposals')}>{t('doc.action.proposals')}{#if rivals > 0}{` · ${rivals}`}{/if}</button>
<button type="button" onclick={() => onaction('propose')}>{proposeLabel ?? t('doc.action.propose')}</button>
</span>
</footer>
{/if}
{#if voteOpen && fold}{@render fold()}{/if}
</svelte:element>
</article>
<style>
.unit {
@@ -98,18 +101,12 @@
background: var(--surface);
box-shadow: var(--shadow);
}
/* A module closes its section: set apart, quieter than the articles. */
.unit.module {
margin-top: 0.9rem;
margin-bottom: 1.4rem;
padding-block: 0.85rem 0.6rem;
background: var(--water-wash);
/* A seed proposal: the water of a vote under way, never the bare page of a text in force. */
.unit.seed {
background: color-mix(in srgb, var(--water-wash) 55%, transparent);
}
.unit.module:hover,
.unit.module:focus-within,
.unit.module.selected,
.unit.module.unfolded {
background: var(--surface);
.unit.seed :global(.prose) {
color: var(--ink-soft);
}
header {
display: flex;
@@ -168,4 +165,15 @@
opacity: 1;
}
}
@media (max-width: 899px) {
.links button {
min-height: 2.75rem;
}
}
@media (prefers-reduced-motion: reduce) {
.unit,
.links {
transition: none;
}
}
</style>