generated from Labyricorn/labyricorn-project-template
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.
This commit is contained in:
@@ -39,23 +39,42 @@
|
||||
* second computation of state.
|
||||
*
|
||||
* @param {object} core an XZBTContractCore.ContractCore instance
|
||||
* @param {object} [options]
|
||||
* @param {function} [options.onConnectionChange] (attachedCount)
|
||||
*/
|
||||
function createOwner(core) {
|
||||
function createOwner(core, options) {
|
||||
options = options || {};
|
||||
var channel = new BroadcastChannel(CHANNEL_NAME);
|
||||
var attachedCount = 0;
|
||||
/* Participant bookkeeping is identity-based (a Set of live participant
|
||||
* ids), not a bare counter. This is what makes it idempotent: a
|
||||
* duplicate `attach` from a still-live participant cannot inflate the
|
||||
* count, and a `detach` for an id that is not (or no longer) present
|
||||
* cannot underflow it -- both matter because iframe reload/removal can
|
||||
* make attach/detach delivery imperfect (see attach()'s participantId
|
||||
* and local-surfaces.js's pre-removal dispose call). */
|
||||
var participants = new Set();
|
||||
|
||||
function notifyConnectionChange() {
|
||||
if (typeof options.onConnectionChange === 'function') {
|
||||
options.onConnectionChange(participants.size);
|
||||
}
|
||||
}
|
||||
|
||||
channel.onmessage = function (ev) {
|
||||
var msg = ev.data;
|
||||
if (!msg || typeof msg !== 'object') return;
|
||||
|
||||
if (msg.type === 'attach') {
|
||||
attachedCount += 1;
|
||||
if (typeof msg.participantId === 'string') {
|
||||
participants.add(msg.participantId);
|
||||
}
|
||||
channel.postMessage({
|
||||
type: 'attach.snapshot',
|
||||
inReplyTo: msg.requestId,
|
||||
snapshot: core.stateSnapshot(),
|
||||
registryRevision: core.registryRevision
|
||||
});
|
||||
notifyConnectionChange();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,7 +94,13 @@
|
||||
}
|
||||
|
||||
if (msg.type === 'detach') {
|
||||
attachedCount = Math.max(0, attachedCount - 1);
|
||||
/* A detach with no/unknown participantId (a stray legacy message, a
|
||||
* duplicate, or one that arrives after that id was already removed)
|
||||
* is a no-op -- Set.delete() only reports and fires the callback
|
||||
* when it actually removed something live. */
|
||||
if (typeof msg.participantId === 'string' && participants.delete(msg.participantId)) {
|
||||
notifyConnectionChange();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,7 +112,7 @@
|
||||
|
||||
return {
|
||||
channel: channel,
|
||||
attachedCount: function () { return attachedCount; },
|
||||
attachedCount: function () { return participants.size; },
|
||||
close: function () { channel.close(); }
|
||||
};
|
||||
}
|
||||
@@ -109,7 +134,14 @@
|
||||
options = options || {};
|
||||
var channel = new BroadcastChannel(CHANNEL_NAME);
|
||||
var requestId = randomId();
|
||||
/* A stable identity for THIS attachment (this document load, this
|
||||
* BroadcastChannel instance). A reload creates a brand-new attach() call
|
||||
* and therefore a brand-new participantId, which is exactly what lets
|
||||
* the owner's Set-based bookkeeping retire the old identity and adopt
|
||||
* the new one without the two ever being confused for each other. */
|
||||
var participantId = randomId();
|
||||
var attached = false;
|
||||
var detached = false;
|
||||
|
||||
var timer = setTimeout(function () {
|
||||
if (attached) return;
|
||||
@@ -134,7 +166,21 @@
|
||||
}
|
||||
};
|
||||
|
||||
channel.postMessage({ type: 'attach', requestId: requestId });
|
||||
channel.postMessage({ type: 'attach', requestId: requestId, participantId: participantId });
|
||||
|
||||
function detach() {
|
||||
/* Idempotent: local-surfaces.js's deterministic pre-removal dispose
|
||||
* call and this document's own best-effort `beforeunload` handler can
|
||||
* both end up calling detach() for the same attachment. Only the
|
||||
* first actually posts a message; every call still tears down the
|
||||
* timer/channel so repeated calls are always safe. */
|
||||
if (attached && !detached) {
|
||||
detached = true;
|
||||
channel.postMessage({ type: 'detach', participantId: participantId });
|
||||
}
|
||||
clearTimeout(timer);
|
||||
channel.close();
|
||||
}
|
||||
|
||||
return {
|
||||
channel: channel,
|
||||
@@ -148,11 +194,7 @@
|
||||
channel.postMessage(payload);
|
||||
return true;
|
||||
},
|
||||
detach: function () {
|
||||
if (attached) channel.postMessage({ type: 'detach' });
|
||||
clearTimeout(timer);
|
||||
channel.close();
|
||||
}
|
||||
detach: detach
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user