# Acadenice Patches Liste des patches custom appliques sur le fork Acadenice de Docmost. Ce document est maintenu manuellement pour faciliter le rebase upstream. Repo upstream : `github.com/docmost/docmost` Branche fork : `acadenice/main` ## Conventions - Chaque patch est commit isole avec scope `feat(rebrand)` / `feat(custom)` / etc. - Les modifications in-line de fichiers upstream sont documentees ici avec rationale. - Les nouveaux fichiers (extensions Tiptap custom, hooks, etc.) vont dans des emplacements dedies pour minimiser les conflits de rebase. --- ## Patch 013 — R3.5.2 frontend graph view (page /graph, react-force-graph-2d) **Date** : 2026-05-08 **Scope** : knowledge graph frontend — page `/graph`, force-directed canvas, controls sidebar, side panel **Rationale** : rend le graphe de liens inter-pages interactif (style Obsidian/AFFiNE). Consomme `GET /api/acadenice/graph` (R3.5.1). Finalise R3.5 entierement. ### Architecture - Lib : `react-force-graph-2d` (Canvas-based, d3-force, jusqu'a 5k nodes interactifs). Peer deps : `d3-force`. A installer : `pnpm add react-force-graph-2d d3-force`. - Chargee en dynamic import (`React.lazy`) avec fallback placeholder si lib absente. - Filter state : jotai atoms (`graphFiltersAtom`, `selectedNodeIdAtom`, `focusNodeIdAtom`, `sidePanelOpenAtom`). - Data : React Query hook avec 300ms debounce sur les filtres. - Side panel : Mantine Drawer ancre a droite. - Navigation : `useNavigate` vers `/p/{slugId}` (slugId map a etendre quand le backend inclura `slugId` dans `GraphNode`). ### Route `/graph` — workspace-level, accessible via sidebar (icone `IconAffiliate`). ### Nouveaux fichiers | Fichier | Role | |---------|------| | `apps/client/src/features/acadenice/graph/services/graph-client.ts` | HTTP client fetchGraph | | `apps/client/src/features/acadenice/graph/hooks/use-graph-controls.ts` | Jotai atoms filtres + UI state | | `apps/client/src/features/acadenice/graph/hooks/use-graph-data.ts` | React Query hook + debounce | | `apps/client/src/features/acadenice/graph/components/graph-canvas.tsx` | Canvas force-graph wrapper | | `apps/client/src/features/acadenice/graph/components/graph-controls.tsx` | Sidebar filtres + stats | | `apps/client/src/features/acadenice/graph/components/graph-node-tooltip.tsx` | Card tooltip node hover | | `apps/client/src/features/acadenice/graph/components/graph-side-panel.tsx` | Drawer detail node | | `apps/client/src/features/acadenice/graph/pages/graph-page.tsx` | Page orchestratrice | | `apps/client/src/features/acadenice/graph/__tests__/graph-client.test.ts` | 10 tests | | `apps/client/src/features/acadenice/graph/__tests__/use-graph-controls.test.ts` | 16 tests | | `apps/client/src/features/acadenice/graph/__tests__/use-graph-data.test.ts` | 8 tests | | `apps/client/src/features/acadenice/graph/__tests__/graph-controls.test.tsx` | 11 tests | | `apps/client/src/features/acadenice/graph/__tests__/graph-canvas.smoke.test.tsx` | 5 smoke tests | | `apps/client/src/features/acadenice/graph/__tests__/graph-side-panel.test.tsx` | 8 tests | ### Fichiers upstream modifies (patches) | Fichier | Modification | |---------|-------------| | `apps/client/src/App.tsx` | +import GraphPage + route `/graph` dans `` | | `apps/client/src/components/layouts/global/global-sidebar.tsx` | +import IconAffiliate + entree `graph.page_title` dans mainNavItems | | `apps/client/public/locales/en-US/translation.json` | +24 cles `graph.*` | | `apps/client/public/locales/fr-FR/translation.json` | +24 cles `graph.*` (traductions FR) | ### Tests - 58 tests total (10 graph-client + 16 use-graph-controls + 8 use-graph-data + 11 graph-controls + 5 canvas-smoke + 8 graph-side-panel) - Canvas smoke : verifie le rendu sans crash quand react-force-graph-2d est absent (fallback) - Hooks : debounce behavior, state transitions jotai, React Query staleTime ### Nouvelles deps (a installer) ``` react-force-graph-2d ^1.43.x (peer: d3-force ^3.0.x) d3-force ^3.0.x ``` ### Choix techniques - Dynamic import (`React.lazy`) : isole la dep Canvas du bundle critique. Fallback `` affiche les instructions d'installation si la lib est absente. - Jotai vs useState local : les 4 atoms sont cross-composant (canvas <-> controls <-> side panel). Un Context React aurait necessit un provider supplementaire ; jotai reste hors de l'arbre JSX. - `spaceColorCache` module-level : mapping deterministe spaceId -> couleur Mantine palette. Reset automatique au rechargement de page (pas de persistence necessaire). - slugId map actuellement vide : le backend GraphNode (R3.5.1) n'expose pas `slugId`. Extension possible sans casser le contrat : ajouter `slugId?: string` a GraphNode. ### Point a debattre avec Corentin 1. **slugId dans GraphNode** : faut-il enrichir le backend (R3.5.1) pour inclure `slugId` dans chaque node ? Permettrait l'activation du bouton "Open page" et la navigation double-click. Patch mineur service + DTO cote backend. 2. **react-force-graph-2d version exacte** : pinner a la derniere stable avant install (`pnpm add react-force-graph-2d@latest d3-force@latest --filter docmost-client`). 3. **Context menu right-click** : actuellement le right-click fait "Focus mode" (recenter). Un vrai context menu (Mantine Menu) avec "Open in new tab" / "Focus" / "Copy link" est possible mais necessite un overlay positionne sur le canvas (hors Mantine portals). --- ## Patch 012 — R3.5.1 backend graph endpoint GET /api/acadenice/graph **Date** : 2026-05-08 **Scope** : knowledge graph backend — nodes + edges from acadenice_backlink table **Rationale** : expose workspace link-graph as JSON for the R3.5.2 frontend (Obsidian-style graph view). Reads the `acadenice_backlink` table populated by R3.2. ### Architecture - Source de verite : table `acadenice_backlink` (indexee par R3.2 sur chaque save). - Permission filter : meme join space_members / visibility='public' que BacklinkService. - BFS iteratif en memoire (apres chargement des edges) pour les graphes centres (pageId). - Cache Redis TTL 60s par cle composite. Invalidation sur evenement `acadenice.page.content.updated` (meme event que R3.2). - Truncation a 1000 nodes pour les workspaces larges (top-inDegree + flag `truncated: true`). ### Endpoint `GET /api/acadenice/graph?workspaceId=X&spaceId=Y&pageId=Z&depth=N&types=wikilink,mention,database_embed&includeOrphans=false` - `workspaceId` : ignore — resolu depuis le JWT pour eviter les fuites cross-workspace - `spaceId` : optionnel, filtre les nodes au space - `pageId` : optionnel, centre le graphe + BFS depth hops - `depth` : 1-5, default 2 - `types` : filtre par type de lien (defaut: tous les 3) - `includeOrphans` : default false ### Reponse ```ts { nodes: Array<{ id, label, type, spaceId, spaceName, icon, isOrphan, metrics: { inDegree, outDegree } }>, edges: Array<{ id, source, target, type, weight }>, meta: { totalNodes, totalEdges, workspaceId, rootPageId?, depth?, truncated } } ``` ### Fichiers crees | Fichier | Role | |---------|------| | `apps/server/src/core/acadenice/graph/graph.module.ts` | NestJS module (R3.5.1) | | `apps/server/src/core/acadenice/graph/dto/graph.dto.ts` | Zod schemas + interfaces response | | `apps/server/src/core/acadenice/graph/services/graph.service.ts` | buildGraph, BFS, Redis cache | | `apps/server/src/core/acadenice/graph/controllers/graph.controller.ts` | GET /api/acadenice/graph | | `apps/server/src/core/acadenice/graph/spec/graph.service.spec.ts` | Tests service (21 tests) | | `apps/server/src/core/acadenice/graph/spec/graph.controller.spec.ts` | Tests controller (14 tests) | ### Fichiers modifies (patches upstream) | Fichier | Modification | |---------|-------------| | `apps/server/src/core/core.module.ts` | +AcadeniceGraphModule import + declaration | ### Tests - 35 tests total (21 service + 14 controller) - Service : full graph, edge weight, BFS depth=1/2, spaceId filter, types filter, permission filter, truncation@1000, orphans inclus/exclus, inDegree/outDegree, cache hit, cache invalidation, bfsReachable unit, buildCacheKey, error resilience - Controller : routing, params parsing (depth/spaceId/pageId/types/includeOrphans), validation errors (bad UUID, depth>5, depth<1), workspace isolation ### Strategies techniques - SQL : GROUP BY (source_page_id, target_page_id, link_type) avec COUNT(*) pour weight. Double join pages/spaces (source ET target) avec permission check inline. - BFS : iteratif avec Set visited. Graphe non-oriente (in+out edges). Cap MAX_NODES=1000. - Cache key : `acadenice:graph::::::` - Invalidation : pattern `acadenice:graph::*` via KEYS + DEL. Acceptable pour TTL=60s. --- ## Patch 011 — R3.4 dual editor (WYSIWYG + markdown source) **Date** : 2026-05-08 **Scope** : toggle WYSIWYG <-> raw markdown source, custom-node round-trip **Rationale** : permet aux utilisateurs power-users d'editer le source markdown directement, avec une conversion aller-retour complete preservant les nodes Acadenice custom (database-view, wikilink, mention). ### Architecture - Source de verite : Tiptap JSON (persiste en DB). Le markdown est une vue. - Mode persist : localStorage `acadenice:editor-mode:` par page. - Switch lossy : modal de confirmation listant les elements alteres. - Save : en mode markdown, le doc Tiptap est maintenu sync (setContent) a chaque keystroke pour que le mecanisme save Docmost natif reste fonctionnel. ### Syntaxe custom nodes en markdown | Node | Syntaxe markdown | |------|-----------------| | `database-view` | `[[!db tableId=X viewId=Y viewType=Z]]` | | `wikilink` | `[[Page Title]]` ou `[[Page Title\|alias]]` | | `mention` | `@(displayName)` | Choix : tokens entre `[[...]]` pour etre lisibles et reversibles. Le prefixe `!db` distingue les database-view des wikilinks. Les mentions encodent le userId (UUID) pour eviter la necessite d'une resolution serveur au re-parse. ### Fichiers crees | Fichier | Role | |---------|------| | `apps/client/src/features/acadenice/dual-editor/services/custom-node-serializers.ts` | Registre des serializers custom (databaseView, wikilink, mention) | | `apps/client/src/features/acadenice/dual-editor/services/markdown-converter.ts` | `tiptapToMarkdown` + `markdownToTiptap` — converter custom sans dep externe | | `apps/client/src/features/acadenice/dual-editor/hooks/use-editor-mode.ts` | Jotai atom `editorModeAtom` + `useEditorMode` hook + `initEditorMode` | | `apps/client/src/features/acadenice/dual-editor/components/mode-toggle-button.tsx` | Bouton toggle (IconCode / IconEye) dans la toolbar | | `apps/client/src/features/acadenice/dual-editor/components/markdown-editor.tsx` | Textarea monospace auto-resize (Tab -> 2 espaces) | | `apps/client/src/features/acadenice/dual-editor/components/dual-editor.tsx` | Wrapper WYSIWYG / markdown avec modal warning lossy | | `apps/client/src/features/acadenice/dual-editor/__tests__/markdown-converter.test.ts` | 61 tests round-trip (JSON->MD->JSON et MD->JSON->MD) | | `apps/client/src/features/acadenice/dual-editor/__tests__/custom-node-serializers.test.ts` | 12 tests unitaires serializers | | `apps/client/src/features/acadenice/dual-editor/__tests__/use-editor-mode.test.ts` | 4 tests persistence localStorage | ### Fichiers modifies (patches upstream) | Fichier | Modification | |---------|-------------| | `apps/client/src/features/editor/full-editor.tsx` | +import DualEditor + wrap `` avec `` | | `apps/client/public/locales/en-US/translation.json` | +8 cles `dual_editor.*` | | `apps/client/public/locales/fr-FR/translation.json` | +8 cles `dual_editor.*` traduits | ### Nouvelles dependances requises Aucune. Le converter est custom TypeScript pur. L'editeur markdown utilise une `