Clepit
Auf einen Blick
- Kategorie
- Entwicklerplattform
- Website
- clepit.com
- Konsole
- app.clepit.com
- Dokumentation
- clepit.com/en/docs
- GraphQL-API
- api.clepit.com/graphql
- Echtzeit
- ws.clepit.com
- MCP-Schnittstelle
- api.clepit.com/mcp
- Veröffentlichte Seiten
- clepit.space
Formatierter Text landet meist als ein Block HTML in der Datenbank. Das genügt, solange Sie nichts anderes damit vorhaben, als ihn wieder anzuzeigen: jede Seite finden, die eine Kundin erwähnt, dasselbe Dokument als Webseite, als E-Mail und in einer mobilen App darstellen, oder zwei Personen gleichzeitig daran schreiben lassen, ohne dass einer von ihnen einen Absatz verliert. Dann sind Wörter und Formatierung längst ineinander verwoben, und das Einzige, was das Dokument noch zuverlässig lesen kann, ist der Editor, der es geschrieben hat.
Clepit hält beides auseinander. Eine Seite ist eine Liste von Blöcken, jeder ein kleines typisiertes Objekt, und das Dokument ist JSON: kein Markup, das geparst werden müsste, und kein Editor, um es zu lesen. Genau deshalb steht der Editor auch für sich allein. @clepit/core erscheint auf npm unter MIT-Lizenz und weiß nichts von der gehosteten Seite, während der Arbeitsbereich unter app.clepit.com das ist, was aus denselben Dokumenten wird, sobald sie Mitarbeitende, Berechtigungen, eine Versionsgeschichte und eine Adresse im öffentlichen Web bekommen.
Eine Seite ist eine Liste von Blöcken
Ein Block besteht aus vier Feldern: einer Kennung, einem Typ, den Daten, die dieser Typ festlegt, und den auf ihn angewandten Einstellungen. Die Daten eines Absatzes haben eine andere Form als die einer Tabelle, und der Typ sagt Ihnen, welche Form zu erwarten ist, sodass ein gespeichertes Dokument geprüft und nicht bloß geparst werden kann. Eine ganze Seite ist ein Zeitstempel, eine Version und die Blöcke in ihrer Reihenfolge, klein genug, um sie mit eigenen Augen zu lesen und in einem Pull Request durchzusehen. Nichts darin beschreibt, wie die Seite aussehen soll: das gehört dem, was sie zeichnet, und genau deshalb kann ein Dokument zur Webseite, zur E-Mail und zum Handybildschirm werden, ohne dass es davon drei Kopien gibt.
Ein Dokument ohne Browser zeichnen
Der Renderer schreibt nie direkt in den Browser. Er zeichnet über eine dünne Schicht, hinter der zwei Unterlagen stehen: eine baut echte Seitenknoten, die andere baut eine Zeichenkette, und derselbe Blockcode läuft auf beiden. So wird eine veröffentlichte Seite auf einem Server gezeichnet, auf dem es gar keinen Browser gibt, und deshalb ist das, was der Server erzeugt, dasselbe Dokument, das der Editor gezeigt hätte, und keine zweite Umsetzung, die mit der Zeit auseinanderläuft. Die Zeichenketten-Unterlage verlangt einen Bereiniger als Pflichtargument und hat bewusst keinen Standardwert: der übliche Bereiniger des Pakets baut zum Parsen erst eine Seite auf, kann dort also nicht laufen, und ein stilles Ausweichen auf Maskierung würde jedem Dokument die Formatierung im Text nehmen, ohne es zu sagen. Die Lücke bleibt daher dort sichtbar, wo sie verwendet wird, statt sich in einem Standardwert zu verstecken.
Lesen kostet die Lesenden kein JavaScript
Der React-Adapter liefert seine beiden Hälften getrennt aus, weil sie Gegensätzliches wollen. Die Inhaltskomponente läuft auf dem Server und gibt fertiges Markup aus, während die Seite gebaut wird, sodass Lesende das Dokument schon in der ersten Antwort erhalten. Die Editorkomponente läuft ausschließlich im Client, denn sie trägt den Lebenszyklus des Editors, und vor dem Browser gibt es nichts zu tragen. Ein Clepit-Dokument zu lesen braucht daher überhaupt kein JavaScript. Beim Schreiben kommt die Laufzeit hinzu.
Was eine Seite fassen kann
Sechsundzwanzig Blockarten liegen dem Paket bei. Die meisten sind jene, die jeder Editor braucht: Überschriften, Absätze, Listen, Checklisten, Zitate, Code, Tabellen, Bilder, Audio, Video, Dateien, Hinweiskästen und Trenner. Die übrigen gibt es, weil Dokumentation Dinge verlangt, die ein Schreibwerkzeug für gewöhnlich übergeht. Ein Inhaltsverzeichnis, das sich aus den Überschriften des Dokuments selbst aufbaut und auf jede einzelne verweist. Einklappbare Abschnitte und Spalten. Eine Karte, die für eine andere Seite einsteht. Eine freihändige Skizze. Und ein Aktivitätsblock, der speichert, welches Dokument zu beobachten ist, und nicht eine Kopie von dessen Aktivität, sodass er weiter zeigt, was gerade geschieht, statt am Tag des Einfügens zu erstarren. Die Formatierung innerhalb eines Blocks umfasst die üblichen Auszeichnungen, fett, kursiv, unterstrichen, durchgestrichen, Code im Fließtext, Hervorhebung und Links, dazu Kurzhinweise, Statuskennzeichen und Erwähnungen. Das Paket selbst hat keinerlei Laufzeitabhängigkeiten.
Formeln und Diagramme, im Paket gezeichnet
Zwei dieser Blöcke stellen LaTeX und Mermaid dar, und beide erledigen die ganze Arbeit im Paket: die Quelle parsen, das Layout berechnen, das Ergebnis zeichnen. Darunter liegt keine Zeichenbibliothek, und es geht kein Aufruf an einen Dienst hinaus, der aus einer Formel oder einem Flussdiagramm ein Bild machen soll. Das ist weniger eine Vorliebe in Sachen Abhängigkeiten als eine Folge der Zeichenketten-Unterlage. Ein Block, der nach etwas griffe, das nur ein Browser bereitstellt, oder nach dem Netz, ließe sich auf dem Server, der veröffentlichte Seiten ausliefert, gar nicht zeichnen, und dieselbe Seite sähe dann je nachdem, wer sie anfordert, anders aus.
Eine API-Referenz, die in der Seite wohnt
Geben Sie dem OpenAPI-Block eine Spezifikation, eingefügt oder über eine Adresse benannt, und er zeichnet, was diese Spezifikation beschreibt: die Operationen, ihre Pfade und Parameter, die Schemata für Anfrage und Antwort und das Verfahren zur Authentifizierung. Zu jeder Operation baut er außerdem ein Anfragebeispiel in cURL, TypeScript, Dart und Python, erzeugt aus der Spezifikation statt getippt von einer Autorin, die das Aktualisieren vergessen wird. Der Einbettungsblock sieht die Außenwelt genauso: Er erkennt eine Handvoll Dienste, die er wirklich darstellen kann, behandelt für alles Übrige einen schlichten Link als ordentliches Ergebnis und nicht als Fehlschlag, und weigert sich rundheraus, eine Adresse zu zeichnen, der er nicht traut.
Zwei Menschen im selben Absatz
Eine Seite, die gerade bearbeitet wird, hält auf dem Server eine einzige Aufgabe, eine je Seite, und jede Änderung läuft der Reihe nach durch sie hindurch. Genau das macht gleichzeitiges Bearbeiten überhaupt durchdenkbar: es gibt keinen zweiten Schreiber, der mit dem ersten um die Wette läuft. Das Dokument selbst ist ein CRDT, also verschmelzen zwei Personen, die im selben Absatz tippen, statt einander zu überschreiben, und ein zurückgefallener Client holt auf, indem beide Seiten austauschen, was der jeweils anderen fehlt. Jede Änderung wird in ein Write-Ahead-Log geschrieben, bevor sie überhaupt jemandem zugestellt wird: was die übrigen Anwesenden im Dokument sehen, ist somit bereits dauerhaft festgehalten und nicht bloß weitergereicht. Berechtigungen setzt der Server durch, nicht die Oberfläche: wer ohne Schreibrecht beitritt, wird auf Lesen heruntergestuft, und die Sitzung prüft dieses Recht regelmäßig nach, solange das Dokument geöffnet ist, sodass ein entzogener Zugriff jemanden trifft, der gerade tippt, statt auf ein Neuladen zu warten.
Jeder Schreibvorgang geht durch eine Tür
Ein Dokument kann von jemandem geändert werden, der darin tippt, und von einem Programm, das die API aufruft, und diese beiden Wege konnten früher unabhängig voneinander auf dieselbe Seite schreiben. Das können sie nicht mehr. Ein Schreibvorgang über die API wird an dieselbe Sitzung weitergereicht, die das lebende Dokument hält, und dort als eine Transaktion neben den laufenden Änderungen angewandt: es gibt also eine einzige Reihenfolge der Ereignisse statt zweier Schreiber mit getrennten Meinungen darüber, was auf der Seite steht. Die Kennungen der Blöcke bleiben beim Zurückschreiben erhalten, denn Kommentare sind an ihnen verankert, und ein Abgleich, der neue Kennungen vergäbe, ließe jeden Kommentar ins Leere zeigen. Und wenn die entstandene Blockmenge mit der bereits gespeicherten übereinstimmt, wird überhaupt nichts geschrieben.
Der eine Transportweg, den ein Maschinenschlüssel nicht erreicht
Ein persönlicher API-Schlüssel funktioniert bei REST, GraphQL, dem GraphQL-Abonnement-Socket und MCP. Beim Kollaborations-Socket funktioniert er nicht, und das mit Absicht. Jede gemeinsame Änderung trägt den Stempel der Person, die sie vorgenommen hat, und diese Stempel werden zur Urheberschaft, die im Verlauf der Seite festgehalten ist. Ein maschineller Principal, der dort schriebe, trüge eine Urheberin ein, die kein Mensch geschrieben hat, und das später rückgängig zu machen hieße, Geschichte umzuschreiben, statt eine Zeile zu löschen. Die Grenze verläuft nicht zwischen Websockets und HTTP: sie verläuft dort, wo ein Transportweg zugeschriebene Geschichte schreibt. Durchgesetzt wird die Regel von der Form des Codes und nicht vom Gedächtnis, denn einen Schlüssel anzunehmen verlangt den bewussten Wechsel zu einem anderen Authentifizierungsaufruf, und ein Test schlägt fehl, sobald ein Transportweg das tut.
Ein Arbeitsbereich unter eigener Adresse
Jeder Arbeitsbereich ist vom Augenblick seiner Anlage an ein Mandant mit eigener Subdomain, und der Mandant wird aus der Adresse ermittelt, über die die Anfrage eintraf. In welchem Mandanten Sie sich befinden, steht damit fest, bevor irgendetwas von Ihren Daten gelesen wird, und nicht durch einen nachträglich angehängten Filter, den jemand vergessen könnte. Darunter setzt die Datenbank die Grenze selbst durch, mit Sicherheit auf Zeilenebene: jede Anfrage entleiht eine Verbindung, prägt ihr die Identität des Aufrufers auf, und der Pool löscht diesen Zustand bei der Rückgabe, sodass die Identität einer Anfrage nicht in die Abfragen der nächsten sickern kann.
Eine Domain zu beanspruchen heißt nicht, sie zu belegen
Ein Arbeitsbereich im Enterprise-Tarif kann seine Seiten von einer eigenen Domain ausliefern. Eine Domain zu beanspruchen und von ihr auszuliefern sind bewusst getrennte Schritte: die Domain wird unbestätigt gespeichert, und der Resolver übergeht sie vollständig, bis im DNS ein Bestätigungseintrag erscheint. Jede und jeder kann die Adresse eines fremden Unternehmens in ein Formular tippen. Nur wer diese Domain kontrolliert, kann den Eintrag veröffentlichen, der sie scharf schaltet.
Jede Fassung, die die Seite hatte
Clepit bewahrt Fassungen auf, nicht einen einzigen aktuellen Stand. Während gearbeitet wird, entsteht automatisch eine Momentaufnahme, gedrosselt, damit gewöhnliches Tippen nicht Hunderte davon erzeugt: eine neue wird geschrieben, sobald zehn Minuten vergangen sind oder zehn Blöcke sich geändert haben, je nachdem, was zuerst eintritt. Das Wiederherstellen ist eine einzige Transaktion: die alte Momentaufnahme wird angewandt, die Blöcke werden abgeglichen, und die Wiederherstellung selbst wird als neue Fassung geschrieben, sodass ein Rückschritt festgehalten und nicht stillschweigend getilgt wird. Der Abgleich behält die Blockkennungen der Quelle absichtlich bei, denn Kommentare sind an Blöcken verankert, und eine Seite mit frischen Kennungen wiederherzustellen ließe jeden Kommentar darauf ohne Anker zurück.
Es wiederfinden
Die Suche läuft über eine Projektion der Seiten, und die Anfrage geht durch den Websuche-Parser von Postgres selbst, statt von Hand zu SQL zusammengesetzt zu werden: man kann also Anführungszeichen und Minuszeichen tippen, ohne dass daraus irgendeine Angriffsfläche für Injektion entstünde. Worauf es in einem geteilten Arbeitsbereich aber ankommt, ist, wo die Rechteprüfung sitzt. Die Suche verbindet die Seitentabelle, und deren Sicherheit auf Zeilenebene gilt für diese Verbindung, sodass die Treffer bereits auf jene Seiten beschränkt sind, die die fragende Person sehen darf. Filter (ein Teilbaum der Seitenhierarchie, wer zuletzt überarbeitet hat, wann zuletzt bearbeitet wurde) legen sich darüber als zusätzliche Bedingungen. Jeder von ihnen engt ein; keiner kann erweitern, denn alle sitzen hinter derselben Prüfung.
Veröffentlichen friert ein, Teilen nicht
Das sind zwei verschiedene Dinge, und Clepit behandelt sie mit Absicht verschieden. Eine Seite zu veröffentlichen friert das aktuelle Dokument als Fassung ein, richtet die Seite darauf aus und macht sie öffentlich: was eine Besucherin auf clepit.space liest, unter einer Adresse wie acme.clepit.space/handbook, ist eben diese eingefrorene Fassung und nicht die seither gemachten Änderungen. Das Zurückziehen löscht diese Verweise, behält aber die öffentliche Adresse, sodass ein späteres erneutes Veröffentlichen zur selben URL zurückkehrt, statt jeden Link zu brechen, der dorthin zeigte. Ein Freigabelink ist das Gegenteil: er liefert das lebende Dokument aus, sodass sich das, was die Empfängerin sieht, mit der Seite mitverändert.
Ein Link, den Sie zurücknehmen können
Ein Freigabelink ist ein Token, das Sie widerrufen können, und beim Anlegen lässt sich ihm ein Ablauf mitgeben. Gespeichert wird nur ein Hash des Tokens: der Link wird also einmal bei der Erstellung angezeigt und ist danach aus der Datenbank nicht wiederherstellbar, weder von uns noch von jemandem, der an sie gelangt. Diese Links gewähren Lesen, nicht Kommentieren: ein Kommentar braucht eine Urheberin, und wer einen Link hält, ist keine.
Anmeldung aus dem eigenen Verzeichnis
Ein Arbeitsbereich kann die Anmeldung dem eigenen Identitätsanbieter überlassen: OpenID Connect im Business-Tarif, SAML im Enterprise-Tarif, daneben SCIM für den Abgleich mit dem Verzeichnis. SCIM deckt jene Nutzerressource ab, die Okta und Entra tatsächlich ansteuern, und weicht an einer Stelle bewusst von der naheliegenden Lesart des Standards ab: ein Löschen deaktiviert das Mitglied, statt es zu tilgen. Die Spezifikation erlaubt das, und die Alternative wäre ein Verzeichnisabgleich, der den Inhalt eines Arbeitsbereichs vernichten kann, weil jemand aus einer Gruppe genommen wurde.
Vier Wege, mit ihm zu sprechen
REST deckt vierundvierzig Operationen unter /v1 ab, beschrieben von einem OpenAPI-Dokument, das aus den Routen selbst erzeugt und nicht daneben geschrieben wird, und die fortlaufende Integration vergleicht diese Ausgabe mit der eingecheckten Fassung: eine Routenänderung, die an der Registrierung vorbeigeht, kann also nicht still hereinrutschen. GraphQL deckt das Anwendungsmodell ab und trägt Abonnements über einen eigenen Socket. Das Echtzeitteil ist ein eigener Prozess, weshalb ein Neustart des Gateways die Anfrageseite nicht mitreißt, und es autorisiert je Socket statt je Raum: zu jedem Ereignis werden alle Verbindungen im Mandanten in einer gebündelten Prüfung bewertet, und nur wer es sehen darf, bekommt es. MCP stellt dieselben Operationen KI-Agenten als Werkzeuge bereit, und kein Werkzeug traut einer Kennung des Aufrufers, um zu entscheiden, in welchem Arbeitsbereich es handelt; Schreibvorgänge laufen über dieselben Dienste wie die Oberfläche, sodass Rechteprüfungen und Prüfpfad dieselben sind.
Für wen es gedacht ist
Teams, die einen Editor unter eigener Kontrolle brauchen, und Entwickler, die strukturierte Inhalte in ihr eigenes Produkt einbetten.
Besuchen Clepit: clepit.com