# Oberleiste – Angleichung an DOSSIER (Umsetzungs-Spezifikation) Verbindliche Vorlage für den Umbau der Top-Bar (`src/ui/TopBar.tsx`, `src/styles.css`, Verdrahtung in `src/App.tsx`). Quelle: das DOSSIER-Rhino- Plugin (`ToolbarApp.jsx`, `components/BarControls.jsx`, `TextEditorApp.jsx`). Bezeichner englisch, UI-Text/Kommentare deutsch (CONVENTIONS.md). ## 0. Grundprimitive (neu, DOSSIER-konform) Alle Leisten-Controls teilen dieselbe Höhe und Pillenform. - `BAR_H = 22px` Basis-Höhe. Segmentierte Pillen `BAR_H + 2 = 24px` (`box-sizing:border-box`, 1px Rand inbegriffen). - Pille: `border:1px solid var(--border)`, `border-radius:999px`, `background:var(--input)`. Hover (interaktiv): `border-color:var(--accent-border)`, `background:var(--accent-dim)`. - Aktiver Zustand (Toggle AN / aktive Segmentzelle): `background:var(--accent)`, `color:#fff`. ### BarCombo (Pillen-Dropdown) Wir haben bereits `src/ui/Dropdown.tsx`. Der Dropdown-Trigger MUSS optisch der Pille entsprechen (Höhe 24, radius 999, obige Farben). Ein optionales Icon sitzt LINKS **ausserhalb** der Pille (18px breit, `var(--muted)`), ein optionaler Zahnrad-Knopf („settings") sitzt rechts **innerhalb** der Pille. Prüfen, ob `Dropdown` bereits so aussieht; falls nicht → Trigger-CSS angleichen (Klasse `tb-dd-trigger`). KEINE zweite Dropdown-Implementierung bauen. ### Segmentpille (3er/4er) Aussencontainer `display:inline-flex; height:24px; border:1px solid var(--border); border-radius:999px; overflow:hidden`. Zellen ohne eigenen Radius; interne Trenner über `border-left:1px solid var(--border)` (erste Zelle ohne). Aktive Zelle `var(--accent)`/#fff, inaktiv `var(--input)`/`var(--ink)`, Hover `var(--accent-dim)`/`var(--accent)`. Genutzt für: Ansichts-Icons, Zoom (%/fit/center), **B/I/U**, **L/C/R**. ### BarButton (quadratischer Icon-Knopf) 22×22, `border-radius:999px`, sonst wie Pille. Aktiv = Akzentfüllung, Icon #fff. ## 1. Reihenfolge der Gruppen (links → rechts) 1. Marke (bestehend, unverändert). 2. Ansichts-Gruppe (bestehend `view-grid`; Zellen auf Segmentpillen-Look bringen). 3. Sichtbarkeits-Kombinationen (bestehend, `BarCombo`-Look). 4. Detailgrad + Massstab (gestapelt, `BarCombo`-Look). 5. **Massstab/Zoom-Cluster NEU** (siehe §2) — ersetzt die heutige Gruppe mit der DOPPELTEN Zoom-Anzeige. 6. Darstellungsart (bestehend, `BarCombo`). 7. **Text-Gruppe NEU** (siehe §3) — die zentrale neue Leiste. 8. Referenzlinien / Linien-Modus (bestehend). 9. Rechts: Layout · Ressourcen · Projektname. ## 2. Massstab/Zoom-Cluster (ersetzt Doppel-Zoom-Bug) HEUTE FALSCH: In `TopBar.tsx` wird `tb-zoom` (Zoom %) ZWEIMAL gerendert (einmal im `tb-zoomstack`, einmal darunter als eigener ``). Die zweite, lose `…%` ersatzlos ENTFERNEN. NEUES Layout — 2×2-Raster (`display:grid; grid-template-columns:auto auto; gap:4px 6px; align-items:center`): - **Spalte 1, beide Zeilen** (`grid-row:1 / span 2`): EINE kombinierte Stat-Pille, `width:70px`, Höhe `BAR_H*2+6 = 50px`, `border-radius:14px` (NICHT 999), `border:1px solid var(--border)`, `background:var(--input)`, Innen zwei Zeilen mittig, getrennt durch 1px-Linie (`var(--border)`): - oben: Live-Massstab `1:N` (Akzentfarbe, `var(--font-mono)`, 11px, 700) - unten: Zoom `NN%` (`var(--ink-2)`, mono, 11px) - „am Massstab" (Zoom==gewählter Massstab): Pille `background:var(--accent-dim)`, `border-color:var(--accent)`, Text `var(--accent)`. - Nicht-Plan-Ansicht: beide Werte „—". - **Spalte 2, Zeile 1**: Massstab-Dropdown (`BarCombo`, ~140px, mono) + Print/PDF bleibt separat. (Massstab-Dropdown ist der bestehende `scaleOptions`-Dropdown.) - **Spalte 2, Zeile 2**: Zoom-Segmentpille mit 3 Zellen — `%` (=`onZoom100`, Label „1:1"/100 %), `fit_screen` (=`onFit`), `center_focus_strong` (=`onFitSelection`). Material-Icons. Daneben ggf. Referenzlinien-BarButton. Export-Knöpfe (PDF/DXF) wandern in eine eigene kleine BarButton-Reihe rechts vom Cluster (Icons `picture_as_pdf` / `download`) ODER bleiben Pillen — Hauptsache NICHT mehr Teil des Zoom-Blocks, damit der Cluster ruhig bleibt. ## 3. Text-Gruppe in der Oberleiste (NEU – Kern dieser Aufgabe) Immer sichtbar. 3×2-Raster (`grid-template-columns:110px 130px 80px; gap:4px 6px`). Setzt Defaults für neuen Text UND formatiert die aktuelle Auswahl live. Zeile 1: - **Stil-Preset** `BarCombo` (110px): Optionen aus `DEFAULT_PRESETS` (Titel/Untertitel/Label/Notiz) + „— Stil —". - **Font** `BarCombo` (130px): Systemfont-Liste (mind. Helvetica, Arial, Inter, Times New Roman, Georgia, Courier New). `applyMark(doc,range,'font',v)`. - **Grösse** `BarCombo` (80px): Presets in pt `[8,9,10,11,12,14,18,24,36,48]` + „Eigene…" → Zahl-Input-Pille. `applyMark(...,'sizePt',n)`. Zeile 2: - **B/I/U** Segmentpille (110px, Icons `format_bold`/`format_italic`/ `format_underlined`): `toggleMark(doc,range,'bold'|'italic'|'underline')`. Aktiv-Zustand aus `isMarkActive(doc,range,mark)`. - **L/C/R** Segmentpille (130px, Icons `format_align_left`/`_center`/`_right`): setzt `paragraph.align` im Bereich. - **„+"-Text-Button** (80px, BarButton/Pille, Icon `add`, Label „Text"): startet das Text-Werkzeug (neues Textobjekt platzieren). Falls das Text-Annotation- Werkzeug noch nicht existiert, Button vorerst `disabled` mit Tooltip (kein stiller No-Op) — aber Verdrahtung vorbereiten. **Auswahl-Bewusstsein (WICHTIG):** Prop `textTarget` (oder aus App-State): entweder `null` (nichts Text-artiges selektiert → Controls setzen nur Defaults, Ränder normal) ODER `{ doc: RichTextDoc, range: TextRange|null, apply: (doc)=>void }` für den aktuell selektierten Raumstempel/Text. Ist `textTarget != null`, tragen die Zeile-2-Pillen `border-color:var(--accent)` (Akzent-Glow), und alle Aktionen wirken auf `textTarget.doc` via `textTarget.apply(newDoc)`. Ohne aktive Range (nur Objekt selektiert, kein Editor offen) wirkt Formatierung auf das GANZE Doc. Quelle der `textTarget`-Daten: der Raum-Agent exponiert Stempel-Doc + Setter (`setRoomStampDoc(roomId, doc)`) im App-State (siehe Peer-Absprache). App leitet für den selektierten Raum `{doc: room.stampDoc, range: activeStampRange, apply: d => setRoomStampDoc(room.id, d)}` an die Text-Gruppe. ## 4. Text-Inhalt bearbeiten: kleines Fenster (kein Footer) Doppelklick auf einen Raumstempel/ein Textobjekt öffnet ein **schwebendes Dialog-Fenster** (nicht den Footer, kein Panel-Aufklappen). Umsetzung: neue Komponente `src/ui/TextEditorDialog.tsx` — ein zentriertes/абgesetztes Fenster (~560×420, `--shadow-3`, `border-radius:8px`, Titel „Text bearbeiten", Kopf mit Schliessen-✕), Body = der bestehende `src/text/RichTextEditor.tsx` (er bringt seine eigene Mini-Toolbar mit — das ist hier ok, weil es ein eigenes Fenster ist), Fuss = „Abbrechen" / „Übernehmen". „Übernehmen" ruft `setRoomStampDoc(roomId, editedDoc)`. Der Doppelklick-Handler lebt in App (Plan-View/Viewport → onDoubleClick auf Stempel-Hit → `openTextEditor(roomId)`), NICHT im Footer. Falls der Raum-Agent den Footer benutzt hat: diesen Pfad entfernen und durch den Dialog ersetzen. ## 5. Farb-/Stil-Tokens Bestehende CSS-Variablen weiterverwenden (`--panel`,`--input`,`--border`, `--accent`,`--accent-dim`,`--accent-border`,`--ink`,`--ink-2`,`--muted`, `--font-mono`,`--shadow-1..3`). KEINE neuen Farbwerte hart kodieren. Falls ein Token fehlt (z. B. `--accent-border`), prüfen und ggf. aus bestehenden ableiten. ## 6. i18n Neue Keys in de.ts UND en.ts: `text.style`, `text.font`, `text.size`, `text.size.custom`, `text.bold/italic/underline`, `text.align.left/center/right`, `text.add`, `text.add.hint`, `text.editTitle`, `text.apply`, `text.cancel`, `text.selectedHint`. Presets-Namen über bestehende `rt.*`/Preset-Keys, sofern da. ## 7. Gate (Pflicht) `rm -f tsconfig.tsbuildinfo && npx tsc -b` grün, `npm run build` grün, keine AI-Spuren (grep auf Claude/Anthropic/AI/Generated/Co-Authored), Boot-Probe (`node scripts/probe.mjs`) ohne Konsolenfehler, Screenshot der Oberleiste. KEIN Commit.