diff --git a/playground/ms-ai-architect-v3.html b/playground/ms-ai-architect-v3.html
index c4ba165..6f54f87 100644
--- a/playground/ms-ai-architect-v3.html
+++ b/playground/ms-ai-architect-v3.html
@@ -350,6 +350,144 @@
// Step 6+ vil trigger første render her.
}
+ // ============================================================
+ // EXPORT / IMPORT (Step 3)
+ // ============================================================
+ //
+ // Brukeren kan eksportere hele state som JSON-fil og re-importere på en
+ // annen enhet (eller etter localStorage.clear()). Format er en envelope
+ // med schemaVersion + appId — så fremtidige versjoner kan lese gamle
+ // eksporter via MIGRATIONS-pipeline.
+ //
+ // File System Access API krever HTTPS (secure context) og er ikke
+ // tilgjengelig på file:// — vi bruker Blob + URL.createObjectURL +
+ // for eksport, og + File.text() for
+ // import. Begge fungerer på file:// i alle target-browsers.
+ //
+ // MIGRATIONS er en eager pipeline: ved import (eller cold-load fra
+ // gammel state) kjøres alle migrasjoner sekvensielt fra fil-versjon til
+ // gjeldende SCHEMA_VERSION. Aldri hopp over et steg — selv tomme
+ // migrasjoner skal være registrert (no-op) for å bevise at hoppet er
+ // håndtert.
+
+ const APP_ID = 'ms-ai-architect-playground';
+
+ function buildEnvelope() {
+ // Snapshot av rå state. JSON.stringify(JSON.parse(...)) sørger for
+ // at Proxy-er er stripped; vi vil ikke at envelopet skal beholde
+ // wrapper-referanser.
+ const snapshot = store ? JSON.parse(JSON.stringify(store.raw)) : JSON.parse(JSON.stringify(INITIAL_STATE));
+ return {
+ appId: APP_ID,
+ schemaVersion: snapshot.schemaVersion || SCHEMA_VERSION,
+ exportedAt: new Date().toISOString(),
+ shared: snapshot.shared,
+ projects: snapshot.projects,
+ activeProjectId: snapshot.activeProjectId,
+ activeSurface: snapshot.activeSurface,
+ preferences: snapshot.preferences
+ };
+ }
+
+ function exportState() {
+ const envelope = buildEnvelope();
+ const json = JSON.stringify(envelope, null, 2);
+ const blob = new Blob([json], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ const stamp = envelope.exportedAt.replace(/[:.]/g, '-');
+ a.href = url;
+ a.download = APP_ID + '-' + stamp + '.json';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ // revokeObjectURL etter at klikket har skjedd; setTimeout 0 er nok i
+ // alle target-browsers (Blob URL holdes så lenge nedlastningen står
+ // for å initieres).
+ setTimeout(function () { URL.revokeObjectURL(url); }, 0);
+ return envelope;
+ }
+
+ // MIGRATIONS-pipeline. Nøkkel-format: 'N->M' der N og M er fortløpende
+ // SCHEMA_VERSION-tall. Funksjon tar et state-objekt og returnerer et
+ // nytt state-objekt på neste versjon. Aldri muter input.
+ //
+ // Når SCHEMA_VERSION bumpes til 2: legg til '1->2'-funksjon som
+ // transformerer v1-state til v2-form. importState plukker opp
+ // migrasjonen automatisk.
+ const MIGRATIONS = {
+ // Eksempel for fremtid (no-op stub):
+ // '1->2': function (state) { return Object.assign({}, state, { schemaVersion: 2 }); }
+ };
+
+ function migrateState(state) {
+ let current = state;
+ let from = current.schemaVersion || 1;
+ while (from < SCHEMA_VERSION) {
+ const key = from + '->' + (from + 1);
+ const fn = MIGRATIONS[key];
+ if (!fn) {
+ throw new Error('[playground v3] mangler migrasjon ' + key + ' — kan ikke trygt oppgradere import-fil');
+ }
+ current = fn(current);
+ if (current.schemaVersion !== from + 1) {
+ throw new Error('[playground v3] migrasjon ' + key + ' satte ikke schemaVersion til ' + (from + 1));
+ }
+ from = current.schemaVersion;
+ }
+ return current;
+ }
+
+ async function importState(file) {
+ // file er File-objekt fra change-event.
+ // file.text() er Promise — fungerer på file:// uten secure context.
+ const text = await file.text();
+ let envelope;
+ try {
+ envelope = JSON.parse(text);
+ } catch (err) {
+ throw new Error('Ugyldig JSON: ' + err.message);
+ }
+ if (envelope.appId !== APP_ID) {
+ throw new Error('Fil-en er ikke en ' + APP_ID + '-eksport (appId=' + envelope.appId + ')');
+ }
+ if (typeof envelope.schemaVersion !== 'number') {
+ throw new Error('Mangler schemaVersion i envelope');
+ }
+ // Migrer envelope opp til gjeldende SCHEMA_VERSION før vi commit-er.
+ const migrated = migrateState({
+ schemaVersion: envelope.schemaVersion,
+ shared: envelope.shared || INITIAL_STATE.shared,
+ projects: envelope.projects || [],
+ activeProjectId: envelope.activeProjectId || null,
+ activeSurface: envelope.activeSurface || 'home',
+ preferences: envelope.preferences || INITIAL_STATE.preferences
+ });
+ // Skriv direkte til persistens for å unngå at debounce-vinduet
+ // svelger import-en ved en samtidig page-unload.
+ if (persistence) {
+ await persistence.save(migrated);
+ }
+ // Erstatt store-state in-place. Vi kan ikke bytte ut store.raw
+ // sin referanse fordi Proxy-en er bundet til den; muter feltvis.
+ const target = store.raw;
+ target.schemaVersion = migrated.schemaVersion;
+ target.shared = migrated.shared;
+ target.projects = migrated.projects;
+ target.activeProjectId = migrated.activeProjectId;
+ target.activeSurface = migrated.activeSurface;
+ target.preferences = migrated.preferences;
+ // Trigger en change-event manuelt så subscribers re-rendrer.
+ sharedBus.dispatchEvent(new CustomEvent('change', { detail: { paths: ['*'] } }));
+ return migrated;
+ }
+
+ // Eksponer for UI-handlere (Step 5+) og DevTools-debugging.
+ window.__buildEnvelope = buildEnvelope;
+ window.__exportState = exportState;
+ window.__importState = importState;
+ window.__MIGRATIONS = MIGRATIONS;
+
// Auto-bootstrap. Kjør så snart DOM er parsed; vi er på slutten av
// så DOM er allerede klar.
bootstrap().catch(function (err) {