diff --git a/CHANGELOG.md b/CHANGELOG.md index 038d864..9f37308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to Mosh are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.1] - 2026-06-02 + +### Fixed +- **Invite joiner's chat history now survives restart.** The peer who *accepted* + an invite only obtains its MLS group after processing the creator's Welcome, + so the session record written at accept time kept an empty group-id + placeholder and could not be reloaded — the whole conversation was silently + dropped on the next launch. The record is now refreshed once the group is + established. (The invite *creator* was unaffected.) + +### Changed +- Added a quality-gated CI pipeline (rustfmt, Clippy `-D warnings`, typecheck, + vitest, cargo-nextest with retries) and a Windows release pipeline that builds + and attaches installers. The Rust toolchain is pinned via `rust-toolchain.toml`. + ## [0.2.0] - 2026-06-02 ### Added diff --git a/package-lock.json b/package-lock.json index 68e17a2..4ad07b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mosh", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mosh", - "version": "0.2.0", + "version": "0.2.1", "dependencies": { "@tabler/icons-react": "^3.41.1", "@tauri-apps/api": "^2", diff --git a/package.json b/package.json index d14d799..47eb084 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mosh", "private": true, - "version": "0.2.0", + "version": "0.2.1", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/.config/nextest.toml b/src-tauri/.config/nextest.toml index 615df84..73b4c00 100644 --- a/src-tauri/.config/nextest.toml +++ b/src-tauri/.config/nextest.toml @@ -21,3 +21,7 @@ moss-serial = { max-threads = 1 } [[profile.ci.overrides]] filter = 'test(over_moss) | test(moss_peers) | test(survive_restart)' test-group = 'moss-serial' +# The loopback MLS handshake occasionally fails to form its gossipsub mesh; a +# fresh retry process reliably recovers. Give these extra attempts (the mesh +# either forms within ~10s or not at all, so retrying beats a longer timeout). +retries = 4 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index bde6099..4597326 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3572,7 +3572,7 @@ dependencies = [ [[package]] name = "mosh" -version = "0.2.0" +version = "0.2.1" dependencies = [ "aes-gcm", "base64 0.22.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3d81015..d9fe2fb 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mosh" -version = "0.2.0" +version = "0.2.1" description = "Desktop-first decentralized messenger" authors = ["Mosh contributors"] edition = "2021" diff --git a/src-tauri/src/adapters/private_dm_runtime.rs b/src-tauri/src/adapters/private_dm_runtime.rs index 9563d7c..5cfe585 100644 --- a/src-tauri/src/adapters/private_dm_runtime.rs +++ b/src-tauri/src/adapters/private_dm_runtime.rs @@ -530,6 +530,7 @@ impl PrivateDmRuntime { .get(&session.session_id) .copied() .unwrap_or(0); + let has_new_messages = session.messages.len() > start; for (idx, msg) in session.messages.iter().enumerate().skip(start) { let ts = now_ms(); let message_id = format!("{ts}-{idx:06}"); @@ -543,13 +544,24 @@ impl PrivateDmRuntime { let _ = p.append_message(&session.session_id, ts, &message_id, &json); } } - let _ = p.put_mls_snapshot(&session.session_id, &session.crypto.snapshot()); - // Refresh the session record once (e.g. after the joiner processes - // the Welcome) so its group_id is no longer the empty placeholder. - if !self.finalized_session_records.contains(&session.session_id) - && session.crypto.group_id_bytes().is_some() - { + // The session record needs refreshing once the MLS group exists + // (e.g. after the joiner processes the Welcome), so its group_id is + // no longer the empty placeholder. + let needs_record_refresh = + !self.finalized_session_records.contains(&session.session_id) + && session.crypto.group_id_bytes().is_some(); + + // Only rewrite the (encrypted) MLS snapshot when state actually + // advanced — new messages ratchet the group, and a freshly joined + // group must be captured. Skipping idle polls avoids re-encrypting + // the full snapshot on every UI refresh, which also starved the + // loopback handshake under test on slow CI hardware. + if has_new_messages || needs_record_refresh { + let _ = p.put_mls_snapshot(&session.session_id, &session.crypto.snapshot()); + } + + if needs_record_refresh { if let Ok(json) = serde_json::to_vec(&session.to_persisted_record()) { pending_records.push((session.session_id.clone(), json)); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 74fdd2b..5d68a97 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Mosh", - "version": "0.2.0", + "version": "0.2.1", "identifier": "app.mosh.desktop", "build": { "beforeDevCommand": "npm run moss:prepare && npm run dev",