feat(client): let the session search match the last message

The placeholder promised less than the user wanted and the matcher
delivered less than the placeholder should: the search box only
substring-matched title and directory. It now also matches the
session's newest own message (timeline.lastUser, the card preview
text), case-insensitively, and a non-string fold shape safely never
matches. The placeholder copy follows in both locales ("Search title /
path / last message..." / "搜索标题 / 目录 / 最后消息...").
This commit is contained in:
bowenliang123 committed 2026-09-24 16:47:43 +08:00
1 parent b377e36fc4
commit 07d389993e
3 files changed
+15 -6

No files matched your search

+2 -2
View File
@@ -273,7 +273,7 @@ export const DICT_ZH: Record<string, string> = {
'ov.heat.mon.07': '7月', 'ov.heat.mon.08': '8月', 'ov.heat.mon.09': '9月',
'ov.heat.mon.10': '10月', 'ov.heat.mon.11': '11月', 'ov.heat.mon.12': '12月',
'ov.list.title': '会话',
'ov.list.search': '搜索标题或目录…',
'ov.list.search': '搜索标题 / 目录 / 最后消息…',
'ov.list.sortLabel': '排序',
'ov.list.sort.recent': '最近更新', 'ov.list.sort.tokens': 'Token 总用量', 'ov.list.sort.context': '当前 Context',
'ov.list.empty': '还没有会话 — 发起一轮对话后,这里会列出每个会话的上下文画像',
@@ -563,7 +563,7 @@ export const DICT_EN: Record<string, string> = {
'ov.heat.mon.07': 'Jul', 'ov.heat.mon.08': 'Aug', 'ov.heat.mon.09': 'Sep',
'ov.heat.mon.10': 'Oct', 'ov.heat.mon.11': 'Nov', 'ov.heat.mon.12': 'Dec',
'ov.list.title': 'Sessions',
'ov.list.search': 'Search title or path…',
'ov.list.search': 'Search title / path / last message…',
'ov.list.sortLabel': 'Sort',
'ov.list.sort.recent': 'Last updated', 'ov.list.sort.tokens': 'Total Tokens', 'ov.list.sort.context': 'Current Context',
'ov.list.empty': 'No sessions yet — each session’s context profile appears here once you start one',
+5 -3
View File
@@ -274,8 +274,8 @@ export function createdDayOf(activity: ContextActivity | null): string | undefin
/**
* The panel's row pipeline: range (by last-activity), then the heatmap's
* picked day (sessions contributing to that day's merged ledger), then the
* search box (title or directory substring). Each stage keeps the rows it
* cannot prove out of the result — never an exception.
* search box (title, directory, or last-message substring). Each stage keeps
* the rows it cannot prove out of the result — never an exception.
*/
export function filterRows(
rows: readonly OverviewRow[],
@@ -293,7 +293,9 @@ export function filterRows(
if (query !== '') {
const inTitle = row.title.toLowerCase().includes(query)
const inCwd = row.cwd !== undefined && row.cwd.toLowerCase().includes(query)
if (!inTitle && !inCwd) return false
const lastUser = row.timeline?.lastUser
const inLastUser = typeof lastUser === 'string' && lastUser.toLowerCase().includes(query)
if (!inTitle && !inCwd && !inLastUser) return false
}
return true
})
+8 -1
View File
@@ -228,11 +228,18 @@ describe('filterRows', () => {
assert.deepEqual(filterRows(rows, { range: 'all', day: '2026-09-11', query: '' }, now), [], 'no ledger entry')
})
test('the query matches title or directory, case-insensitively', () => {
test('the query matches title, directory, or last message, case-insensitively', () => {
assert.deepEqual(filterRows(rows, { range: 'all', day: null, query: 'FRESH' }, now).map(r => r.id), ['new'])
assert.deepEqual(filterRows(rows, { range: 'all', day: null, query: '/repo' }, now).map(r => r.id), ['new'])
assert.deepEqual(filterRows(rows, { range: 'all', day: null, query: ' ' }, now).length, 3, 'a blank query matches all')
assert.deepEqual(filterRows(rows, { range: 'all', day: null, query: 'zzz' }, now), [])
// The session's newest own message joins the haystack.
const talked = rowOf({ id: 'talk', title: 'quiet title', timeline: { lastUser: 'ship the QUARTERLY report' } as unknown as ContextTimeline })
assert.deepEqual(filterRows([talked], { range: 'all', day: null, query: 'quarterly' }, now).map(r => r.id), ['talk'])
assert.deepEqual(filterRows([talked], { range: 'all', day: null, query: 'quiet' }, now).map(r => r.id), ['talk'], 'the title still matches')
// A non-string last message (a hostile fold shape) never matches.
const odd = rowOf({ id: 'odd', title: 'odd one', timeline: { lastUser: 42 } as unknown as ContextTimeline })
assert.deepEqual(filterRows([odd], { range: 'all', day: null, query: '42' }, now), [])
})
})