Files
DOSSIER-STANDALONE/HANDOVER.md
T

73 KiB
Raw Blame History

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()aufonConfirmdurch. 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 -b0,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, applysetRoomStampDoc; 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 ~15431548) 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 ~416449).
  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/pickhost.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 FERTIGsrc/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 1314px, 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: selectedDrawingIdselectedDrawingIds[] (selectionSlice); Marquee wählt Linien (marqueeHitDrawings), Ctrl/Cmd+A, Multi-Delete, Multi-Highlight (.plan-sel-draw), Shift-Klick-Mehrfachauswahl (PlanSelection.shiftonPlanSelect 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 1623, 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.onSelectCategoryactiveCategoryCode; 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 CodegeneratePlan.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 #13 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 gerendertDrawing2D 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.mjsscripts/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).