Menüleiste-Panel entkoppeln — Panelinhalt in eigenem, skalierbarem Fenster #167

Closed
opened 2026-10-24 16:18:27 +00:00 by alf · 1 comment
Owner

Ziel

Ein Button „Entkoppeln“ oben rechts im Menüleisten-Panel öffnet ein normales Fenster mit dem Panelinhalt — Assistant-Tab, Settings-Tab, Chat-Liste, alles darin. Das Panel ist ein 400-pt-Dropdown unter der Menüleiste; für die Settings-Listen und die Chat-Übersicht ist das zu klein.

Verhalten

  • Klick auf „Entkoppeln“ (Header, links neben „Schließen“) verschiebt den Inhalt in ein freistellbares, skalierbares Fenster. Das Menüleisten-Panel ist zu, bis das Fenster wieder zu ist.
  • Der Zustand bleibt erhalten: Tab-Auswahl, eingegebene Felder, Suche. Erreicht wird das dadurch, dass dasselbe NSHostingView von panel.contentView nach window.contentView umgehängt wird — nicht eine zweite Instanz von CompanionPanelView aufgebaut wird (deren @State wäre leer).
  • Fenster schließen (roter Knopf) hängt den Inhalt ans Menüleisten-Panel zurück, ohne es zu öffnen. „Andocken“ im Fensterkopf hängt zurück und öffnet das Panel.
  • Klick auf das Menüleiste-Symbol während entkoppelt: holt das Fenster nach vorn statt ein leeres Panel zu zeigen.

Warum nicht eine zweite Instanz

CompanionPanelView hält ~40 @State/@AppStorage-Felder für Eingaben (API-Keys, Server-URLs, Sprachauswahl …). Eine zweite Instanz daneben hätte zwei unabhängige Zustände — dasselbe Feld zwei Mal offen, zwei Mal zu bearbeiten. Umhängen statt Nachbauen hält es bei einem Zustand.

Skalierung

Der feste frame(width: 400) weicht im Fenster nil (View füllt das Fenster). Bewusst als Argument-Wechsel desselben Modifiers geschrieben, nicht als if/else um die View herum: sonst baut SwiftUI die Hierarchie neu auf und der Zustand wäre trotzdem weg. Gleiches Muster für den Hintergrund (RoundedRectangle 12 pt → Füllung ohne Rundung).

Nicht geprüft / Grenzen

  • Die 4600 Zeilen der Ansicht sind durchgehend auf 400 pt gerechnet. Breiter skaliert verhalten sich zweispaltige Abschnitte und Button-Reihen ungetestet; erst im echten Fenster auszuprobieren.
  • Sheets (AddConnectionSheetView, ServerManagerSheetView, Settings-Sheets) hängen an der View — ob sie im Fenster korrekt erscheinen, nicht geprüft.
## Ziel Ein Button **„Entkoppeln“** oben rechts im Menüleisten-Panel öffnet ein normales Fenster mit dem Panelinhalt — Assistant-Tab, Settings-Tab, Chat-Liste, alles darin. Das Panel ist ein 400-pt-Dropdown unter der Menüleiste; für die Settings-Listen und die Chat-Übersicht ist das zu klein. ## Verhalten - Klick auf „Entkoppeln“ (Header, links neben „Schließen“) verschiebt den Inhalt in ein freistellbares, skalierbares Fenster. Das Menüleisten-Panel ist zu, bis das Fenster wieder zu ist. - **Der Zustand bleibt erhalten**: Tab-Auswahl, eingegebene Felder, Suche. Erreicht wird das dadurch, dass dasselbe `NSHostingView` von `panel.contentView` nach `window.contentView` umgehängt wird — nicht eine zweite Instanz von `CompanionPanelView` aufgebaut wird (deren `@State` wäre leer). - Fenster schließen (roter Knopf) hängt den Inhalt ans Menüleisten-Panel zurück, ohne es zu öffnen. „Andocken“ im Fensterkopf hängt zurück **und** öffnet das Panel. - Klick auf das Menüleiste-Symbol während entkoppelt: holt das Fenster nach vorn statt ein leeres Panel zu zeigen. ## Warum nicht eine zweite Instanz `CompanionPanelView` hält ~40 `@State`/`@AppStorage`-Felder für Eingaben (API-Keys, Server-URLs, Sprachauswahl …). Eine zweite Instanz daneben hätte zwei unabhängige Zustände — dasselbe Feld zwei Mal offen, zwei Mal zu bearbeiten. Umhängen statt Nachbauen hält es bei einem Zustand. ## Skalierung Der feste `frame(width: 400)` weicht im Fenster `nil` (View füllt das Fenster). Bewusst als Argument-Wechsel desselben Modifiers geschrieben, nicht als `if`/else um die View herum: sonst baut SwiftUI die Hierarchie neu auf und der Zustand wäre trotzdem weg. Gleiches Muster für den Hintergrund (`RoundedRectangle` 12 pt → Füllung ohne Rundung). ## Nicht geprüft / Grenzen - Die 4600 Zeilen der Ansicht sind durchgehend auf 400 pt gerechnet. Breiter skaliert verhalten sich zweispaltige Abschnitte und Button-Reihen ungetestet; erst im echten Fenster auszuprobieren. - Sheets (`AddConnectionSheetView`, `ServerManagerSheetView`, Settings-Sheets) hängen an der View — ob sie im Fenster korrekt erscheinen, nicht geprüft.
Author
Owner

PR #168 gemergt: feat(panel): Entkoppeln — Panelinhalt in eigenem, frei skalierbarem Fenster, Zustand bleibt erhalten (#167)
#168

Commits:

  • feat(panel): Entkoppeln — Panelinhalt in eigenem, frei skalierbarem Fenster, Zustand bleibt erhalten (#167)

Stand — Panel entkoppeln (iris/Iris#167)

Befund

Das Menüleisten-Panel ist ein 400-pt-Dropdown unter der Menüleiste. Für die Settings-Listen und die
Chat-Übersicht ist das zu klein; es gab keinen Weg, den Inhalt größer zu sehen.

Geändert

  • Iris/DetachedPanelWindowController.swift (neu): normales Fenster (.titled, .closable, .resizable, .miniaturizable), Titel „Iris“, Höhe beim Öffnen aus der zuletzt gemessenen
    Panel-Höhe (kein leerer Raum unter dem Inhalt), Fensterhintergrund auf DS.Colors.background
    (der SwiftUI-Inhalt malt nur seine eigene Höhe). Roter Knopf gibt den Inhalt ans Panel zurück,
    ohne das Panel zu öffnen — wer entkoppelt, will im Fenster arbeiten. „Andocken“ öffnet es
    zusätzlich.
  • Iris/MenuBarPanelManager.swift: hostingView wird von einer Lokalen in createPanel() zu
    einer Eigenschaft; neu detach() / dock() / finishReattach() / makeRootView(detached:) /
    scrollableContainer(for:). Im Fenster sitzt die View als documentView eines NSScrollView
    (höherer Inhalt scrollt, ohne die SwiftUI-Struktur anzufassen), Autoresizing [.width] an der
    Dokument-View, damit Breiterziehen neu laidet. updatePanelHeight(_:) verwendet denselben
    gemessenen Wert für beide Fälle. Klick aufs Menüleiste-Symbol im entkoppelten Zustand holt das
    Fenster nach vorn statt ein leeres Dropdown zu zeigen.
  • Iris/CompanionPanelView.swift: Header-Button „Entkoppeln“ (Text, nicht Icon) links neben
    „Schließen“, im Fenster „Andocken“; neue Felder isDetached / onDetach / onDock /
    onCloseDetached. Panel-Fenster-Unterschiede sind Argumente derselben Modifiers:
    frame(width: isDetached ? nil : 400) und RoundedRectangle(cornerRadius: isDetached ? 0 : 12)
    mit Null-Schatten. Ein if um die View hätte eine andere Hierarchieposition ergeben und jeden
    @State verworfen.
  • Iris.xcodeproj/project.pbxproj: DetachedPanelWindowController.swift in die
    membershipExceptions des IrisClient-Targets (Menüleiste gibt es im IrisClient nicht).
  • CLAUDE.md: ein Satz am Menu-Bar-Panel-Muster (warum umgehängt statt zweiter Instanz).
  • Iris/Localizable.xcstrings: zwei neue Schlüssel, dazu Xcodes Umformatierung der ganzen
    Datei (bekanntes Muster, zuletzt in PR #164/#166).

Warum umhängen statt zweiter Instanz: CompanionPanelView hält ~40 @State/@AppStorage-
Felder (Tab, API-Key-Felder, Suchtext, Server-URLs). Eine zweite Instanz wäre bei null angefangen —
dasselbe Feld zwei Mal offen, zwei Mal auszufüllen. Dasselbe NSHostingView zwischen
panel.contentView und window.contentView zu verschieben hält jeden Zustand exakt so, wie der
Nutzer ihn verlassen hat.

Geprüft

  • Xcode-Build Iris: succeeded.
  • Xcode-Build IrisClient: succeeded (belegt zugleich den neuen membershipExceptions-
    Eintrag; aktives Schema danach zurück auf Iris).
  • Vier echte Compiler-Fehler gefunden und behoben: fehlender Init-Parameter onCloseDetached;
    falsches Argument-Label nach Umbenennung isDetached: → detached: (Kollision mit dem
    View-Feldnamen); NSScrollView.contentView ist nicht optional (kein if let);
    scrollTo(.top) erst ab macOS 15 bei Ziel 14.2 — jetzt contentView.scroll(to:) +
    reflectScrolledClipView.

Nicht geprüft

  • Nichts in der laufenden App. Kein Klick auf „Entkoppeln“, kein Fenster gesehen.
  • Insbesondere unbelegt: dass der Zustand beim Umzug tatsächlich erhalten bleibt (Tab, ein
    eingetippter Key, ein Suchbegriff), wie sich die durchgehend auf 400 pt gerechneten Abschnitte in
    einem breiteren Fenster verhalten, ob Sheets (AddConnectionSheetView, ServerManagerSheetView,
    die Settings-Sheets) im Fenster korrekt erscheinen, und das Scrollen bei höherem Inhalt.

Grenzen / offen

  • Der Header hat jetzt ein Text-Button mehr. Bei 400 pt und drei installierten Agenten (Terminal-
    Buttons + Web-GUI + Index-Manager + Status-Text + „Entkoppeln“ + xmark) kann die Zeile eng
    werden — nicht ausprobiert, erster Kandidat für eine sichtbare Anpassung.
  • Die Ansicht ist auf 400 pt gerechnet; breites Layout ist nicht nachgesehen, sondern bewusst
    freigelassen (Nutzer-Entscheidung gegen eine Nachprüfung aller Abschnitte).
  • idealPanelHeight wird beim Andocken auf 0 gesetzt und neu gemessen — ein kurzes Nachziehen der
    Dropdown-Höhe beim ersten Öffnen danach ist möglich, nicht beobachtet.
  • Kein automatischer Test (UI-Code ohne Test-Infra in diesem Bereich).
PR #168 gemergt: feat(panel): Entkoppeln — Panelinhalt in eigenem, frei skalierbarem Fenster, Zustand bleibt erhalten (#167) https://forgejo.lewerken.duckdns.org/iris/Iris/pulls/168 Commits: - feat(panel): Entkoppeln — Panelinhalt in eigenem, frei skalierbarem Fenster, Zustand bleibt erhalten (#167) --- # Stand — Panel entkoppeln (iris/Iris#167) ## Befund Das Menüleisten-Panel ist ein 400-pt-Dropdown unter der Menüleiste. Für die Settings-Listen und die Chat-Übersicht ist das zu klein; es gab keinen Weg, den Inhalt größer zu sehen. ## Geändert - **`Iris/DetachedPanelWindowController.swift`** (neu): normales Fenster (`.titled, .closable, .resizable, .miniaturizable`), Titel „Iris“, Höhe beim Öffnen aus der zuletzt gemessenen Panel-Höhe (kein leerer Raum unter dem Inhalt), Fensterhintergrund auf `DS.Colors.background` (der SwiftUI-Inhalt malt nur seine eigene Höhe). Roter Knopf gibt den Inhalt ans Panel zurück, **ohne** das Panel zu öffnen — wer entkoppelt, will im Fenster arbeiten. „Andocken“ öffnet es zusätzlich. - **`Iris/MenuBarPanelManager.swift`**: `hostingView` wird von einer Lokalen in `createPanel()` zu einer Eigenschaft; neu `detach()` / `dock()` / `finishReattach()` / `makeRootView(detached:)` / `scrollableContainer(for:)`. Im Fenster sitzt die View als `documentView` eines `NSScrollView` (höherer Inhalt scrollt, ohne die SwiftUI-Struktur anzufassen), Autoresizing `[.width]` an der Dokument-View, damit Breiterziehen neu laidet. `updatePanelHeight(_:)` verwendet denselben gemessenen Wert für beide Fälle. Klick aufs Menüleiste-Symbol im entkoppelten Zustand holt das Fenster nach vorn statt ein leeres Dropdown zu zeigen. - **`Iris/CompanionPanelView.swift`**: Header-Button **„Entkoppeln“** (Text, nicht Icon) links neben „Schließen“, im Fenster „Andocken“; neue Felder `isDetached` / `onDetach` / `onDock` / `onCloseDetached`. Panel-Fenster-Unterschiede sind **Argumente derselben Modifiers**: `frame(width: isDetached ? nil : 400)` und `RoundedRectangle(cornerRadius: isDetached ? 0 : 12)` mit Null-Schatten. Ein `if` um die View hätte eine andere Hierarchieposition ergeben und jeden `@State` verworfen. - **`Iris.xcodeproj/project.pbxproj`**: `DetachedPanelWindowController.swift` in die `membershipExceptions` des IrisClient-Targets (Menüleiste gibt es im IrisClient nicht). - **`CLAUDE.md`**: ein Satz am Menu-Bar-Panel-Muster (warum umgehängt statt zweiter Instanz). - **`Iris/Localizable.xcstrings`**: zwei neue Schlüssel, dazu Xcodes Umformatierung der ganzen Datei (bekanntes Muster, zuletzt in PR #164/#166). **Warum umhängen statt zweiter Instanz:** `CompanionPanelView` hält ~40 `@State`/`@AppStorage`- Felder (Tab, API-Key-Felder, Suchtext, Server-URLs). Eine zweite Instanz wäre bei null angefangen — dasselbe Feld zwei Mal offen, zwei Mal auszufüllen. Dasselbe `NSHostingView` zwischen `panel.contentView` und `window.contentView` zu verschieben hält jeden Zustand exakt so, wie der Nutzer ihn verlassen hat. ## Geprüft - Xcode-Build **Iris**: `succeeded`. - Xcode-Build **IrisClient**: `succeeded` (belegt zugleich den neuen `membershipExceptions`- Eintrag; aktives Schema danach zurück auf `Iris`). - Vier echte Compiler-Fehler gefunden und behoben: fehlender Init-Parameter `onCloseDetached`; falsches Argument-Label nach Umbenennung `isDetached:` → `detached:` (Kollision mit dem View-Feldnamen); `NSScrollView.contentView` ist nicht optional (kein `if let`); `scrollTo(.top)` erst ab macOS 15 bei Ziel 14.2 — jetzt `contentView.scroll(to:)` + `reflectScrolledClipView`. ## Nicht geprüft - **Nichts in der laufenden App.** Kein Klick auf „Entkoppeln“, kein Fenster gesehen. - Insbesondere unbelegt: dass der Zustand beim Umzug tatsächlich erhalten bleibt (Tab, ein eingetippter Key, ein Suchbegriff), wie sich die durchgehend auf 400 pt gerechneten Abschnitte in einem breiteren Fenster verhalten, ob Sheets (`AddConnectionSheetView`, `ServerManagerSheetView`, die Settings-Sheets) im Fenster korrekt erscheinen, und das Scrollen bei höherem Inhalt. ## Grenzen / offen - Der Header hat jetzt ein Text-Button mehr. Bei 400 pt und drei installierten Agenten (Terminal- Buttons + Web-GUI + Index-Manager + Status-Text + „Entkoppeln“ + xmark) kann die Zeile eng werden — nicht ausprobiert, erster Kandidat für eine sichtbare Anpassung. - Die Ansicht ist auf 400 pt gerechnet; breites Layout ist nicht nachgesehen, sondern bewusst freigelassen (Nutzer-Entscheidung gegen eine Nachprüfung aller Abschnitte). - `idealPanelHeight` wird beim Andocken auf 0 gesetzt und neu gemessen — ein kurzes Nachziehen der Dropdown-Höhe beim ersten Öffnen danach ist möglich, nicht beobachtet. - Kein automatischer Test (UI-Code ohne Test-Infra in diesem Bereich).
alf 2026-10-27 17:19:33 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
iris/Iris#167
No description provided.