<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Yioop v10 Mail Refactor — Plan</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 760px;
margin: 2rem auto; padding: 0 1rem; line-height: 1.5;
color: #1a1a1a; }
ol { margin: 0.4rem 0; }
ol ol { margin: 0.3rem 0 0.8rem; }
li { margin: 0.3rem 0; }
.check { color: #1a7f37; font-weight: bold; }
.next { color: #b35900; font-weight: bold; }
.note { color: #555; font-size: 0.92em; margin: 0.6rem 0 1.2rem; }
code { background: #f2f2f2; padding: 0 0.2em; border-radius: 3px; }
</style>
</head>
<body>
<h1>Yioop v10 Mail Refactor — Plan</h1>
<p style="margin:.8rem 2rem;padding:.7rem 1rem;background:#eef2f7">
<strong>Arc Post-Mortem Summary.</strong>
This arc moved the mail logic out of the
oversized <code>SocialComponent</code> into <code>MailModel</code> and the
mail library and backend classes. <code>listMessages</code> was unified onto
the backends, per-user session state (sort, sequence cache, sort-support)
moved to <code>MailModel</code>, default-folder resolution and the
unread-count walk moved onto <code>MailBackend</code> so
<code>SocialComponent</code> issues no raw IMAP, and address and header
parsing and validation became library and model helpers. The result is a
much smaller <code>SocialComponent</code> with mail concerns separated
out.</p>
<p class="note">Two parts, in order. Fixed list; each step only gains a
<span class="check">✓</span> when done.
<span class="next">▶</span> marks the step in progress. Ongoing
in every patch (not a step): inline single-use methods under 20 lines
and leave no stubs in <code>SocialComponent</code>.</p>
<ol>
<li>Part 1 — move mail logic out of <code>SocialComponent</code>
into <code>MailModel</code> and the mail library classes
<ol>
<li><span class="check">✓</span> <code>listMessages</code>
unified onto the backends</li>
<li><span class="check">✓</span> <code>MailModel</code> —
per-user session state (sort, sequence cache,
sort-support)</li>
<li><span class="check">✓</span> default-folder
resolution moved onto <code>MailBackend</code></li>
<li><span class="check">✓</span> unread-count walk moved
into the backend (<code>SocialComponent</code> now issues no
raw IMAP)</li>
<li><span class="check">✓</span> address/header parsing
and validation into a library/model:
<code>userMailParseAddressList</code>,
<code>userMailExtractBareAddress</code>,
<code>userMailIsValidAddress</code>,
<code>userMailCleanHeader</code>,
<code>userMailFilterAddressList</code>,
<code>userMailJoinAddressLists</code>,
<code>userMailCleanEnvelopes</code>,
<code>userMailEnvelopeSender</code></li>
<li><span class="check">✓</span> account-form
parse/validate into <code>MailAccountModel</code>:
<code>userMailParseAccountFields</code>,
<code>userMailValidateAccountFields</code>,
<code>userMailCleanAccountFormValues</code><br>
<em>Reviewed → kept in the controller. These are
controller concerns with no model/library home: parse
reads the request, validate produces <code>tl()</code>
messages, and the clean is display-prep. The model stores
raw values (the IMAP/SMTP client needs raw HOST/USERNAME to
connect) and the controller escapes them for the view at
render time. A first attempt that moved these onto the
model was reverted — the same disposition as
<code>userMailCleanEnvelopes</code> /
<code>userMailFromOptions</code>.</em></li>
<li><span class="check">✓</span> compose / reply /
forward / MIME building into a library builder:
<code>userMailBuildReplyPrefill</code>,
<code>userMailReplySubject</code>,
<code>userMailFromOptions</code>,
<code>userMailCleanComposeValues</code>,
<code>userMailSendIdentities</code><br>
<em>Done: new <code>MailComposeBuilder</code>
(<code>replySubject</code> + <code>replyPrefill</code>);
<code>userMailBuildReplyPrefill</code> kept only the
backend fetch and delegates the building.
<code>userMailSendIdentities</code> →
<code>MailAliasModel::identitiesFor</code>.
<code>userMailFromOptions</code> and
<code>userMailCleanComposeValues</code> stay in the
controller — they call <code>$parent->clean</code>,
which a library class must not.</em></li>
<li><span class="check">✓</span> folder-list session
cache into <code>MailModel</code>:
<code>userMailReadCachedFolders</code>,
<code>userMailRememberFolders</code>, and the
<code>MAIL_FOLDERS_CACHE</code> session state with its
invalidation points<br>
<em>Done: <code>MailModel</code> gained
<code>cachedFolders</code> (fresh-or-null),
<code>cacheFolders</code> (write + freshness stamp),
<code>invalidateFolders</code>, <code>foldersFor</code>
(raw read for the cross-account move and folder-protected
check), and <code>allCachedFolders</code> (the
<code>FOLDERS_BY_ACCOUNT</code> map); the
<code>MAIL_FOLDERS_CACHE_TTL</code> const moved too.
<code>SocialComponent</code> now issues no direct
<code>$_SESSION["MAIL_FOLDERS"]</code> access. Part 1
complete.</em></li>
</ol>
</li>
<li>Part 2 — consolidate the mail library classes into
<code>src/library/mail/</code> (namespace
<code>seekquarry\yioop\library\mail</code>;
<code>MailSite.php</code> stays in atto;
<code>BulkEmailJob</code> / <code>MailCloneJob</code> stay in
<code>media_jobs</code>)
<ol>
<li><span class="check">✓</span> relocate and
re-namespace, updating all references:
<code>ImapClient</code>, <code>ImapMailBackend</code>,
<code>MailBackend</code>, <code>MailSiteMailBackend</code>,
<code>MailSiteFactory</code>, <code>SmtpClient</code>,
<code>MimeMessage</code>, <code>ImapEnvelopeParser</code>,
<code>ImapFolderListParser</code>,
<code>ImapResponseParser</code>, <code>ImapListing</code>,
<code>DkimKey</code>, <code>MailHeaderParser</code>,
<code>MailRecordCache</code>,
<code>MailScheduledDispatcher</code>,
<code>MailUnreadProbe</code>,
<code>MailBackendException</code> (and
<code>MailComposeBuilder</code>, added in 1.7)<br>
<em>In progress. <code>MailHeaderParser</code> relocated
first as the template (a leaf, no outbound mail-class
deps). Established procedure per class: create
<code>src/library/mail/</code>, <code>git mv</code> the
file, change its namespace to
<code>seekquarry\yioop\library\mail</code>, then update
every reference — change the <code>use</code> import
in importing files, and add a <code>use</code> in former
same-namespace <code>library</code> classes that had relied
on same-namespace resolution. The autoloader maps the
namespace straight to the path, so no loader change is
needed. Done so far: <code>MailHeaderParser</code> (the
template), then leaves <code>MailComposeBuilder</code> and
<code>MailRecordCache</code> (the latter adding a
<code>use</code> to its former same-namespace users
<code>DkimKey</code>, <code>SpfCheck</code>,
<code>DmarcCheck</code>). The final 15
(<code>ImapClient</code>, the Imap parsers,
<code>ImapListing</code>, <code>ImapMailBackend</code>,
<code>MailBackend</code>, <code>MailSiteMailBackend</code>,
<code>MailSiteFactory</code>, <code>SmtpClient</code>,
<code>MimeMessage</code>, <code>DkimKey</code>,
<code>MailScheduledDispatcher</code>,
<code>MailUnreadProbe</code>,
<code>MailBackendException</code>) then moved in a single
pass: moving them together kept every mail-to-mail
reference same-namespace, so only external referencers
changed — <code>use</code> imports repointed, the
<code>L\</code> library-alias mail uses rewritten to a new
<code>ML</code> (= <code>library\mail</code>) alias, one
fully-qualified reference fixed, and the now-redundant
same-namespace imports dropped. All 18 mail classes now
live under <code>src/library/mail/</code>;
<code>MailSite.php</code> stays in atto and
<code>BulkEmailJob</code> / <code>MailCloneJob</code> in
<code>media_jobs</code>. The mail email-auth checks
<code>DmarcCheck</code> and <code>SpfCheck</code> (both
DB-clean) were relocated too, so the whole
<code>src/library/mail/</code> set is in place. Part 2
complete.</em></li>
</ol>
</li>
<li>Part 3 — library layering cleanup: nothing in
<code>src/library</code> may touch a model or the database.
Models are the only layer that touches the database;
controllers and components are the only layers that touch
models. Library classes must take their data as arguments.
Mail-related violations to fix (move the DB/model access into a
model, have the library class receive values as parameters):
<ol>
<li><code>MailSiteFactory</code> instantiates
<code>MailAliasModel</code>, <code>UserModel</code>,
<code>MailSenderAllowModel</code> directly.
<em>Coupled with the authenticator item below: both are
set up in <code>MailSiteFactory::build()</code>, which is
called from the <code>MailServer</code> executable,
<code>SocialComponent</code>, and two library classes
(<code>MailSiteMailBackend</code>,
<code>MailCloneJob</code>). The library callers cannot
create models to inject, so this needs the daemon-layer
decision before it can be fixed.</em></li>
<li>✓ <code>MailBackend</code> and
<code>MailUnreadProbe</code> no longer reach a model.
<em>Done: the component loads the account row (new
<code>SocialComponent::userMailBackend</code> helper) and
passes it to <code>MailBackend::forAccountId</code>; the
unread probe takes an accounts-provider closure so the
lookup happens only on a cache miss; new model method
<code>MailAccountModel::getAccountsWithPassword</code>.</em></li>
<li>✓ <code>CredentialCipher</code> no longer touches the
database. <em>Done: the key store moved into
<code>MailAccountModel::masterKey()</code> (loads or mints
the <code>MAIL_SECRET</code> key, caches it on the model);
the cipher is now pure crypto whose encrypt/decrypt take the
raw key as a parameter. The key-persistence test moved from
<code>CredentialCipherTest</code> into
<code>MailAccountModelTest</code>.</em></li>
<li>✓ <code>YioopUserAuthenticator</code> relocated into
<code>src/library/mail/</code>. <em>Done: folder move plus
namespace change. This also repaired a latent break —
<code>MailSiteFactory</code> (already in
<code>library\mail</code>) referenced
<code>new YioopUserAuthenticator()</code> unqualified, which
had been resolving to a non-existent
<code>library\mail\YioopUserAuthenticator</code> since the
factory itself moved; the class now lives there, so the
reference resolves. Its model-instantiation layering fix is
still deferred with the factory below.</em></li>
<li><code>MailSiteFactory</code> — left as-is for now (per
Chris), apart from the authenticator now resolving from
<code>mail/</code>. Still instantiates
<code>MailAliasModel</code>, <code>UserModel</code>,
<code>MailSenderAllowModel</code>; resolving that needs the
daemon-layer decision (<code>build()</code> is called from
the <code>MailServer</code> executable, a component, and two
library classes that cannot inject models).</li>
<li>Note: non-mail library classes violate this too
(<code>FeedDocumentBundle</code>,
<code>VersionFunctions</code>, several media jobs); out of
scope for the mail arc but flagged here</li>
</ol>
</li>
</ol>
</body>
</html>