# Step 6.4 — Local Multi-Surface Rendering Verified 2026-09-14. **Step 6.4 complete.** Contract 5.3 presentation surfaces can be rendered locally through the normal NGN operator UI. ## Pre-change architecture `ExhibitConnection` owned one iframe, remembered its normalized same-origin URL in `connection.url`, and passed it to `ExhibitHost.connect()`. `ExhibitHost` stored that URL as `exhibitBaseUrl`; discovery called `validateSurfaceCatalog()` and stored the surviving descriptors in `host.surfaces`. The UI displayed descriptive surface cards only. The primary iframe alone used `postMessageTransport`, which checked both source window and origin. There was no separate window/frame lifecycle helper for presentation views. Museum Gallery's `control.html` is both its entry point and declared primary. Only this page constructs its Contract Core. `artifact.html` and `info-wall.html` attach through the fixture's existing BroadcastChannel bus and construct no independent Core. NGN does not participate in that bus. SciFi is an external reference implementation, not a source of NGN architecture. The working tree already contained Step 6.3 contract/fixture/test/documentation edits, including an untracked postMessage interoperability test. They were preserved; this task did not commit, push or modify publishing records. ## Files changed for this step | File | Change | | --- | --- | | `src/surface-url.js` | Canonical standards-based resolver | | `src/validation.js` | Reuse resolver; validate optional metadata types | | `src/local-surfaces.js` | Local frame ownership, resource checks and lifecycle | | `src/connection.js` | Establish base from final same-origin control document after navigation | | `src/ui.js` | Descriptor-driven lifecycle controls, state and pane rendering | | `public/index.html` | Operator explanation of local surfaces | | `tests/local-surfaces.test.js` | 17 URL, lifecycle, failure, trust and resource tests | | `tests/connection.test.js` | Final-document URL/navigation regression test | | `README.md` | Current functionality and deferred scope | | This report | Architecture, behavior and verification | | `test-fixtures/evidence/step6.4-*.txt` | Live Museum and SciFi DOM snapshots | No framework, dependencies, exhibit-specific host branches or schema fields were added. Existing CSS supplies pane sizing and button styles. ## Resolution and trust `resolveSurfaceURL(value, exhibitBaseUrl)` uses `new URL()` and returns a normalized absolute URL. Both discovery and opening use it. Sibling paths, `./`, parent paths, query-only and fragment-only references resolve against the supplying exhibit document, never NGN's administrative page. Absolute URLs (including same-origin ones), protocol-relative URLs, invalid values, control characters, surrounding whitespace and cross-origin results are rejected. HTTP(S) and credential-free results are required. The existing static server continues to enforce mount and filesystem containment for both control and presentation resources. Individual invalid entries produce discovery diagnostics without invalidating targets/state. The exactly-one-primary rule is applied to the surviving set. Malformed optional metadata cannot reach the UI's string/array operations. Each open/reload first performs a same-origin HEAD resource check with `redirect: 'error'`. Missing pages become surface errors; redirects are refused before the iframe is created. Frame loads additionally check the resulting origin. Descriptor text is inserted as text, never evaluated. No presentation frame receives a host transport or session, and the existing postMessage source/origin checks are unchanged. A test demonstrates that a same-origin presentation window cannot impersonate the control peer through this transport. ## Rendering, primary handling and lifecycle NGN uses inline iframe panes, one per opened descriptor ID. Nothing is automatically opened beyond the existing control frame. A primary descriptor whose complete normalized URL matches the control document is shown as already open there, with separate surface actions disabled. Its lifecycle belongs to the Connection panel. A separate primary URL is offered through the same Open action as any other presentation view. Queries/fragments remain significant. States are `closed`, `loading`, `open`, `error`, and `control` (already rendered by the authoritative frame). Open during loading/open is a no-op. Reload replaces only the selected presentation document. Close removes it and clears its diagnostic; Open recreates it. UI cards show labels, primary/non-primary status, capability availability, lifecycle state and failures. Disconnect, session reconnect, exhibit replacement and host page departure release owned frames and abort pending checks. Late load/check completions cannot recreate abandoned panes. Removed/changed descriptors release their resources on rediscovery; unchanged frames survive metadata updates. An observer detects externally removed pane frames and updates their state to closed. HTTP failures, frame errors, origin failures and a 15-second load timeout stay local to the affected surface. Reload can retry them. The authoritative control session and synchronized host state remain intact. ## Automated verification Final focused command: ```text node --test tests/local-surfaces.test.js tests/surface-validation.test.js tests/postmessage-interop.test.js tests/connection.test.js 80 passed, 0 failed ``` Coverage includes every requested relative URL form and rejection category; real Museum descriptors loaded from its actual adapter; duplicate-open, reload, close, reopen, disconnect/switch cleanup, pending-load cancellation, registry refresh, distinct primary pages, malformed metadata, timeouts, external removal, cross-origin loads, source impersonation and real HTTP success/404/redirect cases. Lifecycle tests use the production manager and ExhibitHost with EventTarget frames; the existing Step 6.3 suite retains the real production host and exhibit postMessage bridges in browser-like window realms. Full `npm test`: **118 passed, 0 failed**, including Museum Gallery and SciFi connection/synchronization regressions and existing Step 6.3 tests. Node test workers required execution outside the Windows sandbox after its initial `spawn EPERM` denial. `python devlog_editor.py --validate` passed. `git diff --check` passed. ## Live browser verification Performed in the Codex in-app browser against `npm start` at `http://127.0.0.1:4173/`, using the ordinary operator UI: 1. Loaded the requested full Museum `control.html` URL: connected, synchronized, Contract 5.3, three surface descriptors. 2. Primary card reported the existing control frame and disabled duplicate Open. 3. Opened Artifact Display and Information Wall: both reported `open`, rendered their respective page headings/content and displayed “Attached to Control Room.” 4. Reloaded Artifact Display; closed Information Wall (confirmed `closed`), then reopened it (confirmed `open`). 5. Switched to SciFi: both Museum cards/panes disappeared; SciFi reported connected, synchronized, Contract 5.3. 6. Reloaded Museum: both secondary cards began closed and opened cleanly. 7. Explicit Disconnect cleared all frames/catalog; Reconnect rediscovered the closed surfaces and Artifact Display reopened successfully. 8. Loaded SciFi again after the final UI changes: connected and synchronized; no Museum surface cards remained. Saved observations: - [Museum rendered panes](../../test-fixtures/evidence/step6.4-museum-local.txt) - [SciFi regression](../../test-fixtures/evidence/step6.4-scifi-regression.txt) An early batched browser Close action did not change the page; after inspecting the unchanged state, individual Close/Reopen actions passed, including a repeat on the final UI. Failure injection is automated coverage, not a claim of live missing-page or timeout testing. ## Limitations and deferred work - `open` means the document loaded, not that its application scripts are healthy or that cross-surface state synchronization has been proven. - HEAD support is required. HTTP redirects, including same-origin redirects, are conservatively refused for presentation resources. The local static server supports HEAD and does not issue redirects. - These are trusted same-origin exhibit pages under the existing trust model, not a sandbox for hostile exhibit code. A page's later script-driven navigation can only be detected at load; resource checks do not certify application code. - Museum's existing initial shared-state attachment was observed. Its Control Room counter/footer text did not consistently reflect that attachment. No fixture behavior was changed; mutation propagation proof remains **Step 6.5**. - No remote/cast display assignment, device discovery, networking clients, fullscreen orchestration, SciFi Observation adaptation or synchronization architecture was introduced. All Step 6.4 acceptance categories pass. Step 6.5 has not been implemented. *(Adjudication note: Step 6.5 was subsequently adjudicated as satisfied by existing implementation and later evidence at the Step 6.10 completion gate; see the [Step 6.10 closure report](../reference/XZBT-NGN-Step6.10-Closure-Report.md).)*