Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem, dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en) sowie Projektdokumentation und Probe-Harness.
24 KiB
Architektur — Standalone Browser-BIM (cad)
Stand: 2026-06-29 Vision, Phasen und Backlog: ROADMAP.md. Konventionen: CONVENTIONS.md. Detail-Designs: docs/design/elements.md · docs/design/plans-output.md · docs/design/resources-graphics.md.
Dieses Dokument beschreibt, wie die Standalone-Browser-App gebaut wird und
wie sie die Konzepte des DOSSIER-Rhino-Plugins in Browser-Äquivalente übersetzt.
DOSSIER ist ein Rhino-8-Plugin (Python + React-WebView); cad ist die eigen
ständige Browser-Variante: kein Rhino-Document, kein doc.Strings, kein
IronPython — stattdessen ein eigenes typisiertes Datenmodell in TypeScript,
gerendert über Three.js (3D) und SVG (Plan), gespeichert als JSON-Datei.
Alle Bezeichner im Code sind englisch (Vectorworks-Terminologie); Prosa und UI-Texte sind deutsch. Einheiten intern in Metern.
0. Leitprinzip — ein Modell, viele Darstellungen
Das semantische Gebäudemodell ist die einzige Wahrheit. Jede Sicht (3D,
Grundriss, Schnitt, Ansicht) ist eine reine Ableitung (derive) daraus.
Darstellung (Detailgrad, Stile, Schraffuren, Overrides) wird beim Rendern
angewandt, nie in die Geometrie eingebacken.
┌──────────────────────────────────────────┐
│ Project (semantisches Modell, JSON) │ ← einzige Wahrheit
│ resources · types · designLevels · │
│ layers · elements │
└──────────────┬───────────────────────────┘
│ pure derive()
┌───────────────────────┼────────────────────────────┐
▼ ▼ ▼
Scene3D (Three.js) PlanModel → SVG SectionModel → SVG
buildScene() generatePlan() generateSection() (HLR)
│ │ │
└────────── beide lesen dieselben joins/components/styles ──────────┘
Das steht im Spike bereits: generatePlan.ts und Viewport3D.tsx extrudieren
dasselbe gehrte Band-Polygon (clippedBand). Diese Symmetrie ist der Kern
und wird beim Ausbau strikt gehalten.
1. Repo-Struktur (Ziel)
Wächst aus dem heutigen src/ (model/plan/viewport/ui). Module sind klein und
fachlich geschnitten — wir vermeiden bewusst den elemente.py-Monolithen
(7244 LOC) aus DOSSIER (dort als Schwachstelle #4.1 dokumentiert).
src/
model/
types.ts // Project, DesignLevel, LayerCategory, Element-Union (existiert)
geometry.ts // 2D/3D-Mathe ohne Kernel (existiert)
joins.ts // Wand-Verschneidung (Gehrung; später Prio-T/X) (existiert)
sampleProject.ts // Demo-Haus (existiert)
project.ts // Factory, Defaults, Migrationen
selectors.ts // abgeleitete Reads (visibleCodes, elementsOnLevel…)
ids.ts // newId(prefix) — UUID-Erzeugung
elements/ // pro Bauteil ein Modul (Daten + Generierung)
wall.ts opening.ts slab.ts stair.ts roof.ts structure.ts space.ts
resources/
componentManager.ts hatchManager.ts lineManager.ts symbolLibrary.ts
overrides.ts // regelbasierte Engine (Condition → Action)
store/
store.ts // Zustand-Store: { project, ui }, Aktionen, Undo/Redo
persistence.ts // Datei save/load (JSON), Autosave (IndexedDB)
history.ts // Undo/Redo-Ring
plan/
generatePlan.ts // Grundriss aus Footprint + Symbolik (existiert)
generateSection.ts // Schnitt/Ansicht aus 3D-Projektion (HLR-Worker)
PlanView.tsx // SVG-Renderer + Pan/Zoom/Grips (existiert)
primitives.ts // Primitive-Union, SVG-Serializer, DXF/PDF-Export
viewport/
Viewport3D.tsx // Three.js-Szene aus dem Modell (existiert)
scene.ts // buildScene(project) → THREE.Group (Layer-Spiegel)
clip.ts // Plan-/Schnitt-Clipping über THREE.Plane
camera.ts // Kamera-Presets, Norden-Rotation
sheets/
layout.ts // Sheet/Viewport-Datenmodell
SheetEditor.tsx // Plansatz-Editor
exportPdf.ts // Vektor-PDF (svg → pdf-lib / jsPDF)
workers/
geometry.worker.ts // Booleans (Öffnungen) + HLR via Comlink
ui/
App.tsx Navigator panels… // React-Oberfläche (heute in App.tsx, wird gesplittet)
2. Datenmodell
2.1 Zwei unabhängige Achsen (DOSSIER-Modell, im Spike umgesetzt)
DOSSIER trennt zwei orthogonale Konzepte, persistiert als zwei getrennte
JSON-Bäume (dossier_zeichnungsebenen, dossier_ebenen). Wir übernehmen das
1:1 in types.ts (existiert bereits als drawingLevels + layers):
- Zeichnungsebenen (
DesignLevel/DrawingLevel) — die obersten Dokument-Abschnitte. Zwei produktive Arten:- Geschoss (
kind:"floor"):floorHeight,cutHeight,baseElevation(akkumuliert viarecomputeFloorElevations),visible/locked. - Schnitt/Ansicht (
kind:"section"|"elevation"):linePoints,directionSign, Höhenbereich, Tiefe. - Zeichnung (
kind:"drawing"): freie 2D-Ebene ohne Geschossbezug.
- Geschoss (
- Ebenen (
LayerCategory) — das Grafik-Kategorie-Schema, das in jedem Geschoss gilt. Baum mit{code, name, color, lw, visible, locked, hatch?, children}. Codes 1:1 wie DOSSIER (DEFAULT_LAYER_SCHEMAauslauncher/src/App.jsx):00 Raster · 01 Vermessung · 20 Wände (└21 Türen/Fenster, 22 Möbel, 25 Stützen) · 30 Decken · 31 Dächer · 35 Träger · 50 Text · 60 Plangrafik …
Ein Element kennt sein Geschoss (floorId) und seine Ebene
(categoryCode). Sichtbarkeit ergibt sich aus dem Schnitt (Geschoss geschoss × code
Matrix), genau wie DOSSIERs apply_visibility(z_mode, e_mode) in layer_builder.py.
Begriffsnotiz: Heute heißt der Typ im Code
DrawingLevel. ROADMAP §5 nennt die Modell-Eingabe (Geschosse) Vectorworks-konform Design Layer und die abgeleitete Ausgabe Drawing Layer / Sheet. Wir behaltenDrawingLevelals Union (kind=floor ≙ Design Layer, kind=section/elevation/drawing ≙ Drawing Layer) und führenSheeterst separat ein (§2.5, plans-output.md). Kein Rename ohne expliziten Auftrag.
2.2 Ressourcen-Bibliotheken (Vectorworks-Stil)
Neuer Block Project.resources (heute provisorisch als flache materials[]).
Alles verweist per id — zentral änderbar. Details: resources-graphics.md.
interface Resources {
lineStyles: LineStyle[]; // Line Manager: { id, name, weight(mm), color, dash:number[] }
hatches: Hatch[]; // Hatch Manager: { id, name, pattern, scale, angle, lineStyleId }
components: Component[]; // Component Manager (= DOSSIER-Material erweitert):
// { id, name, hatchId, color3d, texture3d?, joinPriority }
}
joinPriority ist DOSSIERs _MATERIAL_PRIO (Beton 800 … Putz 100) als
Daten statt Hardcode — steuert die Prioritäts-T-Verschneidung (Risiko #1).
2.3 Aufbauten (mehrschichtige Typen)
interface WallType { id; name; layers: { componentId; thickness }[]; }
interface SlabType { id; name; layers: { componentId; thickness }[]; }
Schichtdicke liegt am Layer, Priorität am Component (so muss man Prio nur
einmal pflegen). 3D und Plan lesen dieselben layers[].
2.4 Elemente
Diskriminierte Union Element (heute Wall | Door), erweitert um
Window | Slab | Stair | Roof | Column | Beam | Space | Draw2d. Jedes Element:
{ id, type, floorId, categoryCode, ... }. Gehostete Elemente (Tür/Fenster)
tragen hostWallId statt floorId (Geschoss ergibt sich aus der Wand). Volle
Felder: elements.md.
2.5 Pläne / Output (eigene Achse)
interface Sheet { // Plansatz-Blatt (≙ DOSSIER Layout/PageView)
id; name; paper: "A0".."A4"|"Letter"; landscape: boolean;
viewports: SheetViewport[]; // platzierte Ableitungen + Titelblock
folder?: string;
}
interface SheetViewport { // ≙ DOSSIER Detail + gebundener Ausschnitt
id; rect: Rect; sourceViewId: string; // referenziert DrawingLevel oder ViewSnapshot
scale: number; // 1:N
}
interface ViewSnapshot { // ≙ DOSSIER Ausschnitt (View-Snapshot)
id; name; folder?;
camera: CameraState; scale: number; detailLevel: DetailLevel;
visibility: VisibilityState; // pro Geschoss/Ebene
overrides?: { presetId?; enabled };
}
3. State, Persistenz, Undo/Redo
3.1 Store — Zustand
Heute: useState<Project> in App.tsx, immutabel via setProject. Das skaliert
nicht für Werkzeuge + Undo. Migration auf Zustand (ROADMAP §4):
interface AppState {
project: Project; // das Dokument
ui: { // flüchtig, NICHT persistiert/undobar
activeLevelId; activeViewType; selection: string[];
activeTool: ToolId; hover; cameraByView; planTransform;
};
// Aktionen mutieren via Immer-Producer; jede Modell-Mutation pusht History.
apply(mutator: (p: Project) => void): void;
undo(): void; redo(): void;
}
Trennung Modell ↔ UI ist hart: nur project ist undobar und wird gespeichert.
ui (Auswahl, Kamera, aktives Werkzeug) lebt separat. Das ersetzt DOSSIERs
sc.sticky-Bus — wo DOSSIER cross-modul über Sticky-Keys kommuniziert, lesen bei
uns einfach alle Komponenten denselben Store (reaktiv). Kein Sticky, kein
Bridge-Polling, keine is not None-Lücken (DOSSIER-Schwachstelle #4.4).
3.2 Persistenz — JSON-Datei statt doc.Strings
DOSSIER speichert alles in doc.Strings (Key-Value am Rhino-Document) +
globale Presets als JSON-Dateien im User-Home. Browser-Äquivalent:
| DOSSIER | Browser (cad) |
|---|---|
doc.Strings.SetString(key, json) |
Feld im Project-Objekt (ein JSON-Baum) |
.3dm-Datei mit eingebetteten Strings |
.cad.json-Datei via File System Access API |
Globale Presets (~/Library/.../*.json) |
LocalStorage (cad.presets.*) + Export/Import |
| Launcher schreibt Settings-Datei | IndexedDB Autosave (Phase 5) |
// persistence.ts
const FORMAT_VERSION = 1;
function serialize(p: Project): string // JSON.stringify({ version, project })
function deserialize(text: string): Project // + migrate(version) bis aktuell
async function saveToFile(p: Project): Promise<void> // showSaveFilePicker → .cad.json
async function loadFromFile(): Promise<Project> // showOpenFilePicker; Fallback <input type=file>
async function autosave(p: Project) // IndexedDB, debounced 2 s
- Save/Load: File System Access API
(
showSaveFilePicker/showOpenFilePicker); Fallback Download-Blob +<input type=file>für Firefox/Safari. - Autosave/Recovery: IndexedDB (via
idb), entkoppelt vom expliziten Speichern. - Cross-Projekt-Presets: LocalStorage mit Namespacing (
cad.presets.overrides,cad.presets.wallTypes, …) + JSON-Export/Import — ersetzt DOSSIERs~/Library/.../override_presets.json(overrides.py). - Migrationen:
migrate(data, fromVersion)-Kette, additiv. DOSSIER macht das per Sticky-Prefix (traite_→pause_→dossier_); wir machen es versioniert im Datei-Schema.
3.3 Undo/Redo
DOSSIER überlässt Undo Rhino (und hat dadurch Cache-Stale-Bugs, Schwachstelle #4.3). Wir besitzen das Dokument selbst → eigener History-Ring:
// history.ts — Snapshots des project-Teilbaums (strukturelles Sharing via Immer-Patches)
interface History { undo: Patch[][]; redo: Patch[][]; }
Jede apply()-Mutation erzeugt Immer-Patches → Push auf undo. Da abgeleitete
Sichten pure sind, ist nach undo() kein Cache zu invalidieren — der
ganze Cache-Stale-Komplex aus DOSSIER (_JOINTS_CACHE_KEY, Material-Cache)
entfällt strukturell.
4. Rendering
4.1 Three.js-Szene spiegelt den Ebenen-Baum
DOSSIER baut in Rhino eine Layer-Hierarchie Zeichnungsebene → CODE_Name-Sublayer
(layer_builder.build_layers) und hängt jedes Objekt an den passenden Sublayer.
Browser-Spiegel: ein THREE.Group-Baum mit identischer Struktur, gebaut aus
dem Modell (nicht persistiert — reine Ableitung):
scene
└─ levelGroup[floorId] (y-Offset = baseElevation) ← Geschoss
└─ categoryGroup[categoryCode] (userData.code) ← Ebene
└─ element meshes (userData.elementId)
// scene.ts
function buildScene(project: Project, vis: VisibilityState): THREE.Group
function syncScene(root: THREE.Group, project, vis) // diff statt full rebuild (Perf)
- Sichtbarkeit:
categoryGroup.visible = vis.codes.has(code)undlevelGroup.visible = vis.floors.has(floorId)— entspricht DOSSIERsapply_visibility, aber als simples.visible-Toggle statt Layer-Table-Modify. - Farbe/Material: Component →
MeshStandardMaterial(PBR später), percomponentIdgecacht (wie heutelayerMaterial-Cache, aber projektweit). - „Grau/gesperrt"-Modi (DOSSIER
grey/grey_locked): graues Override-Material auf der Gruppe statt Layer-Color-Tausch.
4.2 Grundriss = Clipping-Ebene + symbolische Generierung
DOSSIER zeigt den Grundriss eines Geschosses, indem es eine Clipping-Plane auf
okff + schnitthöhe legt, Normale −Z (layer_builder.update_clipping_plane).
Zwei Browser-Pfade — beide nötig (ROADMAP §3):
- 3D-Viewport im Plan-Modus:
THREE.Plane(normal=(0,0,-1), constant=cutZ)überrenderer.clippingPlanes+material.clippingPlanes.clip.tssetzt die Ebene pro aktivem Geschoss (cutZ = baseElevation + cutHeight), Kamera orthografisch von oben. So sieht man den echten geschnittenen Volumenkörper. - Symbolischer SVG-Grundriss (der Hauptweg, schnell + sauber):
generatePlan.tserzeugt Vektor-Primitive direkt aus Parametern — Wand-Schnittbänder, Öffnungs-Aussparungen, Tür-Schwenkbögen — ohne Mesh zu schneiden. Steht im Spike. Ausbau: Schraffuren, Lauflinien, Bemaßung, LoD (plans-output.md).
4.3 Schnitt/Ansicht = Kamera + Schnittebenen + HLR
DOSSIER setzt 1–2 Clipping-Planes (Cut + Back), Parallel-Projektion senkrecht zur
Linie, zoomt auf die BBox (schnitte.activate_schnitt). Browser:
- 3D-Vorschau: zwei
THREE.Plane(Cut auf der Linie, Back indirectionSign- Richtung umdepthBackversetzt) + orthografische Kamera senkrecht zur Linie. - Vektor-Ergebnis (Risiko #4): Hidden-Line-Removal durch das zusammengebaute
Gebäude → saubere Linien als SVG. Via OpenCascade.js (
HLRBRep) im Web Worker (Comlink), Ergebnis gecacht pro (Schnitt, sichtbare Ebenen, Modell-Hash). Geschnittene Bauteile bekommen Component-Schraffur (Section-Style, resources-graphics.md). Details: plans-output.md.
4.4 SVG-Renderer + Geometrie-Booleans
- Plan-Output: SVG (
PlanView.tsx).Primitive-Union (polygon/line/arc/text/ hatch) → SVG-Elemente; derselbe Serializer speist DXF- und PDF-Export. - Öffnungs-Booleans (Risiko #2): Tür/Fenster schneidet Loch in Wand. Für 3D reicht heute das Aussparen per Segment-Extrusion (im Spike). Für exakte B-Rep- Verschneidung (und IFC) OpenCascade.js oder Manifold im Worker — Entscheidung in Phase 0 (ROADMAP §8): OCC = exakt/B-Rep, Manifold = schnell/Mesh.
5. React-Panel-Struktur
DOSSIER ist eine Sammlung getrennter WebView-Panels (EBENEN, ELEMENTE,
GESTALTUNG, OBERLEISTE, MASSSTAB, AUSSCHNITTE, DIMENSIONEN, LAYOUTS, OVERRIDES),
die über die Python-Bridge + sc.sticky kommunizieren. Im Browser ist alles
eine SPA mit einem geteilten Store — die Panels werden zu Docking-Bereichen.
┌─────────────────────────────────────────────────────────────────────┐
│ TopBar Werkzeuge · Ansichtstyp · Massstab · Snaps · Save/Load │ ≙ OBERLEISTE+MASSSTAB
├──────────────┬──────────────────────────────────────┬───────────────┤
│ Navigator │ Viewport (Three.js oder SVG-Plan) │ Inspector │
│ ┌──────────┐ │ ┌────────────────────────────────┐ │ ┌───────────┐ │
│ │Zeichnungs│ │ │ 3D / Grundriss / Schnitt / │ │ │ aktives │ │ ≙ ELEMENTE/
│ │-ebenen │ │ │ Ansicht — abgeleitet aus dem │ │ │ Element + │ │ GESTALTUNG-
│ ├──────────┤ │ │ Modell │ │ │ Stil │ │ Properties
│ │ Ebenen │ │ └────────────────────────────────┘ │ └───────────┘ │
│ │ (Baum) │ │ │ Manager-Tabs: │
│ │ │ │ │ Components · │ ≙ Resource-
│ └──────────┘ │ │ Hatch · Line │ Manager
├──────────────┴──────────────────────────────────────┴───────────────┤
│ BottomBar Koordinaten · Snaps · Statusmeldungen │
└─────────────────────────────────────────────────────────────────────┘
Modal/Drawer: Ausschnitte · Sheets/Layouts · Overrides · Kamera-Presets · SIA-Bilanz
Komponenten-Map (heute alles in App.tsx; wird gesplittet):
| Bereich | DOSSIER-Panel | cad-Komponente |
|---|---|---|
| Zeichnungsebenen-Liste | EBENEN (oben) | Navigator/DrawingLevels.tsx |
| Ebenen-Baum | EBENEN (Baum) | Navigator/LayerTree.tsx (Spike: CategoryRow) |
| Top-Bar (View/Snaps/Massstab) | OBERLEISTE, MASSSTAB | TopBar.tsx |
| Element-Eigenschaften | ELEMENTE, ELEMENT-PROPERTIES | Inspector/ElementProps.tsx |
| Stil/Attribute der Auswahl | GESTALTUNG | Inspector/StylePanel.tsx |
| Component/Hatch/Line | (Project-Settings, mass_style) | managers/*Manager.tsx |
| View-Snapshots | AUSSCHNITTE | panels/Snapshots.tsx |
| Plansätze | LAYOUTS | sheets/SheetEditor.tsx |
| Regelbasierte Overrides | OVERRIDES | panels/Overrides.tsx |
| Bemaßung | DIMENSIONEN | panels/Dimensions.tsx |
| Element-Übersicht (BIM-Tree) | ELEMENTE-ÜBERSICHT | panels/ElementTree.tsx |
| Kamera-Presets | KAMERA | panels/CameraPresets.tsx |
Werkzeug-System (ersetzt DOSSIERs Rhino-Command-Aliases cmd/wand.py etc.):
ein Tool-Interface mit onPointerDown/Move/Up, das Vorschau-Primitive
zurückgibt und beim Bestätigen store.apply() ruft. Pointer-Events auf Canvas/SVG
mit Snap-Engine (Endpunkt/Mitte/Schnitt/Ortho/Raster). Tabelle der Tools:
elements.md §8.
6. Rhino → Browser — Mapping-Tabelle
| DOSSIER (Rhino-Plugin) | cad (Standalone-Browser) | Anmerkung |
|---|---|---|
Rhino RhinoDoc |
Project (TS-Objekt im Store) |
einzige Wahrheit |
doc.Strings[key]=json |
Feld im Project-JSON |
persistiert in .cad.json |
.3dm-Datei |
.cad.json via File System Access API |
+ IndexedDB-Autosave |
| Globale Presets (~/Library/*.json) | LocalStorage + Export/Import | cross-Projekt |
sc.sticky (cross-modul Bus) |
Zustand-Store (reaktiv) | kein Polling, kein None-Bug |
panel_base.BaseBridge / WebView-IPC |
direkte React-Props/Store | keine document.title-Hacks |
document.title="RHINOMSG::" Polling |
entfällt | SPA, kein WebView |
| Eto.Forms Satelliten-Fenster | React-Modal/Drawer | z.B. Ausschnitt-Settings |
| Rhino Layer-Tabelle (Sublayer-Baum) | LayerCategory[]-Baum + THREE.Group-Spiegel |
scene.ts |
layer_builder.build_layers |
buildScene (Ableitung) |
Gruppen statt Layer |
layer.PlotWeight |
LineStyle.weight (mm) → SVG stroke-width |
massstabsabh. (§plans) |
SectionStyle (Layer) |
Component-Schraffur beim Schnitt-Rendern | resources-graphics.md |
Clipping-Plane (AddClippingPlane) |
THREE.Plane + renderer.clippingPlanes |
clip.ts |
vp.ChangeToParallelProjection |
THREE.OrthographicCamera |
camera.ts |
vp.GetFrustum() → Massstab |
Frustum-Breite (ortho) → 1:N | gleiche Mathe (§plans) |
| CoreGraphics DPI-Auto-Detect | window.devicePixelRatio + CSS-px |
Browser kennt DPI nativ |
Rhino.Geometry.Brep-Booleans |
OpenCascade.js / Manifold (Worker) | Öffnungen, HLR |
HLRBRep (über Rhino-Display) |
OpenCascade.js HLRBRep (Worker) |
Schnitt/Ansicht-Linien |
| Rhino-Grips + DisplayConduit | Pointer-Events auf SVG/Canvas + Overlay | grip-editing (elements.md) |
Rhino.Input.Custom.GetPoint |
Tool-Pointer-Handler + Snap |
Werkzeug-System |
MouseCallback (Doppelklick Schnitt) |
onDoubleClick auf SVG-Symbol |
plans-output.md |
| Rhino Undo/Redo | eigener History-Ring (Immer-Patches) | kein Cache-Stale |
HatchPattern-Tabelle |
Hatch-Ressourcen + SVG <pattern> |
resources-graphics.md |
Linetype-Tabelle |
LineStyle.dash[] → SVG stroke-dasharray |
resources-graphics.md |
TextEntity / Rich-Text |
SVG <text> / HTML-Annotation |
plans-output.md |
RhinoPageView (Layout) |
Sheet + SheetEditor |
plans-output.md |
FilePdf Multi-Page-Export |
svg → pdf-lib/jsPDF (Vektor) |
plans-output.md |
| Swisstopo/OSM via .NET HttpClient | fetch() (CORS-fähige STAC/Overpass-APIs) |
Phase 4 |
| LV95↔WGS84 (Python-Formeln) | dieselben Formeln in TS portiert | Phase 4 |
| IronPython 2.7/3 Runtime-Risiken | entfällt (TS/Browser) | — |
7. Was wir von DOSSIER übernehmen — und was nicht
Übernehmen (bewährte Konzepte):
- Zwei-Achsen-Dokumentmodell (Zeichnungsebenen × Ebenen).
- Layer-Codes/Farben/lw 1:1 (
DEFAULT_LAYER_SCHEMA). - Component/Hatch/Line-Manager mit id-Verweisen +
joinPriority. - Ansichtstypen = Kamera + optionaler Schnitt (vereinheitlicht).
- LoD (
darstellung: einfach/standard/detail) mit Dokument-Override. - Regelbasierte Overrides (Condition → Action, additive Priorität, reversibel).
- Ausschnitte (View-Snapshots), Layer-Kombinationen, Massstab-pro-Viewport.
- SIA-416-Räume, Norden-Rotation, Stil-Kataloge.
Bewusst anders (Browser-nativ, Schwachstellen vermeiden):
- Kein Sticky-Bus → ein reaktiver Store (vermeidet DOSSIER #4.4/#4.5/#4.6).
- Kein Monolith → Bauteil-Module statt 7244-LOC-
elemente.py(#4.1). - Pure Ableitungen statt mutierter Doc-Objekte → kein Cache-Stale (#4.3), kein Undo/Redo-Loch.
- Versioniertes Datei-Schema statt UserString-Sticky-Migration.
- Persistenz im Project-JSON statt verteilt über
doc.Strings.
Anti-Over-Engineering (aus DOSSIER-CONVENTIONS.md übernommen): keine Abstraktion
ohne konkretes Problem; kein try/catch: {} als Bug-Versteck; erst grep/lesen,
dann editieren; Wand-Geometrie nie „nebenbei" refactoren.
8. Reihenfolge bei Code-Arbeit
- Dieses Dokument + das relevante Detail-Design (elements/plans-output/ resources-graphics) lesen.
- Dann das betroffene Modul lesen (nicht raten).
- Erst danach editieren;
Projectimmer immutabel viastore.apply()ändern. - Verifizieren:
npx tsc -b,npm run build, Screenshot vianode scripts/probe.mjs— Geometrie visuell prüfen. - Dieses Dokument aktuell halten, wenn sich Patterns/Mapping ändern.