import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import vm from 'node:vm'; const ROOT = new URL('../test-fixtures/reference-exhibits/museum-gallery/', import.meta.url); const SHARED = new URL('../test-fixtures/reference-exhibits/shared/', import.meta.url); function source(base, file) { return readFileSync(new URL(file, base), 'utf8'); } /** Strips vm-realm object/array identity so assert.deepEqual compares plain * host-realm structures instead of failing on cross-realm prototypes. */ function plain(value) { return JSON.parse(JSON.stringify(value)); } /** * A fresh vm context standing in for one browser document. `BroadcastChannel` * is Node's real global implementation (same spec surface as the DOM one), * shared process-wide by channel name -- so two contexts with the same * channel name genuinely talk to each other, the same cross-document shape * Contract 5.3 Section 31.7 and the Step 6.1 architecture document describe, * minus only the literal separate-window/separate-origin browser plumbing. * * Every test that opens channels is responsible for closing them (see * closeAll below) -- Node's BroadcastChannel is a process-wide bus by name, * so a leftover open channel from one test would otherwise go on answering * a later test's attach requests. */ function makeContext() { return vm.createContext({ window: {}, BroadcastChannel, setTimeout, clearTimeout, Math, Date, console }); } function makeOwner(options) { const ctx = makeContext(); vm.runInContext(source(SHARED, 'contract-core.js'), ctx); for (const file of ['exhibit.js', 'contract-adapter.js', 'surface-bus.js']) { vm.runInContext(source(ROOT, file), ctx); } const gallery = new ctx.window.MuseumGalleryExhibit.Gallery(); const core = ctx.window.MuseumGalleryContract.create(gallery); const bus = ctx.window.MuseumGallerySurfaceBus.createOwner(core, options); return { ctx, gallery, core, bus }; } /** A non-primary-surface context. Deliberately loads ONLY surface-bus.js -- * not contract-core.js, not exhibit.js, not contract-adapter.js -- so it is * structurally incapable of constructing a second Core. */ function makeSurfaceContext() { const ctx = makeContext(); vm.runInContext(source(ROOT, 'surface-bus.js'), ctx); return ctx; } function attachSurface(ctx, timeoutMs) { const events = []; return new Promise((resolve, reject) => { const link = ctx.window.MuseumGallerySurfaceBus.attach({ timeoutMs: timeoutMs || 500, onSnapshot: (snapshot, registryRevision) => resolve({ link, events, snapshot: plain(snapshot), registryRevision }), onEvent: (event) => events.push(plain(event)), onTimeout: () => reject(new Error('attach timed out waiting for an owner')) }); }); } function expectTimeout(ctx, timeoutMs) { return new Promise((resolve, reject) => { const link = ctx.window.MuseumGallerySurfaceBus.attach({ timeoutMs: timeoutMs || 150, onSnapshot: () => reject(new Error('unexpectedly attached')), onTimeout: () => resolve(link) }); }); } function waitForEvent(events, predicate, timeoutMs) { const deadline = Date.now() + (timeoutMs || 500); return new Promise((resolve, reject) => { (function poll() { const found = events.find(predicate); if (found) return resolve(found); if (Date.now() > deadline) return reject(new Error('timed out waiting for event')); setTimeout(poll, 5); })(); }); } /** Closes every channel/link handed to it, tolerating already-closed ones. */ function closeAll(...handles) { for (const h of handles) { try { if (!h) continue; if (typeof h.close === 'function') h.close(); else if (h.link && typeof h.link.detach === 'function') h.link.detach(); else if (h.bus && typeof h.bus.close === 'function') h.bus.close(); } catch { /* best-effort cleanup */ } } } /* ------------------------------------------------------------------ * * 1-4: Contract 5.3 describe / surface descriptor conformance * ------------------------------------------------------------------ */ test('Museum Gallery describe reports Contract 5.3 with a valid, single-primary surfaces array', () => { const { core, bus } = makeOwner(); try { const description = plain(core.describe()); assert.equal(description.contract.major, 5); assert.equal(description.contract.minor, 3); assert.ok(Array.isArray(description.surfaces), 'surfaces must be an array'); assert.equal(description.surfaces.length, 3); const ids = description.surfaces.map((s) => s.id).sort(); assert.deepEqual(ids, ['surface.artifact', 'surface.control', 'surface.info-wall']); const primaries = description.surfaces.filter((s) => s.primary === true); assert.equal(primaries.length, 1, 'exactly one primary surface'); assert.equal(primaries[0].id, 'surface.control'); for (const s of description.surfaces) { assert.equal(s.kind, 'surface'); assert.match(s.id, /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/, 'canonical dotted id grammar (Contract 8.1)'); assert.equal(typeof s.label, 'string'); assert.ok(s.label.length > 0); assert.equal(typeof s.url, 'string'); assert.ok(!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(s.url), 'url must not carry a scheme'); assert.ok(s.url.indexOf('//') !== 0, 'url must not be protocol-relative'); } } finally { closeAll(bus); } }); test('Non-surface-aware exhibits are unaffected: describe() omits `surfaces` entirely with no options.surfaces', () => { const ctx = makeContext(); vm.runInContext(source(SHARED, 'contract-core.js'), ctx); const Core = ctx.window.XZBTContractCore; const catalog = new Core.Catalog([ { id: 'mix.master', kind: 'range', min: 0, max: 1, step: 0.01, readable: true, writable: true, restorable: true, category: 'mix', requires: [] } ]); const caps = new Core.CapabilityRegistry(); const core = new Core.ContractCore({ identity: { product: 'plain', version: '1', build: 'x' }, catalog, capabilities: caps, setters: { 'mix.master': () => ({ changed: false }) }, readers: { 'mix.master': () => 0.5 } }); const description = plain(core.describe()); assert.equal('surfaces' in description, false); assert.equal(description.contract.major, 5); assert.equal(description.contract.minor, 3, 'shared contract-core.js now defaults every exhibit forward to Contract 5.3; surfaces stays opt-in regardless'); }); /* ------------------------------------------------------------------ * * SurfaceCatalog validation order (Contract 5.3 Section 31.3, Part A2) * ------------------------------------------------------------------ */ test('SurfaceCatalog: an individually-invalid entry is discarded before the primary invariant is evaluated', () => { const ctx = makeContext(); vm.runInContext(source(SHARED, 'contract-core.js'), ctx); const SurfaceCatalog = ctx.window.XZBTContractCore.SurfaceCatalog; const catalog = new SurfaceCatalog([ { id: 'not_dotted', label: 'Bad', kind: 'surface', primary: true, url: 'bad.html' }, { id: 'surface.control', label: 'Control', kind: 'surface', primary: true, url: 'control.html' }, { id: 'surface.artifact', label: 'Artifact', kind: 'surface', primary: false, url: 'artifact.html' } ]); assert.equal(catalog.isEmpty(), false); assert.equal(catalog.wasRejectedAsMalformed(), false); assert.equal(catalog.descriptors().length, 2); assert.equal(catalog.primaryId(), 'surface.control'); }); test('SurfaceCatalog: zero primary among valid entries rejects the whole catalog and falls back to absent', () => { const ctx = makeContext(); vm.runInContext(source(SHARED, 'contract-core.js'), ctx); const SurfaceCatalog = ctx.window.XZBTContractCore.SurfaceCatalog; const catalog = new SurfaceCatalog([ { id: 'surface.artifact', label: 'Artifact', kind: 'surface', primary: false, url: 'artifact.html' }, { id: 'surface.info-wall', label: 'Info', kind: 'surface', primary: false, url: 'info-wall.html' } ]); assert.equal(catalog.isEmpty(), true); assert.equal(catalog.wasRejectedAsMalformed(), true); assert.equal(catalog.descriptors().length, 0); assert.ok(catalog.diagnostics().some((d) => /exactly one/.test(d))); }); test('SurfaceCatalog: multiple primary among valid entries rejects the whole catalog', () => { const ctx = makeContext(); vm.runInContext(source(SHARED, 'contract-core.js'), ctx); const SurfaceCatalog = ctx.window.XZBTContractCore.SurfaceCatalog; const catalog = new SurfaceCatalog([ { id: 'surface.control', label: 'Control', kind: 'surface', primary: true, url: 'control.html' }, { id: 'surface.artifact', label: 'Artifact', kind: 'surface', primary: true, url: 'artifact.html' } ]); assert.equal(catalog.isEmpty(), true); assert.equal(catalog.wasRejectedAsMalformed(), true); }); test('SurfaceCatalog: discarding every entry (all individually invalid) behaves as absent, not malformed', () => { const ctx = makeContext(); vm.runInContext(source(SHARED, 'contract-core.js'), ctx); const SurfaceCatalog = ctx.window.XZBTContractCore.SurfaceCatalog; const catalog = new SurfaceCatalog([ { id: 'bad id', label: 'x', kind: 'surface', primary: true, url: 'x.html' }, { id: 'surface.y', label: '', kind: 'surface', primary: true, url: 'y.html' } ]); assert.equal(catalog.isEmpty(), true); assert.equal(catalog.wasRejectedAsMalformed(), false, 'empty valid set is absent-equivalent, not a primary violation'); }); test('SurfaceCatalog: an absolute or scheme-carrying url is rejected as an individually invalid entry', () => { const ctx = makeContext(); vm.runInContext(source(SHARED, 'contract-core.js'), ctx); const SurfaceCatalog = ctx.window.XZBTContractCore.SurfaceCatalog; const catalog = new SurfaceCatalog([ { id: 'surface.control', label: 'Control', kind: 'surface', primary: true, url: 'https://evil.example/control.html' }, { id: 'surface.artifact', label: 'Artifact', kind: 'surface', primary: false, url: '//evil.example/artifact.html' }, { id: 'surface.info-wall', label: 'Info', kind: 'surface', primary: false, url: 'info-wall.html' } ]); // Only the third entry is individually valid; the valid set then has zero // primary entries and is rejected as a whole, falling back to absent. assert.equal(catalog.isEmpty(), true); }); /* ------------------------------------------------------------------ * * 5, 12-13: one authoritative Core; primary works fully standalone * ------------------------------------------------------------------ */ test('The primary surface is fully usable standalone: the Core alone supports full read/set/invoke', () => { const { core, bus } = makeOwner(); try { assert.equal(core.stateSnapshot().values['artifact.selected'], 'the-orrery'); const setResult = core.applyMutation('lighting.level', 0.2, 'ui'); assert.equal(setResult.ok, true); assert.equal(setResult.revisionChanged, true); assert.equal(core.stateSnapshot().values['lighting.level'], 0.2); const invokeResult = core.invokeAction('action.spotlight-flash', {}, 'ui'); assert.equal(invokeResult.ok, true); assert.ok(core.eventLog().some((e) => e.type === 'action.executed' && e.target === 'action.spotlight-flash')); // None of this touched any surface or bus -- this is a plain Core call, // exactly what a person opening control.html directly, with no NGN and // no other surface open, gets. } finally { closeAll(bus); } }); test('Exactly one Exhibit State Core exists: non-primary surface contexts cannot construct one', () => { const ctx = makeSurfaceContext(); assert.equal(ctx.window.MuseumGalleryContract, undefined, 'contract-adapter.js was never loaded here'); assert.equal(ctx.window.XZBTContractCore, undefined, 'contract-core.js was never loaded here'); assert.equal(typeof ctx.window.MuseumGallerySurfaceBus.attach, 'function'); }); /* ------------------------------------------------------------------ * * 6, 7, 8, 9, 11: cross-document synchronization via the real attachment bus * ------------------------------------------------------------------ */ test('Attach sequence: a surface opened after state already changed receives the CURRENT snapshot', async () => { const { core, bus } = makeOwner(); let surface; try { core.applyMutation('lighting.level', 0.9, 'ui'); assert.equal(core.stateRevision, 1); const artifactCtx = makeSurfaceContext(); surface = await attachSurface(artifactCtx); assert.equal(surface.snapshot.stateRevision, 1); assert.equal(surface.snapshot.values['lighting.level'], 0.9, 'reopen/reattach gets current state, not stale or default state'); assert.equal(surface.registryRevision, core.registryRevision); } finally { closeAll(bus, surface); } }); test('State changes from the primary propagate to open non-primary surfaces', async () => { const { core, bus } = makeOwner(); let artifact, info; try { artifact = await attachSurface(makeSurfaceContext()); info = await attachSurface(makeSurfaceContext()); core.applyMutation('rotation.speed', 1.2, 'ui'); // simulates the primary UI's own slider const artifactEvent = await waitForEvent(artifact.events, (e) => e.type === 'state.changed' && e.target === 'rotation.speed'); const infoEvent = await waitForEvent(info.events, (e) => e.type === 'state.changed' && e.target === 'rotation.speed'); assert.equal(artifactEvent.value, 1.2); assert.equal(infoEvent.value, 1.2); assert.equal(artifactEvent.stateRevision, core.stateRevision); assert.equal(infoEvent.stateRevision, core.stateRevision); } finally { closeAll(bus, artifact, info); } }); test('A native interaction on a non-primary surface updates the primary AND the other non-primary surface, through the canonical mutation path', async () => { const { core, bus } = makeOwner(); let artifact, info; try { artifact = await attachSurface(makeSurfaceContext()); info = await attachSurface(makeSurfaceContext()); const before = core.stateRevision; const ok = artifact.link.mutate('set', 'artifact.selected', 'star-map'); assert.equal(ok, true); const infoEvent = await waitForEvent(info.events, (e) => e.type === 'selection.changed' && e.target === 'artifact.selected'); assert.equal(infoEvent.value, 'star-map'); assert.equal(infoEvent.source, 'ui', 'surface interactions use the same source vocabulary as primary UI (Contract 5.3 15)'); // "The primary" here IS the Core itself (control.html owns it) -- its // authoritative state reflects the change made from the artifact surface. assert.equal(core.readValue('artifact.selected'), 'star-map'); assert.equal(core.stateRevision, before + 1, 'stateRevision increments exactly once for the one mutation transaction'); } finally { closeAll(bus, artifact, info); } }); test('Event sequence remains one stream regardless of which surface originated the interaction', async () => { const { core, bus } = makeOwner(); let artifact; try { artifact = await attachSurface(makeSurfaceContext()); core.applyMutation('lighting.level', 0.3, 'ui'); // "primary UI" origin artifact.link.mutate('set', 'rotation.speed', 0.8); // non-primary surface origin await waitForEvent(artifact.events, (e) => e.type === 'state.changed' && e.target === 'rotation.speed'); const sequences = core.eventLog().map((e) => e.sequence); const sorted = sequences.slice().sort((a, b) => a - b); assert.deepEqual(sequences, sorted, 'sequence is monotonic across both origins'); assert.equal(new Set(sequences).size, sequences.length, 'no duplicate sequence numbers -- one stream, not one per surface'); } finally { closeAll(bus, artifact); } }); test('Attachment triggers connection callback without state mutation', async () => { const counts = []; let detached; const detachNotification = new Promise((resolve) => { detached = resolve; }); const { core, bus } = makeOwner({ onConnectionChange(count) { assert.equal(count, bus.attachedCount(), 'callback reports the current owner count'); counts.push(count); if (count === 0) detached(); } }); let surface; try { const before = plain(core.stateSnapshot()); const eventsBefore = plain(core.eventLog()); assert.equal(bus.attachedCount(), 0); assert.deepEqual(counts, []); surface = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 1); assert.deepEqual(counts, [1]); assert.deepEqual(plain(core.stateSnapshot()), before); assert.deepEqual(plain(core.eventLog()), eventsBefore); surface.link.detach(); surface = undefined; let timer; try { await Promise.race([ detachNotification, new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('detach callback timed out')), 500); }) ]); } finally { clearTimeout(timer); } assert.equal(bus.attachedCount(), 0); assert.deepEqual(counts, [1, 0]); // Irrelevant messages and an extra detach at zero are not count changes. for (const data of [null, { type: 'unrelated' }, { type: 'detach' }]) { bus.channel.onmessage({ data }); } assert.equal(bus.attachedCount(), 0); assert.deepEqual(counts, [1, 0]); assert.deepEqual(plain(core.stateSnapshot()), before); assert.deepEqual(plain(core.eventLog()), eventsBefore, 'no fake core events'); } finally { closeAll(bus, surface); } }); test('Detach does not mutate state, and a later reattach still observes it correctly', async () => { const { core, bus } = makeOwner(); let first, second; try { first = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 1); const revisionBeforeDetach = core.stateRevision; const eventCountBeforeDetach = core.eventLog().length; first.link.detach(); await new Promise((r) => setTimeout(r, 20)); assert.equal(core.stateRevision, revisionBeforeDetach, 'closing a surface must not change stateRevision'); assert.equal(core.eventLog().length, eventCountBeforeDetach, 'closing a surface must not itself emit a contract event'); core.applyMutation('labels.enabled', false, 'ui'); second = await attachSurface(makeSurfaceContext()); assert.equal(second.snapshot.values['labels.enabled'], false, 'reopened surface reflects current state automatically'); } finally { closeAll(bus, first, second); } }); test('No independent per-surface state exists: two surfaces attached at once never disagree', async () => { const { core, bus } = makeOwner(); let surfaceA, surfaceB; try { surfaceA = await attachSurface(makeSurfaceContext()); surfaceB = await attachSurface(makeSurfaceContext()); for (const value of [0.1, 0.5, 0.95]) { core.applyMutation('lighting.level', value, 'ui'); } await waitForEvent(surfaceB.events, (e) => e.target === 'lighting.level' && e.value === 0.95, 500); await new Promise((r) => setTimeout(r, 20)); const aValues = surfaceA.events.filter((e) => e.target === 'lighting.level').map((e) => e.value); const bValues = surfaceB.events.filter((e) => e.target === 'lighting.level').map((e) => e.value); assert.deepEqual(aValues, [0.1, 0.5, 0.95]); assert.deepEqual(bValues, [0.1, 0.5, 0.95]); assert.equal(core.readValue('lighting.level'), 0.95, 'the Core remains the single source of truth both surfaces converged on'); } finally { closeAll(bus, surfaceA, surfaceB); } }); /* ------------------------------------------------------------------ * * Standalone degrade behavior: a non-primary surface opened with no owner * ------------------------------------------------------------------ */ test('A non-primary surface opened without the Control Room present times out and does not invent a second Core', async () => { // No makeOwner() call in this test at all -- nothing is listening on the // channel, matching "artifact.html opened with no control.html open". const ctx = makeSurfaceContext(); const link = await expectTimeout(ctx, 150); assert.equal(ctx.window.MuseumGalleryContract, undefined); closeAll({ link }); }); /* ------------------------------------------------------------------ * * Participant lifecycle bookkeeping (Step 6.5B live defect regression) * * These simulate, at the surface-bus level, exactly what NGN's * local-surfaces.js now does around a frame reload/close/reopen: it calls * the frame's `__xzbtSurfaceDispose` hook (== the surface's own * `link.detach()`) synchronously before removing the old iframe, and a * reload/reopen produces a brand-new attach() call with its own fresh * participant identity, exactly as a freshly-loaded document would. * ------------------------------------------------------------------ */ /** Simulates NGN reloading/replacing one secondary's iframe: dispose the * old attachment (as local-surfaces.js's release() does before removing * the frame), then attach a fresh one (as the reloaded document does). */ async function simulateReload(oldSurface) { oldSurface.link.detach(); return attachSurface(makeSurfaceContext()); } test('Participant lifecycle: two secondaries attach to a count of exactly 2', async () => { const { bus } = makeOwner(); let a, b; try { assert.equal(bus.attachedCount(), 0); a = await attachSurface(makeSurfaceContext()); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2); } finally { closeAll(bus, a, b); } }); test('Participant lifecycle: reloading one secondary does not ratchet the count up', async () => { const { bus } = makeOwner(); let a, b; try { a = await attachSurface(makeSurfaceContext()); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2); a = await simulateReload(a); assert.equal(bus.attachedCount(), 2, 'reload retires the old participant and adopts the new one -- net zero'); } finally { closeAll(bus, a, b); } }); test('Participant lifecycle: repeated reloads leave the count stable', async () => { const { bus } = makeOwner(); let a, b; try { a = await attachSurface(makeSurfaceContext()); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2); for (let i = 0; i < 5; i += 1) { a = await simulateReload(a); assert.equal(bus.attachedCount(), 2, `count must remain 2 after reload #${i + 1}`); } } finally { closeAll(bus, a, b); } }); test('Participant lifecycle: closing a secondary decrements deterministically without waiting for a Core mutation', async () => { const { core, bus } = makeOwner(); let a, b; try { a = await attachSurface(makeSurfaceContext()); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2); const revisionBeforeClose = core.stateRevision; b.link.detach(); b = undefined; await new Promise((r) => setTimeout(r, 20)); assert.equal(bus.attachedCount(), 1, 'close must decrement immediately, with no Core mutation involved'); assert.equal(core.stateRevision, revisionBeforeClose, 'closing a secondary never mutates exhibit state'); } finally { closeAll(bus, a, b); } }); test('Participant lifecycle: reopening a closed secondary increments exactly once', async () => { const { bus } = makeOwner(); let a, b; try { a = await attachSurface(makeSurfaceContext()); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2); b.link.detach(); b = undefined; await new Promise((r) => setTimeout(r, 20)); assert.equal(bus.attachedCount(), 1); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2, 'reopen brings the count back to 2, not higher'); } finally { closeAll(bus, a, b); } }); test('Participant lifecycle: repeated close/reopen never ratchets in either direction', async () => { const { bus } = makeOwner(); let a, b; try { a = await attachSurface(makeSurfaceContext()); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2); for (let i = 0; i < 3; i += 1) { b.link.detach(); await new Promise((r) => setTimeout(r, 10)); assert.equal(bus.attachedCount(), 1, `count must be 1 after close #${i + 1}`); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2, `count must be 2 after reopen #${i + 1}`); } } finally { closeAll(bus, a, b); } }); test('Participant lifecycle: duplicate/late attach and detach messages cannot inflate or underflow the count', async () => { const { bus } = makeOwner(); let a; try { a = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 1); // A replayed/duplicated attach for a participant id that is already // live (e.g. a retried message) must not inflate the count -- the // owner tracks identities in a Set, so re-adding a live id is a no-op. bus.channel.onmessage({ data: { type: 'attach', requestId: 'dup-req', participantId: 'known-participant' } }); bus.channel.onmessage({ data: { type: 'attach', requestId: 'dup-req', participantId: 'known-participant' } }); assert.equal(bus.attachedCount(), 2, 'two attach messages for the SAME id count as exactly one participant'); bus.channel.onmessage({ data: { type: 'detach', participantId: 'known-participant' } }); assert.equal(bus.attachedCount(), 1, 'detaching that id removes exactly the one participant it represents'); // A late/duplicate detach for an id that is no longer (or never was) // present must not underflow the count. bus.channel.onmessage({ data: { type: 'detach', participantId: 'known-participant' } }); bus.channel.onmessage({ data: { type: 'detach', participantId: 'not-a-real-participant' } }); assert.equal(bus.attachedCount(), 1, 'stale/unknown detach ids must not drive the count below the real count'); a.link.detach(); await new Promise((r) => setTimeout(r, 20)); assert.equal(bus.attachedCount(), 0); bus.channel.onmessage({ data: { type: 'detach', participantId: 'not-a-real-participant' } }); assert.equal(bus.attachedCount(), 0, 'never goes negative or otherwise corrupts at zero'); } finally { closeAll(bus, a); } }); test('Participant lifecycle: synchronization is unaffected by the lifecycle bookkeeping change', async () => { const { core, bus } = makeOwner(); let a, b; try { a = await attachSurface(makeSurfaceContext()); b = await attachSurface(makeSurfaceContext()); assert.equal(bus.attachedCount(), 2); // Control mutation still reaches both mirrors. core.applyMutation('lighting.level', 0.42, 'ui'); await waitForEvent(a.events, (e) => e.target === 'lighting.level' && e.value === 0.42, 500); await waitForEvent(b.events, (e) => e.target === 'lighting.level' && e.value === 0.42, 500); // A secondary mutation request still reaches the primary and the other // mirror through the canonical mutation path. a.link.mutate('set', 'labels.enabled', false); // default is true, so false is an actual change await waitForEvent(b.events, (e) => e.target === 'labels.enabled' && e.value === false, 500); assert.equal(core.readValue('labels.enabled'), false); // A reload (dispose + fresh attach) still gets a correct, current // snapshot -- late-join/attach.snapshot behavior survives the change. a = await simulateReload(a); assert.equal(a.snapshot.values['lighting.level'], 0.42); assert.equal(a.snapshot.values['labels.enabled'], false); assert.equal(bus.attachedCount(), 2); } finally { closeAll(bus, a, b); } });