Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mosh",
"private": true,
"version": "0.2.0",
"version": "0.2.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/.config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
24 changes: 18 additions & 6 deletions src-tauri/src/adapters/private_dm_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
Expand All @@ -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));
}
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading