feat: implement seamless local network sync via mDNS and CRDTs#45
feat: implement seamless local network sync via mDNS and CRDTs#45Keshav-writes-code wants to merge 18 commits intofeat/local_syncfrom
Conversation
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
be4286f to
5632e5a
Compare
c03333b to
15afd22
Compare
5632e5a to
91caa96
Compare
15afd22 to
901b741
Compare
91caa96 to
fca0a60
Compare
4ed5f15 to
fca0a60
Compare
9942729 to
de6e9ed
Compare
…ypes directories.
…for release artfacts
This commit lays the groundwork for seamless local network synchronization across devices. It introduces: - Backend: Rust implementation using `mdns-sd` for peer discovery, `axum` for a local HTTP server to handle pairing/status requests, and `automerge` for CRDT-based file merging (Last-Write-Wins handling via automerge docs). - Frontend: A new Svelte component `SyncSettings.svelte` to toggle broadcasting, generate pairing PINs, discover peers, and establish secure links. The UI is integrated into the BottomSidebar. - Android: Added background `SyncForegroundService` to Android manifest and Kotlin source to allow background network discovery when the app is minimized. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
This commit lays the groundwork for seamless local network synchronization across devices. It introduces: - Backend: Rust implementation using `mdns-sd` for peer discovery, `axum` for a local HTTP server to handle pairing/status requests, and `automerge` for CRDT-based file merging (Last-Write-Wins handling via automerge docs). - Frontend: A new Svelte component `SyncSettings.svelte` to toggle broadcasting, generate pairing PINs, discover peers, and establish secure links. The UI is integrated into the BottomSidebar. - Android: Added background `SyncForegroundService` to Android manifest and Kotlin source to allow background network discovery when the app is minimized. Fixed minor unused import warnings discovered during code review. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Previously, `mDNS` packets were erroneously broadcasting `0.0.0.0` as the service IP, causing other devices to discover the peer but fail to establish a network route. - Resolved the local network IP properly by testing a mock UDP connection to an external IP before initializing the `ServiceInfo`. - Fixed the visual output of discovered device names in the frontend by stripping the mDNS `.cherit._tcp.local` suffix in the discovery loop. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Resolves an issue where submitting a pairing PIN occasionally failed with "Invalid PIN or pairing not active" even when the PIN was correct. This usually occurs if the initiating device successfully sends the `POST /pair` HTTP request faster than the local receiver processes the mDNS discovery packet, causing the peer to be missing from the receiver's peer list. - The Axum handler now blindly accepts the matching PIN even if the peer is not yet officially recorded in the `peers` map. - Automatically initializes a stub `PeerInfo` entry if one did not exist, allowing the next broadcast from that peer to update its IP, Port, and Name natively. Also successfully rebased and integrated the remote changes as requested. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Implements the actual file synchronization trigger mechanism. - Extends `window_listeners` to invoke a new `sync_file` command whenever a changed file is saved to disk (e.g. when the window blurs). - Adds the `/sync` API endpoint in the `axum` server to receive incoming changes from peers. - Added a `sync_file` Tauri command to loop through all paired peers and POST file updates to them. Note: CRDT payload generation is currently stubbed out (pending full integration with Automerge in a future commit). Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Completes the MVP data sync exchange pipeline between peers. Previously, the `POST /sync` request from the frontend saved a dummy stub payload, and the receiving end logged it without saving. - Modified `sync_file` command to actually read the file contents from the file system disk and push that byte slice over HTTP. - Modified `/sync` route handler on the receiving peer to save incoming modifications into `~/.cherit-sync-inbox/<filename>` directory (via `dirs` crate). Note: Saving directly to an inbox provides immediate visibility that the sync succeeds across the network without accidentally overwriting the root workspace until full CRDT state merging is connected. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Previously, network syncing was only firing when the app lost focus (the `window_blur` event) due to it being hooked only in `window_listeners`. This commit adds a direct `sync_file` invocation into the main `text_editor` component's `write_to_file` callback, which is fired far more frequently as the user types, ensuring the peer receives updates in near real-time. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Replaces the dummy file transmission payload with actual CRDT operations. - Automerge Text CRDT Integration: Switched from using a naive `Last-Write-Wins` scalar string property to an Automerge `ObjType::Text` property manipulated via `splice_text`. This ensures character-level conflict resolution logic correctly preserves history without blowing up memory. - Live Sync Updating: When an incoming payload merges successfully, the backend emits a `sync-file-updated` Tauri event. The `text_editor` Svelte component intercepts this event, reads the new file from disk, and dispatches the change immediately into the CodeMirror editor instance. - Graceful Shutdown: Added a `broadcast` channel to gracefully signal and kill the background `axum` tokio task when stopping the sync service. - Persistent Peers: Serialized the internal `peers` map to a `peers.json` inside the platform-native app data config folder. - Path Serialization: Fixed a bug where Windows systems would send escaped backward-slash paths (`\\`) that macOS/Linux peers could not parse. - Android Background Hook: Implemented Android mobile plugin invocation to spawn the background service notification to keep network sockets alive. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
When editing a completely new or previously un-synced file, the file might exist on disk but lack a corresponding Automerge (.am) tracking file locally. If a sync event was triggered for it, the content was not properly parsed into an `ObjType::Text` before generating the sync payload. - Updated `load_or_create_doc` to inject the existing plaintext content into the newly minted CRDT Text object using `splice_text` if the file is found on disk. - Updated the backend `/sync` route to initialize the parent directories and create the plaintext file automatically if it receives a payload for a file that doesn't yet exist in the receiver's workspace. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
8488795 to
98044b3
Compare
Addresses the issue where newly created files, or existing files that were opened for the first time while sync was running, did not propagate their initial state to the peer correctly. - The `update_doc_from_file` and `load_or_create_doc` routines now detect and load the initial plaintext directly into the Automerge `ObjType::Text` before emitting the first sync payload. - Fixed the receiver side to automatically construct parent directories and a dummy file stub when receiving a payload for a completely unknown path before attempting to apply the CRDT data. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
This commit addresses several critical issues with the syncing process and improves the user experience. - Fixes issue where syncing a completely new or pre-existing but un-tracked file failed to send its contents. `crdt_manager` now reliably provisions an `ObjType::Text` and uses `splice_text` to seed its value if a plaintext file exists before generating payloads. - Fixes issue where offline changes were not synced. Instead of relying solely on the active Svelte editor to push updates, the backend now watches the workspace using `notify-debouncer-full` and asynchronously syncs external changes seamlessly. - Device discovery feels significantly faster. Cached peers are proactively probed via HTTP instead of waiting up to 3 seconds for the next mDNS multicast cycle. - Fixed device naming. Hostnames are properly derived using `gethostname` instead of `Device-PIN`. - Svelte UI now allows renaming and deleting known peers to provide control over persistent trust data. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Fixes a CI build failure on Android targets: `error[E0599]: no method named run_mobile_plugin found for struct AppHandle`. - Removed the direct `run_mobile_plugin` call on `AppHandle` in `commands.rs`. - This method was historically part of internal Tauri mobile traits but is not exposed cleanly on the main AppHandle without specific plugin bindings in Tauri v2. - Replaced with a log statement for the MVP. A full native intent invocation would require pulling in the `jni` crate and querying the JNI env. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Fixes a CI build failure on Android targets: `error[E0599]: no method named run_mobile_plugin found for struct AppHandle`. - Removed the direct `run_mobile_plugin` call on `AppHandle` in `commands.rs`. - This method was historically part of internal Tauri mobile traits but is not exposed cleanly on the main AppHandle without specific plugin bindings in Tauri v2. - Replaced with a log statement for the MVP. A full native intent invocation would require pulling in the `jni` crate and querying the JNI env. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Android 10+ (API 29+) enforces Scoped Storage via the Storage Access Framework (SAF), meaning that standard `std::fs` operations cannot write to user-selected virtual URIs, causing a `Read-only file system (os error 30)` panic when the backend tried to initialize the hidden `.sync` directory inside the workspace. - Relocated the Automerge `.am` metadata files into the Tauri application's native, read-write enabled config directory (`app_config_dir()`). - Added MD5 hashing of the workspace path to ensure different workspace trees get their own isolated sub-folder of sync states to prevent cross-workspace collisions. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
Addresses an issue in `crdt.rs` where the `automerge` crate was panicking or improperly updating the text object when trying to replace the existing text with a new string. - Split the text replacement into two separate `splice_text` operations: one to completely delete the existing length, and a subsequent one to insert the new content. - This ensures that Automerge robustly records the text replacements when an untracked file initializes or an external change causes a sync event without throwing out of bounds errors. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
The previous branch pushes relocated Svelte global states, causing the local `SyncSettings.svelte` compilation to fail during Vite build or checks. - Updated the import path in `SyncSettings.svelte` from `@/lib/global_states/index.svelte` to `@/lib/states/global/index.svelte` to match the user's latest architectural refactoring. Co-authored-by: Keshav-writes-code <95571677+Keshav-writes-code@users.noreply.github.com>
dabd928 to
92d68d2
Compare
Implements the Apple-like seamless local network sync feature for Markdown notes.
Features
mdns-sdcrate) to broadcast and discover instances of the application on the local network. A simple 6-digit PIN mechanism authenticates and establishes trust between devices.automergecrate to manage files using Conflict-free Replicated Data Types (CRDTs). The app projects CRDT history to the visible.mdfiles and maintains a hidden.sync/directory for metadata.ForegroundServiceto keep the sync process alive in the background.axumHTTP server within the Rust backend to exchange pairing statuses and (eventually) CRDT payloads.PR created automatically by Jules for task 6380593602282876537 started by @Keshav-writes-code