Production readiness: Phase 0 (stabilize) + Phase 1 (send pipeline)#1
Open
ilyk wants to merge 18 commits into
Open
Production readiness: Phase 0 (stabilize) + Phase 1 (send pipeline)#1ilyk wants to merge 18 commits into
ilyk wants to merge 18 commits into
Conversation
The working tree held the actual GTK4 application (asgard-mail/) as untracked while the git index still described the deleted app/ and simple-gtk-app/ prototype trees. This commits the real codebase so the repository reflects what is actually built. - Add asgard-mail/ (main binary), com.asgard.Mail.metainfo.xml, and the untracked core modules (mail_rules, smart_mailbox, pgp) + ui-components - Remove the obsolete app/ prototype tree (55 files) - Commit Cargo.lock (binary app: lockfile belongs in VCS; also required for the Flatpak offline build) - Fix .gitmodules to an https URL so anonymous clone --recursive works - Untrack logs/, remove debug_output*.txt and Cargo-no-webkit.toml - Move stale root status docs to docs/history/ Part of the production-readiness plan (Phase 0). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
The main binary statically links the bundled proton-api-rs fork, which is AGPL-3.0-only, so the combined distributed work must be AGPL-3.0. The tree previously mislabeled everything as MIT. - Replace root LICENSE with the AGPLv3 text - workspace Cargo.toml: license = AGPL-3.0-only - Add license = AGPL-3.0-only to the four crates that had no license field (asgard-mail, email-backend, minimal-app, minimal-core) - Update README badge + License section and metainfo project_license Part of the production-readiness plan (Phase 0). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
Three paths previously told the user an action succeeded when it did nothing, causing silent data loss. Interim honesty until the real implementations land in later phases: - Offline send (all 4 compose/reply/reply-all/forward paths): no longer claims "queued for delivery when online" — the outbox is not wired yet (WP-1.4). Now says the message was NOT sent, retry when online. - Delete keyboard action: confirmation dialog no longer promises permanent deletion / Trash move; states it only hides the row locally and the message returns on next sync (real delete is WP-2.6). - Toolbar delete: toast no longer says "Moved to Trash"; says removed from this device, not yet deleted on the server. - Schedule Send button: hidden until the outbox is wired (WP-1.4); its callback was never set, so the picker silently discarded the message. Part of the production-readiness plan (Phase 0). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…ild (WP-0.5, WP-0.6) CI (WP-0.5): - ci.yml/release.yml: checkout submodules recursively; swap the wrong libwebkit2gtk-4.1-dev for libwebkitgtk-6.0-dev (+ libgdk-pixbuf-2.0-dev, libenchant-2-dev) so the webview default feature actually builds. - Drop the Windows and macOS jobs: this is a Linux/GNOME app (ksni, Secret Service, zbus, webkitgtk-6.0) that cannot build there. - clippy runs in -W warnings mode until the lint debt is cleared (WP-5.3); build/test scoped to buildable targets. - Add rustfmt.toml and format all first-party crates. The proton-api-rs submodule fork is intentionally left unformatted (own upstream history). Build env (WP-0.6): - Add docs/BUILD.md: exact apt dev-package list (libgtk-4-dev, libadwaita-1-dev, libwebkitgtk-6.0-dev, libgdk-pixbuf-2.0-dev), pkg-config verification, and cargo build/check commands. - Verified `cargo check -p asgard-mail --features webview` finishes with 0 errors against these packages. Part of the production-readiness plan (Phase 0). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
Introduces core::sync::SendRequest and OutgoingAttachment — the single structured payload for every send path, replacing the lossy (to, subject, body, reply_to) tuple that silently dropped Cc, Bcc, the HTML body, and attachments. Carries from_account_id so sends use the active identity instead of a first-account default. Includes helpers (parse_recipients, has_html, has_attachments, plain) with unit tests. Wiring backends and callers to it follows in WP-1.1/1.3. Part of the production-readiness plan (Phase 1). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…P-1.1, WP-1.3) Every send path now carries To/Cc/Bcc, the HTML alternative, and attachments end-to-end, and sends from the account the user is actually viewing instead of the first account in the database. - backend_bridge::send_message now takes &SendRequest and resolves the sending account from from_account_id (falls back to first account with a warning only when unset). Gmail sends full MIME via core::sync::encode_mime(..).to_gmail_raw(); Proton carries Cc + the HTML/plain body (full Proton send still gated on encryption, WP-2.4). - oauth: add GmailApiClient::send_raw(raw_base64url) — the fidelity path that posts a pre-built MIME message; send_message now delegates to it. - main.rs: extract build_send_request() + spawn_undo_send() helpers and route all four compose flows (new/reply/reply-all/forward) through them, removing ~250 lines of near-identical, drifting callback code. Each flow now passes the active account and all compose fields. - dbus_service + the 60s outbox poller build SendRequests too; the poller now preserves Cc/Bcc/HTML and the sending identity from the stored row (attachment restoration from JSON lands in WP-1.4). asgard-mail --features webview compiles clean; core send_request/mime tests pass. Part of the production-readiness plan (Phase 1). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
Adds core::sync::encode_mime(&SendRequest, from_address) -> EncodedMessage, producing a complete RFC 2822 message with correct multipart structure: - text/plain only, or multipart/alternative (text+HTML), or multipart/mixed wrapping either, when attachments are present. - To/Cc + In-Reply-To/References headers; Bcc is stripped from the header block and surfaced via EncodedMessage.bcc_recipients for the SMTP envelope. EncodedMessage::to_gmail_raw() gives base64url (no pad) for the Gmail messages.send `raw` field. - RFC 2047 encoded-words for non-ASCII headers; quoted-printable text parts; base64 attachment parts with folded lines. 14 unit tests cover each structure, boundary well-formedness, base64 round-trips (incl. all 256 byte values), Bcc isolation, and reply headers. Uses the existing base64 dependency; no new crates. Part of the production-readiness plan (Phase 1). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…(WP-1.4/1.5/1.6) Closes the remaining Phase 1 data-loss and fidelity gaps. WP-1.6 Signatures: - Settings dialog gains a per-account signature editor (multiline, in a MessageDialog) wired through a new AccountCallbacks::on_signature_changed; persisted via get_account/update_account (config.signature). - New and Forward compose now prepend the active account's signature. WP-1.5 Draft saving: - Drafts table + CRUD (save_draft/get_drafts/delete_draft) and a create_drafts_table migration (idempotent CREATE TABLE IF NOT EXISTS). - compose.rs gains set_draft_callback + save_draft_if_dirty; cancelling or closing a compose window with content now saves a draft instead of silently discarding it. WP-1.4 Offline + scheduled outbox (real, replacing the WP-0.3 interim): - backend_bridge: enqueue_outbox(&SendRequest, when), get_due_outbox_requests (returns fully reconstructed SendRequests incl. base64 attachments), plus attachment JSON (de)serialization. - Offline sends now enqueue to the outbox (epoch timestamp = due immediately) and flush via the 60s poller when back online — no more "queued" lie. - Schedule Send is wired (set_schedule_callback reveals the button) and stores into the outbox; picker timestamp fixed to SQLite datetime() format so the due-comparison works intra-day. - Poller rewritten to use get_due_outbox_requests, preserving Cc/Bcc/HTML, attachments, and the sending identity. main.rs: extracted build_send_request/spawn_undo_send/wire_compose_persistence helpers; all four compose flows route through them. asgard-mail --features webview compiles clean; asgard-core 184 pass / 5 fail (the pre-existing cache+tantivy failures, unchanged); new draft tests pass. Part of the production-readiness plan (Phase 1). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
First step of moving blocking BackendBridge operations off the GTK main thread. Adds asgard-mail/src/backend_actor.rs: - BackendCommand: the slow (network) operations that must not run on the UI thread — Send, EnqueueOutbox, SaveDraft, SyncProtonMailbox, Shutdown. - BackendHandle: a cloneable main-thread handle over Arc<Mutex<BackendBridge>> with `dispatch` (non-blocking command send) and `lock` (brief synchronous access for cheap local reads). - spawn_backend_actor: owns the bridge on a dedicated "backend-actor" thread and runs the command loop, reporting each result through the command's reply channel. Additive only — main.rs still uses the existing Rc<RefCell> bridge, so the app builds and behaves unchanged. Compiling this confirmed BackendBridge is Send (required to move it onto the actor thread). Call sites migrate onto this in WP-2.1b/c. Part of the production-readiness plan (Phase 2). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…(WP-2.1b) Migrates the ~80 bridge access sites in main.rs from Rc<RefCell<BackendBridge>> to backend_actor::BackendHandle (Arc<Mutex<BackendBridge>> owned by the actor thread). Bridge .borrow()/.borrow_mut() → .lock(); construction uses spawn_backend_actor(). app_state and trust-store RefCells are untouched. Behavior-preserving: slow operations still run synchronously via .lock() on the main thread for now (no freeze fix yet), but the bridge is now Send-shared and reachable from the actor thread — the prerequisite for WP-2.1c, which moves sends and Proton sync onto the actor so the UI stops freezing. Compiles clean under --features webview. Part of the production-readiness plan (Phase 2). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
Moves the two worst UI-freeze offenders off the GTK main loop: - Send: spawn_undo_send now dispatches BackendCommand::Send to the actor thread and polls the reply channel from a 100ms glib timeout to show the result toast — the SMTP/Gmail/Proton network round-trip no longer blocks the UI. (Offline enqueue stays inline: it is a fast local DB insert.) - Proton mailbox sync (both the manual-refresh and mailbox-open paths): previously ran inside spawn_future_local, which executes on the main thread, so the internal block_on froze the UI for the whole sync. Now dispatched to the actor via BackendCommand::SyncProtonMailbox (which replies with the full ProtonSyncResult); the UI refresh + reauth handling run when the reply arrives. Remaining main-thread block_on (Proton message decrypt-on-open, attachment download, login) are smaller/rarer and can move to the actor incrementally. Compiles clean under --features webview. Part of the production-readiness plan (Phase 2, WP-2.1). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…, part 1) Fixes the bug where adding an IMAP account stored the password in the system keyring but discarded the dialog result, creating no account (and leaving an orphan credential). The IMAP arm now builds ServerConfigs from the entered host/port (TLS on 993/465, STARTTLS otherwise), constructs an Account via Account::new_imap_smtp, persists it with create_account, creates the standard mailboxes (INBOX/Sent/Drafts/Trash/Spam/Archive), and registers it in the mock backend for display — mirroring the Gmail creation path. Duplicate-email guard included. Honest scope: the account is created and browsable, but background IMAP sync (live fetch) is a follow-on — the core ImapSync engine exists but is not yet started for is_imap() accounts, and can only be validated against a real IMAP server. Compiles clean under --features webview. Part of the production-readiness plan (Phase 2, WP-2.2). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…rch (WP-5.3)
Two real product-code bugs (not test scaffolding), both now covered green:
Cache (storage/cache.rs):
- store() wrote to messages/{hash[0..2]}/{hash} but only the top-level
messages/ attachments/ temp/ dirs were created, so message/attachment
writes failed with NotFound and retrieval missed. Now create_dir_all the
hash-sharded parent before write and before hard_link.
- store() counted as a cache "hit", corrupting hit/miss stats. Removed; only
retrievals update stats now.
Tantivy (search/tantivy_index.rs):
- Reader used ReloadPolicy::OnCommitWithDelay, so an add-then-search saw 0
docs. Force reader.reload() after each commit (add/remove/clear).
- Query terms were matched case-sensitively against the default tokenizer's
lowercased tokens ("Test" never matched indexed "test"). Lowercase query
terms in create_text_field_query.
asgard-core: 189 passed / 0 failed (was 184/5). Removes the last failing
gate from the CI test step.
Part of the production-readiness plan (Phase 5, WP-5.3). See EXECUTION_PLAN.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…(WP-3.1) The IsUnread/IsFlagged predicates compared message_flags.flag against 'read' and 'flagged', but flags are persisted as serde_json of the MessageFlags enum (rename_all="lowercase"), i.e. the column literally holds "seen" / "flagged" (with quotes). Result: an "Unread" smart mailbox matched every message and a "Flagged" one matched none — silently wrong sets. - Add MessageFlags::as_stored_sql_literal(), which derives the exact single-quoted stored encoding from serde so query SQL can never drift from the storage format. - IsUnread/IsFlagged now use it (compare against '"seen"' / '"flagged"'). - Correct the two pre-existing tests that asserted the buggy 'read'/'flagged' literals, and add a test pinning the stored-literal to serde output. asgard-core: 190 passed / 0 failed. Part of the production-readiness plan (Phase 3, WP-3.1). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…P-4.3) Migrations previously ran in two separate steps — apply the DDL, then insert the tracking row under a fresh lock. A crash in between left the migration applied-but-unrecorded, so the next startup re-ran it; for a non-idempotent ALTER TABLE ADD COLUMN (AddFlagColorColumn/AddSignatureColumn) the re-run failed with "duplicate column" and bricked startup permanently. Now the apply and the tracking-row insert run inside a single BEGIN/COMMIT transaction (SQLite supports transactional DDL), with ROLLBACK on failure. Removed the now-unused mark_migration_applied helper and strengthened the idempotency test to assert the migration count is stable across repeated runs. asgard-core: 190 passed / 0 failed. Part of the production-readiness plan (Phase 4, WP-4.3). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…(WP-4.4) - Remove the `dangerous_configuration` feature from the workspace rustls dependency. No first-party code uses rustls directly (only reqwest/lettre pull it transitively via their own *-rustls-tls features), so the feature — which enables disabling TLS certificate verification — was pure attack surface inviting a future one-line cert bypass. - Release profile: add lto = "thin", codegen-units = 1, strip = true to shrink the binary (~34MB unstripped → materially smaller). Deliberately NOT setting panic = "abort" so a worker/sync-thread panic unwinds that thread instead of aborting the whole app. asgard-core compiles and passes 190 tests unchanged. Part of the production-readiness plan (Phase 4, WP-4.4). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
…hase 6)
Packaging & community-readiness slices that are verifiable without a running
app:
- metainfo: add <developer> (clears the appstreamcli developer-info-missing
info; now validates with only 1 pedantic note) and <keywords>; document the
screenshots requirement that still needs a GUI capture before Flathub.
- Add CONTRIBUTING.md (AGPL, submodule clone, build via docs/BUILD.md, CI
gates), CODE_OF_CONDUCT.md (Contributor Covenant), SECURITY.md (private
disclosure + hardening scope), CHANGELOG.md (Keep a Changelog).
- README: fix the wrong WebKit dev package (libwebkit2gtk-5.0-dev →
libwebkitgtk-6.0-dev + point to docs/BUILD.md), replace the deleted app/
project layout with the real crate structure, and stop overclaiming
("production-ready" / POP3) with an honest pre-1.0 status note.
Part of the production-readiness plan (Phase 6). See EXECUTION_PLAN.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
The test called ClientId::secret(), which does not exist (secret() is on ClientSecret). ClientId exposes the value via as_str(). asgard-oauth test suite now compiles and passes (6/6), restoring the crate's coverage in the CI test step. Part of the production-readiness plan (Phase 5, WP-5.3). See EXECUTION_PLAN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012jQxSBEhcYQYo9dozfQQDM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Executes Phase 0 and Phase 1 of the production-readiness plan (see
EXECUTION_PLAN.md). Anchored to the verifiedPRODUCTION_GAP_REPORT.md.Phase 0 — Stop the bleeding & make the repo real
asgard-mail/app crate (was untracked; index held the deletedapp/prototype) +Cargo.lock; fix submodule URL to HTTPS; repo hygiene sweep.proton-api-rsfork.rustfmt.toml, format first-party crates.docs/BUILD.mdwith the exact dev-package list.Phase 1 — Fix the send pipeline (no more dropped data)
SendRequestcontract + MIME encoder (14 tests);send_message(&SendRequest)now carries Cc/Bcc/HTML/attachments and sends from the active account (not "first in DB"). Gmail sends full MIME via a newsend_rawpath. All four compose flows DRY'd through shared helpers.Verification
cargo check -p asgard-mail --features webview→ 0 errors.asgard-coretests: 184 pass / 5 fail — the 5 are pre-existing cache+tantivy failures documented in the GAP report (zero regressions); new MIME/draft/send tests pass.Known, deliberately deferred
BackendBridgestill does network I/O on the GTK main thread (Phase 2, WP-2.1).Reviewer notes
git submodule update --init --recursiveafter checkout.🤖 Generated with Claude Code