From c7c838b4e5cf129135d1a6120f770ffb8c5e5ff6 Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 22 Jul 2026 16:08:45 +0900 Subject: [PATCH] fix: filter web timeline assistant marks --- .yoi/tickets/00001KY1ZKCPC/item.md | 2 +- .yoi/tickets/00001KY1ZKCPC/thread.md | 38 +++++++++++++++++++ .../src/lib/workspace/console/model.test.ts | 29 ++++++++++++++ .../src/lib/workspace/console/model.ts | 33 ++++++++++++++++ .../workers/[workerId]/console/+page.svelte | 7 +++- 5 files changed, 106 insertions(+), 3 deletions(-) diff --git a/.yoi/tickets/00001KY1ZKCPC/item.md b/.yoi/tickets/00001KY1ZKCPC/item.md index 033e4792..8d0bf59a 100644 --- a/.yoi/tickets/00001KY1ZKCPC/item.md +++ b/.yoi/tickets/00001KY1ZKCPC/item.md @@ -2,7 +2,7 @@ title: 'Backend runtime経由の操作をprotocol transportへ統一しTUI同等にする' state: 'planning' created_at: '2026-07-21T09:20:07Z' -updated_at: '2026-07-22T06:36:51Z' +updated_at: '2026-07-22T07:08:39Z' assignee: null readiness: 'draft' --- diff --git a/.yoi/tickets/00001KY1ZKCPC/thread.md b/.yoi/tickets/00001KY1ZKCPC/thread.md index 2f5881ff..91223852 100644 --- a/.yoi/tickets/00001KY1ZKCPC/thread.md +++ b/.yoi/tickets/00001KY1ZKCPC/thread.md @@ -376,3 +376,41 @@ Verification: - `git diff --check` --- + + + +## Comment + +Requested Web Console timeline-bar display change before implementation: + +- Keep user-message markers/items as they are today. +- For assistant display in the timeline bar, show only the last assistant item within each turn. + +Implementation plan: +- Locate the Web timeline-bar projection/rendering path. +- Change only the timeline-bar item derivation/filtering, not the underlying transcript/event model. +- Add/update focused Web tests if there is a projection helper; otherwise verify through existing Web check and keep the diff scoped. + +--- + + + +## Implementation report + +Implementation report for Web Console timeline-bar assistant filtering: + +- Added `selectConsoleTimelineLines()` in `web/workspace/src/lib/workspace/console/model.ts`. +- The selector keeps every `user` line in the timeline selection. +- Assistant lines are grouped by user-delimited turns, and only the last assistant line in each turn is selected. +- Non-user/non-assistant lines remain excluded from the timeline bar selection. +- Updated `+page.svelte` timeline layout construction to use the selector before converting selected lines to timeline marks. Positioning still uses the full transcript line count, so marks remain aligned to scroll position. +- Added a focused model test for `user, assistant, tool, assistant, user, assistant, assistant` to assert the selected timeline items are `u1`, `a2`, `u2`, and `a4`. + +Verification: +- `cd web/workspace && deno test --allow-read=src --allow-env=VSCODE_TEXTMATE_DEBUG src/lib/workspace/console/model.test.ts` +- `cd web/workspace && deno task check` passed with 0 errors; it still reports the existing 2 a11y warnings in `+page.svelte`. +- `git diff --check` + +Note: `deno fmt --check` on the touched files still reports existing formatting changes outside this patch scope, so it was not used as the gating check for this small change. + +--- diff --git a/web/workspace/src/lib/workspace/console/model.test.ts b/web/workspace/src/lib/workspace/console/model.test.ts index c0192e81..1e584d14 100644 --- a/web/workspace/src/lib/workspace/console/model.test.ts +++ b/web/workspace/src/lib/workspace/console/model.test.ts @@ -3,7 +3,9 @@ import { createConsoleProjector, projectConsole, segmentsToText, + selectConsoleTimelineLines, workerConsoleHref, + type ConsoleLine, } from "./model.ts"; declare const Deno: { @@ -24,6 +26,16 @@ function assertEquals(actual: T, expected: T): void { } } +function consoleLine(id: string, kind: ConsoleLine["kind"]): ConsoleLine { + return { + id, + kind, + title: kind, + body: id, + source: "event", + }; +} + Deno.test("workerConsoleHref encodes runtime and worker target authority", () => { assert( workerConsoleHref({ @@ -857,3 +869,20 @@ Deno.test("projectConsole reseeds visible rows from segment rotation", () => { ["user:after rotation seed"], ); }); + +Deno.test("selectConsoleTimelineLines keeps all users and only last assistant per turn", () => { + const items = [ + consoleLine("u1", "user"), + consoleLine("a1", "assistant"), + consoleLine("tool1", "tool"), + consoleLine("a2", "assistant"), + consoleLine("u2", "user"), + consoleLine("a3", "assistant"), + consoleLine("a4", "assistant"), + ]; + + assertEquals( + selectConsoleTimelineLines(items).map(({ item, index }) => `${index}:${item.id}`), + ["0:u1", "3:a2", "4:u2", "6:a4"], + ); +}); diff --git a/web/workspace/src/lib/workspace/console/model.ts b/web/workspace/src/lib/workspace/console/model.ts index 9cd5b629..b8623901 100644 --- a/web/workspace/src/lib/workspace/console/model.ts +++ b/web/workspace/src/lib/workspace/console/model.ts @@ -64,6 +64,39 @@ export type ConsoleProjection = { lastEventId: string | null; }; +export type ConsoleTimelineLineSelection = { + item: ConsoleLine; + index: number; +}; + +export function selectConsoleTimelineLines( + items: ConsoleLine[], +): ConsoleTimelineLineSelection[] { + const selected: ConsoleTimelineLineSelection[] = []; + let lastAssistant: ConsoleTimelineLineSelection | null = null; + + const flushAssistant = () => { + if (lastAssistant) { + selected.push(lastAssistant); + lastAssistant = null; + } + }; + + items.forEach((item, index) => { + if (item.kind === "user") { + flushAssistant(); + selected.push({ item, index }); + return; + } + if (item.kind === "assistant") { + lastAssistant = { item, index }; + } + }); + + flushAssistant(); + return selected; +} + export type WorkerTarget = { runtime_id: string; worker_id: string; diff --git a/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte index d69254ae..73544f9a 100644 --- a/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte @@ -17,6 +17,7 @@ import { fitTextarea } from "$lib/workspace/console/textarea-fit"; import { createConsoleProjector, + selectConsoleTimelineLines, type ConsoleEventInput, type ConsoleLine, type ConsoleProjection, @@ -643,8 +644,10 @@ metrics: ScrollMetrics, ): TimelineLayout { const denominator = Math.max(items.length - 1, 1); - const rawMarks = items - .map((item, index) => timelineMarkForLine(item, index, denominator)) + const rawMarks = selectConsoleTimelineLines(items) + .map(({ item, index }) => + timelineMarkForLine(item, index, denominator) + ) .filter((mark): mark is TimelineMark => mark !== null); const trackHeight = timelineTrackHeight(metrics); const positioned = positionTimelineMarks(rawMarks, trackHeight);