@@ -86,6 +90,11 @@ export function ResultSection({content, taskType}: {content: string; taskType?:
if (payload) return
}
+ if (taskType && isQueryToolModeType(taskType)) {
+ const docs = parseQueryToolModeResult(content)
+ if (docs) return
+ }
+
return (
diff --git a/src/webui/features/tasks/components/task-detail-shared.tsx b/src/webui/features/tasks/components/task-detail-shared.tsx
index 2128f355f..4ed7d9dc4 100644
--- a/src/webui/features/tasks/components/task-detail-shared.tsx
+++ b/src/webui/features/tasks/components/task-detail-shared.tsx
@@ -1,6 +1,5 @@
import type {ReactNode} from 'react'
-import {Tooltip, TooltipContent, TooltipTrigger} from '@campfirein/byterover-packages/components/tooltip'
import {cn} from '@campfirein/byterover-packages/lib/utils'
import {Check, X} from 'lucide-react'
@@ -24,48 +23,6 @@ export function SectionLabel({children, count}: {children: ReactNode; count?: nu
)
}
-export type EventTone = 'completed' | 'error' | 'muted' | 'running'
-
-const DOT_BG: Record = {
- completed: 'bg-emerald-500',
- error: 'bg-red-400',
- muted: 'bg-muted-foreground/60',
- running: 'bg-blue-400',
-}
-
-export const RAIL_BG: Record = {
- completed: 'bg-emerald-500/70',
- error: 'bg-red-400/70',
- muted: 'bg-muted-foreground/30',
- running: 'rail-running',
-}
-
-export function EventDot({flash, tone, tooltip}: {flash?: boolean; tone: EventTone; tooltip?: ReactNode}) {
- const dot = (
-
-
- {tone === 'running' && (
-
- )}
-
- )
-
- if (!tooltip) return dot
-
- return (
-
-
- {tooltip}
-
- )
-}
-
export function TerminalDot({tone}: {tone: 'completed' | 'error'}) {
const Icon = tone === 'completed' ? Check : X
const bg = tone === 'completed' ? 'bg-emerald-500' : 'bg-red-400'
diff --git a/src/webui/features/tasks/components/task-detail-tool-call.tsx b/src/webui/features/tasks/components/task-detail-tool-call.tsx
deleted file mode 100644
index 8e46a0af2..000000000
--- a/src/webui/features/tasks/components/task-detail-tool-call.tsx
+++ /dev/null
@@ -1,345 +0,0 @@
-import {cn} from '@campfirein/byterover-packages/lib/utils'
-import {ChevronDown, ChevronUp} from 'lucide-react'
-import {Fragment, memo, ReactNode, useMemo, useState} from 'react'
-
-import type {ToolCallEvent} from '../types/stored-task'
-
-import {oneDark, SyntaxHighlighter} from '../../../lib/syntax-highlighter'
-import {formatToolArgs} from '../utils/format-tool-args'
-import {stripTaskIdSuffix} from '../utils/strip-task-id'
-import {MarkdownInline} from './markdown-inline'
-import {EventDot} from './task-detail-shared'
-
-type RowFormat = 'code' | 'keypath' | 'markdown' | 'plain'
-
-const TOOL_LABEL_TONE: Record = {
- completed: 'text-emerald-500/80',
- error: 'text-red-400/80',
- running: 'text-blue-400/80',
-}
-
-interface ToolLangs {
- in: string
- out: string
-}
-
-/* eslint-disable camelcase */
-// Tool names match daemon registrations — see src/agent/core/domain/tools/constants.ts
-// and the .txt files under src/agent/resources/tools/.
-const TOOL_LANGUAGES: Record = {
- bash_exec: {in: 'bash', out: 'bash'},
- bash_output: {in: 'bash', out: 'bash'},
- code_exec: {in: 'javascript', out: 'json'},
-}
-
-const TOOL_DISPLAY_NAME: Record = {
- agentic_map: 'map',
- bash_exec: 'bash',
- bash_output: 'bash output',
- code_exec: 'code exec',
- create_knowledge_topic: 'new topic',
- delete_memory: 'delete memory',
- detect_domains: 'detect domains',
- edit_file: 'edit',
- edit_memory: 'edit memory',
- expand_knowledge: 'expand',
- glob_files: 'glob',
- grep_content: 'grep',
- ingest_resource: 'ingest',
- kill_process: 'kill',
- list_directory: 'list',
- list_memories: 'memories',
- llm_map: 'llm map',
- read_file: 'read',
- read_memory: 'read memory',
- read_todos: 'todos',
- search_history: 'history',
- search_knowledge: 'search',
- swarm_query: 'swarm query',
- swarm_store: 'swarm store',
- write_file: 'write',
- write_memory: 'write memory',
- write_todos: 'todos',
-}
-/* eslint-enable camelcase */
-
-function getToolDisplayName(toolName: string): string {
- return TOOL_DISPLAY_NAME[toolName] ?? toolName.replaceAll('_', ' ')
-}
-
-const EXTENSION_LANGUAGE: Record = {
- bash: 'bash',
- c: 'c',
- cjs: 'javascript',
- cpp: 'cpp',
- cs: 'csharp',
- css: 'css',
- go: 'go',
- h: 'c',
- hpp: 'cpp',
- htm: 'html',
- html: 'html',
- java: 'java',
- js: 'javascript',
- json: 'json',
- jsx: 'jsx',
- kt: 'kotlin',
- less: 'less',
- md: 'markdown',
- mdx: 'markdown',
- mjs: 'javascript',
- php: 'php',
- py: 'python',
- rb: 'ruby',
- rs: 'rust',
- scss: 'scss',
- sh: 'bash',
- sql: 'sql',
- swift: 'swift',
- toml: 'toml',
- ts: 'typescript',
- tsx: 'tsx',
- xml: 'xml',
- yaml: 'yaml',
- yml: 'yaml',
- zsh: 'bash',
-}
-
-function inferFileLanguage(args: Record): string | undefined {
- const path =
- (typeof args.path === 'string' && args.path) ||
- (typeof args.file_path === 'string' && args.file_path) ||
- (typeof args.filePath === 'string' && args.filePath)
- if (!path) return undefined
- const dot = path.lastIndexOf('.')
- if (dot === -1 || dot === path.length - 1) return undefined
- const ext = path.slice(dot + 1).toLowerCase()
- return EXTENSION_LANGUAGE[ext]
-}
-
-const MEMORY_TOOLS = new Set(['delete_memory', 'edit_memory', 'list_memories', 'read_memory', 'write_memory'])
-const PLAIN_OUT_TOOLS = new Set(['bash_output'])
-const JSON_OUT_TOOLS = new Set([
- 'create_knowledge_topic',
- 'curate',
- 'detect_domains',
- 'expand_knowledge',
- 'ingest_resource',
- 'list_directory',
- 'list_memories',
- 'read_todos',
- 'search_history',
- 'search_knowledge',
- 'swarm_query',
- 'swarm_store',
- 'write_todos',
-])
-
-function getInFormat(toolName: string): {format: RowFormat; language?: string} {
- if (toolName in TOOL_LANGUAGES) return {format: 'code', language: TOOL_LANGUAGES[toolName].in}
- if (MEMORY_TOOLS.has(toolName)) return {format: 'keypath'}
- return {format: 'markdown'}
-}
-
-function getOutFormat(toolName: string, fileLang: string | undefined): {format: RowFormat; language?: string} {
- if (PLAIN_OUT_TOOLS.has(toolName)) return {format: 'plain'}
- if (fileLang) return {format: 'code', language: fileLang}
- if (toolName in TOOL_LANGUAGES) return {format: 'code', language: TOOL_LANGUAGES[toolName].out}
- if (JSON_OUT_TOOLS.has(toolName)) return {format: 'code', language: 'json'}
- return {format: 'markdown'}
-}
-
-const CodeBlock = memo(({content, language = 'bash'}: {content: string; language?: string}) => (
-
- {content}
-
-))
-CodeBlock.displayName = 'CodeBlock'
-
-export function ToolCallContent({
- call,
- flash,
- taskId,
- tooltip,
-}: {
- call: ToolCallEvent
- flash: boolean
- taskId: string
- tooltip: ReactNode
-}) {
- const [expanded, setExpanded] = useState(false)
- const argsText = useMemo(() => stripTaskIdSuffix(formatToolArgs(call), taskId), [call, taskId])
- const resultText = useMemo(() => stripTaskIdSuffix(formatResult(call), taskId), [call, taskId])
- const fileLang = useMemo(() => inferFileLanguage(call.args), [call.args])
- const inFormat = getInFormat(call.toolName)
- const outFormat = getOutFormat(call.toolName, fileLang)
- const hasResult = resultText.length > 0
- const isRunning = call.status === 'running'
-
- const toggle = () => setExpanded((prev) => !prev)
-
- return (
- <>
-
-
-
-
- {getToolDisplayName(call.toolName)}
-
- {isRunning && running}
-
-
-
- >
- )
-}
-
-const IN_COLLAPSED_LINES = 1
-const OUT_COLLAPSED_LINES = 3
-
-function IORow({
- collapsedHeight,
- collapsedLines,
- content,
- empty,
- expanded,
- format,
- label,
- language,
- placeholder,
-}: {
- collapsedHeight: string
- collapsedLines: number
- content: string
- empty: boolean
- expanded: boolean
- format: RowFormat
- label: 'in' | 'out'
- language?: string
- placeholder?: string
-}) {
- const overflowLines = useMemo(() => {
- if (expanded) return 0
- return Math.max(0, content.split('\n').length - collapsedLines)
- }, [content, collapsedLines, expanded])
-
- const renderContent = () => {
- switch (format) {
- case 'code': {
- return
- }
-
- case 'keypath': {
- return
- }
-
- case 'plain': {
- return
- }
-
- default: {
- return {content}
- }
- }
- }
-
- return (
-
-
- {label}
- {overflowLines > 0 && (
- +{overflowLines}
- )}
-
-
- {empty ? (
-
{placeholder ?? '—'}
- ) : expanded ? (
- renderContent()
- ) : (
-
{renderContent()}
- )}
-
-
- )
-}
-
-function PlainBlock({content}: {content: string}) {
- return {content}
-}
-
-function KeyPath({path}: {path: string}) {
- const parts = path.split('/')
- return (
-
- {parts.map((part, i) => (
-
- {i > 0 && /}
- {part}
-
- ))}
-
- )
-}
-
-function formatResult(call: ToolCallEvent): string {
- if (call.status === 'error' && call.error) return call.error
- if (call.result === undefined || call.result === null) return ''
- if (typeof call.result === 'string') {
- const trimmed = call.result.trim()
- return trimmed.length > 1200 ? `${trimmed.slice(0, 1200)}\n…` : trimmed
- }
-
- try {
- const json = JSON.stringify(call.result, null, 2)
- return json.length > 1200 ? `${json.slice(0, 1200)}\n…` : json
- } catch {
- return String(call.result)
- }
-}
diff --git a/src/webui/features/tasks/components/task-detail-view.tsx b/src/webui/features/tasks/components/task-detail-view.tsx
index 0362262dc..0529226b9 100644
--- a/src/webui/features/tasks/components/task-detail-view.tsx
+++ b/src/webui/features/tasks/components/task-detail-view.tsx
@@ -8,7 +8,6 @@ import {useTickingNow} from '../hooks/use-ticking-now'
import {useTaskById} from '../stores/task-store'
import {taskHistoryEntryToStoredTask} from '../utils/task-history-entry-to-stored-task'
import {isActiveStatus} from '../utils/task-status'
-import {EventLogSection} from './task-detail-event-log'
import {DetailHeader} from './task-detail-header'
import {ErrorSection, InputSection, LiveStreamSection, NotFound, ResultSection} from './task-detail-sections'
@@ -81,7 +80,6 @@ export function TaskDetailView({cancelling, onCancel, taskId}: TaskDetailViewPro
ref={scrollRef}
>
-
{showLive && }
{result && }
{error && }
diff --git a/src/webui/features/tasks/components/task-list-table.tsx b/src/webui/features/tasks/components/task-list-table.tsx
index 683dff5a7..c92bce617 100644
--- a/src/webui/features/tasks/components/task-list-table.tsx
+++ b/src/webui/features/tasks/components/task-list-table.tsx
@@ -15,11 +15,11 @@ import {CircleStop, LoaderCircle, Trash2} from 'lucide-react'
import type {StatusFilter} from '../stores/task-store'
import type {StoredTask} from '../types/stored-task'
-import {curateHtmlDirectRowTitle, isCurateHtmlDirectType} from '../utils/curate-tool-mode'
import {getCurrentActivity} from '../utils/current-activity'
import {formatDuration, formatRelative, formatTimeOfDay, shortTaskId} from '../utils/format-time'
import {isInterrupted} from '../utils/is-interrupted'
import {rowActionKind} from '../utils/row-action-kind'
+import {taskDisplayTitle} from '../utils/task-display-title'
import {displayTaskType, isTerminalStatus} from '../utils/task-status'
import {StatusPill} from './status-pill'
import {NoMatchState} from './task-list-empty'
@@ -141,9 +141,7 @@ function TaskRow({
const isRunning = !terminal
const interrupted = isInterrupted(task)
const activity = getCurrentActivity(task)
- // For curate-tool-mode, task.content is a JSON blob — decode it so the
- // row shows the user's intent (CLI) or topic path (MCP) instead.
- const displayInput = isCurateHtmlDirectType(task.type) ? curateHtmlDirectRowTitle(task.content) : task.content
+ const displayInput = taskDisplayTitle(task)
const actionKind = rowActionKind(task.status)
const row = (
diff --git a/src/webui/features/tasks/utils/build-event-timeline.ts b/src/webui/features/tasks/utils/build-event-timeline.ts
deleted file mode 100644
index 339946e9a..000000000
--- a/src/webui/features/tasks/utils/build-event-timeline.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type {ReasoningContentItem, StoredTask, ToolCallEvent} from '../types/stored-task'
-
-export type TimelineEvent =
- | {call: ToolCallEvent; kind: 'toolCall'; timestamp: number}
- | {item: ReasoningContentItem; kind: 'reasoning'; timestamp: number}
-
-export function buildEventTimeline(task: StoredTask): TimelineEvent[] {
- const events: TimelineEvent[] = []
- for (const item of task.reasoningContents ?? []) {
- events.push({item, kind: 'reasoning', timestamp: item.timestamp})
- }
-
- for (const call of task.toolCalls ?? []) {
- events.push({call, kind: 'toolCall', timestamp: call.timestamp})
- }
-
- events.sort((a, b) => a.timestamp - b.timestamp)
- return events
-}
diff --git a/src/webui/features/tasks/utils/curate-tool-mode.ts b/src/webui/features/tasks/utils/curate-tool-mode.ts
index f0c4c698f..bd5c002c9 100644
--- a/src/webui/features/tasks/utils/curate-tool-mode.ts
+++ b/src/webui/features/tasks/utils/curate-tool-mode.ts
@@ -7,6 +7,8 @@
* structured view instead of dumping the raw JSON.
*/
+import {safeJsonParse} from './safe-json-parse'
+
export interface CurateHtmlDirectInputPayload {
confirmOverwrite?: boolean
html: string
@@ -122,11 +124,3 @@ function isWriteError(value: unknown): value is CurateHtmlWriteError {
const obj = value as Record
return typeof obj.kind === 'string' && typeof obj.message === 'string'
}
-
-function safeJsonParse(content: string): unknown {
- try {
- return JSON.parse(content)
- } catch {
- return undefined
- }
-}
diff --git a/src/webui/features/tasks/utils/query-tool-mode-results.ts b/src/webui/features/tasks/utils/query-tool-mode-results.ts
new file mode 100644
index 000000000..8b7b59151
--- /dev/null
+++ b/src/webui/features/tasks/utils/query-tool-mode-results.ts
@@ -0,0 +1,34 @@
+import {safeJsonParse} from './safe-json-parse'
+
+export type QueryToolModeMatchedDoc = {
+ format?: string
+ path: string
+ rendered_md?: string
+ score: number
+ title: string
+}
+
+export function isQueryToolModeType(type: string): boolean {
+ return type === 'query-tool-mode'
+}
+
+export function parseQueryToolModeResult(content: string): QueryToolModeMatchedDoc[] | undefined {
+ const parsed = safeJsonParse(content)
+ if (!parsed || typeof parsed !== 'object') return undefined
+ const obj = parsed as Record
+ if (!Array.isArray(obj.matchedDocs)) return undefined
+ return obj.matchedDocs.filter((element) => isMatchedDoc(element))
+}
+
+export function queryToolModeRowTitle(content: string): string | undefined {
+ const parsed = safeJsonParse(content)
+ if (!parsed || typeof parsed !== 'object') return undefined
+ const obj = parsed as Record
+ return typeof obj.query === 'string' ? obj.query : undefined
+}
+
+function isMatchedDoc(value: unknown): value is QueryToolModeMatchedDoc {
+ if (typeof value !== 'object' || value === null) return false
+ const obj = value as Record
+ return typeof obj.title === 'string' && typeof obj.path === 'string' && typeof obj.score === 'number'
+}
diff --git a/src/webui/features/tasks/utils/safe-json-parse.ts b/src/webui/features/tasks/utils/safe-json-parse.ts
new file mode 100644
index 000000000..5dcdd11d0
--- /dev/null
+++ b/src/webui/features/tasks/utils/safe-json-parse.ts
@@ -0,0 +1,7 @@
+export function safeJsonParse(content: string): unknown {
+ try {
+ return JSON.parse(content)
+ } catch {
+ return undefined
+ }
+}
diff --git a/src/webui/features/tasks/utils/task-display-title.ts b/src/webui/features/tasks/utils/task-display-title.ts
new file mode 100644
index 000000000..275398115
--- /dev/null
+++ b/src/webui/features/tasks/utils/task-display-title.ts
@@ -0,0 +1,8 @@
+import {curateHtmlDirectRowTitle, isCurateHtmlDirectType} from './curate-tool-mode'
+import {isQueryToolModeType, queryToolModeRowTitle} from './query-tool-mode-results'
+
+export function taskDisplayTitle(task: {content: string; type: string}): string | undefined {
+ if (isCurateHtmlDirectType(task.type)) return curateHtmlDirectRowTitle(task.content)
+ if (isQueryToolModeType(task.type)) return queryToolModeRowTitle(task.content) ?? task.content
+ return task.content
+}
diff --git a/src/webui/lib/syntax-highlighter.ts b/src/webui/lib/syntax-highlighter.ts
index 46c57eb00..ae5229bc7 100644
--- a/src/webui/lib/syntax-highlighter.ts
+++ b/src/webui/lib/syntax-highlighter.ts
@@ -5,8 +5,8 @@
* react-syntax-highlighter — `Prism` eagerly bundles ~280 PrismJS language
* definitions (~1MB minified), while `PrismLight` only ships languages we
* explicitly register. This module is the single registration point so the
- * three call sites (markdown-inline, markdown-view, task-detail-tool-call)
- * share one configured highlighter.
+ * call sites (markdown-inline, markdown-view) share one configured
+ * highlighter.
*
* Add a language here when content using it appears in the UI; unregistered
* languages render as plain text (no error, just no syntax colors).
diff --git a/test/unit/webui/features/tasks/utils/query-tool-mode-results.test.ts b/test/unit/webui/features/tasks/utils/query-tool-mode-results.test.ts
new file mode 100644
index 000000000..7b610e8ac
--- /dev/null
+++ b/test/unit/webui/features/tasks/utils/query-tool-mode-results.test.ts
@@ -0,0 +1,115 @@
+/* eslint-disable camelcase */
+import {expect} from 'chai'
+
+import {
+ isQueryToolModeType,
+ parseQueryToolModeResult,
+ queryToolModeRowTitle,
+} from '../../../../../../src/webui/features/tasks/utils/query-tool-mode-results.js'
+
+const doc = (overrides: Record = {}) => ({
+ format: 'html',
+ path: 'analytics/lifecycle_pipeline.html',
+ rendered_md: '# Analytics Lifecycle Pipeline\n\nBody text.',
+ score: 0.85,
+ title: 'Analytics Lifecycle Pipeline',
+ ...overrides,
+})
+
+describe('query-tool-mode result parser', () => {
+ describe('isQueryToolModeType', () => {
+ it('matches query-tool-mode', () => {
+ expect(isQueryToolModeType('query-tool-mode')).to.equal(true)
+ })
+
+ it('does not match the LLM query type', () => {
+ expect(isQueryToolModeType('query')).to.equal(false)
+ })
+
+ it('does not match unrelated task types', () => {
+ expect(isQueryToolModeType('curate-tool-mode')).to.equal(false)
+ })
+ })
+
+ describe('parseQueryToolModeResult', () => {
+ it('parses a tool-mode result with matched docs', () => {
+ const content = JSON.stringify({matchedDocs: [doc()], metadata: {}, status: 'ok'})
+ const parsed = parseQueryToolModeResult(content)
+ if (!parsed) throw new Error('expected parsed docs')
+
+ expect(parsed).to.have.lengthOf(1)
+ const [first] = parsed
+ expect(first.title).to.equal('Analytics Lifecycle Pipeline')
+ expect(first.path).to.equal('analytics/lifecycle_pipeline.html')
+ expect(first.score).to.equal(0.85)
+ expect(first.rendered_md).to.contain('Analytics Lifecycle Pipeline')
+ })
+
+ it('keeps multiple matched docs in order', () => {
+ const content = JSON.stringify({
+ matchedDocs: [doc({path: 'a', title: 'A'}), doc({path: 'b', title: 'B'})],
+ status: 'ok',
+ })
+ const parsed = parseQueryToolModeResult(content)
+ if (!parsed) throw new Error('expected parsed docs')
+
+ expect(parsed.map((entry) => entry.title)).to.deep.equal(['A', 'B'])
+ })
+
+ it('returns an empty array for a no-matches result', () => {
+ const content = JSON.stringify({matchedDocs: [], metadata: {}, status: 'no-matches'})
+ expect(parseQueryToolModeResult(content)).to.deep.equal([])
+ })
+
+ it('drops entries that are missing a required field', () => {
+ const content = JSON.stringify({
+ matchedDocs: [
+ doc(),
+ {path: 'x', score: 0.1},
+ {score: 0.2, title: 'no path'},
+ {path: 'y', title: 'no score'},
+ ],
+ status: 'ok',
+ })
+ const parsed = parseQueryToolModeResult(content)
+ if (!parsed) throw new Error('expected parsed docs')
+
+ expect(parsed).to.have.lengthOf(1)
+ })
+
+ it('returns undefined for malformed JSON', () => {
+ expect(parseQueryToolModeResult('not-json{')).to.equal(undefined)
+ })
+
+ it('returns undefined when matchedDocs is missing', () => {
+ expect(parseQueryToolModeResult(JSON.stringify({status: 'ok'}))).to.equal(undefined)
+ })
+
+ it('returns undefined when matchedDocs is not an array', () => {
+ expect(parseQueryToolModeResult(JSON.stringify({matchedDocs: 'nope'}))).to.equal(undefined)
+ })
+
+ it('returns undefined for a non-object payload', () => {
+ expect(parseQueryToolModeResult(JSON.stringify('a string'))).to.equal(undefined)
+ })
+ })
+
+ describe('queryToolModeRowTitle', () => {
+ it('returns the decoded query from an encoded payload', () => {
+ const content = JSON.stringify({limit: 10, query: 'agent loop and computer use automation'})
+ expect(queryToolModeRowTitle(content)).to.equal('agent loop and computer use automation')
+ })
+
+ it('returns undefined when query is missing', () => {
+ expect(queryToolModeRowTitle(JSON.stringify({limit: 10}))).to.equal(undefined)
+ })
+
+ it('returns undefined for malformed JSON', () => {
+ expect(queryToolModeRowTitle('not-json{')).to.equal(undefined)
+ })
+
+ it('returns undefined for a non-object payload', () => {
+ expect(queryToolModeRowTitle(JSON.stringify('a string'))).to.equal(undefined)
+ })
+ })
+})
diff --git a/test/unit/webui/features/tasks/utils/task-display-title.test.ts b/test/unit/webui/features/tasks/utils/task-display-title.test.ts
new file mode 100644
index 000000000..993277613
--- /dev/null
+++ b/test/unit/webui/features/tasks/utils/task-display-title.test.ts
@@ -0,0 +1,24 @@
+import {expect} from 'chai'
+
+import {taskDisplayTitle} from '../../../../../../src/webui/features/tasks/utils/task-display-title.js'
+
+describe('taskDisplayTitle', () => {
+ it('returns the decoded query for a query-tool-mode task', () => {
+ const content = JSON.stringify({limit: 10, query: 'agent loop and computer use automation'})
+ expect(taskDisplayTitle({content, type: 'query-tool-mode'})).to.equal('agent loop and computer use automation')
+ })
+
+ it('falls back to the raw content when a query-tool-mode payload is unparseable', () => {
+ expect(taskDisplayTitle({content: 'not-json{', type: 'query-tool-mode'})).to.equal('not-json{')
+ })
+
+ it('returns the raw content for a plain query task', () => {
+ const content = 'what is the point on the corner top-right?'
+ expect(taskDisplayTitle({content, type: 'query'})).to.equal(content)
+ })
+
+ it('decodes the row title for a curate-tool-mode task', () => {
+ const content = JSON.stringify({html: ''})
+ expect(taskDisplayTitle({content, type: 'curate-tool-mode'})).to.equal('security/auth')
+ })
+})