Offline compose: write mail offline, Scheduled folder, undo countdown fix
Compose, Reply and Forward work offline. Drafts are kept on the device (IndexedDB, lib/scheduled.ts), with files attached offline; Send becomes "Schedule" and puts the message in a new Scheduled folder. When the server is reachable again, scheduled messages are sent through the normal outbox (Undo offered for a single one) and offline drafts become server drafts — files uploaded first, each step recorded so nothing is created or sent twice, under a Web Lock so two tabs never send the same message. Drafts written offline show in Drafts as "On this device". Sign-out sends what is scheduled, then wipes the device's copy. Also: offline failures from keyboard shortcuts show "Not available offline"; no recipient suggestions are asked for offline; a reply no longer quotes an error page when the original is not saved. The undo countdown counted to the server's deadline on the browser's clock, so a few seconds of skew showed (18 s instead of 10). /send now also returns undo_seconds, and the composer counts down from that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Query, UploadFile
|
||||
@@ -300,7 +300,10 @@ async def send(body: SendRequest, user: CurrentUser, db: DbSession) -> SendRespo
|
||||
str(outbox.id),
|
||||
_defer_by=timedelta(seconds=get_settings().undo_seconds),
|
||||
)
|
||||
return SendResponse(outbox_id=outbox.id, undo_until=outbox.send_after.isoformat())
|
||||
left = max(0.0, (outbox.send_after - datetime.now(UTC)).total_seconds())
|
||||
return SendResponse(
|
||||
outbox_id=outbox.id, undo_until=outbox.send_after.isoformat(), undo_seconds=left
|
||||
)
|
||||
|
||||
|
||||
@router.post("/send/{outbox_id}/undo")
|
||||
|
||||
@@ -56,6 +56,9 @@ class SendRequest(BaseModel):
|
||||
class SendResponse(BaseModel):
|
||||
outbox_id: uuid.UUID
|
||||
undo_until: str
|
||||
# The same deadline as seconds from now: the browser counts down from this,
|
||||
# since its clock may be seconds off the server's.
|
||||
undo_seconds: float
|
||||
|
||||
|
||||
class OutboxStateOut(BaseModel):
|
||||
|
||||
@@ -398,6 +398,8 @@ async def test_send_creates_outbox_and_defers(ctx: Ctx) -> None:
|
||||
resp = await ctx.client.post("/api/send", json={"draft_id": did}, headers=_hdr())
|
||||
assert resp.status_code == 200
|
||||
outbox_id = resp.json()["outbox_id"]
|
||||
# The window as a duration too, so the browser's clock does not matter.
|
||||
assert 0 < resp.json()["undo_seconds"] <= get_settings().undo_seconds
|
||||
|
||||
ob = await ctx.session.get(Outbox, uuid.UUID(outbox_id))
|
||||
assert ob is not None and ob.state == "undo_window"
|
||||
|
||||
Reference in New Issue
Block a user