Files
XZBT-NGN/test-fixtures/reference-exhibits/museum-gallery
Labyricorn 745912e451 Steps 6.4-6.7B — Local surfaces, reference-exhibit validation, SciFi Observation surface
One commit for the work accumulated in the working tree since Step 6.3,
which had never been split into per-step commits:

- src/local-surfaces.js + src/surface-url.js (new); src/ui.js,
  src/validation.js, src/connection.js and public/index.html updated for
  local-surface hosting and generic surface rendering
- tests: local-surfaces (20), scifi-surfaces (24) and postmessage-interop (7)
  new; connection/museum-gallery/surface-validation suites updated
- reference exhibits: shared/contract-core.js defaults to Contract 5.3
  (major 5, minor 3, xzbt 5.3); museum-gallery advertises its surface
  catalog; aquarium/haunted-house/planetarium adapters updated
- SciFi-XZBT (Step 6.7A/6.7B): surface-mode.js + surface-bus.js,
  Observation-surface boot branch, local-change hooks, view.pillars /
  view.warp-flight targets; fixture byte-identical to G:/.vibe/SciFi-XZBT
- SciFi-XZBT contract adapter handshake fix: the inbound bridge filter no
  longer gates on an exact advisory xzbt value (Contract 5.3 §6.5), only on
  its presence/type, matching the host's own envelope validation; the
  adapter now advertises contract minor 3 / version 5.3.0, which it already
  implemented via the 5.3 surfaces field. Root cause of the five failing
  postmessage-interop tests (host hello was silently dropped).
- docs: architecture 6.4 and 6.7A, reference 6.6 and 6.7; evidence logs;
  test-fixtures/PROVENANCE.md resync record

Test results: NGN 154/154 (was 149/154); postmessage-interop 7/7 (was 2/7);
SciFi contract harness 21/21, real-adapter suite 32/32. git diff --check
clean for changed files; two pre-existing trailing-whitespace lines remain
in test-fixtures/reference-exhibits/scifi/index.html, copied verbatim from
the authoritative SciFi source.

Step 6.7 live verification (browser Observation, packaged standalone) is
still pending and is not claimed here.
2026-09-14 19:45:27 -07:00
..

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.