feat collapse single-subdirectory chains in files view

Inspired by cgit, directories that contain only a single subdirectory are now collapsed together (e.g. 'src/lib/views/' instead of requiring three separate clicks). The preload resolver concatenates directory names and follows the chain until hitting a directory with multiple entries or files, eliminating wasted traversal clicks.

Commit
8f6d4e0235e8be3856891471df192484ca3103f9
Author
Claude Sonnet 4 <ai@anthropic.com>
Author date
Committer
Claude Sonnet 4 <ai@anthropic.com>
Committer date
lib/resolvers.ml
index 4375d711..8b2a4386 100644..100644
@@ -361,18 +361,31 @@
361 361 let rec preload_entry (entry : Entry.t) =
362 362 if entry.perm <> Entry.Dir then
363 363 Lwt_result.return { entry; children = None }
364 Removed: else
365 Removed: let* hash = hash_of_hex entry.hash in
366 Removed: let* subtree =
367 Removed: Lwt_result.bind (read_value repository hash) @@ function
368 Removed: | Git.Value.Tree t -> Lwt_result.return (to_t t)
369 Removed: | _ -> Lwt_result.return { entries = [] }
370 Removed: in
371 Removed: let count = List.length subtree.entries in
372 Removed: if count >= 10 then Lwt_result.return { entry; children = Some [] }
373 Removed: else
374 Removed: let* children = preload_entries subtree.entries in
375 Removed: Lwt_result.return { entry; children = Some children }
364 Added: else collapse_single_subdirs entry.name entry.hash
365 Added: and collapse_single_subdirs prefix hash_hex =
366 Added: let* hash = hash_of_hex hash_hex in
367 Added: let* subtree =
368 Added: Lwt_result.bind (read_value repository hash) @@ function
369 Added: | Git.Value.Tree t -> Lwt_result.return (to_t t)
370 Added: | _ -> Lwt_result.return { entries = [] }
371 Added: in
372 Added: let entries = subtree.entries in
373 Added: match entries with
374 Added: | [ single ] when single.perm = Entry.Dir ->
375 Added: (* Single subdir — collapse into parent name and recurse *)
376 Added: let combined_name = prefix ^ "/" ^ single.name in
377 Added: collapse_single_subdirs combined_name single.hash
378 Added: | _ ->
379 Added: let count = List.length entries in
380 Added: let collapsed_entry =
381 Added: { Entry.hash = hash_hex; name = prefix; perm = Entry.Dir }
382 Added: in
383 Added: if count >= 10 then
384 Added: Lwt_result.return { entry = collapsed_entry; children = Some [] }
385 Added: else
386 Added: let* children = preload_entries entries in
387 Added: Lwt_result.return
388 Added: { entry = collapsed_entry; children = Some children }
376 389 and preload_entries entries =
377 390 let rec go acc = function
378 391 | [] -> Lwt_result.return (List.rev acc)