Suche/Eine App bauen, die Aktivitätsereignisse aus vielen Quellen sammelt und auf mehreren Oberflächen zeigt

Eine App bauen, die Aktivitätsereignisse aus vielen Quellen sammelt und auf mehreren Oberflächen zeigt

Für eine App, die Ereignisse vieler Werkzeuge sammelt und in einer Editor-Erweiterung und einem eigenständigen Server zeigt. Hält Schichten in einer Richtung, macht aus jeder Quelle einen Ereignistyp, nimmt neue Quellen ohne andere Änderungen auf und speichert den Zustand sicher.

Diese KI-Regeln verwenden
## Abhängigkeiten nur in eine Richtung laufen lassen, damit Oberflächen sich nie gegenseitig importieren Lass Abhängigkeiten durch benannte Schichten immer nur in eine Richtung fließen. Eine `core`-Schicht enthält ausschließlich Protokoll- und Schnittstellendefinitionen ohne jegliche Laufzeit-Nebenwirkungen. Eine Runtime-Schicht hängt nur von `core` ab. Jede Host-Oberfläche – etwa ein Adapter für eine Editor-Erweiterung und ein eigenständiger Kommandozeilen-Server – hängt von `core` und der Runtime ab, aber die Oberflächen importieren sich niemals gegenseitig. Lege die erlaubte Richtung ausdrücklich fest und setze sie durch, denn schon ein einziger grenzüberschreitender Import koppelt still zwei Oberflächen, die unabhängig voneinander austauschbar bleiben müssen. ### Warum das funktioniert Eine abhängigkeitsfreie Vertragsschicht erlaubt es jeder Oberfläche und jeder Integration, gegen dieselben Typen zu kompilieren, ohne hostspezifischen Code mitzuziehen. Neue Oberflächen setzen sich aus den gemeinsamen Bausteinen zusammen; sie greifen niemals seitwärts in die internen Strukturen einer anderen Oberfläche ein. Wenn du keine Oberfläche hinzufügen kannst, ohne eine bestehende zu bearbeiten, ist die Schichtung bereits gebrochen. ## Typisierte Bindings aus einer Protokolldatei erzeugen und in der CI prüfen Lass einen einzigen maschinenlesbaren Vertrag die alleinige Wahrheitsquelle für das Übertragungsprotokoll sein und generiere daraus die typisierten Bindings, statt sie von Hand zu schreiben. Behandle die generierte Datei als schreibgeschützt: Versieh sie mit einem Hinweis auf die automatische Generierung und bearbeite sie nie manuell. Füge einen Continuous-Integration-Schritt hinzu, der die Bindings neu generiert und den Build bei jedem nicht leeren Diff fehlschlagen lässt, damit Vertrag und Code niemals auseinanderdriften können. ### Den Generator festlegen Lege die Vertragsversion auf das fest, was dein Codegenerator tatsächlich unterstützt. Eine neuere Schemaversion, die der Generator nicht verarbeiten kann, degradiert generierte Typen möglicherweise still zu einem nachsichtigen Auffangtyp und löscht damit jede Typsicherheit, ohne einen offensichtlichen Fehler zu erzeugen. Halte fest, warum die Version festgelegt wurde, und überprüfe das erst wieder, wenn das Tooling nachgezogen hat. ## Jede Quelle in einem zentralen Hub in denselben Ereignistyp umwandeln Führe jede Aktivität über einen einzigen Hub zusammen. Ganz gleich, woher sie stammt – über HTTP gepushte Ereignisse oder aus Log- und Transkriptdateien geparste Zeilen –, normalisiere jedes einzelne zu einem kanonischen Ereignistyp, bevor es in die Runtime gelangt. Nachgelagerter Code verzweigt anhand der Ereignisart, niemals anhand quellenspezifischer Aktions- oder Toolnamen, sodass das Hinzufügen einer Quelle nicht auf die Runtime oder die Oberfläche durchschlagen kann. ### Die Ereignismenge klein und geschlossen halten Definiere eine kleine, geschlossene Menge von Ereignisarten: Sitzungsstart und -ende, Arbeitsstart und -ende, Lebenszyklus von Teilaufgaben, Fortschritt und Berechtigungsanfrage. Jeder Erfassungspfad hat als einzige Aufgabe, diese kanonischen Ereignisse zu erzeugen. Alles hinter dem Hub ist quellenunabhängig und bleibt stabil, sobald neue Quellen hinzukommen. ## Die Verbindung hinter einer einzigen Transportschnittstelle verbergen Verberge die Übertragung hinter einer einzigen, schlanken Transportschnittstelle: eine Nachricht senden, eingehende Nachrichten abonnieren, ein Ready-Promise sowie einen Verbindungsstatus bereitstellen und sich sauber beenden. Implementiere sie einmal pro Umgebung – etwa ein prozessinterner Nachrichtenkanal für eine eingebettete Oberfläche und ein sich neu verbindender Socket für eine eigenständige – und verzweige zwischen den Implementierungen an genau einer Factory-Funktion. Alles Nachgelagerte hängt nur von der Schnittstelle ab und erfährt nie, welcher Transport gerade aktiv ist. ### Robustheit steckt in der Implementierung Statte die vernetzte Implementierung mit exponentiellem Backoff und einer Sendewarteschlange aus, die sich beim Wiederverbinden leert. Die prozessinterne Variante kann während ihrer gesamten Lebensdauer als verbunden gelten. Aufrufende sehen in beiden Fällen identisches Verhalten, sodass kein Feature-Code transportspezifische Verzweigungen enthält. ## Jede Integration als Provider ergänzen, ohne anderen Code anzufassen Modelliere jede externe Integration als Provider, der eine einzige Schnittstelle implementiert: ein Rohereignis in ein kanonisches umwandeln, seine Hooks installieren, deinstallieren und abfragen, den Status formatieren und festlegen, welche seiner Aktionsnamen von bestimmten Regeln ausgenommen sind. Biete optionale Fähigkeiten – einen Polling-Fallback, einen erweiterten Mehrakteur-Modus – als optionale Methoden an, damit einfache Integrationen klein bleiben. ### Die Probe auf eine saubere Grenze Eine neue Integration sollte genau ein neues Unterverzeichnis sein, das die Schnittstelle implementiert – ohne jede Änderung an der Runtime, der Oberfläche oder einem bestehenden Provider. Zwingt das Hinzufügen einer Quelle zu Änderungen an anderer Stelle, ist die Grenze undicht und muss abgedichtet werden, bevor sich weitere Integrationen auf diesem Riss stapeln. ## Einen Runtime-Kern und einen Store für beide Oberflächen teilen Lass beide Host-Oberflächen denselben Runtime-Kern zusammensetzen; nur der Persistenz-Namespace, der Transport und ein etwaiger hostspezifischer Adapter unterscheiden sich. Leite jede Zustandsänderung über einen einzigen Store, der die Daten besitzt, typisierte Mutationsereignisse aussendet und die alleinige Wahrheitsquelle ist. Lass genau eine Broadcast-Schicht diese Ereignisse abonnieren und dort in ausgehende Protokollnachrichten übersetzen – kein anderes Modul ruft eine Transportmethode direkt auf. ### Die Host-Schnittstelle schmal halten Stelle zwischen der Runtime und ihrem Host genau eine Callback-Schnittstelle bereit, für die wenigen Dinge, die wirklich hostspezifisch sind: Arbeit starten, Ordner öffnen, Import und Export. Genau diese schmale Schnittstelle ermöglicht es, dass zwei Oberflächen den Kern unverändert teilen – erweitere sie also nur, wenn ein echter Host-Unterschied es verlangt. ## Zuerst Push-Signale nutzen und Datei-Polling als Reserve behalten Wenn ein schnelles, zuverlässiges Signal existiert, bevorzuge es, und halte einen langsameren Fallback für den Fall bereit, dass es fehlt. Ein Push-Kanal aus Ereignis-Callbacks über HTTP liefert sofortige, geordnete Signale; das Abfragen von Dateien per Polling ist der Fallback, wenn Push nicht installiert ist. Sichere beide so ab, dass nie doppelt gezählt wird: Ein Zugestellt-Flag pro Eintrag zusammen mit einem globalen Schalter entscheidet, ob die Timer des Fallbacks laufen. ### Inhalte lesen von Zustandsableitung trennen Manche Daten kommen möglicherweise nur über den Fallback-Pfad – feingranularer Fortschritt, Statustext –, deshalb lässt du das Polling dafür auch im Push-Modus weiterlaufen, unterdrückst aber die daraus abgeleiteten Timer des Fallbacks (Berechtigungsabfragen, Leerlauferkennung), sobald Push für diesen Eintrag bestätigt ist. Erst die Trennung von „Inhalt lesen“ und „Zustand ableiten“ lässt beide Modi konfliktfrei nebeneinander bestehen. ## Den Zustand je Oberfläche getrennt speichern und die Kopie prüfen, bevor die alte entfernt wird Trenne den persistierten Zustand pro Oberfläche in eigene Namensräume, damit zwei parallel laufende Oberflächen sich niemals gegenseitig überschreiben. Schreibe Dateien atomar – zuerst eine temporäre Datei schreiben, dann umbenennen – und markiere die eigenen Schreibvorgänge, damit ein prozessübergreifender Watcher sie nicht fälschlich als externe Änderungen einliest. Kombiniere die Dateiüberwachung mit einem Polling-Backup, da native Dateiwatcher auf manchen Plattformen unzuverlässig sind. ### Vor dem Löschen verifizieren Verifiziere beim Migrieren von Altzuständen, bevor du löschst: Schreibe an den neuen Ort, lies ihn zurück, und entferne erst dann den alten Schlüssel. Solange noch etwas nicht migriert ist, zeige eine nicht blockierende Warnung an, statt Erfolg anzunehmen. Lösche die Wahrheitsquelle nie, bevor die Kopie auf der Festplatte bestätigt ist. ## Konstanten an einem Ort sammeln und per Lint-Regeln durchsetzen Zentralisiere jede magische Zahl und jeden magischen String – Timing-Konstanten, Bezeichner, Protokoll-Literale, Style-Token – in einer kleinen Anzahl von Konstanten-Modulen, eines pro Schicht, und schreibe sie niemals inline. Inline geschriebene Werte driften auseinander, duplizieren sich und verbergen Kopplungen, über die spätere Refactorings stolpern. ### Per Lint erzwingen, nicht durch Wachsamkeit Untermauere die Richtlinie mit eigenen Lint-Regeln auf Fehler-Stufe, sodass Verstöße Merges blockieren: verbiete inline geschriebene Farb- und Style-Literale außerhalb der Token-Datei, verlange die gemeinsamen Schatten- und Abstands-Token, verlange die gemeinsame Schriftreferenz. Eine Regel, die eine Maschine durchsetzt, übersteht den Wechsel von Mitwirkenden; eine nur in einem Dokument festgehaltene Konvention nicht. ## In Schichten testen und oft geänderten Code mit End-to-End-Tests absichern Verfolge eine geschichtete Teststrategie mit dem jeweils passenden Werkzeug pro Schicht. Schnelle Unit- und Integrationstests decken reine Logik ab: Zustandsänderungen, Ereignis-Routing, Parsing, Migration, Atomarität des Installers. End-to-End-Tests steuern die echten Oberflächen an und prüfen für den Nutzer sichtbare Ergebnisse statt interner Details. ### Für offene Mitwirkung stabile Nahtstellen bevorzugen Bevorzuge für Code, den Mitwirkende ständig verändern, etwa interne UI-Details, End-to-End-Tests, die das für Nutzer sichtbare Verhalten fixieren, gegenüber Unit-Tests, die an interne Details gebunden sind, damit Refactorings Mitwirkende nicht zwingen, neben der eigentlichen Feature-Arbeit auch noch interne Tests umzuschreiben. Halte jeden Test isoliert – mit eigenem temporärem Verzeichnis, Workspace und Datenordner –, damit kein Zustand zwischen Tests durchsickert, und prüfe jedes automatisch generierte Test-Inventar in der CI genauso auf Drift wie generierten Code. ## Fehlerhafte Eingaben überspringen statt abzustürzen und jede Logzeile kennzeichnen Degradiere kontrolliert: Fange Fehler ab und protokolliere sie, aber lass sie niemals den Host zum Absturz bringen. Überspringe eine fehlerhafte Eingabezeile stillschweigend, statt den gesamten Stream abzubrechen. Fehlt eine Ressource, protokolliere eine Warnung und fahre mit einem Null- oder Fallback-Wert fort, statt den gesamten Vorgang scheitern zu lassen. ### Logs durchsuchbar machen Versieh jede Logzeile mit einem in Klammern gesetzten Kontext-Tag, das das Subsystem benennt, damit ein flacher Konsolen-Stream durchsuchbar bleibt. Entscheide bewusst, ob du zentrales Fehler-Reporting oder Telemetrie brauchst; entscheidest du dich dagegen, halte diese Entscheidung fest und sorge für konsistentes lokales Logging, damit spätere Leser nicht rätseln müssen. ## Wachsende Logdateien sicher lesen und asynchronen Tests genug Zeit geben Diese Regeln sind billig auszusprechen und teuer neu zu lernen. ### Nur-Anhänge-Dateien lesen Kombiniere jeden nativen Dateiwatcher mit regelmäßigem Polling in kurzen Intervallen, und trage unabgeschlossene Teilzeilen über Lesevorgänge hinweg in einem Puffer mit, denn ein Reader kann eine Datei mitten im Schreibvorgang erwischen. Eine gelöschte oder zurückgesetzte Sitzung beginnt oft eine brandneue Datei, während die alte einfach aufhört, also erkenne den Wechsel am Inhalt, statt anzunehmen, dass dasselbe Handle immer weiter wächst. ### Zeitbudgets in Tests Wenn du am Ende einer asynchronen Pipeline ein Ergebnis prüfst, kalkuliere die Latenz jeder einzelnen Stufe ein – die Lücke zwischen dem auslösenden und dem abgeleiteten Signal, die Timer-Verzögerung sowie Prozess- und Render-Spielraum. Ein Timeout, das kürzer ist als das summierte Budget, ist konstruktionsbedingt instabil. ### Rendering und Batching Halte hochfrequenten Animationszustand in einem imperativen Store statt im Framework-Zustand, und verzögere Übergänge zu „Arbeit abgeschlossen“ leicht, damit UI-Batching kurze aktive Zustände nicht vor dem Nutzer verbirgt.