v2 : nettoyage des orphelins v1 + documentation livrable
- 43 fichiers v1 supprimés (composants documents/protocols/sanctuary/toolbox, stores auth/documents/groups/mandates/organizations/protocols/votes, composables api/notifications/formula/websocket, utils doublons du moteur) - nuxt.config épuré (polkadot retiré, KaTeX et apiBase gardés), meta v2 - README.md, CONTRIBUTING.md, CLAUDE.md réécrits pour la v2 - Build zéro erreur, 342/342 tests verts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Display vote formula with KaTeX rendering.
|
||||
*
|
||||
* Renders the WoT threshold formula using KaTeX when available,
|
||||
* falling back to a code display. Shows parameter values and
|
||||
* optional Smith/TechComm criteria formulas.
|
||||
*/
|
||||
import type { FormulaConfig } from '~/stores/protocols'
|
||||
|
||||
const props = defineProps<{
|
||||
formulaConfig: FormulaConfig
|
||||
showExplanation?: boolean
|
||||
}>()
|
||||
|
||||
const showExplain = ref(props.showExplanation ?? false)
|
||||
|
||||
/**
|
||||
* Render a LaTeX string to HTML using KaTeX, with code fallback.
|
||||
*/
|
||||
function renderFormula(tex: string): string {
|
||||
if (typeof window !== 'undefined' && (window as any).katex) {
|
||||
return (window as any).katex.renderToString(tex, { throwOnError: false, displayMode: true })
|
||||
}
|
||||
return `<code class="text-sm font-mono">${tex}</code>`
|
||||
}
|
||||
|
||||
/** Main threshold formula in LaTeX. */
|
||||
const mainFormulaTeX = 'Seuil = C + B^W + \\left(M + (1-M) \\cdot \\left(1 - \\left(\\frac{T}{W}\\right)^G\\right)\\right) \\cdot \\max(0,\\, T - C)'
|
||||
|
||||
/** Smith criterion formula. */
|
||||
const smithFormulaTeX = computed(() => {
|
||||
if (props.formulaConfig.smith_exponent === null) return null
|
||||
return `Seuil_{Smith} = \\lceil W_{Smith}^{${props.formulaConfig.smith_exponent}} \\rceil`
|
||||
})
|
||||
|
||||
/** TechComm criterion formula. */
|
||||
const techcommFormulaTeX = computed(() => {
|
||||
if (props.formulaConfig.techcomm_exponent === null) return null
|
||||
return `Seuil_{TechComm} = \\lceil W_{TechComm}^{${props.formulaConfig.techcomm_exponent}} \\rceil`
|
||||
})
|
||||
|
||||
const mainFormulaHtml = computed(() => renderFormula(mainFormulaTeX))
|
||||
const smithFormulaHtml = computed(() => smithFormulaTeX.value ? renderFormula(smithFormulaTeX.value) : null)
|
||||
const techcommFormulaHtml = computed(() => techcommFormulaTeX.value ? renderFormula(techcommFormulaTeX.value) : null)
|
||||
|
||||
const parameters = computed(() => [
|
||||
{ label: 'Duree', code: 'D', value: `${props.formulaConfig.duration_days} jours`, description: 'Duree du vote en jours' },
|
||||
{ label: 'Majorite', code: 'M', value: `${props.formulaConfig.majority_pct}%`, description: 'Ratio de majorite cible a haute participation' },
|
||||
{ label: 'Base', code: 'B', value: String(props.formulaConfig.base_exponent), description: 'Exposant de base (B^W tend vers 0 si B < 1)' },
|
||||
{ label: 'Gradient', code: 'G', value: String(props.formulaConfig.gradient_exponent), description: 'Exposant du gradient d\'inertie' },
|
||||
{ label: 'Constante', code: 'C', value: String(props.formulaConfig.constant_base), description: 'Plancher fixe de votes requis' },
|
||||
...(props.formulaConfig.smith_exponent !== null ? [{
|
||||
label: 'Smith', code: 'S', value: String(props.formulaConfig.smith_exponent), description: 'Exposant du critere Smith',
|
||||
}] : []),
|
||||
...(props.formulaConfig.techcomm_exponent !== null ? [{
|
||||
label: 'TechComm', code: 'T', value: String(props.formulaConfig.techcomm_exponent), description: 'Exposant du critere TechComm',
|
||||
}] : []),
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- Main formula -->
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg overflow-x-auto">
|
||||
<div v-html="mainFormulaHtml" class="text-center" />
|
||||
</div>
|
||||
|
||||
<!-- Smith criterion -->
|
||||
<div v-if="smithFormulaHtml" class="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg overflow-x-auto">
|
||||
<p class="text-xs font-semibold text-blue-600 dark:text-blue-400 mb-2">Critere Smith</p>
|
||||
<div v-html="smithFormulaHtml" class="text-center" />
|
||||
</div>
|
||||
|
||||
<!-- TechComm criterion -->
|
||||
<div v-if="techcommFormulaHtml" class="p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg overflow-x-auto">
|
||||
<p class="text-xs font-semibold text-purple-600 dark:text-purple-400 mb-2">Critere TechComm</p>
|
||||
<div v-html="techcommFormulaHtml" class="text-center" />
|
||||
</div>
|
||||
|
||||
<!-- Parameters grid -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
<div
|
||||
v-for="param in parameters"
|
||||
:key="param.code"
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-mono font-bold text-primary text-sm">{{ param.code }}</span>
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{ param.label }}</span>
|
||||
</div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">{{ param.value }}</div>
|
||||
<p v-if="showExplain" class="text-xs text-gray-500 mt-1">{{ param.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Explanation toggle -->
|
||||
<div class="flex justify-end">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
size="xs"
|
||||
:icon="showExplain ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||
@click="showExplain = !showExplain"
|
||||
>
|
||||
{{ showExplain ? 'Masquer les explications' : 'Afficher les explications' }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,114 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Visual gauge showing votes vs threshold.
|
||||
*
|
||||
* Displays a horizontal progress bar with green (pour) and red (contre) fills,
|
||||
* a vertical threshold marker, participation statistics, and a pass/fail badge.
|
||||
*/
|
||||
const props = defineProps<{
|
||||
votesFor: number
|
||||
votesAgainst: number
|
||||
threshold: number
|
||||
wotSize: number
|
||||
}>()
|
||||
|
||||
const totalVotes = computed(() => props.votesFor + props.votesAgainst)
|
||||
|
||||
/** Percentage of "pour" votes relative to total votes. */
|
||||
const forPct = computed(() => {
|
||||
if (totalVotes.value === 0) return 0
|
||||
return (props.votesFor / totalVotes.value) * 100
|
||||
})
|
||||
|
||||
/** Percentage of "contre" votes relative to total votes. */
|
||||
const againstPct = computed(() => {
|
||||
if (totalVotes.value === 0) return 0
|
||||
return (props.votesAgainst / totalVotes.value) * 100
|
||||
})
|
||||
|
||||
/** Position of the threshold marker as a percentage of total votes. */
|
||||
const thresholdPosition = computed(() => {
|
||||
if (totalVotes.value === 0) return 50
|
||||
// Threshold as a percentage of total votes
|
||||
const pct = (props.threshold / totalVotes.value) * 100
|
||||
return Math.min(pct, 100)
|
||||
})
|
||||
|
||||
/** Whether the vote passes (votes_for >= threshold). */
|
||||
const isPassing = computed(() => props.votesFor >= props.threshold)
|
||||
|
||||
/** Participation rate. */
|
||||
const participationRate = computed(() => {
|
||||
if (props.wotSize === 0) return 0
|
||||
return (totalVotes.value / props.wotSize) * 100
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-3">
|
||||
<!-- Progress bar -->
|
||||
<div class="relative h-8 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<!-- Green fill (pour) -->
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 bg-green-500 transition-all duration-500"
|
||||
:style="{ width: `${forPct}%` }"
|
||||
/>
|
||||
<!-- Red fill (contre) -->
|
||||
<div
|
||||
class="absolute inset-y-0 bg-red-500 transition-all duration-500"
|
||||
:style="{ left: `${forPct}%`, width: `${againstPct}%` }"
|
||||
/>
|
||||
|
||||
<!-- Threshold marker -->
|
||||
<div
|
||||
v-if="totalVotes > 0"
|
||||
class="absolute inset-y-0 w-0.5 bg-yellow-400 z-10"
|
||||
:style="{ left: `${thresholdPosition}%` }"
|
||||
>
|
||||
<div class="absolute -top-5 left-1/2 -translate-x-1/2 text-xs font-bold text-yellow-600 dark:text-yellow-400 whitespace-nowrap">
|
||||
Seuil
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Percentage labels inside the bar -->
|
||||
<div class="absolute inset-0 flex items-center px-3 text-xs font-bold text-white">
|
||||
<span v-if="forPct > 10">{{ forPct.toFixed(1) }}%</span>
|
||||
<span class="flex-1" />
|
||||
<span v-if="againstPct > 10">{{ againstPct.toFixed(1) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Counts and threshold text -->
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-green-600 dark:text-green-400 font-medium">
|
||||
{{ votesFor }} pour
|
||||
</span>
|
||||
<span class="text-red-600 dark:text-red-400 font-medium">
|
||||
{{ votesAgainst }} contre
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="font-medium" :class="isPassing ? 'text-green-600 dark:text-green-400' : 'text-gray-600 dark:text-gray-400'">
|
||||
{{ votesFor }} / {{ threshold }} requis
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Participation rate -->
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>
|
||||
{{ totalVotes }} vote{{ totalVotes !== 1 ? 's' : '' }} sur {{ wotSize }} membres
|
||||
({{ participationRate.toFixed(2) }}%)
|
||||
</span>
|
||||
|
||||
<!-- Pass/fail badge -->
|
||||
<UBadge
|
||||
:color="isPassing ? 'success' : 'error'"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
{{ isPassing ? 'Adopte' : 'Non adopte' }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user