Step 6.2 Complete — Museum Gallery reference exhibit and Contract 5.3 spec

This commit is contained in:
2026-09-14 14:09:13 -07:00
parent 961919e017
commit 44f2ad3ee6
20 changed files with 5672 additions and 8 deletions
@@ -0,0 +1,121 @@
# 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.