From a55e12bc6392978e0d1a7698edb0c86aeb5396cb Mon Sep 17 00:00:00 2001 From: Paddy Mullen Date: Fri, 22 May 2026 20:36:36 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20close=20server-parity=20gaps=20?= =?UTF-8?q?=E2=80=94=20load=5Fcompare=20command=20+=20missing=20/load=20fi?= =?UTF-8?q?elds=20+=20permissions=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the buckaroo-tauri plugin to full parity with `buckaroo.server`'s HTTP surface. Adds the remaining `/load_compare` passthrough, the two optional `/load` fields the plugin wasn't forwarding (`prompt`, `component_config`), and fixes the permissions registration that the prior expand-surface PR missed for `load_expr` and `diagnostics`. ## Rust (crates/buckaroo-tauri/src) - New `LoadCompareArgs` + `buckaroo_load_compare` command. POSTs `/load_compare` with `{session, path1, path2, join_columns, how?, no_browser: true}` and opens the internal WS to the returned session — same end-of-call wiring as `buckaroo_load_path` / `_load_expr`. Unlike the other two, `session` is a required field on the Rust side because the server's `/load_compare` requires it. - `LoadPathArgs` gains `prompt` and `component_config` (forwarded verbatim into the POST body when present). Mirrors what `LoadExprArgs` already accepts. Pre-existing callers that omit these fields are unaffected. - `buckaroo_load_compare` registered in `init()`. - `build.rs` `COMMANDS` updated to list all seven commands. Previously it still listed only the original four — `load_expr` and `diagnostics` (added in the prior PR) weren't being registered with the Tauri 2 permission system, so a host that opted into `buckaroo-tauri:default` could not actually call them. The autogenerated per-command tomls are regenerated by `cargo build`. - `permissions/default.toml` updated to allow all seven commands. ## JS adapter (packages/buckaroo-tauri-adapter) - New `loadCompare(path1, path2, join_columns, { session, how? })` helper paired with `buckaroo_load_compare`. Required-`session` signature surfaces the server-side contract at the type level so callers don't discover it via a 400. - `loadPath` opts gain `prompt` and `component_config`, forwarded to the Rust command. - `loadCompare` exported from `index.ts`. ## Parity matrix | Server route | Plugin command | Status (before → after) | |--------------------|------------------------|-------------------------| | `GET /health` | `buckaroo_health` | unchanged (Rust-side liveness, not a `/health` proxy by design) | | `GET /diagnostics` | `buckaroo_diagnostics` | unchanged (passthrough) | | `POST /load` | `buckaroo_load_path` | adds `prompt`, `component_config` | | `POST /load_expr` | `buckaroo_load_expr` | unchanged | | `POST /load_compare` | `buckaroo_load_compare` | **new** | | `WS /ws/` | `buckaroo_send` + events | unchanged (passthrough) | ## Test plan - [x] `cargo build` clean. - [x] Existing Playwright DOM tests on the example app still pass (6/6). - [x] `tsc --noEmit` clean on the adapter package. - [ ] Manual: `loadCompare(p1, p2, ["id"], { session: "s1" })` against a sidecar with two parquets that share an `id` column produces a merged view with diff styling and `eqs` populated. - [ ] Manual: a host using `buckaroo-tauri:default` capability can invoke `buckaroo_load_expr`, `buckaroo_diagnostics`, and `buckaroo_load_compare` without Tauri's permission system rejecting the call (verifies the build.rs fix). Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/buckaroo-tauri/build.rs | 3 + .../commands/buckaroo_diagnostics.toml | 13 +++ .../commands/buckaroo_load_compare.toml | 13 +++ .../commands/buckaroo_load_expr.toml | 13 +++ .../permissions/autogenerated/reference.md | 83 +++++++++++++- .../buckaroo-tauri/permissions/default.toml | 5 +- .../permissions/schemas/schema.json | 40 ++++++- crates/buckaroo-tauri/src/commands.rs | 106 ++++++++++++++++++ crates/buckaroo-tauri/src/lib.rs | 1 + .../src/TauriIPCModel.ts | 30 ++++- packages/buckaroo-tauri-adapter/src/index.ts | 1 + 11 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_diagnostics.toml create mode 100644 crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_compare.toml create mode 100644 crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_expr.toml diff --git a/crates/buckaroo-tauri/build.rs b/crates/buckaroo-tauri/build.rs index a3eadc0..f9d7693 100644 --- a/crates/buckaroo-tauri/build.rs +++ b/crates/buckaroo-tauri/build.rs @@ -5,7 +5,10 @@ /// Hosts opt in via `"buckaroo:default"` in their capability file. const COMMANDS: &[&str] = &[ "buckaroo_health", + "buckaroo_diagnostics", "buckaroo_load_path", + "buckaroo_load_expr", + "buckaroo_load_compare", "buckaroo_send", "buckaroo_pick_file", ]; diff --git a/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_diagnostics.toml b/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_diagnostics.toml new file mode 100644 index 0000000..478672d --- /dev/null +++ b/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_diagnostics.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-buckaroo-diagnostics" +description = "Enables the buckaroo_diagnostics command without any pre-configured scope." +commands.allow = ["buckaroo_diagnostics"] + +[[permission]] +identifier = "deny-buckaroo-diagnostics" +description = "Denies the buckaroo_diagnostics command without any pre-configured scope." +commands.deny = ["buckaroo_diagnostics"] diff --git a/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_compare.toml b/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_compare.toml new file mode 100644 index 0000000..a64e900 --- /dev/null +++ b/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_compare.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-buckaroo-load-compare" +description = "Enables the buckaroo_load_compare command without any pre-configured scope." +commands.allow = ["buckaroo_load_compare"] + +[[permission]] +identifier = "deny-buckaroo-load-compare" +description = "Denies the buckaroo_load_compare command without any pre-configured scope." +commands.deny = ["buckaroo_load_compare"] diff --git a/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_expr.toml b/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_expr.toml new file mode 100644 index 0000000..4f09e66 --- /dev/null +++ b/crates/buckaroo-tauri/permissions/autogenerated/commands/buckaroo_load_expr.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-buckaroo-load-expr" +description = "Enables the buckaroo_load_expr command without any pre-configured scope." +commands.allow = ["buckaroo_load_expr"] + +[[permission]] +identifier = "deny-buckaroo-load-expr" +description = "Denies the buckaroo_load_expr command without any pre-configured scope." +commands.deny = ["buckaroo_load_expr"] diff --git a/crates/buckaroo-tauri/permissions/autogenerated/reference.md b/crates/buckaroo-tauri/permissions/autogenerated/reference.md index 438ccce..a82cfab 100644 --- a/crates/buckaroo-tauri/permissions/autogenerated/reference.md +++ b/crates/buckaroo-tauri/permissions/autogenerated/reference.md @@ -1,11 +1,14 @@ ## Default Permission -Default permissions for the buckaroo Tauri plugin. Allows the webview to call all four IPC commands the plugin exposes. +Default permissions for the buckaroo Tauri plugin. Allows the webview to call every IPC command the plugin exposes. #### This default permission set includes the following: - `allow-buckaroo-health` +- `allow-buckaroo-diagnostics` - `allow-buckaroo-load-path` +- `allow-buckaroo-load-expr` +- `allow-buckaroo-load-compare` - `allow-buckaroo-send` - `allow-buckaroo-pick-file` @@ -18,6 +21,32 @@ Default permissions for the buckaroo Tauri plugin. Allows the webview to call al + + + +`buckaroo-tauri:allow-buckaroo-diagnostics` + + + + +Enables the buckaroo_diagnostics command without any pre-configured scope. + + + + + + + +`buckaroo-tauri:deny-buckaroo-diagnostics` + + + + +Denies the buckaroo_diagnostics command without any pre-configured scope. + + + + @@ -47,6 +76,58 @@ Denies the buckaroo_health command without any pre-configured scope. +`buckaroo-tauri:allow-buckaroo-load-compare` + + + + +Enables the buckaroo_load_compare command without any pre-configured scope. + + + + + + + +`buckaroo-tauri:deny-buckaroo-load-compare` + + + + +Denies the buckaroo_load_compare command without any pre-configured scope. + + + + + + + +`buckaroo-tauri:allow-buckaroo-load-expr` + + + + +Enables the buckaroo_load_expr command without any pre-configured scope. + + + + + + + +`buckaroo-tauri:deny-buckaroo-load-expr` + + + + +Denies the buckaroo_load_expr command without any pre-configured scope. + + + + + + + `buckaroo-tauri:allow-buckaroo-load-path` diff --git a/crates/buckaroo-tauri/permissions/default.toml b/crates/buckaroo-tauri/permissions/default.toml index bde23f5..23db589 100644 --- a/crates/buckaroo-tauri/permissions/default.toml +++ b/crates/buckaroo-tauri/permissions/default.toml @@ -1,10 +1,13 @@ "$schema" = "schemas/schema.json" [default] -description = "Default permissions for the buckaroo Tauri plugin. Allows the webview to call all four IPC commands the plugin exposes." +description = "Default permissions for the buckaroo Tauri plugin. Allows the webview to call every IPC command the plugin exposes." permissions = [ "allow-buckaroo-health", + "allow-buckaroo-diagnostics", "allow-buckaroo-load-path", + "allow-buckaroo-load-expr", + "allow-buckaroo-load-compare", "allow-buckaroo-send", "allow-buckaroo-pick-file", ] diff --git a/crates/buckaroo-tauri/permissions/schemas/schema.json b/crates/buckaroo-tauri/permissions/schemas/schema.json index 20e9408..0603c5f 100644 --- a/crates/buckaroo-tauri/permissions/schemas/schema.json +++ b/crates/buckaroo-tauri/permissions/schemas/schema.json @@ -294,6 +294,18 @@ "PermissionKind": { "type": "string", "oneOf": [ + { + "description": "Enables the buckaroo_diagnostics command without any pre-configured scope.", + "type": "string", + "const": "allow-buckaroo-diagnostics", + "markdownDescription": "Enables the buckaroo_diagnostics command without any pre-configured scope." + }, + { + "description": "Denies the buckaroo_diagnostics command without any pre-configured scope.", + "type": "string", + "const": "deny-buckaroo-diagnostics", + "markdownDescription": "Denies the buckaroo_diagnostics command without any pre-configured scope." + }, { "description": "Enables the buckaroo_health command without any pre-configured scope.", "type": "string", @@ -306,6 +318,30 @@ "const": "deny-buckaroo-health", "markdownDescription": "Denies the buckaroo_health command without any pre-configured scope." }, + { + "description": "Enables the buckaroo_load_compare command without any pre-configured scope.", + "type": "string", + "const": "allow-buckaroo-load-compare", + "markdownDescription": "Enables the buckaroo_load_compare command without any pre-configured scope." + }, + { + "description": "Denies the buckaroo_load_compare command without any pre-configured scope.", + "type": "string", + "const": "deny-buckaroo-load-compare", + "markdownDescription": "Denies the buckaroo_load_compare command without any pre-configured scope." + }, + { + "description": "Enables the buckaroo_load_expr command without any pre-configured scope.", + "type": "string", + "const": "allow-buckaroo-load-expr", + "markdownDescription": "Enables the buckaroo_load_expr command without any pre-configured scope." + }, + { + "description": "Denies the buckaroo_load_expr command without any pre-configured scope.", + "type": "string", + "const": "deny-buckaroo-load-expr", + "markdownDescription": "Denies the buckaroo_load_expr command without any pre-configured scope." + }, { "description": "Enables the buckaroo_load_path command without any pre-configured scope.", "type": "string", @@ -343,10 +379,10 @@ "markdownDescription": "Denies the buckaroo_send command without any pre-configured scope." }, { - "description": "Default permissions for the buckaroo Tauri plugin. Allows the webview to call all four IPC commands the plugin exposes.\n#### This default permission set includes:\n\n- `allow-buckaroo-health`\n- `allow-buckaroo-load-path`\n- `allow-buckaroo-send`\n- `allow-buckaroo-pick-file`", + "description": "Default permissions for the buckaroo Tauri plugin. Allows the webview to call every IPC command the plugin exposes.\n#### This default permission set includes:\n\n- `allow-buckaroo-health`\n- `allow-buckaroo-diagnostics`\n- `allow-buckaroo-load-path`\n- `allow-buckaroo-load-expr`\n- `allow-buckaroo-load-compare`\n- `allow-buckaroo-send`\n- `allow-buckaroo-pick-file`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the buckaroo Tauri plugin. Allows the webview to call all four IPC commands the plugin exposes.\n#### This default permission set includes:\n\n- `allow-buckaroo-health`\n- `allow-buckaroo-load-path`\n- `allow-buckaroo-send`\n- `allow-buckaroo-pick-file`" + "markdownDescription": "Default permissions for the buckaroo Tauri plugin. Allows the webview to call every IPC command the plugin exposes.\n#### This default permission set includes:\n\n- `allow-buckaroo-health`\n- `allow-buckaroo-diagnostics`\n- `allow-buckaroo-load-path`\n- `allow-buckaroo-load-expr`\n- `allow-buckaroo-load-compare`\n- `allow-buckaroo-send`\n- `allow-buckaroo-pick-file`" } ] } diff --git a/crates/buckaroo-tauri/src/commands.rs b/crates/buckaroo-tauri/src/commands.rs index d83cdf1..fb64f24 100644 --- a/crates/buckaroo-tauri/src/commands.rs +++ b/crates/buckaroo-tauri/src/commands.rs @@ -48,6 +48,35 @@ pub(crate) struct LoadPathArgs { /// Requires a `buckaroo[xorq]` install on the sidecar. #[serde(default)] pub backend: Option, + /// Optional: prompt string echoed back in the initial_state payload, + /// used by some hosts for breadcrumb / title chrome. Matches the field + /// on `LoadExprArgs`. + #[serde(default)] + pub prompt: Option, + /// Optional: server-side component_config override forwarded verbatim + /// into the session's `df_viewer_config.component_config`. Matches the + /// field on `LoadExprArgs`. + #[serde(default)] + pub component_config: Option, +} + +#[derive(Deserialize)] +pub(crate) struct LoadCompareArgs { + /// Path to the "left" dataset (any extension load_file handles). + pub path1: String, + /// Path to the "right" dataset. + pub path2: String, + /// Columns to join on. Forwarded verbatim into the server's + /// `col_join_dfs(...)` call. + pub join_columns: Vec, + /// Optional: join kind, forwarded as-is. Server defaults to `"outer"` + /// when omitted. Accepts the values pandas merge `how=` accepts. + #[serde(default)] + pub how: Option, + /// `/load_compare` requires a session id (unlike `/load` and + /// `/load_expr`, which mint one when omitted). Surface that contract + /// at the IPC layer rather than papering over it. + pub session: String, } #[derive(Deserialize)] @@ -122,6 +151,12 @@ pub(crate) async fn buckaroo_load_path( if let Some(b) = &args.backend { body.as_object_mut().unwrap().insert("backend".into(), serde_json::Value::String(b.clone())); } + if let Some(p) = &args.prompt { + body.as_object_mut().unwrap().insert("prompt".into(), serde_json::Value::String(p.clone())); + } + if let Some(c) = &args.component_config { + body.as_object_mut().unwrap().insert("component_config".into(), c.clone()); + } let resp = reqwest::Client::new() .post(format!("http://127.0.0.1:{}/load", port)) @@ -219,6 +254,77 @@ pub(crate) async fn buckaroo_load_expr( }) } +/// Diff two files via the sidecar's HTTP `/load_compare` endpoint, then open +/// the internal WS to that session if not already open. The server runs +/// `col_join_dfs(df1, df2, join_columns, how)`, stores the merged frame on +/// the session with diff styling baked into the column config, and pushes +/// the new state to any connected WS clients. +/// +/// Unlike `/load` and `/load_expr`, the server requires the caller to +/// supply a session id — the IPC type makes that non-optional rather than +/// papering over it. The response carries the per-column equality stats +/// (`eqs`) the server returns so host UIs can surface "n of N rows equal". +#[tauri::command] +pub(crate) async fn buckaroo_load_compare( + args: LoadCompareArgs, + app: AppHandle, + state: tauri::State<'_, SidecarState>, +) -> Result { + let port = state + .port + .lock() + .unwrap() + .ok_or_else(|| "sidecar not yet ready".to_string())?; + + let mut body = serde_json::Map::new(); + body.insert("session".into(), serde_json::Value::String(args.session.clone())); + body.insert("path1".into(), serde_json::Value::String(args.path1)); + body.insert("path2".into(), serde_json::Value::String(args.path2)); + body.insert( + "join_columns".into(), + serde_json::Value::Array( + args.join_columns + .into_iter() + .map(serde_json::Value::String) + .collect(), + ), + ); + if let Some(h) = args.how { + body.insert("how".into(), serde_json::Value::String(h)); + } + body.insert("no_browser".into(), serde_json::Value::Bool(true)); + + let resp = reqwest::Client::new() + .post(format!("http://127.0.0.1:{}/load_compare", port)) + .json(&serde_json::Value::Object(body)) + .send() + .await + .map_err(|e| format!("POST /load_compare failed: {}", e))?; + + let status = resp.status(); + let text = resp.text().await.map_err(|e| e.to_string())?; + if !status.is_success() { + return Err(format!("POST /load_compare returned {}: {}", status, text)); + } + let metadata: serde_json::Value = serde_json::from_str(&text) + .map_err(|e| format!("invalid JSON from /load_compare: {}", e))?; + + let session_id = metadata + .get("session") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .ok_or_else(|| "load_compare response missing 'session' field".to_string())?; + let rows = metadata.get("rows").and_then(|v| v.as_u64()); + + supervisor::connect_internal_ws(&app, port, &session_id).await?; + + Ok(LoadResult { + session_id, + rows, + metadata, + }) +} + /// Fetch the sidecar's server-side diagnostics blob (`GET /diagnostics`). /// Useful for host UIs that want to surface server version, uptime, /// installed extras, etc. diff --git a/crates/buckaroo-tauri/src/lib.rs b/crates/buckaroo-tauri/src/lib.rs index f23c8e6..2a23293 100644 --- a/crates/buckaroo-tauri/src/lib.rs +++ b/crates/buckaroo-tauri/src/lib.rs @@ -51,6 +51,7 @@ pub fn init(config: BuckarooConfig) -> TauriPlugin { commands::buckaroo_diagnostics, commands::buckaroo_load_path, commands::buckaroo_load_expr, + commands::buckaroo_load_compare, commands::buckaroo_send, commands::buckaroo_pick_file, ]) diff --git a/packages/buckaroo-tauri-adapter/src/TauriIPCModel.ts b/packages/buckaroo-tauri-adapter/src/TauriIPCModel.ts index 931d840..41696ab 100644 --- a/packages/buckaroo-tauri-adapter/src/TauriIPCModel.ts +++ b/packages/buckaroo-tauri-adapter/src/TauriIPCModel.ts @@ -217,7 +217,13 @@ export async function listenForInitialState(): Promise; + }, ): Promise<{ sessionId: string; rows?: number; metadata: any }> { return invoke("plugin:buckaroo-tauri|buckaroo_load_path", { args: { path, ...(opts ?? {}) }, @@ -238,3 +244,25 @@ export async function loadExpr( args: { build_dir, ...(opts ?? {}) }, }); } + +/** + * Convenience: trigger the Rust plugin's load_compare command, diffing + * two files via the server's `col_join_dfs(...)` and serving the merged + * frame with diff styling. The session id is required (unlike loadPath / + * loadExpr, where the server mints one when omitted) — the server's + * `/load_compare` contract requires it on the caller's side. + * + * `eqs` on the returned metadata is the per-column equality dictionary + * the server computes; host UIs can use it to surface "n of N rows equal" + * style summaries. + */ +export async function loadCompare( + path1: string, + path2: string, + join_columns: string[], + opts: { session: string; how?: string }, +): Promise<{ sessionId: string; rows?: number; metadata: any }> { + return invoke("plugin:buckaroo-tauri|buckaroo_load_compare", { + args: { path1, path2, join_columns, ...opts }, + }); +} diff --git a/packages/buckaroo-tauri-adapter/src/index.ts b/packages/buckaroo-tauri-adapter/src/index.ts index 902d0a6..22406f5 100644 --- a/packages/buckaroo-tauri-adapter/src/index.ts +++ b/packages/buckaroo-tauri-adapter/src/index.ts @@ -4,4 +4,5 @@ export { listenForInitialState, loadPath, loadExpr, + loadCompare, } from "./TauriIPCModel";