Files
archicratie-edition/src/lib/glossary.ts
Archicratia 36bb47f9c6
All checks were successful
SMOKE / smoke (push) Successful in 8s
CI / build-and-anchors (push) Successful in 40s
CI / build-and-anchors (pull_request) Successful in 42s
fix(glossaire): préserver les parcours intelligents explicites
2026-04-26 21:50:08 +02:00

746 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { CollectionEntry } from "astro:content";
import { GLOSSARY_NAV_DEFAULTS } from "./glossary-navigation-defaults";
export type GlossaryEntry = CollectionEntry<"glossaire">;
export type GlossaryPortalLink = {
href: string;
label: string;
};
export type GlossaryRelationSection = {
title: string;
items: GlossaryEntry[];
};
export type GlossaryRelationBlock = GlossaryRelationSection & {
className: string;
};
export type GlossarySmartNavigationPathKey =
| "understand"
| "deepen"
| "compare"
| "apply";
export type GlossarySmartNavigationPath = {
key: GlossarySmartNavigationPathKey;
label: string;
entries: GlossaryEntry[];
};
export type GlossarySmartNavigationFlow = {
key: string;
label: string;
primaryNext?: GlossaryEntry;
primaryReason?: string;
};
export type GlossarySmartNavigation = {
primaryNext?: GlossaryEntry;
primaryReason?: string;
paths: GlossarySmartNavigationPath[];
flows: GlossarySmartNavigationFlow[];
};
export type GlossaryHomeStats = {
totalEntries: number;
paradigmesCount: number;
doctrinesCount: number;
metaRegimesCount: number;
};
export type GlossaryEntryAsideData = {
displayFamily: string;
displayDomain: string;
displayLevel: string;
showNoyau: boolean;
showSameFamily: boolean;
fondamentaux: GlossaryEntry[];
sameFamilyTitle: string;
sameFamilyEntries: GlossaryEntry[];
relationSections: GlossaryRelationSection[];
contextualTheory: GlossaryEntry[];
};
export type GlossaryHomeData = {
fondamentaux: GlossaryEntry[];
scenes: GlossaryEntry[];
dynamiques: GlossaryEntry[];
metaRegimes: GlossaryEntry[];
metaRegimesPreview: GlossaryEntry[];
arcalite?: GlossaryEntry;
cratialite?: GlossaryEntry;
tension?: GlossaryEntry;
sceneDepreuve?: GlossaryEntry;
archicration?: GlossaryEntry;
};
export const GLOSSARY_COLLATOR = new Intl.Collator("fr", {
sensitivity: "base",
numeric: true,
});
export const FAMILY_LABELS: Record<string, string> = {
"concept-fondamental": "Concept fondamental",
scene: "Scène",
dynamique: "Dynamique",
pathologie: "Pathologie",
topologie: "Topologie",
"meta-regime": "Méta-régime",
paradigme: "Paradigme",
doctrine: "Doctrine",
verbe: "Verbe",
"dispositif-ia": "Dispositif IA",
"tension-irreductible": "Tension irréductible",
figure: "Figure",
qualification: "Qualification",
epistemologie: "Épistémologie",
};
export const KIND_LABELS: Record<string, string> = {
concept: "Concept",
diagnostic: "Diagnostic",
topologie: "Topologie",
verbe: "Verbe",
paradigme: "Paradigme",
doctrine: "Doctrine",
dispositif: "Dispositif",
figure: "Figure",
qualification: "Qualification",
epistemologie: "Épistémologie",
};
export const DOMAIN_LABELS: Record<string, string> = {
transversal: "Transversal",
theorie: "Théorie",
"cas-ia": "Cas IA",
};
export const LEVEL_LABELS: Record<string, string> = {
fondamental: "Fondamental",
intermediaire: "Intermédiaire",
avance: "Avancé",
};
export const FONDAMENTAUX_WANTED = [
"archicratie",
"arcalite",
"cratialite",
"archicration",
"co-viabilite",
"tension",
] as const;
export const FAMILY_SECTION_TITLES: Record<string, string> = {
"concept-fondamental": "Noyau archicratique",
scene: "Scènes archicratiques",
dynamique: "Dynamiques archicratiques",
pathologie: "Pathologies archicratiques",
topologie: "Topologies voisines",
"meta-regime": "Méta-régimes archicratiques",
paradigme: "Paradigmes voisins",
doctrine: "Doctrines fondatrices",
verbe: "Verbes de la scène",
"dispositif-ia": "Dispositifs IA",
"tension-irreductible": "Tensions irréductibles",
figure: "Figures archicratiques",
qualification: "Qualifications archicratiques",
epistemologie: "Outillage épistémologique",
};
export const SMART_NAV_PATH_LABELS: Record<
GlossarySmartNavigationPathKey,
string
> = {
understand: "Situer",
deepen: "Déployer",
compare: "Mettre en tension",
apply: "Mettre à lépreuve",
};
const PREFERRED_PARADIGME_SLUGS = [
"gouvernementalite",
"gouvernementalite-algorithmique",
"cybernetique",
"biopolitique",
"domination-legale-rationnelle",
"democratie-deliberative",
"gouvernance-des-communs",
"agencement-machinique",
"pharmacologie-technique",
"preemption-algorithmique",
"dissensus-politique",
"lieu-vide-du-pouvoir",
"habitus-et-violence-symbolique",
"theorie-de-la-resonance",
"conatus-et-multitude",
"configuration-et-interdependance",
"technodiversite-et-cosmotechnie",
"grammatisation-et-proletarisation-cognitive",
] as const;
const PREFERRED_DOCTRINE_SLUGS = [
"contractualisme-hobbesien",
"droit-naturel-et-propriete",
"volonte-generale",
"decisionnisme-souverain",
] as const;
export function normalizeGlossarySlug(value: unknown): string {
return String(value ?? "")
.trim()
.replace(/^\/+|\/+$/g, "")
.replace(/\.(md|mdx)$/i, "")
.toLowerCase();
}
export function slugOfGlossaryEntry(
entry: Pick<GlossaryEntry, "id"> | null | undefined,
): string {
return normalizeGlossarySlug(entry?.id ?? "");
}
export function hrefOfGlossaryEntry(
entry: Pick<GlossaryEntry, "id"> | null | undefined,
): string {
const slug = slugOfGlossaryEntry(entry);
return slug ? `/glossaire/${slug}/` : "/glossaire/";
}
export function buildGlossaryBySlug(
entries: GlossaryEntry[] = [],
): Map<string, GlossaryEntry> {
return new Map(entries.map((entry) => [slugOfGlossaryEntry(entry), entry]));
}
export function sortGlossaryEntries(
entries: GlossaryEntry[] = [],
): GlossaryEntry[] {
return [...entries].sort((a, b) =>
GLOSSARY_COLLATOR.compare(a.data.term, b.data.term),
);
}
export function uniqueGlossaryEntries(
entries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const seen = new Set<string>();
const out: GlossaryEntry[] = [];
for (const entry of entries) {
const slug = slugOfGlossaryEntry(entry);
if (!slug || seen.has(slug)) continue;
seen.add(slug);
out.push(entry);
}
return out;
}
export function slugsOfGlossaryEntries(
entries: GlossaryEntry[] = [],
): Set<string> {
const slugs = new Set<string>();
for (const entry of entries) {
const slug = slugOfGlossaryEntry(entry);
if (!slug) continue;
slugs.add(slug);
}
return slugs;
}
export function excludeGlossaryEntries(
entries: GlossaryEntry[] = [],
excluded: Iterable<string> = [],
): GlossaryEntry[] {
const excludedSlugs = new Set(
Array.from(excluded)
.map((value) => normalizeGlossarySlug(value))
.filter(Boolean),
);
return entries.filter((entry) => {
const slug = slugOfGlossaryEntry(entry);
return Boolean(slug) && !excludedSlugs.has(slug);
});
}
export function resolveGlossaryEntriesInSourceOrder(
slugs: string[] = [],
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const bySlug = buildGlossaryBySlug(allEntries);
const seen = new Set<string>();
const resolved: GlossaryEntry[] = [];
for (const rawSlug of slugs) {
const slug = normalizeGlossarySlug(rawSlug);
if (!slug || seen.has(slug)) continue;
const entry = bySlug.get(slug);
if (!entry) continue;
seen.add(slug);
resolved.push(entry);
}
return resolved;
}
export function resolveGlossaryEntries(
slugs: string[] = [],
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
return sortGlossaryEntries(
resolveGlossaryEntriesInSourceOrder(slugs, allEntries),
);
}
export function rawFamilyOf(
entry: GlossaryEntry | null | undefined,
): string {
return String(entry?.data?.family ?? "");
}
export function kindOf(
entry: GlossaryEntry | null | undefined,
): string {
return String(entry?.data?.kind ?? "");
}
export function familyOf(
entry: GlossaryEntry | null | undefined,
): string {
const explicit = rawFamilyOf(entry);
if (explicit) return explicit;
const slug = slugOfGlossaryEntry(entry);
const kind = kindOf(entry);
if (kind === "paradigme") return "paradigme";
if (kind === "doctrine") return "doctrine";
if (kind === "verbe") return "verbe";
if (slug === "scene-depreuve") return "scene";
if (slug === "autarchicratie") return "pathologie";
if (slug === "obliteration-archicratique") return "dynamique";
if (
FONDAMENTAUX_WANTED.includes(
slug as (typeof FONDAMENTAUX_WANTED)[number],
)
) {
return "concept-fondamental";
}
if (slug === "archicrations-differentielles-et-formes-hybrides") {
return "topologie";
}
if (kind === "topologie" && slug.startsWith("archicrations-")) {
return "meta-regime";
}
return "";
}
export function getDisplayFamily(
entry: GlossaryEntry | null | undefined,
): string {
const familyKey = rawFamilyOf(entry) || familyOf(entry);
return FAMILY_LABELS[familyKey] ?? KIND_LABELS[kindOf(entry)] ?? "Fiche";
}
export function getDisplayDomain(
entry: GlossaryEntry | null | undefined,
): string {
const key = String(entry?.data?.domain ?? "");
return key ? (DOMAIN_LABELS[key] ?? key) : "";
}
export function getDisplayLevel(
entry: GlossaryEntry | null | undefined,
): string {
const key = String(entry?.data?.level ?? "");
return key ? (LEVEL_LABELS[key] ?? key) : "";
}
export function getFondamentaux(
entries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const bySlug = buildGlossaryBySlug(entries);
return sortGlossaryEntries(
FONDAMENTAUX_WANTED
.map((slug) => bySlug.get(slug))
.filter(Boolean) as GlossaryEntry[],
);
}
export function getGlossaryEntriesByFamily(
entries: GlossaryEntry[] = [],
familyKey: string,
): GlossaryEntry[] {
return sortGlossaryEntries(
entries.filter((entry) => familyOf(entry) === familyKey),
);
}
export function countGlossaryEntriesByKind(
entries: GlossaryEntry[] = [],
kindKey: string,
): number {
return entries.filter((entry) => kindOf(entry) === kindKey).length;
}
export function countGlossaryEntriesByFamily(
entries: GlossaryEntry[] = [],
familyKey: string,
): number {
return entries.filter((entry) => familyOf(entry) === familyKey).length;
}
export function getGlossaryPortalLinks(): GlossaryPortalLink[] {
return [
{ href: "/glossaire/", label: "Accueil du glossaire" },
{ href: "/glossaire/concepts-fondamentaux/", label: "Concepts fondamentaux" },
{ href: "/glossaire/index-complet/", label: "Index complet" },
{
href: "/glossaire/paradigme-archicratique/",
label: "Paradigme archicratique",
},
{
href: "/glossaire/scenes-archicratiques/",
label: "Scènes archicratiques",
},
{
href: "/glossaire/dynamiques-archicratiques/",
label: "Dynamiques archicratiques",
},
{
href: "/glossaire/tensions-irreductibles/",
label: "Tensions irréductibles",
},
{
href: "/glossaire/archicrations/",
label: "Méta-régimes archicratiques",
},
{ href: "/glossaire/paradigmes/", label: "Paradigmes et doctrines" },
{ href: "/glossaire/verbes-de-la-scene/", label: "Verbes de la scène" },
];
}
export function getGlossaryHomeStats(
allEntries: GlossaryEntry[] = [],
): GlossaryHomeStats {
return {
totalEntries: allEntries.length,
paradigmesCount: countGlossaryEntriesByKind(allEntries, "paradigme"),
doctrinesCount: countGlossaryEntriesByKind(allEntries, "doctrine"),
metaRegimesCount: countGlossaryEntriesByFamily(allEntries, "meta-regime"),
};
}
export function getEntriesOfSameFamily(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const familyKey = familyOf(entry);
if (!familyKey) return [];
if (familyKey === "concept-fondamental") {
return getFondamentaux(allEntries);
}
return getGlossaryEntriesByFamily(allEntries, familyKey);
}
export function getSameFamilyTitle(
entry: GlossaryEntry,
): string {
return FAMILY_SECTION_TITLES[familyOf(entry)] ?? "Même famille";
}
export function getRelationBlocks(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryRelationBlock[] {
const currentSlug = slugOfGlossaryEntry(entry);
const relatedEntries = resolveGlossaryEntriesInSourceOrder(
entry.data.related ?? [],
allEntries,
).filter((item) => slugOfGlossaryEntry(item) !== currentSlug);
const opposedEntries = resolveGlossaryEntriesInSourceOrder(
entry.data.opposedTo ?? [],
allEntries,
).filter((item) => slugOfGlossaryEntry(item) !== currentSlug);
const seeAlsoEntries = resolveGlossaryEntriesInSourceOrder(
entry.data.seeAlso ?? [],
allEntries,
).filter((item) => slugOfGlossaryEntry(item) !== currentSlug);
return [
{
title: "Concepts liés",
items: relatedEntries,
className: "is-related",
},
{
title: "En tension avec",
items: opposedEntries,
className: "is-opposed",
},
{
title: "Voir aussi",
items: seeAlsoEntries,
className: "is-see-also",
},
].filter((block) => block.items.length > 0);
}
export function getGlossarySmartNavigation(
currentEntry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossarySmartNavigation {
const currentSlug = slugOfGlossaryEntry(currentEntry);
const rawNavigation = currentEntry.data.navigation;
const defaultNavigation = GLOSSARY_NAV_DEFAULTS[familyOf(currentEntry)] ?? {};
const withDefaultPath = (
explicit: string[] | undefined,
fallback: string[] | undefined,
): string[] => (explicit && explicit.length > 0 ? explicit : fallback ?? []);
const navigationSource = {
primaryNext: rawNavigation?.primaryNext,
primaryReason: rawNavigation?.primaryReason,
paths: {
understand: withDefaultPath(rawNavigation?.paths?.understand, defaultNavigation.understand),
deepen: withDefaultPath(rawNavigation?.paths?.deepen, defaultNavigation.deepen),
compare: withDefaultPath(rawNavigation?.paths?.compare, defaultNavigation.compare),
apply: withDefaultPath(rawNavigation?.paths?.apply, defaultNavigation.apply),
},
flows: rawNavigation?.flows ?? {},
relationWeights: rawNavigation?.relationWeights ?? {},
};
const primaryNext = resolveGlossaryEntriesInSourceOrder(
navigationSource.primaryNext ? [navigationSource.primaryNext] : [],
allEntries,
).find((entry) => slugOfGlossaryEntry(entry) !== currentSlug);
const pathKeys: GlossarySmartNavigationPathKey[] = [
"understand",
"deepen",
"compare",
"apply",
];
const paths = pathKeys
.map((key) => {
const entries = resolveGlossaryEntriesInSourceOrder(
navigationSource.paths?.[key] ?? [],
allEntries,
).filter((entry) => slugOfGlossaryEntry(entry) !== currentSlug);
return {
key,
label: SMART_NAV_PATH_LABELS[key],
entries,
};
})
.filter((path) => path.entries.length > 0);
const flows = Object.entries(navigationSource.flows ?? {})
.map(([key, flow]) => {
const primaryNext = resolveGlossaryEntriesInSourceOrder(
flow.primaryNext ? [flow.primaryNext] : [],
allEntries,
).find((entry) => slugOfGlossaryEntry(entry) !== currentSlug);
return {
key,
label: flow.label,
primaryNext,
primaryReason: flow.primaryReason,
};
})
.filter((flow) => flow.primaryNext);
if (primaryNext || paths.length > 0 || flows.length > 0) {
return {
primaryNext,
primaryReason: navigationSource.primaryReason,
paths,
flows,
};
}
const fallbackEntries = uniqueGlossaryEntries([
...resolveGlossaryEntriesInSourceOrder(
currentEntry.data.related ?? [],
allEntries,
),
...resolveGlossaryEntriesInSourceOrder(
currentEntry.data.seeAlso ?? [],
allEntries,
),
]).filter((entry) => slugOfGlossaryEntry(entry) !== currentSlug);
const fallbackPrimary = fallbackEntries[0];
return {
primaryNext: fallbackPrimary,
primaryReason: fallbackPrimary
? "Ce lien prolonge directement les relations conceptuelles de cette fiche."
: undefined,
paths: [],
flows: [],
};
}
export function getRelationSections(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryRelationSection[] {
return getRelationBlocks(entry, allEntries).map(({ title, items }) => ({
title,
items,
}));
}
function isTheoryEntry(entry: GlossaryEntry): boolean {
const familyKey = familyOf(entry);
const kindKey = kindOf(entry);
return (
familyKey === "paradigme" ||
familyKey === "doctrine" ||
kindKey === "paradigme" ||
kindKey === "doctrine"
);
}
export function getContextualTheory(
entry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryEntry[] {
const currentSlug = slugOfGlossaryEntry(entry);
const bySlug = buildGlossaryBySlug(allEntries);
const fromRelations = uniqueGlossaryEntries([
...resolveGlossaryEntriesInSourceOrder(entry.data.related ?? [], allEntries),
...resolveGlossaryEntriesInSourceOrder(entry.data.seeAlso ?? [], allEntries),
...resolveGlossaryEntriesInSourceOrder(entry.data.opposedTo ?? [], allEntries),
])
.filter((item) => slugOfGlossaryEntry(item) !== currentSlug)
.filter((item) => isTheoryEntry(item));
if (fromRelations.length > 0) {
return fromRelations.slice(0, 6);
}
if (familyOf(entry) === "paradigme") {
return uniqueGlossaryEntries(
PREFERRED_PARADIGME_SLUGS
.filter((slug) => slug !== currentSlug)
.map((slug) => bySlug.get(slug))
.filter(Boolean) as GlossaryEntry[],
).slice(0, 8);
}
if (familyOf(entry) === "doctrine") {
return uniqueGlossaryEntries(
PREFERRED_DOCTRINE_SLUGS
.filter((slug) => slug !== currentSlug)
.map((slug) => bySlug.get(slug))
.filter(Boolean) as GlossaryEntry[],
).slice(0, 6);
}
return [];
}
export function getGlossaryEntryAsideData(
currentEntry: GlossaryEntry,
allEntries: GlossaryEntry[] = [],
): GlossaryEntryAsideData {
const currentFamily = familyOf(currentEntry);
const currentSlug = slugOfGlossaryEntry(currentEntry);
const fondamentaux = getFondamentaux(allEntries);
const sameFamilyTitle = getSameFamilyTitle(currentEntry);
const relationSections = getRelationSections(currentEntry, allEntries);
const relationEntries = uniqueGlossaryEntries(
relationSections.flatMap((section) => section.items),
);
const relationSlugs = slugsOfGlossaryEntries(relationEntries);
const contextualTheory = excludeGlossaryEntries(
getContextualTheory(currentEntry, allEntries),
new Set([currentSlug, ...relationSlugs]),
).slice(0, 6);
const contextualTheorySlugs = slugsOfGlossaryEntries(contextualTheory);
const sameFamilyEntries = excludeGlossaryEntries(
getEntriesOfSameFamily(currentEntry, allEntries),
new Set([currentSlug, ...relationSlugs, ...contextualTheorySlugs]),
).slice(0, 8);
const showNoyau =
currentFamily !== "concept-fondamental" &&
fondamentaux.length > 0;
const showSameFamily =
currentFamily !== "concept-fondamental" &&
sameFamilyEntries.length > 0;
return {
displayFamily: getDisplayFamily(currentEntry),
displayDomain: getDisplayDomain(currentEntry),
displayLevel: getDisplayLevel(currentEntry),
showNoyau,
showSameFamily,
fondamentaux,
sameFamilyTitle,
sameFamilyEntries: showSameFamily ? sameFamilyEntries : [],
relationSections,
contextualTheory,
};
}
export function getGlossaryHomeData(
entries: GlossaryEntry[] = [],
): GlossaryHomeData {
const bySlug = buildGlossaryBySlug(entries);
const fondamentaux = getGlossaryEntriesByFamily(entries, "concept-fondamental");
const scenes = getGlossaryEntriesByFamily(entries, "scene");
const dynamiques = sortGlossaryEntries(
entries.filter((entry) =>
["dynamique", "pathologie"].includes(familyOf(entry)),
),
);
const metaRegimes = getGlossaryEntriesByFamily(entries, "meta-regime");
return {
fondamentaux,
scenes,
dynamiques,
metaRegimes,
metaRegimesPreview: metaRegimes.slice(0, 6),
arcalite: bySlug.get("arcalite"),
cratialite: bySlug.get("cratialite"),
tension: bySlug.get("tension"),
sceneDepreuve: bySlug.get("scene-depreuve"),
archicration: bySlug.get("archicration"),
};
}