feat: close STREAMING.md production readiness gaps
CI / test (push) Failing after 9s
CI / docker (push) Skipped
Deploy to Cloudflare Pages / deploy (push) Failing after 10s
CD / deploy (push) Successful in 47s

- Added run ID + unique segment naming (r-{ISOTIME}/seg-{seq}.m4s)
- Added fsnotify watcher for real-time R2 segment upload (~7s)
- Added atomic manifest publication (temp key → CopyObject)
- Fixed manifest Cache-Control: public, max-age=1, s-maxage=2, must-revalidate
- Added hls.js error recovery: bounded retries (5x), exponential backoff, reconnecting UI
- Enriched /health endpoint: run_id, active_item, last_segment_seq, last_r2_pub, consecutive_failures, unhealthy detection
- Updated tests: HealthHandler (new fields + unhealthy), player error recovery test
- Updated TODO.md, BACKLOG.md, DONE.md, MEMORY.md
This commit is contained in:
zwitschi committed 2026-08-10 17:20:54 +02:00
1 parent f6ca89747e
commit 18459a9811
12 files changed
+699 -48

No files matched your search

+41 -2
View File
@@ -13,7 +13,17 @@ const HlsMock = Object.assign(
vi.fn().mockImplementation(() => hlsInstance),
{
isSupported: vi.fn(() => true),
Events: { MANIFEST_PARSED: "hlsManifestParsed" },
Events: {
MANIFEST_PARSED: "hlsManifestParsed",
ERROR: "hlsError",
LEVEL_UPDATED: "hlsLevelUpdated",
},
ErrorTypes: {
NETWORK_ERROR: "networkError",
MEDIA_ERROR: "mediaError",
MUX_ERROR: "muxError",
OTHER_ERROR: "otherError",
},
},
);
@@ -331,8 +341,37 @@ describe("message rendering", () => {
});
const messages = document.getElementById("messages")!;
expect(messages.innerHTML).toContain("alice");
expect(messages.innerHTML).toContain("hey");
expect(messages.innerHTML).toContain("alice");
expect(messages.querySelector(".message-other")).toBeTruthy();
});
});
describe("player error recovery", () => {
it("shows reconnecting overlay on fatal network error", async () => {
const main = await import("../src/main");
await main.initPlayerAndChat(async () => ({ default: HlsMock }) as any);
// Find the error handler registered by initPlayerAndChat
const errorCall = HlsMock.mock.results?.[0]?.value?.on?.mock?.calls?.find(
(c: [string, Function]) => c[0] === "hlsError"
);
// Use hlsInstance.on mock calls
const errorHandler = hlsInstance.on.mock.calls.find(
(c: [string, Function]) => c[0] === "hlsError"
);
if (errorHandler) {
const handler = errorHandler[1];
handler("hlsError", {
type: "networkError",
details: "manifestLoadError",
fatal: true,
});
const overlay = document.getElementById("reconnecting-overlay")!;
expect(overlay.classList.contains("hidden")).toBe(false);
expect(document.getElementById("reconnecting-text")?.textContent).toBe("Reconnecting...");
}
});
});