Markpad

Un plugin Obsidian et un site pour écrire a plusieurs.
Installer BRAT : Cliquez ici.
Ajouter notre plugin : Utilisez l’option « Add Beta plugin » avec l’URL GitHub.
📸 Aperçu
| Plugin Obsidian | Interface web |
|---|---|
![]() |
![]() |
📖 À propos
Markpad est un mono-repo de collaboration temps réel pour notes Markdown avec 3 composants :
plugin/: plugin Obsidian (source de partage)server/: backend Node.js + Yjs + WebSocket + Redisweb/: frontend React + CodeMirror 6 (markdown source, collab Yjs) + aperçu HTML, barre d’outils type Obsidian (icônes Lucide) ; l’arborescence des partages dossier affiche des chemins relatifs au dossier partagé (sans exposer le préfixe « en amont » du vault).
⬇️ Plugin Obsidian (BRAT)
- 🔌 Installer BRAT : Obsidian — BRAT
- ➕ Ajouter ce dépôt avec l’option « Add Beta plugin » :
https://github.com/Atelier-Recherche/markpad
ID du plugin :
markpad— dossier cible :<vault>/.obsidian/plugins/markpad/💡 Nos plugins peuvent être en attente de validation sur le catalogue Obsidian ; BRAT permet de les tester dès maintenant. Voir aussi 🌐 l’Atelier.
🏗️ Architecture
- Le plugin crée une session via l’API REST du serveur.
- Le serveur retourne un
roomIdet une URL de partage. - Plugin et Web se connectent au WebSocket de la room.
- Les updates Yjs sont relayées en temps réel.
- L’état Yjs est persisté dans Redis pour reprise de session.
- Le plugin enregistre
markpadSharedans le frontmatter (roomId,shareUrl) pour retrouver la room à l’ouverture de la note.
Plugin Obsidian : sync et reconnexion
- Reconnexion automatique (paramètre dans les réglages du plugin, activé par défaut) : si la note active contient déjà un partage et que
User IDest renseigné, le plugin ouvre la session WebSocket au démarrage / au changement de note, sans relancer « Start Sharing ». - Réconciliation après le premier sync : le fichier
.mdsur le vault est comparé au corps du document Yjs (hors frontmatter). En cas de divergences (édition hors ligne, Redis vide ou ancien), le plugin applique un fusionnement par patches (diff-match-patch) dans Yjs pour limiter les pertes au lieu d’écraser un côté. Si Yjs est vide et la note locale non vide, le contenu local est réinjecté (protection anti-vide après redémarrage Redis/serveur). - Barre d’état : affiche notamment connecté, synchronisation en cours, ou hors ligne.
- Indicateurs dans l’explorateur et les onglets (Lucide via l’API Obsidian) : icône lien pour une note partagée ; icône dossiers pour la racine d’un partage dossier. Pour la session WebSocket active, l’icône reflète l’état (ex. chargement pendant la connexion ou la réconciliation post-sync, rafraîchissement si Yjs n’est pas encore à jour, hors ligne si le WS est coupé).
- Sens Obsidian → Web : en plus de y-codemirror, un petit module CodeMirror réaligne
Y.Textsur le document si la frappe locale et Y divergent (contourne des cas où le binding CM→Y ne propage pas, selon la version Obsidian / Live Preview). La bonne instanceEditorViewest résolue (source,editorComponent, sous-mode d’édition) avant de monter la collab. - Dépannage : dans les réglages du plugin, activer « Logs diagnostic (console) », ouvrir la console développeur (Ctrl+Shift+I), filtrer sur
Markpad:collab: tu verras les événements horodatés (résolution CM, pont,Y.Doc update, WebSocket,editor-change). En partage dossier, les lignes préfixéesfolder:détaillent le démarrage (startSharing,attach computed, clés duY.Map,syncFolderFilesFromY, patch WebSocket sortant) — indispensable si la sync dossier ou les icônes dossier posent problème.
🖥️ Infrastructure (production)
Le déploiement serveur passe par Coolify et docker-compose.coolify.yml :
redis: stockage temporaire des documents Yjsserver: API + WebSocket (interne, port 1234)web: frontend Nginx + reverse proxy API (exposé via Traefik)
Variables principales (configurées dans l’UI Coolify, voir .env.example comme référence) :
SERVER_PORT,SERVER_HOSTPUBLIC_SERVER_URL,PUBLIC_WEB_URL(même URL en prod, ex.https://sync.atechnologie.fr)REDIS_URLSESSION_MAX_IDLE_MS— durée sans activité avant suppression de la room (défaut ~1 an)SESSION_CLEANUP_INTERVAL_MS— fréquence du nettoyage des rooms expiréesJWT_SECRET— signature des jetons web (connexion par e-mail)ADMIN_EMAILS— liste d’e-mails (séparés par des virgules) ayant le rôle admin à la première connexionMARKPAD_SQLITE_PATH— fichier SQLite (métadonnées partages, utilisateurs, index)MAX_SHARES_PER_USER— nombre maximal de sessions de partage actives par utilisateur dans l’index SQLite (défaut200) ; au-delà,POST /sessionsrépond 429 avec{ "error": "share_limit_reached" }SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS,EMAIL_FROM— envoi des liens magiques (optionnel ; sans SMTP le lien est loggé dans la console serveur). Important : l’hôte SMTP est en général celui du fournisseur de messagerie (ex.smtp.…,ssl0.ovh.netpour une boîte OVH), pas forcément le nom de domaine du site seul ; si le port 465 ne se connecte pas, essayez 587 avecSMTP_SECURE=false(STARTTLS). Voir aussiSMTP_CONNECTION_TIMEOUT_MSdans.env.example.
🌐 Web : interface invité
- Langue : français, anglais, espagnol, allemand (sélecteur dans la barre du haut, mémorisé dans
localStorage). - Nom affiché : mémorisé (
markpad-display-name). Si la room n’a pas de mot de passe, la connexion se fait sans écran « Rejoindre ». Clic droit sur la pastille « Vous » pour renommer. - Panneaux : arborescence (partage dossier), éditeur, aperçu, plan — largeurs ajustables par poignées entre colonnes (jusqu’à ~72 % pour les panneaux latéraux). Pour un partage dossier, les entrées de l’arborescence sont affichées en chemin relatif au dossier partagé ; le chemin complet du vault reste disponible en infobulle (
title) sur chaque fichier. - Routes utiles :
/share/:roomId(édition collaborative),/auth/verify?token=…(après le lien reçu par e-mail),/me(liste des partages avec un compte web),/admin(administration — voir ci‑dessous).
👤 Compte web et liste des partages
POST /auth/magic/requestavec{ "email": "vous@exemple.com" }(ou utiliser la page/medans l’app web pour saisir l’e‑mail).- Ouvrir le lien reçu (ou celui affiché dans les logs du serveur si SMTP n’est pas configuré) :
/auth/verify?token=…retourne un JWT et l’id utilisateur ; le jeton est stocké côté navigateur (localStorage, clémarkpad-jwt). GET /me/shares(en-têteAuthorization: Bearer <JWT>) liste les partages dont le propriétaire est le mêmeUser IDque celui configuré dans le plugin Obsidian (copier l’id affiché après vérification dans les réglages du plugin).
🛡️ Compte administrateur (web)
- Renseigner
ADMIN_EMAILSdans Coolify (ou.enven dev local), ou promouvoir un utilisateur en base (champis_adminsur la tableusers). - Se connecter avec le lien magique comme n’importe quel utilisateur (même adresse que dans
ADMIN_EMAILSsi vous utilisez cette méthode). - Appeler les routes
/admin/shares,/admin/users,DELETE /admin/sessions/:roomId, etc. avec le mêmeAuthorization: Bearer <JWT>que pour/me. La page/admindu frontend affiche un aperçu JSON (à compléter selon vos besoins).
📂 Plugin Obsidian : partage dossier et panneau des partages
- Partager un dossier : clic droit sur le dossier dans l’explorateur → « Markpad : partager ce dossier ». Un fichier
.markpad-folder-share.mdest créé dans le dossier ; les notes.mddu dossier sont synchronisées dans une même room Yjs (Y.Mapfiles). - Dossier déjà partagé : le même menu contextuel propose copier le lien et arrêter le partage du dossier. La ligne du dossier dans l’explorateur affiche aussi une icône (clic pour copier le lien du dossier).
- Panneau latéral : commande « Ouvrir le panneau des partages » — liste tous les partages (notes et dossiers) avec copier le lien et supprimer le partage (côté serveur + nettoyage local). La liste se met à jour quand le frontmatter ou l’ancre change (y compris après suppression depuis le panneau).
- Suppression dans le vault : supprimer l’ancre
.markpad-folder-share.md, un dossier racine de partage, ou un fichier inclus dans un partage dossier met à jour ou termine le partage côté plugin et serveur (selon le cas : arrêt du dossier, retrait d’un fichier, ou dernière note → fin de room). Pour une note seule partagée, la suppression du fichier déclenche aussi la fin de session côté serveur (meilleur effort). - Renommage / déplacement : renommer ou déplacer un dossier ou une note dans un partage dossier met à jour les chemins dans la méta locale, l’ancre et, si une session dossier est ouverte, les clés du
Y.Mapfilespour rester aligné avec le web. - Clic droit sur une note dans un dossier partagé : arrêter le partage ne supprime plus toute la room par erreur : le fichier est retiré du partage dossier ; la room n’est fermée que s’il ne reste plus aucun fichier.
- Réglages : bouton « Purger… » (section fin de page) — après confirmation, arrêt des partages dossier, suppression des fichiers
.markpad-folder-share.mdrestants et réinitialisation de l’état local (sessions serveur terminées si possible).
🔄 Désynchronisation et reconnexion (rappel)
- Note seule, auteur Obsidian : après reconnexion, une réconciliation peut fusionner le
.mdlocal avec leY.Text(voir plus haut) : en cas de forte divergence, le résultat est un CRDT au niveau caractères, pas une résolution « intelligente » du markdown. - Note rejointe depuis Obsidian (« join ») : le distant prime au premier sync ; le brouillon local peut être recouvert.
- Partage dossier : pour le contenu des fichiers, Yjs est la source de vérité tant que les clients sont connectés ; le plugin écrit sur le disque depuis Y (ex. nouveaux fichiers vus sur le web). Après une coupure, la reprise suit le merge Yjs habituel.
- Il n’y a pas de verrou « qui a raison » au niveau document : plusieurs éditeurs peuvent produire un texte fusionné caractère par caractère.
📋 Prérequis
- Node.js / npm (build du plugin en local)
- Obsidian (pour charger le plugin)
🛠️ Build et installation du plugin Obsidian
Le script build-plugin.ps1 compile le plugin et le copie dans le vault :
powershell -ExecutionPolicy Bypass -File .\build-plugin.ps1
Équivalent npm (sans copie vers le vault) :
npm run build:plugin
Fichiers copiés vers D:\Notes\.obsidian\plugins\markpad (par défaut) :
plugin/main.jsplugin/manifest.jsonplugin/versions.json
Options :
-VaultPluginsPath "D:\Notes\.obsidian\plugins"-PluginId "markpad"-SkipNpmInstall
Ensuite, dans Obsidian :
- Active le plugin communautaire
Markpad. - Configure
Server URL(ex.https://sync.atechnologie.fr), puis colle le jeton de connexion copié depuis Mon compte. - Ouvre une note et lance
Start Sharing.
Release publique du plugin (GitHub Actions) : voir Release-Plugin.ps1.
📦 Plugin Obsidian — catalogue
| Élément | Détail |
|---|---|
| ID plugin | markpad |
| Dépôt | github.com/Atelier-Recherche/markpad |
| Licence | MIT — LICENSE |
| Réseau | Oui — connexion WebSocket et API REST vers votre serveur Markpad (URL configurable). Aucune télémétrie vers des tiers. |
| Compte requis | Oui pour créer/gérer des partages via le serveur (JWT par e-mail sur le serveur Markpad) |
| Fichiers hors vault | Non (le serveur stocke les sessions Yjs ; voir doc infra) |
| Télémétrie / mise à jour auto | non |
🧪 Dev local (optionnel, sans Docker)
- Lint global :
npm run lint - Server dev :
npm run -w server dev(variables dans.envà la racine) - Web dev :
npm run -w web dev - Plugin watch :
npm run -w plugin dev
💾 Sauvegardes et durabilité des données
Les rôles du vault Obsidian, de Redis, de SQLite et les recommandations de sauvegarde sont décrits dans docs/strategies-sauvegarde.md.
📝 Notes
- La persistance des rooms est faite via Redis.
- Le frontend est servi en statique via Nginx.
- Le serveur exige un JWT (connexion par e-mail) pour créer ou supprimer des partages depuis le plugin ; un mot de passe optionnel protège chaque room.
- Sur le web, l’éditeur est le texte brut Markdown dans
Y.Text(comme Obsidian en mode source), ce qui aligne la collab avec le plugin.
🔗 Ressources
| 🌐 l’Atelier | atelier.atechnologie.fr |
| 📦 Dépôt | github.com/Atelier-Recherche/markpad |
📸 Vorschau
| Obsidian-Plugin | Web-Oberfläche |
|---|---|
![]() |
![]() |
📖 Über
Markpad ist ein Mono-Repo für Echtzeit-Zusammenarbeit an Markdown-Notizen mit 3 Komponenten:
plugin/: Obsidian-Plugin (Freigabe-Quelle)server/: Backend Node.js + Yjs + WebSocket + Redisweb/: Frontend React + CodeMirror 6 (Markdown-Quelle, Yjs-Collab) + HTML-Vorschau, Obsidian-ähnliche Werkzeugleiste (Lucide-Icons); die Baumansicht von Ordner-Freigaben zeigt Pfade relativ zum freigegebenen Ordner (ohne das vorgelagerte Vault-Präfix freizulegen).
⬇️ Obsidian-Plugin (BRAT)
- 🔌 BRAT installieren: Obsidian — BRAT
- ➕ Dieses Repository mit « Add Beta plugin » hinzufügen:
https://github.com/Atelier-Recherche/markpad
Plugin-ID:
markpad— Zielordner:<vault>/.obsidian/plugins/markpad/💡 Unsere Plugins können auf die Freigabe im Obsidian-Katalog warten; BRAT ermöglicht das Testen schon jetzt. Siehe auch 🌐 l’Atelier.
🏗️ Architektur
- Das Plugin erstellt eine Sitzung über die REST-API des Servers.
- Der Server liefert eine
roomIdund eine Freigabe-URL. - Plugin und Web verbinden sich mit dem WebSocket der Room.
- Yjs-Updates werden in Echtzeit weitergeleitet.
- Der Yjs-Zustand wird in Redis persistiert, um Sitzungen fortzusetzen.
- Das Plugin speichert
markpadShareim Frontmatter (roomId,shareUrl), um die Room beim Öffnen der Notiz wiederzufinden.
Obsidian-Plugin: Sync und Wiederverbindung
- Automatische Wiederverbindung (Plugin-Einstellung, standardmäßig aktiv): wenn die aktive Notiz bereits eine Freigabe hat und
User IDgesetzt ist, öffnet das Plugin die WebSocket-Sitzung beim Start / Notizwechsel, ohne „Start Sharing“ erneut auszuführen. - Abgleich nach dem ersten Sync: die Vault-
.md-Datei wird mit dem Yjs-Dokumentkörper (ohne Frontmatter) verglichen. Bei Abweichungen (Offline-Bearbeitung, leeres oder veraltetes Redis) wendet das Plugin Patch-Merges (diff-match-patch) in Yjs an, um Verluste zu begrenzen, statt eine Seite zu überschreiben. Ist Yjs leer und die lokale Notiz nicht, wird der lokale Inhalt erneut eingefügt (Anti-Leer-Schutz nach Redis-/Server-Neustart). - Statusleiste: zeigt u. a. verbunden, Synchronisation läuft oder offline.
- Indikatoren in Explorer und Tabs (Lucide über die Obsidian-API): Link-Icon für eine freigegebene Notiz; Ordner-Icon für die Wurzel einer Ordner-Freigabe. Für die aktive WebSocket-Sitzung spiegelt das Icon den Zustand wider (z. B. Laden während Verbindung oder Abgleich nach Sync, Aktualisieren wenn Yjs noch nicht aktuell ist, offline wenn der WS getrennt ist).
- Richtung Obsidian → Web: neben y-codemirror richtet ein kleines CodeMirror-Modul
Y.Textam Dokument aus, wenn lokale Eingabe und Y divergieren (umgeht Fälle, in denen die CM→Y-Bindung nicht propagiert, je nach Obsidian-/Live-Preview-Version). Die passendeEditorView-Instanz wird aufgelöst (Quelle,editorComponent, Bearbeitungs-Untermodus), bevor die Collab gemountet wird. - Fehlerdiagnose: in den Plugin-Einstellungen „Diagnose-Logs (Konsole)“ aktivieren, Entwicklerkonsole öffnen (Ctrl+Shift+I), nach
Markpad:collabfiltern: zeitgestempelte Ereignisse (CM-Auflösung, Brücke,Y.Doc update, WebSocket,editor-change). Bei Ordner-Freigabe detaillieren Zeilen mit Präfixfolder:den Start (startSharing,attach computed,Y.Map-Schlüssel,syncFolderFilesFromY, ausgehender WebSocket-Patch) — unverzichtbar, wenn Ordner-Sync oder Ordner-Icons Probleme machen.
🖥️ Infrastruktur (Produktion)
Das Server-Deployment läuft über Coolify und docker-compose.coolify.yml:
redis: temporärer Speicher für Yjs-Dokumenteserver: API + WebSocket (intern, Port 1234)web: Nginx-Frontend + API-Reverse-Proxy (über Traefik exponiert)
Wichtige Variablen (in der Coolify-UI konfiguriert; siehe .env.example als Referenz):
SERVER_PORT,SERVER_HOSTPUBLIC_SERVER_URL,PUBLIC_WEB_URL(gleiche URL in Prod, z. B.https://sync.atechnologie.fr)REDIS_URLSESSION_MAX_IDLE_MS— Dauer ohne Aktivität vor Room-Löschung (Standard ~1 Jahr)SESSION_CLEANUP_INTERVAL_MS— Häufigkeit der Bereinigung abgelaufener RoomsJWT_SECRET— Signatur der Web-Tokens (E-Mail-Anmeldung)ADMIN_EMAILS— kommagetrennte E-Mail-Liste mit Admin-Rolle bei erster AnmeldungMARKPAD_SQLITE_PATH— SQLite-Datei (Freigabe-Metadaten, Benutzer, Index)MAX_SHARES_PER_USER— maximale Anzahl aktiver Freigabe-Sitzungen pro Benutzer im SQLite-Index (Standard200); darüber antwortetPOST /sessionsmit 429 und{ "error": "share_limit_reached" }SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS,EMAIL_FROM— Versand von Magic Links (optional; ohne SMTP wird der Link in der Server-Konsole geloggt). Wichtig: der SMTP-Host ist in der Regel der des Mail-Anbieters (z. B.smtp.…,ssl0.ovh.netfür ein OVH-Postfach), nicht unbedingt der Domainname der Website allein; wenn Port 465 nicht verbindet, 587 mitSMTP_SECURE=false(STARTTLS) versuchen. Siehe auchSMTP_CONNECTION_TIMEOUT_MSin.env.example.
🌐 Web: Gast-Oberfläche
- Sprache: Französisch, Englisch, Spanisch, Deutsch (Auswahl in der oberen Leiste, gespeichert in
localStorage). - Anzeigename: gespeichert (
markpad-display-name). Hat die Room kein Passwort, erfolgt die Verbindung ohne „Beitreten“-Bildschirm. Rechtsklick auf den Chip „Sie“, um umzubenennen. - Paneele: Baum (Ordner-Freigabe), Editor, Vorschau, Gliederung — Breiten per Spaltengriffen anpassbar (bis ~72 % für Seitenpaneele). Bei einer Ordner-Freigabe werden Baumeinträge als relativer Pfad zum freigegebenen Ordner angezeigt; der vollständige Vault-Pfad bleibt als Tooltip (
title) auf jeder Datei verfügbar. - Nützliche Routen:
/share/:roomId(kollaboratives Bearbeiten),/auth/verify?token=…(nach dem E-Mail-Link),/me(Freigabeliste mit Web-Konto),/admin(Administration — siehe unten).
👤 Web-Konto und Freigabeliste
POST /auth/magic/requestmit{ "email": "sie@beispiel.com" }(oder die Seite/mein der Web-App nutzen, um die E-Mail einzugeben).- Den erhaltenen Link öffnen (oder den in den Server-Logs, wenn SMTP nicht konfiguriert ist):
/auth/verify?token=…liefert ein JWT und die Benutzer-ID; das Token wird im Browser gespeichert (localStorage, Schlüsselmarkpad-jwt). GET /me/shares(HeaderAuthorization: Bearer <JWT>) listet Freigaben, deren Eigentümer dieselbeUser IDist wie im Obsidian-Plugin konfiguriert (die nach der Verifizierung angezeigte ID in die Plugin-Einstellungen kopieren).
🛡️ Administratorkonto (Web)
ADMIN_EMAILSin Coolify setzen (oder.envfür lokale Entwicklung), oder einen Benutzer in der Datenbank befördern (is_adminauf der Tabelleusers).- Mit dem Magic Link anmelden wie jeder Benutzer (gleiche Adresse wie in
ADMIN_EMAILS, falls diese Methode genutzt wird). - Routen
/admin/shares,/admin/users,DELETE /admin/sessions/:roomIdusw. mit demselbenAuthorization: Bearer <JWT>wie für/meaufrufen. Die Frontend-Seite/adminzeigt eine JSON-Übersicht (nach Bedarf zu erweitern).
📂 Obsidian-Plugin: Ordner-Freigabe und Freigabe-Panel
- Ordner freigeben: Rechtsklick auf den Ordner im Explorer → „Markpad: diesen Ordner freigeben“. Eine Datei
.markpad-folder-share.mdwird im Ordner erstellt;.md-Notizen im Ordner werden in derselben Yjs-Room synchronisiert (Y.Mapfiles). - Bereits freigegebener Ordner: dasselbe Kontextmenü bietet Link kopieren und Ordner-Freigabe beenden. Die Ordnerzeile im Explorer zeigt auch ein Icon (Klick zum Kopieren des Ordner-Links).
- Seitenpanel: Befehl „Freigabe-Panel öffnen“ — listet alle Freigaben (Notizen und Ordner) mit Link kopieren und Freigabe entfernen (serverseitig + lokale Bereinigung). Die Liste aktualisiert sich, wenn Frontmatter oder Anker sich ändern (auch nach Entfernen aus dem Panel).
- Löschen im Vault: Löschen des Ankers
.markpad-folder-share.md, eines Freigabe-Wurzel-Ordners oder einer Datei in einer Ordner-Freigabe aktualisiert oder beendet die Freigabe auf Plugin- und Serverseite (je nach Fall: Ordner stoppen, Datei entfernen oder letzte Notiz → Room-Ende). Bei einer freigegebenen Einzelnotiz beendet das Löschen der Datei auch die Serversitzung (Best Effort). - Umbenennen / Verschieben: Umbenennen oder Verschieben eines Ordners oder einer Notiz innerhalb einer Ordner-Freigabe aktualisiert Pfade in lokaler Meta, Anker und — wenn eine Ordner-Sitzung offen ist — die Schlüssel des
Y.Mapfiles, damit sie mit dem Web ausgerichtet bleiben. - Rechtsklick auf eine Notiz in einem freigegebenen Ordner: Freigabe beenden löscht nicht mehr fälschlich die ganze Room: die Datei wird aus der Ordner-Freigabe entfernt; die Room schließt erst, wenn keine Dateien mehr übrig sind.
- Einstellungen: Schaltfläche „Bereinigen…“ (unten auf der Seite) — nach Bestätigung Stopp der Ordner-Freigaben, Löschen verbleibender
.markpad-folder-share.md-Dateien und Zurücksetzen des lokalen Zustands (Serversitzungen möglichst beendet).
🔄 Desynchronisation und Wiederverbindung (Erinnerung)
- Einzelnotiz, Obsidian-Autor: nach Wiederverbindung kann ein Abgleich die lokale
.mdmitY.Textmergen (siehe oben): bei starker Divergenz ist das Ergebnis ein zeichenweises CRDT, keine „intelligente“ Markdown-Auflösung. - Notiz aus Obsidian beigetreten („join“): das Entfernte hat Vorrang beim ersten Sync; der lokale Entwurf kann überschrieben werden.
- Ordner-Freigabe: für den Inhalt der Dateien ist Yjs die Wahrheitsquelle, solange Clients verbunden sind; das Plugin schreibt von Y auf die Festplatte (z. B. neue Dateien aus dem Web). Nach einer Unterbrechung folgt die Fortsetzung dem üblichen Yjs-Merge.
- Es gibt kein Dokument-Sperre „wer hat recht“: mehrere Editoren können zeichenweise gemergten Text erzeugen.
📋 Voraussetzungen
- Node.js / npm (lokaler Plugin-Build)
- Obsidian (zum Laden des Plugins)
🛠️ Build und Installation des Obsidian-Plugins
Das Skript build-plugin.ps1 kompiliert das Plugin und kopiert es in den Vault:
powershell -ExecutionPolicy Bypass -File .\build-plugin.ps1
npm-Äquivalent (ohne Kopie in den Vault):
npm run build:plugin
Kopierte Dateien nach D:\Notes\.obsidian\plugins\markpad (Standard):
plugin/main.jsplugin/manifest.jsonplugin/versions.json
Optionen:
-VaultPluginsPath "D:\Notes\.obsidian\plugins"-PluginId "markpad"-SkipNpmInstall
Danach in Obsidian:
- Community-Plugin
Markpadaktivieren. Server URLkonfigurieren (z. B.https://sync.atechnologie.fr), dann das aus Mein Konto kopierte Anmelde-Token einfügen.- Eine Notiz öffnen und
Start Sharingausführen.
Öffentliche Plugin-Release (GitHub Actions): siehe Release-Plugin.ps1.
📦 Obsidian-Plugin — Katalog
| Element | Detail |
|---|---|
| Plugin-ID | markpad |
| Repository | github.com/Atelier-Recherche/markpad |
| Lizenz | MIT — LICENSE |
| Netzwerk | Ja — WebSocket- und REST-API-Verbindung zu Ihrem Markpad-Server (konfigurierbare URL). Keine Telemetrie an Dritte. |
| Konto erforderlich | Ja zum Erstellen/Verwalten von Freigaben über den Server (E-Mail-JWT auf dem Markpad-Server) |
| Dateien außerhalb des Vaults | Nein (der Server speichert Yjs-Sitzungen; siehe Infra-Doku) |
| Telemetrie / Auto-Update | nein |
🧪 Lokale Entwicklung (optional, ohne Docker)
- Globales Lint:
npm run lint - Server-Dev:
npm run -w server dev(Variablen in der Root-.env) - Web-Dev:
npm run -w web dev - Plugin-Watch:
npm run -w plugin dev
💾 Backups und Datendauerhaftigkeit
Rollen von Obsidian-Vault, Redis, SQLite und Backup-Empfehlungen sind in docs/strategies-sauvegarde.md beschrieben.
📝 Hinweise
- Die Persistenz der Rooms erfolgt über Redis.
- Das Frontend wird als Statik über Nginx ausgeliefert.
- Der Server verlangt ein JWT (E-Mail-Anmeldung), um Freigaben aus dem Plugin zu erstellen oder zu löschen; ein optionales Passwort schützt jede Room.
- Im Web ist der Editor rohes Markdown in
Y.Text(wie Obsidian im Quellmodus), was die Collab mit dem Plugin ausrichtet.
🔗 Ressourcen
| 🌐 l’Atelier | atelier.atechnologie.fr |
| 📦 Repository | github.com/Atelier-Recherche/markpad |
📸 Preview
| Obsidian plugin | Web interface |
|---|---|
![]() |
![]() |
📖 About
Markpad is a real-time collaboration mono-repo for Markdown notes with 3 components:
plugin/: Obsidian plugin (sharing source)server/: Node.js + Yjs + WebSocket + Redis backendweb/: React + CodeMirror 6 frontend (source markdown, Yjs collab) + HTML preview, Obsidian-style toolbar (Lucide icons); the folder share tree shows paths relative to the shared folder (without exposing the vault’s upstream prefix).
⬇️ Obsidian plugin (BRAT)
- 🔌 Install BRAT: Obsidian — BRAT
- ➕ Add this repository with « Add Beta plugin »:
https://github.com/Atelier-Recherche/markpad
Plugin ID:
markpad— target folder:<vault>/.obsidian/plugins/markpad/💡 Our plugins may be pending Obsidian catalogue approval; BRAT lets you try them now. See also 🌐 l’Atelier.
🏗️ Architecture
- The plugin creates a session via the server’s REST API.
- The server returns a
roomIdand a share URL. - Plugin and Web connect to the room’s WebSocket.
- Yjs updates are relayed in real time.
- Yjs state is persisted in Redis for session resume.
- The plugin stores
markpadSharein the frontmatter (roomId,shareUrl) so the room can be found when the note is opened.
Obsidian plugin: sync and reconnection
- Automatic reconnection (plugin setting, enabled by default): if the active note already has a share and
User IDis set, the plugin opens the WebSocket session on startup / note change, without needing to run “Start Sharing” again. - Reconciliation after the first sync: the vault
.mdfile is compared to the Yjs document body (excluding frontmatter). On divergence (offline edits, empty or stale Redis), the plugin merges with patches (diff-match-patch) into Yjs to limit data loss instead of overwriting one side. If Yjs is empty and the local note is not, local content is reinjected (anti-empty protection after Redis/server restart). - Status bar: shows connected, syncing, or offline, among other states.
- Explorer and tab indicators (Lucide via the Obsidian API): link icon for a shared note; folders icon for a folder-share root. For the active WebSocket session, the icon reflects state (e.g. loading during connect or post-sync reconciliation, refresh if Yjs is not yet up to date, offline if the WS is down).
- Obsidian → Web direction: besides y-codemirror, a small CodeMirror module realigns
Y.Textwith the document when local typing and Y diverge (works around cases where the CM→Y binding does not propagate, depending on Obsidian / Live Preview version). The correctEditorViewinstance is resolved (source,editorComponent, edit sub-mode) before mounting collab. - Troubleshooting: in plugin settings, enable “Diagnostic logs (console)”, open the developer console (Ctrl+Shift+I), filter on
Markpad:collab: you will see timestamped events (CM resolution, bridge,Y.Doc update, WebSocket,editor-change). For folder share, lines prefixedfolder:detail startup (startSharing,attach computed,Y.Mapkeys,syncFolderFilesFromY, outgoing WebSocket patch) — essential if folder sync or folder icons misbehave.
🖥️ Infrastructure (production)
Server deployment goes through Coolify and docker-compose.coolify.yml:
redis: temporary storage for Yjs documentsserver: API + WebSocket (internal, port 1234)web: Nginx frontend + API reverse proxy (exposed via Traefik)
Main variables (set in the Coolify UI; see .env.example as reference):
SERVER_PORT,SERVER_HOSTPUBLIC_SERVER_URL,PUBLIC_WEB_URL(same URL in prod, e.g.https://sync.atechnologie.fr)REDIS_URLSESSION_MAX_IDLE_MS— idle duration before room deletion (default ~1 year)SESSION_CLEANUP_INTERVAL_MS— how often expired rooms are cleaned upJWT_SECRET— web token signing (email login)ADMIN_EMAILS— comma-separated email list granted admin role on first loginMARKPAD_SQLITE_PATH— SQLite file (share metadata, users, index)MAX_SHARES_PER_USER— max active share sessions per user in the SQLite index (default200); beyond that,POST /sessionsreturns 429 with{ "error": "share_limit_reached" }SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS,EMAIL_FROM— magic-link delivery (optional; without SMTP the link is logged in the server console). Important: the SMTP host is usually that of the mail provider (e.g.smtp.…,ssl0.ovh.netfor an OVH mailbox), not necessarily the site domain alone; if port 465 fails to connect, try 587 withSMTP_SECURE=false(STARTTLS). See alsoSMTP_CONNECTION_TIMEOUT_MSin.env.example.
🌐 Web: guest interface
- Language: French, English, Spanish, German (selector in the top bar, stored in
localStorage). - Display name: stored (
markpad-display-name). If the room has no password, connection skips the “Join” screen. Right-click the “You” chip to rename. - Panels: tree (folder share), editor, preview, outline — resizable with column handles (up to ~72% for side panels). For a folder share, tree entries show a relative path to the shared folder; the full vault path remains available as a tooltip (
title) on each file. - Useful routes:
/share/:roomId(collaborative editing),/auth/verify?token=…(after the email link),/me(share list with a web account),/admin(administration — see below).
👤 Web account and share list
POST /auth/magic/requestwith{ "email": "you@example.com" }(or use the/mepage in the web app to enter the email).- Open the received link (or the one shown in server logs if SMTP is not configured):
/auth/verify?token=…returns a JWT and the user id; the token is stored in the browser (localStorage, keymarkpad-jwt). GET /me/shares(Authorization: Bearer <JWT>header) lists shares whose owner is the sameUser IDas configured in the Obsidian plugin (copy the id shown after verification into the plugin settings).
🛡️ Admin account (web)
- Set
ADMIN_EMAILSin Coolify (or.envfor local dev), or promote a user in the database (is_adminfield on theuserstable). - Sign in with the magic link like any user (same address as in
ADMIN_EMAILSif you use that method). - Call
/admin/shares,/admin/users,DELETE /admin/sessions/:roomId, etc. with the sameAuthorization: Bearer <JWT>as for/me. The frontend/adminpage shows a JSON overview (to be extended as needed).
📂 Obsidian plugin: folder share and shares panel
- Share a folder: right-click the folder in the explorer → “Markpad: share this folder”. A
.markpad-folder-share.mdfile is created in the folder;.mdnotes in the folder sync into one Yjs room (Y.Mapfiles). - Already shared folder: the same context menu offers copy link and stop folder sharing. The folder row in the explorer also shows an icon (click to copy the folder link).
- Side panel: command “Open shares panel” — lists all shares (notes and folders) with copy link and remove share (server side + local cleanup). The list updates when frontmatter or the anchor changes (including after removal from the panel).
- Deletion in the vault: deleting the
.markpad-folder-share.mdanchor, a share-root folder, or a file included in a folder share updates or ends the share on plugin and server (depending on the case: stop folder, remove a file, or last note → end room). For a shared single note, deleting the file also ends the server session (best effort). - Rename / move: renaming or moving a folder or note inside a folder share updates paths in local meta, the anchor, and, if a folder session is open, the
Y.Mapfileskeys so they stay aligned with the web. - Right-click a note in a shared folder: stop sharing no longer wrongly deletes the whole room: the file is removed from the folder share; the room closes only when no files remain.
- Settings: “Purge…” button (bottom of the page) — after confirmation, stops folder shares, deletes remaining
.markpad-folder-share.mdfiles and resets local state (server sessions ended when possible).
🔄 Desync and reconnection (reminder)
- Single note, Obsidian author: after reconnect, reconciliation may merge the local
.mdwithY.Text(see above): on strong divergence, the result is a character-level CRDT, not “smart” markdown resolution. - Note joined from Obsidian (“join”): the remote wins on first sync; the local draft may be overwritten.
- Folder share: for file content, Yjs is the source of truth while clients are connected; the plugin writes to disk from Y (e.g. new files seen on the web). After a disconnect, resume follows the usual Yjs merge.
- There is no document-level “who is right” lock: multiple editors can produce character-by-character merged text.
📋 Prerequisites
- Node.js / npm (local plugin build)
- Obsidian (to load the plugin)
🛠️ Build and install the Obsidian plugin
The build-plugin.ps1 script compiles the plugin and copies it into the vault:
powershell -ExecutionPolicy Bypass -File .\build-plugin.ps1
npm equivalent (no copy to the vault):
npm run build:plugin
Files copied to D:\Notes\.obsidian\plugins\markpad (default):
plugin/main.jsplugin/manifest.jsonplugin/versions.json
Options:
-VaultPluginsPath "D:\Notes\.obsidian\plugins"-PluginId "markpad"-SkipNpmInstall
Then, in Obsidian:
- Enable the community plugin
Markpad. - Set
Server URL(e.g.https://sync.atechnologie.fr), then paste the login token copied from My account. - Open a note and run
Start Sharing.
Public plugin release (GitHub Actions): see Release-Plugin.ps1.
📦 Obsidian plugin — catalogue
| Item | Detail |
|---|---|
| Plugin ID | markpad |
| Repository | github.com/Atelier-Recherche/markpad |
| Licence | MIT — LICENSE |
| Network | Yes — WebSocket and REST API connection to your Markpad server (configurable URL). No third-party telemetry. |
| Account required | Yes to create/manage shares via the server (email JWT on the Markpad server) |
| Files outside vault | No (the server stores Yjs sessions; see infra docs) |
| Telemetry / auto-update | no |
🧪 Local dev (optional, without Docker)
- Global lint:
npm run lint - Server dev:
npm run -w server dev(variables in root.env) - Web dev:
npm run -w web dev - Plugin watch:
npm run -w plugin dev
💾 Backups and data durability
Roles of the Obsidian vault, Redis, SQLite, and backup recommendations are described in docs/strategies-sauvegarde.md.
📝 Notes
- Room persistence uses Redis.
- The frontend is served as static files via Nginx.
- The server requires a JWT (email login) to create or delete shares from the plugin; an optional password protects each room.
- On the web, the editor is raw Markdown in
Y.Text(like Obsidian source mode), which aligns collab with the plugin.
🔗 Resources
| 🌐 l’Atelier | atelier.atechnologie.fr |
| 📦 Repository | github.com/Atelier-Recherche/markpad |
📸 Vista previa
| Plugin Obsidian | Interfaz web |
|---|---|
![]() |
![]() |
📖 Acerca de
Markpad es un mono-repo de colaboración en tiempo real para notas Markdown con 3 componentes:
plugin/: plugin Obsidian (origen del compartido)server/: backend Node.js + Yjs + WebSocket + Redisweb/: frontend React + CodeMirror 6 (markdown fuente, collab Yjs) + vista previa HTML, barra de herramientas tipo Obsidian (iconos Lucide); el árbol de compartidos de carpeta muestra rutas relativas a la carpeta compartida (sin exponer el prefijo «anterior» del vault).
⬇️ Plugin Obsidian (BRAT)
- 🔌 Instalar BRAT: Obsidian — BRAT
- ➕ Añadir este repositorio con la opción « Add Beta plugin »:
https://github.com/Atelier-Recherche/markpad
ID del plugin:
markpad— carpeta destino:<vault>/.obsidian/plugins/markpad/💡 Nuestros plugins pueden estar pendientes de validación en el catálogo de Obsidian; BRAT permite probarlos ya. Ver también 🌐 l’Atelier.
🏗️ Arquitectura
- El plugin crea una sesión mediante la API REST del servidor.
- El servidor devuelve un
roomIdy una URL de compartido. - Plugin y Web se conectan al WebSocket de la room.
- Las actualizaciones Yjs se retransmiten en tiempo real.
- El estado Yjs se persiste en Redis para reanudar la sesión.
- El plugin guarda
markpadShareen el frontmatter (roomId,shareUrl) para recuperar la room al abrir la nota.
Plugin Obsidian: sync y reconexión
- Reconexión automática (ajuste del plugin, activado por defecto): si la nota activa ya tiene un compartido y
User IDestá definido, el plugin abre la sesión WebSocket al inicio / al cambiar de nota, sin volver a lanzar « Start Sharing ». - Reconciliación tras el primer sync: el archivo
.mddel vault se compara con el cuerpo del documento Yjs (sin frontmatter). En caso de divergencias (edición offline, Redis vacío o antiguo), el plugin aplica una fusión por parches (diff-match-patch) en Yjs para limitar pérdidas en lugar de sobrescribir un lado. Si Yjs está vacío y la nota local no, se reinyecta el contenido local (protección anti-vacío tras reinicio de Redis/servidor). - Barra de estado: muestra entre otros conectado, sincronización en curso u offline.
- Indicadores en el explorador y las pestañas (Lucide vía la API de Obsidian): icono de enlace para una nota compartida; icono de carpetas para la raíz de un compartido de carpeta. Para la sesión WebSocket activa, el icono refleja el estado (p. ej. carga durante la conexión o la reconciliación post-sync, actualización si Yjs aún no está al día, offline si el WS está cortado).
- Sentido Obsidian → Web: además de y-codemirror, un pequeño módulo CodeMirror realinea
Y.Textcon el documento si la escritura local e Y divergen (sortea casos en que el binding CM→Y no propaga, según la versión de Obsidian / Live Preview). Se resuelve la instancia correcta deEditorView(fuente,editorComponent, submodo de edición) antes de montar la collab. - Diagnóstico: en los ajustes del plugin, activar « Logs de diagnóstico (consola) », abrir la consola de desarrollador (Ctrl+Shift+I), filtrar por
Markpad:collab: verás eventos con marca de tiempo (resolución CM, puente,Y.Doc update, WebSocket,editor-change). En compartido de carpeta, las líneas con prefijofolder:detallan el arranque (startSharing,attach computed, claves delY.Map,syncFolderFilesFromY, parche WebSocket saliente) — imprescindible si la sync de carpeta o los iconos de carpeta fallan.
🖥️ Infraestructura (producción)
El despliegue del servidor pasa por Coolify y docker-compose.coolify.yml:
redis: almacenamiento temporal de documentos Yjsserver: API + WebSocket (interno, puerto 1234)web: frontend Nginx + reverse proxy API (expuesto vía Traefik)
Variables principales (configuradas en la UI de Coolify; ver .env.example como referencia):
SERVER_PORT,SERVER_HOSTPUBLIC_SERVER_URL,PUBLIC_WEB_URL(misma URL en prod, p. ej.https://sync.atechnologie.fr)REDIS_URLSESSION_MAX_IDLE_MS— duración sin actividad antes de eliminar la room (por defecto ~1 año)SESSION_CLEANUP_INTERVAL_MS— frecuencia de limpieza de rooms caducadasJWT_SECRET— firma de los tokens web (inicio de sesión por correo)ADMIN_EMAILS— lista de correos (separados por comas) con rol admin en el primer inicio de sesiónMARKPAD_SQLITE_PATH— archivo SQLite (metadatos de compartidos, usuarios, índice)MAX_SHARES_PER_USER— número máximo de sesiones de compartido activas por usuario en el índice SQLite (por defecto200); más allá,POST /sessionsresponde 429 con{ "error": "share_limit_reached" }SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS,EMAIL_FROM— envío de enlaces mágicos (opcional; sin SMTP el enlace se registra en la consola del servidor). Importante: el host SMTP suele ser el del proveedor de correo (p. ej.smtp.…,ssl0.ovh.netpara un buzón OVH), no necesariamente el nombre de dominio del sitio solo; si el puerto 465 no conecta, pruebe 587 conSMTP_SECURE=false(STARTTLS). Ver tambiénSMTP_CONNECTION_TIMEOUT_MSen.env.example.
🌐 Web: interfaz de invitado
- Idioma: francés, inglés, español, alemán (selector en la barra superior, memorizado en
localStorage). - Nombre mostrado: memorizado (
markpad-display-name). Si la room no tiene contraseña, la conexión se hace sin pantalla « Unirse ». Clic derecho en la pastilla « Usted » para renombrar. - Paneles: árbol (compartido de carpeta), editor, vista previa, esquema — anchos ajustables con asas entre columnas (hasta ~72 % para paneles laterales). En un compartido de carpeta, las entradas del árbol se muestran como ruta relativa a la carpeta compartida; la ruta completa del vault sigue disponible en información emergente (
title) en cada archivo. - Rutas útiles:
/share/:roomId(edición colaborativa),/auth/verify?token=…(tras el enlace recibido por correo),/me(lista de compartidos con cuenta web),/admin(administración — ver más abajo).
👤 Cuenta web y lista de compartidos
POST /auth/magic/requestcon{ "email": "usted@ejemplo.com" }(o usar la página/meen la app web para introducir el correo).- Abrir el enlace recibido (o el mostrado en los logs del servidor si SMTP no está configurado):
/auth/verify?token=…devuelve un JWT y el id de usuario; el token se almacena en el navegador (localStorage, clavemarkpad-jwt). GET /me/shares(cabeceraAuthorization: Bearer <JWT>) lista los compartidos cuyo propietario es el mismoUser IDconfigurado en el plugin Obsidian (copiar el id mostrado tras la verificación en los ajustes del plugin).
🛡️ Cuenta de administrador (web)
- Indicar
ADMIN_EMAILSen Coolify (o.enven desarrollo local), o promover un usuario en la base (campois_adminen la tablausers). - Iniciar sesión con el enlace mágico como cualquier usuario (misma dirección que en
ADMIN_EMAILSsi usa ese método). - Llamar a las rutas
/admin/shares,/admin/users,DELETE /admin/sessions/:roomId, etc. con el mismoAuthorization: Bearer <JWT>que para/me. La página/admindel frontend muestra una vista previa JSON (a completar según sus necesidades).
📂 Plugin Obsidian: compartido de carpeta y panel de compartidos
- Compartir una carpeta: clic derecho en la carpeta del explorador → « Markpad: compartir esta carpeta ». Se crea un archivo
.markpad-folder-share.mden la carpeta; las notas.mdde la carpeta se sincronizan en una misma room Yjs (Y.Mapfiles). - Carpeta ya compartida: el mismo menú contextual ofrece copiar el enlace y detener el compartido de carpeta. La fila de la carpeta en el explorador también muestra un icono (clic para copiar el enlace de la carpeta).
- Panel lateral: comando « Abrir el panel de compartidos » — lista todos los compartidos (notas y carpetas) con copiar el enlace y eliminar el compartido (lado servidor + limpieza local). La lista se actualiza cuando cambia el frontmatter o el ancla (también tras eliminar desde el panel).
- Eliminación en el vault: eliminar el ancla
.markpad-folder-share.md, una carpeta raíz de compartido o un archivo incluido en un compartido de carpeta actualiza o termina el compartido en plugin y servidor (según el caso: parar la carpeta, retirar un archivo o última nota → fin de room). Para una nota sola compartida, eliminar el archivo también termina la sesión del servidor (mejor esfuerzo). - Renombrar / mover: renombrar o mover una carpeta o una nota dentro de un compartido de carpeta actualiza las rutas en la meta local, el ancla y, si hay una sesión de carpeta abierta, las claves del
Y.Mapfilespara seguir alineado con la web. - Clic derecho en una nota de una carpeta compartida: detener el compartido ya no elimina por error toda la room: el archivo se retira del compartido de carpeta; la room solo se cierra si no queda ningún archivo.
- Ajustes: botón « Purgar… » (sección al final de la página) — tras confirmación, detiene los compartidos de carpeta, elimina los archivos
.markpad-folder-share.mdrestantes y reinicia el estado local (sesiones de servidor terminadas si es posible).
🔄 Desincronización y reconexión (recordatorio)
- Nota sola, autor Obsidian: tras la reconexión, una reconciliación puede fusionar el
.mdlocal con elY.Text(ver más arriba): en caso de fuerte divergencia, el resultado es un CRDT a nivel de caracteres, no una resolución « inteligente » del markdown. - Nota unida desde Obsidian (« join »): lo remoto prevalece en el primer sync; el borrador local puede sobrescribirse.
- Compartido de carpeta: para el contenido de los archivos, Yjs es la fuente de verdad mientras los clientes están conectados; el plugin escribe en disco desde Y (p. ej. archivos nuevos vistos en la web). Tras un corte, la reanudación sigue el merge Yjs habitual.
- No hay un bloqueo de « quién tiene razón » a nivel de documento: varios editores pueden producir un texto fusionado carácter a carácter.
📋 Requisitos previos
- Node.js / npm (build local del plugin)
- Obsidian (para cargar el plugin)
🛠️ Build e instalación del plugin Obsidian
El script build-plugin.ps1 compila el plugin y lo copia en el vault:
powershell -ExecutionPolicy Bypass -File .\build-plugin.ps1
Equivalente npm (sin copia al vault):
npm run build:plugin
Archivos copiados a D:\Notes\.obsidian\plugins\markpad (por defecto):
plugin/main.jsplugin/manifest.jsonplugin/versions.json
Opciones:
-VaultPluginsPath "D:\Notes\.obsidian\plugins"-PluginId "markpad"-SkipNpmInstall
Después, en Obsidian:
- Activa el plugin comunitario
Markpad. - Configura
Server URL(p. ej.https://sync.atechnologie.fr), luego pega el token de conexión copiado desde Mi cuenta. - Abre una nota y lanza
Start Sharing.
Release pública del plugin (GitHub Actions): ver Release-Plugin.ps1.
📦 Plugin Obsidian — catálogo
| Elemento | Detalle |
|---|---|
| ID plugin | markpad |
| Repositorio | github.com/Atelier-Recherche/markpad |
| Licencia | MIT — LICENSE |
| Red | Sí — conexión WebSocket y API REST hacia su servidor Markpad (URL configurable). Sin telemetría hacia terceros. |
| Cuenta requerida | Sí para crear/gestionar compartidos vía el servidor (JWT por correo en el servidor Markpad) |
| Archivos fuera del vault | No (el servidor almacena las sesiones Yjs; ver doc infra) |
| Telemetría / actualización auto | no |
🧪 Dev local (opcional, sin Docker)
- Lint global:
npm run lint - Server dev:
npm run -w server dev(variables en.enven la raíz) - Web dev:
npm run -w web dev - Plugin watch:
npm run -w plugin dev
💾 Copias de seguridad y durabilidad de los datos
Los roles del vault Obsidian, Redis, SQLite y las recomendaciones de copia de seguridad se describen en docs/strategies-sauvegarde.md.
📝 Notas
- La persistencia de las rooms se hace vía Redis.
- El frontend se sirve en estático vía Nginx.
- El servidor exige un JWT (inicio de sesión por correo) para crear o eliminar compartidos desde el plugin; una contraseña opcional protege cada room.
- En la web, el editor es el texto Markdown en bruto en
Y.Text(como Obsidian en modo fuente), lo que alinea la collab con el plugin.
🔗 Recursos
| 🌐 l’Atelier | atelier.atechnologie.fr |
| 📦 Repositorio | github.com/Atelier-Recherche/markpad |


