M internal/server/web/dist/index.html => internal/server/web/dist/index.html +2 -2
@@ 13,8 13,8 @@
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap"
rel="stylesheet"
/>
- <script type="module" crossorigin src="/assets/index-Cjf6VAVm.js"></script>
- <link rel="stylesheet" crossorigin href="/assets/index-D-7MmxJh.css">
+ <script type="module" crossorigin src="/assets/index-BLnRqqxn.js"></script>
+ <link rel="stylesheet" crossorigin href="/assets/index-Dc2JzkyG.css">
</head>
<body class="density-compact">
<div id="root"></div>
M web/src/features/search/SearchTable.tsx => web/src/features/search/SearchTable.tsx +4 -2
@@ 1,6 1,6 @@
import React from 'react'
import { useNavigate } from '@tanstack/react-router'
-import { EmptyState } from '../../primitives'
+import { EmptyState, ToolDot } from '../../primitives'
import type { SearchRow } from '../../api/adapters'
import { highlightSnippet } from './highlightSnippet'
@@ 11,7 11,7 @@ interface SearchTableProps {
onLoadMore: () => void
}
-const COLS = '1fr 1fr 2fr 60px'
+const COLS = '80px 1fr 1fr 2fr 60px'
export function SearchTable({ rows, hasMore, loadingMore, onLoadMore }: SearchTableProps): React.JSX.Element {
const navigate = useNavigate()
@@ 27,6 27,7 @@ export function SearchTable({ rows, hasMore, loadingMore, onLoadMore }: SearchTa
return (
<div className="search-table body">
<div className="search-thead search-cols" style={{ gridTemplateColumns: COLS }}>
+ <span>tool</span>
<span>host</span>
<span>cwd</span>
<span>snippet</span>
@@ 45,6 46,7 @@ export function SearchTable({ rows, hasMore, loadingMore, onLoadMore }: SearchTa
})
}}
>
+ <span className="mono"><ToolDot tool={r.tool} /> {r.tool}</span>
<span className="mono truncate">{r.host}</span>
<span className="mono muted truncate">{r.cwd}</span>
<span className="snippet truncate">{highlightSnippet(r.snippet)}</span>
M web/src/features/search/highlightSnippet.ts => web/src/features/search/highlightSnippet.ts +1 -4
@@ 4,10 4,7 @@ const MARKER_RUNES = /\x02|\x03/g
/**
* Splits a snippet string on \x02/\x03 marker runes and interleaves plain text
- * with `<mark>` elements for matched segments.
- *
- * Respects IV2 — no dangerouslySetInnerHTML, marker-rune split with React text
- * nodes only, safe against XSS.
+ * with `<mark>` elements for matched segments — safe against XSS.
*/
export function highlightSnippet(snippet: string): (string | React.JSX.Element)[] {
const parts = snippet.split(MARKER_RUNES)
M web/src/features/search/useSearch.ts => web/src/features/search/useSearch.ts +0 -2
@@ 36,8 36,6 @@ interface UseSearchReturn {
* - Query key includes filters so changing any filter triggers a fresh fetch.
* - Empty `q` short-circuits to an empty result set without issuing a request.
* - Cursor pagination appends results (never replaces) when fetchNextPage is called.
- *
- * Respects: IV3 (cursor pagination), AS1 (cursor pagination is stable).
*/
export function useSearch(filters: SearchFilters, enabled: boolean): UseSearchReturn {
const query = useInfiniteQuery<SearchResponseDTO, Error, SearchResult>({
M web/src/styles/search.css => web/src/styles/search.css +2 -2
@@ 37,9 37,9 @@
background: var(--paper-2);
}
-/* Column grid: HOST · CWD · SNIPPET · RANK */
+/* Column grid: TOOL · HOST · CWD · SNIPPET · RANK */
.search-cols {
- grid-template-columns: 1fr 1fr 2fr 60px;
+ grid-template-columns: 80px 1fr 1fr 2fr 60px;
}
/* ─── Snippet with highlights ─── */