fix(display): retry a display that could not start, and re-resolve a running one periodically
CI / verify (pull_request) Successful in 1m7s
CI / infrastructure-smoke (pull_request) Successful in 38s
CI / package-release (pull_request) Successful in 50s
CI / migration-smoke (pull_request) Successful in 2m9s
CI / image-build (pull_request) Successful in 57s
CI / deploy-smoke (same-origin) (pull_request) Successful in 53s
CI / deploy-smoke (split) (pull_request) Successful in 2m35s
CI / package-publication (pull_request) Successful in 1m1s

A kiosk that booted before its server, as after a power cut, stopped on 'This
display could not be reached' until someone pressed Retry: with the network
up, no online event ever came. It now retries on its own, from two seconds
doubling to a minute. A running display also re-resolves its descriptor every
five minutes in the background, so a module upgraded on the server is picked
up without a reload, and the same answer disturbs nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-12 21:45:37 -05:00
co-authored by Claude Opus 5.5
parent 8f90871be0
commit 9331a8f7df
2 changed files with 110 additions and 1 deletions
+65
View File
@@ -1161,4 +1161,69 @@ describe("display host — continuity across reconnects and revocation", () => {
});
expect(query("display-offline")?.textContent).toBe("Offline — reconnecting");
});
it("starts on its own when the server comes up after the screen did", async () => {
// A kiosk that boots before its server: the network is up, so no `online` event will ever come.
let up = false;
const fetch = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.endsWith("/api/v1/module-displays/announcements")) {
return up ? json(descriptorPayload()) : json({ code: "UNAVAILABLE" }, 503);
}
return json({ code: "NOT_FOUND" }, 404);
}) as unknown as typeof globalThis.fetch;
const display = countingArtifact();
await act(async () => {
root.render(
<DisplayApp
config={{ apiBaseUrl, fetch }}
importModule={display.importModule}
moduleId="announcements"
offlineIndicatorDelayMs={0}
retryBaseMs={20}
/>,
);
});
await flush();
expect(query("display-host-error")).not.toBeNull();
up = true;
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 200));
});
await flush();
expect(display.counts.mounts).toBe(1);
expect(query("display-host-error")).toBeNull();
});
it("re-resolves a running display periodically, remounting only for a new version", async () => {
const server = changingServer();
const display = countingArtifact();
await act(async () => {
root.render(
<DisplayApp
config={{ apiBaseUrl, fetch: server.fetch }}
importModule={display.importModule}
moduleId="announcements"
offlineIndicatorDelayMs={0}
periodicRevalidationMs={40}
/>,
);
});
await flush();
const before = server.descriptorCalls();
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 150));
});
await flush();
expect(server.descriptorCalls()).toBeGreaterThan(before);
expect(display.counts).toEqual({ mounts: 1, unmounts: 0 });
server.set(200, descriptorPayload("announcements", "2.0.0"));
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 150));
});
await flush();
expect(display.counts.mounts).toBe(2);
});
});