diff --git a/docs/superpowers/plans/2026-06-17-anti-track-v2-plan2d-social-top5.md b/docs/superpowers/plans/2026-06-17-anti-track-v2-plan2d-social-top5.md
new file mode 100644
index 00000000..6a5b688e
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-17-anti-track-v2-plan2d-social-top5.md
@@ -0,0 +1,159 @@
+# Anti-Track v2 — Plan 2d (#social top-5) Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Show the top-5 trackers by default in the `#social` panel with a "tout afficher" expander, and HTML-escape the tracker domain — a pure frontend change to `loadSocial()`.
+
+**Architecture:** Edit the tracker-table render block in `loadSocial()` (`www/toolbox/index.html`). The aggregate already returns top-50 `by_tracker_domain`; render the first 5 visible, rows 6+ hidden behind a toggle. Vanilla JS, no backend change.
+
+**Tech Stack:** vanilla JS in `www/toolbox/index.html`. No Python impact. Issue #633.
+
+**Spec:** `docs/superpowers/specs/2026-06-17-anti-track-v2-plan2d-social-top5-design.md`.
+
+**Conventions:** worktree `secubox-deb-worktrees/633-…` branch `feature/633-…`; commits end `(ref #633)`.
+
+**Verified current code** (the block to replace, in `loadSocial`):
+```javascript
+ const td = agg.by_tracker_domain || [];
+ trk.innerHTML = td.length
+ ? '
Tracker domain
hits
clients
' +
+ td.map(r => `
${r.tracker_domain}
${r.hits}
${r.clients}
`).join('') +
+ '
'
+ : '
aucun tracker dans la fenêtre
';
+```
+
+---
+
+### Task 1: top-5 default + toggle + escape in `loadSocial`
+
+**Files:**
+- Modify: `packages/secubox-toolbox/www/toolbox/index.html` (the tracker block in `loadSocial`)
+
+- [ ] **Step 1: Locate the block**
+
+Run: `cd packages/secubox-toolbox && grep -n "by_tracker_domain\|social-trackers\|aucun tracker" www/toolbox/index.html`
+Confirm the block matches the "Verified current code" above (the `const td = ...; trk.innerHTML = ...` lines).
+
+- [ ] **Step 2: Replace the block**
+
+Replace exactly that `const td = ...` / `trk.innerHTML = ...` block with:
+```javascript
+ const td = agg.by_tracker_domain || [];
+ const escT = s => String(s).replace(/&/g, '&').replace(//g, '>');
+ if (!td.length) {
+ trk.innerHTML = '
aucun tracker dans la fenêtre
';
+ } else {
+ const row = r => `
${escT(r.tracker_domain)}
${r.hits}
${r.clients}
`;
+ const head = '
Tracker domain
hits
clients
';
+ const top = td.slice(0, 5).map(row).join('');
+ const rest = td.slice(5).map(row).join('');
+ let html = head + top;
+ if (td.length > 5) {
+ html += '
' + '
'.repeat(0) + ''; // placeholder removed below
+ }
+ // Build the hidden rows + toggle without nesting
incorrectly:
+ if (td.length > 5) {
+ html = head + top + '
'
+ + '
' + rest + '
'
+ + ``;
+ } else {
+ html = head + top + '';
+ }
+ trk.innerHTML = html;
+ }
+```
+
+NOTE: keep it simpler if the above feels convoluted — the REQUIRED behavior is: (a)
+escape `tracker_domain` via `escT`; (b) ≤5 → render all in one table, no toggle;
+(c) >5 → render the top-5 table, then the remaining rows in a second `` that
+starts `display:none`, then a toggle button that flips that display and swaps its
+label between `▾ tout afficher (N)` and `▴ réduire`. A clean equivalent
+implementation is acceptable as long as it produces valid HTML (no stray
+placeholder line — delete the `// placeholder` line) and the static checks in Step 3
+pass. Prefer the clean form:
+```javascript
+ const td = agg.by_tracker_domain || [];
+ const escT = s => String(s).replace(/&/g, '&').replace(//g, '>');
+ if (!td.length) {
+ trk.innerHTML = '
aucun tracker dans la fenêtre
';
+ } else {
+ const row = r => `
${escT(r.tracker_domain)}
${r.hits}
${r.clients}
`;
+ const top = td.slice(0, 5).map(row).join('');
+ const rest = td.slice(5).map(row).join('');
+ let html = '
Tracker domain
hits
clients
'
+ + '' + top + '';
+ if (rest) {
+ html += '' + rest + '';
+ }
+ html += '
';
+ if (rest) {
+ html += ``;
+ }
+ trk.innerHTML = html;
+ }
+```
+USE THE CLEAN FORM above (two `` in one table + a toggle button). Discard the
+convoluted first variant entirely.
+
+- [ ] **Step 3: Static verification**
+
+Run from `packages/secubox-toolbox`:
+- `grep -n "escT\|sbx-trk-rest\|tout afficher\|td.slice(0, 5)" www/toolbox/index.html` → all present.
+- `python -c "import pathlib;t=pathlib.Path('www/toolbox/index.html').read_text();assert t.count('');print('html ok')"`.
+Run: `dpkg-parsechangelog -l debian/changelog | grep Version` → `2.6.47-1~bookworm1`.
+Run: `git status --short` → empty after commit.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add packages/secubox-toolbox/debian/changelog
+git commit -m "chore(toolbox): changelog for Anti-Track v2 Plan 2d (ref #633)"
+```
+
+---
+
+## Self-Review
+
+**Spec coverage:**
+- top-5 default + "tout afficher (N)" toggle revealing rows 6+ → Task 1 (two-`` + button). ✓
+- ≤5 → no toggle → Task 1 (`if (rest)` guards the toggle). ✓
+- escape `tracker_domain` → Task 1 (`escT`). ✓
+- empty-list guard preserved (`aucun tracker dans la fenêtre`) → Task 1. ✓
+- no backend change, other #social tables untouched → only the tracker block edited. ✓
+- changelog/ship → Task 2 (www already shipped by debian/rules). ✓
+
+**Placeholder scan:** the first code variant in Task 1 Step 2 intentionally contains a `// placeholder` line and is explicitly DISCARDED in favor of the clean form below it — the plan instructs to USE THE CLEAN FORM. No placeholder reaches the code.
+
+**Type consistency:** `escT` defined and used on `tracker_domain` only; `td.slice(0,5)`/`.slice(5)`; toggle id `sbx-trk-rest` referenced by the button handler — consistent. `hits`/`clients` are ints, rendered unescaped (safe).
+
+**Rollout:** cosmetic, data-neutral; ships with #633. No gating, no shared-dir changes.