Index: package.json
===================================================================
--- package.json	(revision 495b0191f40d1e9e9de36d8ce98cad627ffb13cb)
+++ package.json	(revision b4aaaddef9ee9b6109381addc714faa320110e88)
@@ -1,5 +1,5 @@
 {
   "name": "klonkt",
-  "version": "1.8.0",
+  "version": "2.0.0-experimental",
   "description": "Klonkt — self-hosted multi-site music & blog platform (solo, hub or circles). Node + SQLite + htmx.",
   "license": "AGPL-3.0-or-later",
Index: src/middleware/render.js
===================================================================
--- src/middleware/render.js	(revision 495b0191f40d1e9e9de36d8ce98cad627ffb13cb)
+++ src/middleware/render.js	(revision b4aaaddef9ee9b6109381addc714faa320110e88)
@@ -74,6 +74,17 @@
 // automatically on every deploy, so the displayed version is never stale.
 let APP_VERSION = '';
+/** Een versie zoals mensen hem lezen. package.json houdt een geldige semver
+ *  (`2.0.0-experimental`), want die gaat ook naar nodeinfo en de User-Agent van
+ *  elke federatie-aanvraag, en een spatie daarin breekt wie hem ontleedt. Op
+ *  het scherm staat het label leesbaar: `2.0.0 - Experimental`. */
+export function displayVersion(v) {
+  const m = /^(\d+\.\d+\.\d+)-([0-9A-Za-z.-]+)$/.exec(String(v || ''));
+  if (!m) return String(v || '');
+  const label = m[2].split(/[.-]/).filter(Boolean)
+    .map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
+  return `${m[1]} - ${label}`;
+}
 try {
-  APP_VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8')).version || '';
+  APP_VERSION = displayVersion(JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8')).version || '');
   try {
     const sha = fs.readFileSync(path.join(__dirname, '..', '..', '.klonkt-version'), 'utf8').trim().slice(0, 7);
