From 446a456005f4bb8b722b12ac2c5fc500136586b0 Mon Sep 17 00:00:00 2001 From: Sergei Date: Tue, 2 Jun 2026 19:21:23 +0200 Subject: [PATCH 1/2] chore(release): v0.2.1 Bump version to 0.2.1 and document the joiner chat-history persistence fix plus the new CI/release pipeline in the changelog. --- CHANGELOG.md | 15 +++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 6 files changed, 21 insertions(+), 6 deletions(-) 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/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/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", From e4343dfb5fad960900fff18fdf89397c311e7c82 Mon Sep 17 00:00:00 2001 From: Sergei Date: Tue, 2 Jun 2026 19:36:20 +0200 Subject: [PATCH 2/2] fix(dm): persist MLS snapshot only when state advances persist_session_tail re-encrypted and rewrote the full MLS snapshot on every drain, i.e. on every UI poll. Besides the wasted I/O, the constant AES-GCM + redb work on each poll starved the loopback gossipsub handshake on slow 2-core CI runners, so a joiner's session never reached "ready" within the test window. Write the snapshot only when state actually advances: new messages (which ratchet the group) or a freshly joined group being captured for the first time. Idle polls now do no snapshot I/O. Also raise nextest retries to 4 for the Moss-runtime test group: the loopback mesh occasionally fails to form and a fresh retry process recovers reliably. --- src-tauri/.config/nextest.toml | 4 ++++ src-tauri/src/adapters/private_dm_runtime.rs | 24 +++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) 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/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)); }