# Aktuelle Systemarchitektur ## Status und unterstützter Pfad Der unterstützte Editor ist Circuit-First: `Project → DistributionBoard → CircuitList → CircuitSection → Circuit → CircuitDeviceRow` Ein Stromkreis ist nicht dasselbe wie eine Gerätezeile. BMK, Schutz- und Kabeldaten gehören zum Stromkreis; Last-, Raum- und Kategoriedaten gehören zur Gerätezeile. Die frühere Consumer-Oberfläche, ihre API, Tabellen und Upgrade-Werkzeuge sind entfernt. Neue Funktionen bauen ausschließlich auf dem Circuit-First-Modell auf. ## Laufzeit ```text Browser :3001 │ ▼ Next.js App Router ── /api/* Rewrite ──▶ Express API :3000 │ ▼ Repository / Transaktion │ ▼ data/leistungsbilanz.db (SQLite) ``` Im Docker-Entwicklungssetup laufen Frontend und API in getrennten Containern. Das Frontend leitet `/api/*` über `API_INTERNAL_URL` an die API weiter. Die Datenbank liegt über einen Host-Mount außerhalb des Containers. ## Wichtige Einstiegspunkte - `src/app/projects/page.tsx` – Projektliste und globale Gerätebibliothek - `src/app/projects/[projectId]/page.tsx` – Projektstammdaten, Verteilungen, Räume und Projektgeräte - `src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree-edit/page.tsx` – unterstützte Editorroute - `src/frontend/components/circuit-tree-editor.tsx` – Editorzustand, Befehlsausführung, Drag-and-drop und der schrittweise Historien-Cutover - `src/frontend/components/circuit-grid-*.ts` – reine Grid-Projektion, Zellbesitz, Einfügen und Sicherheitsregeln - `src/frontend/utils/api.ts` – typisierte Frontend-API-Aufrufe - `src/server/index.ts` und `src/server/routes/` – API-Komposition - `src/domain/services/` – fachliche Command- und Synchronisierungsregeln - `src/server/composition/` – Verdrahtung fachlicher Services mit konkreten SQLite-Repositories - `src/db/repositories/` – Abfragen, Persistenzmapper und Transaktionsadapter - `src/db/schema/` und `src/db/migrations/` – SQLite-Schema und Migrationen ## Daten- und Befehlsfluss 1. Das Grid projiziert den geladenen Circuit-Tree in sichtbare Zeilen. 2. Eine Benutzeraktion wird im Frontend validiert und als API-Befehl gesendet. 3. Controller validieren Requestdaten mit Zod. 4. Domain-Services prüfen fachliche Regeln wie BMK-Eindeutigkeit, Abschnittszuordnung und Reserveverhalten. 5. Repositories schreiben Daten. Kritische Mehrfachschreibvorgänge besitzen explizite SQLite-Transaktionsadapter mit Commit-/Rollback-Integrationstests. 6. Das Frontend lädt den Circuit-Tree neu und stellt Auswahl beziehungsweise Viewport soweit möglich wieder her. Der Editor besitzt keinen sitzungslokalen Undo-/Redo-Stapel mehr. Beim initialen Laden und nach jedem Tree-Reload liest er den persistenten History-Status und gleicht dessen Revision mit `currentRevision` des Trees ab. Das Datenmodell besitzt einen projektbezogenen Revisionszähler sowie getrennte Revision-/Change-Set- Tabellen. Eine direkt getestete Persistence-Funktion schreibt diese Historienmetadaten innerhalb der zentralen Command-Transaktion optimistisch und atomar fort. Vorwärts- und Rückwärtskommandos besitzen einen versionierten, JSON-sicheren Umschlag; Typ und Payload können dadurch nach einem Neustart verlustfrei rekonstruiert werden. Alle aktuell unterstützten Circuit-, Gerätezeilen-, Projektgeräte-, Projektstruktur- und Projekteinstellungsänderungen verwenden typisierte Command-Stores, die Fachänderung, automatisch erzeugtes inverses Kommando, Revision und Historienstapel gemeinsam committen beziehungsweise zurückrollen. Gerätezeilen-Kommandos bewahren dabei auch lokale ProjectDevice-Overrides und prüfen Projektzugehörigkeit von Verknüpfungen und Räumen. Projektweite, persistente Undo-/Redo-Stacks verwalten die zulässige LIFO-Reihenfolge und verwerfen den Redo-Zweig bei einem neuen Benutzerkommando. Ihr Status ist über `GET /api/projects/:projectId/history` lesbar. Die unveränderliche, absteigend paginierte Revisions-Timeline ist ohne Befehls-Payloads über `GET /api/projects/:projectId/history/revisions` lesbar. Ein zentraler Dispatcher führt die nachfolgend beschriebenen typisierten Kommandos über öffentliche Command-, Undo- und Redo-Endpunkte aus. Alle unterstützten Runtime-Project-Command-Stores einschließlich der vollständigen Snapshot-Wiederherstellung verwenden dabei `project-command-transaction.persistence.ts` als gemeinsame äußere Transaktionsgrenze. Sie führt Fachänderung, Revisions-Append und History-Transition in fester Reihenfolge innerhalb derselben SQLite-Transaktion aus. Für CircuitDeviceRow-Feldänderungen übernimmt eine typisierte Variante den während der Fachänderung um Override-Metadaten ergänzten Forward-Command. Die fachlichen Validierungs-, Forward- und Inversenregeln bleiben im jeweiligen Store. Die Low-Level-Funktion `appendProjectRevision` bleibt ein internes Detail dieser Persistenzgrenze und wird direkt mit einer realen SQLite-Transaktion getestet. Ein eigenständiges Runtime-Revisions-Repository existiert nicht. `ProjectDeviceSyncService` und `CircuitNumberingService` kennen nur schmale, fachlich benannte Reader-Interfaces. Ihre SQLite-Repositories werden ausschließlich in `src/server/composition/` erzeugt und injiziert. Auch die allgemeinen Projekt-, Geräte-, Raum-, Geschoss- und Circuit- Repositories verlangen einen expliziten `AppDatabase`-Kontext. Ihre Anwendungsinstanzen werden zentral in `src/server/composition/application-repositories.ts` erzeugt; Controller importieren weder den globalen SQLite-Client noch konkrete Repository-Klassen. `circuit-device-row.insert` und `circuit-device-row.delete` sind atomare Strukturkommandos. Beim Löschen wird die vollständige Zeile im inversen Kommando gesichert, sodass Undo dieselbe UUID und alle Fachwerte wiederherstellt. `circuit.insert` und `circuit.delete` behandeln einen Stromkreis mit null, einer oder mehreren Gerätezeilen als vollständigen Block. Undo bewahrt dabei sämtliche Circuit-/Row-UUIDs und ändert keine Betriebsmittelkennzeichen. Bestehende Circuit- und Gerätezeilen-Zelländerungen sowie eigenständiges Einfügen und Löschen verwendet das Grid bereits über die öffentliche Command-Grenze. Neue Circuits und Gerätezeilen erhalten ihre stabile UUID vor dem Command; Löschen und Undo bewahren diese Identität. Der Tree liefert dazu `currentRevision`; Undo/Redo für diese Aktionen läuft über die projektweite Serverhistorie. Direkte Feld-PATCH-, Struktur-POST-, Move-, Circuit- und Gerätezeilen-DELETE-Endpunkte sind entfernt. Gerätezeilen-Moves, Stromkreis-Reorders und die explizite Neunummerierung im Grid verwenden die persistenten Kommandos. Die Toolbar leitet ihre Undo-/Redo-Verfügbarkeit direkt aus den serverseitigen Stack-Tiefen ab, sodass ein Reload die Bedienbarkeit nicht unterbricht. Benannte logische Projektstände werden in `project_snapshots` getrennt von Datenbank-Backups gespeichert. `POST /api/projects/:projectId/snapshots` erzeugt bei passender erwarteter Revision transaktional einen vollständigen, schema-versionierten Projektzustand mit SHA-256-Prüfwert. Enthalten sind Projekteinstellungen, Verteiler, Stromkreislisten, Bereiche, Stromkreise und Gerätezeilen sowie Projektgeräte, Geschosse und Räume. Globale Geräte sind nicht Teil des Projekt-Snapshots. Create/List verändern weder Projektrevision noch Undo-/Redo-Stapel. `kind` unterscheidet benannte und automatische Stände. Die zentrale Revisionspersistenz erzeugt nach jeweils 25 weiteren Projektänderungen transaktional einen automatischen Stand. Pro Projekt bleiben die neuesten 12 automatischen Stände erhalten; ältere automatische Stände werden in derselben Transaktion entfernt. Benannte Stände und die unveränderliche Revisionshistorie sind von dieser Aufbewahrung ausdrücklich ausgeschlossen. `POST /api/projects/:projectId/snapshots/:snapshotId/restore` prüft Payload, Prüfsumme, erwartete Revision und den unmittelbar zuvor gelesenen Projektzustand. Der Restore ersetzt alle unterstützten Projektdaten in einer Transaktion und schreibt dabei eine neue Revision mit Quelle `restore` sowie ein vollständiges inverses Kommando. Undo und Redo können deshalb auch einen Restore nach einem Neustart exakt zurücknehmen oder wiederholen. Die Projektseite bindet diese APIs in einem einklappbaren Bereich „Versionen und Sicherungspunkte“ ein. Dort können Benutzer Sicherungspunkte benennen, jeden aufgeführten benannten oder automatischen Stand nach expliziter Bestätigung wiederherstellen und die paginierte Revisions-Timeline mit deutschen Quellen- und Änderungsbezeichnungen lesen. Die Snapshot-Liste ordnet jede positive `sourceRevision` serverseitig ihren unveränderlichen Revisionsmetadaten zu. Dadurch zeigt auch ein älterer automatischer Stand die auslösende Änderung, ohne eine zweite frei formulierte Beschreibung zu speichern oder von den zuletzt paginiert geladenen Timeline-Einträgen abzuhängen. Revision null wird als Projektstart dargestellt. Projektweites Rückgängig/Wiederholen bleibt im Kopf dieses Bereichs auch im eingeklappten Zustand erreichbar. Die Verfügbarkeit stammt direkt aus den persistierten Server-Stacks; nach einer Historienaktion oder einem Restore lädt die Seite sämtliche Projektdaten neu. `circuit-device-row.move` verschiebt oder sortiert eine oder mehrere Zeilen zwischen vorhandenen Stromkreisen derselben Liste. Erwartete und neue Stromkreis-/Sortierpositionen machen Forward und Inverse deterministisch; Reservewerte aller beteiligten Stromkreise werden atomar neu abgeleitet. Das Kompositkommando `circuit-device-row.move-with-new-circuit` bildet auch das Verschieben auf einen freien Platz ab: Ein Zielstromkreis mit stabiler UUID und BMK wird zusammen mit allen Zeilenbewegungen erzeugt. Undo stellt die exakten Quellpositionen wieder her und löscht den erzeugten Stromkreis nur, wenn dessen Felder und vollständiger Zeilenbestand unverändert sind. Der Editor erzeugt die vollständigen Move-Zuweisungen aus dem geladenen Tree, vergibt für neue Ziele vor dem Kommando eine stabile UUID und führt das Toolbar-Undo/Redo über die projektweite Historie aus. Die Projektgerätepalette belegt keine permanente Layoutspalte mehr. Sie wird über die Editor-Toolbar als überlagernder Drawer geöffnet, während das Stromkreis-Grid standardmäßig die gesamte verfügbare Breite nutzt. Auswahl, Schnelleinfügen und die vorhandenen Drag-and-drop-Payloads bleiben im Drawer unverändert verfügbar; während eines aktiven Projektgeräte-Drags kann er nicht geschlossen werden. Der Drawer liegt am rechten Fensterrand und verdeckt damit nicht die führenden BMK- und Anzeigenamenspalten. Das Grid zeigt Geräte- und Stromkreisleistungen in einer gemeinsamen Spalte `Gesamtsumme`: Gerätezeilen verwenden `rowTotalPower`, Stromkreis-Sammelzeilen `circuitTotalPower`. Zahlen werden ausschließlich für die Anzeige deutsch und begrenzt formatiert; gespeicherte Werte und Bearbeitungsentwürfe behalten ihre volle Genauigkeit. Jeder Abschnitt liefert und zeigt seine aufsummierte Stromkreisleistung. Die Stromkreisliste zeigt außerdem die ungefilterte Gesamtleistung des Verteilers, den am Verteiler gespeicherten Gleichzeitigkeitsfaktor und die daraus abgeleitete Gesamtleistung unter Berücksichtigung dieses Faktors. Sortierung und Filter verändern diese fachlichen Summen nicht. Reihenfolge und Sichtbarkeit der Grid-Spalten sind reine UI-Präferenzen. Sie werden im Browser unter einem projektspezifischen Schlüssel gespeichert und deshalb beim Wechsel zwischen Verteilern desselben Projekts wiederverwendet, ohne Projektrevisionen oder fachliche Snapshots zu erzeugen. `circuit.reorder-section` speichert die erwartete und neue Sortierposition jedes Stromkreises eines vollständigen Abschnitts. Forward, Undo und Redo ändern ausschließlich `sortOrder`; Stromkreisblöcke, Gerätezeilen und BMKs bleiben unverändert. `circuit.renumber-section` bildet die getrennte, ausdrücklich ausgelöste Neunummerierung ab. Es speichert alle erwarteten und neuen BMKs des Abschnitts, löst Tauschkollisionen über temporäre Werte und ändert weder Sortierung noch Gerätezeilen. Die Bereichsaktion erzeugt die vollständigen erwarteten und neuen BMKs aus Präfix und aktueller Stromkreisreihenfolge und verwendet das persistente Kommando für Toolbar-Undo/Redo. Die früheren direkten Renumber- und Identifier-Restore-Endpunkte sowie ihr separater Transaktionsadapter sind entfernt. Drag-and-drop verwendet `circuit.reorder-section`. Die explizite Übernahme einer sortierten Ansicht verwendet `circuit.reorder-sections`, damit alle betroffenen Bereiche in einer Transaktion und als ein Undo-Schritt gespeichert werden. Direkte Reorder-Endpunkte existieren nicht mehr. `project-device.sync-rows` persistiert Synchronisierung, Trennen und erneutes Verknüpfen als atomaren Mehrzeilen-Command. Jede betroffene Zeile enthält den vollständigen erwarteten und neuen Stand aller synchronisierbaren Felder, einschließlich ProjectDevice-Verknüpfung und lokaler Override-Metadaten. Damit werden stille Überschreibungen veralteter Zeilen verhindert und Undo/Redo stellt exakt die vorherigen lokalen Werte wieder her. Die Synchronisieren- und Trennen-Endpunkte sowie die Projektseite verwenden diesen Command mit optimistischer Revisionsprüfung. Rückgängig/Wiederholen erfolgt dort einheitlich über die persistente projektweite Historie; der frühere sitzungslokale Spezial-Undo und separate direkte Restore-/Reconnect-Schreibwege existieren nicht mehr. `project-device.update` versioniert Änderungen an den kanonischen Projektgerätefeldern unabhängig davon. Der Store erzeugt die Inverse aus dem gespeicherten Gerät und schreibt Geräteänderung, Revision und Historienstapel atomar. Verknüpfte Stromkreiszeilen werden dabei bewusst nicht automatisch synchronisiert. Der ProjectDevice-`PUT`-Endpunkt und die Projektseite verwenden diesen Command mit optimistischer Revisionsprüfung. `project-device.insert` und `project-device.delete` versionieren außerdem den Lebenszyklus eines Projektgeräts mit stabiler UUID. Beim Löschen speichert das inverse Insert den vollständigen Gerätestand sowie vollständige, nach dem Löschen erwartete Snapshots aller zuvor verknüpften Gerätezeilen. Undo setzt die Links nur zurück, wenn diese Zeilen weiterhin zum Projekt gehören, unverknüpft und vollständig unverändert sind. Gerät, Linkänderungen, Revision und Historienstapel teilen dieselbe Transaktion. Create, Import aus der globalen Gerätebibliothek und Delete laufen über dieselbe Command-Grenze; ihre Antworten liefern Gerät und aktualisierten Historienstand an die Projektseite zurück. `project.update-settings` versioniert Projektname, interne und externe Projektnummer, Bauherr, Beschreibung, die Kennzeichnung als öffentliches Gebäude, beide Standardspannungen sowie die im Projekt freigeschalteten Verteiler-Netzarten als eine atomare Änderung. Die Gebäudekennzeichnung ist der spätere Eingang für die Auswahl halogenfreier Kabel und Leitungen. Der Systemkatalog besteht aus `AV`, `SV`, `EV`, `USV`, `MSR` und `SiBe`; mindestens eine Netzart muss aktiv bleiben und eine bereits von einer Verteilung verwendete Netzart kann nicht deaktiviert werden. Die Projektseite bearbeitet diese Angaben in einem beschrifteten Einstellungsmodal statt in einer permanenten Formularkarte. Der Store leitet das inverse Kommando aus dem gespeicherten Projekt ab und schreibt Werte, Revision und Historienstapel gemeinsam. `PUT /api/projects/:projectId` verlangt deshalb `expectedRevision`, liefert Projekt plus aktualisierten Historienstand und besitzt keinen separaten direkten Settings-Schreibweg mehr. Kommando- und Snapshot-Payloads müssen dem aktuellen Baseline-Schema entsprechen. Die Projektseite zeigt Verteilungen, Etagen und Räume als kompakte Bestandsübersichten; ihre versionierten Erstellwege öffnen beschriftete Modals statt dauerhafter Eingabezeilen. Projektgeräte werden als durchsuchbare, fünfspaltige Übersicht dargestellt. Manuelle Anlage, Übernahme aus der globalen Bibliothek und vollständige Bearbeitung erfolgen in einem gemeinsamen Modal. Die anschließende Vorschau verknüpfter Stromkreiszeilen bleibt davon getrennt, damit Änderungen weiterhin niemals still synchronisiert werden. `GET /api/projects/:projectId/export` verpackt denselben vollständigen Projektzustand in ein portables, format- und schema-versioniertes JSON-Dokument mit SHA-256-Prüfsumme. `POST /api/projects/:projectId/import` prüft Format, Snapshot-Relationen und Prüfsumme vor jedem Schreibzugriff. Der Modus `replace` läuft über `project.restore-state` und ist dadurch eine atomare, dauerhaft rückgängig machbare Projektrevision. Der Modus `duplicate` ordnet Projekt-, Struktur-, Gruppen-, Verteilerkomponenten-, Raum-, Stromkreis- und Gerätezeilen-UUIDs sowie Schutzgeräte-Referenzen vollständig neu zu und legt die Kopie mit Revision `0` in einer Transaktion an. Fachliche Verknüpfungen innerhalb des unterstützten Laufzeitmodells bleiben erhalten. Die Projektübersicht verwendet dafür den separaten Collection-Endpunkt `POST /api/projects/import`, der ausschließlich eine neue Kopie anlegt und deshalb weder eine bestehende Projekt-ID noch `expectedRevision` annimmt. Das Ersetzen eines Projekts bleibt auf dessen Einstellungsmodal und den projektgebundenen Endpunkt beschränkt. `distribution-board.insert` versioniert die Anlage einer Verteilung mit Command-Schema 1 als vollständigen Block aus Verteilung, Stromkreisliste, den drei Gruppen `Beleuchtung 1`, `1-phasig 1`, `3-phasig 1`, Hauptschalter `-Q0` und Überspannungsableiter `-FA`. Alle UUIDs entstehen vor dem Command und bleiben über Undo/Redo stabil. `distribution-board.delete` ist die persistierte Inverse und entfernt nur den vollständig unveränderten, weiterhin stromkreislosen Block. Controller und Projektseite übergeben die erwartete Projektrevision; der frühere direkte Controller-Schreibweg ist entfernt. Verteilungen besitzen eine optionale Etagenreferenz sowie eine Netzart aus dem Projektkatalog. Anlage und nachträgliche Bearbeitung prüfen die Projektzugehörigkeit der Etage und die Freigabe der Netzart in den Projekteinstellungen. Bereits befüllte Verteilungen werden über `distribution-board.insert-subtree` und `distribution-board.delete-subtree` als vollständiger Unterbaum kopiert beziehungsweise gelöscht. Der Snapshot umfasst Verteilung, Stromkreisliste, Gruppen, Stromkreise, Gerätezeilen, Verteilerkomponenten und alle zugehörigen Schutzgeräte. Beim Kopieren werden sämtliche besitzenden UUIDs neu vergeben, während fachliche Verknüpfungen zu Projektgeräten und Räumen erhalten bleiben. Beide Aktionen sind atomare Projektrevisionen und bleiben nach einem Neustart über Undo/Redo umkehrbar. Die Projektseite bietet sie im Einstellungsmodal der jeweiligen Verteilung an; Löschen verlangt dort eine ausdrückliche Bestätigung. `distribution-board-component.insert` und `distribution-board-component.delete` sowie `distribution-board-component.update` versionieren Anlage, Entfernung, Bearbeitung und Sortierung veränderlicher Gruppen-Schutzgeräte und zusätzlicher Verteilergeräte. Die vollständigen Snapshots enthalten stabile Komponenten-UUIDs und, für Gruppen-Schutzgeräte, die getrennte 1:1-Schutzgerätekonfiguration. Updates tauschen einen exakt erwarteten gegen einen vollständigen Zielzustand aus; ID, Eigentum, Rolle und Platzierungszone bleiben dabei unverändert. Projekt-, Listen- und Gruppenzugehörigkeit sowie veraltete Zustände werden innerhalb derselben Transaktion geprüft. Die festen Kopfkomponenten Hauptschalter und Überspannungsableiter sind von diesen allgemeinen Commands ausgeschlossen. Veränderliche Gruppen- und Fußkomponenten werden im Editor über eigene Modale und diese Commands verwaltet. Leere Stromkreisgruppen werden über `circuit-group.insert`, `circuit-group.update` und `circuit-group.delete` verwaltet. Ihr vollständiger Snapshot koppelt Kategorie, positive Gruppennummer und daraus abgeleiteten BMK-Präfix; allgemeine Updates ändern ausschließlich den Anzeigenamen. Löschen verlangt einen exakt unveränderten Zustand ohne Stromkreise und ohne Gruppenkomponenten. Befüllte Gruppen verwenden getrennt davon den vollständigen `circuit-group.delete-subtree`-Command mit ausdrücklicher Warnung. `circuit-group.reorder` sortiert Gruppen als vollständige Stromkreislisten- Zuordnung. Jede vorhandene Gruppe muss mit erwarteter und neuer Position enthalten sein. Der Command verändert ausschließlich `sortOrder`; Nummern, Präfixe und Stromkreis-BMKs bleiben stabil. Die gesamte Sortierung bildet eine Revision und einen Undo/Redo-Schritt. `src/domain/services/circuit-group-renumbering.ts` plant explizite Gruppennummerierungen deterministisch. Der reine Plan erhält Stromkreis-Endnummern, leitet Gruppenpräfixe und optionale `.0`-Komponenten-BMKs neu ab und prüft Nummerntausch sowie Kollisionen mit unveränderten Gruppen. `circuit-group.renumber` führt diesen Plan über die gemeinsame Projekt-Command-Transaktion aus. Vor dem Schreiben müssen alle betroffenen Gruppen, Stromkreise und Gruppenkomponenten exakt dem erwarteten Zustand entsprechen. Nummerntausch verwendet kollisionsfreie temporäre Präfixe und BMKs über beide BMK-Tabellen; anschließend werden alle Zielwerte finalisiert. Der vollständige inverse Plan ermöglicht dauerhaftes Undo/Redo. `src/domain/services/circuit-group-move-planning.ts` plant den Wechsel eines vollständigen Stromkreises zwischen zwei verschiedenen Gruppen derselben Kategorie. Die Ziel-BMK verwendet die höchste vorhandene Stromkreis-Endnummer der Zielgruppe plus eins; Lücken werden nicht gefüllt. Quell-/Zielgruppe, Quell-/Zielposition und beide BMKs werden festgeschrieben, damit eine spätere Wiederholung nichts neu berechnet. `circuit.move-group` führt den geplanten Wechsel als eine Projekt-Revision aus. Nur `sectionId`, `equipmentIdentifier` und `sortOrder` des Stromkreises ändern sich. Gerätezeilen und das getrennte 1:1-Schutzgerät bleiben über die stabile Stromkreis-UUID verbunden. Der inverse Wechsel speichert Quellgruppe, BMK und Position vollständig für dauerhaftes Undo/Redo. `CircuitGroupSubtreeSnapshot` ist der kanonische Vertrag für destruktive Gruppenoperationen. Er umfasst Gruppe, optionale Gruppenkomponenten samt 1:1-Schutzdaten, vollständige Stromkreise samt 1:1-Schutzdaten und sämtliche Gerätezeilen einschließlich Link- und Override-Metadaten. Seine Warnzusammenfassung liefert Vorsicherung/FI sowie Stromkreis- und Gerätezeilenanzahl. `circuit-group.delete-subtree` nimmt den aktuellen Unterbaum unmittelbar vor dem Löschen erneut auf und verlangt exakte Übereinstimmung mit dem bestätigten Snapshot. `circuit-group.restore-subtree` schreibt ihn in FK-sicherer Reihenfolge mit denselben UUIDs zurück. Beide Richtungen laufen als vollständige inverse Projekt-Commands; späte Fehler hinterlassen weder Teillöschungen noch Teilwiederherstellungen. Der Circuit-Tree liefert zusätzlich `headerComponents`, gruppenbezogene `components` und `footerComponents`. Abschnitte enthalten optionale Gruppenkategorie und -nummer; Stromkreise und Gruppenkomponenten enthalten ihre getrennte Schutzgerätekonfiguration. Die 1:1-Schutzgerätetabellen sind die einzige unterstützte Quelle dieser Daten. Separate aktive Read-Repositories lesen Komponenten- und beide Schutzgerätetabellen; Schreibzugriffe bleiben ausschließlich in Commands. `src/frontend/utils/circuit-structure-projection.ts` projiziert diesen Tree rein und deterministisch in die drei Zonen Kopf, vollständige Gruppen und Fuß. Innerhalb einer Gruppe folgen auf die Überschrift optionale Schutzkomponenten, vollständige Stromkreisblöcke und der freie Einfügeplatz. Leere Gruppen bleiben sichtbar; Komponenten werden stabil nach `sortOrder` und ID sortiert. `circuit-grid-projection.ts` normalisiert diese Struktur zusammen mit den bisherigen kompakten und mehrzeiligen Stromkreisblöcken für den Editor. Kopf-, Gruppen- und Fußkomponenten erscheinen als Zeilen über die volle Tabellenbreite und nehmen nicht an Zellauswahl oder Tastatur-Einfügen teil. Hauptschalter und Überspannungsableiter bleiben feste, schreibgeschützte Kopfkomponenten. Optionale Gruppenvorsicherungen, Gruppen-FIs und zusätzliche Verteilergeräte werden über beschriftete Modale angelegt, bearbeitet und nach Bestätigung entfernt. Der Frontend-API-Adapter serialisiert dafür die vorhandenen `distribution-board-component.*`-Commands; die verbindliche geräteabhängige Validierung und atomare Revision bleiben serverseitig. Neue Stromkreisgruppen erhalten im Editor die höchste vorhandene Gruppennummer ihrer Kategorie plus eins und ein daraus abgeleitetes Präfix. Das Bearbeiten ändert ausschließlich den Anzeigenamen. Nur vollständig leere Gruppen können über `circuit-group.delete` entfernt werden; für befüllte Gruppen bleibt die Aktion bis zum gesonderten Warn- und Unterbaumdialog gesperrt. `circuit-protection.update` bildet die eigene persistente Schreibgrenze für die neue 1:1-Stromkreisschutztabelle. Der Command vergleicht den vollständigen erwarteten Datensatz, validiert Typ und abhängige Felder, prüft die Projektzugehörigkeit und schreibt Schutzgerät, Revision und Historienübergang atomar. Benutzer können Schutzdaten anlegen oder ändern, aber nicht entfernen; Undo einer erstmaligen Anlage darf den zuvor fehlenden Datensatz exakt wiederherstellen. Der vollständige `CircuitSnapshot` enthält den zugehörigen `protectionDevice`-Datensatz, sofern einer vorhanden ist. Neue Stromkreise und durch Geräteverschiebung erzeugte Zielstromkreise verwenden die vereinbarten Kategorie-Standardwerte und schreiben Schutzgerät, Stromkreis sowie Gerätezeilen atomar. Delete/Undo erfasst denselben Datensatz vollständig. Im Editor sind die Schutzspalten deshalb eine schreibgeschützte Projektion der 1:1-Daten; Änderungen erfolgen über ein geräteabhängiges Schutzgeräte-Modal und `circuit-protection.update`. Gruppen lassen sich im Editor schrittweise nur gegenüber einer benachbarten Gruppe derselben Kategorie verschieben. Der Client sendet dabei immer die vollständige erwartete und gewünschte Sortierreihenfolge über `circuit-group.reorder`; Gruppennummer, Präfix und Kind-BMK bleiben unverändert. Eine separate, ausdrücklich bestätigte Editoraktion erzeugt dagegen einen vollständigen `circuit-group.renumber`-Plan für genau eine Kategorie. Sie nummeriert deren Gruppen gemäß aktueller Reihenfolge ab eins und ändert Gruppenpräfixe, optionale Gruppen-Schutz-BMK und Stromkreis-BMK gemeinsam; die Stromkreisnummer hinter dem letzten Punkt bleibt erhalten. Das vorhandene Stromkreis-Drag-Handle kann außerdem genau einen vollständigen Stromkreis in eine andere Gruppe derselben Kategorie verschieben. Der atomare `circuit.move-group`-Befehl erhält Gerätezeilen und Schutzdaten, setzt die Zielposition und vergibt dort die höchste vorhandene Stromkreisnummer plus eins; Lücken werden nicht automatisch gefüllt. Befüllte Gruppen werden im Editor erst nach einer Warnung mit Anzahl der enthaltenen Stromkreise, Gerätezeilen und Gruppenschutzgeräte gelöscht. Der Client sendet dafür den vollständigen aktuellen Unterbaum an `circuit-group.delete-subtree`; der Server vergleicht ihn innerhalb derselben Transaktion mit dem Datenbankstand. Undo stellt Gruppe, Schutzgeräte, Stromkreise, Gerätereihen und Verknüpfungs-/Override-Metadaten vollständig wieder her. `distribution-board.update` versioniert Etage, Netzart und den verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und ist für bestehende sowie neu angelegte Verteilungen standardmäßig `1`. Snapshot-Schema 5 enthält zusätzlich den manuellen Mengenanteil jeder Gerätezeile. Version 4 wird mit `manualQuantity = quantity` hochgestuft und enthält bereits den vollständigen externen Modellzustand aus Quelle, Batches, Raum-Mappings und Objekten. Es enthält außerdem die projektweite Revit-CSV-Konfiguration, die Gebäudekennzeichnung sowie den Gleichzeitigkeitsfaktor, Gruppenkategorie und -nummer, Verteilerkomponenten sowie die getrennten 1:1-Schutzgerätedaten. Capture, benannte und automatische Snapshots, Wiederherstellung, Undo/Redo und beide JSON-Importmodi verwenden denselben vollständigen Zustand. Die unterstützte Version 3 wird mit leerem externem Modellzustand und Version 2 zusätzlich mit leerer externer Konfiguration hochgestuft. Die unterstützte Baseline-Version 1 wird zusätzlich mit `isPublicBuilding = false` hochgestuft; Formate vor dieser Baseline werden nicht eingelesen. Persistente Insert-, Update- und Delete-Commands versionieren Anlage, Bearbeitung und Löschung von Geschossen und Räumen mit stabilen UUIDs und exakten Vorher-/Nachher-Snapshots. Ein Geschoss wird nur entfernt, solange ihm weder ein Raum noch eine Verteilung zugeordnet wurde. Ein Raum wird nur entfernt, solange keine CircuitDeviceRow auf ihn verweist. Alle Schreibendpunkte verlangen `expectedRevision` und liefern den aktualisierten Historienstand. ## Projektgeräte Die Projektgerätekategorie `lighting`, `single_phase` oder `three_phase` ist die fachliche Klassifikation und Zielgruppe. `phaseType` ist keine separate Benutzereingabe, sondern wird daraus für Spannung, Verknüpfung und spätere Dimensionierung abgeleitet. Beim Einfügen entsteht eine verknüpfte `CircuitDeviceRow`. Der Anzeigename wird kopiert, aber nicht still synchronisiert. Spätere Änderungen am Projektgerät werden als Diff angezeigt und nur für ausdrücklich gewählte Felder und Zeilen übernommen. Die globale Gerätebibliothek ist ein einfacher, datenbankweiter Vorlagenbestand. Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät. ## Persistenz und Migration - SQLite ist die aktuell unterstützte Datenbank. - Fremdschlüssel werden für jeden Datenbankkontext aktiviert. - `npm run db:migrate` richtet mit Baseline-Migration `0000` und den folgenden additiven Migrationen das aktuelle Schema ein. - `npm run db:verify:circuit-schema` prüft erforderliche und entfernte Spalten. - `npm run db:backup` erzeugt ein konsistentes und verifiziertes Online-Backup. - `npm run typecheck:scripts` prüft die TypeScript-Wartungsskripte, ohne Code zu erzeugen. - Die Baseline ersetzt alle vorigen Entwicklungsmigrationen. Datenbanken, Snapshots und persistierte Commands aus Vor-Baseline-Ständen werden bewusst nicht unterstützt. Ab der ersten veröffentlichten Version sind angewendete Migrationen unveränderlich und Änderungen erfolgen additiv. - Allgemeine Circuit-, Gerätezeilen-, CircuitList- und DistributionBoard- Repositories stellen im Anwendungspfad nur noch benötigte Leseabfragen bereit. Fachliche Schreibvorgänge liegen in den typisierten Command-Repositories. - Die vollständige Verteilungs-Testfixture liegt unter `tests/support/distribution-board-fixture.ts` und ist kein exportierter Produktions-Schreibweg. - Die Baseline bildet die relationale Grundlage für geschützte Stromkreisgruppen und Verteilerkomponenten. Separate 1:1-Tabellen halten Stromkreis- und Komponenten-Schutzgeräte. Die früheren flachen Stromkreis-Schutzfelder sind aus der Baseline entfernt. Ein triggergeführtes Register erzwingt bereits eine normalisierte, stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden aktuell Snapshot-Schema 5. Persistente Insert/Delete/Update-Commands für veränderliche Verteilerkomponenten, Gruppen einschließlich befüllter Unterbäume sowie vollständige Gruppensortierung sind integriert. Der Editor zeigt die geschützte Struktur an und bearbeitet veränderliche Gruppen- und Fußkomponenten über dedizierte Command-Modale. Gruppenanlage, -umbenennung, -sortierung, explizite Neunummerierung, Same-Category-Stromkreiswechsel, geschütztes Unterbaumlöschen und Stromkreisschutz sind integriert. PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und Transaktionsgrenzen sollen portabel bleiben; Schema und Betriebsmodell benötigen bei einem späteren Wechsel trotzdem einen eigenen PostgreSQL-Adapter. ## Revit-CSV-Grundlage Der reine Adapter unter `src/external-model/csv` validiert die projektweite Transport- und Mappingkonfiguration und parst UTF-8-CSV verlustarm in eine klassifizierte Zellmatrix. Er erkennt Metadaten, Header, Passthrough-, Objekt- und Verdachtszeilen, prüft IFCGUIDs und serialisiert unveränderte Revit-Exporte einschließlich BOM, CRLF und Quotierung bytegleich zurück. Der lokale Befehl `npm run revit:verify-reference` prüft die bereitgestellte Referenzdatei. `external_csv_configurations` enthält höchstens eine versionierte Konfiguration je Projekt. `external-csv-configuration.update` schreibt Anlage, Änderung oder Entfernung zusammen mit Revision und persistentem Undo/Redo. Snapshot-Schema 5, Restore und portabler Projekttransfer enthalten diesen Zustand; beim Duplizieren werden Konfigurations-UUID und Projektlink remapped. Migration `0003` ergänzt die relationale Grundlage für genau eine implizite `revit_csv`-Quelle je Projekt, geordnete Importbatches, normalisierte Quellraummappings und über `(sourceId, ifcGuid)` eindeutige externe Objekte. Batches halten Konfigurationssnapshot, Originalbytes und die klassifizierte Zellmatrix zusammen. Migration `0004` ergänzt die monotone projektweite Konfigurationsversion am Batch, getrennt von der Schema-Version des Konfigurationsformats. Quellwerte, lokale Planungswerte und Override-Metadaten der Objekte sind getrennt; optionale Links auf Raum, Verteilung, ProjectDevice und CircuitDeviceRow bleiben nullable. `ExternalModelStateRepository` liest den vollständigen Zustand deterministisch und unterscheidet unbekannte von noch nicht importierten Projekten. Der letzte bestätigte Batch wird aus der Batchreihenfolge abgeleitet statt redundant an der Quelle gespeichert. Snapshot/Transfer v5 erfasst zusätzlich `manualQuantity`; Version 4 wird mit dem bisherigen Gesamtwert als manuellem Anteil hochgestuft. Originalbytes, Matrix und alle internen Links werden weiterhin vollständig erfasst, stellt sie in Fremdschlüsselreihenfolge wieder her und remappt beim Projektduplikat jede interne UUID; IFCGUID und Quelltransport bleiben unverändert. `external-import.apply-initial` ist die atomare Schreibgrenze für einen bestätigten Erstimport. Vor dem Insert prüft sie den aktuellen Konfigurationsstand, SHA-256 und Base64-Kanonizität, parst die Originalbytes erneut, vergleicht Matrix, vollständige IFCGUID-Menge und Quellwerte und validiert alle Raum-, Verteiler- und ProjectDevice-Links gegen das Projekt. Nicht explizit als Override markierte Planungswerte müssen dem regelbasierten Vorschlag entsprechen. Der Command erzeugt und verknüpft ausdrücklich keine CircuitDeviceRow; Undo entfernt den vollständigen unveränderten Importzustand, Redo verwendet dieselben UUIDs und Bytes. Bloße Vorschau und Importplanung erzeugen weiterhin keine dieser Zeilen. Migration `0005` trennt bei `CircuitDeviceRow` den manuellen Mengenanteil vom weiterhin materialisierten Gesamtwert. Bestehende Daten werden mit `manualQuantity = quantity` übernommen. Die Fachregel für spätere externe Zuordnungs-Commands lautet `quantity = manualQuantity + Summe der effectiveQuantity aller verknüpften ExternalModelObjects`; Link und beide Mengenwerte müssen innerhalb desselben Projekt-Commands wechseln. `external-object.update-row-assignment` ist diese atomare Schreibgrenze für bestehende Gerätezeilen. Sie weist mehrere externe Einzelobjekte zu, verschiebt sie zwischen vorhandenen Rows oder löst ihre Links. Vor dem Schreiben werden die vollständigen erwarteten Row- und Objektsnapshots, Projektzugehörigkeit, Verteilung, Gruppenkategorie, Raum und Selektionskennzeichen geprüft. Abweichende lokale Planungswerte benötigen eine explizite Konfliktbestätigung; unterschiedliche Räume, Kategorien oder Selektionskennzeichen werden nicht still zusammengeführt. Alle betroffenen materialisierten Mengen werden aus dem unveränderten manuellen Anteil und sämtlichen Ziel-Objektlinks validiert. Die Gegenrichtung enthält dieselben vollständigen Snapshots und funktioniert nach Neustart; beim Lösen des letzten Objekts bleibt die Row bestehen. `external-object.assign-to-new-row` fügt unzugeordnete Objekte atomar als neue Gerätezeile in einen vorhandenen Stromkreis ein. Der manuelle Anteil dieser Row ist null; Gesamtmenge, Raum, Verteilung, Kategorie, Selektionskennzeichen und Planungswerte werden vor dem Schreiben geprüft. Undo löst exakt diese Objekte und löscht die erzeugte Row nur, wenn sie und ihre Linkmenge unverändert sind. Der Löschbefehl ist ausschließlich für die Projekthistorie zugelassen; eine normale spätere Trennung lässt die Row gemäß der allgemeinen Zuordnungsregel bestehen. `external-object.assign-to-new-circuit` erzeugt einen neuen Stromkreis mit vorab festgeschriebenem BMK, abgeleiteter Projektspannung, genau einer externen Row und verpflichtendem Standardschutz zusammen mit den Objektlinks. Verteilungs-, Gruppen-, Raum-, Mengen- und Planungsregeln werden vor dem Insert gegen den aktuellen Projektstand geprüft. Der historische Gegenbefehl ist kein allgemeiner Löschpfad: Er entfernt den vollständigen Circuit-Teilbaum nur bei unverändertem Snapshot und unveränderter Objektlinkmenge. Redo verwendet exakt dieselben IDs, dasselbe BMK und denselben Schutzsnapshot. Die drei Zuordnungsadapter teilen sich `external-object-assignment.persistence.ts` für Snapshotvergleich, Kompatibilitätsregeln und link-sichere Updates. `GET /api/projects/:projectId/circuit-lists/:circuitListId/external-objects` liefert die verteilungsbezogene Read-Projektion für den späteren Editor-Drawer. Die CircuitList bestimmt den Verteiler serverseitig; die Antwort enthält nur dessen externe Objekte, sortiert unzugeordnet vor zugeordnet, sowie kompakte Quell-, Raum-, Planungs- und Linkangaben. Originalbytes und vollständige Command-Snapshots werden nicht an diese Oberfläche gegeben. Ohne optionalen Revit-Import ist die Projektion leer. `GET` und `PUT /api/projects/:projectId/external-csv/configuration` lesen oder ändern die Konfiguration; der PUT plant Identität und nächsten Konfigurationsstand serverseitig und verwendet den typisierten Command. `POST /api/projects/:projectId/external-csv/preview` nimmt Dateiname und Base64-kodierte CSV bis 18 MB entgegen. Der zustandsfreie Service liefert Hash, Dialekt, Header, Klassifikationszahlen, Verdachtszeilen und gemappte Objektquellwerte. Er schreibt weder Entwurf noch Projektdaten. `POST /api/projects/:projectId/external-csv/initial-import/plan` verwendet denselben begrenzten Transport, setzt eine gespeicherte Konfiguration und einen noch leeren externen Projektzustand voraus und bleibt ebenfalls zustandsfrei. Die Antwort gruppiert Quellräume, schlägt bei genau einer identischen Raumnummer den vorhandenen Raum vor, gruppiert exakte Familie-und-Typ-Werte, liefert regelbasierte Objektplanungswerte und Konfliktzahlen und enthält die vorhandenen Ebenen, Räume, Verteilungen und ProjectDevices als Auswahlkataloge. Unbekannte Familien, ungültige Leistungen/Mengen und Objekte ohne Raum bleiben sichtbare Warnungen; es wird keine Entscheidung automatisch bestätigt. `POST /api/projects/:projectId/external-csv/initial-import/apply` nimmt Datei, erwarteten Hash, Konfigurationsversion und Projektrevision erneut entgegen. Vollständige Entscheidungen ordnen jeden Quellraum optional einem vorhandenen Raum und einer Standardverteilung sowie jede exakte Familiengruppe optional einem vorhandenen ProjectDevice zu. Unklassifizierte Familien oder fehlende Gruppenentscheidungen blockieren. Der Server parst und plant erneut, erzeugt stabile interne UUIDs und übergibt den vollständigen Zustand an `external-import.apply-initial`; CSV-Stromkreiswerte bleiben reine Quellwerte und alle `circuitDeviceRowId` bleiben `null`. Die Projektseite öffnet über „Revit-CSV“ ein deutsches Modal für Dialekt, Spaltenzuordnung, zusätzliche Quellfelder und exakte Familie-und-Typ-Regeln. Nach dem revisionierten Speichern kann eine lokale CSV gewählt und über den zustandsfreien Endpunkt geprüft werden. Die Oberfläche zeigt Kennzahlen und nur die ersten 25 erkannten Objekte; die vollständigen Objekte bleiben in der Response verfügbar, ohne alle Zeilen in den DOM einzufügen. Der anschließende Erstimport-Wizard gruppiert Quellräume und exakte Familie-und-Typ-Werte. Er zeigt eindeutige, mehrdeutige und neue Raumtreffer, lässt vorhandene Räume, Standardverteilungen und ProjectDevices explizit zuordnen und blockiert die Übernahme bei unklassifizierten Familien. Erst nach einem Bestätigungsdialog wird der vollständige Import als eine persistente, rückgängig machbare Projektrevision angewendet. Dabei entstehen keine Stromkreise oder Gerätezeilen. Fehlende Räume und ProjectDevices können direkt aus der jeweiligen Gruppe über die regulären Projektformulare angelegt werden. Revit-Werte befüllen diese Formulare vor, werden aber vor dem Speichern sichtbar vom Nutzer bestätigt. Die Kataloganlage ist jeweils ein eigener Projekt-Command; der Wizard übernimmt den neuen Datensatz anschließend in seinen lokalen Auswahlkatalog und markiert ihn für die betroffene Gruppe. ## Noch nicht unterstützt - Mehrbenutzerbetrieb und Konfliktauflösung - Editor-Oberfläche für die vorhandene Zuordnungslogik, Folgeimport und strukturtreuer Revit-Rückexport - vollständige elektrische Dimensionierung - Produktionsdeployment Details: [Bekannte Einschränkungen](circuit-list-editor-known-limitations.md) und [Zukunftsarchitektur](project-history-and-external-model-architecture.md).