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>
This commit is contained in:
2026-07-18 15:27:09 +02:00
co-authored by Claude Opus 4.8
parent a5ec0d21cb
commit 3951df9755
39 changed files with 1917 additions and 139 deletions
@@ -0,0 +1,30 @@
"""add users.quota_bytes
Per-user storage quota (bytes) so an administrator can define/override it later.
Defaults to 5 GiB for existing and new users.
Revision ID: 0006
Revises: 0005
Create Date: 2026-07-18
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
revision: str = "0006"
down_revision: str | None = "0005"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.execute(
"ALTER TABLE users ADD COLUMN quota_bytes bigint NOT NULL DEFAULT 5368709120"
)
def downgrade() -> None:
op.execute("ALTER TABLE users DROP COLUMN IF EXISTS quota_bytes")
@@ -0,0 +1,29 @@
"""add messages.bcc_addr
The Sent copy of an outgoing message includes a Bcc header (standard client
behavior) so the sender keeps a record of blind recipients; store it structured
alongside to/cc for display.
Revision ID: 0007
Revises: 0006
Create Date: 2026-07-18
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
revision: str = "0007"
down_revision: str | None = "0006"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.execute("ALTER TABLE messages ADD COLUMN bcc_addr jsonb NOT NULL DEFAULT '[]'")
def downgrade() -> None:
op.execute("ALTER TABLE messages DROP COLUMN IF EXISTS bcc_addr")
@@ -0,0 +1,29 @@
"""add drafts.imap_uid
Drafts are mirrored to the account's IMAP Drafts folder (as \\Draft messages) so
they can be continued on any standard mail client. This stores the UID of the
current server copy so it can be replaced/removed.
Revision ID: 0008
Revises: 0007
Create Date: 2026-07-18
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
revision: str = "0008"
down_revision: str | None = "0007"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.execute("ALTER TABLE drafts ADD COLUMN imap_uid integer")
def downgrade() -> None:
op.execute("ALTER TABLE drafts DROP COLUMN IF EXISTS imap_uid")
@@ -0,0 +1,28 @@
"""add accounts.signature
Per-account HTML signature, configured in Settings → Accounts and inserted into
outgoing messages by the composer.
Revision ID: 0009
Revises: 0008
Create Date: 2026-07-18
"""
from __future__ import annotations
from collections.abc import Sequence
from alembic import op
revision: str = "0009"
down_revision: str | None = "0008"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.execute("ALTER TABLE accounts ADD COLUMN signature text NOT NULL DEFAULT ''")
def downgrade() -> None:
op.execute("ALTER TABLE accounts DROP COLUMN IF EXISTS signature")