Phase 4: triage actions (delete, move, mark; bulk select) (AUD-70)
Bulk select in the triage list, with delete/move/mark and a recoverable delete (FR-6, FR-9). - migration v4: messages.folder + messages.mailbox; folder/category views - internal/mailops: app folder -> Gmail label moves over IMAP (IPv4), locate by X-GM-MSGID, create labels lazily, move/save/reply/restore, flag marks - mailfetch: CREATE, UID MOVE (COPY+EXPUNGE fallback), STORE flags, X-GM-MSGID search - server: POST /actions/bulk with CSRF, UI delete confirmation, second confirmation for messages classified Spam (never auto-deleted), per-message and summary action-log rows (FR-13) - inbox: select-all toolbar, checkboxes, folder nav, Restore on non-inbox rows Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
47
README.md
47
README.md
@@ -9,17 +9,16 @@ pre-planning material and is not authoritative.
|
||||
|
||||
## Status
|
||||
|
||||
**Phase 3 — Classify.** The Go skeleton, session login, and HTMX dashboard
|
||||
(Phase 1), real mail over **IMAP with an app password, forced to IPv4**
|
||||
(Phase 2), and now **JEV classification** (Phase 3). Every message gets a
|
||||
category, an importance, and a suggested action, grouped in the triage list with
|
||||
the recommended action and confidence per row. The category list is editable in
|
||||
the UI (add, rename, remove) and JEV uses the current list; a plain-text rules
|
||||
table can override the classifier (FR-4). The FR-2 source address is resolved
|
||||
from `X-Pm-Original-To`, with `To`, the PurelyMail `Received … for` clause, and
|
||||
`Return-Path` as fallbacks; `Delivered-To` is never used. See
|
||||
[`docs/phase3-classify.md`](docs/phase3-classify.md) for the confirmed JEV shape
|
||||
and the phase evidence. Triage actions come in Phase 4.
|
||||
**Phase 4 — Triage actions.** The Go skeleton, session login, and HTMX
|
||||
dashboard (Phase 1), real mail over **IMAP with an app password, forced to IPv4**
|
||||
(Phase 2), **JEV classification** (Phase 3), and now **bulk triage actions**
|
||||
(Phase 4). The inbox has a select-all toolbar for delete, move, and mark (FR-6),
|
||||
and a folder view for the recoverable Delete/Saved/Replied-to Gmail labels (FR-9).
|
||||
A delete moves the message to a Gmail label (never a hard delete) and a row in
|
||||
the Delete folder can be **restored** to the inbox. Bulk delete needs a UI
|
||||
confirmation and a message classified Spam needs a second confirmation — spam is
|
||||
never auto-deleted (§10). Every action is appended to `actions_log` (FR-13). See
|
||||
[`docs/phase4-triage.md`](docs/phase4-triage.md) for the phase evidence.
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -28,6 +27,7 @@ cmd/email-agent/ service entry point (`-fetch`, `-classify`, `-classify-a
|
||||
internal/classify/ JEV (TypeSafe System One) client, rule engine, orchestration
|
||||
internal/config/ environment-driven configuration
|
||||
internal/mailfetch/ stdlib-only IMAP client, MIME parsing, FR-2 source address
|
||||
internal/mailops/ Gmail label moves and flag changes for triage actions
|
||||
internal/session/ server-side session store + signed cookie values
|
||||
internal/store/ SQLite open + schema (internal/store/schema.sql)
|
||||
internal/server/ HTTP routes, auth middleware, HTMX handlers
|
||||
@@ -124,6 +124,26 @@ in `classifications.category` and the Choice option sent to JEV, so the model
|
||||
always sees the current list. Renaming cascades to existing classifications;
|
||||
removing a category reassigns its messages to the first remaining category.
|
||||
|
||||
## Triage actions (Phase 4)
|
||||
|
||||
`internal/mailops` turns an app folder into a Gmail label and performs the move
|
||||
over IMAP, forcing IPv4 exactly like the read path:
|
||||
|
||||
| App folder | Gmail label |
|
||||
|---|---|
|
||||
| `inbox` | `INBOX` |
|
||||
| `delete` | `Delete` (recoverable; never a hard delete) |
|
||||
| `saved` | `Saved` |
|
||||
| `replied` | `Replied-to/<address the mail arrived at>` |
|
||||
|
||||
A move uses the IMAP `MOVE` extension (with a `COPY` + `\Deleted` + `EXPUNGE`
|
||||
fallback). Because Gmail gives each label its own `UID`, a message is located by
|
||||
its stable `X-GM-MSGID`, not by a stored `UID`. A message in the Delete, Saved,
|
||||
or Replied-to folder has a **Restore** button that moves it back to `INBOX`.
|
||||
|
||||
Every action appends to `actions_log`: one `move` or `mark` row per message,
|
||||
plus one `bulk_<action>` summary row.
|
||||
|
||||
## Run locally
|
||||
|
||||
```sh
|
||||
@@ -157,6 +177,7 @@ Caddy on `.35` proxies `mail.lab.audasmedia.com.au` to this port.
|
||||
| `POST` | `/actions/fetch` | session + CSRF | Runs one IMAP scan, returns the refreshed list |
|
||||
| `POST` | `/actions/classify` | session + CSRF | Classifies pending messages, returns the refreshed list |
|
||||
| `POST` | `/actions/reclassify` | session + CSRF | Re-classifies every message with the current categories and rules |
|
||||
| `POST` | `/actions/bulk` | session + CSRF | Bulk delete/move/mark and restore over selected message ids (FR-6/FR-9) |
|
||||
| `GET` | `/categories` | session | Category editor (FR-14) |
|
||||
| `POST` | `/categories/add` | session + CSRF | Adds a category |
|
||||
| `POST` | `/categories/rename` | session + CSRF | Renames a category and updates existing classifications |
|
||||
@@ -175,4 +196,6 @@ schema in `internal/store/schema.sql` creates the AGENTS.md §7 tables:
|
||||
`summaries`, plus `categories`, `schema_migrations`. Schema init is idempotent
|
||||
and runs on every start. Migration v2 adds `imap_uid` and `imap_uidvalidity` to
|
||||
`messages`; migration v3 seeds the first category list and a starter rules
|
||||
version once, so a category removed by the user is not silently re-added.
|
||||
version once, so a category removed by the user is not silently re-added;
|
||||
migration v4 adds `folder` and `mailbox` to `messages` for the Phase 4 triage
|
||||
folders.
|
||||
|
||||
Reference in New Issue
Block a user