Commit Graph

317 Commits

Author SHA1 Message Date
karim c43573859f 2D: Tab in ein Griff-/Kanten-Feld friert die Maus ein, bis der erste Wert getippt ist
Nutzer-Wunsch: "wenn ich mit Tab in die Werte springe, soll die Maus
dann nicht mehr steuern, bis man den ersten Wert eingegeben hat -- das
kann man aber mit Esc lösen." Bisher folgte der Punkt/die Kante nach
Tab weiter der Maus, solange noch kein Wert gelockt war -- ein
Zittern der Hand beim Greifen zur Tastatur konnte die Position also
noch verändern, bevor überhaupt getippt wurde.

Neue gripFrozenPointRef/edgeFrozenPointRef (useGripEditing.ts): beim
ÖFFNEN des Feld-Controllers (erstes Tab, noch kein vorheriges Feld
offen) wird die AKTUELLE Position eingefroren; onGripMove/onEdgeMove
halten den Punkt/die Kante bei aktivem Feld-Controller OHNE Lock exakt
dort fest, unabhängig von der Mausposition. Der erste getippte Wert
(submitGripEditValue/submitEdgeEditValue) löst das Einfrieren -- ab da
gilt wieder die bisherige Locked/Live-Mischung (gelockte Felder fest,
ungelockte folgen der Maus).

Neue closeGripEditField/closeEdgeEditField-Funktionen lösen das
Einfrieren explizit auf und schließen NUR den Feld-Prompt (Punkt/Kante
bleibt bewaffnet, Maus steuert wieder normal) -- ersetzen in App.tsx
die bisherigen direkten setGripEdit(null)/setEdgeEdit(null)-Aufrufe im
CommandLine-onCancel (Escape MIT Fokus in der Befehlszeile). Das
bestehende, GLOBALE Escape (ohne Fokus in einem Eingabefeld) bleibt
unverändert: es verwirft den ganzen Griff-/Kanten-Drag komplett auf
die Ursprungsposition (PlanView.tsx, voriger Commit) -- zwei bewusst
verschiedene Eskalationsstufen.

tsc/vitest 922/922 grün.
2026-08-22 00:16:29 +02:00
karim 0c69162ba6 2D: Shift beim Punkt-Griff-Ziehen snappt jetzt auch auf die Flucht
anliegender schräger Kanten, nicht nur Welt-H/V

Nutzer-Wunsch: "wenn ein Punkt aus zwei Linien besteht, die nicht in
[0°/90°] liegen, sollte auch in diese Richtung mit Shift gesnappt
werden können" -- bisher zwang Shift beim Ziehen eines Vielecks-
Eckpunkts (Drawing2D-Polylinie/rect, Raum-/Deckenumriss) IMMER auf
Welt-Horizontal/Vertikal (applyAngleConstraint mit 90°), unabhängig
davon, in welchem Winkel die beiden an diesem Punkt anliegenden Kanten
tatsächlich standen.

Neue Kandidaten-Achsen-Logik (useGripEditing.ts): Shift wählt jetzt die
NÄCHSTLIEGENDE aus mehreren Achsen (kleinster senkrechter Abstand des
Cursors zur Achse) -- Welt-H/V bleiben als Kandidaten erhalten, dazu
kommen die Richtungen der beiden FIXEN Nachbarkanten (deren jeweils
ANDERER Endpunkt bewegt sich ja nicht), gemessen an der Position des
gezogenen Punkts VOR dem Drag (neuer gripOriginRef, sonst würde die
Flucht-Richtung während des Ziehens mitdriften). resolvePolygonNeighbors
kennt geschlossene Ringe (rect/geschlossene Polylinie/Raum/Decke,
Nachbar 0/n-1 wickelt um) vs. offene Ketten (offene Polylinie/Linie);
Kreis/Bogen/Wand/Öffnung/Treppe haben keine Vielecks-Nachbarn und
bleiben unverändert bei reinem Welt-H/V.

Bewusst NICHT angefasst: Kanten-/Körper-Verschieben (die "M"-Bewegung
und die Kanten-Dreieck-Griffe) -- der Nutzer bezog sich explizit auf
"einen einzelnen Punkt", diese behalten ihr bisheriges Welt-H/V-Ortho.

tsc/vitest 922/922 grün. Manuelle Prüfung nötig (Pointer-Interaktion in
PlanView.tsx ist nicht automatisiert testbar, wie schon bei den
vorherigen Griff-Editier-Fixes dieser Session).
2026-08-22 00:09:30 +02:00
karim 9ce6d86e82 2D: Kanten-Griff bei Vielecken winkeltreu (Nachbarkanten kippten sonst)
Nutzer-Report: bei einem Vieleck mit mehr als 5 Ecken kippten beim
Parallel-Verschieben einer Seite die ANGRENZENDEN Kanten im Winkel, statt
sich nur zu verlängern/verkürzen -- sichtbar bei nicht rechtwinkligen
Anschlüssen (bei Rechtecken fiel es nicht auf, weil deren Nachbarkanten
zufällig immer senkrecht zur bewegten Seite stehen).

Ursache: moveEdge/moveRoomEdge/moveCeilingEdge versetzten die beiden
Eckpunkte der gezogenen Kante stur um dasselbe Delta -- korrekt nur,
wenn die Nachbarkante zufällig senkrecht zur Zugrichtung steht. Bei
schrägen Anschlüssen wandert der gemeinsame Eckpunkt dadurch von der
Nachbarkanten-Linie weg, die Nachbarkante dreht sich mit.

Fix: neue movePolygonEdge/edgeMoveVertex-Helfer (projectSlice.ts)
berechnen die neue Eckpunkt-Position stattdessen als Schnittpunkt der
VERSCHOBENEN Kanten-Linie mit der unendlich verlängerten Nachbarkanten-
Linie durch den FIXEN Nachbarpunkt (lineIntersect, dasselbe Prinzip wie
die Wandstoß-Gehrung in model/joins.ts) -- die Nachbarkante behält ihre
Richtung, nur ihre Länge ändert sich. Fällt bei fehlendem Nachbarn
(offenes Kettenende) oder (fast) paralleler Nachbarkante auf die
bisherige einfache Delta-Verschiebung zurück. Rechtecke bleiben
unverändert (eigener min/max-Pfad, ohnehin immer rechtwinklig).

+3 Tests (Fünfeck-Kante winkeltreu über Drawing2D UND Room, Rechteck-
Regressionsschutz). tsc/vitest 922/922 grün.
2026-08-22 00:01:49 +02:00
karim 31a5b64836 2D: Kanten-Griffe (Seite parallel verschieben) bekommen dieselbe
Bearbeitung wie Punkt-Griffe

Nutzer-Wunsch: "das Ganze soll auch an den Seiten bei diesen Dreiecken
gehen bei denen man eine Fläche parallel verschieben kann" -- also
dieselbe Klick-loslassen-tippen-bestätigen-Geste, dieselbe Cursor-HUD-
Anzeige und dieselbe getippte Werteingabe wie bei den Punkt-Griffen aus
den letzten Commits, jetzt auch für die Kanten-Anfasser.

useGripEditing.ts: onEdgeMove rechnet jetzt mit einem ABSOLUTEN
Zielpunkt relativ zum ORIGINALEN Kanten-Mittelpunkt (Momentaufnahme vom
Grab), nicht mehr rein inkrementell pro Frame -- eine neue
edgeAppliedDeltaRef verankert, wie viel Delta bereits angewandt wurde,
und wendet jeweils nur die Differenz zum neuen Ziel an (moveEdgeOf/
moveRoomEdge/moveCeilingEdge sind additiv). Neuer edgeEdit-Feld-
Controller (Pendant zu gripEdit): geführte Kanten (rect/geschlossene
Polylinie/Wand) haben nur EIN Feld ("Länge" = vorzeichenbehafteter
Abstand entlang der festen Normale, kein Winkel), freie Kanten (offene
Polylinie/Linie) wie ein Punkt-Griff Länge+Winkel relativ zum
Ursprung. Cursor-HUD zeigt bei geführten Kanten "Δ: …m" als Freitext
(das L/W-Schema passt nicht für ein Einzelfeld), bei freien Kanten
L/W wie beim Punkt-Griff. onEdgeCancel verwirft exakt auf den
Ursprung zurück (Ziel = Anker selbst).

PlanView.tsx: Kanten-Griff-Grab läuft jetzt ohne Pointer-Capture
(dasselbe "bewaffnet nach Loslassen"-Verhalten wie beim Punkt-Griff),
Abschluss per erneutem Klick/Enter, Verwerfen per Escape. Neue
confirmEdgeEdit()-Methode am Imperativ-Handle (Pendant zu
confirmGripEdit) fürs Enter-mit-leerem-Text aus der Befehlszeile.

App.tsx: CommandLine-Verdrahtung (active/promptKey/fields/onSubmit/
onCycleField/onCancel) um den edgeEdit-Zweig erweitert, neuer i18n-
Prompt "cmd.edit.edge" (de/en). useToolNumberShortcuts blockt
Zifferntasten jetzt auch während eines bewaffneten Kanten-Griffs
(gleicher Bug wie zuvor bei Punkt-Griffen, präventiv mitgefixt).

useCommandTabShortcut (useKeyboardShortcuts.ts) generalisiert: Tab
zykelt/öffnet jetzt sowohl den Punkt- als auch den neuen Kanten-Feld-
Controller (dieselbe Instanz, zusätzliche optionale Parameter).
2026-08-21 23:56:48 +02:00
karim cdfef949bf 2D: Enter in der Befehlszeile bestätigt Punkt-Griff jetzt auch ohne neuen Wert
Nutzer-Report: Enter sollte den Punkt gemäss Maus-/Feld-Stand platzieren
("die Maus steuert bis man Werte eingibt, dann übernimmt es im Feld den
aktuellen Wert, man kann eigene Werte per Tab eingeben") -- aber Enter
tat nach dem Tippen eines Werts (oder auch ganz ohne Tippen) nichts.

Ursache: die Befehlszeile fokussiert sich selbst, sobald man Tab drückt
(Feld-Controller öffnen). Der GLOBALE Enter/Escape-Tastatur-Handler aus
dem vorigen Commit überspringt bewusst fokussierte Eingabefelder (damit
er sich nicht mit der Befehlszeilen-eigenen Enter-Behandlung beisst) --
aber die Befehlszeile selbst kannte für die Griff-Bearbeitung nur den
Fall "Text eingetippt, als Zahl geparst" (submitGripEditValue). Bei
LEERER Eingabe (Enter ohne neuen Wert -- der Normalfall, wenn man nur
bestätigen will) geschah dadurch schlicht nichts.

Fix: PlanViewHandle bekommt eine neue Methode confirmGripEdit() (liest
den bewaffneten Griff aus der lokalen gripDrag-Ref, ruft
gripHandlers.onGripEnd() -- dieselbe Aktion wie ein bestätigender Klick
auf der Zeichenfläche). Die Befehlszeilen-Eingabe in App.tsx ruft sie
jetzt bei leerem/nicht parsbarem Text während der Griff-Bearbeitung auf.

Da das Imperativ-Handle mit leeren Deps memoisiert ist (stabil für die
Oberleiste), braucht es einen frischen gripHandlersRef-Spiegel statt die
gripHandlers-Prop direkt zu schließen (sonst stale closure vom ersten
Render).
2026-08-21 23:38:05 +02:00
karim f9b202ac25 2D: Zifferntasten während Punkt-Griff-Bearbeitung nicht mehr als Werkzeug-Wechsel
Bug (Nutzer-Report direkt nach dem vorigen Commit): sobald man nach Tab
einen Länge-/Winkel-Wert eintippte, wechselte die App mitten in die
Griff-Bearbeitung ins Zeichenwerkzeug (z.B. "3" -> Kreis-Werkzeug).

Ursache: useToolNumberShortcuts (Vectorworks-Zifferntasten fürs
Werkzeug wählen) erlaubt Zifferntasten auch bei fokussiertem, leerem
Befehlsfeld, SOFERN die Zeichen-Engine gerade keinen Befehl mit
Feldern laufen hat (`!eng.hasFields() && !eng.acceptsFreeText()`).
Genau das trifft während der Griff-Bearbeitung IMMER zu, da dort gar
keine Engine-Befehl läuft, sondern der separate Feld-Controller aus
useGripEditing.ts -- der Wächter kannte diesen zweiten Fall nicht.

Fix: gripDragInfoRef (schon vorhanden, zeigt "ein Punkt-Griff ist
bewaffnet" an) wird von useGripEditing zurückgegeben und an
useToolNumberShortcuts durchgereicht; dessen Handler bricht jetzt ganz
vorne ab, wenn ein Griff bewaffnet ist -- unabhängig davon, ob/wo
gerade Fokus liegt. Der useToolNumberShortcuts-Aufruf in App.tsx
musste dafür hinter den useGripEditing()-Aufruf wandern (Reihenfolge
war vorher umgekehrt, gripDragInfoRef existierte an der alten Stelle
noch nicht).
2026-08-21 23:13:02 +02:00
karim a6c984e517 2D: Punkt-Griff ziehen ohne gehaltene Maustaste, Cursor-HUD dabei
Nutzer-Wunsch: sobald ein Punkt-Griff angewählt ist, soll man die
Maustaste loslassen können und trotzdem weiter per Cursor UND per
getipptem Wert positionieren können -- wie beim Zeichnen (Klick setzt
Punkt, Maus bewegt sich frei, Wert tippbar, nächster Klick/Enter
bestätigt), nicht wie bisher als reine Halten-und-Ziehen-Geste.

PlanView.tsx: der Griff wird beim Klick "bewaffnet" statt per
Pointer-Capture gehalten zu werden; Loslassen der Maus beendet die
Bearbeitung nicht mehr, der Punkt folgt weiter per normalem Hover-
Pointermove. Abgeschlossen wird per erneutem Linksklick (irgendwo,
verhindert dass derselbe Klick eine andere Aktion auslöst) oder Enter,
verworfen per Escape (beide nur ausserhalb von Eingabefeldern, damit
die Befehlszeile ihr eigenes Enter behält). Neuer onGripCancel-Handler
setzt dafür exakt auf die Ausgangsposition zurück.

useGripEditing.ts: die Anker-Auflösung (Nachbar-Ecke bzw. Kreis-/Bogen-
Zentrum) ist jetzt ein gemeinsamer resolveGripAnchor()-Helper, genutzt
vom Tab-Feld-Controller UND einem neuen Cursor-HUD (Länge/Winkel neben
der Maus, dieselbe Darstellung wie beim Zeichnen) -- vorher zeigte das
Cursor-HUD beim Griff-Ziehen gar keine Werte an, nur die Befehlszeile
unten.

App.tsx: das Cursor-HUD-Feld-Objekt zeigt bei aktivem Griff-Tab-Feld
jetzt dieselben Felder wie die Befehlszeile (vorher nur bei laufendem
Zeichenbefehl).

Kanten-Griff und Körper-Verschieben (ganzes Element) bleiben bewusst
unverändert (Halten-und-Ziehen) -- nur explizit angefragt war der
Einzelpunkt-Fall.
2026-08-21 21:50:00 +02:00
karim 13619e9271 2D: Griff-Editieren funktioniert jetzt auch bei Mehrfachauswahl
Bug: grips/edgeGrips in useGripEditing.ts waren über sich gegenseitig
ausschliessende Zweige auf genau ein selektiertes Element zugeschnitten;
bei zwei oder mehr gewählten Elementen (Zeichnungen oder Wänden) traf
kein Zweig, beide Arrays wurden leer -> keine Griffe mehr sichtbar/ziehbar.

Neues GripOwner-Modell (gripOwners/gripOwnerRange parallel zum flachen
grips-Array, EdgeGrip.owner? neu im Modell): jedes gewählte Element
liefert weiterhin einen eigenen, zusammenhängenden Vertex-/Kanten-Block.
applyGrip/applyEdge/cycleGripEditField/onGripMove lösen die Zielroute
(moveGripOf/moveEdgeOf) jetzt pro Griff über dessen owner auf statt über
die singulären selectedDrawingId/selectedWallId. Einzelauswahl ist ein
Spezialfall derselben Logik, kein separater Pfad. Der Tab-Feld-Controller
(getippte Länge/Winkel beim Ziehen) funktioniert dadurch auch bei
Mehrfachauswahl korrekt pro Griff. Öffnung/Treppe/Decke/Raum bleiben
bewusst exklusiv (kein Multi-Select für diese vier).

PlanView.tsx brauchte keine Änderung, da Hit-Test und Rendering dort
bereits generisch über die flachen grips/edgeGrips-Arrays laufen.
2026-08-21 20:50:00 +02:00
karim d9cbf2cb07 2D: Tab-Feld-Controller für Kreis/Bogen-Griffe repariert (Radius/Winkel tippbar)
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.
2026-08-21 20:37:15 +02:00
karim d6fde93c57 2D: Inline-Text-Editor-Nachbesserung — WYSIWYG-Textgrösse, Toolbar bleibt normal
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).
2026-08-21 19:48:14 +02:00
karim 7d3fd40cac 2D: Inline-Text-Editor nicht mehr zoomabhängig skaliert (riesig/winzig-Bug)
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.
2026-08-20 21:59:19 +02:00
karim 73d147b37d 2D: Inline-Text-Editor-Nachbesserung (unsichtbarer Anker) + Textspalte als echtes Rechteck
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.
2026-08-20 21:44:37 +02:00
karim f2d169cc67 2D: Text/Textspalte auf Inline-Editing direkt auf der Zeichenfläche umgestellt
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.
2026-08-20 21:34:40 +02:00
karim 4f48b73267 2D: Multiline-Werkzeug (Basislinie + N-1 parallele Kopien in einer Geste)
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.
2026-08-20 21:07:21 +02:00
karim 245b7d7fe1 2D: Chamfer (Eckpunkt-Fasung) neu gebaut und verdrahtet
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.
2026-08-20 21:01:15 +02:00
karim 51e594ee90 2D: Fillet und Extend als Befehle verdrahtet (Geometrie existierte bereits)
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.
2026-08-20 20:53:31 +02:00
karim f450a1aa08 2D: By-Layer/By-Object-Attribute für Wand/Decke konsistent verdrahtet
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.
2026-08-20 20:41:28 +02:00
karim 1c3fb289d0 2D: Kreis/Bogen-Zeichenelemente vollständig anwählbar und editierbar
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.
2026-08-20 20:31:46 +02:00
karim bdbd87bf85 Zweite Leichen-Runde: weitere tote Exporte + orphaned ribbonItems.ts 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.
2026-08-20 20:01:26 +02:00
karim 003fde4320 Leichen-Suche: tote Dateien/Funktionen entfernt, revokeMaterial-Bug behoben
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.
2026-08-20 19:51:52 +02:00
karim cbdc3d5d76 Maus-Schema aus App.tsx in eigenen Hook extrahiert (state/useMouseSelectionHandlers.ts).
Ü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.
2026-08-20 00:51:35 +02:00
karim c224f9e4f9 Tab-Drag-Steuerung aus App.tsx in eigenen Hook extrahiert (state/useDockDragState.ts).
Reorder/Einreihen/Stapeln/Lösen (usePanelDrag-Wrapper) + Float-Titel-Drag über
Rand-Andockzonen. Reine Verschiebung, keine Verhaltensänderung.

App.tsx: 3690 → 3655 Zeilen.

tsc -b sauber, vitest 891/891 grün, vite build ok.
2026-08-20 00:48:34 +02:00
karim b6d47b433d Host-Context für Panels aus App.tsx in eigenen Hook extrahiert (state/usePanelHostBase.ts).
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.
2026-08-20 00:46:22 +02:00
karim 103f704e87 2D-Griff-Editieren aus App.tsx in eigenen Hook extrahiert (state/useGripEditing.ts).
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.
2026-08-20 00:35:03 +02:00
karim d6fae13326 Viewport3D.tsx aufgeteilt: Kamera-Helfer + Mesh-Builder nach src/viewport/viewport3d/.
Gleiches Muster wie beim PlanView.tsx-Split: die Komponente (ThreeViewport3D)
bleibt, die anschliessenden ~1000 Zeilen reiner three.js-Erzeugung wandern in
zwei fokussierte Module. Reine Verschiebung, keine Verhaltensänderung.

- viewport3d/camera.ts: visibleFloorKey, updateOrthoFrustum, applyView3d
  (Ortho-Frustum-Bemessung + die kanonischen Blickwinkel-Presets).
- viewport3d/meshBuilders.ts: MatCaches/BuildOpts/ContextMats, buildContext,
  buildBuilding (+ private buildFloor/addWallMeshes/addOpeningMeshes/
  addCeilingMesh/addStairMeshes/addLayerPrism/addDrawing2DLines/layerMaterial).

Viewport3D.tsx: 2661 → 1588 Zeilen.

tsc -b sauber, vitest 891/891 grün, vite build ok.
2026-08-19 23:05:25 +02:00
karim 528f213f1f PlanView.tsx aufgeteilt: Mathe/Overlays/Primitiv-Rendering nach src/plan/planView/.
Reine Verschiebung, keine Verhaltensänderung. PlanView.tsx (4169 → 2465
Zeilen) behält nur noch die Komponente selbst + Interaktions-Logik
(Maus/Tastatur/Griffe/Snapping/Undo).

- planView/geometry.ts: ViewBox, toScreen, Papier-Massstab-Umrechnung
  (meetScale/scaleFromView/viewForScale/printStrokeVb), Marquee-/Punkt-
  in-Polygon-Tests (pointInPolygon, marqueeHit, marqueeHitDrawings).
- planView/primitives.tsx: HatchPattern, buildDrawingRuns, DrawingRunShape,
  PrimitiveShape, renderPrimitive (der grosse Primitiv→SVG-Renderer).
- planView/overlays.tsx: DraftOverlay (Werkzeug-Vorschau, Cursor-HUD,
  Winkel-Führungslinie, Snap-Marker).
- planView/PlanRulers.tsx: die Lineale oben/links.

tsc -b sauber, vitest 891/891 grün, vite build ok.
2026-08-19 22:54:58 +02:00
karim 1507c2bf00 God-Files aufgeteilt (ResourceManager/generatePlan/toWalls3d/App.tsx-Handler), Totcode entfernt (geometry-Crate, OCCT/HLR-Spike), PDF/Bild-Import ergänzt.
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.
2026-08-19 22:45:41 +02:00
karim 06c0143d32 Start-Sequenz ergänzt: Splash-Screen, dann Startbildschirm mit Neues Projekt/Projekt öffnen/Zuletzt geöffnet.
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.
2026-07-31 17:40:35 +02:00
karim 08e74b7e0e Projektdatei-I/O (Speichern/Öffnen/Lock) und Schnellexport (CSV/IFC/OBJ/
STL) sowie die Ausführung eines bestätigten DXF/DWG-Imports aus App.tsx
in useProjectFileActions() extrahiert.
2026-07-31 17:03:41 +02:00
karim 6ee6bf5dd1 Selbst-Update-Funktion ergänzt (Tauri Updater-Plugin) + App-Branding
"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).
2026-07-31 17:03:23 +02:00
karim 9c9e118d98 TopBar: In-App-Menüleiste unter Tauri ausgeblendet — die native macOS-
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.
2026-07-31 17:01:18 +02:00
karim 31fe93c122 Zeichenwerkzeug-Zifferntasten: 1 (Text) und 3 (Kreis) fehlten in der
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).
2026-07-31 17:00:57 +02:00
karim 1ba6f7bcf4 Dach/Wand: Giebel-Endfüllung (dünne Einzelfläche) nicht mehr emittiert — lag
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.
2026-07-31 17:00:09 +02:00
karim d1de33bd6d Ressourcen-Manager: "+" im nativen Fenster fügte keine neuen Einträge hinzu
"+"-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.
2026-07-22 20:07:31 +02:00
karim 29018f2dff Projekt-Mutationen/Ressourcen-Typ-CRUD aus App.tsx in useProjectActions() extrahiert
Bündelt Projekt-Mutationen (Store-Actions), Wand-/Decken-/Dach-/Tür-/
Fenster-/Treppentyp-CRUD sowie diverse Element-Mutationsaktionen (Teil
der laufenden App.tsx-Verkleinerung).
2026-07-22 20:06:32 +02:00
karim 448546994c Auswahl-Zustand aus App.tsx in useSelectionState() extrahiert
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).
2026-07-22 00:42:07 +02:00
karim b3e6aaaec1 Zeichenwerkzeug-Zustand aus App.tsx in useDrawingToolState() extrahiert
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).
2026-07-22 00:35:12 +02:00
karim e41821c97a 2D-Massstab: dpi-Berechnung fälschlich mit devicePixelRatio multipliziert
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).
2026-07-22 00:30:57 +02:00
karim 18aee59cff Oberleisten-/Status-Zustand aus App.tsx in useTopBarState() extrahiert
Bündelt Detailstufe, 3D-Rendermodus, Sichtfeld, Boden-Referenzraster,
Plan-Farbmodus, Achslinien, Linienmodus, Fadenkreuz-/Snap-Farbe,
Papier-Massstab, Live-Zoom, Cursor und Ausschnitt-Auswahl (Teil der
laufenden App.tsx-Verkleinerung).
2026-07-22 00:28:34 +02:00
karim 3b8d48eaf5 Zahnrad-Icons in Zeichnungsebenen/Ebenen öffnen jetzt die passenden Fenster
Bisher öffneten beide Panel-Kopfzeilen die allgemeinen Einstellungen statt
ihrer eigenen: Zeichnungsebenen jetzt den Zeichnungsebenen-Fenster-Manager,
Ebenen die Ebeneneinstellungen der aktiven Kategorie.
2026-07-21 19:51:03 +02:00
karim 476f31ccad Kontextmenü-/Editor-State aus App.tsx in useContextMenuState() extrahiert
Bündelt layerClipboard, menu, editor, nativeLayerSettingsCode,
stampEditorRoomId und editTextId in einem eigenen Hook (Teil der
laufenden App.tsx-Verkleinerung).
2026-07-21 19:31:19 +02:00
karim 85f386a496 App.tsx: Hell/Dunkel-State nach theme/useThemeMode.ts
Reiner Umzug von useState + Setter-Wrapper (persistiert in localStorage via
themeMode.ts) in einen eigenen Hook. Verhaltensgleich.
2026-07-21 16:24:33 +02:00
karim 43ea7c5d8e App.tsx: Dialog-Sichtbarkeits-State nach state/useDialogState.ts
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.
2026-07-21 16:15:59 +02:00
karim b621a7da64 App.tsx entschlackt: Kontextmenüs nach src/menus/, Views nach src/views/
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.
2026-07-21 14:47:40 +02:00
karim 76c9d164c9 Materialbibliothek: statisches Broad-Set entfernt, Live-ambientCG bleibt
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).
2026-07-21 13:35:44 +02:00
karim 78d3e6a955 Fix: doppelten RoofingTiles013A-Eintrag nach Rebase entfernt 2026-07-20 11:05:08 +02:00
karim a009a5e9b4 Materials-Feature: Bibliothek + Fetch-Script + Manifest (aus Desktop-WIP übernommen) 2026-07-20 11:03:30 +02:00
karim b9e330ecb7 Schnittebenen 2D↔3D Phase 1+2, native Fenster, Hell/Dunkel-Theme, SWISSIMAGE-Import
- Schnittebenen: 3D-Live-Schnitt folgt der gewählten Grundriss-Schnittlinie
  (section3dCutId/sectionPlaneFromLevel), Schalter "Im 3D schneiden" im
  Objekt-Info, Doppelklick auf Schnittlinie springt in 2D-Schnittansicht,
  unsichtbare Ebenen blenden ihre Führungslinie aus
- Eigene native Tauri-Fenster für Kontext-Import/Zeichnungsebenen/
  Ebenen-Einstellungen/Ressourcen/Einstellungen + klassische Menüleiste
  (AppMenuBar) neben der Wortmarke
- Hell/Dunkel-Umschalter (Einstellungen → Darstellung), persistiert,
  flackerfrei vor erstem Render gesetzt
- SWISSIMAGE-Luftbild-Import (swisstopo WMS) als Kontext-Hintergrundebene
- UI-Politur: Werkzeug-Panel Symbole/Liste umschaltbar, Topbar-Quick-Access-
  Icons entfernt, Zahnrad→Einstellungen in Panel-Köpfen, Footerbar/
  Snap-Marker/Maß-HUD auf helle Pillen-Sprache umgestellt
- Neues Dachziegel-Material (RoofingTiles013A)
2026-07-20 10:51:37 +02:00
karim 51e57c368b swissBUILDINGS3D-Import: auf den gesuchten Radius zuschneiden statt die ganze STAC-Kachel zu liefern
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.
2026-07-12 21:35:31 +02:00
karim 932a229f09 Fenster/Tür: 2D und 3D setzen "aussen" jetzt auf dieselbe Wandfläche
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.
2026-07-12 21:24:48 +02:00