feat: show P2P diagnostics in the status pane and log

Show expandable relay, peer, and sync observations below Stats and route Diag
events to the existing Log Pane without notification or editor-status churn.
Retain departed-peer evidence and replay logs recorded before the pane opens.

Keep narrow panes usable with wrapping peer controls, readable device names,
and larger buttons. Add browser regressions for disclosure, control bounds,
button targets, and existing actions.
This commit is contained in:
vorotamoroz committed 2026-11-06 12:17:31 +00:00
1 parent fc5f02ca3b
commit cf527fbf86
22 files changed
+978 -88

No files matched your search

+55 -1
View File
@@ -22,7 +22,10 @@ vi.mock("@vrtmrz/livesync-commonlib/compat/replication/trystero/P2PLogCollector"
P2PLogCollector: class {},
}));
vi.mock("@vrtmrz/livesync-commonlib/p2p", () => ({ EVENT_P2P_DIAGNOSTIC: "p2p-diagnostic" }));
async function createHarness() {
const { createLiveSyncEventHub } = await import("@vrtmrz/livesync-commonlib/context");
const { ModuleLog } = await import("./ModuleLog.ts");
const { bindableFunction, allFunction } =
await import("@vrtmrz/livesync-commonlib/compat/services/lib/HandlerUtils");
@@ -34,7 +37,7 @@ async function createHarness() {
addRibbonIcon: vi.fn(),
registerProtocolHandler: vi.fn(),
},
context: { events: {}, notices: { hide: vi.fn() } },
context: { events: createLiveSyncEventHub(), notices: { hide: vi.fn() } },
appLifecycle: {
onInitialise: allFunction<() => Promise<boolean>>(),
onSettingLoaded: allFunction<() => Promise<boolean>>(),
@@ -61,6 +64,57 @@ describe("ModuleLog service connection", () => {
vi.unstubAllGlobals();
});
it("routes accepted P2P diagnostics only through their owning context and disposes the subscription", async () => {
const first = await createHarness();
const second = await createHarness();
await first.services.appLifecycle.onSettingLoaded();
await second.services.appLifecycle.onSettingLoaded();
first.sink.mockClear();
second.sink.mockClear();
first.services.context.events.emitEvent("p2p-diagnostic", {
category: "peer",
code: "auth-timeout",
timestamp: 123,
});
expect(first.sink).toHaveBeenCalledWith(
expect.stringContaining("[P2P Diag] Peer: Authentication timed out"),
expect.any(Number),
"p2p-diagnostic"
);
expect(second.sink).not.toHaveBeenCalled();
await first.module.logResource.dispose();
first.sink.mockClear();
const consoleLog = vi.spyOn(console, "log").mockImplementation(() => undefined);
first.services.context.events.emitEvent("p2p-diagnostic", {
category: "peer",
code: "peer-left",
timestamp: 124,
});
expect(first.sink).not.toHaveBeenCalled();
expect(consoleLog).not.toHaveBeenCalled();
consoleLog.mockRestore();
});
it("does not repeat unchanged diagnostic state but retains repeated failure observations", async () => {
const { services, sink } = await createHarness();
await services.appLifecycle.onSettingLoaded();
sink.mockClear();
services.context.events.emitEvent("p2p-diagnostic", {
category: "sync",
code: "watch-enabled",
timestamp: 100,
});
services.context.events.emitEvent("p2p-diagnostic", {
category: "sync",
code: "watch-enabled",
timestamp: 200,
});
expect(sink).toHaveBeenCalledTimes(1);
services.context.events.emitEvent("p2p-diagnostic", { category: "peer", code: "auth-timeout", timestamp: 300 });
services.context.events.emitEvent("p2p-diagnostic", { category: "peer", code: "auth-timeout", timestamp: 400 });
expect(sink).toHaveBeenCalledTimes(3);
});
it("buffers startup logs and preserves global and service call order", async () => {
const { services, sink, Logger } = await createHarness();
Logger("before-global");