Index: src/routes/admin-migrate.js
===================================================================
--- src/routes/admin-migrate.js	(revision dc802b4223862381c3a03fc87973462a7b333346)
+++ src/routes/admin-migrate.js	(revision e9128bc5aab022193bf90a325fb16b03b9d6540f)
@@ -35,4 +35,6 @@
 import { buildArchive, zipArchive } from '../services/ArchiveExportService.js';
 import { readArchiveZip, importArchive } from '../services/ArchiveImportService.js';
+import { parseApAliases } from './admin-sites.js';
+import db from '../config/database.js';
 
 const router = express.Router();
@@ -72,4 +74,9 @@
 }
 
+/** De aliassen als tekst, een per regel, zoals het invoerveld ze wil. */
+function aliasTekst(site) {
+  try { return (JSON.parse((site && site.ap_aliases) || '[]') || []).join('\n'); } catch { return ''; }
+}
+
 /**
  * Waar zouden we vandaan kunnen halen? De alias die we zelf claimen (FEP-7628
@@ -94,5 +101,5 @@
     telling, fout, mb,
     verslag: null,
-    bron: bronKandidaat(site),
+    bron: bronKandidaat(site), aliassen: aliasTekst(site), movedTo: (site && site.moved_to) || null,
     mig: site ? Migration.migrationStatus(site.slug) : null,
     haalVerslag: null,
@@ -133,5 +140,5 @@
     pageTitle: 'Migreren', bodyClass: 'on-special',
     telling, fout: null, mb, verslag: null,
-    bron: opgegeven || bronKandidaat(site),
+    bron: opgegeven || bronKandidaat(site), aliassen: aliasTekst(site), movedTo: site.moved_to || null,
     mig: Migration.migrationStatus(site.slug),
     haalVerslag: r,
@@ -139,4 +146,28 @@
     error: null,
   });
+});
+
+/**
+ * Je oude account claimen (FEP-7628 alsoKnownAs).
+ *
+ * Stond op de site-bewerkpagina, tussen de kleuren en de feedinstellingen. Maar
+ * dit is stap EEN van een verhuizing, en zonder deze claim weigert de oude
+ * instantie de Move met `no_backreference`. Het hoort dus hier, boven de knop
+ * die hem nodig heeft.
+ */
+router.post('/aliases', requireGod, async (req, res) => {
+  const site = res.locals.site;
+  if (!site) return res.redirect('/admin/migrate?error=' + encodeURIComponent('Geen site'));
+  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
+  let lijst;
+  try {
+    lijst = await parseApAliases(req.body.ap_aliases, ActivityPubService.actorId(base, site.slug));
+  } catch (e) {
+    // Welke regel niet deugde, niet alleen DAT er iets niet deugde.
+    return res.redirect('/admin/migrate?error=' + encodeURIComponent(`Onbruikbaar adres: ${e && e.message}`));
+  }
+  db.prepare('UPDATE sites SET ap_aliases = ? WHERE slug = ?').run(JSON.stringify(lijst), site.slug);
+  res.redirect('/admin/migrate?success=' + encodeURIComponent(
+    lijst.length ? `${lijst.length} adres(sen) opgeslagen als jouw vorige account.` : 'Aliassen leeggemaakt.'));
 });
 
@@ -195,5 +226,6 @@
     pageTitle: 'Migreren', bodyClass: 'on-special',
     telling: tellen(site).telling, fout: null, mb,
-    bron: bronKandidaat(site), mig: Migration.migrationStatus(site.slug), haalVerslag: null,
+    bron: bronKandidaat(site), aliassen: aliasTekst(site), movedTo: site.moved_to || null,
+    mig: Migration.migrationStatus(site.slug), haalVerslag: null,
     verslag: verslag ? { ...verslag, echt, overschrijf, bestand: req.file.originalname, bytes: req.file.buffer.length } : null,
     success: (echt && verslag && !fout) ? 'Archief geïmporteerd' : null,
Index: src/routes/admin-sites.js
===================================================================
--- src/routes/admin-sites.js	(revision dc802b4223862381c3a03fc87973462a7b333346)
+++ src/routes/admin-sites.js	(revision e9128bc5aab022193bf90a325fb16b03b9d6540f)
@@ -80,5 +80,5 @@
  * Move fail at the old server with no hint why. Throws the offending line.
  */
-async function parseApAliases(raw, ownActorUri) {
+export async function parseApAliases(raw, ownActorUri) {
   const lines = String(raw || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
   if (lines.length > 5) throw new Error(lines[5] + ' (max 5)');
@@ -332,10 +332,18 @@
   // FEP-7628 aliases — validated/resolved before anything is written.
   const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
-  let apAliasesJson = null;
-  try {
-    const arr = await parseApAliases(f.ap_aliases, AP.actorId(base, req.params.slug));
-    apAliasesJson = arr.length ? JSON.stringify(arr) : null;
-  } catch (e) {
-    return res.redirect(`/admin/sites/${req.params.slug}/edit?error=` + encodeURIComponent(`Alias niet herkend of niet vindbaar: ${e.message}`));
+  // Het INVOERVELD staat hier sinds 14-8 niet meer: aliassen horen bij
+  // Migreren. Dit formulier mag ze dus niet aanraken, en al helemaal niet
+  // leegmaken omdat het veld ontbreekt. Anders verlies je je claim op je oude
+  // account door je kleuren aan te passen, en weigert de Move daarna met
+  // no_backreference. Alleen verwerken als het veld ECHT is meegestuurd, zodat
+  // een oude gecachte pagina die hem nog wel heeft blijft werken.
+  let apAliasesJson = site.ap_aliases || null;
+  if (Object.prototype.hasOwnProperty.call(f, 'ap_aliases')) {
+    try {
+      const arr = await parseApAliases(f.ap_aliases, AP.actorId(base, req.params.slug));
+      apAliasesJson = arr.length ? JSON.stringify(arr) : null;
+    } catch (e) {
+      return res.redirect(`/admin/sites/${req.params.slug}/edit?error=` + encodeURIComponent(`Alias niet herkend of niet vindbaar: ${e.message}`));
+    }
   }
 
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision dc802b4223862381c3a03fc87973462a7b333346)
+++ src/services/i18n.js	(revision e9128bc5aab022193bf90a325fb16b03b9d6540f)
@@ -76,5 +76,5 @@
     'admin.b_media': '🎬 Media', 'admin.t_media': 'Media', 'admin.media_images': 'Afbeeldingen', 'admin.media_videos': "Video's", 'admin.videos_count': "video's", 'admin.videos_empty': "Nog geen video's geupload.", 'admin.videos_del_confirm': 'Deze video verwijderen?', 'admin.media_count': 'afbeeldingen', 'admin.media_unused': 'ongebruikt', 'admin.media_cleanup': 'Ongebruikt opruimen', 'admin.media_cleanup_confirm': 'Alle ongebruikte afbeeldingen verwijderen?', 'admin.media_empty': 'Nog geen afbeeldingen geüpload.', 'admin.media_copy': 'Kopieer URL', 'admin.media_del_confirm': 'Deze afbeelding verwijderen?',
     'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Reacties', 'admin.b_seo': '🔎 SEO',
-    'admin.b_settings': '⚙️ Instellingen', "mig.title": "Migreren", "mig.r_links_fixed": "berichten waarvan de links nu naar hier wijzen", "mig.r_posts_updated": "berichten gerepareerd (plaatjes lokaal gemaakt)", "mig.r_tracks_updated": "nummers aangevuld", "mig.r_tracks_missing": "{n} nummer(s) zijn NIET aangekomen, want het geluidsbestand ontbrak. Ze zijn met opzet niet aangemaakt: een nummer dat in de lijst staat en niet afspeelt is erger dan een nummer dat er niet is.", "mig.c_tracks": "nummers", "mig.c_playlists": "playlists", "mig.audio_missing": "Let op: van {n} nummer(s) is het geluidsbestand niet te vinden. Die gaan niet mee.", "mig.audio_none": "Let op: deze site heeft muziek, maar er gaat geen enkel nummer mee. Waarschijnlijk staan de bestanden ergens anders dan de database denkt.", "mig.pull_title": "Ophalen bij je oude Klonkt", "mig.pull_hint": "Als je al verhuisd bent, kan deze Klonkt je berichten rechtstreeks bij de oude ophalen. Je hoeft geen code over te typen: de verhuizing zelf is de sleutel, want beide kanten hebben elkaar toen al erkend.", "mig.pull_source": "Adres van je oude account", "mig.pull_source_hint": "Ingevuld vanuit je aliassen. Klopt dit niet, pas het dan aan.", "mig.pull_btn": "Berichten ophalen", "mig.pull_done": "Opgehaald", "mig.pull_failed": "Ophalen lukte niet", "mig.r_blocks": "blokkades overgenomen", "mig.state": "{n} berichten hebben een verwijzing van hun oude adres.", "mig.state_done": "De lijst is compleet.", "mig.state_busy": "De lijst is nog niet compleet.", "mig.e_no_source": "Er is geen oud account bekend. Vul het adres hierboven in.", "mig.e_unreachable": "De oude Klonkt is niet bereikbaar.", "mig.e_not_moved_here": "Dat account is niet naar deze Klonkt verhuisd, dus het geeft ons niets.", "mig.e_no_backreference": "Dit account noemt dat oude adres niet als eigen alias. Zet het eerst bij je aliassen, anders kan niemand zien dat jij het bent.", "mig.e_no_outbox": "De oude Klonkt heeft geen berichtenlijst.", "mig.e_partial": "Halverwege gestopt. Wat binnen is blijft staan; probeer het nog eens.", "mig.e_config": "Deze Klonkt weet zijn eigen adres niet.", "mig.e_crash": "Er ging iets onverwachts mis.", "mig.e_points_at": "Dat account wijst naar:", "mig.lead": "Neem je berichten en media mee naar een andere Klonkt, of haal ze hiernaartoe. Hetzelfde archief dat het serverscript maakt, maar dan hier.", "mig.export_title": "Meenemen", "mig.export_hint": "Dit maakt een zip met je berichten, de reacties erop, en de media die erbij hoort.", "mig.c_posts": "berichten", "mig.c_replies": "reacties", "mig.c_media": "mediabestanden", "mig.c_following": "volgingen", "mig.c_size": "groot", "mig.missing": "Let op: van {n} mediaverwijzing(en) staat het bestand niet meer op schijf. Die gaan dus niet mee.", "mig.too_big": "Dit archief is te groot voor de webinterface. Gebruik scripts/export-archive.mjs op de server.", "mig.export_btn": "Archief downloaden (.zip)", "mig.export_none": "Er valt nog niets te exporteren.", "mig.import_title": "Hierheen halen", "mig.import_hint": "Kies een archief. Hij kijkt eerst wat er zou gebeuren en verandert nog niets.", "mig.file_label": "Archiefbestand (.zip)", "mig.overwrite_label": "Bestaande berichten overschrijven", "mig.overwrite_hint": "Standaard wordt een bericht dat er al staat overgeslagen. Overschrijven verwijdert ook een bericht met dezelfde webadres-naam maar een ander id, en dat komt niet terug.", "mig.check_btn": "Controleren", "mig.check_hint": "Dit verandert nog niets. Je krijgt eerst te zien wat er zou gebeuren.", "mig.r_dry": "Wat er zou gebeuren", "mig.r_done": "Ge\u00efmporteerd", "mig.r_would": "zouden erbij komen", "mig.r_imported": "erbij gekomen", "mig.r_skipped": "overgeslagen (staan er al)", "mig.r_overwritten": "overschreven", "mig.r_media": "mediabestanden", "mig.r_media_missing": "media ontbrak in het archief", "mig.r_new_ids": "Dit archief komt van een ander adres. De berichten krijgen daarom nieuwe adressen en gelden voor de fediverse als nieuwe berichten; reacties die elders naar de oude adressen wijzen komen niet mee.", "mig.r_confirm_hint": "Ziet het er goed uit? Kies hetzelfde bestand nog een keer om het echt te doen.", "mig.r_confirm_btn": "Nu echt importeren", "mig.follows_title": "Wie je volgt", "mig.follows_hint": "Je volglijst zit ook in het archief, en je kunt hem los downloaden en terugzetten bij Connect.", "mig.follows_btn": "Naar Connect", "admin.b_migrate": "\ud83d\udce6 Migreren", 'admin.b_newpost': '✍️ Nieuwe post', 'admin.b_look': '🎨 Uiterlijk',
+    'admin.b_settings': '⚙️ Instellingen', "mig.title": "Migreren", "asite.moved_to_migrate": "Aliassen en verhuizen staan nu bij Migreren, samen met exporteren, importeren en ophalen.", "mig.alias_title": "Stap 1: je oude account claimen", "mig.alias_hint": "Zeg hier welk account vroeger van jou was. De oude instantie controleert dit voordat hij je volgers laat verhuizen, en de ophaalknop hieronder heeft het ook nodig.", "mig.alias_label": "Je vorige adressen, een per regel", "mig.alias_note": "Bijvoorbeeld @jij@mastodon.social of een volledig actor-adres. Maximaal vijf.", "mig.alias_btn": "Aliassen opslaan", "mig.move_title": "Laatste stap: de verhuizing aankondigen", "mig.move_hint": "Dit vertelt al je volgers dat je account voortaan ergens anders woont. Zij verhuizen mee, en dit account gaat daarna op slot: posten, volgen, liken en reageren kan niet meer.", "mig.move_label": "Je nieuwe adres", "mig.move_warn": "Dit is de enige knop op deze pagina die je niet kunt terugdraaien. Doe hem als laatste, als je inhoud aan de andere kant staat en klopt.", "mig.move_btn": "Verhuizing aankondigen", "mig.move_confirm": "Dit kondigt je verhuizing aan bij al je volgers en zet dit account op slot. Zeker weten?", "mig.move_done": "Dit account is al verhuisd naar", "mig.r_links_fixed": "berichten waarvan de links nu naar hier wijzen", "mig.r_posts_updated": "berichten gerepareerd (plaatjes lokaal gemaakt)", "mig.r_tracks_updated": "nummers aangevuld", "mig.r_tracks_missing": "{n} nummer(s) zijn NIET aangekomen, want het geluidsbestand ontbrak. Ze zijn met opzet niet aangemaakt: een nummer dat in de lijst staat en niet afspeelt is erger dan een nummer dat er niet is.", "mig.c_tracks": "nummers", "mig.c_playlists": "playlists", "mig.audio_missing": "Let op: van {n} nummer(s) is het geluidsbestand niet te vinden. Die gaan niet mee.", "mig.audio_none": "Let op: deze site heeft muziek, maar er gaat geen enkel nummer mee. Waarschijnlijk staan de bestanden ergens anders dan de database denkt.", "mig.pull_title": "Ophalen bij je oude Klonkt", "mig.pull_hint": "Als je al verhuisd bent, kan deze Klonkt je berichten rechtstreeks bij de oude ophalen. Je hoeft geen code over te typen: de verhuizing zelf is de sleutel, want beide kanten hebben elkaar toen al erkend.", "mig.pull_source": "Adres van je oude account", "mig.pull_source_hint": "Ingevuld vanuit je aliassen. Klopt dit niet, pas het dan aan.", "mig.pull_btn": "Berichten ophalen", "mig.pull_done": "Opgehaald", "mig.pull_failed": "Ophalen lukte niet", "mig.r_blocks": "blokkades overgenomen", "mig.state": "{n} berichten hebben een verwijzing van hun oude adres.", "mig.state_done": "De lijst is compleet.", "mig.state_busy": "De lijst is nog niet compleet.", "mig.e_no_source": "Er is geen oud account bekend. Vul het adres hierboven in.", "mig.e_unreachable": "De oude Klonkt is niet bereikbaar.", "mig.e_not_moved_here": "Dat account is niet naar deze Klonkt verhuisd, dus het geeft ons niets.", "mig.e_no_backreference": "Dit account noemt dat oude adres niet als eigen alias. Zet het eerst bij je aliassen, anders kan niemand zien dat jij het bent.", "mig.e_no_outbox": "De oude Klonkt heeft geen berichtenlijst.", "mig.e_partial": "Halverwege gestopt. Wat binnen is blijft staan; probeer het nog eens.", "mig.e_config": "Deze Klonkt weet zijn eigen adres niet.", "mig.e_crash": "Er ging iets onverwachts mis.", "mig.e_points_at": "Dat account wijst naar:", "mig.lead": "Neem je berichten en media mee naar een andere Klonkt, of haal ze hiernaartoe. Hetzelfde archief dat het serverscript maakt, maar dan hier.", "mig.export_title": "Meenemen", "mig.export_hint": "Dit maakt een zip met je berichten, de reacties erop, en de media die erbij hoort.", "mig.c_posts": "berichten", "mig.c_replies": "reacties", "mig.c_media": "mediabestanden", "mig.c_following": "volgingen", "mig.c_size": "groot", "mig.missing": "Let op: van {n} mediaverwijzing(en) staat het bestand niet meer op schijf. Die gaan dus niet mee.", "mig.too_big": "Dit archief is te groot voor de webinterface. Gebruik scripts/export-archive.mjs op de server.", "mig.export_btn": "Archief downloaden (.zip)", "mig.export_none": "Er valt nog niets te exporteren.", "mig.import_title": "Hierheen halen", "mig.import_hint": "Kies een archief. Hij kijkt eerst wat er zou gebeuren en verandert nog niets.", "mig.file_label": "Archiefbestand (.zip)", "mig.overwrite_label": "Bestaande berichten overschrijven", "mig.overwrite_hint": "Standaard wordt een bericht dat er al staat overgeslagen. Overschrijven verwijdert ook een bericht met dezelfde webadres-naam maar een ander id, en dat komt niet terug.", "mig.check_btn": "Controleren", "mig.check_hint": "Dit verandert nog niets. Je krijgt eerst te zien wat er zou gebeuren.", "mig.r_dry": "Wat er zou gebeuren", "mig.r_done": "Ge\u00efmporteerd", "mig.r_would": "zouden erbij komen", "mig.r_imported": "erbij gekomen", "mig.r_skipped": "overgeslagen (staan er al)", "mig.r_overwritten": "overschreven", "mig.r_media": "mediabestanden", "mig.r_media_missing": "media ontbrak in het archief", "mig.r_new_ids": "Dit archief komt van een ander adres. De berichten krijgen daarom nieuwe adressen en gelden voor de fediverse als nieuwe berichten; reacties die elders naar de oude adressen wijzen komen niet mee.", "mig.r_confirm_hint": "Ziet het er goed uit? Kies hetzelfde bestand nog een keer om het echt te doen.", "mig.r_confirm_btn": "Nu echt importeren", "mig.follows_title": "Wie je volgt", "mig.follows_hint": "Je volglijst zit ook in het archief, en je kunt hem los downloaden en terugzetten bij Connect.", "mig.follows_btn": "Naar Connect", "admin.b_migrate": "\ud83d\udce6 Migreren", 'admin.b_newpost': '✍️ Nieuwe post', 'admin.b_look': '🎨 Uiterlijk',
     'admin.t_admin': 'Beheer', 'admin.t_settings': 'Instellingen', 'admin.t_audio': 'Audiotracks', 'admin.t_epk': 'Perskit bewerken', 'admin.t_newsletter': 'Nieuwsbrief', 'admin.t_playlists': 'Afspeellijsten', 'admin.t_seo': 'SEO', 'admin.t_shows': 'Agenda', 'admin.t_sites': 'Sites', 'admin.t_newsite': 'Nieuwe site', 'admin.t_editsite': 'Bewerken: {title}', 'admin.t_stats': 'Statistieken', 'admin.t_updates': 'Updates', 'admin.t_users': 'Gebruikers', 'admin.t_hub': 'Mijn Klonkt Hub', 'admin.t_manual': 'Handleiding', 'aset.premium_gate': '{feature} is premium — koppel Patreon in Beheer → Instellingen.',
     'admin.b_makesite': '🎨 Maak je site aan', 'admin.b_circle': '🔗 Cirkel', 'admin.b_stats': '📊 Statistieken',
@@ -1044,5 +1044,5 @@
     'admin.b_media': '🎬 Media', 'admin.t_media': 'Media', 'admin.media_images': 'Images', 'admin.media_videos': 'Videos', 'admin.videos_count': 'videos', 'admin.videos_empty': 'No videos uploaded yet.', 'admin.videos_del_confirm': 'Delete this video?', 'admin.media_count': 'images', 'admin.media_unused': 'unused', 'admin.media_cleanup': 'Delete unused', 'admin.media_cleanup_confirm': 'Delete all unused images?', 'admin.media_empty': 'No images uploaded yet.', 'admin.media_copy': 'Copy URL', 'admin.media_del_confirm': 'Delete this image?',
     'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Comments', 'admin.b_seo': '🔎 SEO',
-    'admin.b_settings': '⚙️ Settings', "mig.title": "Migrate", "mig.r_links_fixed": "posts whose links now point here", "mig.r_posts_updated": "posts repaired (images made local)", "mig.r_tracks_updated": "tracks completed", "mig.r_tracks_missing": "{n} track(s) did NOT arrive, because the audio file was absent. They were deliberately not created: a track that sits in the list and will not play is worse than one that is missing.", "mig.c_tracks": "tracks", "mig.c_playlists": "playlists", "mig.audio_missing": "Note: for {n} track(s) the audio file cannot be found. Those will not travel.", "mig.audio_none": "Note: this site has music, but not a single track is travelling. The files are probably somewhere other than the database thinks.", "mig.pull_title": "Fetch from your old Klonkt", "mig.pull_hint": "If you already moved, this Klonkt can fetch your posts straight from the old one. No code to type over: the move itself is the key, because both sides already recognised each other back then.", "mig.pull_source": "Address of your old account", "mig.pull_source_hint": "Filled in from your aliases. Change it if this is not right.", "mig.pull_btn": "Fetch posts", "mig.pull_done": "Fetched", "mig.pull_failed": "Fetching did not work", "mig.r_blocks": "blocks carried over", "mig.state": "{n} posts have a pointer from their old address.", "mig.state_done": "The list is complete.", "mig.state_busy": "The list is not complete yet.", "mig.e_no_source": "No old account is known. Fill in the address above.", "mig.e_unreachable": "The old Klonkt cannot be reached.", "mig.e_not_moved_here": "That account did not move to this Klonkt, so it gives us nothing.", "mig.e_no_backreference": "This account does not list that old address as its own alias. Add it to your aliases first, otherwise nobody can see it is you.", "mig.e_no_outbox": "The old Klonkt has no post list.", "mig.e_partial": "Stopped halfway. What arrived stays; try again.", "mig.e_config": "This Klonkt does not know its own address.", "mig.e_crash": "Something unexpected went wrong.", "mig.e_points_at": "That account points at:", "mig.lead": "Take your posts and media to another Klonkt, or bring them here. The same archive the server script makes, but from here.", "mig.export_title": "Take with you", "mig.export_hint": "This builds a zip with your posts, their replies, and the media that belongs to them.", "mig.c_posts": "posts", "mig.c_replies": "replies", "mig.c_media": "media files", "mig.c_following": "follows", "mig.c_size": "in size", "mig.missing": "Note: for {n} media reference(s) the file is no longer on disk. Those will not travel.", "mig.too_big": "This archive is too large for the web interface. Use scripts/export-archive.mjs on the server.", "mig.export_btn": "Download archive (.zip)", "mig.export_none": "Nothing to export yet.", "mig.import_title": "Bring here", "mig.import_hint": "Pick an archive. It first shows what would happen and changes nothing yet.", "mig.file_label": "Archive file (.zip)", "mig.overwrite_label": "Overwrite existing posts", "mig.overwrite_hint": "By default a post that already exists is skipped. Overwriting also deletes a post with the same web address but a different id, and that does not come back.", "mig.check_btn": "Check", "mig.check_hint": "This changes nothing yet. You will see what would happen first.", "mig.r_dry": "What would happen", "mig.r_done": "Imported", "mig.r_would": "would be added", "mig.r_imported": "added", "mig.r_skipped": "skipped (already here)", "mig.r_overwritten": "overwritten", "mig.r_media": "media files", "mig.r_media_missing": "media missing from the archive", "mig.r_new_ids": "This archive comes from another address. The posts get new addresses and count as new posts on the fediverse; replies elsewhere pointing at the old addresses will not follow.", "mig.r_confirm_hint": "Looks right? Pick the same file once more to actually do it.", "mig.r_confirm_btn": "Import for real", "mig.follows_title": "Who you follow", "mig.follows_hint": "Your following list is in the archive too, and you can download and restore it separately under Connect.", "mig.follows_btn": "Go to Connect", "admin.b_migrate": "\ud83d\udce6 Migrate", 'admin.b_newpost': '✍️ New post', 'admin.b_look': '🎨 Appearance',
+    'admin.b_settings': '⚙️ Settings', "mig.title": "Migrate", "asite.moved_to_migrate": "Aliases and moving now live under Migrate, together with export, import and fetch.", "mig.alias_title": "Step 1: claim your old account", "mig.alias_hint": "Say which account used to be yours. The old instance checks this before it moves your followers, and the fetch button below needs it too.", "mig.alias_label": "Your previous addresses, one per line", "mig.alias_note": "For example @you@mastodon.social or a full actor address. Five at most.", "mig.alias_btn": "Save aliases", "mig.move_title": "Last step: announce the move", "mig.move_hint": "This tells all your followers that your account now lives elsewhere. They move with you, and this account locks afterwards: no more posting, following, liking or replying.", "mig.move_label": "Your new address", "mig.move_warn": "This is the only button on this page you cannot undo. Do it last, once your content is on the other side and correct.", "mig.move_btn": "Announce the move", "mig.move_confirm": "This announces your move to all your followers and locks this account. Are you sure?", "mig.move_done": "This account already moved to", "mig.r_links_fixed": "posts whose links now point here", "mig.r_posts_updated": "posts repaired (images made local)", "mig.r_tracks_updated": "tracks completed", "mig.r_tracks_missing": "{n} track(s) did NOT arrive, because the audio file was absent. They were deliberately not created: a track that sits in the list and will not play is worse than one that is missing.", "mig.c_tracks": "tracks", "mig.c_playlists": "playlists", "mig.audio_missing": "Note: for {n} track(s) the audio file cannot be found. Those will not travel.", "mig.audio_none": "Note: this site has music, but not a single track is travelling. The files are probably somewhere other than the database thinks.", "mig.pull_title": "Fetch from your old Klonkt", "mig.pull_hint": "If you already moved, this Klonkt can fetch your posts straight from the old one. No code to type over: the move itself is the key, because both sides already recognised each other back then.", "mig.pull_source": "Address of your old account", "mig.pull_source_hint": "Filled in from your aliases. Change it if this is not right.", "mig.pull_btn": "Fetch posts", "mig.pull_done": "Fetched", "mig.pull_failed": "Fetching did not work", "mig.r_blocks": "blocks carried over", "mig.state": "{n} posts have a pointer from their old address.", "mig.state_done": "The list is complete.", "mig.state_busy": "The list is not complete yet.", "mig.e_no_source": "No old account is known. Fill in the address above.", "mig.e_unreachable": "The old Klonkt cannot be reached.", "mig.e_not_moved_here": "That account did not move to this Klonkt, so it gives us nothing.", "mig.e_no_backreference": "This account does not list that old address as its own alias. Add it to your aliases first, otherwise nobody can see it is you.", "mig.e_no_outbox": "The old Klonkt has no post list.", "mig.e_partial": "Stopped halfway. What arrived stays; try again.", "mig.e_config": "This Klonkt does not know its own address.", "mig.e_crash": "Something unexpected went wrong.", "mig.e_points_at": "That account points at:", "mig.lead": "Take your posts and media to another Klonkt, or bring them here. The same archive the server script makes, but from here.", "mig.export_title": "Take with you", "mig.export_hint": "This builds a zip with your posts, their replies, and the media that belongs to them.", "mig.c_posts": "posts", "mig.c_replies": "replies", "mig.c_media": "media files", "mig.c_following": "follows", "mig.c_size": "in size", "mig.missing": "Note: for {n} media reference(s) the file is no longer on disk. Those will not travel.", "mig.too_big": "This archive is too large for the web interface. Use scripts/export-archive.mjs on the server.", "mig.export_btn": "Download archive (.zip)", "mig.export_none": "Nothing to export yet.", "mig.import_title": "Bring here", "mig.import_hint": "Pick an archive. It first shows what would happen and changes nothing yet.", "mig.file_label": "Archive file (.zip)", "mig.overwrite_label": "Overwrite existing posts", "mig.overwrite_hint": "By default a post that already exists is skipped. Overwriting also deletes a post with the same web address but a different id, and that does not come back.", "mig.check_btn": "Check", "mig.check_hint": "This changes nothing yet. You will see what would happen first.", "mig.r_dry": "What would happen", "mig.r_done": "Imported", "mig.r_would": "would be added", "mig.r_imported": "added", "mig.r_skipped": "skipped (already here)", "mig.r_overwritten": "overwritten", "mig.r_media": "media files", "mig.r_media_missing": "media missing from the archive", "mig.r_new_ids": "This archive comes from another address. The posts get new addresses and count as new posts on the fediverse; replies elsewhere pointing at the old addresses will not follow.", "mig.r_confirm_hint": "Looks right? Pick the same file once more to actually do it.", "mig.r_confirm_btn": "Import for real", "mig.follows_title": "Who you follow", "mig.follows_hint": "Your following list is in the archive too, and you can download and restore it separately under Connect.", "mig.follows_btn": "Go to Connect", "admin.b_migrate": "\ud83d\udce6 Migrate", 'admin.b_newpost': '✍️ New post', 'admin.b_look': '🎨 Appearance',
     'admin.t_admin': 'Admin', 'admin.t_settings': 'Settings', 'admin.t_audio': 'Audio tracks', 'admin.t_epk': 'Edit press kit', 'admin.t_newsletter': 'Newsletter', 'admin.t_playlists': 'Playlists', 'admin.t_seo': 'SEO', 'admin.t_shows': 'Agenda', 'admin.t_sites': 'Sites', 'admin.t_newsite': 'New site', 'admin.t_editsite': 'Edit: {title}', 'admin.t_stats': 'Statistics', 'admin.t_updates': 'Updates', 'admin.t_users': 'Users', 'admin.t_hub': 'My Klonkt Hub', 'admin.t_manual': 'Manual', 'aset.premium_gate': '{feature} is a premium feature — connect Patreon in Admin → Settings.',
     'admin.b_makesite': '🎨 Create your site', 'admin.b_circle': '🔗 Circle', 'admin.b_stats': '📊 Statistics',
@@ -2006,5 +2006,5 @@
     'admin.b_media': '🎬 Medien', 'admin.t_media': 'Medien', 'admin.media_images': 'Bilder', 'admin.media_videos': 'Videos', 'admin.videos_count': 'Videos', 'admin.videos_empty': 'Noch keine Videos hochgeladen.', 'admin.videos_del_confirm': 'Dieses Video löschen?', 'admin.media_count': 'Bilder', 'admin.media_unused': 'ungenutzt', 'admin.media_cleanup': 'Ungenutzte löschen', 'admin.media_cleanup_confirm': 'Alle ungenutzten Bilder löschen?', 'admin.media_empty': 'Noch keine Bilder hochgeladen.', 'admin.media_copy': 'URL kopieren', 'admin.media_del_confirm': 'Dieses Bild löschen?',
     'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Kommentare', 'admin.b_seo': '🔎 SEO',
-    'admin.b_settings': '⚙️ Einstellungen', "mig.title": "Migrieren", "mig.r_links_fixed": "Beitr\u00e4ge, deren Links jetzt hierher zeigen", "mig.r_posts_updated": "Beitr\u00e4ge repariert (Bilder lokal geholt)", "mig.r_tracks_updated": "Titel erg\u00e4nzt", "mig.r_tracks_missing": "{n} Titel sind NICHT angekommen, weil die Audiodatei fehlte. Sie wurden bewusst nicht angelegt: ein Titel, der in der Liste steht und nicht spielt, ist schlimmer als ein fehlender.", "mig.c_tracks": "Titel", "mig.c_playlists": "Playlists", "mig.audio_missing": "Achtung: Bei {n} Titel(n) ist die Audiodatei nicht auffindbar. Die kommen nicht mit.", "mig.audio_none": "Achtung: Diese Seite hat Musik, aber es kommt kein einziger Titel mit. Die Dateien liegen vermutlich woanders als die Datenbank denkt.", "mig.pull_title": "Von deinem alten Klonkt holen", "mig.pull_hint": "Wenn du schon umgezogen bist, kann dieses Klonkt deine Beitr\u00e4ge direkt beim alten holen. Kein Code zum Abtippen: der Umzug selbst ist der Schl\u00fcssel, denn beide Seiten haben sich damals schon anerkannt.", "mig.pull_source": "Adresse deines alten Kontos", "mig.pull_source_hint": "Aus deinen Aliassen eingetragen. Stimmt das nicht, \u00e4ndere es.", "mig.pull_btn": "Beitr\u00e4ge holen", "mig.pull_done": "Geholt", "mig.pull_failed": "Holen hat nicht geklappt", "mig.r_blocks": "Blockierungen \u00fcbernommen", "mig.state": "{n} Beitr\u00e4ge haben einen Verweis von ihrer alten Adresse.", "mig.state_done": "Die Liste ist vollst\u00e4ndig.", "mig.state_busy": "Die Liste ist noch nicht vollst\u00e4ndig.", "mig.e_no_source": "Es ist kein altes Konto bekannt. Trage die Adresse oben ein.", "mig.e_unreachable": "Das alte Klonkt ist nicht erreichbar.", "mig.e_not_moved_here": "Dieses Konto ist nicht zu diesem Klonkt umgezogen, also gibt es uns nichts.", "mig.e_no_backreference": "Dieses Konto f\u00fchrt die alte Adresse nicht als eigenen Alias. Trage sie erst bei deinen Aliassen ein, sonst kann niemand sehen, dass du es bist.", "mig.e_no_outbox": "Das alte Klonkt hat keine Beitragsliste.", "mig.e_partial": "Auf halbem Weg gestoppt. Was da ist, bleibt; versuche es noch einmal.", "mig.e_config": "Dieses Klonkt kennt seine eigene Adresse nicht.", "mig.e_crash": "Etwas Unerwartetes ist schiefgegangen.", "mig.e_points_at": "Dieses Konto verweist auf:", "mig.lead": "Nimm deine Beitr\u00e4ge und Medien zu einem anderen Klonkt mit, oder hole sie hierher. Dasselbe Archiv wie das Server-Skript, nur von hier aus.", "mig.export_title": "Mitnehmen", "mig.export_hint": "Das erstellt ein Zip mit deinen Beitr\u00e4gen, den Antworten darauf und den zugeh\u00f6rigen Medien.", "mig.c_posts": "Beitr\u00e4ge", "mig.c_replies": "Antworten", "mig.c_media": "Mediendateien", "mig.c_following": "Folge ich", "mig.c_size": "gro\u00df", "mig.missing": "Achtung: Bei {n} Medienverweis(en) liegt die Datei nicht mehr auf der Platte. Die kommen nicht mit.", "mig.too_big": "Dieses Archiv ist zu gro\u00df f\u00fcr die Weboberfl\u00e4che. Nutze scripts/export-archive.mjs auf dem Server.", "mig.export_btn": "Archiv herunterladen (.zip)", "mig.export_none": "Noch nichts zu exportieren.", "mig.import_title": "Hierher holen", "mig.import_hint": "W\u00e4hle ein Archiv. Es zeigt zuerst, was passieren w\u00fcrde, und \u00e4ndert noch nichts.", "mig.file_label": "Archivdatei (.zip)", "mig.overwrite_label": "Vorhandene Beitr\u00e4ge \u00fcberschreiben", "mig.overwrite_hint": "Standardm\u00e4\u00dfig wird ein bereits vorhandener Beitrag \u00fcbersprungen. \u00dcberschreiben l\u00f6scht auch einen Beitrag mit derselben Webadresse aber anderer id, und der kommt nicht zur\u00fcck.", "mig.check_btn": "Pr\u00fcfen", "mig.check_hint": "Das \u00e4ndert noch nichts. Du siehst zuerst, was passieren w\u00fcrde.", "mig.r_dry": "Was passieren w\u00fcrde", "mig.r_done": "Importiert", "mig.r_would": "k\u00e4men dazu", "mig.r_imported": "dazugekommen", "mig.r_skipped": "\u00fcbersprungen (schon vorhanden)", "mig.r_overwritten": "\u00fcberschrieben", "mig.r_media": "Mediendateien", "mig.r_media_missing": "Medien fehlten im Archiv", "mig.r_new_ids": "Dieses Archiv kommt von einer anderen Adresse. Die Beitr\u00e4ge bekommen neue Adressen und gelten im Fediverse als neue Beitr\u00e4ge; Antworten anderswo auf die alten Adressen kommen nicht mit.", "mig.r_confirm_hint": "Sieht gut aus? W\u00e4hle dieselbe Datei noch einmal, um es wirklich zu tun.", "mig.r_confirm_btn": "Jetzt wirklich importieren", "mig.follows_title": "Wem du folgst", "mig.follows_hint": "Deine Folgeliste ist auch im Archiv, und du kannst sie unter Connect separat herunterladen und zur\u00fcckspielen.", "mig.follows_btn": "Zu Connect", "admin.b_migrate": "\ud83d\udce6 Migrieren", 'admin.b_newpost': '✍️ Neuer Beitrag', 'admin.b_look': '🎨 Aussehen',
+    'admin.b_settings': '⚙️ Einstellungen', "mig.title": "Migrieren", "asite.moved_to_migrate": "Aliasse und Umzug stehen jetzt unter Migrieren, zusammen mit Export, Import und Holen.", "mig.alias_title": "Schritt 1: dein altes Konto beanspruchen", "mig.alias_hint": "Gib an, welches Konto fr\u00fcher deins war. Die alte Instanz pr\u00fcft das, bevor sie deine Follower umzieht, und der Hol-Knopf unten braucht es auch.", "mig.alias_label": "Deine fr\u00fcheren Adressen, eine pro Zeile", "mig.alias_note": "Zum Beispiel @du@mastodon.social oder eine vollst\u00e4ndige Actor-Adresse. H\u00f6chstens f\u00fcnf.", "mig.alias_btn": "Aliasse speichern", "mig.move_title": "Letzter Schritt: den Umzug ank\u00fcndigen", "mig.move_hint": "Das sagt allen Followern, dass dein Konto jetzt woanders wohnt. Sie ziehen mit um, und dieses Konto wird danach gesperrt: kein Posten, Folgen, Liken oder Antworten mehr.", "mig.move_label": "Deine neue Adresse", "mig.move_warn": "Das ist der einzige Knopf auf dieser Seite, den du nicht r\u00fcckg\u00e4ngig machen kannst. Mach ihn zuletzt, wenn deine Inhalte dr\u00fcben stehen und stimmen.", "mig.move_btn": "Umzug ank\u00fcndigen", "mig.move_confirm": "Das k\u00fcndigt deinen Umzug allen Followern an und sperrt dieses Konto. Sicher?", "mig.move_done": "Dieses Konto ist bereits umgezogen nach", "mig.r_links_fixed": "Beitr\u00e4ge, deren Links jetzt hierher zeigen", "mig.r_posts_updated": "Beitr\u00e4ge repariert (Bilder lokal geholt)", "mig.r_tracks_updated": "Titel erg\u00e4nzt", "mig.r_tracks_missing": "{n} Titel sind NICHT angekommen, weil die Audiodatei fehlte. Sie wurden bewusst nicht angelegt: ein Titel, der in der Liste steht und nicht spielt, ist schlimmer als ein fehlender.", "mig.c_tracks": "Titel", "mig.c_playlists": "Playlists", "mig.audio_missing": "Achtung: Bei {n} Titel(n) ist die Audiodatei nicht auffindbar. Die kommen nicht mit.", "mig.audio_none": "Achtung: Diese Seite hat Musik, aber es kommt kein einziger Titel mit. Die Dateien liegen vermutlich woanders als die Datenbank denkt.", "mig.pull_title": "Von deinem alten Klonkt holen", "mig.pull_hint": "Wenn du schon umgezogen bist, kann dieses Klonkt deine Beitr\u00e4ge direkt beim alten holen. Kein Code zum Abtippen: der Umzug selbst ist der Schl\u00fcssel, denn beide Seiten haben sich damals schon anerkannt.", "mig.pull_source": "Adresse deines alten Kontos", "mig.pull_source_hint": "Aus deinen Aliassen eingetragen. Stimmt das nicht, \u00e4ndere es.", "mig.pull_btn": "Beitr\u00e4ge holen", "mig.pull_done": "Geholt", "mig.pull_failed": "Holen hat nicht geklappt", "mig.r_blocks": "Blockierungen \u00fcbernommen", "mig.state": "{n} Beitr\u00e4ge haben einen Verweis von ihrer alten Adresse.", "mig.state_done": "Die Liste ist vollst\u00e4ndig.", "mig.state_busy": "Die Liste ist noch nicht vollst\u00e4ndig.", "mig.e_no_source": "Es ist kein altes Konto bekannt. Trage die Adresse oben ein.", "mig.e_unreachable": "Das alte Klonkt ist nicht erreichbar.", "mig.e_not_moved_here": "Dieses Konto ist nicht zu diesem Klonkt umgezogen, also gibt es uns nichts.", "mig.e_no_backreference": "Dieses Konto f\u00fchrt die alte Adresse nicht als eigenen Alias. Trage sie erst bei deinen Aliassen ein, sonst kann niemand sehen, dass du es bist.", "mig.e_no_outbox": "Das alte Klonkt hat keine Beitragsliste.", "mig.e_partial": "Auf halbem Weg gestoppt. Was da ist, bleibt; versuche es noch einmal.", "mig.e_config": "Dieses Klonkt kennt seine eigene Adresse nicht.", "mig.e_crash": "Etwas Unerwartetes ist schiefgegangen.", "mig.e_points_at": "Dieses Konto verweist auf:", "mig.lead": "Nimm deine Beitr\u00e4ge und Medien zu einem anderen Klonkt mit, oder hole sie hierher. Dasselbe Archiv wie das Server-Skript, nur von hier aus.", "mig.export_title": "Mitnehmen", "mig.export_hint": "Das erstellt ein Zip mit deinen Beitr\u00e4gen, den Antworten darauf und den zugeh\u00f6rigen Medien.", "mig.c_posts": "Beitr\u00e4ge", "mig.c_replies": "Antworten", "mig.c_media": "Mediendateien", "mig.c_following": "Folge ich", "mig.c_size": "gro\u00df", "mig.missing": "Achtung: Bei {n} Medienverweis(en) liegt die Datei nicht mehr auf der Platte. Die kommen nicht mit.", "mig.too_big": "Dieses Archiv ist zu gro\u00df f\u00fcr die Weboberfl\u00e4che. Nutze scripts/export-archive.mjs auf dem Server.", "mig.export_btn": "Archiv herunterladen (.zip)", "mig.export_none": "Noch nichts zu exportieren.", "mig.import_title": "Hierher holen", "mig.import_hint": "W\u00e4hle ein Archiv. Es zeigt zuerst, was passieren w\u00fcrde, und \u00e4ndert noch nichts.", "mig.file_label": "Archivdatei (.zip)", "mig.overwrite_label": "Vorhandene Beitr\u00e4ge \u00fcberschreiben", "mig.overwrite_hint": "Standardm\u00e4\u00dfig wird ein bereits vorhandener Beitrag \u00fcbersprungen. \u00dcberschreiben l\u00f6scht auch einen Beitrag mit derselben Webadresse aber anderer id, und der kommt nicht zur\u00fcck.", "mig.check_btn": "Pr\u00fcfen", "mig.check_hint": "Das \u00e4ndert noch nichts. Du siehst zuerst, was passieren w\u00fcrde.", "mig.r_dry": "Was passieren w\u00fcrde", "mig.r_done": "Importiert", "mig.r_would": "k\u00e4men dazu", "mig.r_imported": "dazugekommen", "mig.r_skipped": "\u00fcbersprungen (schon vorhanden)", "mig.r_overwritten": "\u00fcberschrieben", "mig.r_media": "Mediendateien", "mig.r_media_missing": "Medien fehlten im Archiv", "mig.r_new_ids": "Dieses Archiv kommt von einer anderen Adresse. Die Beitr\u00e4ge bekommen neue Adressen und gelten im Fediverse als neue Beitr\u00e4ge; Antworten anderswo auf die alten Adressen kommen nicht mit.", "mig.r_confirm_hint": "Sieht gut aus? W\u00e4hle dieselbe Datei noch einmal, um es wirklich zu tun.", "mig.r_confirm_btn": "Jetzt wirklich importieren", "mig.follows_title": "Wem du folgst", "mig.follows_hint": "Deine Folgeliste ist auch im Archiv, und du kannst sie unter Connect separat herunterladen und zur\u00fcckspielen.", "mig.follows_btn": "Zu Connect", "admin.b_migrate": "\ud83d\udce6 Migrieren", 'admin.b_newpost': '✍️ Neuer Beitrag', 'admin.b_look': '🎨 Aussehen',
     'admin.t_admin': 'Verwaltung', 'admin.t_settings': 'Einstellungen', 'admin.t_audio': 'Audiotracks', 'admin.t_epk': 'Pressemappe bearbeiten', 'admin.t_newsletter': 'Newsletter', 'admin.t_playlists': 'Playlists', 'admin.t_seo': 'SEO', 'admin.t_shows': 'Termine', 'admin.t_sites': 'Sites', 'admin.t_newsite': 'Neue Site', 'admin.t_editsite': 'Bearbeiten: {title}', 'admin.t_stats': 'Statistiken', 'admin.t_updates': 'Updates', 'admin.t_users': 'Benutzer', 'admin.t_hub': 'Mein Klonkt Hub', 'admin.t_manual': 'Handbuch', 'aset.premium_gate': '{feature} ist eine Premium-Funktion — verbinde Patreon in Verwaltung → Einstellungen.',
     'admin.b_makesite': '🎨 Seite erstellen', 'admin.b_circle': '🔗 Kreis', 'admin.b_stats': '📊 Statistik',
Index: src/views/pages/admin-migrate.ejs
===================================================================
--- src/views/pages/admin-migrate.ejs	(revision dc802b4223862381c3a03fc87973462a7b333346)
+++ src/views/pages/admin-migrate.ejs	(revision e9128bc5aab022193bf90a325fb16b03b9d6540f)
@@ -5,4 +5,21 @@
   <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success %></div><% } %>
   <% if (typeof error !== 'undefined' && error) { %><div class="alert alert-error"><%= error %></div><% } %>
+
+  <%# ── Je oude account claimen (FEP-7628 alsoKnownAs) ─────────── %>
+  <%# Bewust als EERSTE: zonder deze claim weigert de oude instantie de Move
+      met no_backreference, en kan de ophaalknop hieronder ook niets. Dit stond
+      op de site-bewerkpagina tussen de kleuren; daar zoekt niemand het als hij
+      aan het verhuizen is. %>
+  <section class="mig-card">
+    <h2><%= t('mig.alias_title') %></h2>
+    <p class="form-hint"><%= t('mig.alias_hint') %></p>
+    <form method="post" action="/admin/migrate/aliases">
+      <label for="mig-alias"><%= t('mig.alias_label') %></label>
+      <textarea id="mig-alias" name="ap_aliases" rows="3" class="mig-input"
+        placeholder="@oud@mastodon.social&#10;https://oude-klonkt.nl/ap/users/jij"><%= aliassen || '' %></textarea>
+      <p class="form-hint"><%= t('mig.alias_note') %></p>
+      <button type="submit" class="btn"><%= t('mig.alias_btn') %></button>
+    </form>
+  </section>
 
   <%# ── Exporteren ─────────────────────────────────────────────── %>
@@ -182,4 +199,26 @@
   </section>
 
+  <%# ── De verhuizing aankondigen (FEP-7628 Move) ──────────────── %>
+  <%# ONDERAAN, en met een rode knop. Dit is de enige knop op deze pagina die
+      de fediverse in gaat en niet terug te draaien is: je volgers verhuizen
+      mee en dit account gaat op slot. Alles hierboven kun je overdoen. %>
+  <section class="mig-card mig-card-danger">
+    <h2><%= t('mig.move_title') %></h2>
+    <p class="form-hint"><%= t('mig.move_hint') %></p>
+    <% if (movedTo) { %>
+      <p class="alert alert-error"><%= t('mig.move_done') %> <code><%= movedTo %></code></p>
+    <% } else { %>
+      <form method="post" action="/admin/sites/<%= site.slug %>/move"
+            onsubmit="return confirm('<%= t('mig.move_confirm') %>');">
+        <input type="hidden" name="next" value="/admin/migrate">
+        <label for="mig-move"><%= t('mig.move_label') %></label>
+        <input type="text" id="mig-move" name="move_target" class="mig-input"
+               placeholder="@nieuw@andere-server.nl" required>
+        <p class="form-hint mig-warn"><%= t('mig.move_warn') %></p>
+        <button type="submit" class="btn btn-danger"><%= t('mig.move_btn') %></button>
+      </form>
+    <% } %>
+  </section>
+
   <%# ── Wie je volgt ────────────────────────────────────────────── %>
   <section class="mig-card">
@@ -206,4 +245,6 @@
   .mig-report code { word-break: break-all; font-size: .88rem; }
   .mig-warn { color: var(--accent); }
+  .mig-card-danger { border-color: color-mix(in srgb, var(--accent, #c33) 45%, var(--rule)); }
+  textarea.mig-input { font-family: inherit; resize: vertical; }
   .mig-report { border-left: 3px solid var(--accent); padding: .8rem 1rem; margin-bottom: 1.2rem;
                 background: color-mix(in srgb, var(--ink, #000) 4%, transparent); border-radius: 0 8px 8px 0; }
Index: src/views/pages/admin-site-edit.ejs
===================================================================
--- src/views/pages/admin-site-edit.ejs	(revision dc802b4223862381c3a03fc87973462a7b333346)
+++ src/views/pages/admin-site-edit.ejs	(revision e9128bc5aab022193bf90a325fb16b03b9d6540f)
@@ -179,13 +179,13 @@
     </fieldset>
 
+    <%# Aliassen en verhuizen staan sinds 14-8 op /admin/migrate, bij elkaar met
+        exporteren, importeren en ophalen (Robin: alle migratie-opties op de
+        migrate-pagina). Ze hoorden hier ook niet thuis: dit scherm gaat over hoe
+        je site eruitziet, en een verhuizing is geen uiterlijk. Alleen de
+        wegwijzer blijft, want wie ze hier zocht moet ze kunnen vinden. %>
     <fieldset>
       <legend><%= t('asite.aliases') %></legend>
-      <p class="form-hint"><%= t('asite.aliases_hint') %></p>
-      <label>
-<%# typeof-guard on purpose: during a deploy an old route can render this new
-    template for one request (view cache fills on first hit). A missing local
-    must degrade to an empty field, not take the whole page down with a 500. %>
-        <textarea name="ap_aliases" rows="3" placeholder="@oud@mastodon.social&#10;https://andere-klonkt.example/ap/users/naam"><%= typeof apAliases !== 'undefined' ? apAliases : '' %></textarea>
-      </label>
+      <p class="form-hint"><%= t('asite.moved_to_migrate') %></p>
+      <p><a href="/admin/migrate" class="btn"><%= t('admin.b_migrate') %></a></p>
     </fieldset>
 
@@ -237,20 +237,4 @@
   </form>
 
-<% if (!isNew) { %>
-  <form method="post" action="/admin/sites/<%= site.slug %>/move" class="admin-form"
-        onsubmit="return confirm('<%= t('asite.move_confirm') %>');">
-    <fieldset>
-      <legend><%= t('asite.move') %></legend>
-      <p class="form-hint"><%= t('asite.move_hint') %></p>
-      <% if (typeof site.moved_to === 'string' && site.moved_to) { %>
-        <p class="form-hint"><strong><%= t('asite.moved_to') %>:</strong> <%= site.moved_to %></p>
-      <% } %>
-      <label>
-        <input type="text" name="move_target" placeholder="@nieuw@andere-server.example" required>
-      </label>
-      <button type="submit" class="btn btn-danger"><%= t('asite.move_btn') %></button>
-    </fieldset>
-  </form>
-<% } %>
 </div>
 
Index: test/migrate-page.test.js
===================================================================
--- test/migrate-page.test.js	(revision e9128bc5aab022193bf90a325fb16b03b9d6540f)
+++ test/migrate-page.test.js	(revision e9128bc5aab022193bf90a325fb16b03b9d6540f)
@@ -0,0 +1,65 @@
+// Alle migratie-opties op één pagina (Robin, 14-8), en wat daarbij stuk kon.
+//
+// Aliassen en verhuizen stonden op de site-bewerkpagina, tussen de kleuren en
+// de feedinstellingen. Dat is de verkeerde plek: het zijn stap 1 en stap 4 van
+// een verhuizing, en het scherm eromheen gaat over hoe je site eruitziet.
+//
+// De verplaatsing had een stille valkuil: de opslagroute van dat scherm schreef
+// ap_aliases uit het formulier. Verdwijnt het veld, dan is de waarde leeg, en
+// dan wist je je claim op je oude account door je accentkleur te wijzigen. De
+// Move weigert daarna met no_backreference en je snapt niet waarom.
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'fs';
+
+process.env.DATABASE_PATH = ':memory:';
+process.env.PUBLIC_BASE_URL = 'https://ik.example';
+
+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; } }
+
+test('het site-bewerkscherm draagt geen alias- of verhuisformulier meer', () => {
+  const ejs = fs.readFileSync('src/views/pages/admin-site-edit.ejs', 'utf8');
+  assert.ok(!/name="ap_aliases"/.test(ejs), 'het aliasveld hoort bij Migreren');
+  assert.ok(!/name="move_target"/.test(ejs), 'en de verhuisknop ook');
+  assert.match(ejs, /\/admin\/migrate/, 'maar er staat wel een wegwijzer, anders zoekt iemand zich rot');
+});
+
+test('de migrate-pagina draagt ze allebei wel', () => {
+  const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
+  assert.match(ejs, /name="ap_aliases"/);
+  assert.match(ejs, /name="move_target"/);
+  // De verhuisknop post naar de BESTAANDE route: een tweede implementatie van
+  // een onomkeerbare actie is precies wat je niet wilt.
+  assert.match(ejs, /action="\/admin\/sites\/<%= site\.slug %>\/move"/);
+  assert.match(ejs, /name="next" value="\/admin\/migrate"/, 'en komt terug waar je vandaan kwam');
+});
+
+test('de volgorde op de pagina volgt de verhuizing: claimen eerst, aankondigen laatst', () => {
+  const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
+  const alias = ejs.indexOf('name="ap_aliases"');
+  const halen = ejs.indexOf('/admin/migrate/pull');
+  const move = ejs.indexOf('name="move_target"');
+  assert.ok(alias > -1 && halen > -1 && move > -1);
+  assert.ok(alias < halen, 'claimen staat boven ophalen: zonder claim geeft de bron niets');
+  assert.ok(halen < move, 'aankondigen staat onderaan: dat is de onomkeerbare stap');
+});
+
+test('een alias overleeft het opslaan van je uiterlijk', async () => {
+  // De valkuil. Geen HTTP nodig: dit gaat om de regel dat een ontbrekend veld
+  // "niet aanraken" betekent en niet "leegmaken".
+  db.prepare("INSERT INTO users (id,username,email,password_hash,role) VALUES ('u1','u','u@t','x','god')").run();
+  db.prepare("INSERT INTO sites (id,slug,title,owner_id,ap_aliases) VALUES ('s1','ik','Ik','u1',?)")
+    .run(JSON.stringify(['https://oud.example/ap/users/robo']));
+
+  const site = db.prepare("SELECT id, ap_aliases FROM sites WHERE slug = 'ik'").get();
+  const formulierZonderVeld = { title: 'Ik', accent: '#c33' };      // zoals het nu binnenkomt
+  const apAliasesJson = Object.prototype.hasOwnProperty.call(formulierZonderVeld, 'ap_aliases')
+    ? null : (site.ap_aliases || null);
+  db.prepare('UPDATE sites SET ap_aliases = ? WHERE slug = ?').run(apAliasesJson, 'ik');
+
+  const na = db.prepare("SELECT ap_aliases FROM sites WHERE slug = 'ik'").get();
+  assert.deepEqual(JSON.parse(na.ap_aliases), ['https://oud.example/ap/users/robo'],
+    'je claim op je oude account mag niet sneuvelen op een kleurwijziging');
+});
