@@ -26,7 +26,7 @@ pub struct NoteContent {
2626 pub incoming_links : Vec < String > ,
2727 pub mentions_people : Vec < String > ,
2828 pub mentioned_by : Vec < String > ,
29- pub char_count : usize ,
29+ pub byte_count : usize ,
3030}
3131
3232#[ derive( Debug , Serialize ) ]
@@ -110,18 +110,8 @@ fn resolve_file(
110110 return Ok ( Some ( f) ) ;
111111 }
112112
113- // Basename fallback: append .md if needed, then case-insensitive suffix match
114- let target = if file_or_docid. ends_with ( ".md" ) {
115- file_or_docid. to_string ( )
116- } else {
117- format ! ( "{}.md" , file_or_docid)
118- } ;
119- let target_lower = target. to_lowercase ( ) ;
120- let all = params. store . get_all_files ( ) ?;
121- Ok ( all. into_iter ( ) . find ( |f| {
122- let p = f. path . to_lowercase ( ) ;
123- p == target_lower || p. ends_with ( & format ! ( "/{}" , target_lower) )
124- } ) )
113+ // Basename fallback via SQL
114+ params. store . find_file_by_basename ( file_or_docid)
125115}
126116
127117/// Split content into (frontmatter YAML, body) parts.
@@ -190,7 +180,7 @@ pub fn context_read(params: &ContextParams, file_or_docid: &str) -> Result<NoteC
190180 . filter_map ( |( fid, _) | params. store . get_file_path_by_id ( * fid) . ok ( ) . flatten ( ) )
191181 . collect ( ) ;
192182
193- let char_count = content. len ( ) ;
183+ let byte_count = content. len ( ) ;
194184 Ok ( NoteContent {
195185 path : record. path ,
196186 docid : record. docid ,
@@ -202,7 +192,7 @@ pub fn context_read(params: &ContextParams, file_or_docid: &str) -> Result<NoteC
202192 incoming_links,
203193 mentions_people,
204194 mentioned_by,
205- char_count ,
195+ byte_count ,
206196 } )
207197}
208198
@@ -214,9 +204,14 @@ pub fn context_list(
214204 limit : usize ,
215205) -> Result < Vec < NoteListItem > > {
216206 let files = params. store . list_files ( folder, tags, limit) ?;
207+ let file_ids: Vec < i64 > = files. iter ( ) . map ( |f| f. id ) . collect ( ) ;
208+ let edge_counts = params
209+ . store
210+ . edge_counts_for_files ( & file_ids)
211+ . unwrap_or_default ( ) ;
217212 let mut items = Vec :: new ( ) ;
218213 for f in files {
219- let edge_count = params . store . edge_count_for_file ( f. id ) . unwrap_or ( 0 ) ;
214+ let edge_count = edge_counts . get ( & f. id ) . copied ( ) . unwrap_or ( 0 ) ;
220215 items. push ( NoteListItem {
221216 path : f. path ,
222217 docid : f. docid ,
@@ -270,15 +265,7 @@ pub fn vault_map(params: &ContextParams) -> Result<VaultMap> {
270265
271266/// Build a person context bundle: note content, mentions, wikilink connections.
272267pub fn context_who ( params : & ContextParams , name : & str ) -> Result < PersonContext > {
273- let name_md = format ! ( "{}.md" , name) ;
274- let name_lower = name_md. to_lowercase ( ) ;
275- let all_files = params. store . get_all_files ( ) ?;
276- let person_file = all_files. iter ( ) . find ( |f| {
277- let basename = f. path . rsplit ( '/' ) . next ( ) . unwrap_or ( & f. path ) . to_lowercase ( ) ;
278- basename == name_lower
279- } ) ;
280-
281- let ( note, person_id) = if let Some ( pf) = person_file {
268+ let ( note, person_id) = if let Some ( pf) = resolve_file ( params, name) ? {
282269 let n = context_read ( params, & pf. path ) ?;
283270 ( Some ( n) , Some ( pf. id ) )
284271 } else {
@@ -323,7 +310,7 @@ pub fn context_who(params: &ContextParams, name: &str) -> Result<PersonContext>
323310 }
324311 }
325312
326- let total_chars = note. as_ref ( ) . map ( |n| n. char_count ) . unwrap_or ( 0 )
313+ let total_chars = note. as_ref ( ) . map ( |n| n. byte_count ) . unwrap_or ( 0 )
327314 + mentioned_in. iter ( ) . map ( |m| m. snippet . len ( ) ) . sum :: < usize > ( ) ;
328315
329316 Ok ( PersonContext {
@@ -364,38 +351,23 @@ fn get_mention_snippet(params: &ContextParams, file_id: i64, name: &str) -> Stri
364351
365352/// Build a project context bundle: note, child notes, tasks, team, recent mentions.
366353pub fn context_project ( params : & ContextParams , name : & str ) -> Result < ProjectContext > {
367- let name_md = format ! ( "{}.md" , name) ;
368- let name_lower = name_md. to_lowercase ( ) ;
369- let all_files = params. store . get_all_files ( ) ?;
370- let project_file = all_files. iter ( ) . find ( |f| {
371- let basename = f. path . rsplit ( '/' ) . next ( ) . unwrap_or ( & f. path ) . to_lowercase ( ) ;
372- basename == name_lower
373- } ) ;
374-
375- let ( note, project_id, project_folder) = if let Some ( pf) = project_file {
376- let n = context_read ( params, & pf. path ) ?;
354+ let ( note, project_id, project_folder) = if let Some ( pf) = resolve_file ( params, name) ? {
377355 let folder = pf. path . rsplit_once ( '/' ) . map ( |( f, _) | f. to_string ( ) ) ;
356+ let n = context_read ( params, & pf. path ) ?;
378357 ( Some ( n) , Some ( pf. id ) , folder)
379358 } else {
380359 ( None , None , None )
381360 } ;
382361
383362 let mut child_ids = HashSet :: new ( ) ;
384- let mut child_notes = Vec :: new ( ) ;
363+ let mut child_records : Vec < crate :: store :: FileRecord > = Vec :: new ( ) ;
385364
386365 // Files in same folder
387366 if let Some ( folder) = & project_folder {
388367 let folder_files = params. store . list_files ( Some ( folder) , & [ ] , 50 ) ?;
389368 for f in folder_files {
390369 if Some ( f. id ) != project_id && child_ids. insert ( f. id ) {
391- let ec = params. store . edge_count_for_file ( f. id ) . unwrap_or ( 0 ) ;
392- child_notes. push ( NoteListItem {
393- path : f. path ,
394- docid : f. docid ,
395- tags : f. tags ,
396- indexed_at : f. indexed_at ,
397- edge_count : ec,
398- } ) ;
370+ child_records. push ( f) ;
399371 }
400372 }
401373 }
@@ -407,18 +379,31 @@ pub fn context_project(params: &ContextParams, name: &str) -> Result<ProjectCont
407379 if child_ids. insert ( * fid)
408380 && let Some ( f) = params. store . get_file_by_id ( * fid) . ok ( ) . flatten ( )
409381 {
410- let ec = params. store . edge_count_for_file ( * fid) . unwrap_or ( 0 ) ;
411- child_notes. push ( NoteListItem {
412- path : f. path ,
413- docid : f. docid ,
414- tags : f. tags ,
415- indexed_at : f. indexed_at ,
416- edge_count : ec,
417- } ) ;
382+ child_records. push ( f) ;
418383 }
419384 }
420385 }
421386
387+ // Batch edge counts for all children
388+ let child_file_ids: Vec < i64 > = child_records. iter ( ) . map ( |f| f. id ) . collect ( ) ;
389+ let edge_counts = params
390+ . store
391+ . edge_counts_for_files ( & child_file_ids)
392+ . unwrap_or_default ( ) ;
393+ let child_notes: Vec < NoteListItem > = child_records
394+ . into_iter ( )
395+ . map ( |f| {
396+ let ec = edge_counts. get ( & f. id ) . copied ( ) . unwrap_or ( 0 ) ;
397+ NoteListItem {
398+ path : f. path ,
399+ docid : f. docid ,
400+ tags : f. tags ,
401+ indexed_at : f. indexed_at ,
402+ edge_count : ec,
403+ }
404+ } )
405+ . collect ( ) ;
406+
422407 // Active tasks
423408 let mut active_tasks = Vec :: new ( ) ;
424409 let scan_tasks = |path : & str , tasks : & mut Vec < TaskItem > | {
@@ -483,7 +468,15 @@ pub fn context_project(params: &ContextParams, name: &str) -> Result<ProjectCont
483468 }
484469 }
485470
486- let total_chars = note. as_ref ( ) . map ( |n| n. char_count ) . unwrap_or ( 0 ) ;
471+ let total_chars = note. as_ref ( ) . map ( |n| n. byte_count ) . unwrap_or ( 0 )
472+ + child_notes
473+ . iter ( )
474+ . filter_map ( |c| {
475+ let full = params. vault_path . join ( & c. path ) ;
476+ std:: fs:: metadata ( & full) . ok ( ) . map ( |m| m. len ( ) as usize )
477+ } )
478+ . sum :: < usize > ( )
479+ + active_tasks. iter ( ) . map ( |t| t. text . len ( ) ) . sum :: < usize > ( ) ;
487480
488481 Ok ( ProjectContext {
489482 name : name. to_string ( ) ,
@@ -700,7 +693,7 @@ mod tests {
700693 assert ! ( note. tags. contains( & "rust" . to_string( ) ) ) ;
701694 assert_eq ! ( note. outgoing_links. len( ) , 1 ) ;
702695 assert_eq ! ( note. incoming_links. len( ) , 1 ) ;
703- assert ! ( note. char_count > 0 ) ;
696+ assert ! ( note. byte_count > 0 ) ;
704697 }
705698
706699 #[ test]
0 commit comments