refactor(console-web): centralize page lifecycle and UI composition
Declare navigation, page metadata, lazy loaders, and keep-alive policy in one registry. Retain primary iframe pages until a continuous inactive TTL expires and expose typed show, hide, and unload events through the Page SDK. Move business-aware components out of the UI primitive directory, migrate views and Host dialogs to variant primitives and local Tailwind classes, and remove the global component style layer. Keep native dialogs inside the overlay flex context so they remain centered. Cover iframe expiration, lifecycle dispatch, UI dependency direction, and global style ownership with focused tests.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { createSignal, onCleanup, onMount } from "solid-js";
|
||||
import type { View } from "../lib/model";
|
||||
import type { View } from "../pages/registry";
|
||||
import { createPageLifecycleDispatcher, type PageLifecycleEventType } from "../sdk/lifecycle";
|
||||
import { isHostMessage, type HostSharedState, type PageCommand } from "../sdk/protocol";
|
||||
import { notifyReady, sendCommand } from "../sdk/page";
|
||||
|
||||
@@ -7,12 +8,14 @@ import { notifyReady, sendCommand } from "../sdk/page";
|
||||
* Connects one iframe document to the persistent Host Shell.
|
||||
*
|
||||
* A keep-alive document remains mounted while inactive and exposes that state
|
||||
* through `active`. Removing the iframe still runs `onCleanup`, so an evicted
|
||||
* document cannot continue to receive Host snapshots.
|
||||
* through `active`. Host `dispose`, browser `pagehide` and Solid cleanup all
|
||||
* converge on one deduplicated SDK unload transition. The document boundary
|
||||
* itself remains the final guarantee that an evicted Page releases its realm.
|
||||
*/
|
||||
export function createFrameBridge(view: View) {
|
||||
const [state, setState] = createSignal<HostSharedState | null>(null);
|
||||
const [active, setActive] = createSignal(false);
|
||||
const pageLifecycle = createPageLifecycleDispatcher();
|
||||
|
||||
const receive = (event: MessageEvent<unknown>) => {
|
||||
if (event.origin !== window.location.origin || event.source !== window.parent) return;
|
||||
@@ -22,7 +25,12 @@ export function createFrameBridge(view: View) {
|
||||
}
|
||||
if (event.data.type === "lifecycle") {
|
||||
const phase = event.data.phase;
|
||||
setActive(phase === "activate");
|
||||
const lifecycleEvent = pageLifecycle.dispatch(eventForPhase(phase), "host");
|
||||
if (!lifecycleEvent) return;
|
||||
setActive(lifecycleEvent.current === "visible");
|
||||
|
||||
// Preserve the original DOM events for existing pages. New code should
|
||||
// use the typed SDK lifecycle returned by this bridge.
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("wasmeld:lifecycle", {
|
||||
detail: { phase },
|
||||
@@ -34,16 +42,39 @@ export function createFrameBridge(view: View) {
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener("message", receive);
|
||||
const unload = (event: PageTransitionEvent) => {
|
||||
// A persisted document is frozen in the back-forward cache, not unloaded.
|
||||
// Its Host and iframe will resume together when the browser restores it.
|
||||
if (event.persisted) return;
|
||||
if (pageLifecycle.dispatch("unload", "document")) setActive(false);
|
||||
};
|
||||
window.addEventListener("pagehide", unload);
|
||||
notifyReady(view);
|
||||
onCleanup(() => window.removeEventListener("pagehide", unload));
|
||||
});
|
||||
|
||||
onCleanup(() => window.removeEventListener("message", receive));
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("message", receive);
|
||||
pageLifecycle.dispatch("unload", "document");
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
active,
|
||||
lifecycle: pageLifecycle.lifecycle,
|
||||
command(command: PageCommand) {
|
||||
sendCommand(view, command);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function eventForPhase(phase: "activate" | "deactivate" | "dispose"): PageLifecycleEventType {
|
||||
switch (phase) {
|
||||
case "activate":
|
||||
return "show";
|
||||
case "deactivate":
|
||||
return "hide";
|
||||
case "dispose":
|
||||
return "unload";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user