# Handoff — Rhino-artiges Befehlssystem + Modellier-Werkzeuge > Für die Instanz, die das Befehlssystem (Tab-getriggert) und die Rhino-artigen > Modellierfunktionen baut. Stand: 2026-06-30. Zuerst lesen: `CONVENTIONS.md`, > `ROADMAP.md`, `HANDOVER.md`, `docs/design/drawing-tools.md`. Volle Autonomie, > selbst bestätigen (Memory `proceed-autonomously`, `wire-dont-stub`, `prefer-agents`). Dieses Dokument hat drei Teile: 1. **Was schon steht** (worauf du aufbaust — exakte Dateien/Typen/Actions). 2. **Rhino-Referenz** (Interaktionsmodell, das nachzubilden ist). 3. **Konkreter Bauplan für DIESE Codebase** (Architektur, Dateien, Reihenfolge). --- ## TL;DR — die Kernidee Es gibt **kein** Befehlssystem, keine Command-Line, keinen Tab-Handler. Das baust du greenfield. **Aber:** Das Werkzeug-System ist bereits eine saubere Pure-Function-Registry (`src/tools/`) mit generischem Controller in `App.tsx`, und die Mutations-Schicht (`projectSlice`) ist umfassend. Ein neues Modellier-Tool steckst du durch Hinzufügen eines `Tool`-Objekts ein — **PlanView muss dafür nicht angefasst werden**. Das Befehlssystem ist im Kern eine **State-Machine-Engine über prompt → pick/type → options**, plus eine **Command-Line-UI** (Statusleiste), plus ein **Koordinaten-Parser**. Befehle dispatchen auf bestehende Store-Actions + `setActiveTool`/`setProject`. **Wichtigster konzeptioneller Sprung:** Die heutigen Tools haben je eine *eigene* ad-hoc-Phasenlogik (`onClick`/`onMove`). Rhino-Feel verlangt eine **gemeinsame Prompt/Option/Numerik-Engine**, die alle Befehle teilen. Plane das als Verallgemeinerung des bestehenden `Tool`-Interfaces, nicht als Parallelwelt daneben (sonst zwei Eingabe-Pfade, die divergieren). --- # TEIL 1 — Was schon steht (Baufundament) Stack: React 18 + TS + Vite, `three` 0.169 (nur 3D-Display). Einheiten intern **Meter**. Eigener winziger Store (`useSyncExternalStore`, kein Redux/Zustand). Identifier englisch, UI-Text deutsch via `t()`. Strict tsc (`noUnusedLocals` → ungenutzte Vars brechen den Build). ## 1.1 Werkzeug-System — `src/tools/` - **`Tool`-Interface** `src/tools/types.ts:139` — reine Funktionen über internen `ToolState` (Discriminated Union je Tool). Handler geben `[nextState, ToolResult]` zurück. Tools schreiben NIE Plan-Primitive; sie geben `commit(project) => project` zurück. - `ToolId` `types.ts:11`: `"select" | "wall" | "line" | "polyline" | "rect"`. - `ToolContext` `types.ts:72`: `{ project, level, defaultCategoryCode, activeWallTypeId, activeLineStyleId }`. - `ToolPointer` `types.ts:85`: `{ raw, snap, point (=snap?.point ?? raw), shift, ctrl, alt, button }`. - `ToolResult` `types.ts:121`: `{ draft, commit?, done? }`. `ToolDraft` `types.ts:109`: `{ preview: DraftShape[], vertices: Vec2[], snap?, hud?: {at,text} }`. - **Registry** `src/tools/tools.ts:375`: `TOOLS: Record`, `getTool(id)` `:384`, `TOOL_ORDER` `:389`. Implementiert: select (Platzhalter), wall, line, polyline, rect. - `uniqueId(prefix)` `types.ts:188` — ID-Generator. ## 1.2 Controller / Verdrahtung — alles in `App.tsx` (NICHT in den Tool-Dateien) - Aktives Tool: `const [activeTool,setActiveTool]=useState("select")` `App.tsx:197`. - Laufender Zustand in **Ref** `toolStateRef` `App.tsx:205` (kein Re-Render je Mausschritt). Live-Vorschau `const [draft,setDraft]` `App.tsx:206`. - **Controller** `runToolStep(kind,raw,pxPerMeter,mods)` `App.tsx:350`: baut `ToolContext` (`toolCtx` `:294`), snappt via `snapFor` `:322` (→ `computeSnap`), baut `ToolPointer`, ruft `tool.onClick/onMove`, speichert State in Ref, `applyToolResult` `:339` wendet draft/commit/done an. `toolHandlers` `App.tsx:419` verbindet PlanView↔Controller. - Tool-Tasten `App.tsx:448`: Esc=Abbruch/zurück-zu-select, Enter=Commit-Geste, Backspace=Punkt zurück. ## 1.3 Semantisches Modell — `src/model/types.ts` - `type Element = Wall | Door | Drawing2D` `:251`. `Vec2={x,y}`. **Kein Slab/Stair-Typ.** - `Project` `:254`: `{ ..., wallTypes[], drawingLevels[], layers[], walls[], doors[], drawings2d[] }`. - `Wall` `:150`: `{ id,type:"wall", floorId, categoryCode, start, end, wallTypeId, height, color? }` (Mittellinie + mehrschichtiger `WallType`). - **Plan-Primitive `Drawing2DGeom`** `:200` — die Geom-Typen existieren bereits ALLE: `line | polyline | rect | circle | arc | text`. Aber Tools erzeugen heute nur line/polyline/rect, und `drawingVertices` (Grips) kennt nur diese drei. **circle/arc/text sind im Typ da, aber nicht durchgängig gerendert/editierbar** — Lücke, kein Neubau nötig. - `Drawing2D` `:209`: `{ id,type:"drawing2d", levelId, categoryCode, geom, lineStyleId?, hatchId?, color?, fillColor?, weightMm? }`. ## 1.4 Geometrie — `src/model/geometry.ts` `sub,add,scale,len,normalize`; `leftNormal(a)={x:-a.y,y:a.x}` `:17` (Wand-Normale-Konvention); `cross`, `lineIntersect(a,da,b,db)`, `along`, `wallBand`, `wallCorners` `:70`, `clippedBand` `:87`. `src/model/joins.ts`: `computeJoins(project,walls)` `:44` (nur L-Ecken gehrt; T/X eckig). **Für Offset/Trim/Fillet (Rhino-Kern) gibt es NOCH KEIN 2D-Geometrie-Kernel** — Kurven/Kurven- Schnitt, Polylinien-Offset usw. musst du ergänzen (siehe Bauplan §3.4). ## 1.5 Store — `src/state/` - `createStore` `store.ts:51` über `useSyncExternalStore`. **Actions leben IM State** (`useStore(s=>s.action)`, referenzstabil). `RootState = Project & Selection & View & Layout` `appStore.ts:21`. Exports `useStore`, `getState`, `setState`. - **projectSlice**: `project` + `setProject(next|(p)=>p)`. Mutationen u.a. `addFloor`, `addCategory`, `setElementColor/Weight/Fill`, `resizeElement`, `moveGripOf`, `moveElementByOf`, `moveEdgeOf`, `commitTransformOn`. **Es gibt keine generische „addWall/addDrawing2d"-Action** — Tools committen via `setProject`. (Beim Befehlssystem ggf. saubere Actions ergänzen.) - **Aktive Zeichenebene + aktive Kategorie liegen im viewSlice**, NICHT in selection: `activeLevelId` `viewSlice.ts:46`/`setActiveLevelId`, `activeCategoryCode` `:42`/`setActiveCategoryCode`. - selectionSlice: `selectedWallIds[]`, `selectedDrawingId` + Setter/`clearSelection`. ## 1.6 Views, Eingabe, Koordinaten — `src/plan/PlanView.tsx` (SVG-Vektor) - Modell → `Plan`-Primitive via `generatePlan` `src/plan/generatePlan.ts:203`. `Primitive` = `polygon|line|arc` (polygons tragen `wallId`/`drawingId` für Hit-Test). - **Transform (entscheidend):** `PX_PER_M=90` `:20`; `toScreen(p)={x:p.x*90,y:-p.y*90}` `:31` (fixer Welt-Ursprung 0,0; Y flippt). Invers `viewToModel` `:440`. SVG `viewBox`=State `view`; Pan/Zoom ändern nur `view`, nie das Modell↔Screen-Mapping. - **`rawModelAt(clientX,clientY)`** `:446` = aktuelle Mauswelt-Position in Meter (der Eine-Aufruf, den ein Tool/Befehl braucht). `currentPxPerMeter()` `:488`. - **Pointer-Events** alle am `` `:910`: down `:553`, move `:625`, up `:715`, wheel `:820`, dblclick `:847`, contextmenu `:857`. Schema: Mitte=Pan, Links=Select/Marquee/Tool, Rechts=Menü. Bei `toolActive` `:268` routen Links-Events zu `toolHandlers`. **PlanView meldet bereits `(rawModelAt, currentPxPerMeter, toolMods)` nach oben** — neue Tools brauchen hier NICHTS. - **Snapping** `src/tools/snapping.ts`: `computeSnap(input)` `:88` — endpoint/midpoint/intersection/ onEdge/grid/ortho mit Prioritätstabelle. Wird in App (`snapFor`) konsumiert, nicht in PlanView. `applyAngleConstraint` für Ortho. `SnapSettings`/`DEFAULT_SNAP` in `tools/types.ts:36/55`. - 3D `src/viewport/Viewport3D.tsx` (three.js, Raycaster): nur Anzeige+Auswahl, **keine Zeichenwerkzeuge**. 3D-Authoring = eigene spätere Phase (Raycast auf Arbeitsebene). ## 1.7 Tastatur / globale Eingabe — **kein Dispatch-System** - `main.tsx:38` globaler `contextmenu`→preventDefault; `:42` blockt Ctrl/Cmd+A außerhalb Inputs; `isTextEntry(el)` `:24`. - App-useEffects mit `window.addEventListener("keydown")`: Tool-Tasten `:448`, Delete `:604`, Transform-Shortcuts m/s/d + u/i/o/p `:637`. **Jeder Guard wiederholt inline den INPUT/TEXTAREA/contentEditable-Check** — es gibt keine geteilte Keymap. Dein Tab-Handler + Command-Input kommt als neuer globaler `keydown` dazu (siehe §3.2). ## 1.8 UI-Shell + i18n - `App.tsx` (~2200 Z., enthält noch ToolController/Grips/Transform). JSX `:1043`: TopBar → body (Dock links, Content-View-Router, TransformBar, Dock rechts, Floating) → StatusBar → ResourceManager → ContextMenu → InlineEditor. Panel-Daten via `PanelHostContext` (`baseHost` `App.tsx:729`, Typ `host.ts`). - `StatusBar.tsx` — Footer: links `hint` (Tool-Hinweis), rechts X/Y, Einheit, Massstab 1:N, Zoom, aktives Geschoss, aktive Ebene. **Bester Ort für die Command-Line** (Rhino hat sie klassisch unten). - **i18n** `src/i18n/`: `t(key,params?)` `index.ts:67`, `useT()` `:84`. Flaches `as const`-Dict, Punkt-Namespaces (`tool.*`,`snap.*`,`transform.*`,`status.*`…). `de.ts` (Quelle, ~309 Keys) + `en.ts`; `TranslationKey=keyof typeof de` erzwingt Parität. **Neue Keys IMMER in beide Dateien.** Keine hartcodierten JSX-Strings. ## 1.9 Verifizieren - `npx tsc -b` · `npm run build` · Dev `npm run dev` (Vite 5173, `host:true`). - Screenshot `node scripts/probe.mjs` → `scripts/probe.png` (Puppeteer headless, `deviceScaleFactor:2`, URL via `PROBE_URL`). Viele task-Probes existieren (`probe-tools.mjs`, `probe-line.mjs`, `probe-transform.mjs` …) — gute Vorlagen, um Tools/Befehle programmatisch zu treiben. **Screenshot ansehen + Geometrie prüfen**, nicht nur „kompiliert". --- # TEIL 2 — Rhino-Referenz (das Interaktionsmodell) ## 2.1 Die Command-Line ist das Rückgrat **Alles ist ein Befehl**, und die Command-Line **hört immer zu**: Tastenanschläge gehen an die Command-Line, wenn sie nicht von einem Feld konsumiert werden. Kein „Tool aktiv vs. Eingabe aktiv". Die Zeile hat gleichzeitig drei Rollen: **Eingabe** (Befehl/Wert tippen), **Prompt** („Start of line", „Next point"), **Optionen** (eckige, klickbare Inline-Optionen). ## 2.2 Befehl aufrufen - Namen tippen, z. B. `Line`. **Präfix-Autocomplete** (case-insensitiv): `L`→`Li`→`Lin` zeigt Kandidatenliste mit Best-Match. **Tab/Pfeile** akzeptieren Vorschlag, **Enter/Leertaste** führt aus. - **Aliase**: nutzerdefinierte Kürzel → Makro (z. B. `L`→`!_Line`, `cp`→`!_Copy`). Werden VOR Autocomplete gematcht. (Minimal: Einzelbuchstabe→Befehl.) ## 2.3 Enter / Leertaste / Rechtsklick (leicht falsch gemacht) - **Enter = Leertaste** in der Command-Line. Beide: Befehl ausführen / Default akzeptieren / mehrteiligen Befehl **beenden** / bei **leerer** Zeile **letzten Befehl wiederholen**. - **Rechtsklick im Viewport = Enter.** Also: Rechtsklick beendet Polyline UND wiederholt bei leerer Zeile den letzten Befehl. → `lastCommand` speichern, bei Leer-Enter/Rechtsklick neu starten. ## 2.4 Inline-Optionen (klickbare Klammern) ``` Start of line ( BothSides=No Chamfer Mode=Distance ): ``` - Jede Option **klickbar UND tippbar** (genug Buchstaben zur Eindeutigkeit + Enter). - **Toggle** `Name=Value` flippt beim Klick. **Value**-Option fragt Unterwert ab. **Action**-Option (ohne `=`) verzweigt sofort. - Optionen sind **innerhalb des Befehls persistent**, viele **über Aufrufe hinweg** (letzte Offset-Distanz, Array-Anzahl, Fillet-Radius merken). **Zuletzt benutzte Optionswerte je Befehl persistieren** — Nutzer erwarten das. ## 2.5 Sub-Prompts = State-Machine Befehle laufen Prompts ab. `Line`: „Start of line:" → Punkt → „End of line:" → Punkt → fertig. `Polyline`: „Start" → „Next point ( Close Undo ):" → … → **Enter** beendet. Prompt-Text ist sichtbar und lehrreich („Next point. Press Enter when done") — literal nachbilden. ## 2.6 Transparente/verschachtelbare Befehle Manche Befehle (Zoom/Pan, Osnap-Toggle, alles mit `'`-Präfix) laufen **innerhalb** eines anderen, ohne ihn abzubrechen, und kehren zum Original-Prompt zurück. → Command-Runner braucht einen **Stack**. ## 2.7 Koordinaten- & Numerik-Eingabe (Herz der Präzision) | Eingabe | Bedeutung | |---|---| | `5,3` / `5,3,2` | absolut X,Y(,Z) | | `r5,3` | **relativ** zum letzten Punkt (das `r`-Idiom) | | `<45` | Winkel-Constraint auf 45°, dann Maus/Distanz | | `5<45` | **polar**: Distanz 5 unter 45° vom letzten Punkt | | Zahl tippen während Drag | **Distanz-Lock**: Richtung per Maus, Länge per Zahl+Enter (meistgenutzte Geste) | | Zahl + **Tab** | Lock umschalten (Länge fix → Winkel folgt Maus, oder umgekehrt) | Das Feld parst **kontextabhängig**: Befehlsname / Optionsbuchstabe / Koordinate / nackte Zahl — je nach Befehlszustand. Das Live-Tool muss **einen primären Skalar** (Länge/Radius/Distanz) exponieren, an den eine getippte Zahl bindet. ## 2.8 Osnaps + Ortho + Gumball - **Osnaps** (persistente Toggles): End, Mid, Cen, Int, Perp, Near, Quad, Tan, Point. Pro Mausschritt gegen nahe Geometrie geprüft (Pixel-Toleranz), Marker+Label am Cursor; liefert **exakte Modellkoordinate** (nie Roh-Maus, wenn Snap aktiv). One-Shot-Osnap überschreibt für den nächsten Pick. - **Ortho** (F8): Winkelraster (90°/konfigurierbar), **Shift** togglet temporär. **Grid Snap** (F9). **SmartTrack**: temporäre Hilfslinien aus zuletzt gehoverten Punkten. - **Gumball**: On-Object-Widget (Pfeile=Move, Bögen=Rotate, Handles=Scale); Handle klicken → Zahl tippen für exakten Transform. Direkt-Manipulations-Gegenstück zu getippten Befehlen. > Präzisionsmodell = **(Snap ODER getippte Koordinate) × (Ortho/Winkel-Constraint) × > (Distanz-Constraint)**, in EINEM Pick komponierbar. ## 2.9 Auswahl-Modell (links/rechts-Regel exakt) - Klick = wählen; Shift+Klick add; Ctrl+Klick remove. - **Links→rechts = Window** (nur voll umschlossene; **durchgezogenes** Rechteck). - **Rechts→links = Crossing** (auch berührte; **gestricheltes** Rechteck). Richtung bestimmt Modus — starke Konvention, exakt nachbilden. - **SelLast** (zuletzt erzeugte/gewählte erneut wählen) ist enorm nützlich („erzeugen, dann sofort bewegen"). Min. `SelLast`, `SelAll`, `SelNone`, `Invert`. ## 2.10 Befehls-Prompt-Sequenzen (Kurz) 2D: **Line** (2 Pkt) · **Polyline** (Close/Undo, Enter beendet) · **Rectangle** (Ecke+Ecke, oder Breite/Höhe tippen; 3Point/Center) · **Circle** (Center+Radius; 2P/3P/Tan) · **Arc** (Center-Start-End / 3Point) · **Offset** (Kurve wählen → Seite klicken/Distanz tippen; Distanz persistent) · **Fillet/Chamfer** (Kurve1→Kurve2, Radius/Distances persistent) · **Trim** (Schneider wählen→Enter→ wegzuschneidendes Stück klicken) · **Split** · **Extend** · **Join** · **Explode** · **Move/Copy/Rotate/Scale/Mirror** (Auswahl→Basispunkt→Ziel; Copy-Option) · **ArrayRect/ArrayPolar** · **Group/Ungroup**. 3D (braucht CSG, später): **ExtrudeCrv** (geschlossene Kurve→Solid, Cap) · **Box** · **Boolean Union/Difference/Intersection** · **Cap** · **Gumball-Face-Drag = PushPull** · Loft/Sweep/Revolve. --- # TEIL 3 — Bauplan für DIESE Codebase > Ziel: nutzbarer 2D-Architektur-Drafter mit Rhino-Feel, dann einfaches Massing. Halte das > ROADMAP-Prinzip: **ein semantisches Modell → Sichten abgeleitet**; Extrusionshöhe ist eine > Eigenschaft, nie eingebackene Geometrie. ## 3.0 Kuratierungs-Prinzip (WICHTIG — Nutzer-Vorgabe) **NICHT den ganzen Rhino-Katalog stumpf portieren.** Wir bauen ein **Wohnbau-BIM**, keinen NURBS-Allzweck-Modeller. Nimm nur, was dem Wohnbau-Workflow dient; lass den Rest weg, bis er konkret gebraucht wird. Faustregel: *Brauche ich das, um ein Einfamilienhaus zu zeichnen und daraus Pläne zu ziehen?* Wenn nein → weglassen. **Bewusst WEGLASSEN (vorerst):** Loft / Sweep1+2 / Revolve (Sonderformen, kaum Wohnbau) · freie NURBS-Kurven (`Curve`/`InterpCrv` Grad>1, Deformable, FromFoci) · Ellipse · Tangent/ Bisector/4Point-Linienvarianten · SmartTrack (nett, nicht kritisch) · der volle `Sel*`-Zoo (nur SelLast/SelAll/SelNone/Invert) · Knot/Vertex/Tan-Osnaps. Alle leicht später additiv nachrüstbar — kein Grund, sie jetzt mitzuschleppen. **Booleans sind KEIN „nice to have später"** — sie werden gebraucht, **sobald Tür/Fenster als echte 3D-Öffnung** kommen (heute schneidet `Door` nur eine Plan-Lücke, kein 3D-Boolean, siehe HANDOVER). Darum: CSG/Booleans an die **Tür/Fenster-Phase koppeln** und dann reinnehmen — nicht ans Ende schieben. ABER (das ist der „nicht stumpf"-Teil): > Für **rechteckige** Öffnungen in extrudierten Wänden braucht es **keinen allgemeinen > Boolean-Kernel**. Eine analytische **Wand-minus-Box-Subtraktion** (Öffnung als parametrische > Aussparung im Wand-Solid) ist einfacher, robuster und für 95 % Wohnbau ausreichend. Den > allgemeinen CSG-Boolean (`rhino3dm`) erst ziehen, wenn schräge/runde/verschnittene Fälle > wirklich auftreten. Also: **Öffnungen zuerst analytisch, allgemeine Booleans erst bei Bedarf.** ## 3.1 Leitentscheidung: Engine verallgemeinern, nicht parallel bauen Baue eine gemeinsame **Command-Engine**, die das bestehende `Tool`-Interface erweitert/ablöst, sodass es **einen** Eingabepfad gibt (Maus + Tastatur + Command-Line speisen dieselbe Maschine). Konkret: ein `Command`-Modell, das je Schritt einen **Prompt** (Text), erwartete **Eingabearten** (Punkt | Zahl | Option | Auswahl) und **Optionen** beschreibt. Die heutigen Tools werden zu Befehlen dieser Engine (wall/line/polyline/rect lassen sich 1:1 portieren — ihre Phasenlogik ist schon eine Mini-State-Machine). **Warum nicht das alte Tool-Interface unangetastet lassen und Command-Line nur draufsetzen?** Weil die Command-Line getippte Koordinaten/Optionen in denselben Schritt einspeisen muss, in dem die Maus pickt. Zwei getrennte Pfade divergieren garantiert (Snapping, Constraints, HUD doppelt). ## 3.2 Neue Dateien (Vorschlag) - `src/commands/engine.ts` — Command-Runner: aktiver Befehl, Prompt-Stack (für transparente Befehle §2.6), `lastCommand`-Wiederholung, Routing von Maus-Pick / getippter Eingabe / Option-Klick in den aktuellen Schritt. Hält `CommandState`. - `src/commands/types.ts` — `Command`-Interface (Verallgemeinerung von `Tool`): Schritte mit `prompt: TranslationKey`, `accepts: ("point"|"number"|"option"|"selection")[]`, `options: CmdOption[]`, `onInput(state,input,ctx): [state, CommandResult]`. `CommandResult` wie `ToolResult` (+`commit`). - `src/commands/parseInput.ts` — Koordinaten-Parser (§2.7): `5,3` · `r5,3` · `5<45` · `<45` · nackte Zahl (Distanz-Lock) · Optionsbuchstabe. Liefert eine Discriminated Union, die die Engine in einen Modellpunkt/Constraint auflöst (mit `lastPoint` für `r`/polar). - `src/commands/registry.ts` — `COMMANDS: Record` + Aliase + Autocomplete (Präfix). - `src/ui/CommandLine.tsx` — die Command-Line-UI **in/über der Statusleiste** (`StatusBar.tsx`): zeigt Prompt + klickbare Optionen + Texteingabe; Autocomplete-Dropdown. Tab fokussiert sie. - (später) `src/geometry/kernel2d.ts` — 2D-Kernel für Offset/Trim/Fillet/Schnitt (§3.4). - (viel später) `src/geometry/solid3d.ts` o. `rhino3dm`-Anbindung für Massing/Booleans (§3.5). ## 3.3 Verdrahtung (minimal-invasiv) - **Globaler Tab-Handler**: neuer `window.keydown` in App (gleicher Guard wie `App.tsx:448` — INPUT/TEXTAREA/contentEditable überspringen). Tab → Command-Line fokussieren/öffnen. Jeder getippte Buchstabe ohne aktives Tool startet den Befehlsmodus (Rhino „hört immer zu" — optional in Phase 2; Phase 1 reicht Tab). - **Command-Line → Engine → Store**: Befehle dispatchen auf `setActiveTool` (für tool-artige) bzw. direkt auf Store-Actions / `setProject`. Nutze `getState()/setState()` (referenzstabil) aus `appStore.ts`. - **Pick-Eingabe**: die Engine konsumiert dieselben `(rawModelAt, currentPxPerMeter, toolMods)`, die PlanView schon hochmeldet (`ToolHandlers`). `computeSnap` für Punktfang wiederverwenden. → PlanView braucht im Idealfall **keine Änderung** (höchstens: Window/Crossing-Marquee-Visual durchgezogen vs. gestrichelt nach Drag-Richtung, §2.9 — heute evtl. nur ein Modus). - **Prompt/HUD**: Prompt-Text in die Statusleiste (`StatusBar` `hint` existiert schon). Distanz/ Winkel-HUD am Cursor existiert in `ToolDraft.hud`. ## 3.4 Reihenfolge (Tiers — strikt 2D zuerst) **Tier 0 — Substrat (VOR jedem Befehl; das ist der „Feel"):** 1. Command-Runner + Command-Line-UI (Prompt → pick/type → Optionen; Enter/Space/Rechtsklick = bestätigen/beenden/wiederholen; `lastCommand`). 2. Koordinaten-Parser (`x,y` · `rdx,dy` · `dist