fix(techdemo): rebootstrap inserted islands

Keep opaque island widgets live when slhx swaps or later DOM insertion adds a new island, and tear down the listener when an island leaves the document.

req: interop/001

req: examples/001
This commit is contained in:
slhx agent
2026-06-01 19:57:39 +02:00
parent 9d3f70e9fe
commit d732c921c2
3 changed files with 46 additions and 2 deletions
+20 -2
View File
@@ -80,12 +80,18 @@
const state = { frame: 0, snapshot: parseSnapshot(island.getAttribute("data-island-snapshot")) };
roots.set(island, state);
rootOf(island).addEventListener("slhx:island-orbit", (event) => {
const root = rootOf(island);
const update = (event) => {
state.snapshot = parseSnapshot(event.detail);
island.setAttribute("data-island-snapshot", event.detail);
});
};
root.addEventListener("slhx:island-orbit", update);
function tick() {
if (!document.contains(island)) {
root.removeEventListener("slhx:island-orbit", update);
return;
}
state.frame += 1;
render(canvas, readout, state);
requestAnimationFrame(tick);
@@ -99,6 +105,18 @@
});
}
function bootAddedIslands(records) {
for (const record of records) {
for (const node of record.addedNodes) {
if (node.nodeType === 1) {
scan();
return;
}
}
}
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", scan);
else scan();
if (typeof MutationObserver !== "undefined") new MutationObserver(bootAddedIslands).observe(document.documentElement, { childList: true, subtree: true });
})();
+24
View File
@@ -52,6 +52,8 @@ async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> {
assert_text(&driver, "No selectors. Generated resources address every target.").await?;
assert_text(&driver, "Opaque island bridge").await?;
wait_for_runtime(&driver).await?;
insert_probe_island(&driver).await?;
wait_for_text(&driver, "#probe-island [data-island-readout]", "probe live").await?;
driver
.find(By::Css(&handle_selector(ui::control_center::handles::simulate_push)))
@@ -97,6 +99,7 @@ async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> {
driver.find(By::Css("a[href='/architecture']")).await?.click().await?;
wait_for_text(&driver, &slot_selector(ui::control_center::slots::inspector), "Page swap").await?;
wait_for_text(&driver, "[data-island-readout]", "activity rows").await?;
assert!(driver.current_url().await?.as_str().ends_with("/architecture"));
driver.goto(&format!("http://{APP_ADDR}/")).await?;
@@ -133,6 +136,27 @@ fn card_button_selector(handle_id: impl std::fmt::Display, work_id: u64) -> Stri
format!(r#"[data-hid="{handle_id}"][data-work-id="{work_id}"]"#)
}
async fn insert_probe_island(driver: &WebDriver) -> WebDriverResult<()> {
driver
.execute(
r#"
document.querySelector('[data-slhx-root]').insertAdjacentHTML('beforeend', `
<article id="probe-island" data-slhx-island="orbit" data-island-snapshot="1|1|1|probe waiting">
<canvas width="32" height="16"></canvas>
<p data-island-readout="">waiting</p>
</article>
`);
setTimeout(() => {
document.querySelector('[data-slhx-root]').dispatchEvent(new CustomEvent('slhx:island-orbit', { bubbles: true, detail: '7|2|8|probe live' }));
}, 25);
return true;
"#,
Vec::new(),
)
.await?;
Ok(())
}
async fn drag_card_to_lane(driver: &WebDriver, work_id: u64, lane: &str) -> WebDriverResult<()> {
driver
.execute(
+2
View File
@@ -71,6 +71,8 @@ fn product_is_e2e_working_over_http() {
assert!(island.header("content-type").contains("javascript"));
assert!(island.text().contains("slhx:island-orbit"));
assert!(island.text().contains("data-slhx-island"));
assert!(island.text().contains("MutationObserver"));
assert!(island.text().contains("removeEventListener"));
let architecture = request("GET", "/architecture", &[("X-SLHX-Partial", "1")], "");
assert_eq!(architecture.status, 200);