v0.0.6
This commit is contained in:
1 parent
52d5f1745a
commit
1234aae646
19 files changed
+145
-16
No files matched your search
@@ -1,5 +1,10 @@
|
||||
# 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
|
||||
- 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.
|
||||
|
||||
Generated
+1
-1
@@ -2957,7 +2957,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustbox"
|
||||
version = "0.0.5"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"async-imap",
|
||||
"async-native-tls",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustbox"
|
||||
version = "0.0.5"
|
||||
version = "0.0.6"
|
||||
edition = "2024"
|
||||
rust-version = "1.88"
|
||||
default-run = "rustbox"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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`
|
||||
- Base snapshot: `7f1507c486b78cf33e54beb701dd460d3bd2bd5d`
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -138,7 +138,7 @@ Initialize Git after extracting the release archive:
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "RustBox 0.0.5 release"
|
||||
git commit -m "RustBox 0.0.6 release"
|
||||
./scripts/setup-upstream.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
RustBox 0.0.5 source release
|
||||
RustBox 0.0.6 source release
|
||||
|
||||
Runtime/source model:
|
||||
- one root Cargo package
|
||||
@@ -19,7 +19,7 @@ Oxi tracking:
|
||||
- Oxi frontend changes are reviewed and manually ported to src/web/
|
||||
- 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 right-click exposes explicit Preview and Download actions
|
||||
- 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.
|
||||
- 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
|
||||
- WebSocket heartbeat, reconnect backoff, watchdog, resync, dedupe and fallback polling
|
||||
- reliable live new-message refresh and unread reconciliation
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# RustBox 0.0.5
|
||||
# RustBox 0.0.6
|
||||
host = "127.0.0.1"
|
||||
port = 3001
|
||||
environment = "production"
|
||||
|
||||
@@ -294,7 +294,8 @@ pub fn update_preferences(
|
||||
v,
|
||||
&[
|
||||
"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",
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ async fn https_get_address(
|
||||
format!("{host}:{port}")
|
||||
};
|
||||
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()))
|
||||
.await
|
||||
|
||||
@@ -174,7 +174,7 @@ pub async fn export_ics(
|
||||
.map_err(|e| AppError::InternalError(format!("Database error: {e}")))?;
|
||||
|
||||
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 {
|
||||
append_ics_event(&mut body, event);
|
||||
|
||||
@@ -228,7 +228,7 @@ async fn post_one_click_to_address(
|
||||
format!("{host}:{port}")
|
||||
};
|
||||
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()
|
||||
);
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ impl fmt::Display for SieveError {
|
||||
write!(f, "ManageSieve server rejected the command: {msg}")
|
||||
}
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +194,54 @@ html[data-color-preset="red"] {
|
||||
--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 {
|
||||
--toolbar-density-factor: 1;
|
||||
|
||||
@@ -625,6 +625,14 @@
|
||||
<option value="violet">Violet</option>
|
||||
<option value="rose">Rose</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>
|
||||
</select></label>
|
||||
<label><span data-i18n="accent_color">Accent color</span>
|
||||
|
||||
@@ -562,7 +562,7 @@ function renderMessageBody(detail, forceRemote = false) {
|
||||
if (hasRemote && !allowRemote && (policy === "ask" || policy === "contacts")) remoteBar.classList.remove("hidden");
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.setAttribute("sandbox", "");
|
||||
iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
|
||||
iframe.setAttribute("referrerpolicy", "no-referrer");
|
||||
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:">`;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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"]:
|
||||
assert f'"{value}"' in db_source
|
||||
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()
|
||||
assert "pub default_theme: String" in config
|
||||
assert '"system" | "dark" | "light"' in config
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user