# 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 `<span>`). Die zweite,
lose `<span className="tb-zoom">…%</span>` 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.
