From 792a9b844ec0b67d8f72958b2a5b8a3df433a1c9 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 10 Jul 2026 16:33:01 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=9D=E6=8C=81=E7=BA=AF=20Kafka=20Tr?= =?UTF-8?q?ace=20=E7=BB=88=E6=80=81=E5=B1=95=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/deploy.yaml | 2 +- .../components/agent/TraceTimeline.test.ts | 75 +++++++++++++++++++ .../src/components/agent/TraceTimeline.vue | 24 +++++- 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/deploy/deploy.yaml b/deploy/deploy.yaml index 77725898..bb721b95 100644 --- a/deploy/deploy.yaml +++ b/deploy/deploy.yaml @@ -372,7 +372,7 @@ lanes: secretPlaneRef: config/hwlab-v03/secrets.yaml#secretPlane workbench: traceTimeline: - autoExpandRunning: false + autoExpandRunning: true autoCollapseTerminal: false rawHwlabEventWindow: enabled: true diff --git a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.test.ts b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.test.ts index 6bf5a5de..0bb7b62d 100644 --- a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.test.ts +++ b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.test.ts @@ -16,6 +16,71 @@ describe("TraceTimeline sequence authority", () => { Object.defineProperty(HTMLElement.prototype, "scrollTo", { configurable: true, value: () => undefined }); }); + it("auto-opens a running Trace without persisting programmatic state", async () => { + const storageKey = "trace-lifecycle.running"; + const wrapper = mount(TraceTimeline, { + props: { + autoExpanded: true, + storageKey, + trace: liveTrace("running") + } + }); + await nextTick(); + + expect(wrapper.get(".trace-disclosure").element.open).toBe(true); + expect(window.localStorage.getItem(storageKey)).toBeNull(); + wrapper.unmount(); + }); + + it("keeps an auto-opened Trace expanded when terminal policy has no collapse request", async () => { + const wrapper = mount(TraceTimeline, { + props: { + autoExpanded: true, + storageKey: "trace-lifecycle.keep-open", + trace: liveTrace("running") + } + }); + await nextTick(); + expect(wrapper.get(".trace-disclosure").element.open).toBe(true); + + await wrapper.setProps({ autoExpanded: null, trace: liveTrace("completed") }); + await nextTick(); + + expect(wrapper.get(".trace-disclosure").element.open).toBe(true); + wrapper.unmount(); + }); + + it("collapses on an explicit terminal request while keeping a manual stored value authoritative", async () => { + const storageKey = "trace-lifecycle.collapse"; + const wrapper = mount(TraceTimeline, { + props: { + autoExpanded: true, + storageKey, + trace: liveTrace("running") + } + }); + await nextTick(); + expect(wrapper.get(".trace-disclosure").element.open).toBe(true); + + await wrapper.setProps({ autoExpanded: false, trace: liveTrace("completed") }); + await nextTick(); + expect(wrapper.get(".trace-disclosure").element.open).toBe(false); + wrapper.unmount(); + + window.localStorage.setItem(storageKey, "1"); + const storedWrapper = mount(TraceTimeline, { + props: { + autoExpanded: false, + storageKey, + trace: liveTrace("completed") + } + }); + await nextTick(); + + expect(storedWrapper.get(".trace-disclosure").element.open).toBe(true); + storedWrapper.unmount(); + }); + it("renders sourceSeq-only live Kafka rows while terminal and does not label them projected", async () => { const wrapper = mount(TraceTimeline, { props: { @@ -70,6 +135,16 @@ describe("TraceTimeline sequence authority", () => { }); }); +function liveTrace(status: "running" | "completed") { + return { + traceId: "trc_live_lifecycle", + eventSource: "hwlab-kafka-sse" as const, + status, + eventCount: 1, + events: [{ sourceEventId: "evt_live_lifecycle", runId: "run_live", sourceSeq: 1, source: "agentrun.kafka", type: "assistant", label: "agentrun:assistant:message", message: "live answer" }] + }; +} + class FakeResizeObserver { observe(): void {} unobserve(): void {} diff --git a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue index 75d661a9..5e3387f0 100644 --- a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue +++ b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue @@ -46,10 +46,30 @@ watch(() => [eventCount.value, props.trace?.status, readableRows.value.length, e void keepBottomAfterUpdate(); }); -watch(() => [props.trace?.traceId, props.storageKey, props.defaultExpanded, props.autoExpanded] as const, () => { - setExpandedProgrammatically(readStoredExpanded() ?? props.autoExpanded ?? props.defaultExpanded ?? false); +watch(() => [traceExpansionScope(), props.defaultExpanded, props.autoExpanded] as const, (current, previous) => { + const stored = readStoredExpanded(); + if (stored !== null) { + setExpandedProgrammatically(stored); + return; + } + + const [scope, defaultExpanded, autoExpanded] = current; + if (!previous || scope !== previous[0]) { + setExpandedProgrammatically(autoExpanded ?? defaultExpanded ?? false); + return; + } + if (typeof autoExpanded === "boolean") { + setExpandedProgrammatically(autoExpanded); + return; + } + if (defaultExpanded !== previous[1]) setExpandedProgrammatically(defaultExpanded ?? false); }, { immediate: true }); +function traceExpansionScope(): string { + if (props.storageKey) return `storage:${props.storageKey}`; + return `trace:${String(props.trace?.traceId ?? "")}`; +} + function rowKey(row: TraceEventRow, index: number): string { return `${row.rowId}-${row.sequenceAuthority}-${row.seq ?? "no-seq"}-${index}`; }