# Architektur — Standalone Browser-BIM (cad)

> Stand: 2026-06-29
> Vision, Phasen und Backlog: [ROADMAP.md](ROADMAP.md). Konventionen: [CONVENTIONS.md](CONVENTIONS.md).
> Detail-Designs: [docs/design/elements.md](docs/design/elements.md) ·
> [docs/design/plans-output.md](docs/design/plans-output.md) ·
> [docs/design/resources-graphics.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`):

1. **Zeichnungsebenen** (`DesignLevel` / `DrawingLevel`) — die obersten
   Dokument-Abschnitte. Zwei produktive Arten:
   - **Geschoss** (`kind:"floor"`): `floorHeight`, `cutHeight`,
     `baseElevation` (akkumuliert via `recomputeFloorElevations`), `visible`/`locked`.
   - **Schnitt/Ansicht** (`kind:"section"|"elevation"`): `linePoints`,
     `directionSign`, Höhenbereich, Tiefe.
   - **Zeichnung** (`kind:"drawing"`): freie 2D-Ebene ohne Geschossbezug.
2. **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_SCHEMA` aus `launcher/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 behalten `DrawingLevel` als
> Union (kind=floor ≙ Design Layer, kind=section/elevation/drawing ≙ Drawing
> Layer) und führen `Sheet` erst 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.

```ts
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)

```ts
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)

```ts
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):

```ts
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) |

```ts
// 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](https://developer.mozilla.org/docs/Web/API/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**:

```ts
// 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)
```

```ts
// 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)` und
  `levelGroup.visible = vis.floors.has(floorId)` — entspricht DOSSIERs
  `apply_visibility`, aber als simples `.visible`-Toggle statt Layer-Table-Modify.
- **Farbe/Material:** Component → `MeshStandardMaterial` (PBR später), per
  `componentId` gecacht (wie heute `layerMaterial`-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):

1. **3D-Viewport im Plan-Modus:** `THREE.Plane(normal=(0,0,-1), constant=cutZ)` über
   `renderer.clippingPlanes` + `material.clippingPlanes`. `clip.ts` setzt die Ebene
   pro aktivem Geschoss (`cutZ = baseElevation + cutHeight`), Kamera orthografisch
   von oben. So sieht man den **echten geschnittenen Volumenkörper**.
2. **Symbolischer SVG-Grundriss** (der Hauptweg, schnell + sauber): `generatePlan.ts`
   erzeugt 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 in `directionSign`-
  Richtung um `depthBack` versetzt) + 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

1. **Dieses Dokument + das relevante Detail-Design** (elements/plans-output/
   resources-graphics) lesen.
2. **Dann das betroffene Modul** lesen (nicht raten).
3. **Erst danach editieren**; `Project` immer immutabel via `store.apply()` ändern.
4. **Verifizieren:** `npx tsc -b`, `npm run build`, Screenshot via
   `node scripts/probe.mjs` — Geometrie visuell prüfen.
5. **Dieses Dokument aktuell halten**, wenn sich Patterns/Mapping ändern.
