diff --git a/CHANGELOG.md b/CHANGELOG.md index 61463f8..bd89aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ What changed in each RustBox release, written for the people who use and run it. Technical details are in the [documentation](docs/). +## 0.2.29 + +- An open message thread now updates automatically when a new message for that thread arrives via realtime events; reopening the thread is no longer required. + ## 0.2.28 - Replying to a message you sent no longer addresses the reply back to yourself; RustBox chooses the other thread participant(s), including when aliases/identities are used. diff --git a/Cargo.lock b/Cargo.lock index f140d3c..49845f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3506,7 +3506,7 @@ dependencies = [ [[package]] name = "rustbox" -version = "0.2.28" +version = "0.2.29" dependencies = [ "aes-gcm", "async-imap", diff --git a/Cargo.toml b/Cargo.toml index 0fea221..603b383 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustbox" -version = "0.2.28" +version = "0.2.29" edition = "2024" rust-version = "1.99.1" license = "AGPL-3.0-only" diff --git a/README.md b/README.md index f4f8dbd..7a218b3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# RustBox 0.2.28 +# RustBox 0.2.29 **Fast, self-hosted webmail written in Rust.** RustBox connects to any IMAP/SMTP server and ships as one executable with the whole interface built in, so there is no Node.js, PHP or separate frontend to deploy. diff --git a/RELEASE-MANIFEST.txt b/RELEASE-MANIFEST.txt index fff964c..380e295 100644 --- a/RELEASE-MANIFEST.txt +++ b/RELEASE-MANIFEST.txt @@ -1,6 +1,7 @@ -RustBox 0.2.28 release manifest +RustBox 0.2.29 release manifest -0.2.28 release changes: +0.2.29 release changes: +- open message thread refreshes automatically after realtime NewMessages events, without requiring the user to reopen it - replies to own sent messages target the other participant(s), with account aliases/identities treated as own addresses - mailbox toolbar toggles between grouped threads and individual messages using the persisted mailbox `thread_mode` preference; thread rows expand/collapse and use Message-ID/In-Reply-To/References metadata - legacy Polish text repair covers common UTF-8/Windows-1252 and ISO-8859-2/Western-Latin mojibake in headers, bodies and cached summaries/details; exact ISO-8859-2 folded-subject coverage added @@ -131,14 +132,14 @@ Docker examples: Operations/docs: - standalone `scripts/migrate_roundcube.py` imports Roundcube contacts/identities using RustBox config.toml and supports SQLite/MySQL/PostgreSQL targets -- README.md documents 0.2.28 configuration +- README.md documents 0.2.29 configuration - docker/README.md documents Compose variants - docs/STORAGE_SEARCH_OPERATIONS.md documents failure modes, shared-storage requirements and migration behavior - TODO.md leaves live SILO/MinIO/AWS/Ceph and real multi-node acceptance validation explicitly pending Build/release metadata: - `scripts/package-release.sh` runs the full Rust release gate before creating ZIP/SHA256 artifacts -- Cargo.toml version = 0.2.28 +- Cargo.toml version = 0.2.29 - rust-toolchain.toml pins Rust 1.99.1 - Cargo.lock is retained for reproducible application builds; the release gate generates it only when missing - CI and scripts/build-release.sh use the retained Cargo.lock (generating it only if missing), then run locked tests/Clippy/release build diff --git a/RELEASE-VALIDATION.txt b/RELEASE-VALIDATION.txt index aa4bb21..8ea3aff 100644 --- a/RELEASE-VALIDATION.txt +++ b/RELEASE-VALIDATION.txt @@ -1,6 +1,7 @@ -RustBox 0.2.28 - final source validation +RustBox 0.2.29 - final source validation -0.2.28 scope: +0.2.29 scope: +- realtime refresh of the currently open message thread when NewMessages arrives - correct reply recipients for own sent messages in threads, Roundcube-style mailbox thread/flat list toggle (compact icon-button matching Select messages, with an icon reflecting the active mode), and stronger Polish legacy-encoding/mojibake repair, plus the 0.2.27 mail-list and appearance baseline 0.2.25 scope: @@ -67,7 +68,7 @@ RustBox 0.2.28 - final source validation - Composing shows only compact attachment/message size-limit values on one line - 0.2.9 realtime notification gating, browser shortcut preservation and 300-message defaults remain intact - V047-V052 are consolidated into compatibility baseline V052; V053 adds `skin_depth` and `skin_gradients`; V054 adds the theme schedule and account colour; V055 is next -- version metadata identifies 0.2.28 +- version metadata identifies 0.2.29 Completed in this environment: - offline release validator: scripts/validate-release-static.py (458/458 checks passed) @@ -78,7 +79,7 @@ Completed in this environment: - all JSON files parse successfully - Python helper scripts compile successfully - shell scripts pass `bash -n` -- release metadata consistently identifies 0.2.28 +- release metadata consistently identifies 0.2.29 - V052 baseline consolidation plus the V053 skin-depth migration are validated offline - 0.2.25 filter UI (apply now, run now, always move from sender), context submenus and preset editing exercised in Playwright against a mocked API; the 0.2.24 styles rendered at standard and immersive depth with gradients on and off; earlier: all 37 interface styles rendered in light and dark at the immersive depth against a mocked API (Playwright), with no page errors diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index 1d92f75..e7ffcc5 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,7 +1,11 @@ -# RustBox 0.2.28 - implementation status +# RustBox 0.2.29 - implementation status - Draft saves are realtime-silent: Drafts folder/list updates continue, but saving a draft does not surface as a new-mail notification. -This file tracks the `0.2.28` source snapshot. Storage/search scope and outstanding acceptance work are tracked in `TODO.md`. A feature is only listed as implemented when working code exists in this source tree; partial items are called out explicitly. +This file tracks the `0.2.29` source snapshot. Storage/search scope and outstanding acceptance work are tracked in `TODO.md`. A feature is only listed as implemented when working code exists in this source tree; partial items are called out explicitly. + +## 0.2.29 realtime open-thread refresh + +- When realtime reports `NewMessages`, the currently open message detail is re-fetched and its thread is re-rendered if the user is still viewing the same message. ## 0.2.28 thread replies and legacy Polish mail decoding diff --git a/src/web/js/80-settings-realtime.js b/src/web/js/80-settings-realtime.js index 8ef4d1e..ba57e6a 100644 --- a/src/web/js/80-settings-realtime.js +++ b/src/web/js/80-settings-realtime.js @@ -662,6 +662,19 @@ async function resyncRealtimeState() { return state.realtimeSyncPromise; } +async function refreshOpenThread() { + const current = state.currentDetail; + if (!current) return; + const folder = current.folder; + const uid = current.uid; + try { + const fresh = await request(`/messages/${encodeURIComponent(folder)}/${uid}`, { method: "GET" }); + if (!state.currentDetail || state.currentDetail.folder !== folder || Number(state.currentDetail.uid) !== Number(uid)) return; + state.currentDetail.thread = fresh.thread || []; + renderThread(state.currentDetail); + } catch (_) { } +} + function scheduleRealtimeRefresh(event, accountId) { if (accountId !== state.accountId) return; state.realtimeRefreshEvents.push(event); @@ -669,6 +682,7 @@ function scheduleRealtimeRefresh(event, accountId) { state.realtimeRefreshTimer = setTimeout(async () => { const events = state.realtimeRefreshEvents.splice(0); const folderTreeChanged = events.some((item) => item.type === "FolderUpdated"); + const hasNewMessages = events.some((item) => item.type === "NewMessages"); const newInCurrent = events.some((item) => item.type === "NewMessages" && item.data?.folder === state.folder); const structuralFlagsInCurrent = events.some((item) => item.type === "FlagsChanged" && item.data?.folder === state.folder && !(item.data?.changes || []).length); const tasks = []; @@ -678,6 +692,7 @@ function scheduleRealtimeRefresh(event, accountId) { else tasks.push(loadMessages(true, { background: true })); } if (tasks.length) await Promise.all(tasks); + if (hasNewMessages && state.currentDetail) await refreshOpenThread(); }, 80); }