Skip to content

feat(core): implement event system (task 07)#7

Merged
mpiton merged 3 commits intomainfrom
feat/07-event-system
Apr 8, 2026
Merged

feat(core): implement event system (task 07)#7
mpiton merged 3 commits intomainfrom
feat/07-event-system

Conversation

@mpiton
Copy link
Copy Markdown
Owner

@mpiton mpiton commented Apr 8, 2026

Summary

  • Implement TokioEventBus adapter using tokio::sync::broadcast channel, with lag detection via tracing::warn for slow subscribers
  • Add Tauri event bridge that subscribes to the EventBus and forwards all 18 DomainEvent variants to the frontend as kebab-case events with camelCase JSON payloads
  • Add useTauriEvent<T> React hook with useRef callback pattern, race-safe async cleanup (cancelled flag), and .catch() on unlisten promise

Architecture

Domain (DomainEvent) → EventBus port (trait) → TokioEventBus (broadcast)
                                              → Tauri bridge (subscribe + emit)
                                              → Frontend (useTauriEvent hook)
  • No serde in domain — serialization handled in adapter layer (tauri_bridge.rs)
  • Exhaustive pattern matching on all 18 DomainEvent variants
  • Bounded channel prevents backpressure from blocking publishers

Test plan

  • test_publish_and_receive_event — subscriber receives published event (Notify sync)
  • test_multiple_subscribers_receive_same_event — broadcast to 2 subscribers
  • test_publish_no_subscriber_doesnt_block — no panic without subscribers
  • test_new_with_zero_capacity_uses_minimum — capacity clamped to 1
  • test_event_name_* — all 18 variants map to correct kebab-case names
  • test_event_payload_*_camel_case — payloads use camelCase keys
  • TS: subscribe, callback, cleanup, re-subscribe, ref pattern, cancelled flag, rejection handling

Summary by cubic

Implements a real-time event system from backend to frontend using Tauri, with a TokioEventBus, a Tauri bridge, and a useTauriEvent React hook. Also adds safety fixes for promise handling and JS number precision.

  • New Features

    • TokioEventBus using tokio::sync::broadcast with bounded channel and lag warnings; exposed as TokioEventBus and spawn_tauri_event_bridge.
    • Tauri bridge emits all 18 DomainEvent variants as kebab-case events with camelCase JSON payloads.
    • useTauriEvent<T> built on @tauri-apps/api/event listen, with ref-based callback updates and race-safe cleanup.
  • Bug Fixes

    • Attach .catch() to listen() to prevent unhandled promise rejections.
    • Remove redundant AppHandle clone in the bridge.
    • Serialize PackageCreated.id as a string for JS precision safety.

Written for commit 061e71e. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Real-time event pipeline: non-blocking in-process event bus and a background bridge that emits structured events to the UI.
    • Exported hook for React components to subscribe to app events (downloads, segments, plugins, packages) with camelCase payload fields.
  • Tests

    • Added unit and integration tests covering event delivery, payload shape, subscription lifecycle, and edge cases.

mpiton added 2 commits April 8, 2026 11:29
…e (task 07)

Add the event system infrastructure that connects domain events to the
frontend via Tauri emit. This is the decoupling mechanism between command
handlers and the UI.

Backend:
- TokioEventBus adapter using tokio::sync::broadcast channel
- Tauri event bridge subscribing to EventBus and forwarding to webview
- Event name mapping (22 DomainEvent variants → kebab-case event names)
- Event payload serialization (camelCase JSON, no serde in domain)
- Lag detection with tracing::warn for slow subscribers

Frontend:
- useTauriEvent<T> generic hook with useRef callback pattern
- Race-safe cleanup with cancelled flag and .catch() on unlisten
- 7 tests covering subscribe, cleanup, re-subscribe, ref pattern, rejection

Tests: 145 Rust (4 new), 7 TypeScript (all new)
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8fc1e154-fc9f-4843-92f5-9eb663148af5

📥 Commits

Reviewing files that changed from the base of the PR and between 11f965f and 061e71e.

📒 Files selected for processing (2)
  • src-tauri/src/adapters/driven/event/tauri_bridge.rs
  • src/hooks/useTauriEvent.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/useTauriEvent.ts
  • src-tauri/src/adapters/driven/event/tauri_bridge.rs

📝 Walkthrough

Walkthrough

Adds a new event subsystem: a broadcast-backed TokioEventBus, a Tauri bridge that converts and emits domain events to the webview, and a frontend React hook to subscribe to emitted Tauri events.

Changes

Cohort / File(s) Summary
Event module aggregator & exports
src-tauri/src/adapters/driven/event/mod.rs
New module file declaring tauri_bridge and tokio_event_bus, re-exporting spawn_tauri_event_bridge and TokioEventBus.
Tauri bridge
src-tauri/src/adapters/driven/event/tauri_bridge.rs
Adds spawn_tauri_event_bridge(app_handle, event_bus) that subscribes to domain events, maps variants to Tauri event names, shapes JSON payloads (camelCase fields), emits to webview, and includes unit tests for mappings and payload keys.
Tokio-based EventBus
src-tauri/src/adapters/driven/event/tokio_event_bus.rs
Introduces TokioEventBus using tokio::sync::broadcast with capacity clamping, non-blocking publish, subscription loop handling Ok, Lagged, and Closed, plus unit tests for delivery and capacity behavior.
Backend integration
src-tauri/src/adapters/driven/mod.rs, src-tauri/src/lib.rs
Adds pub mod event; and re-exports TokioEventBus and spawn_tauri_event_bridge from the crate API.
Frontend hook & tests
src/hooks/useTauriEvent.ts, src/hooks/useTauriEvent.test.ts
New exported useTauriEvent<T>(eventName, callback) hook that keeps latest callback in a ref, listens via Tauri listen, handles cleanup and listen errors; comprehensive Vitest tests validate subscription, callback invocation, cleanup, re-render behavior, and error handling.

Sequence Diagram

sequenceDiagram
    actor Domain
    participant EventBus as TokioEventBus
    participant Bridge as Tauri Bridge
    participant Tauri as Tauri Webview
    participant Hook as useTauriEvent Hook
    actor Frontend

    Domain->>EventBus: publish(DomainEvent)
    EventBus->>Bridge: subscription callback(event)
    Bridge->>Bridge: map variant -> (eventName, jsonPayload)
    Bridge->>Tauri: emit(eventName, jsonPayload)
    Tauri->>Hook: delivers event(payload)
    Hook->>Frontend: invoke callback(payload)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hop with glee through code and log,

Domain to view, a tiny frog;
Tokio hums, the bridge emits,
Hooks await those JSON bits.
Hooray — events now reach the bog! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change—implementing an event system across the application (backend, bridge, and frontend components).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/07-event-system

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 8, 2026

Greptile Summary

This PR adds the full event pipeline: a TokioEventBus adapter backed by tokio::sync::broadcast, a Tauri bridge that maps all 18 DomainEvent variants to kebab-case events with camelCase JSON payloads, and a useTauriEvent<T> React hook with safe async cleanup. The implementation is solid and well-tested, with three minor P2 observations noted inline.

Confidence Score: 5/5

Safe to merge; all findings are P2 style/hardening suggestions with no blocking correctness issues.

The core event bus, Tauri bridge, and React hook are all correctly implemented and well-tested. The three inline comments are: a potential unhandled-rejection window in the hook (low practical impact since Tauri listen rarely fails), a redundant clone in the bridge, and a note that the bridge isn't yet wired into run() (expected for an incremental task PR). None of these affect correctness of the code as written.

src/hooks/useTauriEvent.ts (unhandled rejection window) and src-tauri/src/lib.rs (bridge not wired into run()).

Vulnerabilities

No security concerns identified. Event payloads are constructed manually via serde_json::json! with explicit field names rather than auto-deriving Serialize on domain types, which avoids accidental field exposure. The Tauri emit call is best-effort (.ok()) and does not propagate internal errors to the frontend.

Important Files Changed

Filename Overview
src-tauri/src/adapters/driven/event/tokio_event_bus.rs Implements EventBus via tokio broadcast channel with lag detection; clean tests covering subscribe/publish/broadcast/zero-capacity cases.
src-tauri/src/adapters/driven/event/tauri_bridge.rs Exhaustive mapping of all 18 DomainEvent variants to kebab-case names and camelCase JSON payloads; redundant app_handle clone is a minor style issue.
src/hooks/useTauriEvent.ts useRef callback pattern and cancelled flag are correct; has a window where a rejected listen promise can become unhandled if the component stays mounted.
src/hooks/useTauriEvent.test.ts Good coverage of subscribe, callback dispatch, cleanup, re-subscribe, ref pattern, cancelled flag, and rejection; rejection test unmounts immediately which avoids the unhandled-rejection window.
src-tauri/src/lib.rs Exports TokioEventBus and spawn_tauri_event_bridge but neither is wired into run(); the event pipeline is inert until integrated in a Tauri .setup() callback.
src-tauri/src/adapters/driven/event/mod.rs Thin re-export module, no issues.
src-tauri/src/adapters/driven/mod.rs Adds event module declaration alongside existing sqlite module, no issues.

Sequence Diagram

sequenceDiagram
    participant Domain as Domain Layer
    participant Bus as TokioEventBus<br/>(broadcast::Sender)
    participant Bridge as spawn_tauri_event_bridge<br/>(tokio task)
    participant Tauri as Tauri Runtime<br/>(AppHandle::emit)
    participant Hook as useTauriEvent<br/>(React hook)

    Domain->>Bus: publish(DomainEvent)
    Bus-->>Bridge: broadcast::Receiver::recv()
    Bridge->>Bridge: event_name() + event_payload()
    Bridge->>Tauri: handle.emit(name, payload)
    Tauri-->>Hook: listen callback fires
    Hook->>Hook: check cancelled flag
    Hook->>Hook: callbackRef.current(event.payload)

    Note over Hook: On unmount: cancelled=true<br/>unlistenFn?.()
Loading

Comments Outside Diff (1)

  1. src-tauri/src/lib.rs, line 23-29 (link)

    P2 Event bridge not wired into run()

    TokioEventBus and spawn_tauri_event_bridge are exported from lib.rs but neither is instantiated nor called inside run(). As a result, the backend→frontend event pipeline described in the PR architecture is inert — the frontend's useTauriEvent hook will never receive any events until spawn_tauri_event_bridge is called during Tauri setup. Something like the following is needed in the .setup() callback:

    .setup(|app| {
        let event_bus = Arc::new(TokioEventBus::new(64));
        spawn_tauri_event_bridge(app.handle().clone(), &*event_bus);
        app.manage(event_bus);
        Ok(())
    })

    If wiring is intentionally deferred to a follow-up task, a // TODO(task-XX) comment here would prevent the gap from being overlooked.

    Fix in Claude Code

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(core): resolve TypeScript type error..." | Re-trigger Greptile

Comment on lines +10 to +17
const unlistenPromise = listen<T>(eventName, (event) => {
if (!cancelled) {
callbackRef.current(event.payload);
}
});
return () => {
cancelled = true;
unlistenPromise.then((fn) => fn()).catch(() => {});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unhandled rejection window before cleanup

unlistenPromise has no rejection handler attached at creation time. If listen rejects asynchronously (e.g., IPC bridge unavailable during a network hiccup) while the component stays mounted, the Promise rejection is flagged as unhandled by the runtime before the cleanup's .catch() is ever attached. The existing test side-steps this by calling unmount() synchronously right after mount, which attaches the .catch() before the microtask checkpoint.

A safe fix is to keep a separate resolved state instead of storing the raw promise:

useEffect(() => {
  let cancelled = false;
  let unlistenFn: (() => void) | undefined;

  listen<T>(eventName, (event) => {
    if (!cancelled) callbackRef.current(event.payload);
  })
    .then((fn) => {
      if (cancelled) fn(); // already unmounted, clean up immediately
      else unlistenFn = fn;
    })
    .catch(() => {}); // suppress listen errors eagerly

  return () => {
    cancelled = true;
    unlistenFn?.();
  };
}, [eventName]);

This attaches .catch() immediately, eliminating the unhandled-rejection window regardless of how long the component stays mounted.

Fix in Claude Code

Comment on lines +8 to +13
pub fn spawn_tauri_event_bridge(app_handle: AppHandle, event_bus: &dyn EventBus) {
let handle = app_handle.clone();
event_bus.subscribe(Box::new(move |event: &DomainEvent| {
let (name, payload) = to_tauri_event(event);
handle.emit(name, payload).ok(); // Best-effort, don't crash on emit failure
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant app_handle clone

app_handle is taken by value (AppHandle is Clone + Send) and then immediately cloned into handle. The original binding is never used after the clone. You can move app_handle directly into the closure and drop the intermediate binding.

Suggested change
pub fn spawn_tauri_event_bridge(app_handle: AppHandle, event_bus: &dyn EventBus) {
let handle = app_handle.clone();
event_bus.subscribe(Box::new(move |event: &DomainEvent| {
let (name, payload) = to_tauri_event(event);
handle.emit(name, payload).ok(); // Best-effort, don't crash on emit failure
}));
pub fn spawn_tauri_event_bridge(app_handle: AppHandle, event_bus: &dyn EventBus) {
event_bus.subscribe(Box::new(move |event: &DomainEvent| {
let (name, payload) = to_tauri_event(event);
app_handle.emit(name, payload).ok(); // Best-effort, don't crash on emit failure
}));
}

Fix in Claude Code

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/hooks/useTauriEvent.ts">

<violation number="1" location="src/hooks/useTauriEvent.ts:10">
P2: Attach a rejection handler to the `listen` promise when it is created. Right now `.catch()` is only added during cleanup, so if `listen()` rejects while the component is still mounted it can surface as an unhandled promise rejection.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src-tauri/src/adapters/driven/event/tauri_bridge.rs`:
- Around line 39-85: The event payload currently serializes u64 numeric IDs
(e.g., DomainEvent::PackageCreated { id, name } and other cases like
DownloadProgress total_bytes/totalBytes, Download* { id }, Segment* download_id)
as JSON numbers which can lose precision in JS; update each match arm that emits
a u64 to serialize it as a string (e.g., replace "id": id with "id":
id.to_string() and do the same for total_bytes/totalBytes, downloadId,
segmentId, timestamps, etc.), or alternatively switch those fields to a
string-serializing newtype so the JSON produced by event_payload uses strings
for all u64 identifiers and byte/timestamp fields consumed by the frontend.

In `@src/hooks/useTauriEvent.ts`:
- Around line 8-18: The current useEffect calls listen<T>(eventName, ...) and
only handles promise rejection during cleanup which can produce unhandled
rejections; change the logic in useEffect so you attach .catch() to the listen
promise immediately and only store or call the returned unlisten function after
the promise resolves: call listen(...).then(unlistenFn => { if (!cancelled)
save/unsubscribe via that unlistenFn; else call unlistenFn() immediately }).
Also ensure you handle errors from listen by adding a .catch(err => { /* swallow
or report error appropriately */ }) to the promise returned by listen, and keep
references to cancelled and callbackRef as currently used to avoid race
conditions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 355bb83f-efd8-407b-86c1-65a3ff6cf310

📥 Commits

Reviewing files that changed from the base of the PR and between c78afb8 and 11f965f.

📒 Files selected for processing (7)
  • src-tauri/src/adapters/driven/event/mod.rs
  • src-tauri/src/adapters/driven/event/tauri_bridge.rs
  • src-tauri/src/adapters/driven/event/tokio_event_bus.rs
  • src-tauri/src/adapters/driven/mod.rs
  • src-tauri/src/lib.rs
  • src/hooks/useTauriEvent.test.ts
  • src/hooks/useTauriEvent.ts

Comment on lines +39 to +85
fn event_payload(event: &DomainEvent) -> serde_json::Value {
match event {
DomainEvent::DownloadCreated { id }
| DomainEvent::DownloadStarted { id }
| DomainEvent::DownloadPaused { id }
| DomainEvent::DownloadResumed { id }
| DomainEvent::DownloadResumedFromWait { id }
| DomainEvent::DownloadCompleted { id }
| DomainEvent::DownloadWaiting { id }
| DomainEvent::DownloadChecking { id }
| DomainEvent::DownloadExtracting { id } => json!({ "id": id.0 }),

DomainEvent::DownloadFailed { id, error } => json!({ "id": id.0, "error": error }),
DomainEvent::DownloadRetrying { id, attempt } => {
json!({ "id": id.0, "attempt": attempt })
}
DomainEvent::DownloadProgress {
id,
downloaded_bytes,
total_bytes,
} => {
json!({ "id": id.0, "downloadedBytes": downloaded_bytes, "totalBytes": total_bytes })
}

DomainEvent::SegmentStarted {
download_id,
segment_id,
}
| DomainEvent::SegmentCompleted {
download_id,
segment_id,
} => {
json!({ "downloadId": download_id.0, "segmentId": segment_id })
}
DomainEvent::SegmentFailed {
download_id,
segment_id,
error,
} => {
json!({ "downloadId": download_id.0, "segmentId": segment_id, "error": error })
}

DomainEvent::PluginLoaded { name, version } => {
json!({ "name": name, "version": version })
}
DomainEvent::PluginUnloaded { name } => json!({ "name": name }),
DomainEvent::PackageCreated { id, name } => json!({ "id": id, "name": name }),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 'PackageCreated|DownloadId' src-tauri/src
rg -n -C2 'package-created|payload\.id|downloadId|segmentId|Number\.MAX_SAFE_INTEGER|bigint' src

Repository: mpiton/vortex

Length of output: 50369


🏁 Script executed:

find src -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.vue" -o -name "*.js" \) 2>/dev/null | head -20

Repository: mpiton/vortex

Length of output: 250


🏁 Script executed:

rg -i "package-created|PackageCreated" src --type ts --type tsx --type vue -A 3 -B 1

Repository: mpiton/vortex

Length of output: 84


🏁 Script executed:

rg "payload\.id|segmentId|downloadId" src --type ts --type tsx --type vue -A 2

Repository: mpiton/vortex

Length of output: 84


🏁 Script executed:

rg -i "package.*created|packageCreated" src -A 3 -B 1

Repository: mpiton/vortex

Length of output: 39


🏁 Script executed:

cat src/hooks/useTauriEvent.ts

Repository: mpiton/vortex

Length of output: 637


🏁 Script executed:

rg "id.*number|id.*string|Number.MAX_SAFE_INTEGER|BigInt" src -A 2

Repository: mpiton/vortex

Length of output: 39


🏁 Script executed:

find src -name "*.ts" -o -name "*.tsx" | xargs rg "listen|payload" -l

Repository: mpiton/vortex

Length of output: 115


🏁 Script executed:

cat src/App.tsx

Repository: mpiton/vortex

Length of output: 390


🏁 Script executed:

find src -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "download\|package" {} \;

Repository: mpiton/vortex

Length of output: 88


🏁 Script executed:

cat src/hooks/useTauriEvent.test.ts

Repository: mpiton/vortex

Length of output: 3521


🏁 Script executed:

find src -name "*.ts" -o -name "*.tsx" | xargs cat | grep -i "type\|interface" | head -30

Repository: mpiton/vortex

Length of output: 473


🏁 Script executed:

git ls-files src | head -30

Repository: mpiton/vortex

Length of output: 264


🏁 Script executed:

rg "PackageCreated\|package.*id\|Package {" src-tauri/src/domain -A 5 -B 1

Repository: mpiton/vortex

Length of output: 196


🏁 Script executed:

rg "pub fn.*id\|generate.*id\|new.*id" src-tauri/src/domain/model/package.rs -A 2

Repository: mpiton/vortex

Length of output: 39


🏁 Script executed:

cat src-tauri/src/domain/model/package.rs

Repository: mpiton/vortex

Length of output: 3131


🏁 Script executed:

rg "uuid|nanoid|snowflake|id.*generate|fn.*id" src-tauri/src/domain/ports/driven -A 3

Repository: mpiton/vortex

Length of output: 3370


🏁 Script executed:

rg "PackageCreated" src-tauri/src -B 2 -A 2

Repository: mpiton/vortex

Length of output: 2214


🏁 Script executed:

grep -r "PackageRepository\|create.*package" src-tauri/src/domain/ports/driven --include="*.rs" -A 3

Repository: mpiton/vortex

Length of output: 39


Confirm u64 IDs should be serialized as strings for JavaScript safety.

PackageCreated.id is u64 and serialized directly as a JSON number via json!({ "id": id, ... }). JavaScript will lose precision for values exceeding Number.MAX_SAFE_INTEGER (2^53−1). The frontend has no visible type narrowing or BigInt conversion to prevent this. Consider serializing the ID as a string instead:

Suggested fix
DomainEvent::PackageCreated { id, name } => json!({ "id": id.to_string(), "name": name })

This applies to any other u64 fields exposed to JavaScript (e.g., DownloadMeta.total_bytes, segment byte ranges, timestamps). Alternatively, use a newtype wrapper like DownloadId to enforce string serialization at the type level.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/src/adapters/driven/event/tauri_bridge.rs` around lines 39 - 85,
The event payload currently serializes u64 numeric IDs (e.g.,
DomainEvent::PackageCreated { id, name } and other cases like DownloadProgress
total_bytes/totalBytes, Download* { id }, Segment* download_id) as JSON numbers
which can lose precision in JS; update each match arm that emits a u64 to
serialize it as a string (e.g., replace "id": id with "id": id.to_string() and
do the same for total_bytes/totalBytes, downloadId, segmentId, timestamps,
etc.), or alternatively switch those fields to a string-serializing newtype so
the JSON produced by event_payload uses strings for all u64 identifiers and
byte/timestamp fields consumed by the frontend.

- Attach .catch() eagerly to listen() promise to prevent unhandled
  rejections while component is mounted (greptile, cubic, coderabbit)
- Remove redundant app_handle clone in spawn_tauri_event_bridge (greptile)
- Serialize PackageCreated.id as string for JS precision safety (coderabbit)
@mpiton mpiton merged commit b4bf312 into main Apr 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant