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
|
# 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
@@ -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
@@ -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,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`
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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:">`;
|
||||||
|
|||||||
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"]:
|
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
|
||||||
|
|||||||
@@ -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