perf(tauri): Rust-native desktop event transport#15
Conversation
Reintroduce the smallest Tauri-native event transport slice needed to compare it against the browser EventSource path on real sessions. Add a frontend/server benchmark hook so the desktop runtime can report end-to-end transport behavior without pulling in the broader UI performance changes from the original PR. # Conflicts: # packages/tauri-app/src-tauri/src/cli_manager.rs # Conflicts: # packages/tauri-app/src-tauri/src/main.rs
Handle named SSE ping events with explicit pong replies so the native transport survives the server heartbeat window, and remove the unused active-session fast path while keeping the benchmark harness authenticated. # Conflicts: # packages/tauri-app/src-tauri/src/cli_manager.rs
Require the explicit bench build flag before the UI harness can be activated, remove query-string command overrides, gate perf logging behind a runtime flag, and add a regression test for named SSE ping frames.
Reuse the desktop event transport session cookie for the native /api/client-connections/pong request so heartbeat updates follow the same auth contract as the SSE stream, and cover it with a request-header regression test.
Expose a device-level setting that lets Tauri users disable the native Rust desktop event transport and fall back to the browser EventSource path when debugging transport issues or comparing behavior. Persist the preference in UI settings, mirror it into localStorage so startup transport selection can read it synchronously, and restart the backend event stream immediately when the toggle changes so the new transport takes effect without a full app restart. Validation: npx tsc --noEmit --pretty -p packages/ui/tsconfig.json; npx tsc --noEmit --pretty -p packages/server/tsconfig.json; cargo test --no-run
Move the Tauri native transport fallback toggle out of the shared UI config and treat it as a true device-local preference. The setting now lives only in local state plus localStorage, so browser, Electron, and other Tauri clients do not inherit a host-specific transport choice through storage broadcasts. While updating the selector, remove the hidden forceBrowserEvents override so the visible Tauri fallback setting becomes the only shipped transport-selection path outside the benchmark build gates. Validation: npx tsc --noEmit --pretty -p packages/ui/tsconfig.json; npx tsc --noEmit --pretty -p packages/server/tsconfig.json; cargo test --no-run
The transport benchmark harness still called loadMessages with the old boolean force flag, which broke UI typecheck after the session API moved to an options object. This keeps the PR242 benchmark code compatible with the current Pagec_tauri integration baseline instead of leaving a validation-only regression on the stack.
The change is limited to the benchmark harness and preserves the existing forced reload behavior by switching to the current { force: true } call shape. This keeps the fix scoped to the branch that introduced the transport bench so downstream integrations can cherry-pick a normal commit without rewriting history.
Validation: npm run typecheck --workspace @codenomad/ui
Drop the temporary in-app benchmark harness, perf logging endpoint, and batch metrics now that the native desktop transport has been validated on Windows and Linux. Keep the shipped PR focused on the transport implementation and the user-facing Tauri fallback setting rather than the instrumentation used during evaluation. The benchmark results are preserved in the PR description as historical validation, but the benchmark code itself no longer ships in the branch. Validation: npx tsc --noEmit --pretty -p packages/ui/tsconfig.json; npx tsc --noEmit --pretty -p packages/server/tsconfig.json; cargo test --no-run # Conflicts: # packages/ui/src/transport-bench.tsx
Guard the async event-stream connect failure path with the same generation check used by the success callbacks so an older rejected connection attempt cannot tear down a newer healthy stream. This keeps Tauri restarts and other reconnect races from disconnecting the active transport after the local desktop transport preference changes or another connection attempt wins first. Validation: npx tsc --noEmit --pretty -p packages/ui/tsconfig.json; npx tsc --noEmit --pretty -p packages/server/tsconfig.json
Fix the current upstream/dev baseline type mismatches exposed during the integrated PR batch validation. Align the session SDK imports with the v2 surface and narrow the git status workspace payload typing so the merged batch typechecks cleanly without changing feature behavior.
Merge current upstream/dev into pagec/rust-desktop-event-transport to clear PR NeuralNomadsAI#242 conflicts. The conflict resolution preserves the recent-folder/project-name launch flow from upstream while keeping the native Tauri event transport wiring, and retains the SDK v2 session type/diff compatibility already validated in the integrated Pagec_tauri branch. Validation: git diff --check and npm run typecheck --workspace @codenomad/ui.
|
| Filename | Overview |
|---|---|
| packages/tauri-app/src-tauri/src/desktop_event_transport.rs | Core module for the Rust-native SSE transport: defines configs, stats structs, and the DesktopEventTransportManager. The is_equivalent_start guard correctly excludes the ephemeral connection_id so idempotency works as intended. |
| packages/tauri-app/src-tauri/src/desktop_event_transport/transport.rs | Reconnect loop, stream consumption, and event batching. The synchronous pong call on the consumer thread uses the shared reqwest client (no per-call timeout), which could theoretically stall the consumer if the local server hangs, but in practice the local loopback connection makes this an acceptable trade-off given reqwest's blocking client constraint. |
| packages/tauri-app/src-tauri/src/desktop_event_transport/stream.rs | SSE reader, cookie handling, and pong dispatch. The previous cookie-injection concern is resolved: encode_cookie_header_value now percent-encodes control characters and special bytes before building the Cookie header. |
| packages/tauri-app/src-tauri/src/desktop_event_transport/assembler.rs | PendingBatch coalescing logic for deltas, snapshots, and status events. Well-tested and handles the snapshot-supersedes-delta cleanup correctly. |
| packages/tauri-app/src-tauri/src/cli_manager.rs | Adds session_cookie and auth_cookie_name tracking to CliProcessManager, exposes desktop_event_stream_config(), and refactors prod_entry_candidates() into a testable helper. Exe-relative candidates remain first and workspace fallback last, which the new test explicitly validates. |
| packages/ui/src/lib/native/desktop-events.ts | TypeScript side of the Tauri event transport: registers Tauri event listeners before invoking desktop_events_start, buffers events that arrive before the generation is known, and provides a clean disconnect path. |
| packages/ui/src/lib/event-transport.ts | Transport selector: routes local Tauri windows to the Rust-native path and all other contexts (browser, Electron, remote Tauri) to the browser EventSource path. Falls back gracefully on error. |
| packages/ui/src/lib/server-events.ts | Refactored ServerEvents class: connection is now async via connectWorkspaceEvents, events are dispatched as batches inside a SolidJS solidBatch(), and a restart() method was added for the settings toggle. |
| packages/ui/src/stores/preferences.tsx | Adds useTauriNativeEventTransport signal backed by localStorage preference, with setUseTauriNativeEventTransport triggering an immediate serverEvents.restart() on change. |
| packages/ui/src/lib/desktop-event-transport-preference.ts | Thin localStorage wrapper for the Tauri transport preference; defaults to enabled (true) and handles missing/errored localStorage gracefully. |
Sequence Diagram
sequenceDiagram
participant UI as TypeScript UI
participant TS as connectTauriWorkspaceEvents
participant Tauri as Tauri IPC
participant Rust as DesktopEventTransportManager
participant SSE as Local SSE Server
UI->>TS: connectWorkspaceEvents()
TS->>Tauri: listen("desktop:event-batch")
TS->>Tauri: listen("desktop:event-stream-status")
TS->>Tauri: "invoke("desktop_events_start", {request})"
Tauri->>Rust: start(app, stream_config, request)
Rust-->>Tauri: "{started:true, generation:N}"
Tauri-->>TS: DesktopEventsStartResult
TS->>TS: "expectedGeneration = N, flushPending()"
loop SSE stream running
Rust->>SSE: "GET /api/events?clientId=...&connectionId=..."
SSE-->>Rust: text/event-stream (reader thread)
Rust->>Rust: coalesce events into PendingBatch
Rust->>Tauri: "emit("desktop:event-batch", {generation, events})"
Tauri-->>TS: event listener fires
TS->>UI: callbacks.onBatch(events)
end
opt server sends codenomad.client.ping
SSE-->>Rust: "event: codenomad.client.ping / data: {ts:...}"
Rust->>SSE: POST /api/client-connections/pong
end
opt disconnect
UI->>TS: connection.disconnect()
TS->>Tauri: invoke("desktop_events_stop")
Tauri->>Rust: stop()
Rust->>Rust: stop flag, thread exits
end
Reviews (9): Last reviewed commit: "fix: task-080 scope native transport to ..." | Re-trigger Greptile
|
PR builds are available as GitHub Actions artifacts: https://github.com/Pagecran/CodeNomad/actions/runs/27058591619 Artifacts expire in 7 days.
|
Prioritize packaged server entrypoint candidates before the workspace fallback in production desktop launches, compare native event transport starts without the generated connection id, and percent-encode unsafe cookie-value bytes before building Cookie headers. Adds focused Rust coverage for production candidate ordering, idempotent transport starts with fresh connection ids, material stream changes, and Cookie header delimiter/control-byte encoding. Validated with the Tauri Rust test suite.
|
Addressed the Greptile summary items and pushed commit 1f79720 to \pagec/rust-desktop-event-transport: (1) production server entry resolution now prefers packaged/exe-relative candidates before the workspace fallback, (2) native event transport idempotency compares stable stream/reconnect inputs without the generated connection id, and (3) Cookie header session values are percent-encoded for delimiter/control-byte safety. Rust validation: \cargo test\ in \packages/tauri-app/src-tauri\ passed (18/18). |
|
PR builds are available as GitHub Actions artifacts: https://github.com/Pagecran/CodeNomad/actions/runs/27059209432 Artifacts expire in 7 days.
|
Ensure native desktop terminal status handling raises onError only once per terminal sequence, including stopped events after terminal failures. Trim leading fragment markers from CODENOMAD_UI_LAUNCH_QUERY before appending launch query values, and add focused TypeScript and Rust tests for the regressions.
|
PR builds are available as GitHub Actions artifacts: https://github.com/Pagecran/CodeNomad/actions/runs/27063307975 Artifacts expire in 7 days.
|
Refresh the PR242 native desktop event transport branch onto the current upstream/dev head in a clean isolated worktree and keep the existing Tauri-only native transport behavior intact. Preserve the browser and Electron EventSource path while taking upstream's session list, permission, and question transport updates. Resolve the only textual merge conflict in packages/ui/src/stores/session-api.ts by keeping the branch's SnapshotFileDiff import compatibility and upstream's newer V2 session pagination/session metadata flow. Keep the merged SDK 1.16.0 dependency bump from upstream/dev so the updated V2 event and session types validate correctly. Validation in the isolated worktree covered git diff --check, UI typecheck, UI build, tauri prebuild, and the tauri Rust test suite. nomadworks_validate was attempted but still fails with the known tool-side undefined-object error.
|
PR builds are available as GitHub Actions artifacts: https://github.com/Pagecran/CodeNomad/actions/runs/27092927368 Artifacts expire in 7 days.
|
|
PR builds are available as GitHub Actions artifacts: https://github.com/Pagecran/CodeNomad/actions/runs/27098053982 Artifacts expire in 7 days.
|
|
PR builds are available as GitHub Actions artifacts: https://github.com/Pagecran/CodeNomad/actions/runs/27109634708 Artifacts expire in 7 days.
|
…NeuralNomadsAI#519) ## Problem When the client receives a ping from the server, it responds with a pong via HTTP POST. On unstable networks (mobile, WiFi with poor signal, network switches), this POST can fail in multiple ways: - **Hung requests**: `fetch()` never rejects, blocking retries indefinitely - **Network disconnection**: `Failed to fetch` - **Brief disconnections**: transient network errors Previously, a single missed pong would cause the server to close the SSE connection after 45s, leaving message responses stuck in queue until the next message triggered a reconnection. ## Solution Three improvements to make the pong POST resilient: ### 1. Request timeout (10s) Each pong POST is now bounded with a 10s `AbortSignal` timeout. Hung requests fail fast instead of blocking indefinitely, allowing retries to start before the server's stale connection sweep. ### 2. Selective retry with `isRetryableError()` Only retries transient failures where recovery is possible: - `AbortError` / `TimeoutError` (hung or timed-out requests) - `Failed to fetch` (network disconnected) - `NetworkError` (browser network errors) Non-retryable errors like `404 Client connection not found` (permanently closed connection) fail immediately instead of wasting retry attempts. ### 3. Exponential backoff (3 attempts, 100ms → 2000ms) Handles burst failures gracefully without hammering the server. ## Changes - **`packages/ui/src/lib/retry-utils.ts`** (new): Reusable retry utility with `timeoutMs` and `shouldRetry` predicate support - **`packages/ui/src/lib/server-events.ts`**: Updated pong handler to use bounded timeout + selective retry ## Verification - Build passes: `npm run build:ui` ✅ - Manually verified in production logs: retries now visible as `Pong failed after retries` instead of single immediate failure - SSE monitor log at `~/.codenomad/logs/sse-monitor.log` shows `PONG_OK` / `PONG_FAIL` / `STALE` events for ongoing monitoring
## Summary
This PR removes tracked NomadWorks/OpenCode/task-tracking artifacts that
were accidentally committed to the repository and now interfere with
local NomadWorks usage for other contributors.
NomadWorks generates local workflow state such as `tasks/`, SCR
registries, and codemaps as needed. Keeping those generated artifacts
tracked in the shared repository creates stale state, false active
tasks, and confusing/broken NomadWorks behavior for other users who
initialize or run the workflow locally.
## Problematic commits / artifacts
The cleanup addresses artifacts introduced by the following
Shantur-authored commits:
- `e708c565ef6221dcded8a3da81c73e4755824f2d`
- `docs(wake-lock): record wake-lock change workflow`
- Introduced NomadWorks wake-lock SCR/task artifacts:
- `docs/scrs/SCR-2026-04-21-001-wake-lock-system-sleep-only.md`
- `tasks/current.md`
-
`tasks/discussions/DISCUSSION-001-wake-lock-behavior-change-for-macos-sleep-vs-screen-lock.md`
- `tasks/todo/055-wake-lock-investigation.md`
- `tasks/todo/056-wake-lock-behavior-change.md`
- `tasks/todo/057-implement-system-sleep-only-wake-lock.md`
- `a337c19b63198f076836644c9a9a4d29e8a47c18`
- `Init nomadworks`
- Introduced tracked NomadWorks/OpenCode initialization artifacts:
- `.nomadworks/**`
- root `codemap.yml`
- `docs/scrs/current.md`
- `docs/scrs/done.md`
- `tasks/done.md`
- `.opencode/opencode.jsonc`
- `.opencode/package-lock.json`
- The removed `.opencode/opencode.jsonc` only contained the OpenCode
schema plus a commented NomadWorks plugin entry:
- `"$schema": "https://opencode.ai/config.json"`
- `// "@neuralnomads/nomadworks@0.1.0-rc.10"`
- The removed `.opencode/package-lock.json` was an orphan lockfile with
no tracked `.opencode/package.json`. It pinned old OpenCode
plugin/runtime packages, notably:
- `@opencode-ai/plugin@1.14.24`
- `@opencode-ai/sdk@1.14.24`
- Those pinned OpenCode versions are stale for the current
CodeNomad/OpenCode integration, so keeping this lockfile is misleading
and can imply an unsupported local plugin setup.
- `b06b8104a519e7c3a798651c1980f3fc59284786`
- `Add task specifications for Phase 5 advanced input features`
- Introduced tracked task-planning artifacts including:
- `tasks/todo/015-keyboard-shortcuts.md`
- `tasks/todo/020-command-palette.md`
- `tasks/todo/021-file-attachments.md`
- `tasks/todo/022-long-paste-handling.md`
- `tasks/todo/023-symbol-attachments.md`
- `tasks/todo/024-agent-attachments.md`
- `tasks/todo/025-image-clipboard-support.md`
- `0a3ac6cbf24b4eaa19bba972ff691f7a6ff12275`
- `docs: add theme overhaul task series`
- Introduced tracked task-planning artifacts `tasks/todo/041-*` through
`tasks/todo/045-*`.
- `cb56eed4f94f6a656f78965386bb4ac5e3ab0d6f`
- `docs: add tailwind refactor follow-up tasks`
- Introduced tracked task-planning artifacts `tasks/todo/046-*` through
`tasks/todo/048-*`.
- `7267baf23d34e71e32e193540e08d718167415ac`
- `docs: queue remaining style cleanup tasks`
- Introduced tracked task-planning artifacts `tasks/todo/049-*` through
`tasks/todo/054-*`.
Some older mixed product commits also added `tasks/done/*` or
`PROGRESS.md` while introducing real product code. Those commits should
not be fully reverted because that would remove product functionality,
so this PR removes the stale tracking files directly in a final cleanup
commit.
## Changes
- Reverts the previous partial NomadWorks cleanup so the full original
faulty state can be reverted cleanly.
- Fully reverts the original `Init nomadworks` commit.
- Fully reverts the wake-lock NomadWorks workflow artifact commit.
- Removes remaining stale historical progress/task-tracking files that
are not used by CodeNomad runtime, builds, or GitHub workflows.
- Removes stale `.opencode` root config/lock artifacts introduced by the
NomadWorks initialization because the config only references a commented
NomadWorks plugin and the lockfile pins unsupported old OpenCode
packages without a matching package manifest.
## Validation
- `git status --short` is clean.
- `git diff --check upstream/dev..HEAD` passes with no output.
- Final diff only removes tracked NomadWorks/OpenCode/task/progress
artifacts; no runtime source files are changed.
## Summary - remove the obsolete session.diff fetch/store/SSE pipeline from packages/ui - delete the right drawer Session Changes tab and Status accordion section - map stored removed "changes" tab values to Git Changes and remove unused i18n keys ## Validation - npm run typecheck --workspace @codenomad/ui - git diff --check
Merge latest upstream/dev into PR NeuralNomadsAI#242 and resolve the remaining conflicts without broadening the desktop event transport feature. The native Rust transport now requires a Tauri host, a local window context, and the enabled preference; remote Tauri windows fall back to browser EventSource instead of invoking desktop_events_start. The server-events conflict also preserves upstream pong retry behavior while keeping the PR's transport abstraction. Validation: git diff --check and npm run typecheck --workspace @codenomad/ui.
|
PR builds are available as GitHub Actions artifacts: https://github.com/Pagecran/CodeNomad/actions/runs/27153762086 Artifacts expire in 7 days.
|
Summary
EventSourcepath to a native Rust desktop event transport while leaving browser and Electron unchangedevent:frames and replying tocodenomad.client.pingwith an authenticated/api/client-connections/pongEventSourcetransport without leaking that choice through shared configBenchmark
The temporary in-app benchmark harness used during validation has been removed from the final code, but the measured results are retained here for review context.
Real Tauri/WebView2 benchmark on Windows using the dedicated session:
D:\CodeNomadses_21feb15b3ffeLz3uRModK4KKnGShort command:
node -e "for (let i = 1; i <= 400; i += 1) console.log('line ' + i)"Results:
EventSourceforced in Tauri:131479.7mssawWorking=falsereachedIdle=falsebatchesReceived=84eventsReceived=84maxBatchSize=11437.4mssawWorking=truereachedIdle=truebatchesReceived=4eventsReceived=45maxBatchSize=27Long heartbeat / stale-timeout validation:
powershell -NoProfile -Command Start-Sleep -Seconds 7071689.5mssawWorking=truereachedIdle=truebatchesReceived=13eventsReceived=72maxBatchSize=25Confirmed separately afterward: the native transport also behaves better on Linux.
Validation
cargo test named_ping_event_is_routed_to_ping_channelcargo test session_cookie_is_attached_to_requestscargo test --no-runnpx tsc --noEmit --pretty -p packages/ui/tsconfig.jsonnpx tsc --noEmit --pretty -p packages/server/tsconfig.jsonNotes
EventSourcepathFork-local note
This PR is opened in the Pagecran fork to test Greptile review on the existing upstream PR NeuralNomadsAI#242 changes.
Source upstream PR: NeuralNomadsAI#242