@@ -40,19 +40,23 @@ pub struct OcrBlockHit {
4040 pub screen_last_ms : Option < u64 > ,
4141}
4242
43- /// 会话检索范围上限(与旧实现 list_sessions(None, 500, 0) 同口径)。
44- const SESSION_SCOPE : i64 = 500 ;
45-
43+ /// 会话检索范围(TD-2026-08-19-G 偿还:原 500 上限静默截断已移除——全库检索,
44+ /// 结果由 limit 有界;本常量删除,保留占位注释防回归引入)。
45+ ///
46+ /// @ai-context: 曾经 const SESSION_SCOPE: i64 = 500;若未来重新加上限,必须
47+ /// 在 UI 暴露"仅搜最近 N 会话"并明示(静默截断是 2026-08-19-G 根因)。
4648/// 检索会话 OCR 块(REQ-133):关键词 → 命中块 + 会话标题 + 图路径 + 屏区间。
4749///
4850/// @ai-context: 只搜画面要点(region=full——字幕区是转写冗余,搜字幕用段搜索);
4951/// 大小写不敏感(LIKE 对 ASCII 天然忽略大小写,CJK 无大小写概念);
5052/// 结果有界(100 条防超大 payload)。
51- /// @ai-context: M2 修复——单条 SQL 语义等价于旧逐会话循环:
52- /// ① 会话范围 = 最近 500 会话(started_at DESC, id DESC 子查询);
53- /// ② 屏区间 = 同会话同屏全部块(含字幕块,旧实现不过滤 region)
53+ /// @ai-context: M2 修复——单条 SQL 语义等价于旧逐会话循环(无会话范围上限;
54+ /// 屏区间 = 同会话同屏全部块(含字幕块,旧实现不过滤 region)
5455/// 的 min/max 时间戳(LEFT JOIN 聚合子查询);
55- /// ③ 排序 = 会话倒序 + 块时间升序(与旧双层循环一致)。
56+ /// 排序 = 会话倒序 + 块时间升序(与旧双层循环一致)。
57+ /// @ai-context: TD-2026-08-19-G 偿还——原 SESSION_SCOPE=500 静默截断:会话数
58+ /// >500 时旧会话命中被漏(用户"搜不到"无从感知);移除上限改全库
59+ /// 检索(本地 SQLite 万行级 + 结果 LIMIT 100 有界,无性能顾虑)。
5660/// @param data_dir - 应用数据目录(图存在性校验;None 时 image_path 恒 None)
5761pub fn search_ocr_blocks (
5862 db : & Db ,
@@ -83,13 +87,10 @@ pub fn search_ocr_blocks(
8387 ) sr ON sr.session_id = b.session_id AND sr.screen_id = b.screen_id
8488 WHERE b.region = 'full'
8589 AND b.text LIKE ?1 ESCAPE '\\ '
86- AND b.session_id IN (
87- SELECT id FROM sessions ORDER BY started_at DESC, id DESC LIMIT ?2
88- )
8990 ORDER BY s.started_at DESC, s.id DESC, b.timestamp_ms ASC
90- LIMIT ?3 " ,
91+ LIMIT ?2 " ,
9192 ) ?;
92- let rows = stmt. query_map ( params ! [ pattern, SESSION_SCOPE , limit as i64 ] , |row| {
93+ let rows = stmt. query_map ( params ! [ pattern, limit as i64 ] , |row| {
9394 Ok ( OcrBlockHit {
9495 ocr_block_id : row. get ( 0 ) ?,
9596 session_id : row. get ( 1 ) ?,
@@ -218,6 +219,29 @@ mod tests {
218219 assert_eq ! ( search_ocr_blocks( & db, None , "关键词" , 3 ) . unwrap( ) . len( ) , 3 ) ;
219220 }
220221
222+ #[ test]
223+ fn search_beyond_500_sessions_not_silently_truncated ( ) {
224+ // TD-2026-08-19-G 回归:原 SESSION_SCOPE=500 静默截断——第 501+ 会话的
225+ // 命中被漏(用户"搜不到"无感知);移除上限后全库命中
226+ let db = Db :: open ( ":memory:" ) . unwrap ( ) ;
227+ // Arrange:造 501 会话,命中关键词放在**最早**(第 1 个)会话
228+ let mut hit_sid = 0 ;
229+ for i in 0 ..501 {
230+ let s = db. create_session ( & NewSession {
231+ title : format ! ( "会话{}" , i) , source_window : None , profile : None , kind : None ,
232+ } ) . unwrap ( ) ;
233+ if i == 0 {
234+ add_block ( & db, s. id , 1000 , "老会话目标词" , "full" , None ) ;
235+ hit_sid = s. id ;
236+ }
237+ }
238+ // Act
239+ let hits = search_ocr_blocks ( & db, None , "目标词" , 10 ) . unwrap ( ) ;
240+ // Assert:命中(旧实现此用例必失败——第 1 会话不在最近 500 之内)
241+ assert_eq ! ( hits. len( ) , 1 ) ;
242+ assert_eq ! ( hits[ 0 ] . session_id, hit_sid) ;
243+ }
244+
221245 #[ test]
222246 fn search_returns_screen_range_across_regions ( ) {
223247 // Arrange:同屏含 full 块(2000) 与 subtitle 块(500/3000)——
0 commit comments