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:
@@ -20,7 +20,7 @@ from sqlalchemy import select
|
||||
|
||||
from app.db import session_scope
|
||||
from app.models import Account
|
||||
from app.sync.engine import live_loop
|
||||
from app.sync.engine import live_loop, secondary_poll_loop
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -39,13 +39,29 @@ async def _run_account(account_id: uuid.UUID, stop: asyncio.Event) -> None:
|
||||
await asyncio.wait_for(stop.wait(), timeout=_RECONNECT_BACKOFF)
|
||||
|
||||
|
||||
async def _run_secondary(account_id: uuid.UUID, stop: asyncio.Event) -> None:
|
||||
while not stop.is_set():
|
||||
try:
|
||||
await secondary_poll_loop(account_id, stop)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception:
|
||||
logger.exception("secondary poll loop for %s crashed; retrying", account_id)
|
||||
with contextlib.suppress(TimeoutError):
|
||||
await asyncio.wait_for(stop.wait(), timeout=_RECONNECT_BACKOFF)
|
||||
|
||||
|
||||
async def start_supervisor(ctx: dict[str, Any]) -> None:
|
||||
"""Launch a live loop per live account (called from the worker on_startup)."""
|
||||
async with session_scope() as session:
|
||||
account_ids = list(await session.scalars(select(Account.id).where(Account.state == "live")))
|
||||
stop = asyncio.Event()
|
||||
ctx["supervisor_stop"] = stop
|
||||
ctx["supervisor_tasks"] = [asyncio.create_task(_run_account(aid, stop)) for aid in account_ids]
|
||||
tasks: list[asyncio.Task[None]] = []
|
||||
for aid in account_ids:
|
||||
tasks.append(asyncio.create_task(_run_account(aid, stop))) # INBOX IDLE
|
||||
tasks.append(asyncio.create_task(_run_secondary(aid, stop))) # other folders poll
|
||||
ctx["supervisor_tasks"] = tasks
|
||||
logger.info("live supervisor started for %d account(s)", len(account_ids))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user