From bda6457711474fc5149947e59ebabe65e1a911d2 Mon Sep 17 00:00:00 2001 From: xiduzo Date: Sat, 20 Jun 2026 10:55:20 +0200 Subject: [PATCH 1/2] fix(web): stabilize useListen so transient board-state events aren't dropped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useListen re-created the Tauri listener on every render because callers pass a fresh inline { type, handler } object and the effect depended on [event]. The unlisten/relisten gap dropped events arriving mid-render — notably the fast connecting->connected burst, so the "Connecting" spinner never showed. Route the handler through a ref and subscribe once per type. Co-Authored-By: Claude Opus 4.8 --- apps/web/src/lib/ipc.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/web/src/lib/ipc.ts b/apps/web/src/lib/ipc.ts index 2410d71d..41d7fb65 100644 --- a/apps/web/src/lib/ipc.ts +++ b/apps/web/src/lib/ipc.ts @@ -2,7 +2,7 @@ import { invoke } from "@tauri-apps/api/core"; import { listen, type Event } from "@tauri-apps/api/event"; import { type Node, type Edge } from "@xyflow/react"; import { isDesktop } from "./platform"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; // Generated bindings: ts-rs writes one file per #[derive(TS)] type into // ./bindings/ during `cargo test`. Always import event-payload types from @@ -221,13 +221,20 @@ export type BrokerStatusPayload = BrokerStatus; export type ComponentEventPayload = ComponentEvent; export function useListen(event: { type: string; handler: (event: Event) => void }) { + // Keep the latest handler in a ref so callers can pass a fresh inline object + // every render without tearing down and re-creating the Tauri listener. A + // re-subscribe loop drops events that arrive during the unlisten/relisten + // gap (e.g. the transient `connecting` board-state burst). + const handlerRef = useRef(event.handler); + handlerRef.current = event.handler; + + const { type } = event; useEffect(() => { if (!isDesktop()) return; - const { type, handler } = event; - const listener = listen(type, handler); + const listener = listen(type, (e) => handlerRef.current(e)); return () => { listener.then((unlisten) => unlisten()).catch((error) => console.error(error)); }; - }, [event]); + }, [type]); } From 05a84d967ef76a5c3a3bb2afd507a233387ac844 Mon Sep 17 00:00:00 2001 From: xiduzo Date: Sat, 20 Jun 2026 12:17:38 +0200 Subject: [PATCH 2/2] fix(core): match Boolean variant explicitly in compare emitter Clippy's match_wildcard_for_single_variants flagged the `_` arm in the CompareValidator match, which now covers only the Boolean variant. Name it explicitly so a future added variant is a compile error instead of silently falling through to boolean truthiness. Co-Authored-By: Claude Opus 4.8 --- crates/microflow-core/src/codegen/transformation/compare.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/microflow-core/src/codegen/transformation/compare.rs b/crates/microflow-core/src/codegen/transformation/compare.rs index 245f4229..fc850d83 100644 --- a/crates/microflow-core/src/codegen/transformation/compare.rs +++ b/crates/microflow-core/src/codegen/transformation/compare.rs @@ -54,7 +54,7 @@ fn compare_expr(config: &CompareConfig, v: &str) -> String { } CompareValidator::Text => "false".to_string(), // `boolean` (default): truthiness of the input. - _ => format!("((bool)({v}))"), + CompareValidator::Boolean => format!("((bool)({v}))"), } }