Settings: a section per plugin, and call the folder Spam

Plugin settings pages were stacked at the bottom of Settings -> Accounts, which
put antispam's thresholds and lists under a heading about mail accounts. Each
`settings.page` now gets its own entry in the Settings nav: "Antispam filtering"
and "Mail filters".

Slot gains an optional `title` for this — the host needs a label when it gives a
slot an area of its own, and falls back to the plugin name. PluginSlot gains an
optional `plugin` filter so a host can render one plugin's bundle rather than
every binding for a slot name.

Wording: the app calls that folder Spam (i18n `nav.spam`), so the plugin's copy
now does too — "filed into Spam", "Move to Spam", "marked as spam". The IMAP
special-use stays `junk`, which is what the protocol calls it; only what the
user reads changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SqJYt7jD7oUEkoY68sBZo
This commit is contained in:
2026-09-17 08:19:11 +02:00
co-authored by Claude Opus 5
parent fe471dbf01
commit 6414504f6e
11 changed files with 71 additions and 21 deletions
@@ -33,7 +33,11 @@ plugin = Plugin(
migrations=str(_HERE / "migrations" / "versions"),
ui_manifest=UiManifest(
slots=[
Slot(name="settings.page", bundle=f"{_UI}/settings.js"),
Slot(
name="settings.page",
bundle=f"{_UI}/settings.js",
title="Antispam filtering",
),
# Mounts on every message but renders only for a `banner` verdict —
# a spam warning on ordinary mail is worse than no warning at all.
Slot(name="message.banner", bundle=f"{_UI}/banner.js"),
@@ -33,7 +33,7 @@ export default function mount(el, ctx, props) {
function render() {
const d = busy ? " disabled" : "";
el.innerHTML =
`<button class="wm-btn-ghost" data-v="spam" title="Move to Junk and teach the filter"${d}>Spam</button>` +
`<button class="wm-btn-ghost" data-v="spam" title="Move to Spam and teach the filter"${d}>Spam</button>` +
`<button class="wm-btn-ghost" data-v="ham" title="Move back to Inbox and teach the filter"${d}>Not spam</button>`;
el.querySelectorAll("[data-v]").forEach((b) =>
b.addEventListener("click", (e) => {
@@ -36,7 +36,7 @@ export default function mount(el, ctx, props) {
<strong>Looks like spam</strong>
<span class="wm-muted">score ${Number(v.score).toFixed(1)}${why ? ` · ${esc(why)}` : ""}</span>
<div class="wm-row">
<button class="wm-btn" data-a="junk">Move to Junk</button>
<button class="wm-btn" data-a="junk">Move to Spam</button>
<button class="wm-btn-ghost" data-a="ham">Not spam</button>
<button class="wm-btn-ghost" data-a="trust">Always trust sender</button>
</div>
@@ -78,11 +78,11 @@ export default function mount(el, ctx) {
}
el.className = "wm-root";
el.innerHTML = `
<h2 class="wm-h2">Spam filtering</h2>
<p class="wm-muted">A second pass over mail your provider already delivered. Nothing is deleted — suspect mail is filed into Junk, and the uncertain middle only gets a warning.</p>
<h2 class="wm-h2">Antispam filtering</h2>
<p class="wm-muted">A second pass over mail your provider already delivered. Nothing is deleted — suspect mail is filed into Spam, and the uncertain middle only gets a warning.</p>
<div class="wm-card">
<label class="wm-row">Move to Junk at score
<label class="wm-row">Move to Spam at score
<input class="wm-input" type="number" step="0.5" min="0.5" data-f="move_threshold" value="${settings.move_threshold}">
</label>
<label class="wm-row">Warn with a banner at score
@@ -106,7 +106,7 @@ export default function mount(el, ctx) {
<div class="wm-card">
<strong>Always refuse</strong>
<p class="wm-faint">Filed into Junk without asking the engine.</p>
<p class="wm-faint">Filed into Spam without asking the engine.</p>
<ul class="wm-list">${ruleRows("deny")}</ul>
<div class="wm-row"><input class="wm-input" data-new="deny" placeholder="sender@example.com or @example.com"><button class="wm-btn" data-add="deny">Add</button></div>
</div>
@@ -114,7 +114,7 @@ export default function mount(el, ctx) {
<div class="wm-card">
<label class="wm-row"><input type="checkbox" data-f="network_checks"${settings.network_checks ? " checked" : ""}> Allow online reputation checks</label>
<p class="wm-faint">Off by default. These send hashes of your mail and the links it contains to third-party servers. They also need the modules enabled on the engine itself.</p>
<label class="wm-row"><input type="checkbox" data-f="learn_from_provider_junk"${settings.learn_from_provider_junk ? " checked" : ""}> Learn from mail your provider already marked as junk</label>
<label class="wm-row"><input type="checkbox" data-f="learn_from_provider_junk"${settings.learn_from_provider_junk ? " checked" : ""}> Learn from mail your provider already marked as spam</label>
</div>
<div class="wm-card"><strong>Activity</strong>${statsBlock()}</div>`;