Files
dionotes-client/docs/research/nativer-ordner-dialog.md
MDietermann 8eb0e3325b docs: Rechercheergebnisse zu Ordner-Dialog und Konfigurationsablage uebernehmen
Beide Recherchen lagen bisher nur auf lokalen Wegwerf-Branches. Die
Kurzfassung steht an den Tickets #2 und #3, die Langfassungen sind mit
Quellenangaben und einer Trennung zwischen Belegtem und Vermutetem aber das,
worauf man zurueckkommt, wenn spaeter jemand fragt, warum unter Windows Local
statt Roaming gewaehlt wurde.

Damit sind research/ordner-dialog und research/config-ablageort ueberfluessig
und koennen geloescht werden.
2026-08-27 15:28:28 +02:00

537 lines
24 KiB
Markdown

# Nativer Ordner-Dialog unter Dioxus 0.7 Desktop
> Research zu Gitea-Issue #2. Stand: 2026-08-27.
> Recherchiert gegen Primärquellen (docs.rs, crates.io-API, Quellcode auf GitHub, offizielle Dioxus-Doku).
> **Nichts davon wurde kompiliert** — im Repo wurde bewusst keine Dependency hinzugefügt. Alles unter
> „Belegt" ist aus Signaturen/Quellcode/Manifesten abgeleitet, nicht aus einem laufenden Build.
## Kurzantwort
Die zugrundeliegende Bibliothek ist in beiden Fällen **`rfd` („Rusty File Dialogs"), `0.17.2`** —
aber es gibt **zwei Wege**, und der bequemere war zunächst übersehen worden:
**Weg A — ohne neue Dependency (offiziell dokumentiert).**
`input { r#type: "file", directory: true, onchange: … }` und der Pfad kommt aus
`evt.files()[0].path() -> PathBuf`. `dioxus-desktop` ruft dahinter intern
`rfd::AsyncFileDialog::pick_folder()` auf. Kostet **null** zusätzliche Abhängigkeiten.
Nachteil: braucht ein DOM-Element und einen echten Nutzer-Klick darauf — nicht aus beliebigem
Rust-Code heraus auslösbar.
**Weg B — `rfd` direkt als eigene Dependency.**
`rfd::AsyncFileDialog::new().pick_folder().await` in einem `onclick`-Handler. Der einzige Weg für
einen Dialog, den man **programmatisch** startet. Kostet praktisch nichts, weil `rfd 0.17.2` über
`dioxus-desktop` ohnehin schon im Dependency-Graph steht (siehe Abschnitt 2).
Abbruch liefert in beiden Fällen `None`. Unter Windows läuft beides **ohne Extra-Abhängigkeit und
ohne Feature-Flag** — nur Linux bringt zusätzliche Anforderungen mit.
> **Korrektur zur ersten Fassung dieses Dokuments:** Dort stand „Dioxus selbst bringt keine
> Datei-/Ordner-Dialog-API mit". **Das war falsch** — der `directory`-Attributweg (Weg A) ist in der
> offiziellen 0.7-Doku beschrieben. Richtig bleibt: `dioxus-desktop` **re-exportiert** `rfd` nicht,
> und `dioxus-sdk` hat kein Dialog-Modul.
---
## 1. Welche Crate?
### Belegt
- `rfd` ist eine plattformübergreifende Bibliothek für **native** Öffnen-/Speichern-Dialoge und
bietet „both asynchronous and synchronous APIs". Unterstützte Plattformen laut Crate-Doku:
Windows, macOS, Linux & BSDs (GTK3 oder XDG Desktop Portal), WASM32 (nur async).
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
- Aktuelle Version: **0.17.2**, veröffentlicht 2026-01-12, ~27 Mio. Downloads gesamt.
→ <https://crates.io/api/v1/crates/rfd>
- Repository: <https://github.com/PolyMeilex/rfd>
- „PickFolder" ist in der offiziellen Feature-Matrix der Crate für **Linux ✔ / Windows ✔ / macOS ✔**
ausgewiesen (nur Wasm32 ✖).
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
### Was es *nicht* gibt
- **Das offizielle `dioxus-sdk` hat kein Dialog-Modul.** Aktuell `dioxus-sdk 0.7.0` (2025-11-06),
Repo <https://github.com/DioxusLabs/sdk/>. Die vollständige Feature-Liste der Crate lautet:
`geolocation`, `notification`, `storage`, `sync`, `time`, `util`, `window` — kein `file`,
kein `dialog`, kein `fs`.
→ <https://crates.io/api/v1/crates/dioxus-sdk>
- **`dioxus-desktop` re-exportiert `rfd` nicht.** Die Crate *benutzt* `rfd` intern (siehe Weg A in
Abschnitt 3a), aber `mod file_upload;` ist privat und die Re-Export-Liste in `lib.rs` enthält nur
`tao`, `wry`, `muda`, `trayicon`, `launch`, `AssetRequest`, `Config`, `DesktopContext`,
`DesktopService`, `WryEventHandler`, `hooks::*`, Shortcut-Typen und `wry::RequestAsyncResponder`.
Ein `dioxus_desktop::rfd` gibt es also **nicht** — wer `rfd` direkt aufrufen will, muss es selbst
in die `Cargo.toml` schreiben.
→ <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/lib.rs>
### Korrigiert gegenüber der ersten Fassung
Ein reiner Webview-/HTML-Weg **existiert doch** und ist offiziell dokumentiert — er ist nur kein
„Webview-eigener" Dialog, sondern eine Dioxus-Eigenheit, die auf den nativen `rfd`-Dialog abgebildet
wird. Siehe Abschnitt 3a.
---
## 2. Kompatibilität — und: `rfd` ist bereits im Graph
### Belegt: die tatsächlich aufgelöste Version ist 0.7.10, nicht 0.7.1
`Cargo.toml` deklariert `dioxus = "0.7.1"`, das ist ein Caret-Requirement. Aufgelöst wird in
`Cargo.lock` tatsächlich:
| Crate | Version in `Cargo.lock` |
|---|---|
| `dioxus` | **0.7.10** |
| `dioxus-desktop` | **0.7.10** |
| `rfd` | **0.17.2** |
| `tao` | 0.34.8 |
| `wry` | 0.53.5 |
| `raw-window-handle` | 0.5.2 **und** 0.6.2 |
→ `Cargo.lock` im Repo-Root (Zeilen 2337, 2493, 5847, 6521, 5716/5722).
**Das ist der wichtigste praktische Befund:** `dioxus-desktop 0.7.10` hat `rfd` als **nicht-optionale**
Dependency mit `req = "^0.17.2"`, `default-features = false`, `features = ["xdg-portal"]`, unter
`cfg(any(windows, macos, linux, dragonfly, freebsd, netbsd, openbsd))`.
→ <https://crates.io/api/v1/crates/dioxus-desktop/0.7.10/dependencies>
Ein `rfd = "0.17"` in unserer `Cargo.toml` fügt dem Graph also **keinen neuen Crate hinzu** und
kostet **keinen zusätzlichen Compile-Unit** — es macht nur den bereits gebauten Knoten sichtbar.
Cargo vereinigt die Features additiv; unsere Default-Features von `rfd` sind ohnehin `xdg-portal`,
also genau das, was `dioxus-desktop` schon setzt.
*(Historische Notiz: `dioxus-desktop 0.7.1` hatte noch `rfd ^0.15.3`. Der Sprung auf 0.17 kam
zwischen 0.7.1 und 0.7.10. Für uns zählt 0.7.10.)*
### Belegt: keine Versionskonflikte
- `dioxus-desktop 0.7.1` zieht **`tao ^0.34.0`** und **`wry ^0.53.5`**.
→ <https://crates.io/api/v1/crates/dioxus-desktop/0.7.1/dependencies>
- `rfd 0.17.2` hat **keinerlei Abhängigkeit auf Dioxus, wry oder tao**. Die einzige gemeinsame
Berührungsfläche ist `raw-window-handle ^0.6` (unbedingte Dependency von `rfd`).
→ <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
- `tao 0.34.x` implementiert `HasWindowHandle` und `HasDisplayHandle` aus `raw-window-handle 0.6`
für `tao::window::Window`, hinter dem Feature `rwh_06`.
→ <https://docs.rs/tao/0.34.4/tao/window/struct.Window.html>
- `rwh_06` ist ein **Default-Feature** von `tao` → <https://docs.rs/crate/tao/0.34.4/features>,
und `dioxus-desktop` schaltet Default-Features nicht ab (`tao = { workspace = true, features = ["rwh_05"] }`
— additiv, kein `default-features = false`).
→ <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/Cargo.toml>
**Fazit:** Es gibt keinen Versionskonflikt. `rfd` und `dioxus-desktop` teilen sich `raw-window-handle 0.6`
und passen semver-kompatibel zusammen.
### Vermutung (nicht verifiziert)
- Daraus folgt *rechnerisch*, dass `AsyncFileDialog::set_parent(&window)` mit dem Dioxus-Fenster
typprüfen sollte: `use_window()` liefert `DesktopContext = Rc<DesktopService>`
(<https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/type.DesktopContext.html>,
<https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/fn.use_window.html>), `DesktopService`
implementiert `Deref<Target = tao::window::Window>`
(<https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/struct.DesktopService.html>), und
`set_parent` nimmt `&W where W: HasWindowHandle + HasDisplayHandle + ?Sized`
(<https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>).
**Das ist nicht kompiliert worden.** Für einen ersten Wurf ist `set_parent` optional — ohne
Parent erscheint der Dialog als eigenständiges Fenster statt als besitzer-modaler Dialog.
---
## 3a. Weg A — der offizielle Weg ohne neue Dependency
### Belegt
Die offizielle Dioxus-0.7-Doku beschreibt Ordner-Auswahl über ein `directory`-Attribut am
File-Input:
> „Dioxus will map this attribute to browser specific attributes, because there is no standardized
> way to allow a directory to be selected."
```rust
input {
type: "file",
// Select a folder by setting the directory attribute
directory: true,
onchange: move |evt| {
for file in evt.files() {
println!("{}", file.name());
}
}
}
```
→ <https://dioxuslabs.com/learn/0.7/essentials/basics/event_handlers/>
Auf dem **Desktop** ist das kein Browser-Dialog, sondern feuert intern exakt den nativen
`rfd`-Ordner-Dialog:
```rust
// packages/desktop/src/file_upload.rs
if self.directory {
if self.multiple {
dialog.pick_folders().await
} else {
dialog.pick_folder().await
}
}
pub struct DesktopFileData(pub(crate) PathBuf);
fn path(&self) -> PathBuf { self.0.clone() }
```
→ <https://github.com/DioxusLabs/dioxus/blob/v0.7.10/packages/desktop/src/file_upload.rs>
Und der **absolute Pfad** ist öffentlich abgreifbar — `FileData::path()` ist Teil der
`dioxus-html`-API:
```rust
pub fn path(&self) -> PathBuf
```
→ <https://docs.rs/dioxus-html/0.7.10/dioxus_html/struct.FileData.html>
### Snippet für Weg A
```rust
use dioxus::prelude::*;
use std::path::PathBuf;
#[component]
fn WorkspaceOrdnerWahlOhneDependency() -> Element {
let mut ordner = use_signal(|| None::<PathBuf>);
let label = ordner()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "Noch kein Ordner gewählt".to_string());
rsx! {
input {
r#type: "file",
directory: true,
onchange: move |evt| {
// Bei Abbruch ist die Liste leer -> kein Event-File
if let Some(file) = evt.files().first() {
ordner.set(Some(file.path()));
}
},
}
p { "{label}" }
}
}
```
### Grenzen von Weg A
- Braucht ein **DOM-Element** und einen echten Nutzer-Klick darauf. Ein Ordner-Dialog, den man aus
Rust heraus programmatisch öffnet (z. B. aus einem Menüeintrag, einem Shortcut oder nach einem
anderen Ereignis), ist damit **nicht** möglich.
- Das Styling ist ein `<input type="file">` — für einen hübschen Button muss man das Element
verstecken und per Label/JS triggern.
- **Vermutung, nicht verifiziert:** wie sich `evt.files()` bei Abbruch genau verhält (leere Liste
vs. gar kein `onchange`-Event). Das obige `if let Some(...)` ist gegen beides robust.
---
## 3b. Blockierend oder async? — Für Weg B im `onclick` zwingend **async**
### Belegt: die Signaturen
`rfd` hat zwei parallele Typen. Beide bauen per Builder, unterscheiden sich nur im Rückgabetyp:
```rust
// Synchron — rfd::FileDialog
pub fn pick_folder(self) -> Option<PathBuf>;
pub fn pick_folders(self) -> Option<Vec<PathBuf>>;
// Asynchron — rfd::AsyncFileDialog
pub fn pick_folder(self) -> impl Future<Output = Option<FileHandle>>;
pub fn pick_folders(self) -> impl Future<Output = Option<Vec<FileHandle>>>;
```
→ <https://docs.rs/rfd/0.17.2/rfd/struct.FileDialog.html>,
<https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>
### Belegt: warum die synchrone Variante in Dioxus schadet
- Die synchrone Windows-Implementierung fährt die modale COM-Dialogschleife **auf dem aufrufenden
Thread**:
```rust
fn pick_folder(self) -> Option<PathBuf> {
fn run(opt: FileDialog) -> Result<PathBuf> {
init_com(|| {
let dialog = IDialog::build_pick_folder(&opt)?;
dialog.show()?; // blockiert hier bis der Nutzer fertig ist
dialog.get_result()
})?
}
run(self).ok()
}
```
→ <https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
- Dioxus-Event-Handler und via `spawn` gestartete Tasks laufen auf **demselben Thread** wie die
Event-Loop: `pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task` — **kein `Send`-Bound**,
d. h. die Futures können den Thread gar nicht wechseln.
→ <https://docs.rs/dioxus/0.7.1/dioxus/prelude/fn.spawn.html>
Ein `FileDialog::pick_folder()` direkt im `onclick` blockiert also die tao-Event-Loop → Fenster
friert ein, solange der Dialog offen ist.
- Die Crate-Doku selbst empfiehlt, Dialoge auf dem Main-Thread zu *starten*
(„It is also recommended to spawn dialogs on your main thread") — genau das tut man mit der
async-Variante aus einem Dioxus-Handler heraus.
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
### Belegt: warum die async-Variante unter Windows *nicht* blockiert
Die Windows-async-Variante lagert den modalen Dialog auf einen eigenen OS-Thread aus und liefert das
Ergebnis über einen Channel zurück:
```rust
// async_thread(..) — Kern der Implementierung
std::thread::spawn(move || {
tx.send(f()).ok();
});
// pick_folder_async
async_thread(move || Self::pick_folder(self).map(FileHandle::wrap))
```
→ <https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
Die Dioxus-Event-Loop läuft weiter; das `.await` gibt den Main-Thread frei, bis der Channel liefert.
**Kein Tokio-/async-std-Feature nötig** — `rfd` hat keine Runtime-Features, die Implementierung nutzt
`std::thread` plus Channel. → <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
(keine Runtime-Dependency; nur `log`, `raw-window-handle` unbedingt)
Nebenbedingung aus dem offiziellen `rfd`-Beispiel: der Dialog soll **auf dem Main-Thread gestartet**
(`pick_*()` aufgerufen) und darf anderswo awaited werden — „Spawn dialog on main thread / Await
somewhere else". → <https://github.com/PolyMeilex/rfd/blob/master/examples/async.rs>
Im Dioxus-Fall passiert **beides** auf dem Main-Thread (`spawn` hat kein `Send`-Bound), die Bedingung
ist damit erfüllt.
---
## 4. Was kommt bei Abbruch zurück?
### Belegt
**`None`.** Sowohl `FileDialog::pick_folder() -> Option<PathBuf>` als auch
`AsyncFileDialog::pick_folder() -> impl Future<Output = Option<FileHandle>>` liefern bei Abbruch
`None`. Es gibt keinen `Result`-Typ und keine Fehlerunterscheidung „abgebrochen vs. fehlgeschlagen" —
ein technisch fehlgeschlagener Dialog ist von einem abgebrochenen nicht unterscheidbar (die
Windows-Impl macht am Ende `run(self).ok()`, verwirft den Fehler also).
→ <https://docs.rs/rfd/0.17.2/rfd/struct.FileDialog.html>,
<https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>,
<https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
Aus dem `FileHandle` kommt man so an den Pfad:
```rust
pub fn path(&self) -> &Path; // existiert nicht unter WASM32
pub fn file_name(&self) -> String;
// zusätzlich: impl From<FileHandle> for PathBuf
```
→ <https://docs.rs/rfd/0.17.2/rfd/struct.FileHandle.html>
---
## 5. Plattformen und Feature-Flags
### Belegt: Windows — out of the box, ja
Einzige Windows-Dependency von `rfd 0.17.2`:
| Target | Crate | Version | Features |
|---|---|---|---|
| `cfg(target_os = "windows")` | `windows-sys` | `^0.61` | `Win32_Foundation`, `Win32_System_Com`, `Win32_UI_Shell_Common`, `Win32_UI_Shell`, `Win32_UI_WindowsAndMessaging` |
→ <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
`windows-sys` ist reines Rust-Binding auf die Systembibliotheken — **keine C-Header, kein vcpkg,
kein Build-Tool, kein Feature-Flag**. Zusammen mit der Implementierung über `IFileOpenDialog`/COM
(siehe `init_com` / `IDialog::build_pick_folder` oben) heißt das:
**`rfd = "0.17"` in die `Cargo.toml`, fertig.**
Optional unter Windows: das Feature `common-controls-v6` — betrifft ausschließlich
**Message-Dialoge** (`TaskDialogIndirect` aus ComCtl32.dll v6, für eigene Button-Texte) und braucht
zusätzlich ein Application-Manifest. **Für Ordner-Dialoge irrelevant.**
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
### Belegt: macOS
Reine Rust/objc2-Dependencies (`objc2`, `objc2-app-kit`, `objc2-foundation`, `block2`, `dispatch2`),
keine Feature-Flags nötig. Einschränkung aus der Crate-Doku:
> „macOS async dialogs require an `NSApplication` instance, so the dialog is only truly async when
> opened in windowed environment like `winit` or `SDL2`. Otherwise, it will fallback to sync dialog."
Eine Dioxus-Desktop-App **ist** eine windowed App (tao/`NSApplication`), die Bedingung ist also
erfüllt. → <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>,
<https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
### Belegt: Linux — hier steckt der einzige echte Stolperstein
Zwei Backends, per Cargo-Feature wählbar:
- **`xdg-portal` (Default)** — XDG Desktop Portal über D-Bus. Braucht zur **Laufzeit** `libdbus`
(fällt sonst auf `zenity` zurück) **und** ein installiertes Portal-Backend (GTK, GNOME oder KDE).
Das wlroots-Portal-Backend implementiert die benötigte D-Bus-API **nicht**.
- **`gtk3`** — `default-features = false, features = ["gtk3"]`. Braucht die GTK3-**Entwicklungs**-
Header schon zur **Build**-Zeit (`libgtk-3-dev` / `gtk3-devel` / `pacman -S gtk3`).
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
**Also: nein, „derselbe Aufruf überall" gilt für den *Rust-Code*, aber nicht für das *Packaging*.**
Der Aufruf im Code ist auf allen drei Plattformen identisch; Linux-Builds brauchen zusätzlich eine
Aussage darüber, welches Backend gewählt wird und was beim Nutzer installiert sein muss.
Für DioNotes auf Windows 11 ist das derzeit kein Thema.
---
## 6. Lauffähiges Beispiel-Snippet für Weg B (Dioxus 0.7 API)
`Cargo.toml` — **nicht angewendet**, gehört in ein Implementierungs-Ticket:
```toml
[dependencies]
rfd = "0.17" # bereits transitiv via dioxus-desktop 0.7.10 im Lock-File
```
Komponente:
```rust
use dioxus::prelude::*;
use std::path::PathBuf;
#[component]
fn WorkspaceOrdnerWahl() -> Element {
let mut ordner = use_signal(|| None::<PathBuf>);
// Anzeige-Text vorberechnen, damit im rsx! kein Ausdruck nötig ist
let label = ordner()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "Noch kein Ordner gewählt".to_string());
rsx! {
button {
// Async-Closures in Event-Handlern werden von Dioxus automatisch gespawnt.
onclick: move |_| async move {
let auswahl = rfd::AsyncFileDialog::new()
.set_title("Workspace-Ordner wählen")
.pick_folder()
.await;
match auswahl {
// FileHandle::path() -> &Path
Some(handle) => ordner.set(Some(handle.path().to_path_buf())),
// Nutzer hat abgebrochen -> None; Zustand bewusst unverändert lassen
None => {}
}
},
"Ordner wählen…"
}
p { "{label}" }
}
}
```
Belege für die Dioxus-Seite des Snippets:
- Async-Closure direkt als Event-Handler: „Async closures passed to event handlers are automatically
spawned" — offizielles Beispiel in der 0.7-Doku.
→ <https://dioxuslabs.com/learn/0.7/llms-full.txt> (Abschnitt „Use async closures in event handlers")
- Alternative, falls die Async-Closure irgendwo Ärger macht — `spawn` explizit:
```rust
onclick: move |_| {
spawn(async move {
if let Some(h) = rfd::AsyncFileDialog::new().pick_folder().await {
ordner.set(Some(h.path().to_path_buf()));
}
});
},
```
→ <https://docs.rs/dioxus/0.7.1/dioxus/prelude/fn.spawn.html>,
<https://dioxuslabs.com/learn/0.7/llms-full.txt> (Abschnitt „Spawn background tasks in event handlers")
- `use_signal` / `.set()` statt `use_state`: entspricht der 0.7-API in `AGENTS.md`.
### Optionale Erweiterung (Vermutung, nicht kompiliert)
Damit der Dialog besitzer-modal am App-Fenster hängt:
```rust
let window = use_window(); // DesktopContext = Rc<DesktopService>, Deref -> tao::Window
// ...
rfd::AsyncFileDialog::new()
.set_parent(&*window) // &DesktopService --deref--> &tao::window::Window
.set_title("Workspace-Ordner wählen")
.pick_folder()
.await
```
Typtheoretisch geht das auf (siehe Abschnitt 2), **aber ungetestet**. Zwei offene Punkte:
1. Ob die Deref-Kette bis `tao::window::Window` vom Compiler an `set_parent`s Bound
`W: HasWindowHandle + HasDisplayHandle + ?Sized` angepasst wird, oder ob ein expliziter
`&*(*window)` / Zwischenschritt nötig ist.
2. Windows führt den async-Dialog auf einem **anderen** Thread aus (Abschnitt 3). Ein Parent-HWND
über Thread-Grenzen hinweg ist unter Win32 grundsätzlich zulässig, das Modalitäts-Verhalten
(Deaktivierung des Owner-Fensters) sollte man aber praktisch prüfen.
Empfehlung: **erst ohne `set_parent` implementieren**, das ist die belegte Variante.
---
## 7. Empfehlung für DioNotes
Für „Ordner beim Anlegen eines Workspace wählen" ist die Auswahl eine reine UX-Frage:
- Wenn der Dialog aus einem **sichtbaren Button/Input im Formular** kommt → **Weg A**,
null neue Dependencies, offiziell dokumentiert.
- Wenn der Dialog **programmatisch** aufgehen soll (Menü, Shortcut, Folgeschritt eines Wizards)
→ **Weg B** mit `rfd = "0.17"`. Kostet dank des schon vorhandenen Lock-Eintrags praktisch nichts.
Beide landen unter Windows im selben nativen `IFileOpenDialog`.
## 8. Zusammenfassung der offenen Punkte
| Punkt | Status |
|---|---|
| Crate + Version | **belegt** — `rfd 0.17.2` |
| `rfd` bereits transitiv im Lock-File (via dioxus-desktop 0.7.10) | **belegt** (`Cargo.lock` + crates.io) |
| Projekt löst `dioxus 0.7.1` real auf **0.7.10** auf | **belegt** (`Cargo.lock`) |
| Weg A: `directory: true` + `FileData::path()` | **belegt** (offizielle Doku + docs.rs + Quellcode) |
| Weg A nutzt intern `rfd::…::pick_folder()` | **belegt** (`packages/desktop/src/file_upload.rs`) |
| Kein Konflikt mit tao 0.34 / wry 0.53 | **belegt** (Manifeste) |
| async statt sync in `onclick` (Weg B) | **belegt** (Signaturen + Quellcode + `spawn`-Signatur ohne `Send`) |
| Abbruch → `None` | **belegt** |
| Windows out of the box, kein Feature-Flag | **belegt** (nur `windows-sys ^0.61`) |
| Linux braucht Portal-Backend bzw. `gtk3`-Header | **belegt** |
| `dioxus-sdk` hat kein Dialog-Modul | **belegt** (Feature-Liste 0.7.0) |
| `dioxus_desktop::rfd` existiert nicht (kein Re-Export) | **belegt** (`lib.rs`) |
| `set_parent` mit dem Dioxus-Fenster | **Vermutung** — plausibel, nicht kompiliert |
| `evt.files()`-Verhalten bei Abbruch in Weg A | **Vermutung** — Snippet ist gegen beide Varianten robust |
| Verhalten bei Netzlaufwerken / UNC-Pfaden | **nicht recherchiert** |
| Verhalten unter `dx serve` / Hot-Reload | **nicht recherchiert** |
## Quellenliste
- rfd Crate-Doku (Quellcode-Ansicht der `//!`-Doku): <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
- `rfd::FileDialog`: <https://docs.rs/rfd/0.17.2/rfd/struct.FileDialog.html>
- `rfd::AsyncFileDialog`: <https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>
- `rfd::FileHandle`: <https://docs.rs/rfd/0.17.2/rfd/struct.FileHandle.html>
- rfd Windows-Backend (Quellcode): <https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
- rfd Crate-Metadaten: <https://crates.io/api/v1/crates/rfd>
- rfd async-Beispiel: <https://github.com/PolyMeilex/rfd/blob/master/examples/async.rs>
- dioxus-sdk Crate-Metadaten (Feature-Liste): <https://crates.io/api/v1/crates/dioxus-sdk>
- rfd 0.17.2 Dependencies: <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
- dioxus-desktop 0.7.1 Dependencies: <https://crates.io/api/v1/crates/dioxus-desktop/0.7.1/dependencies>
- dioxus-desktop **0.7.10** Dependencies (die real aufgelöste Version): <https://crates.io/api/v1/crates/dioxus-desktop/0.7.10/dependencies>
- dioxus-desktop `file_upload.rs` (interner rfd-Aufruf) @ v0.7.10: <https://github.com/DioxusLabs/dioxus/blob/v0.7.10/packages/desktop/src/file_upload.rs>
- dioxus-desktop `lib.rs` (Re-Export-Liste) @ v0.7.1: <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/lib.rs>
- `dioxus_html::FileData` (`path()`): <https://docs.rs/dioxus-html/0.7.10/dioxus_html/struct.FileData.html>
- Dioxus 0.7 Doku, `directory`-Attribut: <https://dioxuslabs.com/learn/0.7/essentials/basics/event_handlers/>
- dioxus-desktop `Cargo.toml` @ v0.7.1: <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/Cargo.toml>
- `DesktopService`: <https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/struct.DesktopService.html>
- `DesktopContext`: <https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/type.DesktopContext.html>
- `use_window`: <https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/fn.use_window.html>
- `dioxus::prelude::spawn`: <https://docs.rs/dioxus/0.7.1/dioxus/prelude/fn.spawn.html>
- tao 0.34.4 `Window` (rwh-Impls): <https://docs.rs/tao/0.34.4/tao/window/struct.Window.html>
- tao 0.34.4 Feature-Liste: <https://docs.rs/crate/tao/0.34.4/features>
- Dioxus 0.7 Doku (Async, Desktop): <https://dioxuslabs.com/learn/0.7> / <https://dioxuslabs.com/learn/0.7/llms-full.txt>