# 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 `<svg viewBox>`; 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 `<CommandLine>`-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 `<CommandLine>` (~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 `<select>`: `src/ui/Dropdown.tsx` (Wert- + Aktions-
  Modus, Popover im Kontextmenü-Stil, kein blaues OS-Menü). In TopBar verdrahtet.
- **Kontextmenü/Dropdown-Politur:** Material-Symbols-Icon-Font in `index.html` geladen
  + `.material-symbols-outlined`-Regel in styles.css (sonst Roh-Ligaturtext); Größen
  runterskaliert (`.ctx-menu` 11.5px, Icons 13–14px, weniger Padding, Radius 10).
- **Geschoss-Z-Raster:** `Viewport3D` Bodenraster auf `gridElevation` (= aktives
  Geschoss `baseElevation`).
- **DXF/DWG-Import als DIALOG + Drag&Drop:** `src/ui/ImportDialog.tsx`,
  `src/io/dxfToDrawings.ts`; Ziel-Zeichnungsebene wählen / neue „Zeichnung" anlegen;
  Layer→Kategorie-Handling; App-weiter Drop-Handler.
- **DWG in-app parsen:** `@mlightcad/libredwg-web` (WASM, installiert), `src/io/dwgParser.ts`
  (lazy, mappt DwgDatabase→{meshes,contours}), `vite.config.ts`-Aliase für WASM
  (`virtual:libredwg-glue` + `?url`), `src/io/libredwg-web.d.ts`. Gegen echte DWGs getestet
  (11.668 Konturen). Fallback→ODA-Hinweis bei Lib-Fehler.
- **Wand-Objekt-Info-Attribute** (additiv, abwärtskompatibel): Modell `Wall.referenceLine`
  (`left|center|right`, default center) + `Wall.bottom/top: VerticalAnchor`
  (`{mode:"floor",floorId,offset?}|{mode:"custom",z}`) in `src/model/types.ts`; Resolver
  `src/model/wall.ts` (`wallReferenceOffset`, `wallVerticalExtent`, `nextFloorAbove`);
  angewandt in `generatePlan`, `model/joins.ts`, `Viewport3D`. Panel
  `src/panels/ObjectInfoPanel.tsx`; `src/state/selectionInfo.ts` `Selection.wall: WallInfo`;
  Host-Setter + `projectSlice` `updateWall`/`setWallThickness`. Einschichtig = 1-Layer-WallType.
- **Panel-Text:** `.attr-*`/`.objinfo-*` Untertitel fett + Kontrast (`--label` statt `--muted`).
- **Selektion-Überarbeitung:** `selectedDrawingId`→`selectedDrawingIds[]`
  (`selectionSlice`); Marquee wählt **Linien** (`marqueeHitDrawings`), **Ctrl/Cmd+A**,
  **Multi-Delete**, **Multi-Highlight** (`.plan-sel-draw`), **Shift-Klick-Mehrfachauswahl**
  (`PlanSelection.shift` → `onPlanSelect` toggelt).

- **Editier-Welle 2 — Split/Join/Segment-Löschen (FERTIG & verifiziert):** Ctrl+S Split
  (Rechteck→zwei geschlossene; nur bis Schnittpunkt), Ctrl+J Join (koinzidente Enden),
  Alt+Klick=Segment löschen (Schere). `kernel2d.ts` (`splitPolylineAtParam`/
  `splitClosedByChord`/`removeSegment`/`splitAtIntersections`/`joinChains`; Tests 16–23,
  25 ok), `src/editors/splitJoin.ts`, App-Keydown (Ctrl/Cmd+S/J, `preventDefault`) +
  `onSegmentCut`, PlanView Alt-Klick. Verifiziert (Rechteck→2 geschlossene, Join→1
  Polylinie, Alt→Segment weg). `scripts/probe-splitjoin.mjs`. Wände bleiben no-op.
- **Editier-Welle 3 + Eingabe-Vereinheitlichung + Snap-Feinschliff (FERTIG):** 3D-Drag-Snap
  (gleiche `computeSnap`) · Griffe/Marker immer-oben · koinzidente Endpunkt-Griffe ziehen
  gemeinsam (Viewport3D). **GUI-Werkzeug** (line/polyline/rect) → `engine.start` +
  Befehlsfeld-Fokus; **Shift = H/V-Ortho** beim Griff-Drag (2D+3D, `applyAngleConstraint`
  aus `tools/snapping`). OFFEN: „Wand" noch Legacy (Wand-als-Command); Editieren läuft noch
  AD-HOC, nicht durch die Engine → Tab-Wert + Referenz-Tracking offen. Snap-Marker
  vereinheitlicht: 2D Endpunkt=Kreis/Mittelpunkt=Strich, 3D-Snap=`#ffcc44`, Marker kleiner;
  2D-Vorschaulinie grau-Volllinie.
- **DWG-Parser erweitert (FIX „keine Geometrie gefunden") + Trim (FERTIG):** `io/dwgParser.ts`
  neu — ARC/CIRCLE/ELLIPSE/SPLINE + **INSERT/Block-Expansion** (rekursiv, aus
  `db.tables.BLOCK_RECORD.entries`, volle 2D-Affin-Transform) + Diagnose-Histogramm
  (`DxfImportResult.diagnostics`, in ImportDialog). Real-DWG 37 INSERT → 67k Konturen.
  **Trim** (`commands/cmds/trim.ts`, Alias `tr`, `kernel2d.trimPolyline`) — Quick-Trim bis
  Schnittpunkt. Beide verifiziert.
- **Material-Bibliothek (Daten, FERTIG):** 12 CC0-Materialien von ambientCG →
  `public/assets/materials/<id>/{color,normal,roughness,displacement,ao}.jpg` + `manifest.json`
  + `src/materials/library.ts` (PBR).
- **Welle A — 3D-Materialien (FERTIG & verifiziert):** `Component.material` (`ComponentMaterial`:
  PBR-Map-URLs + `sizeM` Kachelgröße), `src/materials/runtime.ts` (`MaterialRuntime` → gecachte
  `MeshStandardMaterial`, Kacheln in Welt-Metern via Extrude-UVs), `renderMode` „textured"
  (TopBar-Darstellung), Material-Picker (ambientCG-Grid) + Upload + Kachelgröße im
  `ResourceManager` (Bauteile-Tab). Backstein mit Normal-Map-Tiefe verifiziert.
  `scripts/probe-materials.mjs`. Schnittschraffur-Slot frei für Welle B.
- **Editier-Eingabe + Topbar-Politur (FERTIG):** Befehlszeile zeigt **Live-Länge/-Winkel**
  beim Zeichnen (`Command.fieldValues`), **Cursor-HUD entfernt**; **Seiten-Verschieben
  propagiert verbundene Enden** (2D+3D); **Tab beim Vertex-Editieren** setzt Wert
  (`gripEdit`-Controller in App — noch ad-hoc, NICHT durch die Engine). Topbar: Rest
  iconisiert (Referenzlinien/Linien-Modus/Ressourcen), Linien-Modus → Dropdown, Aktions-
  Buttons als Dropdown-Pill-Familie, **Detailgrad+Massstab gestapelt + 1:N/Zoom% kompakt**
  (`.tb-zoomstack`).
- **Wand-als-Command (FERTIG & verifiziert):** `commands/cmds/wall.ts` (chainend, Achs+Band-
  Draft, Tab-Felder; Wandfelder aus `CommandContext.activeWallTypeId`/`level`) + Registry
  (Aliase `w`/`wand`) + App-Kopplung (`TOOL_COMMAND.wall`, Legacy-Wand umgeleitet). Wände in
  2D+3D. `scripts/probe-wall-cmd.mjs`.

**OFFENE WELLEN (nächste Schritte):** Referenzobjekt-Achs-Tracking + Editieren VOLL durch die
Engine (heute `gripEdit` ad-hoc); **Welle B** (3D-Clip/Schnittebene + Stencil-Capping +
Schnittschraffur — nutzt das Material-System aus Welle A); **Welle C** (3D→2D-Schnitt/
Perspektive via HLR, opencascade.js-Spike); **Swisstopo-Importer** (Ort+Radius → Gebäude+
Gelände, geo.admin STAC/Geocode, CORS prüfen); Bögen/Kreise-Trim; Wandstärke-Tab bei
einschichtigen Wänden.

**Offene Wellen (Todo-Spiegel — Todo-Liste lebt im Kontext):**
1. Editier-Welle 3: **koinzidente Endpunkt-Griffe gemeinsam ziehen** + verbundene Enden
   propagieren beim Seiten-Verschieben (topologisch, das Tiefste).
2. **GUI-Werkzeug ↔ Befehlszeile koppeln**: Werkzeug-Klick startet denselben Command →
   Werte unten eintippbar; danach **Cursor-Wert entfernen/optional**.
3. **Selektions-Direkt-Edit**: Länge/Winkel des gewählten Elements im Befehlsfeld (Tab-Zyklus).
4. **Swisstopo/openbureau-Importer-Dialog** (Ort+Radius). Fluss steht: Geocode
   `api3.geo.admin.ch/.../SearchServer` (sr=2056→LV95) → ±Radius → WGS84-bbox → STAC
   `data.geo.admin.ch/api/stac/v1`, Collections `ch.swisstopo.swissalti3d` +
   `ch.swisstopo.swissbuildings3d_3_0` → Download → TIN/Mesh→Kontext. LV95↔WGS84 nötig;
   **Browser-CORS** das Risiko. Referenz: DOSSIER `rhino/swisstopo.py`.
5. **3D→2D-Dokument**: live + statisch (MVP-Kantenprojektion → HLR) für Vektor-PDF.
   Hinweis: `generatePlan` liefert SCHON Vektoren (SVG) → Plan-PDF bereits vektoriell.
6. **Topbar-Rest iconisieren** (Zoom/Referenzlinien/Linien-Modus/Ressourcen).
7. **Wand als Command** (+ Wandstärke-Tab nur bei einschichtig) + **Trim**.

**Resume-Checkliste:** 1. CONVENTIONS.md + diesen HANDOVER + Memory lesen. 2. `npx tsc -b` &
`npm run build` (grün?). 3. `npm run dev -- --port 5187 --strictPort` (User schaut :5187).
4. Split/Join-Stand prüfen (git diff, Probe). 5. Arbeitsweise: **autonom, keine
Rückfragen** (User hat Vollrechte), substanzielle Arbeit an Subagenten delegieren,
jede Änderung per `tsc`+`build`+Screenshot verifizieren. `node scripts/probe.mjs`.

---

## >>> STAND 2026-06-30 (Über-Nacht-Run) — älter <<<
Großer autonomer Build-Lauf. Alles unten ist tsc + build GRÜN und per Screenshot
verifiziert (sofern nicht „läuft" markiert). Befehlssystem-Bauplan:
`docs/design/rhino-command-system.md`.

**Fertig & verifiziert:**
- **Rhino-Befehlssystem (Tier 0 + Tier 1 Zeichnen):** `src/commands/` — `types.ts`
  (Command-Interface), `engine.ts` (State-Machine + lastCommand + Eingabe-Routing),
  `parseInput.ts` (`5,3`·`r5,3`·`5<45`·`<45`·nackte Zahl), `registry.ts` (+Aliase
  `l/pl/rec/c`), `cmds/{line,polyline,rect,circle}.ts`. UI: `src/ui/CommandLine.tsx`
  (über der Statusleiste, Tab fokussiert). Befehle: **Line, Polyline (Close/Undo),
  Rectangle, Circle** — alle mit getippten Koordinaten, per Probe gezeichnet.
  Koexistenz mit Alt-Tools (Werkzeugleiste). `scripts/probe-command-line.mjs`,
  `probe-draw-commands.mjs`.
- **Tab-Feld-Zyklus** (Rhino-Präzision §2.7): beim Zeichnen Tab durch Felder
  (Linie/Polylinie: Länge→Winkel; Rechteck: Breite→Höhe; Kreis: Radius). Zahl lockt
  das aktive Feld, ungelockte folgen der Maus. Additive optionale Command-Methoden
  `fields()`/`pointFromFields()` + Engine-Feldzustand + CommandLine-Boxen. Verifiziert
  exakt (3 m/45°, Rechteck 4×2). `scripts/probe-tab-fields.mjs`.
- **Tier-1-Editierbefehle:** **Move** (Auswahl verschieben), **Copy** (wiederholend
  duplizieren), **Offset** (parallele Kurve via `kernel2d.offsetPolyline`, persistente
  Distanz, Seite per Klick) — `cmds/{move,copy,offset}.ts`, Aliase `m/cp/o`. `CommandContext`
  um `selection` erweitert (App `host.context()` füllt sie aus dem Store). Offset verifiziert
  (paralleler Versatz exakt 0.5 m). `scripts/probe-offset2.mjs`.
- **2D-Geometrie-Kernel** `src/geometry/kernel2d.ts` — Offset/Trim/Extend/Fillet +
  Segment-/Linien-/Kreis-Schnitt + Fläche/Wicklung. 16/16 Unit-Checks
  (`scripts/test-kernel2d.ts`, via `npx esbuild … | node`). Basis für Tier-1-Editierbefehle.
- **Kontext/Gelände Phase 1** (paralleler Strang): `src/io/dxfParser.ts` (DXF: 3DFACE/
  MESH/POLYLINE→Mesh, LWPOLYLINE/LINE→Konturen; DWG bewusst nur via ODA→DXF-Hinweis),
  `src/model/terrain.ts` (`generateTerrainFromContours` → TIN via delaunator),
  `src/state/siteSlice.ts` (`addContextObject`/`generateTerrain`/…), `Project.context`
  (ContextObject = ImportedMesh|ContourSet|TerrainMesh, „dumme" Kontext-Geometrie, NICHT
  semantisch), `Viewport3D` rendert Kontext/Terrain. Deps `dxf-parser`+`delaunator`.
- **Echte Isometrie (Orthographic-Kamera):** `Viewport3D` schaltet front/top/side/iso auf
  OrthographicCamera (parallele Projektion), Perspektive bleibt perspektivisch. Verifiziert.
- **Plan-Tinte-Fix:** 2D-Linien waren unsichtbar (CSS `.draw2d{stroke:var(--ink)}` hell auf
  hellem Papier). CSS-stroke entfernt + dunkler Fallback in generatePlan → 2D dunkel sichtbar.
- **Bugfixes:** 2D-Füllungen (rect/polyline rendern Fill+Schraffur+`fillColor`); Kanten-Griff-
  Dreiecke sitzen auf der Linie + inkrementelles Dragging (keine Akkumulation); Plan-View
  wandert nicht mehr mit (fixer Welt-Ursprung, Reset nur bei Geschoss-Wechsel via `resetKey`).

**LÄUFT gerade (Agent):**
- **Tab-Feld-Zyklus** (Nutzer-Wunsch): beim Zeichnen Tab durch Felder (Länge→Winkel→
  Breite/Höhe/Radius), Zahl lockt Feld, Rest folgt Maus. Additive optionale Command-Methoden
  `fields()`/`pointFromFields()` + Engine-Feldzustand + CommandLine-Boxen.

**NÄCHSTE SCHRITTE (Reihenfolge):**
1. Tab-Feld-Zyklus verifizieren (Agent-Gate).
2. **Tier-1-Editierbefehle:** Move/Copy/Offset(nutzt kernel2d)/Trim/Split/Join/Explode.
   Undo/Redo-Stack im Store erwägen (Commits laufen über `setProject`).
3. **Terrain-Integration:** Konturen im Plan (generatePlan/PlanView), Import-/Terrain-Befehle
   + Site-Panel (`src/panels/`) + i18n + DWG→DXF-Hinweis-UI; `.dxf`-File-Picker → `parseDxf`.
4. **Wand-Stärke-Feld:** Wand ist noch ein Alt-Tool — als Command portieren, dann Tab-Feld
   `thickness` NUR bei einschichtigen/freien Wänden (mehrschichtige Typen: Stärke vorgegeben).
5. Tier 2 (Rotate/Scale/Mirror/Arc/Fillet/Array/Group/Gumball2D), Tier 3 (ExtrudeCrv/Box/
   PushPull + analytische Wand-Öffnungen, §3.0 Bauplan).

**Gotchas (frisch gelernt):**
- Ein Prozess-Neustart killt laufende Hintergrund-Agenten (Status `failed`); nach
  jedem Wiederaufnehmen `src/commands/`-Existenz + `npx tsc -b` prüfen, Abgestürztes neu starten.
- Inline `{...s, feld}` als Tupel-Return löst TS-Excess-Property-Check aus → erst typisierte
  Variable (siehe cmds/line.ts-Muster).
- Ein Subagent ist bei diesem Befehls-Build geflaket (spawnte Research statt zu bauen) →
  verzahnte/quer schneidende Arbeit an einen fähigen Subagenten delegieren oder selbst machen.

---

## Was das ist
Standalone-**Browser-BIM** (React+TS+Vite+Three.js) für Wohnbau — die Browser-Variante
des Rhino-Plugins **DOSSIER** (Referenz-Repo, public/klonbar: https://git.kgva.ch/karim/DOSSIER;
falls weg: neu klonen nach `scratchpad/DOSSIER`). Prinzip: **ein semantisches Modell →
alle Sichten (Plan/3D/Schnitt) abgeleitet**, Darstellung erst beim Rendern.

## Aktueller Stand (tsc + build GRÜN)
Funktioniert & verifiziert: semantisches Modell · **mehrschichtige Wände** mit
**L-Ecken-Gehrung** · Tür mit Öffnung + Schwenkbogen · **Dokumentmodell** (Zeichnungsebenen
= Geschosse+Schnitte/Ansichten/Zeichnungen; Ebenen = Kategorie-Baum, DOSSIER-Codes 1:1) ·
**3D zeigt EG+OG gestapelt** · **Resource-Manager** (Component/Hatch/Line, als Fenster-Overlay) ·
**Panel-System** (Docks links/rechts, Tabs, 5 Anzeige-Modi) · **Top-Bar + Footer** (Massstab
echt 1:N, Detailgrad, Render-Modus Schattiert/Draht/Kanten, Referenzlinien, Cursor X/Y live) ·
**Maus:** Mitte=Pan/Orbit, Links=Auswahl(+Marquee), Rechts=eigenes Kontextmenü · **Native-App**
(kein Browser-Rechtsklick/Textauswahl) · **i18n** (`src/i18n/`, de+en, `t('key')`) ·
**aktive Zeichenwerkzeuge** (Wand/Linie/Polylinie/Rechteck + Snapping endpoint/midpoint/
intersection/onEdge/grid/ortho mit Fang-Menü, Live-Vorschau+HUD — `src/tools/`, Phase 1+2).

### Gerade fertig & verifiziert (2026-06-29)
**Aktive Zeichenwerkzeuge — Phase 1 + 2** (`docs/design/drawing-tools.md`): Tool-System in
**`src/tools/`** (types · snapping · tools/registry) + Verdrahtung in PlanView/App/TopBar/
generatePlan. Per probe verifiziert (`scripts/probe-tools.mjs`, `probe-line.mjs`, `probe-phase2.mjs`):
- **Werkzeugleiste** (TopBar): Auswahl/Wand/Linie/**Polylinie/Rechteck** + Wandtyp-Dropdown +
  **Fang-Menü** (Häkchen je Snap-Art, Rasterweite, Winkelraster — `position:fixed`, da Topbar clippt).
  Nur im Grundriss aktiv.
- **Wand-Werkzeug**: Achs-Polylinie → je Segment ein `Wall`; Live-Band-Vorschau + HUD (Länge·Winkel);
  Rechts-/Doppelklick/Enter committet; **Gehrung automatisch aus `computeJoins`** (mehrschichtiger L-Stoss).
- **2D-Werkzeuge**: Linie (2-Klick), **Polylinie** (Klick auf Start schließt, Doppel-/Rechtsklick beendet
  offen), **Rechteck** (zwei Ecken) → `Drawing2D` (line/polyline/rect); in `generatePlan` abgeleitet
  (`addDrawing2D`, `color` am line-Primitiv, Bounds erweitert).
- **Snapping**: endpoint · midpoint · intersection · onEdge(Lot) · grid · ortho(Shift); Prioritäts-
  gewichtet (`PRIORITY` in snapping.ts); Bildschirm-Marker je Art (Quadrat/Dreieck/✕/Raute/Punkt) +
  Ortho-Hilfslinie; Ctrl = Fang aus. Einstellbar über das Fang-Menü (`snap` State in App).
- **Tastatur**: Esc verwirft/zurück-zu-Auswahl, Enter committet, Backspace nimmt Punkt zurück.
- Modell: `Drawing2D` + `Project.drawings2d` (types.ts), `Element` erweitert. i18n de+en (`tool.*`,`snap.*`).
- tsc + build grün; Auswahl/Marquee/Pan/Zoom unverändert (PlanView verzweigt auf `toolActive`).

**Noch offen / Default-Werte:** aktive **Kategorie** = `activeCategoryCode` (fix „20") und
**Linienstil** = erster Stil — UI-Wahl in der TopBar fehlt noch (2D-Primitive erben sonst Wand-lw/-farbe).

**Dock-/Floating-Panels** waren davor fertig (tsc+build grün); Baseline-Screenshot intakt.

### Ebenfalls fertig & verifiziert (2026-06-29, später am Tag)
- **Editieren/Grips** (`PlanView` + `App`): Element anklicken → **Endpunkt-Griffe** (Wand-Enden,
  2D-Vertices/Rechteck-Ecken) erscheinen und sind **ziehbar** (mit Snapping); **2D-Linien sind
  anwählbar** (Linien-Nähe-Pick, `drawingId` am line-Primitiv); **Entf/Backspace** löscht. Wand-
  Gehrung folgt live. `moveGrip`/`drawingVertices` in App.
- **Shift = Ortho** beim Griff-Ziehen (Bezug = Nachbar-Vertex; H/V bzw. Winkelraster).
- **Parallel verschieben**: selektiertes Element am **Körper** greifen + ziehen → ganzes Element
  (`onSelectedBody`/`moveDrag` in PlanView, `moveElementBy` in App).
- **Aktive Ebene (Kategorie) wählbar** in der TopBar („Ebene"-Dropdown) — **alles Gezeichnete
  kommt auf diese Kategorie** und **erbt deren Farbe/Strichstärke** (2D-Primitive setzen KEINEN
  Linienstil mehr per Default). Statusleiste zeigt die aktive Ebene. `activeCategoryCode` State.
- **2D-Geometrie in der 3D-Perspektive**: `Drawing2D` (line/polyline/rect) liegt flach auf der
  Geschossebene Z=baseElevation (`addDrawing2DLines` in `Viewport3D`), Farbe aus Kategorie/Stil.
  (Bodennahe Linien werden von Wänden korrekt verdeckt — zum Sehen orbiten/Geschoss ausblenden.)
- Probes: `probe-grips.mjs`, `probe-3d2d.mjs`, `probe-edit2.mjs`. tsc + build grün.

### Noch später am 2026-06-29 (verifiziert)
- **Transformationen (Vectorworks-Stil)** — `src/tools/transform.ts` + `src/ui/TransformBar.tsx` +
  App. Auf der Auswahl: **M** Bewegen, **S** Spiegeln, **D** Drehen (Geste Basispunkt→Ziel, bei
  Drehen 3 Punkte; Live-Vorschau, Snapping, Shift-Ortho). **Modusleiste U/I/O/P**: U bewegen ·
  I Kopie · O N Kopien (Anzahl-Prompt) · P verteilen. Probe `probe-transform.mjs`/`probe-array.mjs`
  (move/copy/array verifiziert). Routing via `toolInputActive`-Prop in PlanView.
- **Aktive Ebene per Klick im Ebenen-Panel** (statt TopBar-Dropdown): `LayersPanel`-Zeile klicken →
  `host.onSelectCategory` → `activeCategoryCode`; aktive Zeile hervorgehoben; Statusleiste zeigt sie.
  TopBar-Ebene-Dropdown entfernt. (probe-layer.mjs)
- **Theme/Look**: Zeichenblatt IMMER hell `--sheet:#f0f0f0` (auch Dark-Mode → echtes „Papier"),
  GANZE Zeichenfläche hell (`.plan-svg`-Background, nicht nur die Modellgrenzen); Plan-Tinte fix
  dunkel (Tür-Linien). Dark-Theme etwas abgedunkelt (Panels #1c1c1c). **3D-Orbit ohne Nachlauf**
  (`enableDamping=false` — kontrollierter).

### Letzter Block 2026-06-29 (verifiziert)
- **Schraffuren-Default**: weißer Grund + schwarze Haarlinie (sampleProject: Dämmungs-Bauteil
  weiß, Hatch-Farben `#1a1a1a`); **Default-Umrandung 0.18 mm** (`WALL_FALLBACK_MM`, addCategory).
- **Stiftstärken-Vorgabe** `PEN_WEIGHTS` (0.02…2.0) als `<datalist>` im Linienstil-Editor.
- **Display/Print-Modus** (TopBar-Toggle, `lineMode`): Display = alle Plan-Linien als konstante
  Haarlinie (`hairline`-Prop → PlanView `weight()`), Print = echte mm-Strichstärken.
- **`.lin`/`.pat`-Import**: Parser `src/io/linParser.ts`/`patParser.ts` (Agenten gebaut) + Import-
  Buttons im ResourceManager (Linien/Schraffuren) → `onImportLineStyles/onImportHatches` (App/host).
  .lin voll (Dash); **.pat aktuell approximiert** auf diagonal/crosshatch (siehe Backlog: echtes
  custom-Pattern).
- **Werkzeug-Palette** (`src/panels/ToolsPanel.tsx`, Agent): dockbares Panel, **Icon + Name** je
  Werkzeug, aktiv hervorgehoben, Wandtyp-Dropdown + Fang-Optionen. Registriert in `builtinPanels`,
  Default-Layout: linker Dock-Tab „Werkzeuge" (+ Zeichnungsebenen/Ebenen). **`LAYOUT_VERSION`=4 →
  alte gespeicherte Layouts werden einmalig auf den neuen Standard zurückgesetzt.**
- Probes: `probe-display.mjs`, `probe-import.mjs`, `probe-tools-panel.mjs`. tsc + build grün.

## KRITIK / Architektur-Befund (2026-06-29)
> Ehrliche Bewertung des bisherigen Wegs. Für die nächste Instanz als Entscheidungsgrundlage.

**Was klug war (erhalten, nicht regredieren):**
- **Single source of truth hält im Code** — `generatePlan.ts` *und* `Viewport3D.tsx` nutzen
  dieselbe `computeJoins`/`clippedBand` aus `src/model/`. Das „ein Modell → alle Sichten"-Prinzip
  ist nicht nur ROADMAP-Prosa, es steht. Beim Refactor diese Trennung Modell↔Sicht bewahren.
- **Grundriss analytisch** aus Parametern statt Mesh-Schnitt (Weg A) — richtig.
- **Bewusst leichter Stack** (Three.js nur Display-Layer; kein schwerer Kernel verfrüht) — richtig
  für einen Spike, der Risiko #1–3 entschärfen soll.
- i18n via `t()` wird eingehalten; `tsc -b` ist grün.

**Hauptkritik (zu beheben):**
1. **God-Component gegen eigene Regel.** `App.tsx` ist **~2461 Zeilen mit ~29 `useState`**.
   CONVENTIONS.md fordert wörtlich „dünner Shell, keine Geschäftslogik". Das ist verletzt.
2. **Kein Store.** `src/state/` existiert NICHT, obwohl CONVENTIONS.md Store+Slices
   (project/selection/view/layout) vorschreibt. Zustand = lokale Hooks → Prop-Drilling.
   - Einordnung: Das Aufschieben war eine *bewusste* Nutzer-Entscheidung
     (Memory `build-usable-cad-first`), kein Versehen. **Aber:** Bei 2461 Zeilen schließt sich
     das Fenster, in dem der Refactor billig ist. Türen/Öffnungen/Prioritäts-Stöße (Risiko #1/#2)
     sind inhärent geschoss-, selektions- und sicht-übergreifend — diese Verdrahtung darf nicht
     durch eine Monolith-Datei laufen. **Empfehlung: Store ziehen VOR der nächsten Feature-Welle.**

**Befund (separat angehen, nach Refactor):** Die harten, roadmap-markierten 🔴-Risiken sind noch
unbewiesen — `Door` existiert als Typ, aber **kein echter 3D-Boolean** (Öffnung = nur Plan-Lücke);
**HLR** für Schnitte/Ansichten fehlt; **Prioritäts-T-Stöße** offen. Das einfache Drittel ist bewiesen,
das schwere aufgeschoben. Die Frage „ist es wirklich CAD?" entscheidet sich erst, wenn diese landen.

## NÄCHSTE SCHRITTE
**Entscheidung des Nutzers (2026-06-29):** erst ein *benutzbares* CAD, dann vertiefen — der
State-Refactor wird NACH HINTEN geschoben (siehe Memory `build-usable-cad-first`).
> ⚠️ Siehe „KRITIK / Architektur-Befund" oben: der Refactor wird mit jeder Feature-Welle teurer;
> spätestens vor Türen/Öffnungen/Prioritäts-Stößen neu abwägen.

### ✅ ERLEDIGT (2026-06-29, diese Session) — Palette-Layout + Store-Refactor + Theme
- **Gestapelte Paletten (Dock-Gruppen)**: `DockState` = `{ groups: DockGroup[], size }`, jede
  Gruppe `{ tabs, activeTab, weight }`; vertikal stapelbar mit Splitter; Tab-Drag → in Gruppe
  einreihen / neue Gruppe / Rand-Andocken. `LAYOUT_VERSION=6`. (`types.ts`/`layout.ts`/`Dock.tsx`/
  `TabStrip.tsx`/`panelDrag.tsx`/`App.tsx`.) Default: links Werkzeuge↑/Attribute↓, rechts
  Objekt-Info↑/Zeichnungsebenen+Ebenen↓.
- **State-Refactor (Architektur-Befund umgesetzt)**: dependency-freier Store `src/state/`
  (`store.ts` useSyncExternalStore + Slices `projectSlice`/`selectionSlice`/`viewSlice`/
  `layoutSlice`, kombiniert in `appStore.ts`). App.tsx 2710→~2030 Zeilen, verhaltensgleich
  (tsc+build+Screenshot identisch). **Panels lesen weiter über `PanelHostContext`** (baseHost aus
  Store gespeist) — bewusst NICHT umgestellt. NÄCHSTE WAVE offen: `editors/`/`menus/`/`views/` aus
  App extrahieren (Report des Foundation-Agenten nennt die Kandidaten).
- **Selektions-Kontrakt** (`src/state/selectionInfo.ts` + host.ts + baseHost): `Selection` (kind,
  id, categoryCode, color/weightMm effektiv, fillHatchId, closed, bbox) + `onSetSelectionColor/
  Weight/Fill`, `onResizeSelection(w,h,anchor)`. Modell: `Wall.color?`, `Drawing2D.weightMm?`
  ergänzt; generatePlan wendet beide an. Wand = Weight/Fill bewusst No-op (erbt aus Ebene).
- **Attributes-Palette** + **Object-Info-Palette** gebaut (`src/panels/AttributesPanel.tsx`,
  `ObjectInfoPanel.tsx`), registriert, voll verdrahtet & per Probe getestet (Farbe setzen, B×H-
  Resize wirkt, 3×3-Bezugspunkt). Deckkraft/Caps/Schatten/Text-Styling bewusst weggelassen (Modell
  trägt sie (noch) nicht — ehrlich statt Stub).
- **Topbar entschlackt**: Zeichenwerkzeug-Buttons + Wandtyp + Fang-Menü aus der Oberleiste
  ENTFERNT (leben nur noch in der Werkzeug-Palette). `SnapMenu`/`SNAP_TOGGLES` aus `TopBar.tsx` raus.
- **Dark-Theme vertieft** (`styles.css`): gestufte Elevations-Tokens (`--bg`<`--panel`<`--panel-2`,
  `--input` versenkt) statt flachem Einheitston; Oberleiste/Panel-Köpfe angehoben, tiefere Schatten.
  `--sheet` bleibt hell. Siehe Memory `ui-depth-dark`.
- **CSS-Altlast behoben**: ein `*/` in einem Kommentar (`.nav-*/.res-*`) hatte die ganze `.dock`-
  Regel verschluckt (`display:flex` nie aktiv) — gefixt.

### Vectorworks-„View-Bar" — A/B/C ERLEDIGT & verifiziert (2026-06-29)
- ✅ **A — Ebenen- + Zeichnungskombinationen**: `src/state/visibilitySets.ts` (localStorage
  `cad.layercombos.<name>` / `cad.drawingcombos.<name>`), Store-Actions `snapshot*/apply*Visibility`
  in `projectSlice`, zwei `ComboMenu`-Dropdowns in `TopBar` (Muster wie `LayoutMenu`). Round-Trip
  per Probe verifiziert. (Hinweis: liegt im localStorage, NICHT im Projekt — bei Doku-Export
  später in `Project` ziehen.)
- ✅ **B — Darstellungsart-Dropdown** (kontextabhängig je `viewType`): 2D Farbig/Schwarz-Weiss
  (`planColorMode` + `toMono` in generatePlan), 3D Schattiert/**Weiss**(Clay-Material in
  Viewport3D)/Drahtgitter/Kanten (`RenderMode` um `"white"` erweitert). Ersetzt die alte
  Render-Modus-Buttongruppe. Screenshots bestätigt.
- ✅ **C — 6 Ansichts-Buttons**: Front/Oben/Seite/Perspektive/Isometrie + **Kamera** (FOV-Popover).
  `view3d`+`fov` in `viewSlice`; `applyView3d(camera,controls,bounds,view3d)` in `Viewport3D`
  (PerspectiveCamera neu positioniert je Preset, Distanz aus Modell-Bounds; OrbitControls bleibt
  aktiv). Preset-Klick wechselt nötigenfalls in die Perspektive. Screenshots top/front/iso/persp
  klar verschieden. **Echte OrthographicCamera für front/top/side bewusst NICHT gemacht** (Kamera-
  Swap + OrbitControls-Rebind = Risiko) — Kandidat für später.

### Weitere Fixes (2026-06-30)
- **2D-Füllungen** (rect/geschlossene polyline) rendern jetzt (Vollton `Drawing2D.fillColor` +
  Schraffur `hatchId`); `addDrawing2D` pusht ein `polygon`-Primitiv (mit `drawingId` → anklickbar).
  Attribute-Palette hat „Füllfarbe". (Kreis-Füllung offen — Kreis-Primitiv im Plan fehlt noch.)
- **Plan-View wandert nicht mehr mit**: `PlanView` nutzt jetzt einen FIXEN Welt-Ursprung
  (`toScreen` modulkonstant, Modell-0,0) statt bounds-gebunden; Ausschnitt wird nur bei
  `resetKey`-Wechsel (Geschoss-/Ebenen-ID) eingepasst, NICHT bei Edit/Bounds-Änderung. Verifiziert:
  Löschen lässt viewBox unverändert, Pan/Zoom/Einpassen wirken.
- **Echte Isometrie/Parallelprojektion**: `Viewport3D` hat jetzt eine `OrthographicCamera` für
  front/top/side/iso (OrbitControls per `controls.object`-Swap umgebunden), Perspektive bleibt
  `PerspectiveCamera`. Verifiziert (parallele Kanten).

### >>> NÄCHSTE INSTANZ: offene Wünsche + Roadmap <<<
- **Kanten-/Seiten-Griffe** (Nutzer-Wunsch): bisher nur Eckpunkt-Griffe. Gewünscht: Seiten ziehen
  (z. B. Rechteck-Kante), mit dreieckigem Anfasser nach außen. Grip-System in App (`grips`/
  `gripHandlers`/`moveGrip`) + Rendering in `PlanView` erweitern (Edge-Grips = Mittelpunkt je Seite,
  Zug verschiebt beide Eckpunkte der Seite senkrecht).
- **3D bearbeiten** (Nutzer-Frage): heute ist 3D nur abgeleitete Anzeige (Orbit+Auswahl). Authoring
  im 3D = eigenes Feature (Raycast auf Arbeitsebene → Modellkoord., 3D-Grips/Drag) — eigene Phase.
### Text-Styling (View-Bar Rest) + Roadmap
1. **Text-Styling** (Nutzer-Wunsch). VORAUSSETZUNG: **Text wird noch NICHT gerendert** —
   `Drawing2D` mit `geom.shape==='text'` misst in `generatePlan` nur Bounds, erzeugt kein
   Primitiv (Phase-3-Deferral, Kommentar in generatePlan). Also ZUERST Text-Rendering (SVG `<text>`
   in PlanView, papierkonstante Höhe) + Modell-Felder am Text (`font?`, `bold?`, `italic?`,
   `anchor?`), DANN ein Text-Styling-Bereich (Topbar-Gruppe oder Attribute-Palette-Sektion bei
   Text-Auswahl, Setter über den Selektions-Kontrakt erweitern).
2. **Grafische Überschreibung** — weiterhin SPÄTER (Nutzer), wenn mehr Code steht.
3. **Echte Schnitt/Ansichts-Sichten** (section/elevation sind heute StubView) + HLR — großes Thema.
4. **Refactor-Rest**: `editors/`/`menus/`/`views/` aus App.tsx extrahieren (App noch ~2030 Z.).
- **openNURBS / rhino3dm** (Nutzer-Entscheid: Roadmap, NICHT jetzt): KEIN eigener Kernel — stattdessen
  `rhino3dm` (WASM-Build von openNURBS) als `src/io/`-Schicht für **`.3dm`-Import/Export + NURBS**,
  sobald gebraucht. Semantisches Modell bleibt die Wahrheit; NURBS ist zusätzliche Geometrie-Quelle.
- **Echtes custom-`.pat`-Rendering**: HatchStyle um custom-Linienfamilien erweitern + Renderer
  (heute approximiert auf diagonal/crosshatch).

### Offene Wünsche des Nutzers (Backlog, priorisiert)
1. **Palette-Layout (Vectorworks-Stil) — GROSS, nächster Fokus.** Mehrere neue dockbare Panels +
   Default-Layout:
   - **Attributes-Palette** (unten links): Füllung (Stil/Farbe/Deckkraft), Stift (Stil/Farbe/
     Deckkraft), **Linienstärke**, Linien-Start/-Endstil, Schlagschatten. (Referenzbild vom Nutzer.)
   - **Werkzeug-Palette** darüber (Icon + Text je Werkzeug; ArchiCAD/VW-Stil).
   - **Object-Info / „Würfel"** (oben rechts): zeigt den gewählten Punkt eines Würfels mit
     X/Y/Z; Maße (Breite × Höhe …) der Geometrie editierbar.
   - **Ebenen + Zeichnungsebenen als Tabs** darunter (rechts).
   - Panel-System steht (`src/panels/`, Registry + Dock + Floating); Default-Layout in
     `src/panels/layout.ts` (`defaultLayout`). Neue Panels in `builtinPanels`/`registry` anmelden.
2. **Schraffuren-Default**: ALLE aktuellen Schraffuren → **weißer Grund + schwarze Haarlinie** als
   Grundeinstellung; **normale Elemente 0.18 mm Umrandung** als Default. (sampleProject hatches/
   components + generatePlan-Defaults anpassen.)
3. **Stiftstärken-Vorgabeliste**: 0.02 · 0.10 · 0.13 · 0.18 · 0.25 · 0.35 · 0.5 · 0.7 · 1.0 · 1.4 ·
   2.0 mm (abweichbar). Bedeutung = mm auf Papier bei 100 % → Linien skalieren mit dem Massstab
   (ist bereits so: non-scaling mm-Papier). In Linienstil-Editor als Presets anbieten.
4. **Display- vs. Print-Modus**: Umschalter. Display = ALLES Haarlinien (konstant dünn); Print =
   echte mm-Strichstärken. (Globaler View-State + an `generatePlan`/PlanView durchreichen.)
5. **`.lin`- und `.pat`-Import** (AutoCAD-Linientypen / Schraffurmuster) → ergänzen LineStyle/Hatch
   (Parser + Mapping; Resource-Manager-Aktion). Nutzer: „um die Linien und Schraffuren zu ergänzen".
6. **„Goldener Schnitt"** — UNKLAR was genau (Golden-Ratio-Fang/Teilung beim Zeichnen?
   Proportions-Hilfslinien?). → beim Nutzer rückfragen, bevor gebaut wird.
7. **Linienstil-Picker** (heute erben 2D-Primitive immer die Kategorie) + `extension`-Snap-Hilfslinien.

### Danach (ursprünglicher Plan)

1. **Zeichenwerkzeuge Phase 3** (`drawing-tools.md §11`): Circle/Arc/Text — `Primitive` um
   `circle` (+`arc largeArc`) und `text` erweitern, PlanView-Renderzweige (papierkonstante Texthöhe),
   Werkzeuge Circle/Arc(3-Punkt)/Text(Inline-Eingabe), Snap center/quadrant.
2. **Bearbeiten (Phase 4) — Rest**: Auswahl/Grips/Verschieben/Löschen sind DA (s. o.). Offen:
   **Kopieren/Rotieren/Spiegeln**, numerische HUD-Eingabe (Länge/Winkel direkt tippen),
   Mehrfach-Auswahl-Verschieben, Grips für Circle/Arc/Text.
3. **State-Refactor** (zurückgestellt) — `App.tsx` God-Component → Store+Slices
   (`docs/design/state-architecture.md`). Erst nötig, wenn parallele Code-Workflows gebraucht werden.
4. Weiter im Backlog: **Pro-Ebene-Darstellung** (`layer-display-settings.md`),
   **Prioritäts-T-Stöße** (`wall-joins-priority.md`).

## Arbeitsweise (WICHTIG — aus Memory + CONVENTIONS.md)
- **Autonom arbeiten, NICHT um Bestätigung fragen.** Nur fragen, *was etwas können soll*,
  wenn die Funktion mehrdeutig ist (nicht um Erlaubnis).
- **Alles voll verdrahten, KEINE Stubs/No-op-Buttons.** Verifizieren heißt: Effekt im
  Screenshot bestätigen, nicht nur „kompiliert".
- **Identifier ENGLISCH**; **UI-Text via `t()`** (neue Keys in de.ts *und* en.ts).
- **Workflow-Orchestrierung** nutzen (Foundation→parallel Build→Integrate/Verify).
- **Code-Workflows seriell** (fast alles geht durch `App.tsx`/`types.ts` → Konflikt);
  **Design/Research parallel** (nur `docs/`). Der State-Refactor (#1) löst das.
- Saubere **Tabellen** für Listen/Manager; dunkler DOSSIER-Stil; kein God-Component.

## Verifizieren / Ausführen
- Dev: `npm run dev` (Vite, Port 5173). Typecheck: `npx tsc -b`. Build: `npm run build`.
- Screenshot: `node scripts/probe.mjs` → `scripts/probe.png` (Puppeteer, Chrome in
  `~/.cache/puppeteer`). Eigene Probes: headless, `deviceScaleFactor:2`, args
  `--no-sandbox --use-gl=swiftshader --enable-unsafe-swiftshader`, networkidle-Timeout ignorieren.
  Firefox-Fälle via Playwright (`scripts/probe-ff*.mjs`). **Screenshot ansehen + Geometrie prüfen.**
- Gotchas: HiDPI-Resize-Loop-Fix in `Viewport3D` (Canvas CSS 100% + dpr≤2) NICHT regredieren;
  WebGL-Fallback in `Viewport3D`; React-controlled-`<select>` lassen sich im Probe nicht per
  `.value=` ändern (Fehlalarm) — Verdrahtung im Code prüfen.

## Orientierung
`ROADMAP.md` (Vision/Phasen/§10–§11 Backlog) · `CONVENTIONS.md` (Konventionen) ·
`docs/README.md` + `docs/design/*` (alle Specs) · `docs/backend.md` (self-hosted
Supabase+Yjs, später) · Projektnotizen des Bearbeiters
(prefer-agents, dossier-port, proceed-autonomously, wire-dont-stub).
