docs(plan): fix Phase 8 zbus runtime path (no glib feature exists)

zbus 4.4 does not expose a glib feature; my earlier draft was wrong about
that. Switch the plan to use the workspace tokio config and install a
multi-thread tokio runtime in main.rs, with the runtime guard kept alive for
the lifetime of app.run(). glib::MainContext::spawn_local still drives the
widget-touching closures; tokio's reactor wakes them.

Also: hoist futures-util into the Task 1 dep bundle so Task 6 doesn't need
a separate Cargo.toml edit.
This commit is contained in:
michael
2026-04-27 09:05:58 -07:00
parent 5294ef61ce
commit a17f70c4ac

View File

@@ -4,12 +4,15 @@
**Goal:** Land a working `authforge` GUI that lists the current user's enrolled FIDO2 credentials, enrolls a new key with a touch-prompt modal driven by the daemon's `EnrollmentSucceeded`/`EnrollmentFailed` signals, removes a credential, and degrades gracefully when the daemon is unreachable. **Goal:** Land a working `authforge` GUI that lists the current user's enrolled FIDO2 credentials, enrolls a new key with a touch-prompt modal driven by the daemon's `EnrollmentSucceeded`/`EnrollmentFailed` signals, removes a credential, and degrades gracefully when the daemon is unreachable.
**Architecture:** Single-window GTK4 + libadwaita app on the user's session, talking to the daemon on the **system bus** at `io.dangerousthings.AuthForge / /io/dangerousthings/AuthForge / io.dangerousthings.AuthForge1`. zbus uses its **glib** integration (`features = ["glib"]`) instead of the workspace tokio config, so all async runs on the GTK main thread via `glib::MainContext::spawn_local`. No tokio dep in the GUI process. **Architecture:** Single-window GTK4 + libadwaita app on the user's session, talking to the daemon on the **system bus** at `io.dangerousthings.AuthForge / /io/dangerousthings/AuthForge / io.dangerousthings.AuthForge1`. zbus uses the workspace tokio runtime; a multi-thread tokio runtime is installed at app startup and its enter-guard is held for the lifetime of `app.run()`. Closures that touch widgets dispatch async work via `glib::MainContext::spawn_local`, which polls the future on the GTK main thread; tokio's reactor (running on its own worker threads) wakes the future when zbus I/O is ready, and glib re-polls.
> **Plan note (2026-04-27):** an earlier draft of this plan claimed zbus 4 has a `glib` feature for runtime integration. It does not — zbus 4.4's features are `async-io` (default), `tokio`, `bus-impl`, `chrono`, `p2p`, `time`, `url`, `uuid`, `vsock`. We use `tokio` to match the rest of the workspace.
**Tech Stack:** **Tech Stack:**
- `gtk4-rs` 0.8 (already pinned in `gui/Cargo.toml`). - `gtk4-rs` 0.8 (already pinned in `gui/Cargo.toml`).
- `libadwaita` 0.6 (already pinned). - `libadwaita` 0.6 (already pinned).
- `zbus = { version = "4", default-features = false, features = ["glib"] }`**not** `workspace = true`; the workspace dep is tokio-flavored and would conflict. - `zbus = { workspace = true }` — workspace config is `default-features = false, features = ["tokio", "p2p"]`.
- `tokio = { workspace = true }` — needed for the runtime guard that hosts zbus's tokio executor.
- `authforge-common = { path = "../common" }` for the shared `Credential` wire type. - `authforge-common = { path = "../common" }` for the shared `Credential` wire type.
**Reference:** **Reference:**
@@ -38,18 +41,20 @@
**Step 1:** Append to `gui/Cargo.toml` `[dependencies]`: **Step 1:** Append to `gui/Cargo.toml` `[dependencies]`:
```toml ```toml
zbus = { version = "4", default-features = false, features = ["glib"] } zbus = { workspace = true }
tokio = { workspace = true }
futures-util = { workspace = true }
``` ```
(Do **not** use `workspace = true`. The workspace zbus is tokio-flavored — the GUI needs the glib runtime feature, which is mutually exclusive at the zbus crate level.) The tokio runtime guard installed in Task 4 (`main.rs`) is what makes zbus's tokio-flavored futures actually run when `glib::MainContext::spawn_local` polls them. `futures-util` shows up here so Task 6's `select!`-style race in `enroll_dialog.rs` doesn't need a follow-up Cargo.toml edit.
**Step 2:** `cargo build -p authforge-gui`. Expected: clean compile (no usages yet, but the dep resolves). **Step 2:** `cargo build -p authforge-gui`. Expected: clean compile (no usages yet, but the deps resolve).
**Step 3:** Commit. **Step 3:** Commit.
```bash ```bash
git add gui/Cargo.toml Cargo.lock git add gui/Cargo.toml Cargo.lock
git commit --no-gpg-sign -m "chore(gui): add zbus glib feature for D-Bus client" git commit --no-gpg-sign -m "chore(gui): add zbus + tokio + futures-util deps for D-Bus client"
``` ```
--- ---
@@ -357,6 +362,16 @@ mod keys_page;
const APP_ID: &str = "io.dangerousthings.AuthForge"; const APP_ID: &str = "io.dangerousthings.AuthForge";
fn main() -> glib::ExitCode { fn main() -> glib::ExitCode {
// Multi-thread tokio runtime hosts zbus's tokio-flavored futures. The
// `_guard` keeps the runtime context active on this thread so calls like
// `zbus::Connection::system().await` from within `glib::MainContext::
// spawn_local` closures don't panic with "no current reactor".
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("build tokio runtime");
let _guard = runtime.enter();
let app = adw::Application::builder().application_id(APP_ID).build(); let app = adw::Application::builder().application_id(APP_ID).build();
app.connect_activate(|app| { app.connect_activate(|app| {
let win = adw::ApplicationWindow::builder() let win = adw::ApplicationWindow::builder()
@@ -624,13 +639,7 @@ fn show_failure(dialog: &adw::AlertDialog, msg: &str) {
} }
``` ```
**Step 3:** Add `futures-util` to `gui/Cargo.toml`: **Step 3:** Replace the `start_enroll` body in `gui/src/keys_page.rs`:
```toml
futures-util = { workspace = true }
```
**Step 4:** Replace the `start_enroll` body in `gui/src/keys_page.rs`:
```rust ```rust
async fn start_enroll(self: &Rc<Self>) { async fn start_enroll(self: &Rc<Self>) {
@@ -648,12 +657,12 @@ futures-util = { workspace = true }
} }
``` ```
**Step 5:** `cargo build -p authforge-gui && cargo clippy -p authforge-gui --all-targets -- -D warnings`. Clean. **Step 4:** `cargo build -p authforge-gui && cargo clippy -p authforge-gui --all-targets -- -D warnings`. Clean.
**Step 6:** Commit. **Step 5:** Commit.
```bash ```bash
git add gui/src/enroll_dialog.rs gui/src/keys_page.rs gui/src/main.rs gui/Cargo.toml Cargo.lock git add gui/src/enroll_dialog.rs gui/src/keys_page.rs gui/src/main.rs
git commit --no-gpg-sign -m "feat(gui): TouchDialog modal racing EnrollOwn call against EnrollmentFailed signal" git commit --no-gpg-sign -m "feat(gui): TouchDialog modal racing EnrollOwn call against EnrollmentFailed signal"
``` ```