# HANDOVER — Browser-BIM (cad), Standalone-Port von DOSSIER > Für die nächste Instanz. Stand: 2026-06-29. Lies zuerst > `CONVENTIONS.md`, `ROADMAP.md` und die Projektnotizen (siehe unten). ## >>> COMMIT-REGEL (verbindlich, IMMER beachten) <<< Dieses Repo darf **keinerlei Hinweise auf KI-Werkzeuge** enthalten — weder im Code/Doku noch in der Git-Historie. - **Commit-Messages:** sachlich, in der Sprache des Projekts. **NIEMALS** `Co-Authored-By:`-Trailer, „Generated with …"-Zeilen, Tool-/Modellnamen oder sonstige Urheber-Hinweise auf einen Assistenten. - **Dateien/Kommentare:** keine Erwähnung von Assistenten, Modellen oder Agenten. Wer hier weiterarbeitet, schreibt so, als wäre es Handarbeit des Teams. - Vor jedem Push kurz prüfen: `git log` und `git diff` frei von solchen Spuren. ## >>> KOORDINATION & MEMORY (bei mehreren Instanzen/Agents) <<< **Mehrere Hauptinstanzen gleichzeitig:** - Git: nur **eine** Instanz committet/pusht auf `master`, ODER jede arbeitet auf eigenem Branch und merged kontrolliert. `App.tsx`/`types.ts` ist der serielle Flaschenhals — verschiedene Features ≠ dieselbe Datei. - Dieser HANDOVER ist der **Koordinationskanal** zwischen Instanzen (liegt im Repo, wird mitgepusht). Stand hier kurz festhalten, bevor du übergibst. **Subagents:** - Bekommen **kein** Memory automatisch — nur was im Prompt steht. Regeln (v. a. die COMMIT-REGEL oben) explizit mitgeben, sonst kennt der Agent sie nicht. - Agents **nicht** committen und **nicht** ins Memory schreiben lassen. Sie liefern Diffs/Dateien/Ergebnisse zurück; die Hauptinstanz committet und pflegt das Gedächtnis. **Memory (`~/.claude/...`, außerhalb des Repos — leakt nie hierher):** - In den Kontext geladen wird nur der schlanke **Index** (eine Zeile je Eintrag); einzelne Fakten erscheinen nur bei Relevanz. Größe ist daher selten ein Problem. - **Keine automatische Bereinigung.** Gepflegt wird beim Schreiben (Duplikate aktualisieren statt anlegen, Überholtes löschen) oder auf Ansage. - Nur **beständige** Fakten ablegen, ein Fakt pro Datei, Index-Zeile knapp. Bei parallelen Schreibvorgängen ist der Index (MEMORY.md) die Contention-Stelle — vor dem Edit frisch lesen (der „modified since read"-Guard verhindert blindes Überschreiben). ## >>> STAND 2026-07-03 Abend (Nordstern-Session, Übergabe an neue Instanz) — ZUERST LESEN <<< Ein Master-Branch (alle Feature-/Worktree-Branches konsolidiert und gelöscht), alles auf origin/master gepusht, Working Tree sauber bis auf eine vorbestehende, unabhängige Materials-Library-WIP (`.gitignore`, `public/assets/materials/manifest.json`, `src/materials/library.ts`, `scripts/fetch-materials.mjs` — NICHT anfassen/reverten, ist fremde in Arbeit befindliche Sache, nicht Teil der Engine-Session). Heute gelandet, acht Commits in dieser Reihenfolge: 1. **7ca5197** Resource Manager: Materialien-Tab (PBR-Kugel-Vorschau via geteiltem Offscreen- Renderer, Suche + Kategorie-Chips, lazy per IntersectionObserver). 2. **c8a4188** render3d als WASM/WebGPU-Viewport in der App (`?engine=wasm` bzw. localStorage `cad.rendererMode`; three.js bleibt Default). Probe: scripts/probe-engine3d.mjs (HEADFUL=1 nötig — headless Chromium präsentiert keine WebGPU-Frames, gilt auch für render2d). 3. **54211b1 + 76029db** render3d/src/section.rs: Schnittebene → Cut-Polygone + sichtbare/ verdeckte projizierte Kanten, inkl. Öffnungen (Brüstung/Sturz-Teilrechtecke, Durchblick). 29 Tests, Beweis-SVG docs/welle-c-hlr-spike/section-engine-proof.svg, Doku docs/design/engine-section-pipeline.md. Eigen-Engine-Ersatz für den OCCT-Pfad (66 MB WASM). **mesh.rs (3D-Vollkörper-Extrusion) berücksichtigt Öffnungen weiterhin NICHT** — dokumentierte, bewusst offene Lücke (Cut/Ansicht-Pipeline und 3D-Solid-Mesh sind bis dahin inkonsistent). 4. **ce6bd26** 2D-Engine-Parität: WASM-Pfad deckungsgleich zum SVG-Referenzpfad (**Referenz ist `?gl=0`**, nicht der Default!). Fixes: text_scale entkoppelt, glyphon ColorMode::Web, z-basierte Maler-Reihenfolge (draw_sequence), CSS-Klassenfarben in toRenderScene, greyed 0.3, Dämmwellen-Bézier + userSpaceOnUse-Anker + widthScreen-Schraffurbreite. Probe: scripts/probe-engine-parity.mjs. 5. **4b02e40** render2d Headless: Feature `headless`, HeadlessRenderer ohne Surface (Vulkan), CLI-Bin render_png, Golden-Test (bit-exakt) tests/golden/demo.png, Doku docs/design/engine-headless.md. 6. **382771b** Vektor-PDF aus der RenderScene (**Nordstern 2 abgeschlossen**): exportPdf baut jetzt `planToRenderScene(plan)` — derselbe Aufruf wie der Viewport — und serialisiert über den neuen `src/export/sceneToPrintSvg.ts` nach Papier-mm (PEN_STEPS-Quantisierung wie bisher, widthScreen-Schraffurbreiten korrekt in mm umgerechnet, Texte jetzt als echte Vektor-Texte — der alte Pfad liess sie komplett weg). `planToPrintSvg.ts` bleibt nur als Referenz stehen (Kopfkommentar markiert sie als abgelöst). Verifiziert per `pdftoppm`: 53.51×43.69mm vs. erwartet 53.45×43.45mm bei 1:100. **Bekannte Alt-Lücke, nicht neu:** Zeilenabstand mehrzeiliger Stempeltexte in `toRenderScene.ts` ist nur bei Massstab 1:100 exakt (STAMP_REF_N), bei anderen Export-Massstäben proportional leicht daneben — betrifft Viewport identisch, nicht PDF-spezifisch, absichtlich nicht mitgefixt (out of scope für den additiven Umbau). 7. **b4c4a2c** wgpu 22 → 29, glyphon 0.6 → 0.11 (glyphon pinnt `^29`, obwohl wgpu 30 existiert). `src/engine/requestDeviceShim.ts` komplett entfernt (das 22er-Limit `maxInterStageShaderComponents` wird von 29 nicht mehr gesendet). Verifiziert: cargo check nativ (render2d/render3d/src-tauri), cargo test render2d 17/17 + Golden weiterhin bit-exakt, render3d 29/29, wasm32 --features web für beide, tsc + npm build — alles grün. **Damit sind Nordstern 2, 3, 4 (Kern) und 5 gelandet.** Verifiziert per Screenshot: App läuft im Electron-Fenster (`env -u ELECTRON_RUN_AS_NODE npm run electron`), 3D-Perspektive rendert das echte Modell über den Engine-Renderer (Umschalter unten rechts: WebGL2/Engine). **WICHTIG — was NICHT fertig ist (User hat das selbst am Screenshot bemerkt):** Die Zeichnungsebenen vom Typ „Schnitt"/„Ansicht" (z.B. „Schnitt A", „Ansicht Süd" in der Ebenen-Liste rechts) sind weiterhin **Stubs** — sie zeigen KEINEN echten Inhalt. Das render3d-Schnitt-Modul (section.rs, Commits 54211b1/76029db) liefert das Rohmaterial (SectionOutput: cut_polygons + visible_edges/hidden_edges), ist aber **noch nicht an App.tsx/das Dokumentmodell angebunden** — es gibt noch keinen Code-Pfad, der beim Öffnen einer Schnitt-/Ansichts-Zeichnungsebene das render3d-Modell durchschneidet und das Ergebnis in Plan-Primitive für PlanView/render2d übersetzt. Das ist der wichtigste nächste Schritt (Nordstern 4b, siehe Liste unten). **Nächste Schritte, priorisiert:** 1. **[Nordstern 4b] Schnitt-Pipeline an UI anbinden** — grösster Brocken: SectionOutput (Rust/render3d) → TS-Seite (WASM-Aufruf analog `toWalls3d.ts`/`nativeSync.ts`) → neue Plan-Primitive-Kind(e) für Cut-Polygone + sichtbare/gestrichelte Kanten → generatePlan.ts bindet das für DrawingLevel vom Typ Schnitt/Ansicht ein, statt des aktuellen Stub-Verhaltens. PlanView.tsx rendert es wie normale Primitive (Parität-Infrastruktur aus ce6bd26 gilt 1:1). 2. **mesh.rs: Öffnungen im 3D-Vollkörper** — 3D-Ansicht zeigt Öffnungen zwar korrekt (das war schon vorher so, per Mesh-Aussparung), aber das Schnitt-Modul rechnet gegen dieselben Prismen — sobald 4b. steht, hier gegenprüfen ob mesh.rs nachgezogen werden muss oder ob es getrennte Datenwege bleiben. 3. **Aufräumen:** `src-tauri/`-Ordner umbenennen/entflechten — User will keine Tauri-Reste im Namensraum, Electron ist die Shell. render2d/render3d/geometry sind eigenständige Workspaces ohne Tauri-Abhängigkeit, könnten z.B. nach `engine/` oder `crates/` wandern. Toten Tauri-Host (`cad-tauri`, tauri.conf.json, native.rs/native2d/native3d) explizit mit dem User klären: behalten (Beweis für späteren All-Native-Schritt) oder entfernen. 4. **[Nordstern 1]** Papier-mm-exakte Strichbreiten-Audit über den gesamten Engine-Pfad (Screen bei jedem Zoom/Massstab = Druck) — nach 2/3/4/5 jetzt der einzige verbliebene unbearbeitete Nordstern-Punkt. 5. Nicht-Engine-CAD-Backlog (Wand-Referenzlinien, Editier-Welle, nahtfreie Verschneidung, Overrides/Multi-Page-Layouts) — weiter unten in diesem Dokument, nachrangig zur Engine-Arbeit. **Arbeitsweise dieser Session (fortführen):** Pipeline aus bis zu 2 parallelen Sonnet-Subagents (User-Regel: immer 2 am Laufen halten, nachfüllen sobald einer fertig ist). Jeder Agent bekommt explizit zugewiesenes Terrain (Dateipfade), damit sich parallele Agents nicht überschreiben. Ergebnisse werden von der Hauptinstanz verifiziert (Screenshots ansehen, Gates laufen lassen) und committet — Agents committen nie selbst. Nach Prozessabstürzen (kam heute zweimal vor) lässt sich ein Agent per SendMessage mit seiner agentId aus dem Transcript fortsetzen, sein Working-Tree-Zwischenstand bleibt erhalten. ## >>> ENGINE-NORDSTERN (User-Direktive 2026-07-02): die volle Engine-Vision <<< Der User hat explizit beauftragt, die Engine-Vision KOMPLETT anzustreben ("wir brauchen exakte breiten usw. strebe bitte das komplett an!"). render2d/render3d sind nicht nur Viewport-Ersatz, sondern werden die EINZIGE Render-Wahrheit des CAD: 1. **Papier-mm-exakte Strichbreiten** überall — Bildschirm bei jedem Maßstab/Zoom = Druck. 2. **Deterministische Plan-Ausgabe:** Druck/PDF mm-genau identisch zum Bildschirm, beides aus render2d (kein separater Druckpfad). Vektor-PDF-Backend = dieselbe Szene, anderes Target. 3. **Headless-Rendering:** PNG/PDF-Export und Golden-Image-Tests ohne Fenster (die serde-only/GPU-Trennung in render2d existiert genau dafür; wgpu kann offscreen). 4. **HLR/Schnitt-Pipelines** durch die Engine (render3d → Kantenextraktion → render2d-Szene). 5. **Eine Wahrheit:** SVG-/WebGL2-Geometriepfade im Browser schrittweise durch die WASM/WebGPU-Engine ersetzen (`?engine=wasm`-Spike ist der Anfang); SVG bleibt nur Interaktions-/Overlay-Schicht. Bereits gefundene Duplikations-Bugs: Wandecken-Kerben, fehlende Strichelung im WebGL2-Pfad. 6. **WebKit-Unabhängigkeit** (explizit vom User betont): kein Render- oder Ausgabepfad darf von WebKitGTK abhängen. Brücke = Chromium-Shell (`npm run shell`), Endzustand = All-Native (ein Rust-Prozess, Engines + natives UI, kein Webview). **RICHTUNGSENTSCHEIDUNG 2026-07-02: Electron statt Tauri, all-native aufgeschoben.** Abgewogen mit dem User: (a) „auf Tauri bleiben" = WebKitGTK, das WEDER WebGPU (kein WASM- Viewport) NOCH native-wgpu-Composite ins Fenster (Wayland tot) kann → Engine nur als separate Fenster möglich, kein integrierter Viewport. (b) „all-native jetzt" = kein Shortcut, sondern das GRÖSSERE Rewrite: ~46k Zeilen TS (UI ~18k + Modell-Gehirn ~5k + Interaktion ~23k) müssten nach Rust; die Rust-Engines sind erst ~10% der App. → **Gewählt: Electron/Chromium-Shell + WASM/WebGPU-Engine im Webview** (behält die funktionierende React-UI, verlässt WebKitGTK, integrierter Engine-Viewport möglich). Die Engine-Rust-Crates sind in einem späteren All-Native- Schritt 1:1 wiederverwendbar; nur die dünne WASM-Bindeschicht wäre dann Wegwerf. All-native bleibt legitimes Fernziel, ist aber KEIN Weg zurück zu Tauri (Tauri IST der Webview-Shell). >>> ELECTRON-PROTOTYP: FERTIG (2026-07-02). <<< `electron` als devDep, `scripts/electron-main.cjs` (BrowserWindow, `Menu.setApplicationMenu(null)` + `frame:false`/`autoHideMenuBar` für randloses Fenster wie chromium-shell.sh, WebGPU-Flags `enable-unsafe-webgpu`+`enable-features=Vulkan`), `scripts/electron-shell.sh` (Dev-Server-Guard wie chromium-shell.sh), npm-Script `npm run electron`. Verifiziert per Screenshot: App startet als eigenes randloses Fenster, lädt den echten Grundriss (nicht leer). WICHTIG: - **Datei muss `.cjs` sein** (`package.json` hat `"type":"module"`, Electron-Main läuft als CJS). - **`ELECTRON_RUN_AS_NODE=1`** kann aus dem umgebenden Prozess (z.B. VSCode-Electron-Host) vererbt sein — zwingt Electron in den Node-Modus (`require('electron')` liefert nur einen Pfad-String statt `{app,...}` → `app` ist undefined). Immer mit `env -u ELECTRON_RUN_AS_NODE` starten, falls das Fenster nicht öffnet / `app.commandLine` undefined ist. - **WebGPU/`?engine=wasm` funktioniert in Electron** — verifiziert per Screenshot (RENDERER-Anzeige zeigt "Engine" aktiv, Grundriss korrekt gezeichnet). Ein einzelner Fehlversuch vorher war ein GPU-Prozess-Init-Timing-Rennen (zu früh abgefragt), kein echter Bug — `app.getGPUFeatureStatus()` zeigt nach ~2s alles `enabled`/`enabled_on` inkl. `webgpu` und `vulkan` (AMD RX 7800 XT/RADV). KEINE Ozone-Platform-Flags nötig, `--ozone-platform=x11` NICHT verwenden (bricht die Fenstererstellung, Vulkan-Surface/GetGeometry-Fehler). `electron --version` meldet v24, weil `ELECTRON_RUN_AS_NODE=1` den Prozess in den Node-Modus zwingt und dann Nodes eigene Bundle- Version zeigt (kein echter Versions-Mismatch — `env -u ELECTRON_RUN_AS_NODE electron --version` meldet korrekt v43). Danach: render3d-WASM, Doppeltext-Fix.<<< Klarstellung an den User (er fragte, ob Webview = „billig"): NEIN — Figma (eigene C++/WASM-Engine im Web-Shell), Onshape, VS Code zeigen, dass „eigene Engine + Web-UI" Top-Tier-Architektur ist. CAD/BIM-Substanz = Modell + Engine + Normen (haben wir: parametrische Wände, SIA 416, DOSSIER- Ebenen, ein-Modell→alle-Sichten), NICHT der Fenster-Shell. Praxisregel ab jetzt: Darstellungs-Features NICHT mehr mehrfach (SVG/WebGL2/nativ) bauen, sondern einmal in der Engine + dünne Anbindungen. ## >>> AUFGABE FÜR NEUE INSTANZ: 2D-Plan auf WebGL-GPU-Renderer (ZUERST) <<< **Warum:** Tauri-Linux-Webview = **WebKitGTK**, das SVG/2D auf der **CPU (Cairo)** rastert. User-GPU (AMD RX 7800 XT) langweilt sich; bei **144 Hz** (~6,9 ms/Frame) ruckelt Pan/Zoom/ **Objekt-Bewegen** sichtbar. three.js/WebGL (3D) läuft GPU-beschleunigt → NUR der 2D-SVG-Pfad ist CPU-gebunden. Entscheidung mit User: 2D-Plan **von SVG auf WebGL2 (GPU)** umbauen. **Sofortlösung fürs Vorführen/Entwickeln — Chromium-App-Shell:** `npm run shell` (`scripts/chromium-shell.sh`) startet bei Bedarf den Vite-Dev-Server (Port 5187, Polling per `curl`) und öffnet ihn dann in einem randlosen Chromium-Fenster (`--app=…`, eigenes Profil unter `~/.cache/cad-chromium-shell`), statt im WebKitGTK-Webview von `tauri:dev`. Chromium rendert dieselbe Seite GPU-beschleunigt und spürbar flüssiger. Kein Ersatz für den nativen wgpu-Pfad (`render2d`/`render3d`), aber ein schneller Weg zu einer ruckelfreien Oberfläche, solange der Electron/CEF-Shell (siehe Notizen zu „Render path decision") noch aussteht. Läuft rein im Browser ohne Tauri-Backend — `src/compute/index.ts` guardet den einzigen `invoke()`-Aufruf bereits per dynamischem Import mit TS-Fallback. **Architektur (mit User abgestimmt):** - **Hybrid:** WebGL2-Canvas rendert die schwere Geometrie (Poché-Polygone, Linien, Schraffuren, Kreise/Bögen als Segmente). **Dünne SVG-Ebene DARÜBER** behält Text (Raumstempel/Labels), Griffe, Snap-Marker, Werkzeug-Vorschau (wenige Elemente → billig, DOM-Hit-Test + scharfer Text bleibt). - **Raw WebGL2, KEINE neue npm-Dependency** (User will nicht „abhängiger" werden; nativ). - **Geometrie pro Plan EINMAL tessellieren** (Cache), Pan/Zoom = nur Transform-Matrix-Uniform → keine Re-Tessellation, GPU-buttrig. Objekt-Bewegen: Plan ändert sich → re-tessellieren (leichter als SVG-Reconcile+Cairo). - **Linienbreite konstant in Screen-px** (wie heutiges `non-scaling-stroke`): im Vertex-Shader expandieren (Position world → clip, dann Normalen-Offset in clip via Viewport-px). Miter an Ecken später; Spike: Quads pro Segment. - **SVG-Renderer als Fallback hinter einem Flag behalten**, bis GL-Parität erreicht → kein Risiko. - Vorgehen: **erst Spike** (Linien+gefüllte Polygone + GPU-Pan/Zoom), Glätte bei 144 Hz messen (dev :5187 läuft; `tauri:dev`-Fenster läuft), DANN Schraffur/Feinschliff/Text-Overlay/Hit-Test. **Fixpunkte im Code:** - `src/plan/PlanView.tsx` (~2500 Z.): `toScreen()` = Modell-m → viewBox-Einheiten, FIXER Ursprung, `PX_PER_M=90`; `view={x,y,w,h}` treibt ``; Pan = setView, Zoom = setView(onWheel). BEREITS optimiert: `primitiveEls`/`drawingRunEls` `useMemo` (Pan/Zoom 312→0 Re-Renders) + rAF-Coalescing beim Objekt-Drag (`flushDragMove`/`enqueueDragMove`, 40→1 Update/Frame). Diese bleiben nützlich; der GL-Renderer ersetzt die SVG-Primitiv-Ebene, NICHT die Interaktions-Logik/Griffe. - `src/plan/generatePlan.ts`: `export type Primitive` (Z.133) — kinds: `polygon`{pts,fill,stroke, strokeWidthMm,hatch,*Id}, `line`{a,b,weightMm?,strokeWidthMm?,dash?,color,cls,greyed,drawingId?}, `arc`{center,r,…}, `text`{at,doc,extraLines,basePt,color} (Text → SVG-Overlay, NICHT WebGL). - Koordinaten/Breiten-Logik im SVG-Renderer: `PrimitiveShape` (~Z.2280) + `DrawingRunShape` — dort steht, wie Haarlinie/mm/`paperScale`/`non-scaling-stroke` die Strichstärke bestimmen (für GL nachbilden). - Hit-Test/Auswahl/Griffe laufen weiter über die bestehenden PlanView-Pfade (modellbasiert); GL ist nur Anzeige. - **Vektor-PDF/-Export bleibt** (kommt aus `generatePlan`/DXF, nicht aus der Anzeigefläche) — NICHT anfassen. **Gates:** `npx tsc -b` 0, `npm run build` grün, Trace-Scan sauber (keine KI-Spuren, COMMIT-REGEL oben). Kein Commit ohne Ansage. Verifizieren wie üblich per Puppeteer gegen :5187 (Render korrekt + Glätte messen). **GESAMT-TODO-LISTE (Stand 2026-07-01 Nacht) — im Hinterkopf behalten:** ERLEDIGT+verifiziert: U/I/O/P-Kopiermodi · Backlog#1 Transform→CommandLine · Backlog#4 3D-Zeichnen + Engine-Confirm-Bugfix · Raumstempel#2 (Feld-Modell) · Plan-Perf (Memo + rAF-Coalesce) · Tauri-M1-PoC (geometry-Crate 4/4 + Parität + `tauri:dev`-Boot). OFFEN: **(A) 2D-WebGL-Renderer [DIESE AUFGABE]** · (B) Tauri-M1 Rest: Compute-Boundary in Render-Pfad einhängen (generatePlan sync → precompute+cache) + nächste Ops (kernel2d→DXF/DWG→detectRooms) · (C) three.js→wgpu (M2, nach 2D-WebGL) · Parametric Walls Phase B (andere Instanz) · Wand-Referenzlinien-Option unter Bezugspunkt · Viewport/Editier-Welle (Treppen-Griffe, Öffnung-entlang-Wand, 2D auf Zeichenebenen, runde Linienenden, schattiert-Politur) · Smart-Join/Split + nahtfreie Wandverschneidung · Text/Stempel-Annotation (Frame+Rotation) · Welle B (3D-Schnitt+Stencil-Capping) · Welle C (HLR verdrahten) · Overrides/Ausschnitte/Multi-Page-Layouts. ## >>> STAND 2026-07-01 (Tauri-PoC — App-frei, gelandet) <<< **Tauri v2 + Rust-Compute-Boundary — Proof-of-Concept steht (3 App-freie Agents).** Alle Gates grün, nichts an `App.tsx`/`src/model/` angefasst (nur NEUE Dateien + additive Config). Ref: `docs/design/tauri-migration-plan.md` / `tauri-architecture.md`. - **`src-tauri/` (Rust, neu):** Cargo-**Workspace** aus zwei Crates: - `src-tauri/geometry/` — **serde-only** Lib, Port von `computeJoins` (aus `src/model/joins.ts`) inkl. Vektor-Helfer. Input flach: `WallInput{ id,start,end, thickness,referenceOffset }`; Output `WallCuts{ wallId,startCut,endCut }` (Line= point+dir). **`cargo test` grün: 4/4** (L-Ecke=geteilte Gehrung, freies Ende, T-Stoss, kollinear → alle korrekt). Braucht KEIN webkit → hier testbar. - `src-tauri/` App-Crate (`cad-tauri`) — Tauri-v2-Shell (`lib.rs` mit `#[tauri::command] compute_joins` + `run()`, `main.rs`, `build.rs`, `tauri.conf.json` devUrl **:5187**, `icons/icon.png`). **`cargo check` GRÜN** — wider Erwarten NICHT von webkit blockiert: tauri v2 bindet `webkit2gtk-4.1` (vorhanden); nur `-6.0` fehlte. `src-tauri/target` + `gen/` sind Build-Artefakte (gitignored). - **`src/compute/index.ts` (neu, Boundary):** einzige Stelle für schwere Ops. `computeJoins(project,walls)` flacht ab → `invoke("compute_joins")` (geguardeter dynamischer Import von `@tauri-apps/api/core`, Build bleibt grün OHNE das Paket) → bei null/Fehler `console.warn` + TS-Fallback (`joins.ts`). Zusätzlich Durchreicher `detectRooms` (roomBoundary.ts) + `parseShapeFromDwg`(=`parseDwg`); `computeKernel2D` bewusst ausgelassen (kein Einzel-Entry, ~30 Primitive). - **`package.json`/`vite.config.ts`:** `@tauri-apps/cli`+`api` (v2) installiert, Scripts `tauri`/`tauri:dev`/`tauri:build`; vite Port 5187 `strictPort`, `clearScreen:false`, `envPrefix` — rein additiv. **NÄCHSTE SCHRITTE (Tauri):** (1) **Echte TS↔Rust-Numerik-Parität** end-to-end prüfen — erst sinnvoll, wenn die Boundary in einen Caller verdrahtet ist (aktuell bewusst NICHT verdrahtet, weil das `generatePlan`/`App` async machen würde). (2) `computeJoins` in den Wand-Render-Pfad einhängen (async-Welle). (3) `tauri:dev` end-to-end auf einer Maschine MIT `webkit2gtk` (hier nur `cargo check`, kein Fenster-Boot getestet). (4) Nächste Ops laut Plan: `kernel2d` → DXF/DWG → `detectRooms`. ## >>> STAND 2026-07-01 (Parametric Walls Phase A — ISOLIERT) <<< **Neue Instanz (Opus) — Parallelisierung mit Agents (Sonnet):** - **Branch:** `feature/parametric-walls` (isoliert, keine App.tsx-Änderungen) - **Phase A FERTIG (alle Agents grün):** - **Agent 1:** Types (`ParametricWall`, 5 `ParametricRule` Varianten) + Engine (`resolveParametricWall()`, 5 rule handlers, helpers) in `src/model/parametricWalls.ts` - **Agent 2:** 50 Unit-Tests (Vitest), alle grün; `npm test -- parametricWalls.test.ts` - **Agent 3:** Design-Doku `docs/design/parametric-walls.md` + `docs/README.md` aktualisiert - Verification: `tsc -b` ✓, `npm run build` ✓ (577 modules, 3.72s) - **Phase B (UI-Integration) — für nächste Instanz mit Agents:** - Agent 1: TopBar ParametricWall-Picker + UI-Komponente - Agent 2: Command `pw` (`cmds/parametricWall.ts`) - Agent 3: ResourceManager-Integration (ParametricWall CRUD) - Dann merge → `master`, Screenshot-Verifikation - **Koordination:** andere Instanz committet parallel (Text, Raum, HLR) auf `master`, keine Konflikte (nur `types.ts`+`src/model/` erweitert, nicht `App.tsx`). **Backlog #4 ERLEDIGT (Zeichnen im 3D-View — vorherige Instanz):** Werkzeug-Gate `toolsEnabled` (`App.tsx` ~1877) von `viewType === "grundriss"` auf `activeLevel.kind === "floor"` erweitert → Werkzeuge sind jetzt AUCH in der Perspektive aktiv. Die gesamte Zeichen-Pipeline war bereits verdrahtet (`Viewport3D.workplaneModel()` raycastet den Cursor auf die Geschoss-OKFF; `onWorkplanePoint`/`onWorkplaneConfirm` speisen dieselbe Engine wie der Grundriss). Ende-zu-Ende per Puppeteer verifiziert: in iso-Ansicht Linie mit 2 Klicks gezeichnet → committet (`draw2d` 0→1, Prompt zurück auf „Befehl:"); Picks liefern gültige Modell-Vec2 (raycast korrekt). - **BUG-FIX unterwegs (Engine-Confirm, `src/commands/engine.ts` `confirm()`):** Beim Testen von #4 fiel ein VORBESTEHENDER Bug auf (reproduziert IDENTISCH im Grundriss, nicht durch #4 verursacht): mehrteilige Befehle (Wand/Polylinie) liessen sich per Enter/Rechtsklick NIE abschliessen. Ursache: `confirm()` nahm IMMER den Tab-Feld-Zweig (`fieldResultPoint`), sobald der Schritt Felder hatte — auch OHNE gelocktes Feld → es wurde ein Cursor-Punkt gefüttert (bei Wand: Null- Strecke, ignoriert) statt `onConfirm` (das den Zug committet) aufzurufen. Fix: Feld-Zweig nur noch bei tatsächlich GELOCKTEM Feld (`Object.keys(this.locks).length > 0`); ohne Lock fällt `confirm()` auf `onConfirm` durch. Verifiziert: Wand schliesst jetzt per Rechtsklick im Grundriss (91→98) UND in 3D (99→106); Tab-Feld- Fluss „Länge 4 tippen → Enter lockt → leeres Enter setzt Punkt" unverändert; Linie-Auto-Commit (2 Klicks) unverändert. Gegatet: `tsc -b` 0, `build` grün, keine Konsolenfehler. KEIN Commit. - **Backlog #1 ERLEDIGT (Befehlsleiste-Transform vereinheitlicht):** Die laufende Auswahl-Transformation (nur noch `rotate`/Taste `d` mit U/I/O/P; move/mirror laufen längst über die Engine) speist jetzt die `CommandLine` statt der schwebenden `TransformBar`. Dritter Zweig in der ``-Ternäre parallel zu `gripEdit` (`gripEdit ? … : activeTransform ? … : engineView…`): Prompt = `transform.hint.${op}.${…}`, Inline-Optionen = U/I/O/P (`transformOptions()`, aktiver Modus hervorgehoben via neuem `active?`-Feld an `CommandLineOption` + `.cmdline-option.active` in styles.css, `×count` bei array/distribute), Live-/ lockbares **Winkel**-Feld beim Drehen bzw. **Distanz** beim Bewegen (`transformFields()` aus neuem `transformDragInfoRef`), getippte Zahl committet über `submitTransformValue()`. `d` fokussiert die CommandLine (wie `m`/`s`). `src/ui/TransformBar.tsx` gelöscht; verwaiste `.transform-bar`/`.tf-*`-CSS + `transform.bar`-Key harmlos stehen gelassen. Gegatet: `tsc -b` 0 Fehler, `build` grün, Trace-Scan sauber, Puppeteer-Probe end-to-end (nach `d`: „Drehzentrum klicken" + U/I/O/P; nach Zentrum+Bezug: „Zielwinkel klicken" + Live-Winkelfeld; keine Konsolenfehler). KEIN Commit (Konvention der Vorsessions beibehalten). ## >>> STAND 2026-07-01 (Abend) — Raum + Topbar-Umbau + Backlog — ZUERST lesen <<< Alle unten genannten Agenten GRÜN gelandet und gegatet (`tsc -b` 0 Fehler, `npm run build` grün, Trace-Scan sauber, Boot-Probe ohne Konsolenfehler). Dev- Server läuft/hält auf :5187. KEIN Commit gemacht (Commit-Regel oben beachten). **Diese Session gelandet:** - **Rich-Text-Kern (komplett):** `src/text/richText.ts` (Marks/RichTextDoc/TextRange, toggleMark/applyMark/isMarkActive/DEFAULT_PRESETS/serialize…), `src/text/renderHtml.ts` (docToHtml/docToLines/docToSvgText), `src/text/RichTextEditor.tsx` (contentEditable, Toolbar B/I/U/S + Grösse/Farbe/Presets). - **Raum-Slice (SIA-416), Agent gelandet:** `Room` in `model/types.ts` (id, floorId, categoryCode "60", siaCategory HNF/NNF/VF/FF/KGF, boundary, stampAnchor?, stampDoc?); `cmds/room.ts` (Modus inside=click-inside via `roomFromPointInside`, manual=Polylinie); Plan: neues `text`-Primitive + Füllpolygon + Stempel via `docToLines` + Live-Fläche; `panels/RoomBalancePanel.tsx` (SIA-Bilanz + CSV). **App-State für Stempel:** `selectedRoomId`/`selectedRoom.stampDoc`, `projectSlice.setRoomStampDoc(roomId,doc)`, `moveRoomStamp`, `setRoomStampAnchor`, `stampEditorRoomId`. Stempel-Editor = kleines schwebendes Fenster (kein Footer). - **DOSSIER-Referenz studiert → Spec `docs/design/topbar-dossier.md`** (verbindlich für Topbar-Arbeit; Referenz-Klon lag unter `/tmp/DOSSIER`). Kernbefund: Text-Styling ist im Original eine FEST in der Oberleiste sitzende Text-Gruppe (Stil/Font/Grösse + B/I/U + L/C/R + „+Text"), Akzent-Glow bei Auswahl; Raumstempel-Typografie darüber; Text-INHALT in eigenem kleinem Fenster; Zoom/Massstab als 2×2-Cluster (kombinierte Stat-Pille). - **Topbar-Umbau (Agent gelandet), `src/ui/TopBar.tsx` + `styles.css` + `App.tsx` + `ui/TextEditorDialog.tsx` (neu) + i18n:** Zoom-Doppelung entfernt; 2×2-Massstab/Zoom- Cluster; einheitliche Segmentpillen (neue `Segment`-Komponente); **Text-Gruppe** in der Topbar (`textTarget`-Prop = `null | {doc,range,apply}`, aus selektiertem Raum abgeleitet, `apply`→`setRoomStampDoc`; ohne range wirkt Format auf ganzes Doc); Doppelklick öffnet `TextEditorDialog`. Verwaiste alte CSS-Regeln (`.tb-zoom*`, `.stamp-editor*`) bewusst stehen gelassen (harmlos). „+ Text"-Button verdrahtet, aber DEAKTIVIERT (noch kein Text-Annotations-Werkzeug). **Backlog / nächste Schritte (Priorität dieser Session, siehe auch Todo-Liste):** 1. **Befehlsleiste-Transform vereinheitlichen:** laufende Transformation (move/mirror/rotate) soll die `CommandLine` speisen (Prompt „Basispunkt/Zielpunkt" + U/I/O/P als Inline- Optionen) statt der separaten schwebenden `TransformBar`; freies Befehl-Tippen während der Geste unterdrücken, Zahleneingabe (Distanz/Winkel) via `fields` bleibt. Muster: parallel zum bestehenden `gripEdit`-Zweig bei `` (~App.tsx:2793). U/I/O/P-Routing (App.tsx ~1543–1548) NICHT kaputt machen — nur zusätzlich in die CommandLine spiegeln. 2. **Strukturierter Raumstempel (DOSSIER `RaumProperties`):** Stempel ist KEIN Freitext, sondern ein FELD-Modell: Raumnummer · Raumname · Raumname-Zeile2 (in Listen als ein Name zusammengezogen) · Bodenfläche (an/aus + Präfix) · Fensterfläche (an/aus + Präfix) · Nutzung HNF/… (an/aus). Zeilen-Layout-Editor (welches Feld auf welche Zeile) + Typografie PRO Feld-Einheit (Raumname grösser, andere kleiner, eine Zeile kursiv). Ersetzt den Freitext-`stampDoc`-Zwischenstand; die Topbar-Text-Gruppe formatiert dann die gewählte Feld-Einheit (löst auch die „ganzes Doc statt Selektion"-Einschränkung). 3. **Wand-Optionen:** Referenzlinien-Option UNTER den Bezugspunkt setzen, gleiche Optik/ Anordnung wie die übrigen Optionen (`cmds/wall.ts` / ToolsPanel-Optionsreihenfolge). 4. **Zeichnen im 3D-View:** Cursor per Raycast auf die Arbeitsebene (okff des Geschosses) projizieren → Weltkoordinaten als `engine.move/pick` einspeisen; `toolsEnabled`-Gate (App.tsx ~1834, heute `viewType==="grundriss"`) auch für Perspektive öffnen. 5. **Viewport/Editier-Welle:** Treppen-Griffe (App.tsx grips-memo hat keinen stair-Fall), Öffnung entlang Host-Wand verschieben (kein opening-Handler im Body-Move; `moveOpeningBy` fehlt), 2D-Tools auf Zeichnungsebenen (`toolsEnabled` erweitern + Bau-Tool-Buttons grauen), runde Linienenden (`stroke-linecap:round` auf offene Linien), „schattiert"-Politur (ACES-Tonemapping+sRGB, HemisphereLight/RoomEnvironment-PMREM, MeshLambert→MeshStandard in `Viewport3D.tsx` ~416–449). 6. **Smart-Join/Split + NAHT-FREIE Wandverschneidung** (Poché-Union an Stössen, keine sichtbare innere Naht, Gehrung/T-Stoss). 7. **Text/Stempel-Annotation** (aktiviert „+Text"; Frame+Rotation). 8. **Welle B** (3D-Schnittebene + Stencil-Capping + Schnittschraffur). 9. **Welle C** (HLR verdrahten; `src/section/hlr.ts` bereit). 10. Overrides/Ausschnitte/ Multi-Page-Layouts. **Kollisions-Regel bestätigt:** `App.tsx`, `types.ts`, `generatePlan.ts`, `Viewport3D.tsx`, `registry.ts`, `PlanView.tsx`, i18n, `styles.css` sind Hotspots → nur EIN App.tsx-Agent pro Welle; parallele Tracks nur auf genuin App-freien neuen Modulen. ## >>> STAND 2026-07-01 (Bug-Batch Runde 1) — ZUERST lesen <<< Runde-1-Agenten alle GRÜN gelandet (`tsc -b` + build + Probe verifiziert): - **3D-Editier-Kamera-Bug (kritisch, `src/viewport/Viewport3D.tsx`):** Ursache = schwerer Szenen-Aufbau-Effekt mit `project`-Dependency → jede Griff-Mutation baute Szene neu auf und `applyView3d` resettete die Kamera auf Preset (→ „springt in Top-View", Rechts/Links-Pan-Illusion). Fix: `camStateRef`-Snapshot (Pos/Target/up/ortho-Zoom/aktive Kam) im Render-Loop fortgeschrieben, bei Neuaufbau WIEDERHERGESTELLT statt `applyView3d`; `applyView3d` nur beim ersten Aufbau; `view3d`-Effekt guarded via `gripDragRef`; Top-View `enableRotate=false`; 2D-Drawings bekommen `move`-Griff (Anker=center/at) → in 3D verschiebbar. Kamera pos/target numerisch identisch vor/nach Drag/Löschen bewiesen. - **Print-Linienstärke invertiert → korrekt (`src/plan/PlanView.tsx`, `styles.css`):** Print zeichnet jetzt echte Papier-mm in viewBox-Einheiten OHNE `non-scaling-stroke`: `strokeVb=(mm·N/1000)·PX_PER_M`, N=`paperScale` stabil (nur bei `applyScale`/erstem Messen gesetzt). Rein=dicker, raus=dünner; 1:100 vs 1:10 = exakt ×10. Display/Haarlinie unverändert. **Ecken-Miter:** CSS `stroke-linejoin:miter` + `buildDrawingRuns` bündelt zusammenhängende `line`-Primitive gleicher drawingId zu einem `polyline`/`polygon` (Ringschluss erkannt) → gehrte Ecken statt Butt-Cap-Stufe. Primitive-Modell (und Print-Export) unverändert. - **ambientCG-Live-Bibliothek (`src/materials/ambientcg.ts` neu, `ResourceManager.tsx`):** komplette CC0-Lib live durchsuchbar (Suche/Kategorie/Auflösung 1K-4K), jszip-Entpackung → Blob-URLs, NormalGL bevorzugt. **CORS:** Such-JSON + `/get` brauchen Proxy → Vite-Dev-Proxy `/ambientcg` in `vite.config.ts` (`VITE_AMBIENTCG_PROXY` für Prod); optionale Prod-Route `openbureau-core/api/src/routes/ambientcg.js` (lesend, gemountet VOR Auth-Gate). Thumbnails direkt (ACAO:*). Offline → Schnellauswahl-Fallback. **Runde 2 FERTIG (2026-07-01, alle GRÜN + Probe-verifiziert):** - **2a** [App/engine/editors/registry, neu `cmds/mirror.ts`+`cmds/join.ts`]: Snapping erster Punkt gefixt (Modifier shift/ctrl wurden im Command-Pfad verschluckt → jetzt via `EngineMods` durch `move/pick`→`host.snap` gefädelt); Segment-Move für offene Polylinien/Linien (`EdgeGrip.free`, ganzer 2D-Delta, Nachbar-Segmente folgen über geteilte Vertices); Kürzel↔Befehl vereinheitlicht (`M`→engine.start("move"), `S`→"mirror", `Ctrl+J`→"join", identisch zum Tippen; `Command.autoRun` für nicht-interaktive Befehle; `CommandSelection.drawingIds` für Mehrfachauswahl); **Mirror-Befehl** (2-Punkt-Achse, Vorschau, `commitTransform mirror/copy`). - **2b** [cmds/{polyline,rect,wall}]: Polylinie **offen/geschlossen** (Toggle `m`, Ring-Fill); Rechteck **2pt/3pt/Zentrum** (3pt = gedreht → closed polyline); **Wandstärke-Tab-Feld** nur bei einschichtigen Wandtypen (baut on-commit einen einschichtigen Wandtyp der Zieldicke, Original bleibt). - **Swisstopo/OSM-Kontext-Importer** [io/{lv95,geoContext,swissTopo,osm}, ui/ContextImportDialog, SitePanel]: „Standort importieren" — geocode (geo.admin SearchServer, LV95 direkt), swisstopo-Gebäude (`MapServer/identify` layer `ch.swisstopo.vec25-gebaeude`), OSM-Overpass (Gebäude/Strassen/Wasser/Grün). LV95↔WGS84 (Bern round-trip 0.38 m) + Origin-Shift. CORS: geo.admin direkt, Overpass via neuer read-only-Route `openbureau-core/api/src/routes/geoproxy.js` (vor Auth-Gate, host-allowlist) wenn `VITE_GEO_PROXY` gesetzt. **TIER-1 FORTSCHRITT (2026-07-01):** - **DXF-Vektor-Export FERTIG** [src/export/{dxfWriter,exportDxf}, ui/ExportDxfDialog, TopBar+App-Button]: hand-rollierter R2000/AC1015-ASCII-Writer, `$INSUNITS=6` (Meter), 23 Layer aus Kategorien (ACI+True-Color+lw), LINE/LWPOLYLINE(closed)/CIRCLE/ARC/TEXT; Quelle = `generatePlan()` in Meter-Modellraum. 5 m-Wand→5.345 m Span verifiziert. DWG NICHT geshippt (libredwg-web nur lesend — ehrlich weggelassen). - **Decke (Ceiling) FERTIG** [geometry/ceiling, cmds/ceiling(alias d/decke), types.ts `Ceiling`, projectSlice/selectionSlice, generatePlan `addCeilingPoche`, PlanView-Hittest, Viewport3D `addCeilingMesh`(Extrude+Material+Modi), ToolsPanel-Button+ Deckentyp-Picker, ObjectInfoPanel `CeilingSection`, host.ts, App-Grips/Selection]. Wand-Muster: floorId + categoryCode "30" + wallTypeId/Component + VerticalAnchor(OK=Geschoss-Top) + optionale Dickenüberschreibung; statt Achse/Band ein closed `outline: Vec2[]`. Plan = Umriss-Polygon (Fill+Hatch + schwere Outline) VOR den Wänden gezeichnet. Grips Vertex+Edge+Body. - ⚠️ MERKE: **App.tsx ist der Serialisierungs-Engpass** — pro Welle darf NUR EIN Agent App.tsx editieren (DXF+Decke liefen versehentlich gleichzeitig darauf; Merge hat diesmal geklappt, aber nicht drauf verlassen). - **Öffnung (Fenster/Tür) FERTIG** [geometry/opening, cmds/opening(alias f/fenster, t/tuer/tür, oe), types.ts `Opening`, generatePlan(`wallGaps`), PlanView, Viewport3D(`addWallMeshes` segmentiert + Sturz/Brüstung, `addOpeningMeshes` Rahmen/Glas/Flügel), slices, App, ToolsPanel(Fenster+Tür-Buttons), ObjectInfoPanel(editierbar)]. **Host-Sync:** Öffnung speichert KEINE Weltkoordinaten — alles wird pro Render aus `wall.start/end` neu abgeleitet → Wand verschieben bewegt Öffnung automatisch. Plan-Symbole je Detailgrad (Tür: Blatt→+Schwenkbogen→+Anschlag; Fenster: 1→+Rahmen→+2 Glaslinien). - **SIA-416-Engine FERTIG** [geometry/{roomArea,roomBoundary} — pure, isoliert, 34/34 Tests]: Raumerkennung (planar-arrangement+half-edge face-tracing, `detectRooms`/`roomFromPointInside`), Fläche/Umfang/Zentroid, SIA-Hierarchie (GF→KGF+NGF; NGF→NF(HNF/NNF)/VF/FF), `balance()`, `roomsToCsv`. Bereit für den Raum-Slice. - **Treppe FERTIG** [geometry/stair, cmds/stair(alias treppe/tp — `tr`=trim!), types.ts `Stair`, model/wall `stairVerticalExtent`, generatePlan `addStairSymbol`(Schnittbruchlinie+Pfeil), Viewport3D `addStairMeshes`(Stufenprofil), slices/App/Panels/i18n]. gerade/L/Wendel (Wendel = vereinfachtes Keil-Modell), SIA-17/29, geschoss-übergreifend (totalRise=Geschosshöhe). - **HLR-Spike FERTIG (Welle-C-Fundament)** [src/section/{hlr,occt,occt-wasm.d}.ts, docs/welle-c-hlr-spike/, package.json +opencascade.js, vite.config aliases]. **API-Pfad = `HLRAppli_ReflectLines`** (NICHT HLRBRep_Algo — im Prebuilt nicht konstruierbar). Lazy via dynamic import, Haupt-Bundle unberührt. 62 MB WASM → später Custom-Build auf 5-15 MB. **SESSION-LIMIT-STOP (2026-07-01 ~11:00, reset 13:00 Europe/Zurich):** Raum- + Rich-Text-Agenten wurden vom Session-Limit abgeschnitten. Repo ist trotzdem GRÜN & stabil (tsc+build grün, dev :5187 läuft). Genauer Stand: - **Raum: NICHT begonnen** — kein `cmds/room.ts`, kein `Room`-Typ, keine Verdrahtung. Sauber neu starten. SIA-Engine (`geometry/roomArea.ts`+`roomBoundary.ts`) liegt fertig & getestet bereit zum Import. - **Rich-Text: FAST FERTIG** — `src/text/richText.ts` (Doc-Modell + Marks + plainText/applyMark/toggleMark/serialize/Presets) FERTIG. `src/text/renderHtml.ts` (docToHtml für Editor + docToLines/docToSvgText für SVG-Plan, gemeinsame Mark→Stil-Map) JETZT FERTIG & kompiliert (im Hauptthread nachgezogen, da Agent am Limit war). FEHLT nur noch: `src/text/RichTextEditor.tsx` (WYSIWYG-Komponente mit Toolbar bold/italic/underline/strike/Grösse/Farbe + Presets, controlled: value/onChange). - Zwei Helfer-Subagenten ("Map/Extract …wiring in App.tsx") liefen ebenfalls ins Limit; App.tsx ist laut tsc+build INTAKT. **RESUME NACH RESET (Reihenfolge):** (1) Rich-Text-Kern fertig (renderHtml + Editor-Komponente) — App-frei, isoliert. (2) Raum-Slice (nutzt SIA-Engine, click-inside, Stempel, Bilanz+CSV) — App-Slice. (3) Text/Stempel-Slice (nutzt Rich-Text-Kern). (4) Smart-Join/Split. (5) Welle B (3D-Schnitt+Stencil-Capping) → Welle C (HLR verdrahten, `src/section/hlr.ts` bereit). (6) Overrides-Engine, Ausschnitte, Multi-Page-Layouts. REGEL: nur EIN App.tsx-Agent pro Welle; Parallel-Track nur wenn App-frei. --- ## >>> STAND 2026-06-30 (Fortsetzung, spät) <<< Fortsetzungs-Lauf (UI + Import + Wand-Attribute + Selektion/Editieren). Alles unten ist `npx tsc -b` + `npm run build` GRÜN und per Screenshot/Probe verifiziert, außer „LÄUFT" markiert. Viel wurde an **Subagenten** delegiert (Dateien landen auf der Platte, unabhängig von Benachrichtigungen). **Fertig & verifiziert (diese Fortsetzung):** - **Vektor-PDF-Export des Grundrisses (KERN-ZIEL — fertig & verifiziert):** `src/export/planToPrintSvg.ts` (Druck-SVG aus `generatePlan`, mm-Mapping im Maßstab, echte mm-Linienstärken/ISO-Stifte, Schraffuren als Vektorlinien) + `src/export/exportPdf.ts` (`jspdf`+`svg2pdf.js` → echtes Vektor-PDF, A4/A3 ×Hoch/Quer, Schriftfeld). TopBar-Button „PDF" + `src/ui/ExportPdfDialog.tsx`. Verifiziert: 522 Pfade, 0 Rasterbilder, maßstabsgenau (5,345×4,345 m → 53,45×43,45 mm @1:100). `scripts/probe-pdf.mjs`. - **2D-Boolean (Union/Differenz/Schnitt):** `src/editors/booleanOps.ts` (polygon-clipping), Kontextmenü „Vereinigen/Subtrahieren/Schneiden" + Ctrl/Cmd+Shift+U/D/I; geschlossene Drawing2D, Differenz=erstes Element minus Rest, Loch→Außenring gefüllt + Innenringe als Umriss. Verifiziert (Union 8/Differenz 6/Schnitt 4 Ecken). `scripts/probe-boolean.mjs`. - **3D-1 — ERSTELLEN in 3D (Meilenstein):** der 3D-Viewport ist Erstellungs-Fläche; dieselben Befehle (line/wall/rect/polyline) erzeugen Elemente direkt in 3D. Raycast auf Geschoss-Arbeitsebene (`THREE.Plane` y=gridElevation) → Modell-`Vec2` (model.x=P.x, model.y=P.z, aus `addDrawing2DLines` abgeleitet) → dieselbe `CommandEngine` (`move/pick/confirm`). Live-3D-Draft (Rubber-Band + Snap-Marker, eigene `draftGroup`), OrbitControls bei aktivem Befehl umgeschaltet, `pxPerMeter`-Fallback 90. Viewport3D-Props `commandActive`/`onWorkplanePoint`/`onWorkplaneConfirm`/`draft`. Verifiziert (Wand in 3D → in 3D + Plan sichtbar). `scripts/probe-3d-create-*.png`. - **3D-2 — EDITIEREN in 3D (Meilenstein):** 3D-Griffe (eigene `gripGroup`) bei genau 1 Auswahl + `!commandActive`. Wand: Endpunkt-Kugeln (Achse), Höhen-Griff (OK via `updateWall top:{mode:"custom",z}`), Verschiebe-Griff; Drawing2D: Vertices. Drag raycastet auf Arbeitsebene bzw. vertikale Ebene → DIESELBEN Store-Actions wie Plan-Griffe (`moveGripOf`/`moveElementByOf`/`updateWall`) → Plan+3D konsistent. Laufender Drag in `gripDragRef` (übersteht den Szenen-Neuaufbau pro Move). Verifiziert (Endpunkt + Höhe in 3D editiert, im Plan deckungsgleich). `scripts/probe-3d-edit-*.png`. **Offen (→ Editier-Welle 3): 3D-Snap beim Drag + Griffe „immer oben" (Tiefentest).** - **Zwei-Ton-Dark-Theme + KEIN Petrol-Grün mehr:** `src/styles.css` Dark-Tokens neutral (`--bg #0e0e0e`, `--panel #1d1d1d`, `--accent #4d4d4d` …); Kontextmenü-Tokens `--ctx-hover/-text` neutral; grüner Fokus-Ring entfernt. - **Topbar DOSSIER-Stil:** Icon-Grid 4-oben/3-unten (Grundriss integriert), Kombis gestapelt, kompakte Selects. `src/ui/TopBar.tsx`. - **Themed Dropdowns** statt nativer `