From edf04aa4919af9cb8d44433db941fc39abc406a1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 11 Aug 2026 17:37:22 -0700 Subject: [PATCH] fix(ui): avoid duplicate initial log loads --- ui/src/pages/logs/logs-page.test.ts | 19 +++++++++++++++++++ ui/src/pages/logs/logs-page.ts | 6 ++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ui/src/pages/logs/logs-page.test.ts b/ui/src/pages/logs/logs-page.test.ts index 405646fe03d1..78fbdf8ff08d 100644 --- a/ui/src/pages/logs/logs-page.test.ts +++ b/ui/src/pages/logs/logs-page.test.ts @@ -120,6 +120,25 @@ describe("LogsPage lifecycle", () => { expect(scheduleScroll).toHaveBeenCalledWith(true); }); + it("does not restart a pending initial load for metadata-only gateway snapshots", async () => { + const pending = deferred<{ cursor: number; lines: string[]; reset: boolean }>(); + const request = vi.fn(() => pending.promise); + const client = { request } as unknown as GatewayBrowserClient; + const page = document.createElement("openclaw-logs-page") as TestLogsPage; + const context = contextWithClient(client, true); + page.context = context; + document.body.append(page); + await page.updateComplete; + await vi.waitFor(() => expect(request).toHaveBeenCalledOnce()); + + context.gateway.publish({ client, phase: "connected" } as ApplicationGatewaySnapshot); + await Promise.resolve(); + + expect(request).toHaveBeenCalledOnce(); + pending.resolve({ cursor: 1, lines: ["initial"], reset: true }); + await vi.waitFor(() => expect(page.logsEntries).toHaveLength(1)); + }); + it("discards a log response from a replaced gateway source that reuses its client", async () => { const pending = deferred<{ cursor: number; lines: string[]; reset: boolean }>(); const client = { diff --git a/ui/src/pages/logs/logs-page.ts b/ui/src/pages/logs/logs-page.ts index 637dbe11403b..68af61f771db 100644 --- a/ui/src/pages/logs/logs-page.ts +++ b/ui/src/pages/logs/logs-page.ts @@ -136,10 +136,8 @@ class LogsPage extends OpenClawLightDomElement { this.logsTaskQuiet = false; void this.logsTask.run([null, null, null, false, false]); }, - onSnapshot: () => { - this.syncPolling(); - this.ensureInitialLogs(); - }, + onSnapshot: () => this.syncPolling(), + ensureInitialData: () => this.ensureInitialLogs(), }); private readonly streamFollow = new StreamAutoFollowController(this, { selector: ".log-stream",