This commit is contained in:
Mateusz Gruszczyński committed 2026-10-30 14:41:15 +01:00
1 parent 52d5f1745a
commit 1234aae646
19 files changed
+145 -16

No files matched your search

+5
View File
@@ -1,5 +1,10 @@
# RustBox changelog # RustBox changelog
### 0.0.6
- Allowed links in rendered HTML messages to open in a new tab from the sandboxed reader while keeping the opened page outside the message sandbox.
- Added eight predefined color presets: Emerald, Lime, Sky, Navy, Plum, Magenta, Copper, and Sand.
- Bumped the application/source release to `0.0.6`.
### 0.0.5 ### 0.0.5
- Added native file logging through `log_dir`; the packaged Linux profile writes JSON logs to `/var/log/rustbox/rustbox.log` and ships 7-day logrotate policy. - Added native file logging through `log_dir`; the packaged Linux profile writes JSON logs to `/var/log/rustbox/rustbox.log` and ships 7-day logrotate policy.
- Updated the packaged systemd profile for `/var/lib/rustbox`, `/var/log/rustbox`, hardened service settings, and user-owned configuration. - Updated the packaged systemd profile for `/var/lib/rustbox`, `/var/log/rustbox`, hardened service settings, and user-owned configuration.
Generated
+1 -1
View File
@@ -2957,7 +2957,7 @@ dependencies = [
[[package]] [[package]]
name = "rustbox" name = "rustbox"
version = "0.0.5" version = "0.0.6"
dependencies = [ dependencies = [
"async-imap", "async-imap",
"async-native-tls", "async-native-tls",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "rustbox" name = "rustbox"
version = "0.0.5" version = "0.0.6"
edition = "2024" edition = "2024"
rust-version = "1.88" rust-version = "1.88"
default-run = "rustbox" default-run = "rustbox"
+1 -1
View File
@@ -1,6 +1,6 @@
# RustBox attribution / notice # RustBox attribution / notice
RustBox 0.0.5 is derived from the Oxi webmail project: RustBox 0.0.6 is derived from the Oxi webmail project:
- Upstream: `c0h1b4/oxi` - Upstream: `c0h1b4/oxi`
- Base snapshot: `7f1507c486b78cf33e54beb701dd460d3bd2bd5d` - Base snapshot: `7f1507c486b78cf33e54beb701dd460d3bd2bd5d`
+2 -2
View File
@@ -1,4 +1,4 @@
# RustBox 0.0.5 # RustBox 0.0.6
RustBox is a webmail derived from `c0h1b4/oxi`. The consumed Oxi revision is recorded in `UPSTREAM_BASE`. RustBox is a webmail derived from `c0h1b4/oxi`. The consumed Oxi revision is recorded in `UPSTREAM_BASE`.
@@ -138,7 +138,7 @@ Initialize Git after extracting the release archive:
```bash ```bash
git init git init
git add . git add .
git commit -m "RustBox 0.0.5 release" git commit -m "RustBox 0.0.6 release"
./scripts/setup-upstream.sh ./scripts/setup-upstream.sh
``` ```
+3 -3
View File
@@ -1,4 +1,4 @@
RustBox 0.0.5 source release RustBox 0.0.6 source release
Runtime/source model: Runtime/source model:
- one root Cargo package - one root Cargo package
@@ -19,7 +19,7 @@ Oxi tracking:
- Oxi frontend changes are reviewed and manually ported to src/web/ - Oxi frontend changes are reviewed and manually ported to src/web/
- docs/UI.md documents the browser UI/build conventions - docs/UI.md documents the browser UI/build conventions
Current 0.0.5 release additions: Current 0.0.6 release additions:
- attachment left-click defaults to safe in-app preview for supported types - attachment left-click defaults to safe in-app preview for supported types
- attachment right-click exposes explicit Preview and Download actions - attachment right-click exposes explicit Preview and Download actions
- oversized preview attempts offer direct download instead of ending with a warning - oversized preview attempts offer direct download instead of ending with a warning
@@ -65,7 +65,7 @@ Validation note:
- JavaScript, JSON, TOML, SVG, shell syntax, UI selector/icon integrity and SQLite migrations were validated in the release environment. - JavaScript, JSON, TOML, SVG, shell syntax, UI selector/icon integrity and SQLite migrations were validated in the release environment.
- A full cargo build/test was not executed in this environment because cargo/rustc are not installed; run ./dev.sh or cargo test on a Rust toolchain host before deployment. - A full cargo build/test was not executed in this environment because cargo/rustc are not installed; run ./dev.sh or cargo test on a Rust toolchain host before deployment.
0.0.5 additions: 0.0.6 additions:
- mobile reader action and attachment fixes - mobile reader action and attachment fixes
- WebSocket heartbeat, reconnect backoff, watchdog, resync, dedupe and fallback polling - WebSocket heartbeat, reconnect backoff, watchdog, resync, dedupe and fallback polling
- reliable live new-message refresh and unread reconciliation - reliable live new-message refresh and unread reconciliation
+1 -1
View File
@@ -1,4 +1,4 @@
# RustBox 0.0.5 # RustBox 0.0.6
host = "127.0.0.1" host = "127.0.0.1"
port = 3001 port = 3001
environment = "production" environment = "production"
+2 -1
View File
@@ -294,7 +294,8 @@ pub fn update_preferences(
v, v,
&[ &[
"sage", "blue", "teal", "cyan", "indigo", "slate", "graphite", "amber", "orange", "sage", "blue", "teal", "cyan", "indigo", "slate", "graphite", "amber", "orange",
"violet", "rose", "red", "custom", "violet", "rose", "red", "emerald", "lime", "sky", "navy", "plum", "magenta",
"copper", "sand", "custom",
], ],
)?; )?;
} }
+1 -1
View File
@@ -126,7 +126,7 @@ async fn https_get_address(
format!("{host}:{port}") format!("{host}:{port}")
}; };
let request = format!( let request = format!(
"GET {target} HTTP/1.1\r\nHost: {host_header}\r\nUser-Agent: RustBox/0.0.5\r\nAccept: application/octet-stream, application/pgp-keys, text/plain\r\nConnection: close\r\n\r\n" "GET {target} HTTP/1.1\r\nHost: {host_header}\r\nUser-Agent: RustBox/0.0.6\r\nAccept: application/octet-stream, application/pgp-keys, text/plain\r\nConnection: close\r\n\r\n"
); );
timeout(duration, stream.write_all(request.as_bytes())) timeout(duration, stream.write_all(request.as_bytes()))
.await .await
+1 -1
View File
@@ -174,7 +174,7 @@ pub async fn export_ics(
.map_err(|e| AppError::InternalError(format!("Database error: {e}")))?; .map_err(|e| AppError::InternalError(format!("Database error: {e}")))?;
let mut body = String::from( let mut body = String::from(
"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//RustBox//Calendar 0.0.5//EN\r\nCALSCALE:GREGORIAN\r\n", "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//RustBox//Calendar 0.0.6//EN\r\nCALSCALE:GREGORIAN\r\n",
); );
for event in &events { for event in &events {
append_ics_event(&mut body, event); append_ics_event(&mut body, event);
+1 -1
View File
@@ -228,7 +228,7 @@ async fn post_one_click_to_address(
format!("{host}:{port}") format!("{host}:{port}")
}; };
let request = format!( let request = format!(
"POST {path} HTTP/1.1\r\nHost: {host_header}\r\nUser-Agent: RustBox/0.0.5\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{ONE_CLICK_BODY}", "POST {path} HTTP/1.1\r\nHost: {host_header}\r\nUser-Agent: RustBox/0.0.6\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{ONE_CLICK_BODY}",
ONE_CLICK_BODY.len() ONE_CLICK_BODY.len()
); );
+1 -1
View File
@@ -29,7 +29,7 @@ impl fmt::Display for SieveError {
write!(f, "ManageSieve server rejected the command: {msg}") write!(f, "ManageSieve server rejected the command: {msg}")
} }
Self::Timeout => write!(f, "ManageSieve operation timed out"), Self::Timeout => write!(f, "ManageSieve operation timed out"),
Self::ScriptTooLarge => write!(f, "Sieve script exceeds the RustBox 0.0.5 size limit"), Self::ScriptTooLarge => write!(f, "Sieve script exceeds the RustBox 0.0.6 size limit"),
} }
} }
} }
+48
View File
@@ -194,6 +194,54 @@ html[data-color-preset="red"] {
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel)); --selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
} }
html[data-color-preset="emerald"] {
--accent: #4f9b72;
--accent-strong: #68b38a;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
html[data-color-preset="lime"] {
--accent: #86a84f;
--accent-strong: #9bbb68;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
html[data-color-preset="sky"] {
--accent: #5d9ec4;
--accent-strong: #77b2d2;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
html[data-color-preset="navy"] {
--accent: #647eae;
--accent-strong: #7f96bd;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
html[data-color-preset="plum"] {
--accent: #9a6f9d;
--accent-strong: #af87b1;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
html[data-color-preset="magenta"] {
--accent: #b76591;
--accent-strong: #c87da5;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
html[data-color-preset="copper"] {
--accent: #b57755;
--accent-strong: #c78f70;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
html[data-color-preset="sand"] {
--accent: #aa9465;
--accent-strong: #bcaa7d;
--selected: color-mix(in srgb, var(--accent) 20%, var(--panel));
}
:root { :root {
--toolbar-density-factor: 1; --toolbar-density-factor: 1;
+8
View File
@@ -625,6 +625,14 @@
<option value="violet">Violet</option> <option value="violet">Violet</option>
<option value="rose">Rose</option> <option value="rose">Rose</option>
<option value="red">Red</option> <option value="red">Red</option>
<option value="emerald">Emerald</option>
<option value="lime">Lime</option>
<option value="sky">Sky</option>
<option value="navy">Navy</option>
<option value="plum">Plum</option>
<option value="magenta">Magenta</option>
<option value="copper">Copper</option>
<option value="sand">Sand</option>
<option value="custom">Custom</option> <option value="custom">Custom</option>
</select></label> </select></label>
<label><span data-i18n="accent_color">Accent color</span> <label><span data-i18n="accent_color">Accent color</span>
+1 -1
View File
@@ -562,7 +562,7 @@ function renderMessageBody(detail, forceRemote = false) {
if (hasRemote && !allowRemote && (policy === "ask" || policy === "contacts")) remoteBar.classList.remove("hidden"); if (hasRemote && !allowRemote && (policy === "ask" || policy === "contacts")) remoteBar.classList.remove("hidden");
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
iframe.setAttribute("sandbox", ""); iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
iframe.setAttribute("referrerpolicy", "no-referrer"); iframe.setAttribute("referrerpolicy", "no-referrer");
const imageSources = allowRemote ? "data: cid: https: http:" : "data: cid:"; const imageSources = allowRemote ? "data: cid: https: http:" : "data: cid:";
const csp = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${imageSources}; style-src 'unsafe-inline'; font-src data:">`; const csp = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${imageSources}; style-src 'unsafe-inline'; font-src data:">`;
+1 -1
View File
@@ -119,7 +119,7 @@ def test_backend_skin_contract_and_version():
for value in ["rustbox", "roundcube-classic", "roundcube-larry", "roundcube-elastic", "clean", "gmail", "proton", "zoho", "outlook", "minimal", "graphite", "high-contrast", "fastmail", "apple-mail", "yahoo", "hey", "nord"]: for value in ["rustbox", "roundcube-classic", "roundcube-larry", "roundcube-elastic", "clean", "gmail", "proton", "zoho", "outlook", "minimal", "graphite", "high-contrast", "fastmail", "apple-mail", "yahoo", "hey", "nord"]:
assert f'"{value}"' in db_source assert f'"{value}"' in db_source
cargo = (ROOT / "Cargo.toml").read_text() cargo = (ROOT / "Cargo.toml").read_text()
assert 'version = "0.0.5"' in cargo assert 'version = "0.0.6"' in cargo
config = (ROOT / "src/config.rs").read_text() config = (ROOT / "src/config.rs").read_text()
assert "pub default_theme: String" in config assert "pub default_theme: String" in config
assert '"system" | "dark" | "light"' in config assert '"system" | "dark" | "light"' in config
+67
View File
@@ -0,0 +1,67 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def read(path):
return (ROOT / path).read_text()
def test_release_version():
assert 'version = "0.0.6"' in read("Cargo.toml")
assert "# RustBox 0.0.6" in read("README.md")
def test_realtime_contract():
ws = read("src/realtime/ws.rs")
js = read("src/web/js/80-settings-realtime.js")
assert '"type": "heartbeat"' in ws
assert "Heartbeat timeout" in js
assert "2 ** attempt" in js
assert "resyncRealtimeState" in js
assert "realtimeSeenEvents" in js
assert "realtimePollTimer" in js
assert "realtimeRefreshEvents" in js
def test_upload_progress_is_not_toast_driven():
compose = read("src/web/js/50-compose.js")
block = compose[compose.index("async function uploadComposeFiles"):compose.index("function previewMarkdown")]
assert "progressToast(" not in block
assert "setComposeUploadProgress" in block
assert "AbortController" in block
def test_fullscreen_reader_contract():
html = read("src/web/index.html")
reader = read("src/web/js/40-messages-reader.js")
menu = read("src/web/js/60-mail-actions-search.js")
css = read("src/web/css/30-mail.css")
assert 'id="reader-fullscreen"' in html
assert "Open full screen" in menu
assert "message-fullscreen-action" in reader
assert "reader-fullscreen" in reader
assert "history.pushState" in reader
assert "reader-pane.reader-fullscreen" in css
assert ".reader-pane.reader-fullscreen .message-body" in css
assert "max-width: none" in css
def test_mobile_reader_contract():
css = read("src/web/css/30-mail.css")
responsive = read("src/web/css/60-responsive.css")
bindings = read("src/web/js/95-bindings-boot.js")
assert ".toolbar-menu-popover button" in css
assert ".attachment-item .attachment-button" in css
assert "min-height: 34px" in responsive
assert 'bottom: "8px"' not in bindings
assert 'rect.bottom + 5' in bindings
def test_message_links_can_open_new_tabs_from_sandbox():
reader = read("src/web/js/40-messages-reader.js")
assert 'iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox")' in reader
def test_extended_color_presets_contract():
html = read("src/web/index.html")
css = read("src/web/css/00-tokens.css")
backend = read("src/db/display_preferences.rs")
presets = ["emerald", "lime", "sky", "navy", "plum", "magenta", "copper", "sand"]
for preset in presets:
assert f'<option value="{preset}">' in html
assert f'html[data-color-preset="{preset}"]' in css
assert f'"{preset}"' in backend