From 5d745d04db33f9fc4eb6c47f5a884d55337e03ad Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 18:42:42 +0200 Subject: [PATCH 1/3] Write design 63 and playbook 10, so the mail page's next steps are built from a picked design Design 63 turns the mail page into the operator's list with Owly beside it (ADR 0515 to 0519), in six milestones with their issues; its reference mockup is kept beside it, its data invented. Playbook 10 says how a page is designed before it is built: mockups with fixed data, the operator picks, the pick is the reference. The as-is of the mail modules now describes mail reading as delivered, the glossary gains design 63's words, design 59's milestone 4 is marked replaced, and AGENTS.md allows a design's mockup. Issues 1855 and 1856. --- 00-META/glossary.md | 24 ++- 00-META/process/00-overview.md | 1 + 00-META/process/10-visual-design.md | 64 ++++++ 00-META/process/playbooks.html | 10 +- 03-DESIGN/00-as-is/18-the-mail-modules.md | 165 ++++++++++++--- 03-DESIGN/01-to-be/59-the-mail-web-client.md | 9 +- 03-DESIGN/01-to-be/63-mockup/mail-page.html | 193 +++++++++++++++++ ...-mail-web-ui-with-mail-reading-and-owly.md | 199 ++++++++++++++++++ 03-DESIGN/01-to-be/README.md | 1 + AGENTS.md | 3 + 10 files changed, 635 insertions(+), 34 deletions(-) create mode 100644 00-META/process/10-visual-design.md create mode 100644 03-DESIGN/01-to-be/63-mockup/mail-page.html create mode 100644 03-DESIGN/01-to-be/63-the-mail-web-ui-with-mail-reading-and-owly.md diff --git a/00-META/glossary.md b/00-META/glossary.md index 6ea8b587..937b3c77 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -778,8 +778,8 @@ it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Dec operator granted, note each mail for the operator and tell them of the mail that matters, and send a mail only on the operator's Approve. **Recorded by** `mesh-mail` (its index database and attachment bucket, ADR 0359 §3; its account buckets, ADR 0439) and `mesh-mail-reader` (its database of mail entries, mail groups and reading lessons, -ADR 0459). **Downstream of** Identity and access and Operator and conversation; **upstream of** nothing. -**Decided by** ADR 0359, 0361, 0378, 0439, 0440, 0458, 0459, 0461, 0462, 0472. +ADR 0459; and its handling, action log and suggested actions, ADR 0517, ADR 0519). **Downstream of** Identity and access and Operator and conversation; **upstream of** nothing. +**Decided by** ADR 0359, 0361, 0378, 0439, 0440, 0458, 0459, 0461, 0462, 0472, 0515 to 0519. **Uses:** operator, person, module, seat; grant, identity (Identity and access); ask, warrant, operator channel, content rule, untrusted input (Operator and conversation). A *judgement* is one call of the model with no tools and no session (ADR 0278), in its plain sense. @@ -819,6 +819,26 @@ and no session (ADR 0278), in its plain sense. tools `mail_search` and `mail_read` only, the operator's senders and folders, and the reading lessons (ADR 0462). - **mail question** — what the operator writes in the mail conversation to find or understand mail. Not *ask* (Operator and conversation's request for the operator's input). +- **the operator's list** — what the mail page opens on: the tasks by their due date, the promises, *good to know*, + *read later* and *handled today*, for one mode (ADR 0515 §1). Always *the operator's list*: the bare *list* is + the classical list of mail, shown as *All mail*. +- **task** — what a mail asks the operator to do, with a due date or none, written by mail reading in its own words + (ADR 0516 §1). +- **promise** — what a mail says someone will do: *you promised*, *waiting on others* or *on its way*, with who and + when (ADR 0516 §1). +- **read later** — the mark of a mail worth reading that asks nothing (ADR 0516 §1). +- **handled** — what the operator marked done on the operator's list; it changes nothing in the mailbox (ADR 0517 + §1). +- **action log** — mail reading's record of every act of the operator on the mail page, never mail text, kept a + year (ADR 0517 §3). +- **Owly** — the mail page's assistant: it answers mail questions through mail reading, gives the morning briefing, + and suggests actions; it sends and changes nothing by itself (ADR 0515 §2, ADR 0518). +- **morning briefing** — Owly's account of the day, asked the first time the mail page opens each day (ADR 0518). +- **suggested action** — an *archive*, *report as phishing* or *unsubscribe* that mail reading puts forward for the + operator's Approve on the mail page (ADR 0519 §1). Not *proposal* (Operator and conversation's, which takes effect + on a warrant). +- **approval inbox** — the place on the mail page where the suggested actions wait, each with *Approve* and *Not + now* (ADR 0519 §4). ## The record — how this repository works diff --git a/00-META/process/00-overview.md b/00-META/process/00-overview.md index aa1e2c9f..6e5e78d1 100644 --- a/00-META/process/00-overview.md +++ b/00-META/process/00-overview.md @@ -62,6 +62,7 @@ A drawn overview of them, the cycle, a feature's steps, the review loop and an i | [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery | | [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one try, one review, one delivery | | [09](09-cleanup.md) | Cleanup | Merged branches, stale pull requests, finished worktrees and folders: read first, delete only what the operator approves, report the rest | +| [10](10-visual-design.md) | Visual design | A page the operator will use is about to be designed or rebuilt: mockups with fixed data, the operator picks, the pick is the reference | ## The cycle is checked diff --git a/00-META/process/10-visual-design.md b/00-META/process/10-visual-design.md new file mode 100644 index 00000000..b7eea1d6 --- /dev/null +++ b/00-META/process/10-visual-design.md @@ -0,0 +1,64 @@ +# Playbook 10 — Visual design + +**Trigger.** A page or a screen the operator will use is about to be designed or rebuilt, or the operator judges +one unusable. It runs before the decisions about its look are written and before any of its code: a page built +first and judged after it ships is rebuilt (the operator, 2026-10-22, on design 59's mail page, judged unusable +the day it was delivered). + +**Who runs it.** The lead of the design, on the larger model. Mockups may be drawn by an agent on the smaller +model from the lead's brief. The operator picks; nobody else does. + +## What it produces + +- **The reference**: one mockup the operator picked, kept with the design as `NN-mockup/` beside its to-be + document. It is a direction, not a pixel specification: the build matches what it shows the operator doing, + in the order and the weight it shows, not its exact pixels. +- **The decisions it needs**: what the pick changes about what the mesh keeps, shows or does, written as decision + records (playbook [02](02-graduation.md)) before the design's milestones (playbook [04](04-build-handoff.md)). +- **An acceptance test in the operator's words**: the page is done when the operator uses it, on the devices they + named, for the work the mockup shows. + +## Steps + +1. **Gather what the page must carry.** Read the as-is of what it replaces and the real shape of its data from the + mesh (how many items, how long a line, what is missing in practice), the operator's complaint in their own + words, and a short survey of how others solve it (a handful of products, one line each on what changes for + the person). Write it into the work folder; nothing goes on the mesh. +2. **Draw three to five mockups with fixed data.** Each is one static HTML file that opens from disk: invented + data shaped like the real (names, amounts, lengths, a long subject, an empty sender), no backend, a mock script + only where an interaction is the point. Each mockup is a different direction, not a variation of one; each + shows a phone's width and a desktop's. One index page links them all. +3. **Show them and ask one question.** Put the index on the machine the operator is at, and ask which direction to + keep, one question at a time with the lead's recommendation. A round's answer may be a pick, a combination of + two, or a change; the next round draws only that. Three rounds without a pick mean something the operator wants + is missing: ask what, before drawing again. +4. **Make the pick the reference.** Copy it into the design's `NN-mockup/`, with every name, address, employer, + mail address and amount of the operator's own replaced by invented ones: hq is public. Say in the design that + the reference is a direction, and link it. +5. **Write what is new as decisions,** each with the operator's Approve where it changes what they see, then the + design and its milestones. The design's acceptance tests end with the operator using the page live. +6. **Hold the build to it.** Playbook [07](07-feature-branches.md)'s review reads the built page against the + reference (a screenshot at each width beside the mockup), and the milestone is done only when the operator has + used the page. + +## Bounds + +| Waiting on | Bound | Past the bound | +|---|---|---| +| a round of mockups | 1 hour | say to the operator what is taking long | +| the operator's pick | the next exchange | ask once more, one question, with the recommendation | +| rounds without a pick | 3 | ask what is missing before drawing again | + +## Rules + +- No code reaches the mesh during a visual design: mockups live in the work folder until one is picked. +- Mockups use invented data, never a copy of the operator's mail, files or accounts. +- The operator's pick stands over the lead's taste; the lead's recommendation is said once, with its reason. + +## How it is checked + +- A to-be design of a page the operator uses links its reference mockup in `NN-mockup/`, and that mockup holds + none of the operator's own names, addresses or amounts (read by the reviewer of the design's pull request). +- The milestone's review report shows the built page beside the reference at a phone's and a desktop's width. +- The design's last acceptance test is the operator using the page, and it is proven before the milestone's + issues are resolved. diff --git a/00-META/process/playbooks.html b/00-META/process/playbooks.html index 1e4d442c..69a41178 100644 --- a/00-META/process/playbooks.html +++ b/00-META/process/playbooks.html @@ -485,7 +485,15 @@ const P = [ prod:"A stock report, the approved deletions, issues for missing tools.", next:"03",bounds:"A worktree is safe only if untouched for 2 h · a PR is stale after 3 days", prev:["Deleting unfinished work","Deletion by name or age alone","Missing tools left as session notes"], - check:"The report names the evidence for each deletion."} + check:"The report names the evidence for each deletion."}, + {n:"10",name:"Visual design",kind:"idea",c:"--idea", + trig:"A page the operator will use is about to be designed or rebuilt, or the operator judges one unusable.", + who:"The design's lead; the operator picks.", + steps:["Gather what the page must carry: the as-is, the real data's shape, the operator's words, how others do it.","Draw three to five mockups with fixed, invented data, phone and desktop. (new)","Show them; ask one question per round. (new)","Make the pick the reference, with the operator's own details replaced. (new)","Write what is new as decisions, then the design and its milestones.","Hold the build to the reference; done when the operator uses it."], + prod:"The reference mockup beside the design, its decisions, an acceptance test of live use.", + next:"02, then 04",bounds:"A round 1 h · 3 rounds without a pick: ask what is missing", + prev:["A page judged unusable the day it ships","Rebuilding after delivery"], + check:"The design links its mockup; the review shows the build beside it; the operator's use is the last test."} ]; const kinds={all:"All",idea:"From an idea",symptom:"From a symptom",build:"Code changes",side:"Alongside"}; let current="all"; diff --git a/03-DESIGN/00-as-is/18-the-mail-modules.md b/03-DESIGN/00-as-is/18-the-mail-modules.md index 38e6bac9..31ef5edc 100644 --- a/03-DESIGN/00-as-is/18-the-mail-modules.md +++ b/03-DESIGN/00-as-is/18-the-mail-modules.md @@ -1,8 +1,8 @@ --- layer: as-is status: implemented -code: [mesh-catalog modules/mesh-mail, mesh-catalog modules/mesh-mail-web, mesh-catalog modules/mesh-mail-notifier] -updated: 2026-10-20 +code: [mesh-catalog modules/mesh-mail, mesh-catalog modules/mesh-mail-web, mesh-catalog modules/mesh-mail-notifier, mesh-catalog modules/mesh-mail-reader] +updated: 2026-10-22 decisions: - "ADR 0359" - "ADR 0361" @@ -11,30 +11,42 @@ decisions: - "ADR 0436" - "ADR 0439" - "ADR 0440" + - "ADR 0459" + - "ADR 0461" + - "ADR 0472" --- # The mail modules, as they run **One module, `mesh-mail`, holds every mail account and is the mesh's one `mail-store`. A second module, `mesh-mail-web`, is the mesh's one mail web client. A third, `mesh-mail-notifier`, shows a -desktop notice of new mail on the machines it is assigned to. A caller may read an account's mail, or ask to -send from it, only when `mesh-mail`'s trusted setting `grants` names it for that account — one list per -account, giving both at once, to the same callers. A send is never performed by a tool: it is queued for a -process of its own, which sends only once the operator approves it on Telegram.** Read from `mesh-catalog` on -`origin/main` at `a67f0196` (2026-10-20) and from the live mesh through the mesh MCP server on 2026-10-20. +desktop notice of new mail on the machines it is assigned to. A fourth, `mesh-mail-reader`, holds the seat +`mail-reading`: it hears every new mail, reads it in small batches, and keeps a mail entry — its mail group, a +one-line summary, its mark and why, never a body — for the mail page to show; the operator's own corrections +on the mail page become reading lessons that outrank a mail's own words in a later batch. A caller may read an +account's mail, or ask to send from it, only when `mesh-mail`'s trusted setting `grants` names it for that +account — one list per account, giving both at once, to the same callers; `mesh-mail-reader` is on that list +too, and asks no send, by its own design rather than by a narrower grant. A send is never performed by a tool: +it is queued for a process of its own, which sends only once the operator approves it on Telegram.** Read +from `mesh-catalog` on `origin/main` at `835bf22b` (2026-10-22) and from the live mesh through the mesh MCP +server on 2026-10-22. ``` mesh-mail (mail-store, once for the mesh — the control-node) accounts search read send send-status ── MAIL stream (headers only, 7 days) ──▶ a granted caller own database (index) · own object-store bucket (attachments) · three key-value buckets (sends, account state, newest mails) process keep (IDLE + index) · process watch (mail_health) · process send (mesh-mail-send account; holds the passwords) - ▲ grants: one list per account, read AND send alike + ▲ grants: one list per account, read AND send alike (mesh-mail-reader on it too, asking no send) │ - ┌────┴─────────────────────────────┬───────────────────────────────┐ - mesh-mail-notifier (mail-notifier, mesh-mail-web (mail-webclient, - once per machine — laptop, work- once for the mesh — the control-node) - station) sign-in (Keycloak) ─ pages ─ send ask ─▶ operator's Approve (Telegram) - node-notifier.send on new inbox mail + ┌────┴────────────────┬─────────────────────────┬──────────────────────────────────┐ + mesh-mail-notifier mesh-mail-reader mesh-mail-web (mail-webclient, + (mail-notifier, once (mail-reading, once for once for the mesh — the control-node) + per machine — laptop, the mesh — the control- sign-in (Keycloak) ─ pages ─ send ask ─▶ operator's Approve + workstation) node) ▲ (Telegram) + node-notifier.send on hears MAIL's added events; │ entries groups correct + new inbox mail batches of 10 judged ─▶ its own │ lessons undo status + database (entries, groups, lessons) ─▶ ─┘ + mail signal ─▶ operator's channel (Telegram) ``` ## 1. The modules and their seats @@ -55,9 +67,13 @@ mesh-mail (mail-store, once for the mesh — the control-node) module claiming it needs `node-notifier` held on the same machine (ADR 0378 §7). It is assigned today to two machines, the laptop and the workstation (live read of each machine's modules, 2026-10-20); the control-node and the home server hold no instance of it. -- None of the three seats is named `mesh-*`. A caller reaches them by seat (`mail-store.`, - `mail-webclient.`, `mail-notifier.`); `mesh-mail` also answers its own tool names (`mail_accounts` - and the rest) through the same handlers. +- **`mesh-mail-reader`** is a module of its own (`modules/mesh-mail-reader`), also built from `mesh-mail`'s + source. It claims `mail-reading`, a fourth seat `mesh-mail`'s own manifest declares beside its three mail + seats, held once for the mesh (ADR 0459 §1, §2). It is assigned to the same machine as `mesh-mail`, under + its own account, `mesh-mail-reader` (`runs-as`; confirmed live, 2026-10-22). It is covered whole in §8. +- None of the four seats is named `mesh-*`. A caller reaches them by seat (`mail-store.`, + `mail-webclient.`, `mail-notifier.`, `mail-reading.`); `mesh-mail` also answers its own + tool names (`mail_accounts` and the rest) through the same handlers. ## 2. Accounts and the MAIL stream @@ -92,11 +108,15 @@ mesh-mail (mail-store, once for the mesh — the control-node) second list and no narrower grant for sending alone: a caller named for an account may do both, and a caller left out of it may do neither. - **Who is granted, today, for every account alike** (`mesh-controller.settings` for `mesh-mail`, read live on - 2026-10-20): `mesh-mail-notifier` (named alone, so wherever it is assigned it may read); each of the four + 2026-10-22): `mesh-mail-notifier` (named alone, so wherever it is assigned it may read); each of the four machines' own `node-tools` — the control-node's, the laptop's, the workstation's and the home server's, which - is how an agent session on any of them reaches a mail account at all; and the control-node's `mesh-mail-web`. - So the notifier, every machine's agent sessions, and the web client can each both read an account's mail and - ask to send from it, from the one list. + is how an agent session on any of them reaches a mail account at all; the control-node's `mesh-mail-web`; and + the control-node's `mesh-mail-reader`. So the notifier, every machine's agent sessions, the web client and + the mail reader can each both read an account's mail and ask to send from it, from the one list — ADR 0472 + §1 and §3 decided the mail reader joins this same list, rather than a narrower one of its own, and that it + simply never calls a send verb: its manifest invokes `seat:mail-store.read` and `seat:mail-store.accounts` + alone, no send verb of `mail-store` (`modules/mesh-mail-reader/module.json`, confirmed on `origin/main` at + `835bf22b`). ## 4. The send path @@ -123,12 +143,28 @@ mesh-mail (mail-store, once for the mesh — the control-node) - **Its pages**, all behind sign-in: the accounts page (`/`), a folder (`/folder`), search (`/search`), one mail (`/mail`), an attachment download (`/attachment`), writing a mail (`/write`), its send (`/send`) and its - state (`/send-status`) — plus the sign-in callback (`/signed-in`). Every changing form (today, the send) - carries a token bound to the session (`server.go`, `csrf.go`). + state (`/send-status`), a mail correction (`/mail-correct`) and a reading-lesson undo (`/mail-undo`) — plus + the sign-in callback (`/signed-in`). Every changing form (the send, a correction, an undo) carries a token + bound to the session (`server.go`, `csrf.go`). +- **The accounts page now opens on mail reading, above the classical list** (ADR 0459 §13, ADR 0515 §5, issue + 1430, 1523; `accounts.go`). It asks `mail-reading.status` first; not answering at all makes the whole + section say mail reading is unavailable and why, and nothing else here is asked — the classical list below + still renders regardless. Otherwise it shows, in order: the mail that matters (`mail-reading.entries`, mark + `matters`), each with a correction form (mark, mail group) that posts to `/mail-correct`; the mail groups + (`mail-reading.groups`) when mail reading is healthy, or the unhealthy cause and since when in their place + when it is not; the scam warnings (the same `entries` answer, mark `scam warning`), each with the same kind + of correction form, shown either way; the reading lessons (`mail-reading.lessons`, newest first), each with + its own undo form posting to `/mail-undo`; and the weekly reading-lessons-per-100-read-mails measure, from + `mail-reading.status`'s own answer. A mail body never reaches this section — only a mail entry's headers, + mail group, summary, mark, why and due date. +- **A correction or an undo is asked of `mail-reading`, not acted on here.** `/mail-correct` and `/mail-undo` + are refused with no sign-in or a form token from another session, before `mail-reading.correct` or + `.undo` is ever asked; whatever `mail-reading` answers or refuses, the page is simply shown again, so a + refused correction is never surfaced as its own error (`mailwrite.go`, issue 1523). - **Sign-in is Keycloak's.** The module requires `oidc-client` and uses the authorisation-code flow with PKCE; the identity (the ID token's own username claim) must be in the trusted setting `operators` — nobody by default, and today one identity, the operator's own (ADR 0440 §3; `mesh-controller.settings` for - `mesh-mail-web`, read live on 2026-10-20). The session cookie is `Secure`, `HttpOnly` and `SameSite=Strict`, + `mesh-mail-web`, read live on 2026-10-22). The session cookie is `Secure`, `HttpOnly` and `SameSite=Strict`, ending 12 hours after sign-in (`auth.go`). - **Reachable only from the mesh's own machines.** Its route's reach is set to `internal`, so the route proxy composes only its internal name, never a public one, and the module itself answers any other host 404 (ADR @@ -137,10 +173,12 @@ mesh-mail (mail-store, once for the mesh — the control-node) never parsed as markup; an attachment is answered only as a download, `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`; every page carries a content security policy allowing nothing from elsewhere (ADR 0440 §5; `mail.go`). -- **It writes nothing of its own.** No database, nothing on disk beyond its own settings; it reaches mail only - through `mail-store`'s verbs, calling as `.mesh-mail-web`, and reads one thing directly besides — +- **It keeps no database of its own.** Nothing on disk beyond its own settings; it reaches mail only through + `mail-store`'s verbs, calling as `.mesh-mail-web`, and reads one thing directly besides — `mesh-mail`'s account bucket, for folders and counts, of an account its own grant already let it ask about - (ADR 0440 §4, §6). + (ADR 0440 §4, §6). Its manifest now also invokes `mail-reading`'s six verbs (ADR 0459 §13, ADR 0515 §5): it + reads `entries`, `groups`, `lessons` and `status`, and writes a correction or an undo with `correct` and + `undo` — writes that land in `mail-reading`'s own database, never in anything `mesh-mail-web` keeps itself. ## 6. `mesh-mail-notifier` @@ -156,8 +194,12 @@ mesh-mail (mail-store, once for the mesh — the control-node) sender; a message already shown — tracked by its message id, or by its folder, UIDVALIDITY and UID where it has none — is never shown twice, even redelivered. A notice that fails to send is retried at most once a minute (`notifier.go`, its constants `FoldOver`, `Window`, `RetryBackoff`). -- **Assignment.** It is assigned today to the laptop and the workstation (live read, 2026-10-20), where it needs - `node-notifier` to be held beneath it. +- **Assignment.** It is assigned today to the laptop and the workstation (live read, 2026-10-22; `mesh_machine` + for each shows its seat `mail-notifier` and tool `mail_notifier_health` there, and neither on the + home server or the control-node), where it needs `node-notifier` to be held beneath it. ADR 0461 §7 names + these same two machines as the ones it is to be unassigned from, on the operator's Approve, once mail + signals (§8) are proven live on the operator's real accounts (issue 1418) — as built, that unassignment has + not happened. ## 7. Health @@ -175,3 +217,70 @@ mesh-mail (mail-store, once for the mesh — the control-node) the module by it. A watching process of its own is tracked by issue 841, not yet decided (its `README.md`). The comment in its `health.go` that still names a process `notify` predates that commit. It keeps one state bucket, `shown`, for 30 days, to show no mail twice (`module.json`). + +## 8. `mesh-mail-reader` + +- **It claims `mail-reading`**, the fourth mail seat, held once for the mesh, serving six verbs: + `entries`, `groups`, `correct`, `lessons`, `undo` and `status` (ADR 0459 §1, §2; `module.json`, + `tools.go`). Each answers only a caller the trusted setting `callers` names — a bare module name, granted + on any machine, `.`, or `person.`, the same shapes `mesh-mail`'s own `grants` + matches, copied in miniature so this module never imports `mesh-mail`'s code (`callers.go`). The setting + defaults to `["mesh-mail-web"]` and is unchanged on the live mesh (`mesh-controller.settings` for + `mesh-mail-reader`, read live on 2026-10-22), so only `mesh-mail-web` may call a `mail-reading` verb today; + any other caller is refused, naming why. +- **Its own account and database.** It runs as its own account, `mesh-mail-reader` (`runs-as`), never the + tool runner's, because it hears `mesh-mail`'s restricted stream `MAIL` under it. It is granted reading on + every account `mesh-mail` holds — named as the control-node's `mesh-mail-reader` in `mesh-mail`'s `grants` + for each of the three accounts alike (`mesh-controller.settings` for `mesh-mail`, read live on 2026-10-22) — + and never asks to send: its manifest invokes `seat:mail-store.read` and `seat:mail-store.accounts` only, no + send verb, and its code calls neither `mail_send` nor anything that moves, flags or deletes a mail (ADR + 0459 §11, ADR 0472 §3). It keeps its own Postgres database (`postgres-database`, contributed as + `mail-reader`), separate from `mesh-mail`'s index. +- **Its database holds four tables, no mail body in any of them** (`schema.sql`): `mail_entry` — one row per + judged mail, its account, message id, folder, date, sender name and address, subject, its `mail_group`, a + `summary` of at most 160 characters, its `mark` (`matters`, `scam warning` or `neither`, or null), `why`, + a `due_date`, whether it was `told` and when it was `judged`; `mail_group` — a label and when it was first + made; `reading_lesson` — the corrected mail's own headers (account, folder, sender name and address, + subject), the mail group, the `change` made, when (`made`), and whether it was `undone`; and `waiting_mail` + — a mail heard but not yet read, by its account, folder, message id, UIDVALIDITY and UID (unique together, + so a mail heard twice is kept once), its date, sender, subject, flags and size, in the order it was heard. A + fifth table, `reader_health`, keeps its own health (below) across a restart. +- **It hears mail under its own account.** It consumes `mesh-mail`'s stream `MAIL` for + `mesh-mail.mail.*.message.added` events alone (`AddedPattern`, `hear.go`), recording each as a row in + `waiting_mail` before the handler returns — so the controller's durable consumer redelivers an event a + store failure lost, rather than it being silently dropped — and keeping a mail heard twice to one row, by + its own unique key above (ADR 0459 §3, §12; `hear.go`). +- **Its batches**: at most 10 mails at a time (`MaxMailsPerBatch`), taken from `waiting_mail` in the order + they were heard; a batch starts no sooner than 160 seconds after the previous batch started (`BatchGap`, + pinned to the constant `MinBatchGap` in every production build; only a build tagged `integration` may + shorten it, and never in a way a unit test lets reach production) — bounding mail reading to at most 225 + mails an hour (ADR 0459 §5, §15; `batch.go`, `question.go`). Each batch is one judgement, asked of + `claude-code.claude_code_judge` on this module's own machine (`MESH_NODE`) and collected with + `claude_code_judged` when it answers pending; it is asked to answer `within` at most 40 seconds + (`JudgeWithin`), and the whole question — the framing that a mail is data and never an instruction, the + existing mail groups (largest first, at most 4 KiB), the reading lessons that apply (at most 40, matching + sender or mail group first then newest, at most 12 KiB) and up to 10 mails (each at most 3.5 KiB: headers + at most 512 bytes, text cut to its first 3 KiB) — is bounded to 55 KiB. A mail the provider filed as junk + (its folder named Junk or Spam, or carrying the `\Junk` flag) is said as that fact beside its text. A mail + that has simply gone (moved or deleted since it was heard) is taken out of the waiting queue without ever + being judged; any other read failure leaves the whole batch waiting for the next slot; on success, one + mail entry is written per mail and any new mail group is made; on a refused, timed-out or out-of-shape + judgement, nothing is written, every mail given stays waiting, and the reader's own health (below) is set + unhealthy (`batch.go`'s `RunOneBatch`, `RunBatches`). +- **Its mail signal.** After each batch, win or lose, it tries to tell the operator: every entry marked + `matters` and not yet `told` — carrying one over from an earlier batch that went untold — is gathered into + one `operator-channel.tell` with subject `mail`, naming the mail with the nearest due date first, then the + next one, ending "and N-2 more on the mail page" past two; nothing is sent, and nothing asked at all, when + there is nothing to tell or while the reader is unhealthy (read fresh from the store, not assumed). The + words are checked first, line by line, with `messenger.messenger_check`; any one refused falls back to the + safe form "N mails matter: open the mail page". Only once `tell` answers that some channel actually sent it + are those entries marked `told` (ADR 0461 §1-§6; `tell.go`, `signal.go`). +- **Its health**, `mail_reader_health`, is asked by the node-engine itself (`runtime-health` of kind `tool`, + every 60 seconds) and carries no caller check. It is unhealthy once a batch judgement is refused, times + out, or answers out of shape, naming which of the three, the judgement's own words, and since the first + such failure in a row (a later one in the same run never pushes that time forward); healthy again once a + judgement succeeds. The state survives a restart, kept in its own database rather than only in memory (ADR + 0459 §12; `health.go`). +- **What it never does.** It changes no mail: it moves, flags, deletes and sends nothing, asking no verb of + `mail-store` that would (ADR 0459 §11). It keeps no mail body and no text taken from one — only headers, a + summary at most 160 characters, a mark, why, and a due date (ADR 0459 §4). diff --git a/03-DESIGN/01-to-be/59-the-mail-web-client.md b/03-DESIGN/01-to-be/59-the-mail-web-client.md index 1cee76bc..bf78a16e 100644 --- a/03-DESIGN/01-to-be/59-the-mail-web-client.md +++ b/03-DESIGN/01-to-be/59-the-mail-web-client.md @@ -2,7 +2,7 @@ layer: to-be status: in-progress code: [mesh-catalog] -updated: 2026-10-20 +updated: 2026-10-22 decisions: - "ADR 0440" - "ADR 0378" @@ -274,7 +274,7 @@ before `mesh-mail-notifier` is unassigned; test 12 with milestone 4. | 1 | In a browser on the laptop and the workstation, the operator signs in and browses, searches and reads every account's mail with its attachments, and writes a mail that goes out on their Approve | tests 1 to 9 | mesh-catalog: `mesh-mail`'s account bucket (issue 1255, ADR 0439 §2); the module `mesh-mail-web` (manifest, sign-in, the pages, the send, the seat's verbs, the integration test) | — | | 2 | Each new mail is read into a mail group on the mail page; the mail that matters reaches the operator's phone as one mail signal, a scam warning shows on the page only, the operator's corrections become reading lessons, and the pop-up for every new mail is gone | tests 10, 11, 13 to 17 | mesh-catalog: the seat `mail-reading` declared in `mesh-mail`'s manifest; the module `mesh-mail-reader` (batches, mail entries, reading lessons, mail signals, health, the integration test); `mesh-mail-web`'s mail groups, mail signals, scam warnings and reading lessons; `mesh-mail-notifier` unassigned (issue 1418) | 1 | | 3 | Betterbird is gone from the laptop and the workstation, its profile and mail kept in the home | ADR 0379 §2 | mesh-catalog: Betterbird unassigned on the operator's Approve, then the `betterbird` module removed from the catalogue (issue 806) | 2 | -| 4 | On the mail page the operator asks questions about their mail and gets the mails that answer them, in a mail conversation kept as a thread | test 12 | mesh-catalog: `mesh-mail-web`'s mail conversation as a client of the thread seat; the thread's mail tools under a caller of their own (issue 1419); `mesh-mail`'s folder case and `-any` switches | 2 (the reading lessons); design 58's milestone 3 (issue 880) | +| 4 | **Replaced by design 63** (2026-10-22): on the mail page the operator asks questions about their mail and gets the mails that answer them, in a mail conversation kept as a thread | test 12 | mesh-catalog: `mesh-mail-web`'s mail conversation as a client of the thread seat; the thread's mail tools under a caller of their own (issue 1419); `mesh-mail`'s folder case and `-any` switches | 2 (the reading lessons); design 58's milestone 3 (issue 880) | **Milestone 1, confirmed by the operator on 2026-10-19, is delivered;** its live proof on the laptop and the workstation (a search, an attachment, one send on the Approve) is still the operator's. Its issues, one repository @@ -305,4 +305,7 @@ Milestone 2 is one release of `mesh-catalog`: the new seat, the new module and t `mesh-mail-reader` is named in `grants` after that delivery, on the operator's Approve; `mesh-mail-notifier` is unassigned only after test 13 passes live. Milestone 3 is issue 806. ADR 0379 §2's bar for removing Betterbird is milestone 1's; keeping Betterbird until mail reading (milestone 2) works, and not until the mail conversation, is the lead's proposal, for the operator to confirm with the milestones -(playbook 04, step 5). Milestone 4 waits for threads and for the decision that names its thread's caller (issue 1419, which needs a decision first). +(playbook 04, step 5). **Milestone 4 is replaced by design [63](63-the-mail-web-ui-with-mail-reading-and-owly.md)** +(2026-10-22): Owly answers mail questions through mail reading before threads exist (ADR 0518), and moves onto a +thread once they do. Issue 1432 will not be done; issue 1433 moves to design 63's milestone 3; issue 1419 stays +open for the move onto a thread. diff --git a/03-DESIGN/01-to-be/63-mockup/mail-page.html b/03-DESIGN/01-to-be/63-mockup/mail-page.html new file mode 100644 index 00000000..99c1f206 --- /dev/null +++ b/03-DESIGN/01-to-be/63-mockup/mail-page.html @@ -0,0 +1,193 @@ +Owly and Your list, v2
All mail? shortcuts
Good morning, Alex. Three things need you today; the rest is below.

Told your phone once today, 07:15 — 1 mail matters (bpost).

Your list

New since you last looked (yesterday 22:14): 10

Today2

This week3

Later3

You promised1

  • you →
    Bring the updated extraction numbers to the demo
    tomorrow 10:00 · Sara Claes

Waiting on others2

  • Tim: the error analysis after the logs (asked yesterday)
    Tim Jacobs: Logs from yesterday's extraction run
  • Immoweb agent: the floor plan’s second page (asked yesterday)
    Immoweb: 3 new apartments match 'Brussel centrum, 2 bedrooms'

On its way3

  • → you
    Coolblue delivers your monitor
    tomorrow 13:00–17:00 · Coolblue
  • → you
    Hostco debits €48.10
    28 Oct · Hostco
  • → you
    Proximus debits €55.00
    5 Nov · Proximus

Good to know

Tim zipped yesterday’s extraction logs (open). Plex was down 6 minutes at 04:06 and recovered.
Read later · 6 newsletters

Not a task

⚠ A mail pretending to be KBC wants you to log in within 24 hours. It’s a scam; there’s nothing to do. see why
←ReplyFolderwork

Logs from yesterday's extraction run

Tim Jacobs <tim.jacobs@work.example>
work / INBOX · Thu 22 Oct 2026, 09:25
Work

Here are yesterday's logs, zipped. The errors are in errors.csv.

Correct mail reading ▾
←ReplyFoldergmail

3 new apartments match 'Brussel centrum, 2 bedrooms'

Immoweb <alerts@immoweb.example>
gmail / INBOX · Thu 22 Oct 2026, 09:10
★ Matters · due Sat 24 Oct · Housing
Matches the search you set up; a viewing is on Saturday.

Three new listings match your search.

Kerkstraat 14, 2nd floor — €925/month — open viewing Saturday 11:00.

Photos and the floor plan are attached.

Correct mail reading ▾
←ReplyFolderwork

Re: Atlas demo for the editors — Thursday

Sara Claes <sara.claes@work.example>
work / INBOX · Thu 22 Oct 2026, 08:55
★ Matters · due tomorrow · Work
A colleague asks for a reply before a meeting tomorrow at 10:00.

↳ the newest of 4 messages in this conversation · show all

Hi Alex,

Can you confirm the 10:00 slot tomorrow for the Atlas demo with the editors? I've attached the invite.

Could you also bring the latest draft-extraction numbers? I put last week's in the sheet; please update the totals.

Thanks! Sara

Correct mail reading ▾
←ReplyFoldergmail

Your weekly summary: 42 km

Strava <no-reply@strava.example>
gmail / INBOX · Thu 22 Oct 2026, 07:45
Newsletters

(a newsletter, shown as text)

Correct mail reading ▾
←ReplyFoldergmail

Registered mail arrives today, 08:00–14:00

bpost <noreply@bpost.example>
gmail / INBOX · Thu 22 Oct 2026, 07:12
★ Matters · due today · Post & deliveries
Genuine notice from bpost's own domain; no payment or login asked. Needs you today.

Beste klant,

Uw aangetekende zending wordt vandaag geleverd tussen 08:00 en 14:00 op Stationsstraat 1, 1000 Brussel.

Iemand moet aanwezig zijn om te tekenen, of u machtigt een volmachthouder via de app.

Met vriendelijke groeten, bpost

Correct mail reading ▾
←ReplyFoldergmail

Your order 48213977 is on its way

Coolblue <info@coolblue.example>
gmail / INBOX · Thu 22 Oct 2026, 06:50
Orders & invoices

Your USB-C monitor arrives tomorrow between 13:00 and 17:00.

Track it in the app.

Correct mail reading ▾
←ReplyFoldergmail

Uw rekening wordt geblokkeerd — bevestig binnen 24 uur

KBC Bank <security@kbc-verificatie.example>
gmail / Junk · Thu 22 Oct 2026, 06:03
⚠ Scam warning · Junk
Sent from kbc-verificatie.example, not KBC's own domain; threatens a block within 24 hours and asks you to log in through a link. The provider filed it as junk.
Links are not shown. Nothing in this mail is loaded.

Geachte klant,

Wegens verdachte activiteit wordt uw rekening binnen 24 uur geblokkeerd.

Bevestig uw gegevens via de onderstaande link om dit te voorkomen.

Correct mail reading ▾
←ReplyFoldermesh

[Up] plex.example.org is back after 6 min

Uptime Kuma <alerts@example.org>
mesh / INBOX · Thu 22 Oct 2026, 04:12
Site monitoring

plex.example.org was down from 04:06 to 04:12 (6 minutes).

Correct mail reading ▾
←ReplyFoldermesh

mesh-catalog #397 merged

Gitea <gitea@example.org>
mesh / INBOX · Wed 21 Oct 2026, 23:58
Software releases

home merged pull request #397 into main.

Correct mail reading ▾
←ReplyFoldermesh

Security: ca-certificates-mozilla, php, php-legacy

arch-announce <arch-announce@archlinux.example>
mesh / INBOX · Wed 21 Oct 2026, 22:40
★ Matters · Security advisories
Genuine Arch Linux advisory; your machines run Arch.

ca-certificates-mozilla >= 3.131.1-1 fixes a trust-store issue that allows a man-in-the-middle.

php >= 8.5.12 and php-legacy >= 8.3.36 fix remote code execution CVEs.

Update promptly with pacman -Syu.

Correct mail reading ▾
←ReplyFoldergmail

Bestelling bevestigd: USB-C dock

bol.example <klantenservice@bol.example>
gmail / INBOX · Wed 21 Oct 2026, 21:14
Orders & invoices

Bedankt voor je bestelling. We laten het weten zodra hij onderweg is.

Correct mail reading ▾
←ReplyFoldergmail

Foto's van zondag

Marie Janssens <marie.janssens@gmail.example>
gmail / INBOX · Wed 21 Oct 2026, 20:12
Family

↳ the newest of 2 messages in this conversation · show all

Hier zijn de foto's van zondag in het park!

Zeg maar welke je wil laten afdrukken.

Correct mail reading ▾
←ReplyFoldermesh

yt-dlp 2026.10.21 released

github-actions[bot] <notifications@github.example>
mesh / INBOX · Wed 21 Oct 2026, 19:05
★ Matters · Software releases
Genuine GitHub release notice with a security fix.

yt-dlp 2026.10.21 is out.

Executables from before this release bundle an old certifi; download the new release instead of using -U.

Correct mail reading ▾
←ReplyFoldergmail

New releases for you this Friday

Spotify <no-reply@spotify.example>
gmail / INBOX · Wed 21 Oct 2026, 17:00
Newsletters

(a newsletter, shown as text)

Correct mail reading ▾
←ReplyFolderwork

[Atlas-412] Draft extraction over-counts tables

Jira <jira@atlassian.example>
work / INBOX · Wed 21 Oct 2026, 16:20
Work

Sara Claes assigned Atlas-412 to you.

Draft extraction counts a table split over two pages as two tables.

Correct mail reading ▾
←ReplyFoldergmail

New 'Not found (404)' reason on example.org

Google Search Console <sc-noreply@google.example>
gmail / INBOX · Wed 21 Oct 2026, 14:31
★ Matters · Site monitoring
Genuine site-owner alert for a domain you run; no deadline.

Search Console has identified that some pages on example.org are affected by a new 'Not found (404)' reason.

Open the Pages report to review them.

Correct mail reading ▾
←ReplyFoldermesh

Invoice R0024581133 for October

Hostco <billing@hostco.example>
mesh / INBOX · Wed 21 Oct 2026, 11:22
Orders & invoices

Dear customer,

your invoice R0024581133 for October is attached. The amount will be debited on 28 October.

Correct mail reading ▾
←ReplyFolderwork

Page updated: Release checklist Q4

Confluence <confluence@atlassian.example>
work / INBOX · Wed 21 Oct 2026, 10:05
Work

Tim Jacobs updated Release checklist Q4.

Correct mail reading ▾
←ReplyFoldergmail

Dagelijkse nieuwsbrief

Tweakers <nieuwsbrief@tweakers.example>
gmail / INBOX · Wed 21 Oct 2026, 08:00
Newsletters

(a newsletter, shown as text)

Correct mail reading ▾
←ReplyFoldergmail

Terms of Service and Agentic AI guidelines updated

Google Cloud <CloudPlatform-noreply@google.example>
gmail / INBOX · Tue 20 Oct 2026, 18:02
★ Matters · Legal & terms
Legal notice affecting a Google Cloud account holder; no links or payment asked.

We updated the Google Cloud Terms of Service and introduced Agentic AI Responsible Use Guidelines, effective 12 October 2026.

Correct mail reading ▾
←ReplyFoldergmail

Herfstdeals tot 30%

IKEA Family <ikea@ikea.example>
gmail / INBOX · Tue 20 Oct 2026, 16:45
Newsletters

(a newsletter, shown as text)

Correct mail reading ▾
←ReplyFoldergmail

Je factuur van oktober staat klaar

Proximus <noreply@proximus.example>
gmail / INBOX · Tue 20 Oct 2026, 12:00
Orders & invoices

Je factuur van oktober staat klaar. Het bedrag wordt op 5 november gedomicilieerd.

Correct mail reading ▾
←ReplyFolderwork

Planned maintenance Thursday 23:00

Work IT <it-service@work.example>
work / INBOX · Tue 20 Oct 2026, 09:30
Work

The VPN gateway is down for maintenance on Thursday from 23:00 to 01:00.

Correct mail reading ▾
←ReplyFoldergmail

Verge Daily: the week in AI agents

The Verge <newsletter@theverge.example>
gmail / INBOX · Tue 20 Oct 2026, 07:30
Newsletters

(a newsletter, shown as text)

Correct mail reading ▾
←ReplyFoldergmail

Klaar voor de winter?

Decathlon <news@decathlon.example>
gmail / INBOX · Mon 19 Oct 2026, 15:00
Newsletters

(a newsletter, shown as text)

Correct mail reading ▾
← Back to the mailextraction-logs.zip4.6 MBDownload
▤ run-2026-10-21.log3.1 MB
▤ errors.csv220 KB
▤ tables-per-page.json1.1 MB
▤ README.txt2 KB

4 files · nothing is unpacked on the server

← Back to the mailfloor-plan.pdf340 KBDownload

Plattegrond

Immoweb

Appartement, 2 slaapkamers

Woonkamer 32 m²

Keuken 11 m²

Terras 9 m²

page 1 of 2

← Back to the maildemo-agenda.ics2 KBDownload
THU23OCT

Atlas demo for the editors

Thursday 23 October, 10:00–11:00

Room Antwerpen 2.14 · organiser Sara Claes · 6 invited

Accept Maybe Decline

← Back to the mailextraction-numbers.xlsx18 KBDownload
WeekDraftsTablesOver-counted
411 2043 310212
421 3773 802248
43———
Total2 5817 112460

Sheet 1 of 2 · read-only preview

← Back to the mailR0024581133.pdf52 KBDownload

Invoice R0024581133

Hostco

Dedicated server AX42 — €46.41

Storage Box BX11 — €3.81

IPv4 address — €1.70

Total incl. VAT — €48.10

page 1 of 2

← Back to the mailfactuur-oktober.pdf88 KBDownload

Factuur oktober

Proximus

Internet Flex — €55.00

Mobiel — €17.00

Korting bundel — −€6.00

Totaal — €55.00

page 1 of 2

\ No newline at end of file diff --git a/03-DESIGN/01-to-be/63-the-mail-web-ui-with-mail-reading-and-owly.md b/03-DESIGN/01-to-be/63-the-mail-web-ui-with-mail-reading-and-owly.md new file mode 100644 index 00000000..7d8316a7 --- /dev/null +++ b/03-DESIGN/01-to-be/63-the-mail-web-ui-with-mail-reading-and-owly.md @@ -0,0 +1,199 @@ +--- +layer: to-be +status: in-progress +code: [mesh-catalog] +updated: 2026-10-22 +decisions: + - "ADR 0515" + - "ADR 0516" + - "ADR 0517" + - "ADR 0518" + - "ADR 0519" + - "ADR 0459" + - "ADR 0440" + - "ADR 0461" + - "ADR 0462" + - "ADR 0472" + - "ADR 0378" + - "ADR 0278" +--- + +# 63 — The mail web UI with mail reading and Owly + +**The mail page stops being a list of mail and becomes the operator's list: what needs them today, this week and +later, what is promised, what is good to know and what can be read later, each line written by mail reading from +the mail, never copied from it. Beside it sits Owly, the mail page's assistant, which answers questions about the +mail, writes drafts the operator sends on their Approve, and suggests tidying the mailbox into an approval inbox, +where only the operator's Approve changes anything. Every action of the operator on the page is recorded, so mail +reading and Owly judge better over time. The classical list stays one click away, as All mail.** +(ADR 0515 to ADR 0519, building on design 59 and ADR 0459.) + +The page has no name of its own: it is `mesh-mail-web`'s page, the mail page. Its reference is the mockup the +operator picked on 2026-10-22, kept beside this design as [`63-mockup/mail-page.html`](63-mockup/mail-page.html), a +direction and not a pixel specification; its mail is invented, and the names, addresses and amounts in it are +not the operator's. + +## 1. What it builds on + +Design [59](59-the-mail-web-client.md) delivered the mail web client (its milestone 1) and mail reading (its +milestone 2): `mesh-mail-web` renders the mail page on the server, and `mesh-mail-reader` reads each new mail into +a mail entry, sends a mail signal for the mail that matters and keeps the operator's corrections as reading +lessons. What runs today is the as-is [18](../00-as-is/18-the-mail-modules.md). The operator judged that page +unusable on 2026-10-22: it shows mail groups over a classical list, so the operator still reads each mail to know +what it asks. + +This design replaces design 59's milestone 4 (the mail conversation on a thread): Owly answers before threads +exist (ADR 0518) and moves onto a thread once they do. Design 59 keeps its own last steps (test 13 live, issue +1418, and Betterbird's removal, issue 806). + +## 2. The operator's list (ADR 0515, ADR 0516) + +- **Mail reading notes what each mail asks.** A mail entry also holds at most 3 tasks (what to do, and by when), + at most 3 promises (*you promised*, *waiting on others*, *on its way*, with who and when) and whether the mail is + *read later*. Each line is the model's own sentence, at most 120 characters, and never quotes the mail. A batch + holds at most 6 mails, so its larger answer stays within 40 s (ADR 0516). +- **The page opens on the list.** A greeting says what mail signals told the operator's phone today; then + *today*, *this week* and *later* (tasks by their due date), *you promised*, *waiting on others*, *on its way*, + *good to know*, *read later*, *handled today* with undo, and the scam warnings in their own place (ADR 0515 §1). +- **Modes choose accounts.** *Work*, *Personal* and *Everything* each show the accounts `mesh-mail-web`'s setting + `modes` names (ADR 0515 §4). +- **Phone first.** One column on a phone; a wide screen adds the reading pane, and Owly beside the list (ADR 0515 + §2, §3). +- **All mail is the classical list.** Accounts, folders, search and one mail stay a view the operator can always + open; a correction of mail reading is made from the reading pane (ADR 0515 §5). +- **One script and one stylesheet from its own origin.** The page stays rendered on the server; the script makes + handling, undo and Owly work without a reload and talks only to the page's own origin. A mail's body stays + escaped text, and an attachment stays a download until milestone 2's decision (ADR 0515 §6). +- **The list starts full.** After the delivery, mail reading reads the last 14 days of inbox mail once more + (ADR 0516 §5). + +## 3. Handling and the action log (ADR 0517) + +- **Handled is the operator's.** Ticking a task, a promise or a mail handled hides it under *handled today*, with + an undo until the day ends; *remind me tomorrow* hides it until the next day. Nothing in the mailbox changes. +- **Every action is a line in the action log:** handled, undone, reminded, opened, corrected, asked, approved or + *not now*, the mode chosen, and the page opened (*last looked*, which gives *new since you last looked*). A line + holds the act, the mail by its id, the mode and when, and a question's own words; never mail text. Kept a year. +- **The log teaches.** Per sender over 90 days, the counts of mails, opened, handled unopened, reminded and + corrected reach each batch and each question in a part of their own, at most 4 KiB (milestone 5). +- **The verbs.** `mail-reading` gains `list`, `handle`, `unhandle`, `remind`, `record` and `actions`; the page + calls them for the signed-in operator and keeps no state of its own. + +## 4. Owly answers (ADR 0518) + +- **`ask` on `mail-reading`.** A question (at most 2 KiB) is answered in two judgements: the first chooses up to 5 + mails and 3 search terms from the mail entries; mail reading calls `mail_search` and `mail_read`; the second + answers from those mails and names only mails it was given. A question the mails do not answer is answered so. +- **On another node.** Owly's judgements run on the node `ask-node` names (the workstation by default), never on + the node of the batches and the issue tracker, so ADR 0459 §15's spacing holds unchanged. An answer takes up to + about 90 s; the page says what Owly is doing meanwhile. +- **The morning briefing** is one judgement a day from the last 7 days' mail entries, read aloud by the browser's + own speech on the operator's click. +- **Nothing of the conversation is kept** but the question, in the action log. A follow-up is answered fresh until + Owly moves onto a thread (issue 880, then issue 1419 for the thread's caller). +- **On the page:** Owly is a panel beside the list on a wide screen and a sheet on a phone, with suggestion chips + for the questions the list makes likely. + +## 5. Owly writes (milestone 4) + +Owly offers three short reply options for a mail that asks a question, a draft in the mail's own language, a new +mail from a sentence ("mail Marie that I'll pick the photos tomorrow"), a nudge for a promise the operator waits +on, and an answer to an invitation. Every one is the operator's to change, and goes out only through `mail_send` +on the operator's Approve on Telegram (ADR 0440 §6, ADR 0361 §2). Drafting is outside every decision so far (ADR +0462 §7 leaves it to issue 1259), and a reply needs `In-Reply-To` and cc (issue 1257): the decision is written in +milestone 4's prepare step, tracked by issue 1864. + +## 6. Owly learns and tidies (ADR 0519, ADR 0517 §6) + +- **Suggested actions.** Mail reading may suggest *archive*, *report as phishing* or *unsubscribe*, with why; it + carries none out. Batches suggest from scam warnings and from senders the operator never opens; Owly suggests + when asked in words. +- **The approval inbox** on the page shows each, with *Approve* and *Not now*. The signed-in operator's Approve is + the decision; `mesh-mail-web` then calls `mail-store`'s new verb. *Not now* keeps a sender quiet for 30 days. +- **mail-store's writes:** `archive`, `report`, `restore` (the same day's undo) and `unsubscribe`, by the mail's own + one-click way (RFC 8058, to a public address only) or as a mail send on the operator's Approve. `mesh-mail` + answers them only for the mail web client, which calls them only from the page's Approve form, so nothing else + on the mesh can change the mailbox. Flag, delete and draft stay with issue 730. +- **Teaching in the chat.** "Stop telling me about Strava" becomes a reading lesson, shown under *Owly learned* + with an undo; *remind me tomorrow* and the read-later feed come with it, and the action log's counts reach the + batches. Whether a lesson taught in words needs more than ADR 0459 §8 decides is settled in milestone 5's + prepare step, tracked by issue 1866. + +## 7. Fast on the desktop (milestone 6) + +Keyboard shortcuts (`j` and `k` move, `x` ticks, `r` opens the reply options, `/` jumps to Owly, `?` lists them) +and bulk actions (tick several, handle or archive them at once, each archive still on the approval inbox's +Approve). Tracked by issue 1867. + +## 8. What stays outside + +- **A calendar.** "Put the viewing in my calendar" needs a calendar on the mesh; none exists, so the page shows the + date and offers nothing more. No issue is opened until a calendar is decided. +- **Attachments shown in place** change ADR 0440 §5 (an attachment is only a download); milestone 2 writes that + decision before it builds (issue 1861). +- **The mail conversation on a thread** waits for threads (issue 880) and its caller (issue 1419); ADR 0462 then + decides again for Owly. +- **The work account** stays a later decision of its own (ADR 0359 §7). + +## 9. The build's acceptance tests + +1. Each new mail's entry holds its tasks, promises and read-later mark; every line is at most 120 characters and + shares no run of 8 words with the mail's text; a batch holds at most 6 mails and its question stays under 64 + KiB (ADR 0516 §1 to §3). +2. A correction removing a task or a promise, or changing the read-later mark, is kept as a reading lesson and + followed by the next batch (ADR 0516 §4). +3. After the delivery the last 14 days of inbox mail are read once, and a restart does not read them again + (ADR 0516 §5). +4. The mail page opens on the list: the greeting with today's mail signals, the sections in their order, scam + warnings in their own place, the modes from `modes`, and *All mail* opening the classical list (ADR 0515). +5. Handling a task moves it to *handled today*, undo returns it, remind hides it until the next day, and each act + leaves one line in `actions` with no mail text; a line older than 365 days is removed (ADR 0517). +6. Every page's content security policy allows script and style from its own origin only, no inline script, and + `connect-src 'self'`; a mail body with `