Compare commits

...

32 Commits

Author SHA1 Message Date
d63e72ad35 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 2m14s
Continuous integration / Test Suite (push) Successful in 4m25s
Continuous integration / Trunk (push) Successful in 7m10s
Continuous integration / Rustfmt (push) Successful in 28s
Continuous integration / build (push) Successful in 4m42s
Continuous integration / Disallow unused dependencies (push) Successful in 2m12s
2025-09-14 11:00:40 -07:00
33c0a106b7 server: fix date parsing w/ TZ and cal widget highlight 2025-09-14 11:00:21 -07:00
030d1c2ebe chore: Release
Some checks failed
Continuous integration / Check (push) Successful in 2m10s
Continuous integration / Test Suite (push) Failing after 4m4s
Continuous integration / Trunk (push) Successful in 7m13s
Continuous integration / Rustfmt (push) Successful in 28s
Continuous integration / build (push) Successful in 4m36s
Continuous integration / Disallow unused dependencies (push) Successful in 2m8s
2025-09-11 17:41:08 -07:00
e386d7e74e server: address lint
Some checks failed
Continuous integration / Check (push) Has been cancelled
Continuous integration / Test Suite (push) Has been cancelled
Continuous integration / Trunk (push) Has been cancelled
Continuous integration / Rustfmt (push) Has been cancelled
Continuous integration / build (push) Has been cancelled
Continuous integration / Disallow unused dependencies (push) Has been cancelled
2025-09-11 17:40:53 -07:00
55e38e96a0 chore: Release 2025-09-11 17:39:28 -07:00
7f47fe8de6 web: test plain text jammed in HTML more like plain-text 2025-09-11 17:39:04 -07:00
3889b855a5 server: fix date parsing w/ TZ and cal widget highlight 2025-09-11 16:31:43 -07:00
2c1c7abf0a Generated AI helper 2025-09-11 16:13:02 -07:00
9452a2b014 renovate: disable updating wasm-bindgen
Some checks failed
Continuous integration / Check (push) Successful in 2m6s
Continuous integration / Test Suite (push) Successful in 4m38s
Continuous integration / Rustfmt (push) Has been cancelled
Continuous integration / build (push) Has been cancelled
Continuous integration / Disallow unused dependencies (push) Has been cancelled
Continuous integration / Trunk (push) Has been cancelled
2025-09-11 15:45:21 -07:00
16e559c1c2 chore: Release
All checks were successful
Continuous integration / Check (push) Successful in 2m6s
Continuous integration / Test Suite (push) Successful in 4m5s
Continuous integration / Trunk (push) Successful in 7m29s
Continuous integration / Rustfmt (push) Successful in 33s
Continuous integration / build (push) Successful in 4m22s
Continuous integration / Disallow unused dependencies (push) Successful in 2m2s
2025-09-11 15:38:17 -07:00
93736b386f web: rollback wasm-bindgen 2025-09-11 15:37:54 -07:00
a632e6e6bc Merge pull request 'fix(deps): update rust crate chrono to v0.4.42' (#161) from renovate/all-minor-patch into master
All checks were successful
Continuous integration / Check (push) Successful in 2m7s
Continuous integration / Test Suite (push) Successful in 4m5s
Continuous integration / Trunk (push) Successful in 7m15s
Continuous integration / Rustfmt (push) Successful in 29s
Continuous integration / build (push) Successful in 4m21s
Continuous integration / Disallow unused dependencies (push) Successful in 2m2s
2025-09-08 03:01:20 -07:00
1c0f6d7ed4 fix(deps): update rust crate chrono to v0.4.42
All checks were successful
Continuous integration / Check (push) Successful in 2m11s
Continuous integration / Test Suite (push) Successful in 4m5s
Continuous integration / Trunk (push) Successful in 7m4s
Continuous integration / Rustfmt (push) Successful in 36s
Continuous integration / build (push) Successful in 4m50s
Continuous integration / Disallow unused dependencies (push) Successful in 2m3s
2025-09-08 09:31:30 +00:00
51b0b719d3 Merge pull request 'fix(deps): update all non-major dependencies' (#160) from renovate/all-minor-patch into master
All checks were successful
Continuous integration / Check (push) Successful in 2m54s
Continuous integration / Test Suite (push) Successful in 4m48s
Continuous integration / Trunk (push) Successful in 9m9s
Continuous integration / Rustfmt (push) Successful in 51s
Continuous integration / build (push) Successful in 4m59s
Continuous integration / Disallow unused dependencies (push) Successful in 2m45s
2025-09-07 23:16:23 -07:00
2c6fee0fba fix(deps): update all non-major dependencies
All checks were successful
Continuous integration / Check (push) Successful in 2m17s
Continuous integration / Test Suite (push) Successful in 4m21s
Continuous integration / Trunk (push) Successful in 7m20s
Continuous integration / Rustfmt (push) Successful in 34s
Continuous integration / build (push) Successful in 4m37s
Continuous integration / Disallow unused dependencies (push) Successful in 1m57s
2025-09-08 04:46:37 +00:00
d33e9b87b2 revert 2830c238fd
All checks were successful
Continuous integration / Check (push) Successful in 2m8s
Continuous integration / Test Suite (push) Successful in 4m11s
Continuous integration / Trunk (push) Successful in 7m50s
Continuous integration / Rustfmt (push) Successful in 29s
Continuous integration / build (push) Successful in 4m20s
Continuous integration / Disallow unused dependencies (push) Successful in 2m9s
revert fix(deps): update rust-wasm-bindgen monorepo
2025-09-07 21:40:56 -07:00
02d5c7372c Merge pull request 'chore(deps): lock file maintenance' (#159) from renovate/lock-file-maintenance into master
Some checks failed
Continuous integration / Check (push) Failing after 30s
Continuous integration / Test Suite (push) Failing after 50s
Continuous integration / Trunk (push) Failing after 53s
Continuous integration / Rustfmt (push) Failing after 6s
Continuous integration / build (push) Failing after 1m8s
Continuous integration / Disallow unused dependencies (push) Failing after 15s
2025-09-07 19:32:05 -07:00
d51bcd81ed chore(deps): lock file maintenance
All checks were successful
Continuous integration / Check (push) Successful in 2m9s
Continuous integration / Test Suite (push) Successful in 3m7s
Continuous integration / Trunk (push) Successful in 1m23s
Continuous integration / Rustfmt (push) Successful in 1m7s
Continuous integration / build (push) Successful in 3m47s
Continuous integration / Disallow unused dependencies (push) Successful in 2m36s
2025-09-08 00:47:12 +00:00
4d9dc31f3b Merge pull request 'chore(deps): lock file maintenance' (#158) from renovate/lock-file-maintenance into master
All checks were successful
Continuous integration / Check (push) Successful in 1m34s
Continuous integration / Test Suite (push) Successful in 2m44s
Continuous integration / Trunk (push) Successful in 7m52s
Continuous integration / Rustfmt (push) Successful in 39s
Continuous integration / build (push) Successful in 2m58s
Continuous integration / Disallow unused dependencies (push) Successful in 2m34s
2025-09-07 17:46:48 -07:00
fba91293d9 chore(deps): lock file maintenance
All checks were successful
Continuous integration / Check (push) Successful in 1m36s
Continuous integration / Test Suite (push) Successful in 3m2s
Continuous integration / Trunk (push) Successful in 1m7s
Continuous integration / Rustfmt (push) Successful in 39s
Continuous integration / build (push) Successful in 3m35s
Continuous integration / Disallow unused dependencies (push) Successful in 2m19s
2025-09-08 00:02:00 +00:00
e1be683d73 Merge pull request 'fix(deps): update rust crate html2text to v0.15.5' (#157) from renovate/all-minor-patch into master
All checks were successful
Continuous integration / Check (push) Successful in 1m12s
Continuous integration / Test Suite (push) Successful in 2m10s
Continuous integration / Trunk (push) Successful in 57s
Continuous integration / Rustfmt (push) Successful in 38s
Continuous integration / build (push) Successful in 1m50s
Continuous integration / Disallow unused dependencies (push) Successful in 2m10s
2025-09-07 05:16:24 -07:00
c94b9f088a fix(deps): update rust crate html2text to v0.15.5
All checks were successful
Continuous integration / Check (push) Successful in 1m6s
Continuous integration / Test Suite (push) Successful in 1m57s
Continuous integration / Trunk (push) Successful in 1m0s
Continuous integration / Rustfmt (push) Successful in 40s
Continuous integration / build (push) Successful in 1m52s
Continuous integration / Disallow unused dependencies (push) Successful in 2m8s
2025-09-07 12:01:18 +00:00
663298d650 Merge pull request 'fix(deps): update rust crate html2text to v0.15.4' (#156) from renovate/all-minor-patch into master
All checks were successful
Continuous integration / Check (push) Successful in 53s
Continuous integration / Test Suite (push) Successful in 1m35s
Continuous integration / Trunk (push) Successful in 48s
Continuous integration / Rustfmt (push) Successful in 38s
Continuous integration / build (push) Successful in 1m54s
Continuous integration / Disallow unused dependencies (push) Successful in 2m10s
2025-09-07 03:31:24 -07:00
23e4d3b968 fix(deps): update rust crate html2text to v0.15.4
All checks were successful
Continuous integration / Check (push) Successful in 1m23s
Continuous integration / Test Suite (push) Successful in 1m45s
Continuous integration / Trunk (push) Successful in 7m15s
Continuous integration / Rustfmt (push) Successful in 45s
Continuous integration / build (push) Successful in 2m4s
Continuous integration / Disallow unused dependencies (push) Successful in 2m21s
2025-09-07 10:01:32 +00:00
f82fc5dc77 Merge pull request 'fix(deps): update rust-wasm-bindgen monorepo' (#154) from renovate/rust-wasm-bindgen-monorepo into master
All checks were successful
Continuous integration / Check (push) Successful in 1m11s
Continuous integration / Test Suite (push) Successful in 2m50s
Continuous integration / Trunk (push) Successful in 1m14s
Continuous integration / Rustfmt (push) Successful in 51s
Continuous integration / build (push) Successful in 2m27s
Continuous integration / Disallow unused dependencies (push) Successful in 2m21s
2025-09-04 13:46:39 -07:00
2830c238fd fix(deps): update rust-wasm-bindgen monorepo
All checks were successful
Continuous integration / Check (push) Successful in 1m10s
Continuous integration / Test Suite (push) Successful in 2m56s
Continuous integration / Trunk (push) Successful in 7m9s
Continuous integration / Rustfmt (push) Successful in 44s
Continuous integration / build (push) Successful in 2m22s
Continuous integration / Disallow unused dependencies (push) Successful in 2m13s
2025-09-04 20:16:31 +00:00
6cebb0e7d5 Merge pull request 'fix(deps): update rust crate log to v0.4.28' (#153) from renovate/all-minor-patch into master
All checks were successful
Continuous integration / Check (push) Successful in 1m13s
Continuous integration / Test Suite (push) Successful in 2m18s
Continuous integration / Trunk (push) Successful in 1m13s
Continuous integration / Rustfmt (push) Successful in 52s
Continuous integration / build (push) Successful in 2m43s
Continuous integration / Disallow unused dependencies (push) Successful in 2m22s
2025-09-03 13:46:30 -07:00
1837b5ef01 fix(deps): update rust crate log to v0.4.28
All checks were successful
Continuous integration / Check (push) Successful in 1m30s
Continuous integration / Test Suite (push) Successful in 3m14s
Continuous integration / Trunk (push) Successful in 1m19s
Continuous integration / Rustfmt (push) Successful in 39s
Continuous integration / build (push) Successful in 3m32s
Continuous integration / Disallow unused dependencies (push) Successful in 2m13s
2025-09-03 20:31:29 +00:00
48bf8ba81f Merge pull request 'fix(deps): update rust crate zip to v4.6.1' (#152) from renovate/all-minor-patch into master
All checks were successful
Continuous integration / Check (push) Successful in 1m7s
Continuous integration / Test Suite (push) Successful in 1m44s
Continuous integration / Trunk (push) Successful in 55s
Continuous integration / Rustfmt (push) Successful in 38s
Continuous integration / build (push) Successful in 2m21s
Continuous integration / Disallow unused dependencies (push) Successful in 2m12s
2025-09-03 01:45:57 -07:00
645d008278 fix(deps): update rust crate zip to v4.6.1
All checks were successful
Continuous integration / Check (push) Successful in 1m5s
Continuous integration / Test Suite (push) Successful in 2m21s
Continuous integration / Trunk (push) Successful in 1m0s
Continuous integration / Rustfmt (push) Successful in 40s
Continuous integration / build (push) Successful in 2m31s
Continuous integration / Disallow unused dependencies (push) Successful in 2m16s
2025-09-03 08:31:26 +00:00
0b94f482c7 Merge pull request 'chore(deps): lock file maintenance' (#151) from renovate/lock-file-maintenance into master
All checks were successful
Continuous integration / Check (push) Successful in 1m24s
Continuous integration / Test Suite (push) Successful in 2m5s
Continuous integration / Trunk (push) Successful in 1m21s
Continuous integration / Rustfmt (push) Successful in 56s
Continuous integration / build (push) Successful in 2m44s
Continuous integration / Disallow unused dependencies (push) Successful in 2m28s
2025-09-02 21:15:57 -07:00
067b480856 chore(deps): lock file maintenance
All checks were successful
Continuous integration / Check (push) Successful in 2m0s
Continuous integration / Test Suite (push) Successful in 4m9s
Continuous integration / Trunk (push) Successful in 1m54s
Continuous integration / Rustfmt (push) Successful in 1m4s
Continuous integration / build (push) Successful in 3m42s
Continuous integration / Disallow unused dependencies (push) Successful in 2m36s
2025-09-03 02:32:11 +00:00
11 changed files with 478 additions and 244 deletions

40
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,40 @@
# Copilot/AI Agent Instructions for Letterbox
## Project Overview
- **Letterbox** is a Rust monorepo for a mail/newsreader system with a web frontend and a Rocket/GraphQL backend.
- Major crates: `server` (backend, Rocket+async-graphql), `web` (Seed-based WASM frontend), `notmuch` (mail integration), `shared` (common types), `procmail2notmuch` (migration/utility).
- Data flows: Email/news data is indexed and queried via the backend, exposed to the frontend via GraphQL. SQLx/Postgres is used for persistence. Notmuch and custom SQL are both used for mail storage/search.
## Key Workflows
- **Development**: Use `dev.sh` to launch a tmux session with live-reloading for both frontend (`trunk serve`) and backend (`cargo watch ... run`).
- **Build/Release**: Use `just patch|minor|major` for versioned releases (runs SQLx prepare, bumps versions, pushes). `Makefile`'s `release` target does similar steps.
- **Frontend**: In `web/`, use `cargo make serve` and `cargo make watch` for local dev. See `web/README.md` for Seed-specific details.
- **Backend**: In `server/`, run with `cargo run` or via the tmux/dev.sh workflow. SQL migrations are in `server/migrations/`.
## Project Conventions & Patterns
- **GraphQL**: All API boundaries are defined in `server/src/graphql.rs`. Use the `Query`, `Mutation`, and `Subscription` roots. Types are defined with `async-graphql` derive macros.
- **HTML Sanitization**: See `server/src/lib.rs` for custom HTML/CSS sanitization and transformation logic (e.g., `Transformer` trait, `sanitize_html`).
- **Tag/Query Parsing**: The `Query` struct in `server/src/lib.rs` parses user queries into filters for notmuch/newsreader/tantivy.
- **Shared Types**: Use the `shared` crate for types and helpers shared between frontend and backend.
- **Custom SQL**: Raw SQL queries are in `server/sql/`. Use these for complex queries not handled by SQLx macros.
- **Feature Flags**: The `tantivy` feature enables full-text search via Tantivy. Check for `#[cfg(feature = "tantivy")]` in backend code.
## Integration Points
- **Notmuch**: Integrated via the `notmuch` crate for mail indexing/search.
- **Postgres**: Used for newsreader and other persistent data (see `server/migrations/`).
- **GraphQL**: All client-server communication is via GraphQL endpoints defined in the backend.
- **Seed/Trunk**: Frontend is built with Seed (Rust/WASM) and served via Trunk.
## Examples
- To add a new GraphQL query, update `server/src/graphql.rs` and expose it in the `QueryRoot`.
- To add a new frontend page, add a module in `web/src/` and register it in the Seed app's router.
- To run the full dev environment: `./dev.sh` (requires tmux, trunk, cargo-watch, etc.).
## References
- See `web/README.md` for frontend/Seed workflow details.
- See `Justfile` and `Makefile` for release/versioning automation.
- See `server/src/lib.rs` and `server/src/graphql.rs` for backend architecture and conventions.
- See `server/sql/` for custom SQL queries.
---
If any conventions or workflows are unclear, please ask for clarification or check the referenced files for examples.

410
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2021"
license = "UNLICENSED"
publish = ["xinu"]
version = "0.17.40"
version = "0.17.44"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
[profile.dev]

View File

@@ -3,4 +3,11 @@
"extends": [
"config:recommended"
]
,
"packageRules": [
{
"matchPackageNames": ["wasm-bindgen"],
"enabled": false
}
]
}

View File

@@ -32,8 +32,8 @@ futures = "0.3.31"
headers = "0.4.0"
html-escape = "0.2.13"
ical = "0.11"
letterbox-notmuch = { path = "../notmuch", version = "0.17.40", registry = "xinu" }
letterbox-shared = { path = "../shared", version = "0.17.40", registry = "xinu" }
letterbox-notmuch = { path = "../notmuch", version = "0.17.44", registry = "xinu" }
letterbox-shared = { path = "../shared", version = "0.17.44", registry = "xinu" }
linkify = "0.10.0"
lol_html = "2.3.0"
mailparse = "0.16.1"

View File

@@ -1,7 +1,7 @@
use std::io::{Cursor, Read};
use askama::Template;
use chrono::{Datelike, Local, LocalResult, TimeZone, Utc};
use chrono::{Datelike, LocalResult, TimeZone, Utc};
use chrono_tz::Tz;
use mailparse::{parse_content_type, parse_mail, MailHeader, MailHeaderMap, ParsedMail};
use quick_xml::de::from_str as xml_from_str;
@@ -1837,39 +1837,42 @@ pub fn render_ical_summary(ical_data: &str) -> Result<String, ServerError> {
}
}
// Always use America/Los_Angeles for Google Calendar events if no TZID is present
let event_tz: Tz = tzid
.as_deref()
.unwrap_or("America/Los_Angeles")
.parse()
.unwrap_or(chrono_tz::America::Los_Angeles);
// Parse start/end as chrono DateTime
let (local_fmt_start, local_fmt_end, event_days, recurrence_display) =
if let Some(dtstart) = dtstart {
let tz: Tz = tzid
.as_deref()
.unwrap_or("UTC")
.parse()
.unwrap_or(chrono_tz::UTC);
let fallback = chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0)
.map(|dt| dt.with_timezone(&tz))
.map(|dt| dt.with_timezone(&event_tz))
.unwrap_or_else(|| {
tz.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
event_tz
.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
.single()
.unwrap_or_else(|| tz.timestamp_opt(0, 0).single().unwrap())
.unwrap_or_else(|| event_tz.timestamp_opt(0, 0).single().unwrap())
});
let start = parse_ical_datetime_tz(dtstart, tz).unwrap_or(fallback);
let start = parse_ical_datetime_tz(dtstart, event_tz).unwrap_or(fallback);
let end = dtend
.and_then(|d| parse_ical_datetime_tz(d, tz))
.and_then(|d| parse_ical_datetime_tz(d, event_tz))
.unwrap_or(start);
let local_start = start.with_timezone(&Local);
let local_end = end.with_timezone(&Local);
// Use the event's TZ for all calendar grid/highlighting logic
let allday =
dtstart.len() == 8 && (dtend.map(|s| s.len() == 8).unwrap_or(false));
let fmt_start = if allday {
local_start.format("%a %b %e, %Y").to_string()
start.format("%a %b %e, %Y").to_string()
} else {
local_start.format("%-I:%M %p %a %b %e, %Y").to_string()
start.format("%-I:%M %p %a %b %e, %Y").to_string()
};
let fmt_end = if allday {
local_end.format("%a %b %e, %Y").to_string()
end.format("%a %b %e, %Y").to_string()
} else {
local_end.format("%-I:%M %p %a %b %e, %Y").to_string()
end.format("%-I:%M %p %a %b %e, %Y").to_string()
};
// All calendar grid and event_days logic below uses start/end in event's TZ
// Recurrence support: parse RRULE and generate event_days accordingly
let mut days = vec![];
@@ -2144,6 +2147,39 @@ fn parse_ical_datetime_tz(dt: &str, tz: Tz) -> Option<chrono::DateTime<Tz>> {
#[cfg(test)]
mod tests {
#[test]
fn google_calendar_email_thursday_highlights_thursday() {
use mailparse::parse_mail;
let raw_email = include_str!("../../server/testdata/google-calendar-example-thursday.eml");
let parsed = parse_mail(raw_email.as_bytes()).expect("parse_mail");
let mut part_addr = vec![];
let body = extract_body(&parsed, &mut part_addr).expect("extract_body");
let meta = extract_calendar_metadata_from_mail(&parsed, &body);
// Assert detection as Google Calendar
assert!(meta.is_google_calendar_event);
let html = meta.body_html.expect("body_html");
// Print event date info for debugging
for part in parsed.subparts.iter() {
if part.ctype.mimetype == TEXT_CALENDAR {
if let Ok(ical) = part.get_body() {
println!("ICAL data: {}", ical);
if let Some(start) = ical.lines().find(|l| l.starts_with("DTSTART:")) {
println!("Start date: {}", start);
}
}
}
}
println!("Rendered HTML: {}", html);
// Look for September 11 (Thursday) being highlighted
// The calendar should show Sept 11 highlighted with background:#ffd700 and the correct data-event-day
assert!(html.contains(r#"data-event-day="2025-09-11""#));
assert!(html.contains(r#"background:#ffd700"#));
// Since 1:00 AM UTC on Friday 9/12 is 6:00 PM PDT on Thursday 9/11, verify times are correct
assert!(html.contains("6:00 PM Thu Sep 11, 2025"));
}
use super::*;
#[test]
fn google_calendar_email_3_single_event_metadata() {

View File

@@ -74,13 +74,7 @@
{% for week in all_days|batch(7) %}
<tr>
{% for day in week %}
{% if event_days.contains(day) && today.is_some() && today.unwrap() == day %}
<td
data-event-day="{{ day.format("%Y-%m-%d") }}"
style="background:#ffd700; color:#222; font-weight:bold; border:2px solid #2196f3; border-radius:4px; text-align:center; box-shadow:0 0 0 2px #2196f3;">
{{ day.day() }}
</td>
{% elif event_days.contains(day) %}
{% if event_days.contains(day) %}
<td
data-event-day="{{ day.format("%Y-%m-%d") }}"
style="background:#ffd700; color:#222; font-weight:bold; border:1px solid #aaa; border-radius:4px; text-align:center;">

View File

@@ -0,0 +1,175 @@
Return-Path: <couchmoney+caf_=gmail=xinu.tv@gmail.com>
Delivered-To: bill@xinu.tv
Received: from phx.xinu.tv [74.207.253.222]
by nixos-01.h.xinu.tv with IMAP (fetchmail-6.5.1)
for <wathiede@localhost> (single-drop); Thu, 11 Sep 2025 12:27:35 -0700 (PDT)
Received: from phx.xinu.tv
by phx.xinu.tv with LMTP
id CqRrBqciw2hiKicAJR8clQ
(envelope-from <couchmoney+caf_=gmail=xinu.tv@gmail.com>)
for <bill@xinu.tv>; Thu, 11 Sep 2025 12:27:35 -0700
X-Original-To: gmail@xinu.tv
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2a00:1450:4864:20::130; helo=mail-lf1-x130.google.com; envelope-from=couchmoney+caf_=gmail=xinu.tv@gmail.com; receiver=xinu.tv
Authentication-Results: phx.xinu.tv;
dkim=pass (2048-bit key; unprotected) header.d=google.com header.i=@google.com header.a=rsa-sha256 header.s=20230601 header.b=dc+iKaXd;
dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20230601 header.b=kf8o8wAd
Received: from mail-lf1-x130.google.com (mail-lf1-x130.google.com [IPv6:2a00:1450:4864:20::130])
by phx.xinu.tv (Postfix) with ESMTPS id D7E2D80037
for <gmail@xinu.tv>; Thu, 11 Sep 2025 12:27:33 -0700 (PDT)
Received: by mail-lf1-x130.google.com with SMTP id 2adb3069b0e04-55f716e25d9so1141446e87.1
for <gmail@xinu.tv>; Thu, 11 Sep 2025 12:27:33 -0700 (PDT)
ARC-Seal: i=2; a=rsa-sha256; t=1757618852; cv=pass;
d=google.com; s=arc-20240605;
b=MZ+1JfQuPR9luCCxiZNUeqSEpjt1vLuM3bTRCaal/W0NBxkCH0y5v9WfPR0KJ2BPb1
Rtnt/5ayDtmsLf8l6yTTVsBlFYW70ehqXWMD10MMcDEMvnib4KKDAacGaSmijAK4cYGq
FOU9CGNY986OMXMk54TD9NF3fkKDIKcAoh81D6at5/DE3Puuxofq0vZmtmVqQBNKG169
REkhcDpkXTMs/4rJpmZwXp2HbjD84avusBwSlYIQUWsBgO4g7THHjoR4Uk56cek9aEds
ip8IkTO6KRFe6u8FebQsZ/Q9sSAK3pheMExWFVMha9Y0XhACVOZiV600zRCPS9MNHhYw
XEaA==
ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605;
h=to:from:subject:date:message-id:auto-submitted:sender:reply-to
:mime-version:dkim-signature:dkim-signature:delivered-to;
bh=mVNsDGUAhSGrAIoTy8PIfvCBxBB4yaBy/VZH8i3gPl4=;
fh=WnbwIlqFRbBot/H7TyqablNBDgXRuegsgjC3piothTI=;
b=aYMo5f7VI2b4CiAvLELRJ9zM3dF7ZH8FEqmoAtCcfPHrT9kLLCnriuyXG1R6sC3eoR
++boT29xoScVroIlfcI77Ty7N5X1fawOABkVDWWt7z5w4WhiesT0klxw5nINj9hnLBiK
22nrMevpRpFtmuDO7cle78lSAFZoZuyv+aXCK9RnLKvIm2JuXRrvU8LivxbbpNB4gNl0
hE1jsGuZm1SOJ54SRLwwa4HpSiOJV2x2txTtPCzmvE/LZvNESPjfi3Y2u7gaR87OzkNs
gNi5Xoc+D908zBsmcYKpUYiQcPL79s3DfNwYFIs/rR8Z2xgaHbFD/YmqRUmCEeNLv7o2
RR8g==;
darn=xinu.tv
ARC-Authentication-Results: i=2; mx.google.com;
dkim=pass header.i=@google.com header.s=20230601 header.b=dc+iKaXd;
dkim=pass header.i=@gmail.com header.s=20230601 header.b=kf8o8wAd;
spf=pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) smtp.mailfrom=tconvertino@gmail.com;
dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com;
dara=pass header.i=@gmail.com
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20230601; t=1757618852; x=1758223652;
h=to:from:subject:date:message-id:auto-submitted:sender:reply-to
:mime-version:dkim-signature:dkim-signature:delivered-to
:x-forwarded-for:x-forwarded-to:x-gm-message-state:from:to:cc
:subject:date:message-id:reply-to;
bh=mVNsDGUAhSGrAIoTy8PIfvCBxBB4yaBy/VZH8i3gPl4=;
b=GKJkb+LmE79XIMEhHRvoCodKS+GBTOCShzMe06Q+zKxUZFHi6XMg8GqteuXQO9LVbw
nPUVN4QO2Hvqch0xzjbc0ryyMOD0u7HqpDUAEZCzamFXIfsX6hZXKLhFqy4YomtsG3os
TCOWBGLqwu7KalfOVg2p+csOR68i0mGyBII1sKcL9vUv9kIQJZxQKHGkuIc48cf6tbUB
L+mkVbMwXLSbpuTJszPmIVZV5o0K52KN+2QoLcmXGfw0mUOnjNI0oSovdbPg4SSDZ3cw
iIsC9vjvtCSFS3pf+Fp807s+Zjh5P6xeSxGU57qhC+HT9kTzIioh5EqKnGqcskDTqrI1
uCiQ==
X-Forwarded-Encrypted: i=2; AJvYcCUfSSA2sT31daRt2+W7dAD9YPx1gqa4JFpVuqCtxVtjqbKfKhOX/EcDQiECQ4BEWjmAP+IqTQ==@xinu.tv
X-Gm-Message-State: AOJu0Ywn7D0BjTaGiM/UFG0WhGuyYGfpLijg+ouhrOaGZzSREyTcRa37
XA3bzQ/LKTpzWhhh01GMwnigmELbWdIVr/BeRLVCuJdh+m+JBMgnAjBTIDs9RF3/xfR7rpG7VOB
6k+ugF+8QRKB4BcL2t8MvfJD03CkrzuhhvUtFTRHopcSZrkqzh8GOJayq42VveQ==
X-Received: by 2002:a05:6512:3b24:b0:55f:6580:818c with SMTP id 2adb3069b0e04-57050fe2fa3mr165340e87.46.1757618851553;
Thu, 11 Sep 2025 12:27:31 -0700 (PDT)
X-Forwarded-To: gmail@xinu.tv
X-Forwarded-For: couchmoney@gmail.com gmail@xinu.tv
Delivered-To: couchmoney@gmail.com
Received: by 2002:a05:6504:d09:b0:2c3:f6c4:ad72 with SMTP id c9csp3388833lty;
Thu, 11 Sep 2025 12:27:29 -0700 (PDT)
X-Received: by 2002:a05:6602:36ce:b0:889:b536:779b with SMTP id ca18e2360f4ac-8903378d714mr78653239f.7.1757618849269;
Thu, 11 Sep 2025 12:27:29 -0700 (PDT)
ARC-Seal: i=1; a=rsa-sha256; t=1757618849; cv=none;
d=google.com; s=arc-20240605;
b=Ln2bufZfSNhR/NmMPrG2QFdtvupjJtLDQnFvsL8HTPn+Dlrt5ff+6k6Wpupab/5mS7
hXjtVD0jnryGUiM5h+SNjxwzNPM3PBoueTpAzzBkjHQqMxJVpspgsGJUVOWAVRBWtWo
39qFyoP0vhzGRWDAuAFV+4VDhsvH7GL8lTrZCSMzrngTadmEdJ5haUIQOa50KFUn5HrK
1r12gayb+TaGaWfQfDo0Me689T8MQnS0ITUuzgvFxfgHZBz3h+IPnC0hrlhdziGovETo
GvHzgCCtiVzu6rop6VMLjLuAYmmT9+jZ3GjSRb+078C9cJR17YpguOC14Cyv4od1Tf7y
RFiQ==;
dara=google.com
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605;
h=to:from:subject:date:message-id:auto-submitted:sender:reply-to
:mime-version:dkim-signature:dkim-signature;
bh=mVNsDGUAhSGrAIoTy8PIfvCBxBB4yaBy/VZH8i3gPl4=;
fh=mbzrMIWIgWMC0ni1xEx+ViW4J0RLAdLdPT2cX81nTlk=;
b=JRkHr3CKSkCrafdLzBRtaBOGNl3/0ZSTtgubaNXtvhAiIqRqiQYocfLnVM6N/9sH7O
byTXYaRoaRLw/35WM+QTFGP3zUGRkM3eO4UVS/utVIss1IVLDjfmZHalqLYl8RokW5br
89Z/xYIyjTE7WUdy6uMSrExCNm5VWjO/qcMKsE5s5oDbXdSLaUYxLTurICM3LQksGkCY
wiAWaDDqK14+uhEhW5AyEnebDSYhL9U8UadIv+eK6Ng9q1kwOUzxICRQXEyUtnKhaDKJ
eZ1Qe1mp1CjCulr+I15fz3VwUJ6W1cv6cytcxPbu4p5GPn2gb2hS1eR81HVTL6V1Sp5G
NdDQ==;
dara=google.com
ARC-Authentication-Results: i=1; mx.google.com;
dkim=pass header.i=@google.com header.s=20230601 header.b=dc+iKaXd;
dkim=pass header.i=@gmail.com header.s=20230601 header.b=kf8o8wAd;
spf=pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) smtp.mailfrom=tconvertino@gmail.com;
dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com;
dara=pass header.i=@gmail.com
Received: from mail-sor-f73.google.com (mail-sor-f73.google.com. [209.85.220.73])
by mx.google.com with SMTPS id ca18e2360f4ac-88f2ea1122asor117632339f.3.2025.09.11.12.27.29
for <couchmoney@gmail.com>
(Google Transport Security);
Thu, 11 Sep 2025 12:27:29 -0700 (PDT)
Received-SPF: pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) client-ip=209.85.220.73;
Authentication-Results: mx.google.com;
dkim=pass header.i=@google.com header.s=20230601 header.b=dc+iKaXd;
dkim=pass header.i=@gmail.com header.s=20230601 header.b=kf8o8wAd;
spf=pass (google.com: domain of tconvertino@gmail.com designates 209.85.220.73 as permitted sender) smtp.mailfrom=tconvertino@gmail.com;
dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com;
dara=pass header.i=@gmail.com
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=google.com; s=20230601; t=1757618849; x=1758223649; dara=google.com;
h=to:from:subject:date:message-id:auto-submitted:sender:reply-to
:mime-version:from:to:cc:subject:date:message-id:reply-to;
bh=mVNsDGUAhSGrAIoTy8PIfvCBxBB4yaBy/VZH8i3gPl4=;
b=dc+iKaXdFyqu6K0MIgk848QuwpQXvwzwlEVkxmjuCWvn9DzanMbYn5QJRyRTKilRna
BZ7gJSPriHUHcJd4fVKgGuCaQg0TxenCwm+0R64oB1xcDLfonayo/nCrFqEcCLHNmi7x
lTyWGJ0rLw6nKazxtcCdIbDhVgiE7/fXNI89w6XFp6pcKLl48yFIoCG1f6uY4iQ7QqNU
hLHzjmlzjTi58xFLao7SizZ0lr7E5cHXKHp1Ls/hkDzzcY0Y+O5+3r+NQw4MtpHTcY6/
kQlg6OhyMx8PTu4cuepQKXLHV4aFaNJbDQTp8wew4xPIgi7pm2p6hb6C3GgwY6ptOvLd
wuag==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=gmail.com; s=20230601; t=1757618849; x=1758223649; dara=google.com;
h=to:from:subject:date:message-id:auto-submitted:sender:reply-to
:mime-version:from:to:cc:subject:date:message-id:reply-to;
bh=mVNsDGUAhSGrAIoTy8PIfvCBxBB4yaBy/VZH8i3gPl4=;
b=kf8o8wAd5DSU/NC7SDiuIoohCu+/7wTjWyQqDYbBjUFGaBaYdj6aD5JWNQ1KEA2W8o
E+Qy2ymyrzodKa1eOsQX2UDAYKOKpdxMWvx1u19+SC3Dp8DP4puRMrL2ObiSEMLCuOvz
Mxmkd+ZUP72EhVuQwK1iSm04/cjQaMsSiPhvSBaxXMaaarwlKeOoCoIo+qC/Z9emiBBv
Gk0sQcLA+CByvsxuvD9GInSA0rdoZ0ijhSb0Y475Hieam1QQqy/fhe8lgujzhXNFoIbR
5EA9GE0VV9PDoNanaT+u954YeOFBL2YZ5gm2gHltw8tBI98LKnC42Pa3qyMznBa2dI2Q
A0RQ==
X-Google-Smtp-Source: AGHT+IGmC5/03nTVMeYJBoq1R/BiA19iH0DFaZyyImB3W8mtgjdn+XqIFK1fC8aTwWRXQmsr71Xo0cmkgx6hjPvicQ/d
MIME-Version: 1.0
X-Received: by 2002:a05:6602:380d:b0:887:4c93:f12c with SMTP id
ca18e2360f4ac-8903596aca3mr58994639f.17.1757618848817; Thu, 11 Sep 2025
12:27:28 -0700 (PDT)
Reply-To: tconvertino@gmail.com
Sender: Google Calendar <calendar-notification@google.com>
Auto-Submitted: auto-generated
Message-ID: <calendar-01d5e8a0-fad7-450b-9758-a16472bf2aa8@google.com>
Date: Thu, 11 Sep 2025 19:27:28 +0000
Subject: Canceled event: Scout Babysits @ Thu Sep 11, 2025 6pm - 9pm (PDT) (Family)
From: tconvertino@gmail.com
To: couchmoney@gmail.com
Content-Type: multipart/mixed; boundary="000000000000226b77063e8b878d"
--000000000000226b77063e8b878d
Content-Type: text/calendar; charset="UTF-8"; method=CANCEL
Content-Transfer-Encoding: 7bit
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:CANCEL
X-GOOGLE-CALID:g66m0feuqsao8l1c767pvvcg4k@group.calendar.google.com
BEGIN:VEVENT
DTSTART:20250912T010000Z
DTEND:20250912T040000Z
DTSTAMP:20250911T192728Z
UID:4ang6172d1t7782sn2hmi30fgi@google.com
CREATED:20250901T224707Z
DESCRIPTION:
LAST-MODIFIED:20250911T192728Z
LOCATION:
SEQUENCE:1
STATUS:CANCELLED
SUMMARY:Scout Babysits
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR
--000000000000226b77063e8b878d--

View File

@@ -12,7 +12,7 @@ version.workspace = true
[dependencies]
build-info = "0.0.41"
letterbox-notmuch = { path = "../notmuch", version = "0.17.40", registry = "xinu" }
letterbox-notmuch = { path = "../notmuch", version = "0.17.44", registry = "xinu" }
regex = "1.11.1"
serde = { version = "1.0.219", features = ["derive"] }
sqlx = "0.8.5"

View File

@@ -33,7 +33,7 @@ wasm-bindgen = "=0.2.100"
uuid = { version = "1.16.0", features = [
"js",
] } # direct dep to set js feature, prevents Rng issues
letterbox-shared = { path = "../shared/", version = "0.17.40", registry = "xinu" }
letterbox-shared = { path = "../shared/", version = "0.17.44", registry = "xinu" }
seed_hooks = { version = "0.4.1", registry = "xinu" }
strum_macros = "0.27.1"
gloo-console = "0.3.0"

View File

@@ -1058,6 +1058,8 @@ fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool)
},
) => div![
C!["view-part-text-html"],
// If there isn't any HTML tags, treat more like plain text
IF!(!(contents.contains('<') && contents.contains('>')) => C!["whitespace-pre-line"]),
raw![contents],
IF!(!msg.attachments.is_empty() => render_attachements(&msg.attachments)),
view_content_tree(&content_tree),