Files
XZBT-NGN/test-fixtures/reference-exhibits/museum-gallery/artifact.boot.js
T
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

87 lines
3.9 KiB
JavaScript

/*
* Museum Gallery — Artifact Display boot (non-primary surface).
*
* This document deliberately never loads contract-core.js, exhibit.js, or
* contract-adapter.js. It has no way to construct an Exhibit State Core --
* it can only attach to the one the Control Room document created, over
* surface-bus.js. If the Control Room is not open, this degrades to a clear
* waiting state rather than inventing its own state (Contract 5.3 Section
* 31.8, Step 6.1 Section 7.2).
*/
(function () {
'use strict';
var Shell = window.XZBTShell;
var el = Shell.el;
var statusEl = el('status');
var announcer = new Shell.Announcer({ node: el('announcement'), idleText: 'Waiting for shared state.' });
var cycleButton = el('cycle-button');
var ARTIFACT_ORDER = ['the-orrery', 'star-map', 'meteorite'];
var ARTIFACT_LABELS = { 'the-orrery': 'The Orrery', 'star-map': 'Star Map', meteorite: 'Meteorite Fragment' };
/* This is a read-only MIRROR of authoritative state, not a second copy of
* exhibit state: it is discarded and re-seeded from a fresh snapshot on
* every attach, and every write attempt is a mutate() call routed back
* through the owner's canonical mutation path -- this document never
* writes to `latest` in response to a user action, only in response to
* a snapshot or an event relayed from the one Core. */
var latest = null;
function render() {
if (!latest) return;
Shell.setText(el('artifact-name'), ARTIFACT_LABELS[latest['artifact.selected']] || latest['artifact.selected']);
Shell.setText(el('lighting-readout'), Shell.formatPercent(latest['lighting.level']));
Shell.setText(el('rotation-readout'), latest['rotation.speed'].toFixed(1) + 'x');
}
var link = window.MuseumGallerySurfaceBus.attach({
timeoutMs: 1500,
onSnapshot: function (snapshot) {
latest = snapshot.values;
Shell.setText(statusEl, 'Attached to Control Room.');
statusEl.classList.remove('is-waiting');
cycleButton.disabled = false;
render();
},
onEvent: function (event) {
if (!latest) return;
if (event.type === 'state.changed' || event.type === 'selection.changed') {
latest[event.target] = event.value;
render();
} else if (event.type === 'action.executed' && event.target === 'action.spotlight-flash') {
announcer.flash('Spotlight flashed.');
}
},
onTimeout: function () {
Shell.setText(statusEl, 'Waiting for the Control Room surface to be open…');
statusEl.classList.add('is-waiting');
cycleButton.disabled = true;
}
});
cycleButton.addEventListener('click', function () {
if (!latest) return;
var idx = ARTIFACT_ORDER.indexOf(latest['artifact.selected']);
var next = ARTIFACT_ORDER[(idx + 1) % ARTIFACT_ORDER.length];
/* Routed to the owner's canonical mutation path -- this is the required
* proof that a native interaction on a non-primary surface obeys the
* same mutation/revision/source/event semantics as primary UI or a
* contract set/invoke (Contract 5.3 Section 31.7). */
link.mutate('set', 'artifact.selected', next);
});
/* `beforeunload` is best-effort only -- it does not reliably fire when
* NGN removes/replaces this frame's iframe element directly (as opposed
* to a real navigation), so it is not the correctness mechanism here.
* `__xzbtSurfaceDispose` is a well-known, optional hook: local-surfaces.js
* calls it synchronously, if present, on this frame's contentWindow right
* before removing the iframe -- deterministic because it is a direct
* same-origin call, not a queued postMessage that a doomed frame might
* never get to process. detach() is idempotent, so it is safe for both
* this hook and beforeunload to fire (or neither, or either alone). */
window.__xzbtSurfaceDispose = function () { link.detach(); };
window.addEventListener('beforeunload', function () { link.detach(); });
window.__museumGalleryDebug = { link: link };
})();