Files
XZBT-NGN/test-fixtures/reference-exhibits/museum-gallery
..

Museum Gallery — Step 6.2 multi-surface reference exhibit

A deliberately small XZBT Contract 5.3 exhibit built to prove the multi-surface presentation model from docs/architecture/XZBT-Multi-Surface-Model-Step6.1.md (Revision 2) and docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md Section 31 — not to be visually elaborate. See docs/reference/Museum-Gallery-Step6.2-Verification.md for the full verification writeup; this file documents the exhibit's own structure and its chosen internal attachment transport.

Entry point

control.html is the primary surface (surface.control, primary: true) and the exhibit's canonical entry point — open it directly for full standalone operation, exactly like any other reference exhibit's index.html. index.html in this directory is a plain redirect to control.html, kept only for directory-listing consistency; it has no behavior of its own.

Surfaces

id label primary url role
surface.control Control Room true control.html control
surface.artifact Artifact Display false artifact.html ambient
surface.info-wall Information Wall false info-wall.html information

Exactly one Exhibit State Core

control.html is the only document that loads contract-adapter.js (and therefore the only document that constructs an XZBTContractCore.ContractCore instance). artifact.html and info-wall.html load only surface-bus.js — they are structurally incapable of constructing a second Core; the shared tests/museum-gallery.test.js suite asserts this directly (see "Exactly one Exhibit State Core exists" in that file).

Chosen internal attachment transport: BroadcastChannel

surface-bus.js implements the generic attachment sequence from Step 6.1 §7.2 using a same-origin BroadcastChannel named xzbt-museum-gallery-core-v1, with control.html as the single, clearly identified authoritative owner document (Contract 5.3 §31.7's "cross-document attachment" shape).

Why BroadcastChannel over the other two permitted shapes:

  • it needs no window-handle bookkeeping (unlike postMessage to a specific window reference, which breaks if the owner window reference is lost or the surface was opened independently rather than via window.open);
  • it needs no separate worker lifecycle (unlike SharedWorker, which is unsupported in some embedding contexts and adds a process to reason about for a reference fixture this small);
  • every participant — owner or surface — only ever needs to know one string (the channel name), which keeps control.html, artifact.html, and info-wall.html fully decoupled from each other; none of them reference the other documents by name or handle.

This transport is exhibit-internal. It carries no XZBT contract envelope, is never observed by a host, and is not mentioned anywhere in Contract 5.3 — per Step 6.1 §7.2 and Contract 5.3 §31.7/§31.9, the contract only needs to know that surfaces exist and how a host opens one. A different exhibit is free to choose SharedWorker, in-process attachment, or another same-origin mechanism entirely.

Message shapes (informal, exhibit-internal only)

  • surface → owner: { type: 'attach', requestId }
  • owner → surface: { type: 'attach.snapshot', inReplyTo, snapshot, registryRevision }
  • owner → all: { type: 'core-event', event } — one relayed copy of every normalized event the Core already emits (state.changed, selection.changed, action.executed, …)
  • surface → owner: { type: 'mutate', kind: 'set'|'invoke', target, value|args } — routed straight into core.applyMutation / core.invokeAction with source: 'ui', the same chokepoint the primary UI's own controls use
  • surface → owner: { type: 'detach' } — bookkeeping only; never mutates state

Shared state

artifact.selected (selection), lighting.level (range 0–1), rotation.speed (range 0–2), labels.enabled (state/boolean), plus one impulse, action.spotlight-flash, to prove action.executed propagation across surfaces.

Native interactions proving the canonical mutation path

  • Artifact Display's "Cycle artifact" button calls link.mutate('set', 'artifact.selected', …).
  • Information Wall's "Toggle labels" button calls link.mutate('set', 'labels.enabled', …).

Both are relayed by the bus into core.applyMutation on the one Core that control.html owns — the same call the Control Room's own controls and a contract set from NGN would make. See the verification document for the event-sequence and stateRevision proof.

Running it directly (no test harness)

Serve this repository with npm start and open:

http://127.0.0.1:4173/test-fixtures/reference-exhibits/museum-gallery/control.html

Then open artifact.html and info-wall.html in separate tabs/windows from the same origin. Changing state in any one window updates the other two. Closing and reopening a non-primary surface reflects current state immediately. Opening artifact.html or info-wall.html alone, with control.html not open anywhere, shows a "Waiting for the Control Room surface to be open…" state rather than inventing its own state.

Known limitation: NGN attachment is not exercised in Step 6.2

control.html includes the optional XZBTHostTransport, matching every other reference exhibit. Because Museum Gallery declares xzbtVersion: '5.3' while the current (pre-6.3) NGN host always sends the envelope tag xzbt: '5.2', an actual attach attempt from today's NGN would be rejected at the envelope-version check rather than negotiating a Contract-major-5 session. This is expected and intentional: NGN's generic surface discovery and version negotiation are Phase 6.3+ work, explicitly out of scope for Step 6.2. See the verification document's known-limitations section.