` container with a KPI div + two table mounts
+(mirror the social section markup).
+- [ ] **Step 2:** in `switchTab`, add `if (name === 'ads') loadAds();`. Add:
+```javascript
+async function loadAds() {
+ const d = await J('/admin/ad-stats?hours=24');
+ const kpi = document.getElementById('ads-kpi');
+ const esc = s => String(s).replace(/&/g,'&').replace(//g,'>');
+ if (!d || d.__error) { kpi.innerHTML = `err${(d&&d.__error)||'no data'}`; return; }
+ kpi.innerHTML = `Pubs bloquées ${d.total_blocked||0}`
+ + ` Ko économisés ${Math.round((d.total_bytes||0)/1024)}`
+ + ` Silenced ${(d.by_action&&d.by_action.silent)||0}`
+ + ` Fenêtre ${d.window_hours||24}h`;
+ const hostRows = (d.top_hosts||[]).map(r=>`${esc(r.host)} | ${r.hits} | ${Math.round((r.bytes||0)/1024)} |
`).join('');
+ const siteRows = (d.top_sites||[]).map(r=>`${esc(r.site)} | ${r.hits} |
`).join('');
+ document.getElementById('ads-hosts').innerHTML = hostRows
+ ? '| Ad host | bloqués | Ko |
'+hostRows+'
'
+ : 'aucune pub bloquée dans la fenêtre
';
+ document.getElementById('ads-sites').innerHTML = siteRows
+ ? '| Site | pubs bloquées |
'+siteRows+'
'
+ : '';
+}
+```
+(Match existing ids/markup conventions; reuse the social section's CSS classes.)
+- [ ] **Step 2b: Commit** `feat(toolbox): #ads dashboard tab — contextual ad-block stats (ref #656)`
+
+---
+
+### Task 7: changelog + full suite
+
+- [ ] **Step 1:** new top changelog entry, version after current top (2.6.55 →
+ **2.6.56**): summarise Ad Intelligence (aggressive learn + block/silent +
+ #ads contextual metrics + allowlist/ad_learn safety). NOTE: 2.6.56 was
+ previously used by the reverted #653 — confirm `head -1 debian/changelog`;
+ since #653 was reverted on master, 2.6.56 is free again; if not, use the next
+ free version.
+- [ ] **Step 2:** `python -m pytest tests/ -q` all green; `bash -n` n/a (py);
+ `python3 -c "import ast; ast.parse(open('sbin/secubox-toolbox-autolearn').read())"`.
+- [ ] **Step 3: Commit** `chore(toolbox): changelog for Ad Intelligence (ref #656)`
+
+## Self-review notes
+- Recording is hot-path-safe: dict increments + one regex + bg-thread SQLite flush (mirror local_store/splice). Never blocks the proxy.
+- Allowlist wins everywhere (ad_ghost `_allowed` early-return + autolearn exclusion).
+- Aggressive default AD_MIN_SITES=1, env-tunable; ad_learn toggle stops growth.
+- block-only (no IP-drop) → reversible; metrics surface every block per host/site.
+- version: verify 2.6.56 free post-#653-revert.