Sprint 3 : protocoles de vote et boite a outils

Backend:
- Sessions de vote : list, close, tally, threshold details, auto-expiration
- Protocoles : update, simulate, meta-gouvernance, formulas CRUD
- Service vote enrichi : close_session, get_threshold_details, nuanced breakdown
- Schemas : ThresholdDetailOut, VoteResultOut, FormulaSimulationRequest/Result
- WebSocket broadcast sur chaque vote + fermeture session
- 25 nouveaux tests (threshold details, close, nuanced, simulation)

Frontend:
- 5 composants vote : VoteBinary, VoteNuanced, ThresholdGauge, FormulaDisplay, VoteHistory
- 3 composants protocoles : ProtocolPicker, FormulaEditor, ModeParamsDisplay
- Simulateur de formules interactif (page /protocols/formulas)
- Page detail protocole (/protocols/[id])
- Composable useWebSocket (live updates)
- Composable useVoteFormula (calcul client-side reactif)
- Integration KaTeX pour rendu LaTeX des formules

Documentation:
- API reference : 8 nouveaux endpoints documentes
- Formules : tables d'inertie, parametres detailles, simulation API
- Guide vote : vote binaire/nuance, jauge, historique, simulateur, meta-gouvernance

55 tests passes (+ 1 skipped), 126 fichiers total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-02-28 13:29:31 +01:00
parent 2bdc731639
commit cede2a585f
25 changed files with 3964 additions and 188 deletions

View File

@@ -0,0 +1,100 @@
/**
* Composable for WebSocket connectivity to receive live vote updates.
*
* Connects to the backend WS endpoint and allows subscribing to
* individual vote session channels for real-time tally updates.
*/
export function useWebSocket() {
const config = useRuntimeConfig()
let ws: WebSocket | null = null
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
const connected = ref(false)
const lastMessage = ref<any>(null)
/**
* Open a WebSocket connection to the backend live endpoint.
*/
function connect() {
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
return
}
const wsUrl = config.public.apiBase
.replace(/^http/, 'ws')
.replace(/\/api\/v1$/, '/api/v1/ws/live')
ws = new WebSocket(wsUrl)
ws.onopen = () => {
connected.value = true
if (reconnectTimer) {
clearTimeout(reconnectTimer)
reconnectTimer = null
}
}
ws.onclose = () => {
connected.value = false
reconnect()
}
ws.onerror = () => {
connected.value = false
}
ws.onmessage = (event: MessageEvent) => {
try {
lastMessage.value = JSON.parse(event.data)
} catch {
lastMessage.value = event.data
}
}
}
/**
* Subscribe to real-time updates for a vote session.
*/
function subscribe(sessionId: string) {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ action: 'subscribe', session_id: sessionId }))
}
}
/**
* Unsubscribe from a vote session's updates.
*/
function unsubscribe(sessionId: string) {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ action: 'unsubscribe', session_id: sessionId }))
}
}
/**
* Gracefully close the WebSocket connection.
*/
function disconnect() {
if (reconnectTimer) {
clearTimeout(reconnectTimer)
reconnectTimer = null
}
if (ws) {
ws.onclose = null
ws.close()
ws = null
}
connected.value = false
}
/**
* Schedule a reconnection attempt after a delay.
*/
function reconnect() {
if (reconnectTimer) return
reconnectTimer = setTimeout(() => {
reconnectTimer = null
connect()
}, 3000)
}
return { connected, lastMessage, connect, subscribe, unsubscribe, disconnect }
}