Nutzer-Report: Editieren bestehender Linien/Kreise fühlt sich noch
eingeschränkt an, man sollte einen Wert eingeben können.
Root Cause bei Kreis/Bogen: cycleGripEditField() ermittelt den Anker für die
Tab-Feld-Eingabe (Länge/Winkel) über eine Polylinien-Heuristik ("Nachbar-
Griff im Array") — für den Kreis-Radius-Griff (nur 1 Griff, kein Nachbar)
gab das GAR KEINEN Anker, Tab tat nichts. Beim Bogen (3 Griffe) fand die
Heuristik zwar irgendeinen Nachbar-Griff als Anker, aber semantisch falsch —
moveGrip() misst dort tatsächlich Winkel/Radius relativ zum ZENTRUM, nicht
relativ zum jeweils anderen Griff.
Fix: cycleGripEditField() erkennt jetzt Kreis/Bogen (selectedDrawing.geom)
und nimmt deren `center` als Anker — "Länge" tippen setzt damit exakt den
Radius, konsistent mit moveGrip()s eigener Distanz-vom-Zentrum-Logik.
tsc -b / vitest run (919/919) / npm run build grün. Keine Unit-Tests ergänzt
(useGripEditing ist ein React-Hook ohne renderHook-Infrastruktur im
Projekt — bestehende Testgrenze, s. andere Hooks). Nutzer prüft in der
laufenden App.
Nutzer-Report: nach dem vorigen Fix (kein transform:scale() mehr) war die
Feldgrösse jetzt fest/zoomunabhängig — das getippte Feld sollte aber genau
so aussehen wie der später gerenderte Text (Schriftgrösse) UND genau so
gross sein wie das gezeichnete Feld selbst (Breite/Höhe), beides am echten
Zoom.
Lösung: Position/Breite/Feldhöhe/Schriftgrösse folgen wieder alle
currentPxPerMeter() (live) — aber die Schriftgrösse geht NICHT mehr über
transform:scale() auf die GANZE Box (das hatte die Toolbar beim Reinzoomen
mit aufgebläht, der vorige Bug), sondern nur auf die Textfläche selbst via
CSS-Variable (--rt-font-size/--rt-min-height, gesetzt auf dem Wrapper,
geerbt bis zu .rt-surface in styles.css). Die Toolbar (.rt-toolbar)
referenziert diese Variable nicht und bleibt dadurch immer normal gross und
bedienbar, unabhängig vom Zoom — nur der Text selbst ist WYSIWYG.
tsc -b / vitest run (919/919) / npm run build grün. Nutzer prüft erneut in
der laufenden App (kein Browser-Tool hier verfügbar für eigene visuelle
Kontrolle).
Nutzer-Report nach dem Sichtbarkeits-Fix: Editor erscheint "riesig", nicht
über dem Ankerpunkt, Enter erzeugt keine neue Zeile. Ursache: die Editor-
Box (Toolbar + Text) bekam via transform:scale() dieselbe Skalierung wie
die Modell-Geometrie (scale = heightM·pxPerMeter/16) — bei normalem/nahem
Arbeits-Zoom beim Textplatzieren wird das schnell 3-15x, die GESAMTE Toolbar
(Buttons, Dropdowns) skaliert mit hoch und sprengt den Bildschirm. Plausible
Folge fürs Enter-Problem: bei so einer Verzerrung landet der Fokus nicht
zuverlässig in der eigentlichen Text-Fläche (rt-surface), sondern z. B. auf
einem der riesigen Toolbar-Buttons, wo Enter nichts mit Zeilenumbruch zu tun
hat.
Fix: kein transform:scale() mehr. Die ANKER-Position folgt weiter dem
echten Zoom (vbToClient/toScreen, WO die Box aufgeht bleibt korrekt), aber
die Editor-GRÖSSE (Schrift/Toolbar) bleibt jetzt immer bei ihrer normalen,
zoomunabhängigen CSS-Grösse — wie im Raumstempel-/Layout-Dialog. Die
Textspalten-Breite (wrapWidth) folgt weiterhin der Modell-Breite, aber über
eine FESTE Umrechnung (220px/m) statt dem Live-Zoom, damit der Rahmen beim
Rein-/Rauszoomen während des Tippens nicht ständig neu umbricht.
tsc -b / vitest run (919/919) / npm run build grün. Konnte die konkrete
DOM-Fokus-Kette nicht interaktiv nachvollziehen (kein Browser-Tool
verfügbar) — Nutzer prüft erneut.
Nutzer-Report nach der Umstellung auf Inline-Editing: beides funktionierte
nicht — "Text" liess sich kein Ankerpunkt setzen, "Textspalte" sollte ein
Rechteck statt nur eine Breite sein.
Root Cause für "Text": `.planview-inline-text` bekam ohne Spaltenbreite
`width: undefined` — bei position:fixed ohne rechten Rand greift shrink-to-
fit-Sizing, das für ein LEERES contentEditable auf ~0px kollabiert. Der Klick
erzeugte tatsächlich ein Element + öffnete den Editor, beides war nur
unsichtbar (eine 0px-Box lässt sich nicht anklicken/fokussieren). Fix: neue
Klasse .planview-inline-text-free (kein wrapWidth) erzwingt `width:
max-content` + `white-space: pre` auf Editor/Surface (Box wächst mit dem
Inhalt statt umzubrechen — "Text" bleibt einzeilig bis Enter, wie gewünscht)
plus eine CSS-Mindestbreite (160px) für den leeren Startzustand.
"Textspalte" (textbox.ts) zeichnet jetzt ein RECHTECK (zwei diagonale Ecken,
beliebige Zugrichtung) statt nur eine horizontale Breitenlinie — die
aufgezogene Höhe wird nicht im Modell gespeichert (das Textformat kennt nur
`width` für den Wortumbruch, keine feste Rahmenhöhe), sondern als
Editor-MINDESThöhe durchgereicht: CommandResult/EngineHost.focusDrawing um
`focusMinHeightM` erweitert (Passthrough wie focusDrawingId), App.tsx hält
sie in einem neuen editTextMinHeightM-State (useContextMenuState.ts),
PlanView nutzt sie als CSS-min-height des Editors — der Rahmen wächst bei
mehr Text darüber hinaus, schrumpft aber nicht darunter (InDesign-Verhalten).
Beim Doppelklick-Editieren bestehender Elemente wird die Mindesthöhe
zurückgesetzt (kein Nachwirken einer vorigen Textspalten-Erstellung).
textbox.test.ts an die neue Rechteck-Geste angepasst (+ Test für beliebige
Zugrichtung). tsc -b / vitest run (919/919) / npm run build grün. Die
eigentliche Sichtbarkeits-Vermutung (CSS-Kollaps) konnte ich nicht
interaktiv im Browser verifizieren (kein Browser-Tool verfügbar) — Nutzer
prüft erneut in der laufenden App.
Nutzer-Report: Textfelder waren nicht mehrzeilig. Ursache: die Text-/
Textspalte-Werkzeuge holten ihre Eingabe über die Befehlszeile
(CommandLine.tsx) — ein einzeiliges <input>, Enter committet sofort, kein
Zeilenumbruch möglich. Das betraf auch "Textspalte", obwohl die explizit für
mehrzeiligen Wortumbruch gedacht ist.
Lösung (Nutzer-Vorgabe: kein Dialog, direktes Editieren auf der Fläche wie
im Layout-System): text.ts/textbox.ts committen nach dem Platzieren (Anker
bzw. Anker+Breite) sofort ein LEERES Text-Element und melden
`focusDrawingId` — dafür CommandResult/EngineHost um einen Passthrough
erweitert (engine.ts applyResult ruft host.focusDrawing(id)). App.tsx setzt
darauf Auswahl + editTextId (dieselbe State-Variable wie beim bisherigen
Doppelklick-Editieren). PlanView rendert bei gesetztem editTextId jetzt
einen RichTextEditor-Overlay direkt über dem Element (position:fixed via
vbToClient/toScreen, eigene Mini-Toolbar erscheint darüber) statt des
bisherigen TextEditorDialog-Modals — Prinzip 1:1 vom Layout-Blatt-Inline-
Editor (LayoutSheet.tsx) übernommen. Die neuen Props (editTextId/
onCommitTextEdit/onCloseTextEdit) laufen durch Content/LevelPlanView/
SectionPlanView (viewportContent.tsx) bis zu PlanView durch.
Verhalten der beiden Werkzeuge (Nutzer-Vorgabe): "text" bleibt ohne
Wortumbruch (kein `width`) — Enter erzeugt weitere Zeilen, aber keine
automatische Breite. "textbox" setzt `width` und verhält sich damit wie ein
InDesign-Textrahmen (echter Wortumbruch live beim Tippen, kein reines
Render-Detail mehr).
commitTextEdit (useMouseSelectionHandlers.ts) schreibt jetzt bei JEDEM
Tastendruck (kein Guard mehr gegen leeren Text — nötig, damit Löschen bis
auf null Zeichen sich sofort spiegelt); neues closeTextEdit räumt ein beim
Schliessen noch leeres Element auf (verhindert Leichen bei abgebrochener
Neuerstellung).
4 neue Tests (text/textbox: sofortiger Commit + focusDrawingId). tsc -b /
vitest run (918/918) / npm run build grün. Rendering/Positionierung des
Overlays selbst nicht automatisiert testbar — Nutzer prüft visuell in der
Tauri-App.
Neuer Befehl multiline.ts, baut auf derselben Offset-Geometrie auf wie der
bestehende offset-Befehl (offsetSegment) — erzeugt aber alle Kopien auf
einmal statt einzeln nacheinander offsetten zu müssen. Deckt gerade
Basislinien ab (häufigster Fall: Grenzlinie+Abstand, Leitungspaar,
Randlinien), keine mehrsegmentige Variante.
Ablauf: Start-/Endpunkt (Tab-Felder Länge/Winkel wie line.ts), dann Abstand
per Maus-Klick (Seite bestimmt Vorzeichen, wie bei offset.ts) oder getippt;
die Anzahl (Default 3, Bereich 2-6) zyklt über eine Inline-Option und bleibt
wie Abstand/Seite über Aufrufe hinweg persistent.
In Registry/Aliase (ml/parallel), ToolsPanel (COMMANDS_EDIT neben Offset),
CommandIcon, i18n de/en aufgenommen.
2 neue Tests. tsc -b / vitest run (916/916) / npm run build grün.
Anders als Fillet/Extend existierte hierfür noch gar keine Geometrie —
chamferCorner (src/geometry/kernel2d.ts) ist neu: gleicher Abstand vom Eck
auf beiden Schenkeln, gerade verbunden (klassischer Gleichabstands-Chamfer,
analog zu filletCorner, nur ohne Bogen — daher exakt statt tesselliert).
commands/cmds/chamfer.ts wortwörtlich nach dem fillet.ts-Muster: Ecke wählen,
Distanz per Maus oder getippt (Tab-Feld). In Registry/Aliase
(ch/fasen), ToolsPanel (COMMANDS_EDIT neben Fillet), CommandIcon, i18n
de/en aufgenommen.
3 neue Tests (Fasung, Fehltreffer, Distanz-zu-gross-No-op). tsc -b /
vitest run (914/914) / npm run build grün.
filletCorner (Eckpunkt-Verrundung) und extendSegment (Gegenstück zu Trim)
existierten fertig implementiert und getestet im Kernel (kernel2d.ts), hatten
aber keinen einzigen Aufrufer — kein Command, kein Werkzeug-Knopf, für den
Nutzer nicht erreichbar. Reine Verdrahtungsarbeit, kein neues Geometrie-
Problem:
- fillet.ts: Ecke einer polyline/rect wählen (nächstgelegener Eckpunkt mit
zwei Nachbarn), Radius per Maus oder getippt (Tab-Feld wie bei circle.ts).
Da Polylinien im Modell keine Bogen-Segmente kennen (kein "Bulge" —
bekannte, separate Lücke), wird die Verrundung als kurze Punktfolge
tessselliert statt als echtes Arc-Objekt gespeichert; ein rect wird beim
ersten Fillet zu einer polyline. Ehrlich im Kopfkommentar dokumentiert.
- extend.ts: offene Kurve (line/offene polyline) nahe einem Ende anklicken,
dieses Ende wird bis zur nächsten anderen Kurve verlängert (implizite
Cutter wie bei Trim). Kein Treffer → No-op, Befehl bleibt aktiv.
Beide in COMMANDS/ALIASES registriert (fi/verrunden, ext/verlaengern),
i18n de/en ergänzt, in ToolsPanel COMMANDS_EDIT neben Trim/Join aufgenommen,
eigene Icons in CommandIcon.tsx (bisher: unbekannter Name → kein Icon).
Rotate NICHT angefasst: der ursprüngliche Befund „Rotate ist keine getippte
Kommandozeilen-Aktion, nur ein UI-Button" war unvollständig — App.tsx routet
sowohl den Ribbon-Button als auch das getippte Wort „rotate"/„drehen" ganz
bewusst an startTransformOp() (den reicheren U/I/O/P-Move/Copy/Array/
Verteilen-Fluss), nicht an die generische Befehls-Engine (Kommentar dort
erklärt das explizit). Ein zuerst geschriebener, einfacher rotateCommand
(nur „move"-Modus) wäre über diese Sonderfälle nie erreicht worden UND hätte
über neue Aliase wie „ro" einen inkonsistenten Zweit-Pfad geöffnet (voller
Funktionsumfang bei „rotate", abgespeckter bei „ro") — verworfen, bevor
committet.
6 neue Tests (fillet: Verrundung + Fehltreffer; extend: Verlängerung +
Kein-Cutter-No-op). tsc -b / vitest run (911/911) / npm run build grün.
resolveBackground (generatePlan/shared.ts) existierte korrekt implementiert,
wurde aber nirgends aufgerufen — Wand-/Decken-Poché nutzte überall fest
HATCH_INK/HATCH_PAPER, die By-Layer-Hintergrundfarbe kam nie im Renderer an.
Signatur an resolveForeground angeglichen (string | undefined statt
erzwungenem Component.color-Fallback), damit "kein Override gesetzt" weiter
auf die neutrale SIA-Poché-Konvention fällt statt auf die rohe Bauteilfarbe —
sonst hätte jedes bestehende Projekt ohne gesetzten Override optisch
umgeschlagen. Verdrahtet in walls.ts/ceilings.ts (Grundriss) und neu auch in
splitWallLayers/splitSlabLayers (Schnitt, toSection.ts) — dort fehlte bei
MEHRSCHICHTIGEM Wand-/Deckentyp zusätzlich foreground/hatchId komplett
(owner.foreground/hatchSource wurden nie an resolveForeground/resolveHatchId
übergeben, nur der Default lief); splitSlabLayers bekam dafür einen neuen
`owner: Ceiling`-Parameter (die einschichtigen Pfade über
resolveWallSectionStyle/resolveCeilingSectionStyle waren bereits korrekt).
Ceiling.strokeWeight/strokeWeightSource war komplett unverdrahtet (fixe
LAYER_LINE_MM-Konstante) — das Attribut-Panel bietet aber einen Editier-
Umschalter dafür (identisch zur Wand). addCeilingPoche bekommt jetzt eine
aufgelöste lwMm (resolveStrokeWeight, wie schon bei der Wand); die
Deckenkontur bleibt weiterhin die gestrichelte Überkopf-Ansichtslinie, nur
die Dicke folgt jetzt dem Override — Verhaltensänderung auch im Default-Fall,
wenn eine Kategorie eine von LAYER_LINE_MM abweichende Strichstärke trägt.
Bewusst NICHT angefasst: Wand-/Decken-Strichstärke im Schnitt-Umriss
(SECTION_CUT_OUTLINE_MM in generateSectionPlan) — eine einzige, uniforme
Konstante für ALLE Schnitt-Polygone, kein Per-Bauteil-Wert; das wirkt wie
eine bewusste Zeichnungskonvention (analog zur "grob"-Vollschwarz-Poché nach
SIA 400 B.9/36), nicht wie der "UI verspricht etwas, Renderer hält es nicht"-
Bugmuster der übrigen Funde. Würde ausserdem eine Erweiterung von
SectionCutPolygon um ein Gewichtsfeld brauchen (heute keins) — grösserer,
separat zu scopender Eingriff.
6 neue Tests (splitWallLayers/splitSlabLayers mit/ohne Override, Grundriss
weiterhin über die 907 Gesamttests abgedeckt). tsc -b / vitest run (907/907)
/ npm run build grün.
Seit der Umstellung auf echte SVG-Kreis-/Bogen-Primitive (drawingCircle/
drawingArc statt 64-Ecken-Polygon-Annäherung) wurde die Interaktionsschicht
nie nachgezogen — betraf drei unabhängige Stellen mit derselben Ursache:
- Einzelklick: pickDrawing() testete nur kind==="line", fiel sonst auf
Text-/Bild-Bbox zurück. Neuer distToDrawingRing()-Zweig: Ring-Abstand in
Bildschirm-Pixeln (zoom-unabhängig wie die Linien-Toleranz), bei gefüllten
Kreisen zählt auch innerhalb als Treffer; Bögen zusätzlich auf ihre
Winkel-Spanne begrenzt. grabbedBody() (Körper-Verschieben) profitiert
automatisch mit, da es denselben Picker nutzt.
- Marquee (Rahmenauswahl): marqueeHitDrawings() sammelte nur line/polygon —
drawingCircle/drawingArc UND drawingText/drawingImage fehlten komplett
(Text/Bild waren nie per Rahmen erfassbar, unabhängig von der Kreis-Frage).
Kreis/Bogen werden für den Punkt-Test auf Ring-Stützpunkte abgetastet
(Bogen nur innerhalb seiner Winkel-Spanne, nicht als Vollkreis), Text/Bild
über ihre Bounding-Box.
- Griffe: drawingVertices() hatte keinen Fall für circle/arc → leeres Array,
selbst nach Fix von Klick+Marquee also unverschiebbar/nicht editierbar.
Kreis bekommt einen Radius-Griff (Ost), Bogen Start-/End-Winkel-Griffe plus
einen Radius-Griff auf halbem Bogen — moveGrip() entsprechend erweitert.
10 neue Tests (drawingVertices/moveGripOf für Kreis/Bogen, marqueeHitDrawings
für alle vier zuvor fehlenden Primitive-Arten). tsc -b / vitest run
(901/901) / npm run build grün. Der Einzelklick-Pfad selbst (PlanView.tsx-
Closures) ist nicht automatisiert testbar — visuell in der Tauri-App prüfen.
Faktische Fehler korrigiert: geometry-Crate längst gelöscht (5 statt 6
Rust-Crates), src/section/ existiert nicht mehr, opencascade.js komplett
raus (nicht nur "nur noch von totem Code importiert"), Ribbon-UI gelöscht
(nur noch ein Icon-Helfer übrig). Frische Zahlen (LOC, Testanzahl) statt
vager Formulierung.
Grösster Fund: JEDE im "Weiterlesen"-Abschnitt verlinkte Datei (STATUS.md,
ARCHITECTURE.md, ROADMAP.md, HANDOVER.md, PENDENZEN.md, CONVENTIONS.md,
docs/) ist per .gitignore bewusst vom öffentlichen Repo ausgeschlossen —
das README verlinkte trotzdem überall drauf. Für jeden Besucher des
öffentlichen Gitea-Repos waren das tote Links. Alle entlinkt, mit einer
kurzen ehrlichen Erklärung ersetzt statt stillschweigend entfernt.
Nebenbei: package.json hatte noch ein build:geometry-Skript, das auf die
gelöschte Crate zeigte — ebenfalls tot, entfernt.
ts-prune nach dem ersten Durchgang erneut laufen lassen. compute/index.ts
(101 Z., PoC-Brücke zu einem nie gebauten Rust-Command, im eigenen
Kopfkommentar schon als ungenutzt markiert) komplett gelöscht. ribbonItems.ts
war nur noch für das bereits gelöschte RibbonBar.tsx da (kein anderer
Importeur mehr) — ebenfalls komplett weg.
Einzelne tote Typen/Konstanten: SiaLabel (roomArea.ts), GeoImportResult
(geoContext.ts, plus den dadurch verwaisten GeoOrigin-Import), die drei
MATERIAL_LIBRARY_*-Konstanten (library.ts), ResolveContext-Re-Export
(parametricWalls.ts), roofTotalThickness (types.ts — Duplikat, die echte
Rechnung läuft in toWalls3d/bands.ts inline), DropTarget (panelDrag.tsx).
resolveBackground (generatePlan/shared.ts) bewusst NICHT gelöscht: anders als
das analoge resolveForeground wird es nirgends aufgerufen, obwohl
backgroundSource für Wand/Decke im Modell und im Attribut-Panel existiert —
sieht nach einer echten Lücke aus (By-Layer-Hintergrundfarbe kommt nie im
Renderer an), nicht nach totem Code.
tsc -b / vitest run (891/891) / npm run build grün.
ts-prune + cargo check über alle Crates durchsucht, jeder Fund per grep
gegengeprüft. Gelöscht: ResourcesPanel.tsx (abgelöstes Alt-Panel),
RibbonBar.tsx (Ribbon-Phase-1-Überbleibsel), sowie diverse Funktionen ohne
jeden Aufrufer (sampleTerrainColors, docToSvgText/tspanToSvg,
isEmpty/deserialize/fromJson, findAccentSwatch, unregisterPanel,
removeFromDockLayout, TOOL_ORDER).
revokeMaterial (materials/ambientcg.ts) existierte, wurde aber nie
aufgerufen — jeder Material-Wechsel/-Upload liess Blob-URLs leaken. Jetzt
über revokeMaterialUrls() in ComponentsTab.tsx beim Ersetzen/Löschen/
Bauteil-Löschen verdrahtet.
tsc -b / vitest run (891/891) / npm run build grün.
Übersetzt rohe Pick-/Marquee-/Kontextmenü-Events aus PlanView/Viewport3D in
Selektions-Mutationen (onPlanSelect/onPlanMarquee/onPlanContextMenu/
onViewportSelect{Wall,Stair,Ceiling,Opening}/onViewport3dPick/
onViewportContextMenu) + zwei eng verwandte Doppelklick-Trigger
(onOpenStampEditor, onEditDrawingText) + commitTextEdit. Reine Verschiebung,
keine Verhaltensänderung. Der useImageImportHandling()-Aufruf, der bisher
mitten in diesem Abschnitt sass (unabhängig vom Rest), ist dabei direkt hinter
den neuen Hook-Aufruf gerutscht statt mit extrahiert zu werden.
App.tsx: 3655 → 3278 Zeilen.
tsc -b sauber, vitest 891/891 grün, vite build ok.
Der ~990-zeilige Panel-Host-Vertrag (baseHost-useMemo: Projekt/Selektion/
Snap-/View-State + sämtliche Attribut-Setter/Ausschnitte/Layouts-CRUD, den
PanelHostContext für Docks/Floating-Panels konsumiert) war ein einzelner
riesiger useMemo-Objektliteral direkt in App.tsx. Wortgetreu verschoben (keine
Logik neu geschrieben), damit kein Verhalten unterwegs verändert wird — nur
die Objekt-Konstruktion selbst wandert in eine eigene Funktion.
Der Hook-Aufruf sitzt jetzt spät im Funktionskörper (nach useProjectActions/
useSelectionState/useDialogState/useContextMenuState, kurz vor den frühen
Boot-Returns), weil einige referenzierte Handler (openResources, activeLayout,
patchOpenLayout, openSettings, openLayerSettings, onOpenStampEditor) erst dort
definiert sind — dieselbe Art Vorwärtsreferenz-Falle wie beim vorigen
useGripEditing-Schritt, diesmal mit deutlich mehr betroffenen Namen.
Parameter-Vertrag bündelt wo möglich ganze Hook-Rückgabewerte (projectActions/
selectionState/toolState/topBarState/dialogState/contextMenuState als Pick<>
der jeweiligen Hook-Signatur) statt ~150 Einzelfelder aufzulisten — erhält
volle Typsicherheit ohne manuelles Retippen jedes Handler-Signatur.
App.tsx: 4594 → 3690 Zeilen.
tsc -b sauber, vitest 891/891 grün, vite build ok.
Eckpunkt-/Kanten-Griffe der aktuellen Auswahl (Wand/2D-Element/Decke/Raum/
Öffnung/Treppe), deren Zieh-Handler (Ortho/Snap/koinzidente Nachbar-Ecken)
sowie der Tab-Feld-Controller für präzise Länge/Winkel-Eingabe während eines
Vertex-Drags waren als drei separat benannte, aber tatsächlich zusammen-
hängende Abschnitte in App.tsx verstreut. Jetzt ein Hook mit explizitem
Parameter-Vertrag (Selektion/Projekt/Snap-Settings/Projekt-Mutationen rein,
grips/edgeGrips/gripHandlers/Feld-Controller raus) statt anonymer Closures
über den gesamten Komponenten-Scope.
Reine Verschiebung, keine Verhaltensänderung — einzige Korrektur beim
Verschieben: der Öffnungs-/Wand-Snap in onGripMove nutzte computeSnap direkt
(gegen ein um das gezogene Element bereinigtes Projekt), das im ersten Entwurf
versehentlich mit dem allgemeinen snapFor-Helper vertauscht worden wäre —
beim Nachvollziehen der Original-Logik korrigiert, bevor es committet wurde.
App.tsx: 5096 → 4594 Zeilen.
tsc -b sauber, vitest 891/891 grün, vite build ok.
ResourceManager.tsx, generatePlan.ts und toWalls3d.ts sind nach Bauteil-
Domäne in src/ui/resourceManager/, src/plan/generatePlan/ und
src/plan/toWalls3d/ aufgeteilt (reine Verschiebung, keine Verhaltens-
änderung). App.tsx verliert weitere Handler-Gruppen an eigene Hooks
(useExportHandlers, useImportHandling, useKeyboardShortcuts) sowie
Hit-Testing an src/viewport/planHitTest.ts.
Unbenutzte Rust-geometry-Crate (durch WASM-Joins abgelöst) sowie der alte
OCCT/HLR-Schnitt-Spike (hlr.ts/occt.ts, vite occt-Aliases) entfernt.
Neu: PDF-Import als Rasterbild (src/io/pdfImport.ts) + gemeinsamer
Bild-Asset-Helfer (src/io/imageAsset.ts) für Plan- und Layout-Bilder.
tsc -b sauber, vitest 891/891 grün.
Zuletzt-geöffnet-Liste (state/recentProjects.ts) trägt sich beim Öffnen und
beim Speichern automatisch ein, inkl. Öffnen über einen bekannten Pfad ohne
Dialog (verwaiste Einträge fliegen raus, wenn die Datei fehlt). Der Splash
wartet bewusst nicht auf den Update-Check — der läuft unabhängig im
Hintergrund weiter, damit ein langsames Netz den Start nie verzögert.
"dossier" statt "Dossier".
Prüft beim Start still gegen ein Gitea-Release-Manifest (latest.json),
bietet im Settings-/About-Dialog einen manuellen Check sowie einen
Versionsverlauf mit Rollback-Download (versions.json, eigener schlanker
Index neben dem Updater-Manifest). Dafür: tauri-plugin-updater/process/
http/shell + zugehörige Capabilities, neue App-Icons (auch für die
plattformspezifischen Bundle-Formate), sowie ein kleingeschriebener
Produktname samt erweitertem nativen Info-Fenster (macOS-About).
Systemmenüleiste (useNativeAppMenu) deckt dieselben Aktionen bereits ab,
die eigene Leiste wäre dort nur Dopplung. Im Browser/Electron ohne
natives App-Menü bleibt sie weiterhin die einzige Quelle.
Shortcut-Map, obwohl beide Befehle längst fertig implementiert waren.
Zusätzlich musste man nach jedem gesetzten Punkt Esc drücken, bevor eine
Zifferntaste das nächste Werkzeug wählte — der Fokus blieb im Befehlsfeld
hängen (dort wird nach Abschluss nicht mehr geblurred) und die globale
Kürzel-Prüfung ignorierte jede Eingabe im Feld pauschal. Jetzt gibt die
Engine den Fokus frei, sobald ein Befehl fertig ist (Commit/Abbruch), und
eine Zifferntaste wählt direkt das nächste Werkzeug (Vectorworks-Stil) —
aber nur, wenn das Feld leer ist UND der aktuelle Schritt weder Tab-Felder
noch Freitext erwartet (Textlabel-Eingabe wie „Raum 101" bleibt geschützt).
deckungsgleich vor der ohnehin bis zur Dach-Unterkante hochgeklemmten
Giebelwand und wirkte als sichtbare Doppel-Schicht. Zusätzlich werden
aufeinanderfolgende Wandstücke gleicher geklemmter Oberkante (z. B. am First)
wieder zu einem Body verschmolzen statt in viele 0.15-m-Fragmente zu zerfallen.
Materialschichten) werden jetzt zusätzlich zum aktiven Stil erzwungen
gezeichnet, wenn ein Schnitt aktiv ist — sonst wirkten sie aus vielen
Blickwinkeln wie ein abgelöstes, schwebendes Element.
"+"-Buttons für Bauteile/Schraffuren/Linienstile/Wand-/Decken-/Dachstile
waren direkt als onClick={onAddX} verdrahtet — React reicht dabei das
Klick-Event als Argument durch. Im In-App-Overlay harmlos (Store-
Funktionen ignorieren Extra-Argumente), aber im nativen Ressourcen-
Fenster läuft jeder Aufruf über die Tauri-Event-Brücke (emitTo), die den
Aufruf als JSON serialisiert — ein React-Event mit zirkulären DOM-
Referenzen lässt sich nicht serialisieren, der Aufruf scheiterte lautlos.
Fix: alle betroffenen Buttons rufen den Handler jetzt explizit ohne
Argument auf. Zusätzlich die bisher stillschweigend verschluckten Fehler
in der Fenster-Sync-Brücke sichtbar gemacht (console.error statt leerem
catch), fürs nächste Mal.
Bündelt Projekt-Mutationen (Store-Actions), Wand-/Decken-/Dach-/Tür-/
Fenster-/Treppentyp-CRUD sowie diverse Element-Mutationsaktionen (Teil
der laufenden App.tsx-Verkleinerung).
Bündelt die Auswahl-Mengen aller Bauteil-/Zeichnungs-Arten (Wand, 2D-
Zeichnung, Decke, Dach, Öffnung, Treppe, Raum, Extrusion, Stütze,
Kontext-Objekt) plus abgeleitete Einzel-Ids, die 2D-Zwischenablage und
den 3D-Live-Schnitt-Treiber (Teil der laufenden App.tsx-Verkleinerung).
Bündelt aktives Werkzeug, Wandtyp/Linienstil, Snap-Einstellungen,
Werkzeug-Zustand-Ref, Draft-Vorschau, aktive Transformation
(Bewegen/Spiegeln/Drehen) und die Kopienanzahl für Array/Distribute
(Teil der laufenden App.tsx-Verkleinerung).
CSS-Pixel sind laut Spezifikation physisch konstant 1/96 Zoll, unabhängig
von devicePixelRatio (der beschreibt nur die Geräte-Pixel-Dichte hinter
einem CSS-Pixel, relevant für Rasterungs-Schärfe, nicht für die physische
Grösse). Die Papier-Massstab-Umrechnung in PlanView.tsx nahm bisher
96 * devicePixelRatio an und verfälschte dadurch den angezeigten wie
gemessenen Massstab auf jedem Retina-/HiDPI-Bildschirm um genau den
Faktor devicePixelRatio (per Lineal am Bildschirm nachgemessen). Betrifft
nur die Bildschirmanzeige, nicht den PDF/DXF-Export (eigene Berechnung).
Bisher öffneten beide Panel-Kopfzeilen die allgemeinen Einstellungen statt
ihrer eigenen: Zeichnungsebenen jetzt den Zeichnungsebenen-Fenster-Manager,
Ebenen die Ebeneneinstellungen der aktiven Kategorie.
Bündelt layerClipboard, menu, editor, nativeLayerSettingsCode,
stampEditorRoomId und editTextId in einem eigenen Hook (Teil der
laufenden App.tsx-Verkleinerung).
Reiner Umzug von neun useState-Deklarationen (Ressourcen-Fenster, Layout-
Ansicht, Import-/Export-/Einstellungs-Dialoge, Drop-Feedback) in einen
eigenen Hook — bewusst KEIN Store-Slice (dieser State ist laut den
Original-Kommentaren explizit lokal/transient, keine Undo/Redo- oder
Cross-Component-Relevanz). Verhaltensgleich.
Reiner Umzug (verhaltensgleich, byte-identische Funktionskörper): Kontextmenü-
Builder (buildMenuItems + layer/level/plan/viewport-Menüs) nach
src/menus/contextMenu.tsx; InlineEditor sowie Content/LevelPlanView/
SectionPlanView (der View-Router der Hauptansicht) nach src/views/. App.tsx
7130 → 5917 Zeilen. Setzt die in CONVENTIONS.md/state-architecture.md
vorgesehene, bisher nie umgesetzte Struktur endlich um (STATUS.md §4.3).
Dazu ein unabhängiger CSS-Fix: .plan-selected (Auswahl-Hervorhebung im 2D-Plan)
fehlte die fill-opacity, die zwei Regeln weiter unten bei .plan-marquee/
.tool-preview-fill schon gesetzt ist. Im Dunkel-Theme ist --accent-dim
transparent (zufällig unauffällig), im neuen Standard-Hell-Theme aber fast
deckend weiss — dadurch verdeckte die Auswahl die Schraffur der gewählten
Wand/Decke/des Raums vollständig.
Vollständige Bestandsaufnahme der Codebasis als neue STATUS.md (Kennzahlen,
Feature-Inventar, Mist-Liste: toter Code, verwaiste WASM-Crates,
Doku-Widersprüche). ARCHITECTURE.md/README.md/CONVENTIONS.md waren noch auf
dem Tag-1-Planungsstand (Electron/Three.js/OpenCascade/Zustand/HLR-Worker) und
beschrieben nicht mehr, was tatsächlich gebaut wurde (eigene Rust/WASM-Engines,
eigener Store, analytische Rust-Schnitt-Pipeline, Tauri auf macOS + Electron
auf Linux). ROADMAP.md und HANDOVER.md als historisch markiert (Hinweis-Box),
Inhalt unverändert.
Der aus der Desktop-Session übernommene statische 88er-Katalog listete 75
Materialien ohne mitgelieferte Texturen (kaputte Thumbnails in der
Schnellauswahl). Bibliothek auf die 13 tatsächlich gebündelten Starter
gekürzt; Fetch-Script und der zugehörige .gitignore-Block entfernt. Alles
darüber hinaus läuft über die bereits vorhandene Live-Suche (1K/2K/4K).
Jede STAC-Kachel (DXF) liefert ALLE Gebäude der Kachel als EIN gemeinsames
Mesh (parseDxf sammelt alle 3DFACE/Polyface-Entities eines Files in denselben
Puffer) — ohne Zuschnitt landete bei einem Import IMMER die komplette Kachel
im Modell, teils mehrere Kilometer über den gesuchten Suchradius hinaus.
Live gegen echte STAC-Daten verifiziert: zwei 1.4 km auseinanderliegende
Adressen (gleiche Kachel) lieferten vorher IDENTISCHE, unclipped Geometrie
(62532 Dreiecke); danach jeweils nur die ~400 Dreiecke im eigenen Suchradius.
clipMeshToBbox schneidet + kompaktiert das Mesh (nicht referenzierte Ecken
entfernt, Indizes neu gemappt) — sonst sähe jede Verbraucher-Logik, die roh
über `positions` statt `indices` iteriert (Bounding-Box, Kamera-Fit), weiter
die volle ungeschnittene Ausdehnung.
Nutzer-Report: "im 2D innen bündig und im 3D aussen bündig". Root Cause: das
2D-Vorzeichen für insetFace:"aussen" (windowSymbol) war gegenüber der echten
Aussenschicht der Wand (erste Wandtyp-Schicht, kleinster Achs-Offset in
addWallPoche/pushSegment) invertiert — "aussen" landete am inneren
Wandputz statt am äusseren. Dieselbe Vorzeichenverwechslung steckte auch in
addOpeningFrameBand (Tür-Rahmenband), den Sturzlinien und dem Rollladenkasten.
Die 3D-Seite (openingAxisBox/resolveFrameNormalRange) war bereits korrekt
(zwei kompensierende Vorzeichen ergaben zufällig das richtige Ergebnis) und
bleibt unverändert.
+1 Regressionstest, der 2D- und 3D-Rahmenposition direkt gegen die bekannte
Aussenschicht der Wand prüft.
Bisher teilten sich die geschnittenen Laibungs-/Stulp-Blöcke (O-Blöcke,
Schnittfläche des Rahmenmaterials) und die durchlaufende Rahmen-Kontur
zwischen ihnen (Aufsicht, nicht geschnitten) dieselbe frameLine-Farbe. Neues
Opening.frameViewLine übersteuert nur noch die Kontur; ohne Angabe fällt sie
weiterhin auf frameLine zurück (bisheriges Verhalten unverändert). frameLine
im UI zu "Rahmen (Schnitt)" umbenannt, um die beiden Kategorien zu unterscheiden.
Nach einem Standort-Import landet die Kontext-Geometrie dort, wo der gesuchte
Ort war — das kann weit vom bisherigen Modell-Ursprung liegen. Neuer Befehl
"georef" (Button in SitePanel) verschiebt das GESAMTE Projekt per Klick so,
dass der gewählte Punkt zum neuen Ursprung wird; geoAnchor wandert automatisch
mit, der reale LV95-Bezug bleibt exakt erhalten (rekonstruierbar, auch beim
späteren Export). Bewusst nur Translation, keine Rotation — mehrere Felder
(Roof.ridgeAxis, Column.rotation, Text-/Bild-Rotation) sind achsen-/winkel-
gebunden und bräuchten bei einer Drehung eigene Sorgfalt.
Importierte Gebäude/Terrain-Meshes waren bisher nur ein Eintrag im Geo-Panel,
nicht direkt im 3D anwählbar. Raycast/Pick-Geometrie um Kontext-Meshes
erweitert, Auswahl-Kanal (selectedContextObjectIds) ergänzt, Highlight als
Bounding-Box-Umriss (volles Dreiecks-Wireframe wäre bei grossen Importen zu
dicht), Entf-Taste löscht die Auswahl, SitePanel hebt sie in der Liste hervor.
Neues Project.geoAnchor: verbindet EINEN Modell-Punkt mit seiner realen
LV95-Koordinate. Bisher berechnete jeder Standort-Import (Gebäude/Terrain/
OSM) unabhängig einen neuen Bezug aus der jeweils gesuchten Adresse — bei
mehreren Importen mit leicht unterschiedlichen Suchbegriffen landete
importierter Kontext lagefalsch zueinander.
Der erste Import in einem Projekt setzt den Bezug automatisch (Modell-(0,0)
= gesuchter Ort) und speichert ihn; alle weiteren Importe verwenden densel-
ben Bezug, unabhängig vom neu gesuchten Ort (der bestimmt nur noch WOVON
Daten geladen werden, nicht mehr WOHIN sie im Modell platziert werden).
UI: Anzeige des aktiven Bezugs im Import-Dialog mit Zurücksetzen-Option.
Löst das strukturelle Problem noch nicht vollständig (der Bezug sitzt immer
bei Modell-(0,0) — passt nur, wenn das eigene Gebäude dort gezeichnet ist),
aber behebt die akute Inkonsistenz zwischen mehreren Importen.