diff --git a/rynk/rynk-wasm/index.html b/rynk/rynk-wasm/index.html
index 41524fd6e..2b4cdfce4 100644
--- a/rynk/rynk-wasm/index.html
+++ b/rynk/rynk-wasm/index.html
@@ -53,10 +53,12 @@
Rynk web demo
const v = await fn();
const isObj = v !== null && typeof v === "object";
logLine(`✓ ${pad} ${isObj ? JSON.stringify(v) : v}`, isObj);
+ return v;
} catch (e) {
const kind = (e && e.name) || "Error";
const glyph = kind === "Unsupported" ? "·" : kind === "Rejected" ? "⚠" : "✗";
log(`${glyph} ${pad} ${kind}: ${e && e.message}`);
+ return null;
}
}
@@ -253,13 +255,20 @@ Rynk web demo
}
// One live session across both transports.
- let port = null, device = null, l = null, core = null, client = null, connected = false;
+ let port = null, device = null, l = null, core = null, client = null, connected = false, topicsStarted = false;
+
+ function startTopicPump() {
+ if (!client || topicsStarted) return;
+ topicsStarted = true;
+ log("\n— listening for topics (type on the keyboard); click to disconnect —");
+ pumpTopics(client);
+ }
async function teardown() {
// Closing the link EOFs the transport, ending any parked next_event() pump.
if (l) await l.close();
else if (port) { try { await port.close(); } catch {} }
- port = null; device = null; l = null; core = null; client = null; connected = false;
+ port = null; device = null; l = null; core = null; client = null; connected = false; topicsStarted = false;
serialBtn.textContent = "Connect via Serial (USB)";
bleBtn.textContent = "Connect via BLE (WebHID)";
serialBtn.disabled = false; bleBtn.disabled = false;
@@ -292,7 +301,7 @@ Rynk web demo
// Exercise the typed client surface.
await show("version", () => client.get_version());
- await show("lock status", () => client.get_lock_status());
+ const lockStatus = await show("lock status", () => client.get_lock_status());
await show("default layer", () => client.get_default_layer());
await show("current layer", () => client.get_current_layer());
await show("key L0(0,0)", () => client.get_key(0, 0, 0));
@@ -310,12 +319,16 @@ Rynk web demo
// Gated behind the lock: shows "⚠ Rejected: Locked" until unlocked.
await show("matrix state", () => client.get_matrix_state());
- unlockBtn.hidden = false; // offer the unlock ceremony while connected
- log("\n— listening for topics (type on the keyboard); click to disconnect —");
thisBtn.textContent = "Disconnect";
thisBtn.disabled = false; // re-enable so it can disconnect
otherBtn.disabled = true; // one transport at a time
- pumpTopics(client); // pull topics until the link closes
+ if (lockStatus?.locked && lockStatus.key_positions.length > 0) {
+ unlockBtn.hidden = false;
+ log("\n— unlock before starting the topic listener —");
+ } else {
+ unlockBtn.hidden = true;
+ startTopicPump();
+ }
} catch (e) {
log("ERROR: " + e);
console.error(e);
@@ -345,7 +358,12 @@ Rynk web demo
unlockBtn.disabled = true;
try {
const start = await client.get_lock_status();
- if (!start.locked) { log("· already unlocked"); return; }
+ if (!start.locked) {
+ log("· already unlocked");
+ unlockBtn.hidden = true;
+ startTopicPump();
+ return;
+ }
if (start.key_positions.length === 0) {
log("⚠ permanently locked — set [host].unlock_keys in keyboard.toml");
return;
@@ -354,7 +372,12 @@ Rynk web demo
const deadline = Date.now() + 30000;
for (;;) {
const s = await client.unlock_poll();
- if (!s.locked) { log("✓ unlocked — retry the gated action"); break; }
+ if (!s.locked) {
+ log("✓ unlocked — retry the gated action");
+ unlockBtn.hidden = true;
+ startTopicPump();
+ break;
+ }
log(` holding… ${s.remaining_keys} key(s) left`);
if (Date.now() > deadline) { log("✗ unlock timed out"); break; }
await new Promise((r) => setTimeout(r, 150));