be6cbf90d7
Clean.Email-parity feature build-out plus a Stripe/Notion-style UI rebuild on Tailwind + shadcn-style primitives. Locks the hybrid-automation, Gmail-only, light+dark, incremental-rollout decisions and lays out the backend/frontend build sequence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
3.4 KiB
Markdown
76 lines
3.4 KiB
Markdown
# Spec: Activity Log, Undo & Activity Summaries
|
|
|
|
A unified, trustworthy record of everything automation did — and a way to take it back.
|
|
Built entirely on the `AutomationAction` table from `feature-rules-engine.md`; no new
|
|
storage.
|
|
|
|
## 1. Activity Log
|
|
|
|
- Backed by `AutomationAction` rows with `Status in (Applied, Rejected, Undone, Failed)`.
|
|
- `IAutomationActionService.GetActivityAsync(userId, take)` returns
|
|
`ActivityLogEntryDto`, newest first, grouped where it reads naturally
|
|
(e.g. "Archived 38 emails from LinkedIn — Rule: Social noise").
|
|
- Each entry exposes `CanUndo`:
|
|
- Safe actions (Archive/SkipInbox/ApplyLabel/MarkRead/Star) — always undoable while we
|
|
still hold `UndoStateJson` and the message exists.
|
|
- Trash — undoable (Gmail untrash) within Gmail's 30-day window.
|
|
- Hard delete — N/A (never automated).
|
|
|
|
### UI (`/app/activity`)
|
|
- Reverse-chronological feed with source chips (Rule / Policy / Screener / Age sweep),
|
|
action icon, affected count, timestamp, and an **Undo** button where `CanUndo`.
|
|
- Filter by source and action type. Date range. Search by sender.
|
|
|
|
## 2. Undo
|
|
|
|
`UndoAsync(userId, actionIds)`:
|
|
1. Load the `Applied` actions (verify `UserId`).
|
|
2. For each, parse `UndoStateJson` (captured pre-apply: which labels were present,
|
|
whether `INBOX` was set, whether it was in Trash).
|
|
3. Issue the inverse `BatchModifyAsync` / `BatchUntrash` to restore prior state.
|
|
4. Set `Status = Undone`, stamp `AppliedUtc = now` on the undo.
|
|
5. Log is append-only in spirit: the original row flips to `Undone` rather than being deleted.
|
|
|
|
`UndoStateJson` shape (kept tiny):
|
|
```json
|
|
{ "hadInbox": true, "labels": ["Label_12","Label_88"], "wasTrashed": false }
|
|
```
|
|
Captured by the engine/approval step immediately before mutating.
|
|
|
|
## 3. Activity Summaries (extends existing digest)
|
|
|
|
Clean.Email's "Activity Summaries" = periodic notification of what automation did. We
|
|
already have the SMTP digest infra (`IDigestService`, `DigestWorker`,
|
|
`User.DigestEnabled`). Extend rather than add:
|
|
|
|
- `DigestService.BuildHtml` gains an **"Automation activity since last digest"** section:
|
|
counts of archived/labeled/screened, pending-approval count (with a nudge to review),
|
|
top rules by volume, new screener senders.
|
|
- Pull from `AutomationAction` where `AppliedUtc > user.LastDigestSentUtc`.
|
|
- No new toggle — folds into the existing digest opt-in. (Optional later: a separate
|
|
`User.ActivitySummaryEnabled` if users want activity summaries without the analytics digest.)
|
|
|
|
## 4. Cleanup Reminders
|
|
|
|
A lightweight nudge when the inbox needs attention, reusing `DigestWorker`'s tick:
|
|
|
|
- If a user has **pending destructive approvals** older than `ReminderAfterDays` (default 3)
|
|
and digests are on, the next digest leads with "You have N actions awaiting approval."
|
|
- If automation is **off** but the dashboard health score is poor / unsubscribe backlog
|
|
is large, include a "Time to clean up" prompt with a deep link.
|
|
|
|
No new infrastructure — just content rules inside `DigestService`.
|
|
|
|
## 5. API
|
|
|
|
Covered by `feature-rules-engine.md` §7:
|
|
`GET /automation/activity`, `POST /automation/activity/undo`. Add query params for
|
|
filtering: `?source=&action=&from=&to=&q=`.
|
|
|
|
## 6. Security & safety
|
|
|
|
- [ ] Activity + undo scoped to `UserId`; action ownership re-checked on undo.
|
|
- [ ] Undo is best-effort and idempotent — undoing an already-undone/missing message
|
|
logs and no-ops rather than erroring.
|
|
- [ ] Log never exposes another user's senders/emails.
|