refactor: move presentation shells to _reference/, shrink workspace

Move nuncio-tui, nuncio-gui (+src-tauri, ui), and nuncio-mcp out of the Cargo
workspace into _reference/ (git renames; history preserved), retained for the
Phase 5 native client-repo extraction. Remove the unused nuncio-mcp dev-dep
from nunciod and rework daemon_e2e_test to drive the real nuncio-cli
HeadlessRunner (more coverage, no archived-shell dependency). Workspace is now
engine + daemon + reference CLI (8 members); build/clippy/tests green.

Refs #145
This commit is contained in:
James Maes committed 2026-07-26 21:01:05 -05:00
1 parent 690c32c406
commit 7c0465cf35
60 files changed
+154 -3495

No files matched your search

Generated
+34 -3457
View File
File diff suppressed because it is too large. Load diff
-4
View File
@@ -5,11 +5,7 @@ members = [
"crates/nuncio-mail",
"crates/nuncio-cal",
"crates/nuncio-store",
"crates/nuncio-tui",
"crates/nuncio-gui",
"crates/nuncio-gui/src-tauri",
"crates/nuncio-cli",
"crates/nuncio-mcp",
"crates/nunciod",
"crates/nuncio-filter",
"crates/nuncio-contacts",
+40
View File
@@ -0,0 +1,40 @@
# `_reference/` — archived pre-gRPC presentation shells
This directory holds three presentation shells that were removed from the
Cargo workspace (backlog **0.D.2**, GH-145) as part of shrinking the
workspace to just the engine, the daemon, and the reference CLI:
- `nuncio-tui` — the Ratatui terminal UI shell
- `nuncio-gui` — the Rust GUI core plus its `src-tauri` Tauri desktop shell
and its `ui/` React/Vite frontend
- `nuncio-mcp` — the MCP (Model Context Protocol) server shell for LLM tool
access
They were built directly against the in-process `nuncio-core`/`nuncio-mail`/
`nuncio-cal`/`nuncio-store`/`nuncio-filter`/`nuncio-contacts` library APIs,
from the pre-gRPC phase of the project. Per the target architecture in
[`docs/ROADMAP.md`](../docs/ROADMAP.md), `nunciod` is moving to publish a
versioned gRPC API over loopback, and every presentation shell other than
the in-repo `nuncio-cli` reference client is planned to live in its own
separate native client repository (**Phase 5 — Client projects**):
`nuncio-tui`, `nuncio-gui-macos`, `nuncio-gui-windows`, `nuncio-mcp`, etc.
**This code is retained intact, not deleted**, so it can be mined for UI/UX
ideas, keybindings, view layout, and MCP tool-surface design when those
Phase 5 client repos are built against the new gRPC API. It is **not**
built, checked, or tested by the root Cargo workspace — none of these
directories are members of the workspace `Cargo.toml`, so
`cargo build --workspace`, `cargo check-all`, and `cargo test --workspace`
never touch them.
Each crate's internal `path = "../..."` dependencies on the engine crates
have been updated to point at `../../crates/<name>` (e.g.
`_reference/nuncio-tui/Cargo.toml` now points at `../../crates/nuncio-core`)
so the crate sources still resolve correctly relative to their new location.
They are **not guaranteed to build standalone** as-is: they still declare
`*.workspace = true` fields (`version`, `edition`, `lints`, and workspace
`[dependencies]` aliases) that only resolve inside a Cargo workspace, and
they are not their own workspace root. Restoring a standalone build (for
mining or resurrection) will require either vendoring those workspace
values directly into each `Cargo.toml`, or giving `_reference/` its own
`[workspace]` root.
@@ -10,10 +10,10 @@ repository.workspace = true
workspace = true
[dependencies]
nuncio-core = { path = "../nuncio-core" }
nuncio-mail = { path = "../nuncio-mail" }
nuncio-cal = { path = "../nuncio-cal" }
nuncio-store = { path = "../nuncio-store" }
nuncio-core = { path = "../../crates/nuncio-core" }
nuncio-mail = { path = "../../crates/nuncio-mail" }
nuncio-cal = { path = "../../crates/nuncio-cal" }
nuncio-store = { path = "../../crates/nuncio-store" }
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
@@ -10,7 +10,7 @@ repository.workspace = true
workspace = true
[dependencies]
nuncio-core = { path = "../../nuncio-core" }
nuncio-core = { path = "../../../crates/nuncio-core" }
nuncio-gui = { path = ".." }
tauri = { version = "2.0", features = [] }
tauri-plugin-updater = "2.0"
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -10,12 +10,12 @@ repository.workspace = true
workspace = true
[dependencies]
nuncio-core = { path = "../nuncio-core" }
nuncio-mail = { path = "../nuncio-mail" }
nuncio-cal = { path = "../nuncio-cal" }
nuncio-store = { path = "../nuncio-store" }
nuncio-filter = { path = "../nuncio-filter" }
nuncio-contacts = { path = "../nuncio-contacts" }
nuncio-core = { path = "../../crates/nuncio-core" }
nuncio-mail = { path = "../../crates/nuncio-mail" }
nuncio-cal = { path = "../../crates/nuncio-cal" }
nuncio-store = { path = "../../crates/nuncio-store" }
nuncio-filter = { path = "../../crates/nuncio-filter" }
nuncio-contacts = { path = "../../crates/nuncio-contacts" }
tokio = { workspace = true, features = ["full"] }
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -10,11 +10,11 @@ repository.workspace = true
workspace = true
[dependencies]
nuncio-core = { path = "../nuncio-core" }
nuncio-mail = { path = "../nuncio-mail" }
nuncio-cal = { path = "../nuncio-cal" }
nuncio-store = { path = "../nuncio-store" }
nuncio-filter = { path = "../nuncio-filter" }
nuncio-core = { path = "../../crates/nuncio-core" }
nuncio-mail = { path = "../../crates/nuncio-mail" }
nuncio-cal = { path = "../../crates/nuncio-cal" }
nuncio-store = { path = "../../crates/nuncio-store" }
nuncio-filter = { path = "../../crates/nuncio-filter" }
tokio.workspace = true
ratatui.workspace = true
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-1
View File
@@ -28,4 +28,3 @@ tempfile.workspace = true
[dev-dependencies]
nuncio-cli = { path = "../nuncio-cli" }
nuncio-mcp = { path = "../nuncio-mcp" }
+64 -17
View File
@@ -1,11 +1,29 @@
//! End-to-End multi-interface daemon integration test suite.
//! End-to-End multi-shell daemon integration test suite.
//!
//! Exercises the `nunciod` daemon end-to-end over the real IPC socket using
//! `nuncio-core`'s `IpcClient`/`IpcDaemonServer`, and independently exercises
//! the in-workspace `nuncio-cli` presentation shell (`HeadlessRunner`) to
//! confirm a real reference client shell continues to function alongside
//! the daemon. This suite intentionally has no dependency on the archived
//! presentation shells that were moved to `_reference/` (nuncio-tui,
//! nuncio-gui, nuncio-mcp) as part of shrinking the workspace to the
//! engine + daemon + reference CLI (backlog 0.D.2 / GH-145).
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use nuncio_cli::{Commands, HeadlessRunner, MailSubcommand, SystemSubcommand};
use nuncio_core::ipc::{IpcClient, IpcDaemonServer};
use nuncio_core::{CoreCommand, EventBus};
use std::sync::Arc;
/// Boots a real `nunciod` `IpcDaemonServer` and drives it concurrently from
/// three independent `IpcClient` socket connections (standing in for any
/// number of simultaneous thin presentation shells talking JSON-RPC over
/// TCP), then separately drives the real `nuncio-cli` `HeadlessRunner`
/// reference shell to confirm it still executes full noun+verb commands
/// correctly. This preserves the daemon-boot + real-client-round-trip
/// coverage the previous multi-shell test provided, without requiring the
/// archived `nuncio-mcp` crate.
#[tokio::test]
async fn e2e_multi_shell_daemon_concurrency_test() {
let event_bus = Arc::new(EventBus::new());
@@ -16,16 +34,17 @@ async fn e2e_multi_shell_daemon_concurrency_test() {
let _ = server.run_server().await;
});
// Simulate 3 concurrent shells connecting to nunciod daemon
let client_cli = IpcClient::new(addr);
let client_tui = IpcClient::new(addr);
let client_mcp = IpcClient::new(addr);
// Simulate 3 concurrent thin-shell connections to the nunciod daemon
// over the real IPC socket.
let client_a = IpcClient::new(addr);
let client_b = IpcClient::new(addr);
let client_c = IpcClient::new(addr);
// Give server time to bind TCP socket
// Give server time to bind TCP socket.
let mut ping_res = false;
for _ in 0..15 {
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
if let Ok(res) = client_cli.ping().await {
if let Ok(res) = client_a.ping().await {
ping_res = res;
if ping_res {
break;
@@ -33,27 +52,55 @@ async fn e2e_multi_shell_daemon_concurrency_test() {
}
}
assert!(ping_res);
assert!(client_tui.ping().await.expect("tui ping"));
assert!(client_mcp.ping().await.expect("mcp ping"));
assert!(client_b.ping().await.expect("shell b ping"));
assert!(client_c.ping().await.expect("shell c ping"));
// 2. MCP shell issues sync command
let sync_res = client_mcp
// 2. Shell B issues a sync command over its own concurrent connection.
let sync_res = client_b
.send_command(CoreCommand::SyncAll)
.await
.expect("mcp sync command");
.expect("shell b sync command");
assert_eq!(sync_res["status"], "dispatched");
// 3. CLI shell fetches state and verifies status is Syncing
let state_res = client_cli.get_state().await.expect("cli get state");
// 3. Shell A fetches state and verifies status is Syncing.
let state_res = client_a.get_state().await.expect("shell a get state");
assert_eq!(state_res["status"].as_str().unwrap(), "Syncing");
// 4. TUI shell marks message read
let mark_res = client_tui
// 4. Shell C marks a message read over its own concurrent connection.
let mark_res = client_c
.send_command(CoreCommand::MarkRead {
message_id: "msg-1".to_string(),
read: true,
})
.await
.expect("tui mark read");
.expect("shell c mark read");
assert_eq!(mark_res["status"], "marked");
// 5. Independently, drive the real in-workspace `nuncio-cli` reference
// shell (its own ephemeral local engine, per its documented headless
// design) to confirm a genuine presentation-shell client still
// round-trips full commands correctly end-to-end.
let cli_runner = HeadlessRunner::ephemeral()
.await
.expect("cli headless runner initializes");
let cli_sync = cli_runner
.execute_command(
&Commands::Mail {
action: MailSubcommand::Sync,
},
true,
)
.await;
assert!(cli_sync.contains(r#""status":"sync_started""#));
let cli_status = cli_runner
.execute_command(
&Commands::System {
action: SystemSubcommand::Status,
},
true,
)
.await;
assert!(cli_status.contains(r#""engine_status":"Syncing""#));
}