feat display inline README at bottom of directory listings

When a directory contains a README.* file, its content is now rendered at the bottom of the file listing in a dedicated section with a header and the standard line-numbered blob display. The handler finds the README entry (case-insensitive prefix match), fetches its blob content, and passes it to the files view as an optional parameter. Styled with a bordered container and subdued header.

Commit
92bea32f14e1093bc15db3fe1a35bd99f516d8d9
Author
Claude Sonnet 4 <ai@anthropic.com>
Author date
Committer
Claude Sonnet 4 <ai@anthropic.com>
Committer date
Changed files
lib/handlers.ml
index 38bc7dbd..62152219 100644..100644
@@ -139,10 +139,20 @@
139 139 let* diff = Resolvers.Diff.of_commit repository commit in
140 140 Views.Repo.commit context commit diff
141 141
142 Added: let find_readme repository (tree : Resolvers.Tree.t) =
143 Added: match List.find_opt Resolvers.Entry.is_readme tree.entries with
144 Added: | None -> Lwt_result.return None
145 Added: | Some entry ->
146 Added: Lwt.bind (Resolvers.blob_or_tree repository entry.hash) (function
147 Added: | Ok (`Blob blob) -> Lwt.return (Ok (Some blob))
148 Added: | Ok _ -> Lwt.return (Ok None)
149 Added: | Error e -> Lwt.return (Error e))
150 Added:
142 151 let files_at_head repository context =
143 152 let* tree = Resolvers.Tree.head repository in
144 153 let* preloaded = Resolvers.Tree.preload repository tree in
145 Removed: Views.Repo.files context [] preloaded
154 Added: let* readme = find_readme repository tree in
155 Added: Views.Repo.files context [] ?readme preloaded
146 156
147 157 let file_id repository context id =
148 158 let* trail = Resolvers.Tree.find_path repository id in
@@ -150,7 +160,8 @@
150 160 match object_ with
151 161 | `Tree tree ->
152 162 let* preloaded = Resolvers.Tree.preload repository tree in
153 Removed: Views.Repo.files context trail preloaded
163 Added: let* readme = find_readme repository tree in
164 Added: Views.Repo.files context trail ?readme preloaded
154 165 | `Blob blob -> Views.Repo.file context trail blob
155 166
156 167 let branches repository context =
lib/static/styles.css
index 3edb5328..7acca4c0 100644..100644
@@ -657,6 +657,28 @@
657 657 text-decoration: underline;
658 658 }
659 659
660 Added: /* Inline README display at bottom of directory listings */
661 Added:
662 Added: .readme-inline {
663 Added: margin-top: 1.5em;
664 Added: border: 1px solid #3a3a3a;
665 Added: border-radius: 0.25rem;
666 Added: overflow-x: auto;
667 Added: }
668 Added:
669 Added: .readme-inline h3 {
670 Added: margin: 0;
671 Added: padding: 0.6em 1em;
672 Added: background-color: #242424;
673 Added: font-size: 0.9em;
674 Added: color: #b0b0b0;
675 Added: border-bottom: 1px solid #3a3a3a;
676 Added: }
677 Added:
678 Added: .readme-inline #blob {
679 Added: padding: 0.5em 0;
680 Added: }
681 Added:
660 682 .diff-file {
661 683 margin: 1.5em 0;
662 684 border: 1px solid #3a3a3a;
lib/views/repo.ml
index c9086237..a5ec6100 100644..100644
@@ -340,11 +340,44 @@
340 340 in
341 341 HTML.(span [ class_ "path-pill" ] (root_anchor :: segments))
342 342
343 Removed: let files context trail (entries : Resolvers.Tree.preloaded_entry list) =
343 Added: let files context trail ?readme (entries : Resolvers.Tree.preloaded_entry list)
344 Added: =
344 345 let pill = breadcrumb_pill context.repo trail in
345 346 let tb = toolbar [ pill ] in
347 Added: let readme_section =
348 Added: match readme with
349 Added: | None -> HTML.null []
350 Added: | Some (blob : Resolvers.Blob.t) ->
351 Added: let formatted =
352 Added: String.split_on_char '\n' blob.content
353 Added: |> List.mapi (fun number line ->
354 Added: let line_number = number + 1 in
355 Added: HTML.
356 Added: [
357 Added: a
358 Added: [
359 Added: id "readme-%d" line_number;
360 Added: class_ "line-anchor";
361 Added: href "#readme-%d" line_number;
362 Added: Aria.label "Line %d" line_number;
363 Added: ]
364 Added: [ txt "%d" line_number ];
365 Added: span [ class_ "line" ] [ txt "\t%s\n" line ];
366 Added: ])
367 Added: |> List.concat
368 Added: in
369 Added: HTML.(
370 Added: section
371 Added: [ class_ "readme-inline" ]
372 Added: [ h3 [] [ txt "README" ]; div [ id "blob" ] formatted ])
373 Added: in
346 374 render_page context ~active:Files
347 Removed: HTML.[ tb; ul [] (List.map (li_of_preloaded context.repo) entries) ]
375 Added: HTML.
376 Added: [
377 Added: tb;
378 Added: ul [] (List.map (li_of_preloaded context.repo) entries);
379 Added: readme_section;
380 Added: ]
348 381
349 382 let file ?(active = Layout.Files) context trail (blob : Resolvers.Blob.t) =
350 383 let to_numbered_line number line =