Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 0824351cac00a52a6dc9c87b6c6fdf8668305079)
+++ src/config/database.js	(revision 096f099d60e7e389151f34725f1774d271426dcb)
@@ -739,4 +739,12 @@
   // "Feature" a followed account: its posts show in the local Cirkel.
   ensureColumn('ap_following', 'auto_boost', 'INTEGER DEFAULT 0');
+  // Uitgelicht: dit account tonen op je eigen profiel, als aanbeveling aan je
+  // bezoekers. NIET hetzelfde als de twee buren: `featured` op de actor is de
+  // collectie VASTGEZETTE POSTS (toot:featured), en `auto_boost` hierboven laat
+  // hun posts in JOUW Cirkel meelopen. Dit gaat over wie je aan ANDEREN laat
+  // zien. Vandaar een eigen naam; drie dingen die "featured" heten is er twee
+  // te veel. Lokale vlag: FEP-d471 (Endorsements) modelleert dit als een eigen
+  // object voor webs of trust, en dat is een zwaarder ding dan hier bedoeld.
+  ensureColumn('ap_following', 'highlighted', 'INTEGER DEFAULT 0');
   // A timeline post you boosted (🔁) — also shown in the Cirkel (mixed by date).
   ensureColumn('ap_timeline', 'boosted', 'INTEGER DEFAULT 0');
Index: src/services/ArchiveExportService.js
===================================================================
--- src/services/ArchiveExportService.js	(revision 0824351cac00a52a6dc9c87b6c6fdf8668305079)
+++ src/services/ArchiveExportService.js	(revision 096f099d60e7e389151f34725f1774d271426dcb)
@@ -238,4 +238,100 @@
 }
 
+// ── Wie je volgt, als CSV ─────────────────────────────────────────
+//
+// Kolomvorm van Mastodon, zodat deze lijst ook DAAR te importeren is en die van
+// daar hier. Dat is het hele punt van een verhuisformaat: het moet ook werken
+// als je naar iets anders vertrekt dan waar je vandaan kwam.
+//
+//   Account address,Show boosts,Notify on new posts,Languages,Featured
+//
+// De vijfde kolom is van ons. Mastodon leest de eerste vier en negeert de rest,
+// dus dit blijft daar gewoon importeerbaar. `Featured` is de kolom voor
+// `highlighted`: accounts die je op je profiel uitlicht. `Show boosts` is onze
+// `auto_boost`. `Notify` en `Languages` kent Klonkt niet en blijven leeg; ze
+// staan er alleen omdat Mastodon de posities telt.
+const CSV_KOP = 'Account address,Show boosts,Notify on new posts,Languages,Featured';
+
+/** Een veld dat een komma, aanhalingsteken of nieuwe regel bevat moet geciteerd. */
+function csvVeld(v) {
+  const s = String(v == null ? '' : v);
+  return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
+}
+
+/**
+ * De volglijst van een site als CSV, of null als er niets te melden valt.
+ *
+ * Alleen `accepted`: een openstaand verzoek is geen relatie, en het opnieuw
+ * versturen ervan op de nieuwe plek zou een tweede verzoek zijn bij iemand die
+ * de eerste misschien bewust liet liggen.
+ *
+ * Het adres is de handle zonder de leidende @, want zo schrijft Mastodon hem.
+ * Ontbreekt de handle, dan valt hij terug op de actor-URI: die is altijd te
+ * herleiden, ook als de webfinger-naam ooit verloren ging.
+ */
+export function followingCsv(slug) {
+  let rijen = [];
+  try {
+    rijen = db.prepare(`SELECT actor_uri, handle, auto_boost, highlighted FROM ap_following
+                         WHERE slug = ? AND status = 'accepted'
+                         ORDER BY handle IS NULL, handle, actor_uri`).all(slug);
+  } catch { return null; }        // oude database zonder de kolom
+  if (!rijen.length) return null;
+  const regels = rijen.map((r) => [
+    csvVeld((r.handle || r.actor_uri || '').replace(/^@/, '')),
+    r.auto_boost ? 'true' : 'false',
+    'false',
+    '',
+    r.highlighted ? 'true' : 'false',
+  ].join(','));
+  return `${CSV_KOP}\n${regels.join('\n')}\n`;
+}
+
+/**
+ * Lees zo'n CSV terug. Puur, zodat de vorm te toetsen is zonder database.
+ *
+ * Vergeeflijk met opzet: een bestand uit Mastodon heeft vier kolommen en geen
+ * `Featured`, een handgemaakt bestand heeft misschien alleen adressen. Beide
+ * moeten werken, want anders is het geen uitwisselformaat maar een eigen
+ * bestandje dat toevallig op een CSV lijkt.
+ */
+export function parseFollowingCsv(text) {
+  const uit = [];
+  const regels = String(text || '').split(/\r?\n/).filter((r) => r.trim());
+  if (!regels.length) return uit;
+  // Een kopregel herkennen we aan het eerste veld; anders is regel 1 al data.
+  const start = /^\s*"?account address"?\s*,/i.test(regels[0]) || /^\s*"?account address"?\s*$/i.test(regels[0]) ? 1 : 0;
+  for (const regel of regels.slice(start)) {
+    const velden = splitsCsvRegel(regel);
+    const adres = (velden[0] || '').trim().replace(/^@/, '');
+    if (!adres) continue;
+    uit.push({
+      address: adres,
+      autoBoost: /^(true|1|yes)$/i.test((velden[1] || '').trim()),
+      highlighted: /^(true|1|yes)$/i.test((velden[4] || '').trim()),
+    });
+  }
+  return uit;
+}
+
+/** Eén CSV-regel, met respect voor geciteerde velden en verdubbelde aanhalingstekens. */
+function splitsCsvRegel(regel) {
+  const velden = [];
+  let veld = '';
+  let inCitaat = false;
+  for (let i = 0; i < regel.length; i++) {
+    const c = regel[i];
+    if (inCitaat) {
+      if (c === '"') {
+        if (regel[i + 1] === '"') { veld += '"'; i++; } else inCitaat = false;
+      } else veld += c;
+    } else if (c === '"') inCitaat = true;
+    else if (c === ',') { velden.push(veld); veld = ''; }
+    else veld += c;
+  }
+  velden.push(veld);
+  return velden;
+}
+
 /**
  * Bouw het archief als een lijst bestanden: pad -> inhoud (Buffer).
@@ -336,4 +432,10 @@
   }
 
+  // Wie je volgt. Dit ontbrak, en daarmee was een "verhuizing" halfslachtig: de
+  // Move vertelt je VOLGERS waar je heen ging, maar niets vertelde JOU wie jij
+  // volgde. Die lijst stond alleen in de oude database, en die laat je achter.
+  const volgCsv = followingCsv(slug);
+  if (volgCsv) { bestanden.set('following.csv', Buffer.from(volgCsv, 'utf8')); tellingen.following = volgCsv.trim().split('\n').length - 1; }
+
   const files = {};
   for (const pad of [...bestanden.keys()].sort()) files[pad] = sha256(bestanden.get(pad));
Index: src/services/ArchiveImportService.js
===================================================================
--- src/services/ArchiveImportService.js	(revision 0824351cac00a52a6dc9c87b6c6fdf8668305079)
+++ src/services/ArchiveImportService.js	(revision 096f099d60e7e389151f34725f1774d271426dcb)
@@ -25,5 +25,5 @@
 import db from '../config/database.js';
 import { MEDIA_ROOT } from '../config/paths.js';
-import { FORMAT_VERSION } from './ArchiveExportService.js';
+import { FORMAT_VERSION, parseFollowingCsv } from './ArchiveExportService.js';
 
 const sha256 = (buf) => crypto.createHash('sha256').update(buf).digest('hex');
@@ -116,4 +116,46 @@
 function bestemming(a) {
   return padVanOrigineel(a && a['shaer:originalUrl']) || `/media/archief/${path.basename(String((a && a.url) || ''))}`;
+}
+
+/**
+ * Volg opnieuw wie je volgde, uit de `following.csv` van een archief.
+ *
+ * BEWUST BUITEN importArchive. Die draait in één transactie en raakt alleen de
+ * database; opnieuw volgen stuurt Follow-activiteiten de deur uit en wacht op
+ * het netwerk. Dat hoort niet in een transactie: een trage peer houdt hem open,
+ * en een rollback neemt verzonden activiteiten niet terug.
+ *
+ * Ook een aparte, expliciete stap omdat een archief inlezen stil is maar negen
+ * mensen aanschrijven niet. Dat mag geen bijwerking zijn van een import.
+ *
+ * `followFn` is injecteerbaar, zodat de test geen netwerk raakt en dit bestand
+ * ActivityPubService niet hoeft te importeren.
+ */
+export async function importFollowing(site, csvText, { followFn = null } = {}) {
+  const rijen = parseFollowingCsv(csvText);
+  const rapport = { totaal: rijen.length, gevolgd: 0, overgeslagen: 0, mislukt: [] };
+  if (!followFn) return { ...rapport, error: 'no_follow_fn' };
+  for (const r of rijen) {
+    // Jezelf volgen is geen relatie maar een lus. Kan echt gebeuren bij een
+    // archief van een instance die je onder een nieuwe naam opnieuw opzet.
+    if (site && site.slug && r.address.startsWith(`${site.slug}@`)) { rapport.overgeslagen += 1; continue; }
+    try {
+      const ok = await followFn(site, r.address, r.autoBoost);
+      if (ok === false) { rapport.mislukt.push({ adres: r.address, reden: 'geweigerd' }); continue; }
+      // De uitgelicht-vlag hangt aan de RELATIE, dus die zetten we pas als de
+      // rij bestaat. Mislukt de Follow, dan valt er niets te markeren, en dat
+      // is juist: iemand uitlichten die je niet volgt klopt niet.
+      if (r.highlighted) {
+        try {
+          db.prepare('UPDATE ap_following SET highlighted = 1 WHERE slug = ? AND (handle = ? OR handle = ?)')
+            .run(site.slug, r.address, `@${r.address}`);
+        } catch { /* oude database zonder de kolom */ }
+      }
+      rapport.gevolgd += 1;
+    } catch (e) {
+      rapport.mislukt.push({ adres: r.address, reden: (e && e.message) || 'onbekend' });
+    }
+  }
+  return rapport;
 }
 
Index: test/following-csv.test.js
===================================================================
--- test/following-csv.test.js	(revision 096f099d60e7e389151f34725f1774d271426dcb)
+++ test/following-csv.test.js	(revision 096f099d60e7e389151f34725f1774d271426dcb)
@@ -0,0 +1,141 @@
+// Wie je volgt gaat mee bij een verhuizing.
+//
+// Dit ontbrak: de exporter dekte posts, tracks en antwoorden, maar niet je
+// relaties. De Move vertelt je VOLGERS waar je heen ging; niets vertelde JOU wie
+// jij volgde. Die lijst stond alleen in de database die je achterlaat.
+//
+// Kolomvorm van Mastodon, met een vijfde kolom van ons erachter, zodat de lijst
+// beide kanten op werkt. Een uitwisselformaat dat alleen met zichzelf praat is
+// geen uitwisselformaat.
+import { test, beforeEach } from 'node:test';
+import assert from 'node:assert/strict';
+
+process.env.DATABASE_PATH = ':memory:';
+
+const dbMod = await import('../src/config/database.js');
+const db = dbMod.default;
+{
+  const stil = console.log;
+  console.log = () => {};
+  try { dbMod.initializeDatabase(); } finally { console.log = stil; }
+}
+
+const { followingCsv, parseFollowingCsv } = await import('../src/services/ArchiveExportService.js');
+const { importFollowing } = await import('../src/services/ArchiveImportService.js');
+
+const volg = db.prepare(`INSERT INTO ap_following (slug, actor_uri, handle, status, auto_boost, highlighted)
+                         VALUES (?,?,?,?,?,?)`);
+
+beforeEach(() => {
+  db.prepare('DELETE FROM ap_following').run();
+});
+
+test('de CSV heeft de Mastodon-koppen, met Featured erachter', () => {
+  volg.run('me', 'https://a.example/ap/users/jason', '@jason@a.example', 'accepted', 1, 1);
+  const csv = followingCsv('me');
+  const [kop, eerste] = csv.trim().split('\n');
+  assert.equal(kop, 'Account address,Show boosts,Notify on new posts,Languages,Featured');
+  // Zonder de leidende @, want zo schrijft Mastodon het adres.
+  assert.equal(eerste, 'jason@a.example,true,false,,true');
+});
+
+test('een openstaand verzoek gaat NIET mee', () => {
+  volg.run('me', 'https://a.example/ap/users/wacht', '@wacht@a.example', 'pending', 0, 0);
+  volg.run('me', 'https://a.example/ap/users/ja', '@ja@a.example', 'accepted', 0, 0);
+  const regels = followingCsv('me').trim().split('\n').slice(1);
+  assert.equal(regels.length, 1, 'een verzoek dat iemand bewust liet liggen mag niet opnieuw verstuurd worden');
+  assert.match(regels[0], /^ja@a\.example/);
+});
+
+test('zonder handle valt hij terug op de actor-URI', () => {
+  volg.run('me', 'https://a.example/ap/users/naamloos', null, 'accepted', 0, 0);
+  assert.match(followingCsv('me'), /https:\/\/a\.example\/ap\/users\/naamloos/);
+});
+
+test('een lege lijst levert geen bestand op', () => {
+  assert.equal(followingCsv('me'), null);
+});
+
+test('een export leest zichzelf terug', () => {
+  volg.run('me', 'https://a.example/ap/users/a', '@a@a.example', 'accepted', 1, 0);
+  volg.run('me', 'https://b.example/ap/users/b', '@b@b.example', 'accepted', 0, 1);
+  const terug = parseFollowingCsv(followingCsv('me'));
+  assert.deepEqual(terug, [
+    { address: 'a@a.example', autoBoost: true, highlighted: false },
+    { address: 'b@b.example', autoBoost: false, highlighted: true },
+  ]);
+});
+
+test('een bestand uit Mastodon werkt, ook zonder onze vijfde kolom', () => {
+  const uit = parseFollowingCsv(
+    'Account address,Show boosts,Notify on new posts,Languages\n'
+    + 'iemand@mastodon.social,true,false,\n',
+  );
+  assert.deepEqual(uit, [{ address: 'iemand@mastodon.social', autoBoost: true, highlighted: false }]);
+});
+
+test('een kale lijst adressen werkt ook, zonder kopregel', () => {
+  const uit = parseFollowingCsv('@een@a.example\ntwee@b.example\n');
+  assert.deepEqual(uit.map((r) => r.address), ['een@a.example', 'twee@b.example']);
+});
+
+test('een geciteerd veld met een komma erin blijft heel', () => {
+  const uit = parseFollowingCsv('Account address,Show boosts\n"raar,naam@a.example",true\n');
+  assert.equal(uit[0].address, 'raar,naam@a.example');
+  assert.equal(uit[0].autoBoost, true);
+});
+
+test('importFollowing volgt elke regel, met de boost-stand mee', async () => {
+  const gezien = [];
+  const r = await importFollowing({ slug: 'me' },
+    'Account address,Show boosts,Notify on new posts,Languages,Featured\n'
+    + 'a@a.example,true,false,,false\n'
+    + 'b@b.example,false,false,,true\n',
+    { followFn: async (site, adres, boost) => { gezien.push([adres, boost]); return true; } });
+
+  assert.deepEqual(gezien, [['a@a.example', true], ['b@b.example', false]]);
+  assert.equal(r.gevolgd, 2);
+  assert.equal(r.mislukt.length, 0);
+});
+
+test('jezelf volgen wordt overgeslagen', async () => {
+  const r = await importFollowing({ slug: 'me' },
+    'Account address\nme@eigen.example\nander@a.example\n',
+    { followFn: async () => true });
+  assert.equal(r.overgeslagen, 1);
+  assert.equal(r.gevolgd, 1);
+});
+
+test('een mislukte follow stopt de rest niet, en wordt gemeld', async () => {
+  const r = await importFollowing({ slug: 'me' },
+    'Account address\neen@a.example\nkapot@b.example\ndrie@c.example\n',
+    { followFn: async (s, adres) => { if (adres.startsWith('kapot')) throw new Error('onbereikbaar'); return true; } });
+
+  assert.equal(r.gevolgd, 2, 'de derde moet nog geprobeerd zijn na de tweede');
+  assert.deepEqual(r.mislukt, [{ adres: 'kapot@b.example', reden: 'onbereikbaar' }]);
+});
+
+test('zonder followFn gebeurt er niets, en dat zegt hij ook', async () => {
+  const r = await importFollowing({ slug: 'me' }, 'Account address\na@a.example\n', {});
+  assert.equal(r.error, 'no_follow_fn');
+  assert.equal(r.gevolgd, 0);
+});
+
+// De koppeling zelf. De losse functies hierboven kunnen prima werken terwijl het
+// archief de CSV nooit ziet, en dat is precies het soort stil gat waar een
+// verhuizing op strandt: alles groen, en je volglijst blijft achter.
+test('buildArchive stopt following.csv er echt in, met hash in het manifest', async () => {
+  db.prepare('INSERT OR IGNORE INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
+    .run('u1', 'u1', 'u1@test', 'x', 'god');
+  db.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
+    .run('s1', 'me', 'Mijn site', 'u1');
+  volg.run('me', 'https://a.example/ap/users/jason', '@jason@a.example', 'accepted', 1, 1);
+
+  const { buildArchive } = await import('../src/services/ArchiveExportService.js');
+  const r = buildArchive('me', { origin: 'https://oud.example' });
+
+  assert.ok(r.files.has('following.csv'), 'het archief moet de volglijst dragen');
+  assert.equal(r.counts.following, 1);
+  assert.ok(r.manifest.files['following.csv'], 'en hij hoort in het manifest, anders telt hij niet mee bij de controle');
+  assert.match(String(r.files.get('following.csv')), /jason@a\.example,true,false,,true/);
+});
