Boîtes à outils enrichies : ContextMapper, SocioElection, WorkflowMilestones
- ContextMapper : 4 questions contexte → méthode de décision optimale (advice process Laloux, vote inertiel WoT, consentement sociocratique, Smith…) - SocioElection : guide élection sociocratique 6 étapes + advice process + clarté de rôle - WorkflowMilestones : 11 jalons de protocole (7 essentiels), durées recommandées, principes Ostrom - WorkspaceSelector : sélecteur de collectif multi-site dans le header - SectionLayout : toolbox en USlideover droit sur mobile, sidebar sticky desktop - Décisions : ContextMapper intégré + guide consentement - Mandats : SocioElection intégré + cycle de mandat - Documents : guide inertie 4 niveaux + structure + IPFS - Protocoles : WorkflowMilestones + protocole élection sociocratique ajouté - Renommage projet Glibredecision → libreDecision (dossier + sources) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
551
frontend/app/components/toolbox/WorkflowMilestones.vue
Normal file
551
frontend/app/components/toolbox/WorkflowMilestones.vue
Normal file
@@ -0,0 +1,551 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* WorkflowMilestones — 11 jalons de protocole de fonctionnement.
|
||||
* Sélectif et qualitatif : ce qui fait la différence entre un protocole
|
||||
* qui tient et un qui dérive.
|
||||
* Référence : g1vote, sociocracie, Laloux, Elinor Ostrom (gouvernance des communs).
|
||||
*/
|
||||
|
||||
interface Milestone {
|
||||
num: number
|
||||
name: string
|
||||
icon: string
|
||||
actor: string
|
||||
duration: { min: string; standard: string; major: string }
|
||||
description: string
|
||||
essential: boolean
|
||||
tip?: string
|
||||
ostrom?: string
|
||||
}
|
||||
|
||||
const milestones: Milestone[] = [
|
||||
{
|
||||
num: 1,
|
||||
name: 'Prise d\'initiative',
|
||||
icon: 'i-lucide-lightbulb',
|
||||
actor: 'Tout membre',
|
||||
duration: { min: '—', standard: '1-2j', major: '1-2j' },
|
||||
description: 'Formaliser l\'intention : quel problème, quel besoin, quelle cible visée. Nommer un·e porteur·euse responsable.',
|
||||
essential: true,
|
||||
tip: 'Une initiative sans porteur identifié ne décolle pas. La responsabilité individuelle est le premier jalon.',
|
||||
ostrom: 'Principe 1 — Frontières claires : qui est concerné, pourquoi.',
|
||||
},
|
||||
{
|
||||
num: 2,
|
||||
name: 'Processus d\'avis (advice)',
|
||||
icon: 'i-lucide-message-circle',
|
||||
actor: 'Porteur + experts + impactés',
|
||||
duration: { min: '1j', standard: '3-7j', major: '7-14j' },
|
||||
description: 'Consulter les personnes qui ont l\'expertise ET celles qui seront impactées. Écouter vraiment, intégrer ou expliquer pourquoi on n\'intègre pas.',
|
||||
essential: true,
|
||||
tip: 'Ce jalon est souvent escamoté. C\'est la principale cause d\'échec ou de résistance en implémentation.',
|
||||
ostrom: 'Principe 5 — Résolution des conflits accessible et peu coûteuse.',
|
||||
},
|
||||
{
|
||||
num: 3,
|
||||
name: 'Rédaction + amendements',
|
||||
icon: 'i-lucide-file-edit',
|
||||
actor: 'Porteur + communauté',
|
||||
duration: { min: '1-2j', standard: '3-7j', major: '7-21j' },
|
||||
description: 'Rédiger la proposition formelle. Ouvrir une période d\'amendements publics. Intégrer les modifications acceptées, rejeter les autres avec justification.',
|
||||
essential: true,
|
||||
tip: 'Distinguer amendements substantiels (re-vote possible) et de forme (porteur décide).',
|
||||
},
|
||||
{
|
||||
num: 4,
|
||||
name: 'Qualification technique',
|
||||
icon: 'i-lucide-shield-check',
|
||||
actor: 'Comité technique (si applicable)',
|
||||
duration: { min: '—', standard: '2-5j', major: '5-10j' },
|
||||
description: 'Pour les décisions techniques : revue par les experts désignés. Évaluation de faisabilité, risques, impact. Avis formel (non bloquant, sauf veto défini).',
|
||||
essential: false,
|
||||
tip: 'Optionnel selon la nature de la décision. Systématique pour les Runtime Upgrades.',
|
||||
},
|
||||
{
|
||||
num: 5,
|
||||
name: 'Ouverture du vote',
|
||||
icon: 'i-lucide-vote',
|
||||
actor: 'Porteur + plateforme',
|
||||
duration: { min: '—', standard: '1j', major: '1j' },
|
||||
description: 'Publier la proposition finale. Notifier la communauté. Ouvrir la session de vote avec les paramètres définis (protocole, formule, durée).',
|
||||
essential: true,
|
||||
tip: 'L\'ouverture doit être annoncée à l\'avance (délai de préavis selon règlement).',
|
||||
},
|
||||
{
|
||||
num: 6,
|
||||
name: 'Phase de vote',
|
||||
icon: 'i-lucide-bar-chart-2',
|
||||
actor: 'Membres habilités',
|
||||
duration: { min: '3j', standard: '7-14j', major: '21-30j' },
|
||||
description: 'Les membres habilités votent selon le protocole. Seuil de participation minimal surveillé. Résultats intermédiaires visibles (ou non, selon le protocole).',
|
||||
essential: true,
|
||||
ostrom: 'Principe 3 — Choix collectifs : ceux qui sont concernés participent aux décisions.',
|
||||
},
|
||||
{
|
||||
num: 7,
|
||||
name: 'Contrôle du quorum',
|
||||
icon: 'i-lucide-check-circle',
|
||||
actor: 'Plateforme + porteur',
|
||||
duration: { min: '—', standard: '—', major: '—' },
|
||||
description: 'Vérifier que le quorum minimum est atteint avant clôture. Si non atteint : prolonger, relancer, ou annuler selon les règles préétablies.',
|
||||
essential: true,
|
||||
tip: 'Définir à l\'avance le quorum et la procédure si non atteint — évite les ambiguïtés.',
|
||||
ostrom: 'Principe 4 — Supervision des règles par les membres.',
|
||||
},
|
||||
{
|
||||
num: 8,
|
||||
name: 'Proclamation des résultats',
|
||||
icon: 'i-lucide-megaphone',
|
||||
actor: 'Plateforme + porteur',
|
||||
duration: { min: '—', standard: '1j', major: '1j' },
|
||||
description: 'Annoncer le résultat officiel avec les chiffres détaillés (votes pour, contre, abstentions, taux participation, seuil requis). Archiver on-chain si adopté.',
|
||||
essential: true,
|
||||
tip: 'La transparence des résultats est aussi importante que le résultat lui-même.',
|
||||
ostrom: 'Principe 8 — Gouvernance emboîtée : résultats remontés aux niveaux supérieurs.',
|
||||
},
|
||||
{
|
||||
num: 9,
|
||||
name: 'Mise en application',
|
||||
icon: 'i-lucide-play-circle',
|
||||
actor: 'Porteur + implémenteurs',
|
||||
duration: { min: '—', standard: 'Variable', major: 'Variable' },
|
||||
description: 'Planifier l\'application effective de la décision. Désigner les responsables. Fixer des jalons d\'implémentation si complexe.',
|
||||
essential: true,
|
||||
tip: 'Une décision adoptée mais non implémentée érode la confiance dans le processus.',
|
||||
},
|
||||
{
|
||||
num: 10,
|
||||
name: 'Suivi et accountability',
|
||||
icon: 'i-lucide-activity',
|
||||
actor: 'Porteur + communauté',
|
||||
duration: { min: '—', standard: 'Continu', major: 'Continu' },
|
||||
description: 'Rapports réguliers sur l\'avancement. Signalement des écarts. Mécanisme de remontée si la décision produit des effets inattendus.',
|
||||
essential: false,
|
||||
tip: 'Intégrer dans le prochain cycle de gouvernance si des ajustements s\'imposent.',
|
||||
ostrom: 'Principe 4 — Surveillance continue des comportements et résultats.',
|
||||
},
|
||||
{
|
||||
num: 11,
|
||||
name: 'Rétrospective',
|
||||
icon: 'i-lucide-rotate-ccw',
|
||||
actor: 'Cercle concerné',
|
||||
duration: { min: '—', standard: '1-2h', major: '1-2j' },
|
||||
description: 'Évaluer : le processus a-t-il bien fonctionné ? La décision produit-elle les effets attendus ? Quoi améliorer pour la prochaine fois ?',
|
||||
essential: false,
|
||||
tip: 'La rétrospective est le moteur d\'amélioration du protocole lui-même (méta-gouvernance).',
|
||||
ostrom: 'Principe 7 — Reconnaissance externe de l\'organisation par des autorités supérieures.',
|
||||
},
|
||||
]
|
||||
|
||||
const showOstrom = ref(false)
|
||||
const activeDecisionType = ref<'minor' | 'standard' | 'major'>('standard')
|
||||
|
||||
const decisionTypes = [
|
||||
{ value: 'minor', label: 'Mineur', color: 'teal' },
|
||||
{ value: 'standard', label: 'Standard', color: 'accent' },
|
||||
{ value: 'major', label: 'Majeur', color: 'secondary' },
|
||||
]
|
||||
|
||||
const essentialMilestones = computed(() =>
|
||||
milestones.filter(m => m.essential),
|
||||
)
|
||||
|
||||
const optionalMilestones = computed(() =>
|
||||
milestones.filter(m => !m.essential),
|
||||
)
|
||||
|
||||
const totalDuration = computed(() => {
|
||||
const type = activeDecisionType.value
|
||||
const durations = {
|
||||
minor: '5-10 jours',
|
||||
standard: '14-30 jours',
|
||||
major: '45-90 jours',
|
||||
}
|
||||
return durations[type]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wm">
|
||||
<!-- Header -->
|
||||
<div class="wm__header">
|
||||
<h3 class="wm__title">Jalons de protocole</h3>
|
||||
<p class="wm__subtitle">
|
||||
11 jalons, dont 7 indispensables. Durées recommandées selon le type de décision.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Decision type selector -->
|
||||
<div class="wm__type-selector">
|
||||
<button
|
||||
v-for="dt in decisionTypes"
|
||||
:key="dt.value"
|
||||
class="wm__type-btn"
|
||||
:class="[
|
||||
`wm__type-btn--${dt.color}`,
|
||||
{ 'wm__type-btn--active': activeDecisionType === dt.value },
|
||||
]"
|
||||
@click="activeDecisionType = dt.value as 'minor' | 'standard' | 'major'"
|
||||
>
|
||||
{{ dt.label }}
|
||||
</button>
|
||||
<span class="wm__total-duration">≈ {{ totalDuration }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Essential milestones -->
|
||||
<div class="wm__section">
|
||||
<div class="wm__section-label">
|
||||
<span class="wm__section-badge wm__section-badge--essential">7 essentiels</span>
|
||||
</div>
|
||||
<div class="wm__milestones">
|
||||
<div
|
||||
v-for="m in essentialMilestones"
|
||||
:key="m.num"
|
||||
class="wm__milestone wm__milestone--essential"
|
||||
>
|
||||
<div class="wm__milestone-left">
|
||||
<div class="wm__milestone-num">{{ m.num }}</div>
|
||||
<div v-if="m.num < milestones.length" class="wm__milestone-line" />
|
||||
</div>
|
||||
<div class="wm__milestone-icon">
|
||||
<UIcon :name="m.icon" />
|
||||
</div>
|
||||
<div class="wm__milestone-body">
|
||||
<div class="wm__milestone-head">
|
||||
<span class="wm__milestone-name">{{ m.name }}</span>
|
||||
<span class="wm__milestone-duration">
|
||||
{{ m.duration[activeDecisionType] || '—' }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="wm__milestone-desc">{{ m.description }}</p>
|
||||
<div v-if="m.tip" class="wm__milestone-tip">
|
||||
<UIcon name="i-lucide-lightbulb" />
|
||||
{{ m.tip }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Optional milestones -->
|
||||
<div class="wm__section">
|
||||
<div class="wm__section-label">
|
||||
<span class="wm__section-badge wm__section-badge--optional">4 contextuels</span>
|
||||
</div>
|
||||
<div class="wm__milestones">
|
||||
<div
|
||||
v-for="m in optionalMilestones"
|
||||
:key="m.num"
|
||||
class="wm__milestone wm__milestone--optional"
|
||||
>
|
||||
<div class="wm__milestone-left">
|
||||
<div class="wm__milestone-num wm__milestone-num--optional">{{ m.num }}</div>
|
||||
</div>
|
||||
<div class="wm__milestone-icon wm__milestone-icon--optional">
|
||||
<UIcon :name="m.icon" />
|
||||
</div>
|
||||
<div class="wm__milestone-body">
|
||||
<div class="wm__milestone-head">
|
||||
<span class="wm__milestone-name">{{ m.name }}</span>
|
||||
<span class="wm__milestone-duration">
|
||||
{{ m.duration[activeDecisionType] || '—' }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="wm__milestone-desc">{{ m.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ostrom toggle -->
|
||||
<button class="wm__ostrom-toggle" @click="showOstrom = !showOstrom">
|
||||
<UIcon name="i-lucide-book-open" />
|
||||
<span>Principes Ostrom appliqués</span>
|
||||
<UIcon :name="showOstrom ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'" />
|
||||
</button>
|
||||
|
||||
<Transition name="expand">
|
||||
<div v-if="showOstrom" class="wm__ostrom">
|
||||
<p class="wm__ostrom-intro">
|
||||
Elinor Ostrom (Nobel 2009) a identifié 8 principes pour la gouvernance
|
||||
durable des communs. Les jalons ci-dessus les incarnent.
|
||||
</p>
|
||||
<div class="wm__ostrom-items">
|
||||
<div
|
||||
v-for="m in milestones.filter(x => x.ostrom)"
|
||||
:key="m.num"
|
||||
class="wm__ostrom-item"
|
||||
>
|
||||
<span class="wm__ostrom-jalon">Jalon {{ m.num }}</span>
|
||||
<span class="wm__ostrom-text">{{ m.ostrom }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wm { display: flex; flex-direction: column; gap: 1rem; }
|
||||
|
||||
.wm__header { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
|
||||
.wm__title {
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
color: var(--mood-text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wm__subtitle {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--mood-text-muted);
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Type selector */
|
||||
.wm__type-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wm__type-btn {
|
||||
padding: 0.375rem 0.875rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
background: var(--mood-accent-soft);
|
||||
color: var(--mood-text-muted);
|
||||
transition: all 0.12s ease;
|
||||
}
|
||||
|
||||
.wm__type-btn--accent.wm__type-btn--active {
|
||||
background: var(--mood-accent);
|
||||
color: var(--mood-accent-text);
|
||||
}
|
||||
|
||||
.wm__type-btn--teal.wm__type-btn--active {
|
||||
background: color-mix(in srgb, var(--mood-success) 20%, transparent);
|
||||
color: var(--mood-success);
|
||||
}
|
||||
|
||||
.wm__type-btn--secondary.wm__type-btn--active {
|
||||
background: color-mix(in srgb, var(--mood-secondary, var(--mood-accent)) 20%, transparent);
|
||||
color: var(--mood-secondary, var(--mood-accent));
|
||||
}
|
||||
|
||||
.wm__total-duration {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text-muted);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.wm__section { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
|
||||
.wm__section-label { display: flex; align-items: center; gap: 0.5rem; }
|
||||
|
||||
.wm__section-badge {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.wm__section-badge--essential {
|
||||
background: var(--mood-accent-soft);
|
||||
color: var(--mood-accent);
|
||||
}
|
||||
|
||||
.wm__section-badge--optional {
|
||||
background: color-mix(in srgb, var(--mood-text-muted) 12%, transparent);
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
/* Milestones */
|
||||
.wm__milestones { display: flex; flex-direction: column; gap: 0; }
|
||||
|
||||
.wm__milestone {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.625rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.wm__milestone-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 1.375rem;
|
||||
}
|
||||
|
||||
.wm__milestone-num {
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--mood-accent);
|
||||
color: var(--mood-accent-text);
|
||||
font-size: 0.625rem;
|
||||
font-weight: 800;
|
||||
flex-shrink: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wm__milestone-num--optional {
|
||||
background: color-mix(in srgb, var(--mood-text-muted) 20%, transparent);
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
.wm__milestone-line {
|
||||
width: 2px;
|
||||
flex: 1;
|
||||
min-height: 1.25rem;
|
||||
background: color-mix(in srgb, var(--mood-accent) 20%, transparent);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.wm__milestone-icon {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
background: var(--mood-accent-soft);
|
||||
color: var(--mood-accent);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.wm__milestone-icon--optional {
|
||||
background: color-mix(in srgb, var(--mood-text-muted) 10%, transparent);
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
.wm__milestone-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.wm__milestone-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wm__milestone-name {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
color: var(--mood-text);
|
||||
}
|
||||
|
||||
.wm__milestone--optional .wm__milestone-name {
|
||||
color: var(--mood-text-muted);
|
||||
}
|
||||
|
||||
.wm__milestone-duration {
|
||||
font-size: 0.625rem;
|
||||
font-weight: 600;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
color: var(--mood-accent);
|
||||
background: var(--mood-accent-soft);
|
||||
padding: 1px 6px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.wm__milestone-desc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--mood-text-muted);
|
||||
line-height: 1.5;
|
||||
margin: 0.125rem 0 0;
|
||||
}
|
||||
|
||||
.wm__milestone-tip {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.375rem;
|
||||
margin-top: 0.375rem;
|
||||
padding: 0.375rem 0.5rem;
|
||||
background: color-mix(in srgb, var(--mood-accent) 8%, transparent);
|
||||
border-radius: 8px;
|
||||
font-size: 0.6875rem;
|
||||
color: var(--mood-accent);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Ostrom */
|
||||
.wm__ostrom-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.625rem 0.75rem;
|
||||
background: var(--mood-accent-soft);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text-muted);
|
||||
transition: color 0.12s ease;
|
||||
text-align: left;
|
||||
}
|
||||
.wm__ostrom-toggle:hover { color: var(--mood-text); }
|
||||
.wm__ostrom-toggle .i-lucide-book-open { color: var(--mood-accent); }
|
||||
|
||||
.wm__ostrom {
|
||||
background: var(--mood-accent-soft);
|
||||
border-radius: 12px;
|
||||
padding: 0.875rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.wm__ostrom-intro {
|
||||
font-size: 0.75rem;
|
||||
color: var(--mood-text-muted);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wm__ostrom-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.wm__ostrom-item {
|
||||
display: flex;
|
||||
gap: 0.625rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.wm__ostrom-jalon {
|
||||
font-weight: 700;
|
||||
color: var(--mood-accent);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wm__ostrom-text { color: var(--mood-text-muted); }
|
||||
|
||||
/* Expand transition */
|
||||
.expand-enter-active, .expand-leave-active { transition: all 0.2s ease; overflow: hidden; }
|
||||
.expand-enter-from, .expand-leave-to { max-height: 0; opacity: 0; }
|
||||
.expand-enter-to, .expand-leave-from { max-height: 1000px; opacity: 1; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user