Skip to content

feat: [ENG-3022] Render query results as a list in LocalWebUI#754

Open
ncnthien wants to merge 3 commits into
proj/byterover-tool-modefrom
feat/ENG-3022
Open

feat: [ENG-3022] Render query results as a list in LocalWebUI#754
ncnthien wants to merge 3 commits into
proj/byterover-tool-modefrom
feat/ENG-3022

Conversation

@ncnthien
Copy link
Copy Markdown
Collaborator

@ncnthien ncnthien commented Jun 2, 2026

Summary

Renders LocalWebUI query-tool-mode task results as a scannable list instead of a raw JSON blob, and polishes the surrounding task-detail view.

  • Result list (ENG-3022): each matched doc as a row — title, a match-score badge with a "Match score" tooltip, the path, and an expandable content preview. Legacy / non-JSON results fall through to the existing markdown/HTML rendering.
  • Readable input & title (ENG-3022): decodes the encoded {limit,query} content so the INPUT box, panel title, and list-row title show the query instead of raw JSON.
  • Clickable paths (ENG-3022): each result path opens the file in the Context tab if it still exists, otherwise toasts — reusing the existing findNodeByPath + stale-path convention.
  • EVENT LOG removal (ENG-3023): removes the EVENT LOG section from the task detail and deletes the now-orphaned event-log helpers.

Testing

npm run lint && npm run typecheck && npm test — green (8920 passing, 0 failing).

ncnthien added 3 commits June 2, 2026 09:12
Parse the query-tool-mode task result into matched docs and render them as a list in the task-detail Result section: each row shows the title, a match-score badge with a "Match score" tooltip, the path, and the doc content with a Show more/less expander. Legacy or non-JSON results fall through to the existing markdown/HTML rendering. Adds parseQueryToolModeResult / isQueryToolModeType with unit tests.
Drop the EventLogSection from the LocalWebUI task-detail view for all task types and delete the now-unused task-detail-event-log component.
…input

- Decode the encoded {limit,query} content so the input box, panel title, and list-row title show the query instead of raw JSON (shared taskDisplayTitle).
- Make each query result path clickable: opens the file in the Context tab if it still exists, otherwise toasts (useNavigateToContextPath reuses findNodeByPath).
- Slim the result parser to return the matched-docs array; dedupe safeJsonParse and the stale-path toast message; drop the unused limit field.
- Remove the dead event-log helpers (build-event-timeline, task-detail-tool-call, EventDot/RAIL_BG/EventTone) orphaned by the EVENT LOG removal.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Claude finished @ncnthien's task in 5m 16s —— View job


Code Review

Overall score: 4 / 5 — solid, well-scoped refactor. Net –370 lines from removing the EVENT LOG + tool-call view, plus a clean structured-result panel for query-tool-mode results. Parsing logic is properly type-narrowed (no as/any cheats), test coverage on the parser is thorough, and the consumer-first wiring (component → parser util) matches the project's outside-in approach in CLAUDE.md.

What works well

  • parseQueryToolModeResult / queryToolModeRowTitle (src/webui/features/tasks/utils/query-tool-mode-results.ts): defensive parsing with safeJsonParse + isMatchedDoc type-guard, no assertions. Test file covers happy path, ordering, empty matches, missing/wrong-shape fields, malformed JSON, and non-object payloads — exactly the surface area that matters for untrusted JSON.
  • Clean deletion of task-detail-event-log.tsx (-261) and task-detail-tool-call.tsx (-345): grep shows zero remaining references to EventLogSection, EventDot, EventTone, RAIL_BG, buildEventTimeline, etc. The task-detail-shared.tsx cleanup correctly drops only the now-orphan exports (SectionLabel, TerminalDot, Separator, elapsedMs are still used by the four remaining consumers).
  • stalePathMessage extraction (src/webui/features/context/utils/topic-viewer-navigation.ts): nice small DRY — both the legacy topic-viewer hook and the new task-result hook now share one message.
  • taskDisplayTitle unifies row-title decoding for curate-tool-mode and query-tool-mode, with sensible fall-through for plain task types.
  • syntax-highlighter.ts comment update correctly reflects the new call-site count (2 instead of 3).

Issues / suggestions

suggestionuse-navigate-to-context-path.ts is not branch-aware: it queries default-branch nodes and navigates to /contexts?path=… without preserving branch. On non-default branches this can both (a) falsely toast "stale path" and (b) drop the user back to the default branch when navigating. See inline.

suggestionQueryResultRow.useLayoutEffect only re-checks overflow when doc.rendered_md changes; resizing the Tasks panel won't update the Show more/less toggle. A ResizeObserver would harden this. See inline.

nitpickQueryResultsList builds the label string manually instead of using SectionLabel's existing count prop. See inline.

note — The local QueryToolModeMatchedDoc type duplicates the canonical server-side i-query-executor.ts shape (intentional, due to the webui → server import boundary). Worth tracking for future de-duplication via src/shared/. See inline.

Correctness & safety

  • No as/any/! assertions in the new code — parseQueryToolModeResult properly narrows via runtime type guards.
  • ES module + .js test imports follow project conventions.
  • snake_case rendered_md correctly preserved (matches the JSON wire envelope; mirrors the server's eslint-disable camelcase pattern).
  • No dangerouslySetInnerHTML; HTML rendering goes through MarkdownInline which uses react-markdown (XSS-safe by default).
  • Component handles 0-match, malformed-JSON, and missing-rendered_md cases gracefully.

Tests

PR description: npm test reported 8920 passing, 0 failing. Could not re-run locally (no node_modules in this checkout). Parser tests look thorough; the QueryResultsList component itself is presentational and exercised through visual checks — fine for unit-test scope.


  • Read CLAUDE.md and gather PR diff
  • Review new utility files (query-tool-mode-results, safe-json-parse, task-display-title)
  • Review new component (query-results-list.tsx)
  • Review modified components (task-detail-sections, task-detail-header, task-list-table)
  • Review navigation hook (use-navigate-to-context-path.ts)
  • Verify deletions are properly orphaned (event-log, tool-call)
  • Check tests for completeness
  • Post inline comments + summary with score
    · Branch: feat/ENG-3022


return (path: string) => {
if (findNodeByPath(nodes, path)) {
navigate(`/contexts?path=${encodeURIComponent(path)}`)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): This hook always queries the default branch (no branch arg to useGetContextNodes()) and navigates to /contexts?path=… without a branch query param. Two side-effects worth thinking about:

  1. The existence check (findNodeByPath(nodes, path)) runs against the default-branch tree, so a result whose path exists only on the user's currently-selected branch will toast as "stale" even though clicking through would have shown valid content (or vice versa).
  2. If the user had previously switched branches in the Context tab, navigating from a task result silently drops them back to the default branch, since /contexts?path=… is opened without branch.

For the local-only query-tool-mode use case today this is fine, but mirroring the branch-resolution that useContextTree/useTopicViewerNavigation use would make the behavior consistent. Could read searchParams.get('branch') (or read it from a shared store) and forward it to both the fetch and the navigation URL.

const el = bodyRef.current
if (!el) return
setOverflowing(el.scrollHeight > el.clientHeight + 1)
}, [doc.rendered_md])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): The "Show more/Show less" toggle is only computed when doc.rendered_md changes — it won't re-evaluate if the panel/viewport resizes. A short markdown body that doesn't clip at 800px wide can clip at 500px (or vice versa) and the toggle won't appear/disappear correctly until the next mount.

A ResizeObserver on bodyRef.current (or on the parent panel) would keep overflowing accurate across resizes. Edge case for now, but the Tasks tab is in a resizable layout so it's reachable.

import {SectionLabel, TerminalDot} from './task-detail-shared'

export function QueryResultsList({matchedDocs}: {matchedDocs: QueryToolModeMatchedDoc[]}) {
const label = `Result · ${matchedDocs.length} ${matchedDocs.length === 1 ? 'match' : 'matches'}`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (non-blocking): SectionLabel already accepts a count prop that renders the number on the right side of the divider line (used elsewhere in the file). Could lean on that instead of baking "matches" / "match" into the label string:

<SectionLabel count={matchedDocs.length}>Result</SectionLabel>

If the inline "match/matches" pluralization was intentional (for readability), feel free to ignore.

rendered_md?: string
score: number
title: string
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note (non-blocking): The canonical envelope shape lives in src/server/core/interfaces/executor/i-query-executor.ts (QueryToolModeMatchedDoc) where format is 'html' | 'markdown' and rendered_md is required. The webui's local shape is intentionally weaker because it can't import from server/ (boundary rule), but the difference is worth being aware of: if the wire shape ever changes (e.g., a new format), only the parser test here will catch it — the server side won't fail typecheck against the consumer.

If a shared src/shared/types/ module ever picks up tool-mode envelopes, this is a candidate for de-duplication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant