Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 861c228ab92d14eec6f839d5157dfaba09749127)
+++ src/routes/activitypub.js	(revision dc802b4223862381c3a03fc87973462a7b333346)
@@ -212,5 +212,11 @@
     limit: AP.PAGINA_GROOTTE,
   });
-  const ob = AP.buildOutbox(baseUrl(req), site, posts, tracks, { page: nr, totalItems: totaal, alGesneden: true });
+  // FEP-1580: de instantie waar dit account naartoe verhuisd is krijgt de
+  // RAUWE inhoud, met [[track:]] en [[playlist:]] er nog in. Zij is een
+  // Klonkt en rendert die zelf tot een speler. De gebakken variant komt
+  // daar aan als tekstlink, en is bovendien onherstelbaar afgeknot: het
+  // bakken plakt hooguit vier titels aan.
+  const rauweInhoud = verifiedActor ? AP.isMoveTarget(site.slug, verifiedActor) : false;
+  const ob = AP.buildOutbox(baseUrl(req), site, posts, tracks, { page: nr, totalItems: totaal, alGesneden: true, rauweInhoud });
   if (audience === 'friend') {
     // The owner's app builds its feed from this leg, and every note here is
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 861c228ab92d14eec6f839d5157dfaba09749127)
+++ src/services/ActivityPubService.js	(revision dc802b4223862381c3a03fc87973462a7b333346)
@@ -706,5 +706,14 @@
     } catch { /* non-fatal */ }
   }
-  body = body.replace(/\[\[(track|album|playlist):[^\]]+\]\]/gi, '');
+  // ONVERTAALD voor een verhuizing (FEP-1580). De doelinstantie IS een Klonkt:
+  // die rendert [[track:]], [[album:]] en [[playlist:]] zelf en maakt er een
+  // speler van. Bakken we ze eerst om, dan komt er een tekstlink aan en is de
+  // speler weg. Onherstelbaar bovendien: het bakken STRIPT de shorthand en
+  // plakt achteraan hooguit VIER titels, dus een album van tien nummers
+  // overleeft het niet.
+  //
+  // Dezelfde regel als bij de outbox en de tracks: wie ondertekend vraagt
+  // namens de actor waar wij naartoe verhuisd zijn, krijgt onze eigen kijk.
+  if (!opts.rauweInhoud) body = body.replace(/\[\[(track|album|playlist):[^\]]+\]\]/gi, '');
   // External embeds ([[embed:url]]) → emit the bare URL as a link so Mastodon
   // renders its OWN preview/player card (YouTube/Spotify/SoundCloud/etc) instead
@@ -714,5 +723,5 @@
     return `<p><a href="${u}">${u}</a></p>`;
   });
-  if (hadAudio) {
+  if (hadAudio && !opts.rauweInhoud) {
     // Elke titel als eigen link naar zijn anker; een titel zonder id (een
     // albumnaam zonder tracks) blijft gewone tekst.
@@ -1039,6 +1048,6 @@
 }
 
-export function buildCreate(base, site, post) {
-  const note = buildNote(base, site, post);
+export function buildCreate(base, site, post, opts = {}) {
+  const note = buildNote(base, site, post, opts);
   return {
     '@context': AP_CONTEXT,
@@ -1123,9 +1132,9 @@
 }
 
-export function buildOutbox(base, site, posts, tracks = [], { page = false, totalItems, alGesneden = false } = {}) {
+export function buildOutbox(base, site, posts, tracks = [], { page = false, totalItems, alGesneden = false, rauweInhoud = false } = {}) {
   const id = `${actorId(base, site.slug)}/outbox`;
   const wanneer = (x) => Date.parse(x && x.published ? x.published : 0) || 0;
   const items = [
-    ...(posts || []).map((p) => buildCreate(base, site, p)),
+    ...(posts || []).map((p) => buildCreate(base, site, p, { rauweInhoud })),
     // Eén zoekopdracht voor alle tracks samen, niet per stuk.
     ...(() => {
Index: test/fep1580-migration.test.js
===================================================================
--- test/fep1580-migration.test.js	(revision 861c228ab92d14eec6f839d5157dfaba09749127)
+++ test/fep1580-migration.test.js	(revision dc802b4223862381c3a03fc87973462a7b333346)
@@ -612,4 +612,28 @@
 });
 
+test('de doelinstantie krijgt de RAUWE inhoud, met de shorthand er nog in', () => {
+  // Robins laatste klacht: de post kwam aan met "🎵 TikTik — listen on
+  // soundfabrics" en zonder speler. De bron BAKT de shorthand namelijk om voor
+  // de fediverse: [[track:]] wordt gestript en er komt achteraan een blok met
+  // hooguit VIER titels. Onherstelbaar dus: een album van tien nummers is weg.
+  //
+  // Voor een verhuizing hoort dat bakken niet te gebeuren. De doelinstantie is
+  // zelf een Klonkt en maakt van [[track:]] gewoon weer een speler.
+  const s = site();
+  db.prepare("INSERT INTO media (id, site_id, filename, mime_type, size, storage_path) VALUES ('m1','s1','a.mp3','audio/mpeg',1,'/x/a.mp3')").run();
+  db.prepare("INSERT INTO audio_tracks (id, site_id, title, media_id) VALUES ('t1','s1','Nummer A','m1')").run();
+  db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, content, status, published_at)
+              VALUES ('pa','s1','u1','plaat','Plaat','<p>hoor dit</p>[[track:t1]]','published','2026-01-01T10:00:00Z')`).run();
+  const post = db.prepare("SELECT * FROM posts WHERE id = 'pa'").get();
+
+  const gebakken = AP.buildNote('https://oud.example', s, post);
+  assert.ok(!gebakken.content.includes('[[track:t1]]'), 'de gewone federatie bakt hem om');
+  assert.match(gebakken.content, /listen on/, 'en plakt er een tekstlink aan');
+
+  const rauw = AP.buildNote('https://oud.example', s, post, { rauweInhoud: true });
+  assert.ok(rauw.content.includes('[[track:t1]]'), 'voor een verhuizing blijft de shorthand staan');
+  assert.ok(!/listen on/.test(rauw.content), 'en er wordt geen tekstlink aangeplakt');
+});
+
 test('een bericht dat je zelf hebt verwijderd komt bij een tweede ronde terug', async () => {
   // Robin: "ik kan handmatig deze keer de posts verwijderen en opnieuw ophalen."
