generated from Labyricorn/labyricorn-project-template
756 lines
40 KiB
Markdown
756 lines
40 KiB
Markdown
# 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.
|