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:
Yvv
2026-03-17 00:13:08 +01:00
parent 316d205593
commit 290548703d
29 changed files with 4174 additions and 168 deletions

View File

@@ -0,0 +1,666 @@
<script setup lang="ts">
/**
* SocioElection — Guide processus d'élection sociocratique.
* 6 étapes canoniques + advice process Laloux + clarté de rôle.
* Référence : "La Sociocracie" (Robertson), "Reinventing Organizations" (Laloux).
*/
interface Step {
num: number
title: string
actor: string
duration: string
icon: string
description: string
tips: string[]
pitfall?: string
}
const steps: Step[] = [
{
num: 1,
title: 'Clarifier le rôle',
actor: 'Facilitateur + cercle',
duration: '10-15 min',
icon: 'i-lucide-clipboard-list',
description: 'Définir ensemble la mission du rôle, ses domaines d\'autorité, ses redevabilités et la durée du mandat. Le rôle précède la personne.',
tips: [
'Distinguer redevabilités (obligations) et autorité (domaine de décision)',
'Fixer une durée standard (ex: 1 an renouvelable)',
'Identifier les compétences nécessaires — pas souhaitables',
],
pitfall: 'Ne pas définir le rôle sur mesure pour un candidat déjà imaginé.',
},
{
num: 2,
title: 'Nommer en silence',
actor: 'Tous les membres',
duration: '3-5 min',
icon: 'i-lucide-pencil',
description: 'Chacun écrit sur papier le nom d\'une personne (y compris soi-même) et la raison principale de son choix. En silence, sans influence mutuelle.',
tips: [
'Pas de discussion pendant cette étape',
'S\'auto-nommer est bienvenu et valorisé',
'Une seule nomination par personne',
],
},
{
num: 3,
title: 'Recueillir les nominations',
actor: 'Facilitateur',
duration: '5-10 min',
icon: 'i-lucide-list-checks',
description: 'Le facilitateur lit chaque nomination à voix haute avec la raison. Pas de commentaire, pas de débat. Pure collecte.',
tips: [
'Lire nom + raison tels qu\'écrits',
'Le facilitateur lit aussi sa propre nomination',
'Compter et afficher les nominations',
],
},
{
num: 4,
title: 'Argumenter',
actor: 'Chaque membre',
duration: '1-2 min / personne',
icon: 'i-lucide-message-square',
description: 'Chaque membre peut changer sa nomination et expliquer pourquoi (brièvement). Tour de table structuré, pas de croisements.',
tips: [
'1 minute maximum par personne',
'Argumenter pour, pas contre',
'Les candidats s\'expriment aussi brièvement',
],
pitfall: 'Éviter les longues plaidoiries — la clarté du rôle doit guider.',
},
{
num: 5,
title: 'Lever les objections',
actor: 'Facilitateur + cercle',
duration: '5-15 min',
icon: 'i-lucide-shield-check',
description: 'Le facilitateur propose l\'élection de la personne la plus nommée. Silence = consentement. Une objection grave peut être soulevée et traitée.',
tips: [
'Objection grave ≠ préférence — nuit-elle à la mission du cercle ?',
'Une objection peut mener à reconsidérer une candidature',
'L\'élu·e peut décliner — c\'est légitime',
],
pitfall: 'Une objection n\'est pas un veto — elle doit être travaillée collectivement.',
},
{
num: 6,
title: 'Célébrer',
actor: 'Tous',
duration: '2-3 min',
icon: 'i-lucide-star',
description: 'L\'élection est proclamée. L\'élu·e remercie et s\'engage publiquement. La communauté accueille le nouveau rôle.',
tips: [
'Documenter l\'élection (date, durée, personnes présentes)',
'Annoncer à la communauté au sens large',
'Fixer la prochaine évaluation du rôle',
],
},
]
const expandedStep = ref<number | null>(null)
function toggleStep(num: number) {
expandedStep.value = expandedStep.value === num ? null : num
}
// Advice process (Laloux)
const adviceSteps = [
{ icon: 'i-lucide-search', text: 'Identifier les personnes expertes ET impactées' },
{ icon: 'i-lucide-message-circle', text: 'Les consulter — écouter vraiment' },
{ icon: 'i-lucide-user-check', text: 'Décider seul·e, en intégrant les avis reçus' },
{ icon: 'i-lucide-file-text', text: 'Documenter et communiquer la décision + raisons' },
]
// Role clarity framework
interface RoleAxis {
label: string
icon: string
question: string
example: string
}
const roleAxes: RoleAxis[] = [
{
label: 'Mission',
icon: 'i-lucide-target',
question: 'Pourquoi ce rôle existe-t-il ?',
example: 'Assurer la disponibilité des nœuds validateurs 24h/24',
},
{
label: 'Domaine',
icon: 'i-lucide-shield',
question: 'Sur quoi a-t-il autorité exclusive ?',
example: 'Configuration des serveurs de forge, rotation des clés',
},
{
label: 'Redevabilités',
icon: 'i-lucide-check-square',
question: 'Quelles activités doit-il assurer ?',
example: 'Publier un rapport mensuel, alerter en cas d\'incident',
},
{
label: 'Durée',
icon: 'i-lucide-calendar',
question: 'Pour combien de temps ?',
example: '1 an, renouvelable une fois, réévaluation à 6 mois',
},
]
const activeTab = ref<'election' | 'advice' | 'role'>('election')
</script>
<template>
<div class="se">
<!-- Tabs -->
<div class="se__tabs">
<button
class="se__tab"
:class="{ 'se__tab--active': activeTab === 'election' }"
@click="activeTab = 'election'"
>
<UIcon name="i-lucide-users" />
Élection
</button>
<button
class="se__tab"
:class="{ 'se__tab--active': activeTab === 'advice' }"
@click="activeTab = 'advice'"
>
<UIcon name="i-lucide-message-circle" />
Conseil
</button>
<button
class="se__tab"
:class="{ 'se__tab--active': activeTab === 'role' }"
@click="activeTab = 'role'"
>
<UIcon name="i-lucide-clipboard-list" />
Rôle
</button>
</div>
<!-- Election sociocratique -->
<div v-if="activeTab === 'election'" class="se__panel">
<p class="se__intro">
Processus en 6 étapes garantissant que l'élection repose sur la clarté du rôle
et le consentement collectif — pas sur la popularité.
</p>
<div class="se__steps">
<div
v-for="s in steps"
:key="s.num"
class="se__step"
:class="{ 'se__step--open': expandedStep === s.num }"
>
<button class="se__step-head" @click="toggleStep(s.num)">
<div class="se__step-num">{{ s.num }}</div>
<div class="se__step-icon">
<UIcon :name="s.icon" />
</div>
<div class="se__step-info">
<span class="se__step-title">{{ s.title }}</span>
<span class="se__step-meta">{{ s.actor }} · {{ s.duration }}</span>
</div>
<UIcon
:name="expandedStep === s.num ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
class="se__step-toggle"
/>
</button>
<Transition name="expand">
<div v-if="expandedStep === s.num" class="se__step-body">
<p class="se__step-desc">{{ s.description }}</p>
<ul class="se__step-tips">
<li v-for="tip in s.tips" :key="tip">{{ tip }}</li>
</ul>
<div v-if="s.pitfall" class="se__step-pitfall">
<UIcon name="i-lucide-alert-triangle" />
{{ s.pitfall }}
</div>
</div>
</Transition>
</div>
</div>
</div>
<!-- Advice process -->
<div v-if="activeTab === 'advice'" class="se__panel">
<div class="se__advice-header">
<span class="se__advice-tag">Laloux / Teal</span>
<h4 class="se__advice-title">Processus de sollicitation d'avis</h4>
<p class="se__advice-subtitle">
Toute personne peut prendre une décision à condition d'avoir d'abord
consulté les experts et les impactés.
</p>
</div>
<div class="se__advice-steps">
<div v-for="(as, i) in adviceSteps" :key="i" class="se__advice-step">
<div class="se__advice-dot">
<UIcon :name="as.icon" />
</div>
<span class="se__advice-text">{{ as.text }}</span>
<div v-if="i < adviceSteps.length - 1" class="se__advice-line" />
</div>
</div>
<div class="se__advice-rule">
<UIcon name="i-lucide-lightbulb" class="se__advice-rule-icon" />
<div>
<strong>Règle d'or :</strong> plus la décision est impactante, plus il faut
consulter largement. Mais la décision finale appartient toujours à celui ou
celle qui l'a initiée.
</div>
</div>
<div class="se__advice-when">
<div class="se__advice-when-item se__advice-when-item--yes">
<span class="se__advice-when-label">Adapter pour</span>
<ul>
<li>Décisions urgentes</li>
<li>Rôles bien définis</li>
<li>Culture de confiance</li>
</ul>
</div>
<div class="se__advice-when-item se__advice-when-item--no">
<span class="se__advice-when-label">Éviter si</span>
<ul>
<li>Décision irréversible</li>
<li>Groupe > 100 personnes</li>
<li>Enjeu fondateur</li>
</ul>
</div>
</div>
</div>
<!-- Role clarity -->
<div v-if="activeTab === 'role'" class="se__panel">
<p class="se__intro">
Un rôle bien défini évite les zones grises, les conflits d'autorité
et les mandats flous. Quatre axes suffisent.
</p>
<div class="se__role-axes">
<div v-for="axis in roleAxes" :key="axis.label" class="se__role-axis">
<div class="se__role-axis-icon">
<UIcon :name="axis.icon" />
</div>
<div class="se__role-axis-body">
<span class="se__role-axis-label">{{ axis.label }}</span>
<p class="se__role-axis-question">{{ axis.question }}</p>
<p class="se__role-axis-example">ex: {{ axis.example }}</p>
</div>
</div>
</div>
<div class="se__role-tip">
<UIcon name="i-lucide-info" />
<span>Un rôle n'est pas une fiche de poste. Il peut évoluer au prochain cycle
de gouvernance sans changer la personne qui le tient.</span>
</div>
</div>
</div>
</template>
<style scoped>
.se { display: flex; flex-direction: column; gap: 1rem; }
/* Tabs */
.se__tabs {
display: flex;
gap: 0.25rem;
background: var(--mood-accent-soft);
border-radius: 12px;
padding: 3px;
}
.se__tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
padding: 0.5rem 0.625rem;
font-size: 0.75rem;
font-weight: 700;
color: var(--mood-text-muted);
border-radius: 10px;
cursor: pointer;
transition: all 0.15s ease;
}
.se__tab--active {
background: var(--mood-surface);
color: var(--mood-accent);
box-shadow: 0 1px 4px var(--mood-shadow);
}
.se__panel { display: flex; flex-direction: column; gap: 0.875rem; }
.se__intro {
font-size: 0.8125rem;
color: var(--mood-text-muted);
line-height: 1.6;
margin: 0;
}
/* Steps */
.se__steps { display: flex; flex-direction: column; gap: 0.375rem; }
.se__step {
background: var(--mood-accent-soft);
border-radius: 12px;
overflow: hidden;
}
.se__step--open { background: var(--mood-surface); }
.se__step-head {
display: flex;
align-items: center;
gap: 0.625rem;
width: 100%;
padding: 0.75rem 0.875rem;
cursor: pointer;
text-align: left;
background: none;
}
.se__step-num {
width: 1.375rem;
height: 1.375rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--mood-accent);
color: var(--mood-accent-text);
font-size: 0.6875rem;
font-weight: 800;
}
.se__step-icon {
width: 1.75rem;
height: 1.75rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: var(--mood-surface);
color: var(--mood-accent);
font-size: 0.875rem;
}
.se__step-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.se__step-title {
font-size: 0.875rem;
font-weight: 700;
color: var(--mood-text);
}
.se__step-meta {
font-size: 0.6875rem;
color: var(--mood-text-muted);
}
.se__step-toggle {
color: var(--mood-text-muted);
font-size: 0.875rem;
flex-shrink: 0;
}
.se__step-body {
padding: 0 0.875rem 0.875rem;
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.se__step-desc {
font-size: 0.8125rem;
color: var(--mood-text-muted);
line-height: 1.6;
margin: 0;
}
.se__step-tips {
margin: 0;
padding: 0 0 0 1rem;
font-size: 0.75rem;
color: var(--mood-text-muted);
list-style-type: disc;
line-height: 1.6;
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.se__step-tips li::marker { color: var(--mood-accent); }
.se__step-pitfall {
display: flex;
align-items: flex-start;
gap: 0.375rem;
padding: 0.5rem 0.625rem;
background: color-mix(in srgb, var(--mood-error) 10%, transparent);
border-radius: 8px;
font-size: 0.6875rem;
color: var(--mood-error);
line-height: 1.5;
}
/* Advice */
.se__advice-header { display: flex; flex-direction: column; gap: 0.25rem; }
.se__advice-tag {
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 2px 8px;
border-radius: 20px;
background: color-mix(in srgb, var(--mood-success) 15%, transparent);
color: var(--mood-success);
width: fit-content;
}
.se__advice-title {
font-size: 0.9375rem;
font-weight: 800;
color: var(--mood-text);
margin: 0;
}
.se__advice-subtitle {
font-size: 0.8125rem;
color: var(--mood-text-muted);
margin: 0;
line-height: 1.5;
}
.se__advice-steps { display: flex; flex-direction: column; gap: 0; }
.se__advice-step {
display: flex;
align-items: flex-start;
gap: 0.625rem;
position: relative;
padding: 0.5rem 0;
}
.se__advice-dot {
width: 2rem;
height: 2rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--mood-accent-soft);
color: var(--mood-accent);
font-size: 0.875rem;
z-index: 1;
}
.se__advice-text {
font-size: 0.8125rem;
font-weight: 600;
color: var(--mood-text);
padding-top: 0.375rem;
line-height: 1.4;
}
.se__advice-line {
position: absolute;
left: calc(1rem - 1px);
top: calc(0.5rem + 2rem);
width: 2px;
height: calc(100% - 2rem + 0.5rem);
background: color-mix(in srgb, var(--mood-accent) 20%, transparent);
}
.se__advice-rule {
display: flex;
gap: 0.625rem;
align-items: flex-start;
padding: 0.75rem;
background: var(--mood-accent-soft);
border-radius: 10px;
font-size: 0.75rem;
color: var(--mood-text-muted);
line-height: 1.5;
}
.se__advice-rule-icon { color: var(--mood-accent); flex-shrink: 0; margin-top: 0.1rem; }
.se__advice-rule strong { color: var(--mood-text); }
.se__advice-when {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.se__advice-when-item {
padding: 0.625rem;
border-radius: 10px;
font-size: 0.75rem;
}
.se__advice-when-item--yes {
background: color-mix(in srgb, var(--mood-success) 10%, transparent);
}
.se__advice-when-item--no {
background: color-mix(in srgb, var(--mood-error) 8%, transparent);
}
.se__advice-when-label {
display: block;
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 0.25rem;
}
.se__advice-when-item--yes .se__advice-when-label { color: var(--mood-success); }
.se__advice-when-item--no .se__advice-when-label { color: var(--mood-error); }
.se__advice-when-item ul {
margin: 0;
padding: 0 0 0 0.875rem;
color: var(--mood-text-muted);
list-style-type: disc;
display: flex;
flex-direction: column;
gap: 0.125rem;
}
/* Role */
.se__role-axes { display: flex; flex-direction: column; gap: 0.625rem; }
.se__role-axis {
display: flex;
gap: 0.75rem;
align-items: flex-start;
padding: 0.75rem;
background: var(--mood-accent-soft);
border-radius: 12px;
}
.se__role-axis-icon {
width: 2rem;
height: 2rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: var(--mood-surface);
color: var(--mood-accent);
font-size: 0.875rem;
}
.se__role-axis-body { flex: 1; min-width: 0; }
.se__role-axis-label {
display: block;
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--mood-accent);
margin-bottom: 0.125rem;
}
.se__role-axis-question {
font-size: 0.8125rem;
font-weight: 600;
color: var(--mood-text);
margin: 0;
}
.se__role-axis-example {
font-size: 0.6875rem;
color: var(--mood-text-muted);
margin: 0.125rem 0 0;
line-height: 1.4;
font-style: italic;
}
.se__role-tip {
display: flex;
gap: 0.5rem;
align-items: flex-start;
font-size: 0.75rem;
color: var(--mood-text-muted);
line-height: 1.5;
padding: 0.625rem 0.75rem;
background: var(--mood-accent-soft);
border-radius: 10px;
}
/* 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: 500px;
opacity: 1;
}
</style>