Commit Graph
10 Commits
Author SHA1 Message Date
lukasandClaude Opus 5.5 fba3c947bf Attachment previews, forwarded attachments, row dividers
Attachments in the reading pane are tiles with a preview: images and a PDF's
first page, rendered on the server (Pillow, pypdfium2) as WebP of at most
480 px, cached beside the blob store and in the browser. Tiles are all one
size — at most 230 px wide, narrower so three still fit in a row — and frame
each preview by its shape: close to the tile's it fills it, very tall / wide /
square ones show whole over a blurred copy, small ones are not enlarged, PDFs
show the top of the page. The name bar (checkbox, type, name, size, download)
slides in on hover and adapts to the tile's width; files without a preview
get a large type icon. Previews of opened conversations are kept offline.

Forward now carries the original's attachments: new compose attachments
sharing the stored blobs (POST /messages/{id}/forward-attachments).

Mailbox display / Settings → Appearance: "Show a dividing line between
rows" (off by default, per device), with a new switch. Settings drop the
"On a phone…" note, and "Language" reads "Spellchecking language".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:15:17 +02:00
lukasandClaude Opus 5.5 08d428de8a Mail list: name everyone who wrote in the thread, and call the user "me"
The sender column showed participants[0], and participants are rebuilt
newest message first — so replying to a thread put the user in that slot
and the list named them instead of the person who wrote in.

Thread summaries (list and search) now carry `senders`: the distinct From
addresses of the thread's visible messages, oldest first, with the same
Trash/Spam visibility as the message count. The list joins them and shows
the user's own account addresses as "me" ("me, Alice" for a thread the
user started), and the avatar prefers the other side of the conversation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:17:37 +02:00
lukasandClaude Opus 4.8 12a67825cc Sender avatars, PDF password preview, contacts autosave, account-view + Trash/Spam auto-purge, UI polish
Sender avatars (privacy-safe, server-fetched, cached):
- New GET /api/avatar resolves Gravatar (d=404) then the sender domain's favicon
  under the SSRF guard, Redis-cached (hit+miss); gated by SENDER_AVATARS_ENABLED.
  GuardedResponse gains raw bytes. Shown in the mail list, reading pane, People
  panel, and Contacts (overlay that falls back to initials on a miss).

PDF preview:
- Password-protected PDFs now prompt in-place and retry (pdf.js PasswordException),
  with a clear error for other load failures instead of a blank overlay. The
  password is remembered per attachment until the thread is left; clicking the
  field no longer dismisses the overlay.

Contacts:
- Autosave reply recipients: message.sent now carries to/cc (name+email); the
  contacts plugin subscribes and adds any non-contact recipients to the Autosaved
  group. Contact avatars show the gravatar/favicon via /api/avatar.

Mail views / retention:
- Clicking an account shows all its mail except Trash/Spam (was inbox-only).
- Daily cron autopurge_trash_spam permanently deletes Trash/Spam older than
  TRASH_RETENTION_DAYS (default 30; 0 disables) — server EXPUNGE + local rows.

UI polish:
- Ultimo signet logo in the Mail/Calendar/Contacts sidebar header and collapsed
  rail; consistent 11px top margin and vertically-aligned header row across states
  (fixed-size flex icon buttons); SVG favicon on the browser tab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
2026-08-06 19:23:27 +02:00
lukasandClaude Opus 4.8 dd0b089049 attachments preview overlay, docx/xlsx/odf rendering, composer drag-drop & undo-send UX
Frontend:
- Full-screen attachment preview overlay (AttachmentPreview) for image/pdf/txt
  plus office docs: .docx via docx-preview (rendered live into the sandboxed
  iframe so header/footer VML images survive), .xlsx via xlsx-preview, and
  .odt/.ods/.odp/.odg via a JSZip+DOMParser ODF walker (officeConvert).
- Type-aware attachment icons (green spreadsheet, blue document, red PDF,
  image/archive); new document/image/spreadsheet/print SVGs.
- Composer: drag-drop attachments (body drop inserts images inline, else
  attaches); inserted images are selectable/resizable/deletable (RichTextEditor).
- Undo-send: "Sending…" banner with live "N seconds to Undo" countdown.
- Search result list: pin/archive/delete update correctly; grey-out only for
  search hits; scoped "Search in Trash/Spam".

Backend:
- Search: prefix (search-as-you-type) matching, email-address search, and
  address-first result ordering; Trash/Junk excluded with scoped folder search.
- Attachments: RFC 6266 Content-Disposition for non-ASCII filenames; one-time
  inline image loading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rcj3TpydMTzTSmU7kunzf8
2026-07-26 16:15:49 +02:00
lukasandClaude Opus 4.8 49738740c0 M2: Meili search + saved searches, sync windows + backfill progress, threading & image fixes
Search:
- Meilisearch backend (app/search/meili.py) + factory wiring; pinned SDK 2.7
- Search-as-you-type: debounced query + Postgres prefix matching, built from
  the DB tokenizer so full email addresses match (email stays one lexeme)
- Saved searches API + sidebar virtual mailboxes; save affordance in the box

Account sync windows (SPEC §12 M2):
- Choose at setup: All (recent 30d first, then remainder), Last 3 months, New only
- Per-account sync_window + "Sync remaining mail" from settings (idempotent re-run)
- Provider windowed backfill (SINCE/BEFORE), message_count, newest_uid
- migration 0012: accounts.sync_window + backfill_total/backfill_done

Backfill progress UX:
- Floating progress card (auto-dissolves after 10s) + spinning sidebar ring that
  fills with the account colour; driven by sync.progress over WS

Thread list pagination:
- Server-side offset + accurate grand total, with filter/order in SQL; working
  prev/next across all synced mail

Reading panel:
- "Load images" one-off now actually renders images (?images=1) without trusting
  the sender; envelope carries images_allowed so a trusted sender shows no prompt

Threading:
- Subject fallback no longer merges messages that declare an explicit (different)
  parent; true siblings still group via a shared-parent reference match. Fixes
  unrelated auto-replies with the same subject landing in one thread

Backend 257 tests green, ruff + mypy --strict clean; frontend tsc/eslint/vitest clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
2026-07-23 23:00:08 +02:00
lukasandClaude Opus 4.8 b4e07f11aa collapse cross-account dupes, Lato UI font, mail-list selection & density tuning
Backend:
- Collapse identical cross-account deliveries in a thread: new messages.delivery_id
  (migration 0011) preserves the real Message-ID even when message_id_hdr is
  synthesized (§7.1); get_thread groups by it and reports also_in_account_ids; the
  list count counts distinct deliveries. Backfill script for existing rows.

Frontend:
- Bundle Lato (SIL OFL) as the UI font; unread mail-list rows use Black weight.
- Mail-list selection: hover-avatar checkbox, tri-state "select all", bulk
  Archive/Pin/Delete (Restore/Delete in Archive/Trash/Spam) with a count in the
  reading pane; unread blue dot; "also delivered to" note in the thread.
- Spacing/size tuning: sender-column width, icon gaps, avatar size, bar heights,
  and a round of -1px font sizes across the sidebar, mail list, and reading pane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
2026-07-22 21:49:48 +02:00
lukasandClaude Opus 4.8 fdb6ee002e mail list redesign, folder actions, empty-folder, unread badges & empty-state art
Frontend:
- Redesign the mail list rows (avatar + sender + subject/snippet + right rail),
  even-spaced attachment/pin/archive/trash icons, hover-reveal actions, pink
  always-on pin, monocolor paperclip, row gaps, wider sender-subject gap.
- Sidebar folder badges: Inbox unread + Drafts counts (live via WS + mutations).
- Trash/Spam/Archive: hover Restore icon (replaces Archive), permanent delete in
  Trash; "Empty trash" / "Empty spam" buttons on the sort bar.
- Reading pane empty state: email illustration above an enlarged
  "Select a conversation".
- Search field clear (X) button; mark-read after a 4s dwell instead of instantly.

Backend:
- GET /api/mailboxes/counts (inbox unread + drafts) for the sidebar badges.
- POST /api/folders/{special}/empty + empty_folder_job to permanently clear a
  Trash/Spam/Archive folder (server EXPUNGE + DB rows + thread cleanup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
2026-07-22 07:40:29 +02:00
lukasandClaude Opus 4.8 d650f16feb mail UX: restore-to-folder, draft/attachment fixes, printing, sidebar collapse
Backend:
- Restore returns each message to its original folder (e.g. Sent/Archive) instead
  of always the Inbox: new messages.restore_mailbox_id (migration 0010) +
  restore_thread_job.
- Drafts: expunge reconciliation for the Drafts poll (provider.list_uids /
  _reconcile_drafts) clears phantom "remote" drafts; send + discard drop the
  mirrored DB row; adopting a remote draft carries its real attachments into the
  compose flow.
- Thread detail/list are folder-aware: individually trashed/spam messages are
  hidden outside their folder, Trash/Spam show only their own messages, and the
  mail-list message count matches the view.

Frontend:
- Thread reading panel: fix newest-first card overlap/spacing (expand-all and
  single expand), hide Reply/Forward on trashed messages, close the Actions menu
  on message-body (iframe) clicks, scroll to the inline composer, and prompt
  Save draft / Discard on the X close.
- Composer: remount when switching drafts, dirty-tracking to stop duplicate IMAP
  mirrors, and close the From/More dropdowns on outside click.
- Sidebar: collapse/expand toggle (icons-only ~60px rail) and a log-out icon on
  Sign out.
- Mail-standard printing in a new window with reliable page margins (lib/print.ts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
2026-07-20 23:12:25 +02:00
lukasandClaude Opus 4.8 3951df9755 Two-way draft sync, signatures, sent/bcc fixes, and mail-list UI
Backend
- Sent copies: persist locally on send + periodic multi-folder CONDSTORE poll
  (Sent/Archive/Trash/Junk) so they thread and show without an IDLE-only INBOX.
- Bcc: transmitted copy omits Bcc, Sent copy keeps it; store/display bcc_addr.
- Drafts: mirror local drafts to the IMAP Drafts folder (append/replace/expunge);
  inbound sync of foreign drafts (un-threaded) with adopt-to-edit + dedup by UID.
- Per-user storage quota (5 GiB default) and manual "sync all folders" action.
- Account editing (display/color/server/password) and per-account HTML signature.
- Account view scoped to that account's inbox; new migrations 0006–0009.

Frontend
- Mail-list pagination, folder filters (All/Unread/Pinned), sort toggle, refresh.
- Aligned + widened mail-list margins; reading-pane bars fixed height.
- Rich-text composer: color palette, highlight, image; attachments; expand/pop-out.
- Signatures: auto-insert for new mail, replace on From change, reply insert.
- Restore/Delete actions per folder; drafts view merges local + remote, date-sorted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 15:27:09 +02:00
lukasandClaude Opus 4.8 a5ec0d21cb Initial commit: webmail client (M0/M1)
Three-pane webmail client.
- Backend: FastAPI + async SQLAlchemy + arq worker (Postgres/Redis), IMAP/SMTP
  sync, threading, search, drafts/outbox with undo, attachments, 2FA.
- Frontend: React 18 + Vite + TypeScript + TanStack Query + Tailwind; three-pane
  UI with rich-text composer, light/dark themes, resizable panels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:20:34 +02:00