generated from Labyricorn/labyricorn-project-template
Step 6.2 Complete — Museum Gallery reference exhibit and Contract 5.3 spec
This commit is contained in:
@@ -0,0 +1,755 @@
|
||||
# XZBT Multi-Surface Presentation Model — Step 6.1
|
||||
|
||||
## Status
|
||||
|
||||
**Revision 2 — for final approval.** Contract 5.3 has been approved by the
|
||||
owner as the intended version for multi-surface presentation (Section 14).
|
||||
The versioned `v5.3.md` contract file has **not** been minted and Phase 6.2
|
||||
has **not** begun; both remain gated on approval of this revised document.
|
||||
|
||||
This revision addresses five items raised against Revision 1:
|
||||
|
||||
1. defines a generic surface-attachment mechanism (new Section 7.2);
|
||||
2. restores the canonical target-ID grammar for surface IDs, with no carve-out
|
||||
(Section 5.1);
|
||||
3. tightens `primary` and backward-compatibility semantics, resolving the
|
||||
ambiguity previously left as Open Question 1 (Section 6);
|
||||
4. relaxes the direct-open standalone requirement so it no longer conflicts
|
||||
with the new attachment mechanism (Section 10);
|
||||
5. adds an explicit URL-resolution algorithm for the surface `url` field
|
||||
(new Section 6.3).
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
Step 5 closed the XZBT-NGN MVP around a single implicit assumption: one
|
||||
exhibit has one renderable visual surface, shown in the one iframe the host
|
||||
loads.
|
||||
|
||||
Step 6 removes that assumption. An XZBT-compatible exhibit may have more than
|
||||
one full-screen visual presentation — a main interface, an observation
|
||||
display, an information wall, a second dome view — that are all views onto
|
||||
**one logical exhibit instance and one authoritative state**, not independent
|
||||
exhibits.
|
||||
|
||||
This document defines:
|
||||
|
||||
- what a presentation surface is and is not;
|
||||
- how an exhibit advertises its surfaces;
|
||||
- the generic mechanism by which a surface document attaches to the one
|
||||
authoritative exhibit state, regardless of exhibit implementation;
|
||||
- how surfaces stay synchronized to that one state;
|
||||
- the lifecycle a surface goes through;
|
||||
- who owns what (exhibit / NGN / transport / display endpoint);
|
||||
- whether this requires a new contract version;
|
||||
- what remains explicitly out of scope (Step 7).
|
||||
|
||||
It does not define casting, remote displays, or any display-transport
|
||||
mechanism. Those are Step 7.
|
||||
|
||||
---
|
||||
|
||||
## 2. Definitions
|
||||
|
||||
**Logical exhibit instance.** One running exhibit session, with one
|
||||
`sessionId`, one `stateRevision` history, and one authoritative set of
|
||||
contract-visible persistent state values, as already defined by Contract 5.2
|
||||
§13–§14. Multi-surface support does not change this definition; it depends on
|
||||
it remaining singular.
|
||||
|
||||
**Presentation surface.** A renderable, full-screen(-capable) visual view of
|
||||
the logical exhibit instance, addressable by a stable identifier, that an
|
||||
exhibit advertises as independently viewable. A surface displays a coherent
|
||||
subset (or the whole) of the exhibit's presentation, driven by the same
|
||||
underlying state and the same contract session as every other surface of the
|
||||
same exhibit.
|
||||
|
||||
**Surface descriptor.** The machine-readable metadata an exhibit publishes
|
||||
describing one presentation surface: identifier, label, lifecycle-relevant
|
||||
metadata, and enough information for a generic host to open it without any
|
||||
exhibit-specific knowledge.
|
||||
|
||||
**Exhibit State Core.** The single in-exhibit authority for contract-visible
|
||||
state, mutation, and event fan-out. Every exhibit already has exactly one of
|
||||
these today, whether or not it is named as such — it is whatever internal
|
||||
object/module the existing contract adapter (e.g.
|
||||
`test-fixtures/reference-exhibits/shared/contract-core.js`) reads and writes.
|
||||
Multi-surface support does not introduce a new core; it requires that every
|
||||
surface, however many documents or windows are involved, attach to that one
|
||||
existing core rather than instantiate a second one (Section 7.2).
|
||||
|
||||
**Control UI.** The operator-facing interface NGN renders from `describe`
|
||||
(targets, state, events). It is not a presentation surface. It may run
|
||||
alongside any number of open presentation surfaces.
|
||||
|
||||
**Display endpoint (Step 7, referenced only for contrast).** Where a surface
|
||||
is physically or logically shown — a local browser window, a monitor, a
|
||||
casting receiver. Step 6 defines *what* can be shown; Step 7 defines *where*.
|
||||
|
||||
---
|
||||
|
||||
## 3. Surface vs. Target Distinction
|
||||
|
||||
Contract 5.2 already has a generic extensibility mechanism: targets
|
||||
(state/range/selection/impulse) discovered through `describe`. It is tempting
|
||||
to model a surface as a `state`-kind target (e.g. `surface.observation.open:
|
||||
boolean`). This proposal rejects that approach for surfaces themselves, for
|
||||
four reasons:
|
||||
|
||||
1. **A target represents a piece of exhibit state or an action on it.** A
|
||||
surface is neither — it is a *view*. Modeling "is this surface open" as
|
||||
exhibit state conflates NGN-side presentation bookkeeping with exhibit
|
||||
domain state, which Contract §2 explicitly prohibits ("The contract MUST
|
||||
NOT be expanded merely to move Exhibit Engine responsibilities into the
|
||||
exhibit").
|
||||
2. **Targets have no place to carry surface-specific metadata** a generic
|
||||
host needs to *render a window* rather than *render a control* — no field
|
||||
in the base or kind-specific descriptors (§9) accommodates a document URL,
|
||||
an aspect ratio hint, or a role.
|
||||
3. **A target-based encoding would force every exhibit author to hand-roll an
|
||||
ad hoc convention** (e.g. a `surface.*` naming prefix) for something that
|
||||
should be a first-class, uniformly validated concept — exactly the kind of
|
||||
silent protocol behavior the Step 6 brief prohibits inventing.
|
||||
4. **Surfaces are discovered once per registry revision, not read/written
|
||||
like state.** Their lifecycle (open/visible/hidden/closed) is host-side
|
||||
presentation bookkeeping, not a `set`/`invoke` operation on exhibit state.
|
||||
|
||||
Surfaces are therefore modeled as a **new, parallel discovery construct**:
|
||||
a `surfaces` array returned by `describe`, structurally independent of
|
||||
`targets` and `capabilities`, but discovered through the same message and
|
||||
governed by the same `registryRevision`.
|
||||
|
||||
**What is NOT a presentation surface:**
|
||||
|
||||
- the control/admin UI NGN renders from targets — that is an NGN-owned
|
||||
concept and never appears in `surfaces`;
|
||||
- a capability (§17) — capabilities describe whether a subsystem is usable,
|
||||
not whether a view exists;
|
||||
- a target of any kind — including a hypothetical `kind: "surface"` target,
|
||||
which this proposal does not introduce;
|
||||
- a display endpoint — monitors, browser windows, casting receivers are
|
||||
Step 7 and are never named inside a surface descriptor;
|
||||
- a scenario, recording, or packaging artifact.
|
||||
|
||||
---
|
||||
|
||||
## 4. Ownership Model
|
||||
|
||||
| Concern | Owner |
|
||||
| --- | --- |
|
||||
| What surfaces exist, their identifiers, labels, and descriptors | **Exhibit** |
|
||||
| The actual rendering of each surface (HTML/CSS/JS/canvas/audio) | **Exhibit** |
|
||||
| One authoritative state, `stateRevision`, and event stream (the Exhibit State Core) | **Exhibit**, shared by all surfaces of that exhibit |
|
||||
| How surface documents attach to the Exhibit State Core internally | **Exhibit** (Section 7.2 defines the generic pattern; the specific transport choice is an exhibit implementation decision) |
|
||||
| Discovering surfaces from `describe` and reacting to `registry.changed` | **NGN** |
|
||||
| Deciding which surfaces are currently open and where | **NGN** (Step 6: local rendering only) |
|
||||
| Routing a native in-surface interaction back into one canonical mutation path | **Exhibit internally**, using the same contract-visible state mechanism regardless of which surface originated the interaction |
|
||||
| Transport connecting NGN's contract session to the exhibit | **Exhibit + transport binding**, unchanged from Contract §4 |
|
||||
| Where a surface is physically displayed (monitor, window, cast target) | **Step 7 — out of scope here** |
|
||||
|
||||
The exhibit remains the single owner of truth. NGN never maintains a second
|
||||
copy of exhibit state per surface; it discovers surfaces, opens/closes their
|
||||
presentation, and continues to observe the one session's state and events
|
||||
exactly as it does today. This is the direct application of Contract §2: the
|
||||
exhibit exposes capabilities and state, NGN decides how to coordinate
|
||||
presentation of them.
|
||||
|
||||
---
|
||||
|
||||
## 5. Surface Descriptor Proposal
|
||||
|
||||
### 5.1 Minimum required fields
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "surface.control",
|
||||
"label": "Control Room",
|
||||
"kind": "surface",
|
||||
"primary": true,
|
||||
"url": "control.html"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Requirement | Notes |
|
||||
| --- | --- | --- |
|
||||
| `id` | REQUIRED string | **MUST conform exactly to the canonical target-ID grammar in Contract §8.1** — lowercase ASCII letters, digits, hyphens, and dots; begins with a lowercase letter; one or more dot-separated segments; no whitespace; no empty segments; recommended grammar `[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+`. No surface-specific carve-out exists: a bare single-segment ID (e.g. `"observation"`) is **not** valid, exactly as it would not be valid for a target ID. Authors are expected to namespace surface IDs meaningfully (e.g. `surface.control`, `surface.observation`, or an exhibit-specific namespace such as `gallery.artifact-wall`), the same discretion Contract §8 already gives target authors. Stability rule of §8.2 applies identically: once published, a surface ID is part of the exhibit's compatibility surface and SHOULD NOT be renamed casually. |
|
||||
| `label` | REQUIRED string | Human-readable, for generic host display. |
|
||||
| `kind` | REQUIRED, constant `"surface"` | Distinguishes this array's entries from targets/capabilities defensively, even though they already live in a separate array; makes the descriptor self-describing if ever handled outside its parent array. |
|
||||
| `primary` | REQUIRED boolean | See Section 6.2 for the exact structural invariant and its enforcement. |
|
||||
| `url` | REQUIRED string | See Section 6.3 for the full resolution algorithm. Summary: a same-origin, relative reference (path, and/or query/hash) resolved against the exhibit's own base URL — never an absolute or cross-origin URL. |
|
||||
|
||||
### 5.2 Optional fields (useful, not yet mandatory)
|
||||
|
||||
| Field | Purpose |
|
||||
| --- | --- |
|
||||
| `description` | Longer human-readable text for admin display. |
|
||||
| `role` | Free-text or small controlled vocabulary hint (e.g. `"control"`, `"ambient"`, `"information"`) for host UI grouping. Advisory only; no contract behavior depends on it in 5.3. |
|
||||
| `aspectRatio` | e.g. `"16:9"`. Advisory hint for window sizing. |
|
||||
| `category` | Mirrors target `category` (§9) for consistent grouping conventions across the contract. |
|
||||
| `requires` | Array of capability IDs (§17), same semantics as target `requires`: the surface remains discoverable but SHOULD be presented as degraded/unavailable if a required capability is not `ready`. |
|
||||
|
||||
### 5.3 Explicitly excluded fields
|
||||
|
||||
No field in the surface descriptor may name a display transport, a casting
|
||||
protocol, a network endpoint, a device class, or a rendering target other than
|
||||
"a document this host can load." This is the Step 6/Step 7 boundary enforced
|
||||
at the schema level, not just by convention.
|
||||
|
||||
---
|
||||
|
||||
## 6. Discovery Proposal
|
||||
|
||||
Surfaces are discovered exactly where targets and capabilities are discovered:
|
||||
in the `describe.result` response, as a new top-level array:
|
||||
|
||||
```json
|
||||
{
|
||||
"xzbt": "5.3",
|
||||
"type": "describe.result",
|
||||
"requestId": "req-010",
|
||||
"sessionId": "sess-7f2a",
|
||||
"exhibit": { "product": "museum-gallery", "version": "0.1.0", "build": "dev" },
|
||||
"contract": { "major": 5, "minor": 3 },
|
||||
"registryRevision": 1,
|
||||
"stateRevision": 4,
|
||||
"capabilities": [],
|
||||
"targets": [],
|
||||
"surfaces": [
|
||||
{ "id": "surface.control", "label": "Control Room", "kind": "surface", "primary": true, "url": "control.html" },
|
||||
{ "id": "surface.artifact", "label": "Artifact Display", "kind": "surface", "primary": false, "url": "artifact.html" },
|
||||
{ "id": "surface.info-wall", "label": "Information Wall", "kind": "surface", "primary": false, "url": "info-wall.html" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `surfaces` is governed by `registryRevision`, identically to `targets`. A
|
||||
`registry.changed` event MUST cause NGN to re-run `describe`, which
|
||||
re-reads the current surface set along with the current target set. There
|
||||
is no separate "surface revision" counter — one registry, one revision,
|
||||
covering both targets and surfaces (Section 9).
|
||||
|
||||
### 6.1 Malformed individual entries
|
||||
|
||||
A malformed *individual* surface entry (missing `id`/`label`/`url`, invalid
|
||||
`id` grammar per §8.1, non-relative or unresolvable `url` per Section 6.3) is
|
||||
handled the same way a malformed target entry is already handled: the host
|
||||
SHOULD skip only that entry and log a diagnostic, rather than discarding the
|
||||
entire `surfaces` array. This mirrors the existing pattern in
|
||||
`src/validation.js` and must be proven, not merely asserted, in Phase 6.3.
|
||||
|
||||
### 6.2 `primary` — structural invariant and backward compatibility
|
||||
|
||||
This is tightened relative to the prior revision to remove ambiguity.
|
||||
|
||||
**Three, and only three, recognized forms of `surfaces`:**
|
||||
|
||||
1. **Field absent.** The exhibit does not advertise multi-surface support at
|
||||
all — an ordinary Contract 5.2 exhibit, or a 5.3 exhibit that has not
|
||||
adopted the feature. A 5.3-aware host MUST behave exactly as NGN does
|
||||
today: treat the exhibit's currently-loaded document/frame as one
|
||||
implicit primary surface, with no surface picker UI, no `surfaces`
|
||||
validation, and no behavior change from Step 5. This is the sole
|
||||
legacy/compatibility path and needs no special-casing beyond "the field
|
||||
isn't there."
|
||||
2. **Field present and empty (`[]`).** Treated **identically** to the field
|
||||
being absent (form 1). This removes the ambiguity left open in the prior
|
||||
revision: an exhibit MAY emit `surfaces: []` (e.g. because it computes the
|
||||
field programmatically) without that being treated as an error or as
|
||||
"zero surfaces available." A host MUST NOT distinguish `[]` from absence.
|
||||
3. **Field present and non-empty.** The array MUST contain **exactly one**
|
||||
entry with `primary: true`. This is a *structural* invariant over the
|
||||
whole array, not a per-entry validation concern (unlike the field-level
|
||||
checks in Section 6.1). If the array contains zero `primary: true`
|
||||
entries, or more than one, the **entire `surfaces` array is rejected as
|
||||
malformed** — not just the offending entries — and the host falls back to
|
||||
form 1 behavior (implicit single primary surface = current frame), logging
|
||||
one clear diagnostic identifying the exhibit and the violation. This
|
||||
fallback exists specifically so a broken multi-surface descriptor
|
||||
degrades to "acts like Step 5" rather than to an undefined or partially
|
||||
rendered surface list.
|
||||
|
||||
**Why this matters:** `primary` is what lets a 5.3-aware host that has *not*
|
||||
been updated with any surface-picker UI still do the right thing by default
|
||||
(open the primary surface, ignore the rest) — and it is what tells a fully
|
||||
updated host which surface to treat as "the" exhibit view when no operator
|
||||
choice has been made yet. Exactly one candidate for that role must exist
|
||||
whenever `surfaces` claims to describe more than nothing.
|
||||
|
||||
### 6.3 URL resolution
|
||||
|
||||
`url` MUST be one of:
|
||||
|
||||
- a path relative to the exhibit's own base document location — the same
|
||||
base URL NGN already resolved to load the exhibit's primary document in
|
||||
the first place (i.e., the same resolution basis `src/connection.js` uses
|
||||
today for the initial exhibit URL, not NGN's own admin-page location, and
|
||||
not the requesting surface's future location);
|
||||
- such a relative path with an appended query string and/or fragment
|
||||
(`index.html?view=observation`, `control.html#panel-2`);
|
||||
- a bare query string and/or fragment with no path segment
|
||||
(`#observation`, `?surface=observation`), for a single-page exhibit where
|
||||
surfaces are views within one already-served document rather than separate
|
||||
files. In this form, resolution yields the exhibit's own base document URL
|
||||
with that query/fragment applied.
|
||||
|
||||
**Resolution algorithm:**
|
||||
|
||||
1. Start from the exhibit's currently-established base URL — the same
|
||||
absolute URL already used to load the exhibit and already validated by
|
||||
`src/connection.js`'s same-origin/path-containment checks.
|
||||
2. Resolve `url` against that base using standard relative-URL resolution.
|
||||
3. The resolved absolute URL MUST remain same-origin with the base (Contract
|
||||
§25's same-origin requirement extended to surfaces).
|
||||
4. The resolved path component MUST pass the same path-containment check
|
||||
`server/serve.js` already applies to the primary exhibit path (confined to
|
||||
`public/`, `src/`, or `test-fixtures/`, no `..` escape).
|
||||
5. `url` values that are absolute (`https://…`), protocol-relative
|
||||
(`//…`), or resolve cross-origin or outside the served roots MUST be
|
||||
rejected at step 3 or 4 as a malformed individual entry (Section 6.1) —
|
||||
they do not invalidate the rest of the array.
|
||||
|
||||
This makes explicit what was previously only stated as a prohibition:
|
||||
surface URLs are resolved on exactly the same basis, and validated by
|
||||
exactly the same containment logic, as the primary exhibit document is
|
||||
today. No new resolution concept is introduced.
|
||||
|
||||
---
|
||||
|
||||
## 7. State Synchronization Model
|
||||
|
||||
This is the section the whole feature stands or falls on, per the Step 6
|
||||
brief's critical architectural rule.
|
||||
|
||||
### 7.1 One session, one state, many views
|
||||
|
||||
A surface does **not** open its own contract session. All surfaces of one
|
||||
exhibit instance share the exhibit's single `sessionId`, single
|
||||
`stateRevision` history, and single event `sequence` stream, exactly as
|
||||
defined in Contract §5, §14, and §16 today. Multi-surface support adds zero
|
||||
new session or state-authority concepts — it only adds more places that
|
||||
*read* the one existing truth and more places a native interaction can
|
||||
*originate from*.
|
||||
|
||||
### 7.2 The generic surface-attachment mechanism
|
||||
|
||||
Revision 1 left this as an unresolved implementation choice. This revision
|
||||
defines a concrete, generic pattern every multi-surface exhibit is expected
|
||||
to implement, independent of how many documents or windows are involved.
|
||||
|
||||
**The pattern has two required properties, one required sequence, and two
|
||||
permitted transport shapes.**
|
||||
|
||||
**Required properties:**
|
||||
|
||||
- Exactly one Exhibit State Core exists per exhibit instance (Section 2).
|
||||
Every surface attaches to that one Core. No surface ever instantiates a
|
||||
second Core, a cached copy of state, or an independent computation of
|
||||
state.
|
||||
- Attachment is a runtime relationship between a surface document and the
|
||||
Core, entirely internal to the exhibit. It is not part of the XZBT Exhibit
|
||||
Contract and does not appear in Contract 5.3 — the contract only needs to
|
||||
know that surfaces exist and how NGN opens one (Section 6). This mirrors
|
||||
Contract §4's transport independence: the contract defines semantics, not
|
||||
one mandatory internal wiring.
|
||||
|
||||
**Required attachment sequence** (identical regardless of transport shape
|
||||
below):
|
||||
|
||||
1. The surface document loads and requests attachment from the Core.
|
||||
2. The Core responds with the current authoritative state snapshot — sourced
|
||||
from the same internal read path the contract adapter itself already uses
|
||||
to answer `state.get`, not a second computation — and the current
|
||||
`stateRevision`.
|
||||
3. The Core registers the surface as a subscriber for ongoing
|
||||
`state.changed` / `selection.changed` / relevant event notifications.
|
||||
4. Any native interaction originating in the surface calls the Core's one
|
||||
canonical mutation entry point (Section 7.4) — the same entry point a
|
||||
contract `set`/`invoke` from NGN already uses.
|
||||
5. On detach (surface closed), the Core drops the subscription. No state is
|
||||
held by the surface, so nothing needs to be reconciled or discarded.
|
||||
|
||||
**Two permitted transport shapes for attachment** (an exhibit MUST implement
|
||||
at least one; which one is an exhibit implementation decision, not a
|
||||
contract requirement):
|
||||
|
||||
- **In-process attachment.** The surface is a view rendered within the same
|
||||
JavaScript runtime/document that owns the Core — e.g. a single-page
|
||||
exhibit that swaps visible panels, or a document that embeds other
|
||||
surfaces as same-document components. Attachment is a direct in-memory
|
||||
subscription; no cross-document transport is involved.
|
||||
- **Cross-document attachment.** The surface runs in a separate browsing
|
||||
context (its own window/tab, its own `url` per Section 6.3) and attaches
|
||||
to the Core over a same-origin, exhibit-internal channel — e.g.
|
||||
`BroadcastChannel`, a `SharedWorker`, or same-origin `postMessage` to
|
||||
whichever document hosts the Core (typically the primary surface's
|
||||
document). This channel is entirely separate from, and must not be
|
||||
confused with, the NGN↔exhibit contract transport (Contract §4); it
|
||||
carries no contract envelope and is never observed by NGN.
|
||||
|
||||
The Museum Gallery reference exhibit (Phase 6.2) is required to implement and
|
||||
prove one concrete instance of this pattern — expected to be cross-document
|
||||
attachment, since its surfaces are separate documents/windows opened by NGN
|
||||
(Section 8) — and to document it as the reusable template Phase 6.7 adapts
|
||||
for SciFi-XZBT's Observation surface. Phase 6.2 MAY also demonstrate
|
||||
in-process attachment if useful for the control surface specifically, but at
|
||||
least one cross-document instance must exist to prove the multi-window case
|
||||
the brief requires.
|
||||
|
||||
### 7.3 How a surface receives ongoing state changes
|
||||
|
||||
Covered by the attachment sequence in 7.2 (steps 2–3): a surface never
|
||||
polls; it subscribes at attachment time and receives the same
|
||||
`state.changed`/`selection.changed` notifications the Core already emits
|
||||
internally, over whichever transport shape (in-process or cross-document)
|
||||
its attachment uses.
|
||||
|
||||
### 7.4 Native interaction routing (the canonical mutation path)
|
||||
|
||||
A user acting on any surface — clicking a control on the "Artifact Display,"
|
||||
adjusting a slider on the "Information Wall" — MUST result in exactly the
|
||||
same internal mutation call the exhibit's existing single-surface UI would
|
||||
make, which is the same call the contract adapter uses to satisfy an
|
||||
NGN-issued `set`/`invoke`. There must be exactly **one** function/method per
|
||||
mutable target that changes exhibit state, on the Exhibit State Core itself;
|
||||
every UI element on every surface, and every contract `set`/`invoke` from
|
||||
NGN, calls into that same function. This is not new architecture — it is the
|
||||
existing single-authoritative-state requirement from Contract §14, restated
|
||||
for the case where more than one DOM document can trigger it.
|
||||
|
||||
Concretely for the reference exhibit: the same `contract-adapter.js` /
|
||||
`exhibit.js` core already used by the Step 5 reference fixtures (see
|
||||
`test-fixtures/reference-exhibits/shared/contract-core.js`) is the pattern to
|
||||
extend into a proper Exhibit State Core, not replace. A surface's document is
|
||||
a thin presentation layer attached to that core; it must not contain a
|
||||
second, parallel copy of state or mutation logic.
|
||||
|
||||
### 7.5 What "no duplicate authoritative state" rules out
|
||||
|
||||
Explicitly disallowed by this model, matching the brief's stated
|
||||
anti-pattern:
|
||||
|
||||
- launching a separate exhibit instance (a second `hello`/session with its
|
||||
own `stateRevision` history) per surface;
|
||||
- a surface document that keeps its own local copy of state and only
|
||||
periodically reconciles it, rather than attaching per Section 7.2;
|
||||
- a surface whose native interactions call an exhibit-internal function that
|
||||
bypasses the Core's one canonical mutation entry point;
|
||||
- two surfaces attaching to two different Core instances of the same
|
||||
exhibit, however attachment is transported.
|
||||
|
||||
---
|
||||
|
||||
## 8. Lifecycle
|
||||
|
||||
A surface descriptor's existence in `surfaces` only establishes that the
|
||||
surface is **available**. Whether it is currently shown is host-side,
|
||||
per-surface, per-session bookkeeping — not exhibit state, and not persisted
|
||||
across a full exhibit reload.
|
||||
|
||||
| State | Meaning | Who tracks it |
|
||||
| --- | --- | --- |
|
||||
| `available` | The surface is discoverable via `describe` but no host action has been taken on it. | Derived from `surfaces` array; not separately tracked. |
|
||||
| `opened` | NGN has loaded the surface's document (Step 6: in a local browser window/view), which then performs the attachment sequence in Section 7.2. | NGN, per session. |
|
||||
| `visible` | The opened surface's window/view currently has visual presence (not minimized/backgrounded). Best-effort; browsers do not always expose this reliably. | NGN, best-effort. |
|
||||
| `hidden` | Opened but not currently visible (backgrounded, minimized). | NGN, best-effort. |
|
||||
| `closed` | NGN has torn down the surface's window/view; the surface's document detaches from the Core (Section 7.2, step 5). The surface remains `available` for reopening. | NGN, per session. |
|
||||
| `unavailable` | A `requires` capability (Section 5.2) is not `ready`. The surface stays listed but SHOULD be presented as non-operable. | NGN, derived from `capabilities`. |
|
||||
|
||||
Lifecycle transitions are entirely local bookkeeping on the NGN side about
|
||||
*presentation*, not contract-visible exhibit state. Opening, closing, or
|
||||
reopening a surface MUST NOT itself cause a `set`/`invoke`, MUST NOT change
|
||||
`stateRevision`, and MUST NOT be recorded as an `action.executed` event. It
|
||||
may optionally be logged as an NGN-local diagnostic entry, exactly as
|
||||
`src/host.js` already logs connection lifecycle events today.
|
||||
|
||||
**Reopen behavior.** When a previously-closed surface is opened again, its
|
||||
document performs the same attachment sequence as a first open (Section
|
||||
7.2). Because state lives in the Core, not in the surface, a reopened
|
||||
surface reflects current state automatically — there is nothing to "restore"
|
||||
at the surface level.
|
||||
|
||||
---
|
||||
|
||||
## 9. Session/Revision Implications
|
||||
|
||||
- `registryRevision` covers `surfaces` in addition to `targets`/`capabilities`.
|
||||
No separate surface-registry counter is introduced. A `registry.changed`
|
||||
event already means "re-run `describe`"; that now also refreshes the known
|
||||
surface set.
|
||||
- `stateRevision` and event `sequence` are entirely unaffected by
|
||||
multi-surface support — they remain properties of the one session, per
|
||||
Contract §14 and §16, regardless of how many surfaces are open or how they
|
||||
attach to the Core.
|
||||
- **Reconnect.** On session loss and reconnect (existing `src/connection.js`
|
||||
behavior), NGN re-runs the full negotiation and rediscovery, which
|
||||
naturally re-obtains the current `surfaces` array. Any currently-open
|
||||
surface windows are host-side presentation state and are NOT automatically
|
||||
torn down by a reconnect; whether they should be is a Phase 6.5/6.6
|
||||
implementation question (recommend: leave open, let the surface's own
|
||||
document's attachment to the Core recover independently, rather than NGN
|
||||
force-closing surface windows on transient session loss).
|
||||
- **Are surface definitions static per registry revision?** Yes. Within one
|
||||
`registryRevision`, the `surfaces` array is a stable snapshot, exactly like
|
||||
`targets`. An exhibit that wants to change its surface set (add/remove a
|
||||
surface) MUST bump `registryRevision` and emit `registry.changed`, per the
|
||||
existing §23 mechanism — no new mechanism needed.
|
||||
|
||||
---
|
||||
|
||||
## 10. Standalone Behavior
|
||||
|
||||
A standalone exhibit — opened directly in a browser tab, with no NGN attached
|
||||
— MUST continue to work with zero dependency on this model, per Contract §2's
|
||||
governing rule. This revision relaxes the prior draft's requirement that
|
||||
*every* surface be independently openable without any other document present,
|
||||
because that conflicted with the cross-document attachment shape defined in
|
||||
Section 7.2 (a surface attaching via `postMessage`/`BroadcastChannel` to
|
||||
whichever document hosts the Core may genuinely need that document to already
|
||||
be open).
|
||||
|
||||
The precise, tightened requirement:
|
||||
|
||||
- **The primary surface** (Section 6.2) MUST remain fully, unconditionally
|
||||
standalone-capable: a person navigating directly to the exhibit's normal
|
||||
entry point (its existing `index.html`) gets a complete, independently
|
||||
usable experience with zero dependency on NGN, on any other surface being
|
||||
open, or on any attachment sequence having occurred. This is the exhibit's
|
||||
existing standalone guarantee, entirely unchanged by Step 6. Nothing about
|
||||
standalone use of the primary surface requires a person to know surfaces
|
||||
exist.
|
||||
- **Non-primary surfaces** SHOULD remain directly openable without NGN
|
||||
whenever the exhibit's chosen attachment mechanism allows it:
|
||||
- an exhibit using **in-process attachment**, or a **cross-document**
|
||||
mechanism where the surface can bootstrap its own Core connection
|
||||
on demand (e.g. attaching to a `SharedWorker` that starts itself), can
|
||||
and should support a non-primary surface being opened first, with no
|
||||
other document present — matching how SciFi-XZBT's existing Observation
|
||||
view already behaves today;
|
||||
- an exhibit using **cross-document attachment that depends on the primary
|
||||
document already being open** (e.g. plain `postMessage` to a specific
|
||||
existing window) MAY require the primary surface to be open first for a
|
||||
non-primary surface to attach successfully. This is an accepted
|
||||
consequence of an exhibit's internal transport choice (Section 7.2), not
|
||||
a contract violation, and MUST NOT be worked around by having the
|
||||
non-primary surface silently start a second Core.
|
||||
- In either case, this is a same-origin, exhibit-internal dependency
|
||||
between the exhibit's own documents — never a dependency on NGN. A
|
||||
non-primary surface opened without NGN and without its dependency met
|
||||
SHOULD degrade to a clear "waiting for exhibit" state rather than
|
||||
silently diverging into independent state.
|
||||
- Nothing about surface discovery itself requires network access, a host, or
|
||||
any NGN-specific code inside the exhibit. `surfaces` is just more
|
||||
`describe` metadata; an exhibit run standalone never calls `describe` on
|
||||
itself.
|
||||
|
||||
---
|
||||
|
||||
## 11. Host (NGN) Behavior
|
||||
|
||||
- On `describe`, NGN parses `surfaces` alongside `targets`/`capabilities`
|
||||
using the validation discipline in Sections 6.1–6.2: tolerate individually
|
||||
malformed entries, but reject the whole array (falling back to implicit
|
||||
single-primary behavior) on a `primary` structural violation.
|
||||
- NGN's admin/control UI (`src/ui.js`) gains a generic, descriptor-driven
|
||||
surface list — rendered from `label`/`role`/`primary`, never from any
|
||||
hard-coded surface name. This is the direct analog of the existing generic
|
||||
target-rendering rule in the NGN Implementation Plan §8; no exhibit
|
||||
vocabulary may leak into `src/`.
|
||||
- Opening a surface (Phase 6.4) means NGN loads that surface's resolved `url`
|
||||
(Section 6.3) into a new local browser window/view, same-origin, using the
|
||||
same hosting mechanism `server/serve.js` already provides for the primary
|
||||
exhibit frame today.
|
||||
- NGN's control UI and any number of open surface windows coexist; closing
|
||||
or reloading a surface window never tears down the underlying exhibit
|
||||
session, and disconnecting the exhibit session (existing Disconnect
|
||||
behavior) SHOULD close any surface windows NGN opened, since they have
|
||||
nothing left to observe.
|
||||
- A single-surface exhibit (ordinary case, including every Step 5 reference
|
||||
fixture, and every exhibit using discovery form 1 or 2 from Section 6.2)
|
||||
continues to work with **zero NGN behavior change**: one implicit or
|
||||
declared primary surface, opened automatically into the one frame NGN
|
||||
already loads today, with no surface picker UI shown when there is nothing
|
||||
to pick between.
|
||||
|
||||
---
|
||||
|
||||
## 12. Non-Goals (Explicitly Out of Scope for Step 6)
|
||||
|
||||
Restating the brief's guardrails as binding scope boundaries for this
|
||||
document and everything built from it:
|
||||
|
||||
- Casting, Chromecast, Google TV, Android clients, remote/network display
|
||||
endpoints — all Step 7.
|
||||
- Any encoding of a physical or network display target inside a surface
|
||||
descriptor.
|
||||
- MIDI, MCP, webhooks, scenarios, recording, telemetry acquisition,
|
||||
multi-exhibit orchestration, databases, cloud services, user accounts.
|
||||
- Any redesign of Contract 5.2 behavior unrelated to surfaces.
|
||||
- Any surface concept that requires per-surface authentication/authorization
|
||||
beyond the existing same-origin session trust boundary (§25) — internet-
|
||||
facing auth remains NGN's problem generally, unchanged by this feature.
|
||||
- Standardizing the cross-document attachment transport (`BroadcastChannel`
|
||||
vs. `SharedWorker` vs. `postMessage`) as a contract-level choice — Section
|
||||
7.2 deliberately leaves this to the exhibit.
|
||||
|
||||
---
|
||||
|
||||
## 13. Security / Origin Considerations
|
||||
|
||||
- Every surface `url` resolves same-origin per the algorithm in Section 6.3,
|
||||
a direct extension of Contract §25's same-origin `postMessage` validation
|
||||
requirement (`event.origin` and `event.source` checks) to the new case of
|
||||
multiple documents. A surface document is exactly as trusted, and exactly
|
||||
as constrained, as the exhibit's primary document already is.
|
||||
- The exhibit-internal attachment channel (Section 7.2) — `BroadcastChannel`,
|
||||
`SharedWorker`, or same-origin `postMessage` — inherits the same
|
||||
same-origin trust boundary. It carries no contract envelope and MUST NOT be
|
||||
reachable from any other origin; this is ordinary same-origin browser
|
||||
isolation, not a new security mechanism.
|
||||
- No surface descriptor field may contain executable markup, a remote origin,
|
||||
or a URL scheme other than a relative path/fragment into the served
|
||||
exhibit. This mirrors the existing safe-text-injection rule (§20) applied
|
||||
to the new descriptor type.
|
||||
- NGN's server (`server/serve.js`) already restricts served paths to
|
||||
`public/`, `src/`, and `test-fixtures/` and denies path escapes; surface
|
||||
`url` values are subject to the same path-containment validation (Section
|
||||
6.3, step 4) before the host will load them — a malformed or escaping
|
||||
`url` is a rejected individual entry (Section 6.1), not a followed one.
|
||||
- Multiple surface documents observing one session is a same-origin,
|
||||
same-trust-boundary fan-out, not a new external trust relationship. No new
|
||||
authentication concept is introduced in Step 6.
|
||||
|
||||
---
|
||||
|
||||
## 14. Contract-Version Decision — APPROVED, NOT YET MINTED
|
||||
|
||||
### 14.1 Decision
|
||||
|
||||
**The owner has approved Contract 5.3 as the intended version for
|
||||
multi-surface presentation.** This is no longer a pending recommendation; it
|
||||
is the settled architectural direction this document and Phase 6.2 onward
|
||||
are written against.
|
||||
|
||||
**What remains explicitly deferred, per this instruction, until separately
|
||||
authorized:**
|
||||
|
||||
- minting the actual versioned file
|
||||
(`docs/contract/XZBT-Exhibit-Contract-Specification-v5.3.md`);
|
||||
- any change to `contract.major`/`contract.minor` reported by real
|
||||
`describe.result` responses;
|
||||
- any edit to the existing, currently-normative
|
||||
`XZBT-Exhibit-Contract-Specification-v5.2.md` document.
|
||||
|
||||
This document (6.1) continues to illustrate `describe.result` examples as
|
||||
`"xzbt": "5.3"` / `contract: {major: 5, minor: 3}` (Section 6) purely as the
|
||||
**target shape** the eventual 5.3 document will formalize — this is
|
||||
illustrative design content, not a claim that the file or the reported
|
||||
version exists yet.
|
||||
|
||||
### 14.2 Why 5.3 (retained from Revision 1, unchanged)
|
||||
|
||||
`describe.result` gains a new top-level array, `surfaces`. This is additive
|
||||
in the sense that an unaware consumer can ignore an unknown field — but
|
||||
Contract §28.2 is explicit: *"Backward-compatible additions may increase the
|
||||
minor version."* A new, normatively defined top-level response field with its
|
||||
own validation rules (Section 6), its own descriptor grammar (Section 5), and
|
||||
its own required/optional field contract is exactly the category of change
|
||||
§28.2 describes, not a same-version clarification. Step 5's `arguments` field
|
||||
addition (Contract §9.5) was explicitly scoped as *"clarifies Contract 5.2;
|
||||
it does not introduce Contract 5.3"* — because it formalized behavior that
|
||||
was already implicitly required (impulse arguments had to be validated
|
||||
somehow) without adding a new discoverable construct. Surfaces are different:
|
||||
nothing in 5.2 implies surfaces exist, and no 5.2-conformant exhibit or host
|
||||
is required to know what `surfaces` means.
|
||||
|
||||
Contract major stays 5 — this is additive, not breaking. An exhibit that
|
||||
only implements 5.2 behavior remains fully conformant; it simply never
|
||||
emits `surfaces` (Section 6.2, form 1), and a 5.3-aware host tolerates that
|
||||
identically to an exhibit that emits `surfaces: []` (form 2) or a single
|
||||
`primary` entry (form 3). A 5.3-aware host MUST remain fully capable of
|
||||
driving a 5.2-only exhibit exactly as it does today.
|
||||
|
||||
### 14.3 What still requires owner sign-off before the file is minted
|
||||
|
||||
- Timing: whether to mint the versioned file once this 6.1 design is
|
||||
approved, or defer the formal document bump until the surface model has
|
||||
additionally been proven out end-to-end in the reference exhibit (Phases
|
||||
6.2–6.6).
|
||||
- Any wording changes to the *existing* 5.2 document (this proposal
|
||||
recommends none — 5.3 is additive-only — but that remains a decision to
|
||||
reconfirm at minting time).
|
||||
|
||||
---
|
||||
|
||||
## 15. Open Questions
|
||||
|
||||
Revision 1's Open Question 1 (empty vs. absent `surfaces`) is now resolved by
|
||||
Section 6.2 and removed from this list.
|
||||
|
||||
1. Should `role` (Section 5.2) start as free text or a small closed
|
||||
enumeration? This proposal leaves it free text for 6.1–6.6 and defers
|
||||
tightening it until real reference-exhibit and SciFi-XZBT usage exists to
|
||||
generalize from.
|
||||
2. Exact behavior when a currently-open surface's descriptor disappears from
|
||||
a new `describe` result (e.g. `registryRevision` bump removes a surface
|
||||
that is currently open in a window) — recommend NGN closes the window and
|
||||
surfaces a diagnostic, but this needs to be proven in Phase 6.5/6.6, not
|
||||
just asserted here.
|
||||
3. Whether `requires` on a surface (Section 5.2) should block *opening* the
|
||||
surface outright, or only annotate it as degraded while still allowing
|
||||
open (consistent with how a target with an unavailable capability remains
|
||||
discoverable per §9). This proposal recommends the latter, for
|
||||
consistency, but flags it for confirmation during Phase 6.3.
|
||||
4. Whether the generic attachment mechanism (Section 7.2) should eventually
|
||||
be *documented* (not contractually mandated) as a recommended pattern in
|
||||
the Authoring Guide, once the reference exhibit and SciFi-XZBT adaptation
|
||||
have both proven it out — so future exhibit authors aren't reinventing
|
||||
attachment transport choices from scratch. Recommend deferring this
|
||||
documentation question to after Phase 6.8.
|
||||
|
||||
---
|
||||
|
||||
## 16. Acceptance Criteria for 6.1 (Revision 2)
|
||||
|
||||
This document satisfies Phase 6.1 when:
|
||||
|
||||
- [x] It defines what a presentation surface is and is not (Sections 2–3).
|
||||
- [x] It defines the surface descriptor, required and optional fields
|
||||
(Section 5), with surface IDs conforming exactly to the existing
|
||||
canonical target-ID grammar (Section 5.1) — no carve-out.
|
||||
- [x] It defines discovery as an extension to `describe` governed by
|
||||
`registryRevision` (Sections 6, 9), with a tightened, unambiguous
|
||||
`primary`/backward-compatibility invariant covering exactly three
|
||||
recognized forms of `surfaces` (Section 6.2).
|
||||
- [x] It defines an explicit, generic surface-attachment mechanism — required
|
||||
properties, a required attachment sequence, and two permitted
|
||||
transport shapes — that any exhibit implementation can follow without
|
||||
inventing its own protocol (Section 7.2).
|
||||
- [x] It defines an explicit URL-resolution algorithm for the surface `url`
|
||||
field, including same-origin and path-containment enforcement (Section
|
||||
6.3).
|
||||
- [x] It defines a state-synchronization model with a single authoritative
|
||||
state (the Exhibit State Core) shared by all surfaces, and explicitly
|
||||
rules out per-surface duplicated state (Section 7).
|
||||
- [x] It defines the surface lifecycle (Section 8).
|
||||
- [x] It defines ownership across exhibit / NGN / transport / display
|
||||
endpoint (Section 4), explicitly deferring display endpoints to Step 7.
|
||||
- [x] It defines standalone-exhibit compatibility with a precise,
|
||||
non-conflicting requirement split between the primary surface
|
||||
(unconditional) and non-primary surfaces (best-effort, attachment-
|
||||
mechanism-dependent) (Section 10).
|
||||
- [x] It defines security/origin assumptions, extended to the attachment
|
||||
channel (Section 13).
|
||||
- [x] It records the owner-approved Contract 5.3 decision, with the file
|
||||
mint and contract-document edits still explicitly deferred (Section
|
||||
14).
|
||||
- [x] It lists genuine remaining open questions rather than inventing answers
|
||||
where none are warranted (Section 15).
|
||||
- [x] It does not begin implementation, does not create the Contract 5.3
|
||||
file, and does not modify existing source or contract documents.
|
||||
|
||||
**Next step, upon final owner approval of this revision:** mint the
|
||||
versioned Contract 5.3 file per Section 14.3's timing decision, then begin
|
||||
Phase 6.2 — build the Museum Gallery reference exhibit proving this model.
|
||||
Neither has begun as part of this document.
|
||||
Reference in New Issue
Block a user