A fork of mtelver's day10 project

Integrate blog index generation into dune promotion workflow

gen_blog_index.exe now writes into _blog_gen/ directory (instead of
overwriting in place) and runs from site/ cwd. dune.inc gains a
(dir _blog_gen) rule plus per-index diff rules under @runtest, so
`dune runtest site/` detects stale indexes and `dune promote` updates them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+419 -74
+25 -25
scripts/gen_blog_index.ml
··· 61 61 | _ -> () 62 62 ) entries 63 63 in 64 - scan_dir "site/blog"; 64 + scan_dir "blog"; 65 65 (* Sort by published date descending *) 66 66 List.sort (fun a b -> String.compare b.published a.published) !posts 67 67 ··· 75 75 else 76 76 Printf.sprintf "page-%s" post.slug 77 77 in 78 - Printf.sprintf "{{!/jon-site/blog/%04d/%02d/%s}%s}" 78 + Printf.sprintf "{{!//blog/%04d/%02d/%s}%s}" 79 79 post.year post.month slug_fmt post.title 80 80 81 81 let generate_month_index year month posts = ··· 129 129 |> List.map (fun y -> Printf.sprintf "%d/" y) 130 130 |> String.concat " " 131 131 in 132 - Printf.sprintf "{0 Blog}\n\n@children_order %s\n\nUse the sidebar to navigate the blog posts. The most recent posts are listed first.\n" 133 - children_order 132 + let post_links = 133 + posts 134 + |> List.map (fun p -> "- " ^ post_link p) 135 + |> String.concat "\n" 136 + in 137 + Printf.sprintf "{0 Blog}\n\n@children_order %s\n\n%s\n" 138 + children_order post_links 134 139 135 - let generate_recent_entries posts n = 136 - let recent = 137 - if List.length posts > n then 138 - List.filteri (fun i _ -> i < n) posts 139 - else 140 - posts 140 + let ensure_dir path = 141 + let dir = Filename.dirname path in 142 + let rec mkdir_p d = 143 + if d <> "." && d <> "/" && not (Sys.file_exists d) then begin 144 + mkdir_p (Filename.dirname d); 145 + Sys.mkdir d 0o755 146 + end 141 147 in 142 - recent 143 - |> List.map (fun p -> "- " ^ post_link p) 144 - |> String.concat "\n" 148 + mkdir_p dir 145 149 146 150 let write_file path content = 147 - let oc = open_out path in 151 + let gen_path = Filename.concat "_blog_gen" path in 152 + ensure_dir gen_path; 153 + let oc = open_out gen_path in 148 154 output_string oc content; 149 155 close_out oc; 150 - Printf.printf "Wrote %s\n" path 156 + Printf.eprintf "Wrote %s\n" gen_path 151 157 152 158 let () = 153 159 let posts = find_posts () in 154 - Printf.printf "Found %d posts\n\n" (List.length posts); 160 + Printf.eprintf "Found %d posts\n\n" (List.length posts); 155 161 156 162 (* Get unique year/month combinations *) 157 163 let year_months = ··· 164 170 List.iter (fun (year, month) -> 165 171 match generate_month_index year month posts with 166 172 | Some content -> 167 - let dir = Printf.sprintf "site/blog/%04d/%02d" year month in 173 + let dir = Printf.sprintf "blog/%04d/%02d" year month in 168 174 let path = Filename.concat dir "index.mld" in 169 175 write_file path content 170 176 | None -> () ··· 181 187 List.iter (fun year -> 182 188 match generate_year_index year posts with 183 189 | Some content -> 184 - let path = Printf.sprintf "site/blog/%04d/index.mld" year in 190 + let path = Printf.sprintf "blog/%04d/index.mld" year in 185 191 write_file path content 186 192 | None -> () 187 193 ) years; 188 194 189 195 (* Generate main blog index *) 190 196 let blog_index = generate_blog_index posts in 191 - write_file "site/blog/index.mld" blog_index; 192 - 193 - (* Output recent entries for main index *) 194 - Printf.printf "\n=== Recent entries (copy to index.mld) ===\n\n"; 195 - Printf.printf "{1 Recent entries}\n\n"; 196 - print_endline (generate_recent_entries posts 10); 197 - print_endline "" 197 + write_file "blog/index.mld" blog_index
+32
site-builder/gen_rules.ml
··· 83 83 let pr = Printf.printf 84 84 let prl fmt = Printf.printf (fmt ^^ "\n") 85 85 86 + let is_under_blog rel = 87 + String.length rel >= 5 && String.sub rel 0 5 = "blog/" 88 + 89 + let is_blog_index rel = 90 + is_under_blog rel && Filename.basename rel = "index.mld" 91 + 92 + let is_blog_post rel = 93 + is_under_blog rel 94 + && Filename.check_suffix rel ".mld" 95 + && Filename.basename rel <> "index.mld" 96 + 86 97 (* --- Main ---------------------------------------------------------------- *) 87 98 88 99 let () = ··· 159 170 List.iter (fun rel -> prl " %s" (html_target rel)) mld_files; 160 171 List.iter (fun asset -> prl " _html/%s" asset) assets; 161 172 prl " ))"; 173 + pr "\n"; 174 + (* Blog index generation via gen_blog_index.exe *) 175 + let blog_indexes = List.filter is_blog_index mld_files in 176 + let blog_posts = List.filter is_blog_post mld_files in 177 + if blog_indexes <> [] then begin 178 + prl "(rule"; 179 + prl " (target (dir _blog_gen))"; 180 + prl " (deps"; 181 + List.iter (fun rel -> prl " %s" rel) blog_posts; 182 + List.iter (fun rel -> prl " %s" rel) blog_indexes; 183 + prl " )"; 184 + prl " (action (run ../scripts/gen_blog_index.exe)))"; 185 + pr "\n"; 186 + (* Diff rules under @runtest *) 187 + List.iter 188 + (fun rel -> 189 + prl "(rule"; 190 + prl " (alias runtest)"; 191 + prl " (action (diff %s _blog_gen/%s)))" rel rel) 192 + blog_indexes; 193 + end; 162 194 ()
+1 -1
site/blog/2025/03/index.mld
··· 1 - @children_order module-type-of code-block-metadata 2 1 {0 March} 3 2 3 + @children_order module-type-of code-block-metadata 4 4
+1 -1
site/blog/2025/04/index.mld
··· 1 - @children_order ocaml-docs-ci-and-odoc-3 odoc-3 semantic-versioning-is-hard meeting-the-team this-site 2 1 {0 April} 3 2 3 + @children_order ocaml-docs-ci-and-odoc-3 odoc-3 semantic-versioning-is-hard meeting-the-team this-site 4 4
+1 -1
site/blog/2025/05/index.mld
··· 1 - @children_order docs-progress lots-of-things ticks-solved-by-ai oxcaml-gets-closer ai-for-climate-and-nature-day 2 1 {0 May} 3 2 3 + @children_order docs-progress lots-of-things ticks-solved-by-ai oxcaml-gets-closer ai-for-climate-and-nature-day 4 4
+1 -1
site/blog/2025/06/index.mld
··· 1 - @children_order week23 2 1 {0 June} 3 2 3 + @children_order week23 4 4
+1 -1
site/blog/2025/07/index.mld
··· 1 - @children_order retrospective odoc-3-live-on-ocaml-org week28 week27 2 1 {0 July} 3 2 3 + @children_order retrospective odoc-3-live-on-ocaml-org week28 week27 4 4
+1 -1
site/blog/2025/08/index.mld
··· 1 - @children_order ocaml-lsp-mcp ocaml-mcp-server week33 2 1 {0 August} 3 2 3 + @children_order ocaml-lsp-mcp ocaml-mcp-server week33 4 4
+1 -1
site/blog/2025/09/index.mld
··· 1 - @children_order caching-opam-solutions2 odoc-bugs caching-opam-solutions build-ids-for-day10 giving-hub-cl-an-upgrade 2 1 {0 September} 3 2 3 + @children_order caching-opam-solutions2 odoc-bugs caching-opam-solutions build-ids-for-day10 giving-hub-cl-an-upgrade 4 4
+1 -1
site/blog/2025/11/index.mld
··· 1 - @children_order foundations-of-computer-science 2 1 {0 November} 3 2 3 + @children_order foundations-of-computer-science 4 4
+1 -1
site/blog/2025/12/index.mld
··· 1 - @children_order claude-and-dune an-svg-is-all-you-need 2 1 {0 December} 3 2 3 + @children_order claude-and-dune an-svg-is-all-you-need 4 4
+29 -29
site/blog/2025/index.mld
··· 1 - @children_order 12 11 09 08 07 06 05 04 03 2 1 {0 2025} 3 2 3 + @children_order 12/ 11/ 09/ 08/ 07/ 06/ 05/ 04/ 03/ 4 4 5 - - {{!/jon-site/blog/2025/12/page-"claude-and-dune"}Claude and Dune} 6 - - {{!/jon-site/blog/2025/12/page-"an-svg-is-all-you-need"}An SVG is all you need} 7 - - {{!/jon-site/blog/2025/11/page-"foundations-of-computer-science"}Foundations of Computer Science} 8 - - {{!/jon-site/blog/2025/09/page-"caching-opam-solutions2"}Caching opam solutions - part 2} 9 - - {{!/jon-site/blog/2025/09/page-"odoc-bugs"}Odoc bugs} 10 - - {{!/jon-site/blog/2025/09/page-"caching-opam-solutions"}Caching opam solutions} 11 - - {{!/jon-site/blog/2025/09/page-"build-ids-for-day10"}Build IDs for Day10} 12 - - {{!/jon-site/blog/2025/09/page-"giving-hub-cl-an-upgrade"}Giving hub.cl an upgrade} 13 - - {{!/jon-site/blog/2025/08/page-"ocaml-lsp-mcp"}Using ocaml-lsp-server via an MCP server} 14 - - {{!/jon-site/blog/2025/08/page-"ocaml-mcp-server"}An OCaml MCP server} 15 - - {{!/jon-site/blog/2025/08/page-week33}Week 33} 16 - - {{!/jon-site/blog/2025/07/page-retrospective}4 months in, a retrospective} 17 - - {{!/jon-site/blog/2025/07/page-"odoc-3-live-on-ocaml-org"}Odoc 3 is live on OCaml.org!} 18 - - {{!/jon-site/blog/2025/07/page-week28}Week 28} 19 - - {{!/jon-site/blog/2025/07/page-week27}Weeks 24-27} 20 - - {{!/jon-site/blog/2025/06/page-week23}Week 23} 21 - - {{!/jon-site/blog/2025/05/page-"docs-progress"}Progress in OCaml docs} 22 - - {{!/jon-site/blog/2025/05/page-"lots-of-things"}Lots of things have been happening} 23 - - {{!/jon-site/blog/2025/05/page-"ticks-solved-by-ai"}Solving First-year OCaml exercises with AI} 24 - - {{!/jon-site/blog/2025/05/page-"oxcaml-gets-closer"}OxCaml is getting closer...} 25 - - {{!/jon-site/blog/2025/05/page-"ai-for-climate-and-nature-day"}AI for Climate & Nature Community Day} 26 - - {{!/jon-site/blog/2025/04/page-"ocaml-docs-ci-and-odoc-3"}OCaml-Docs-CI and Odoc 3} 27 - - {{!/jon-site/blog/2025/04/page-"odoc-3"}Odoc 3: So what?} 28 - - {{!/jon-site/blog/2025/04/page-"semantic-versioning-is-hard"}Semantic Versioning in OCaml is Hard} 29 - - {{!/jon-site/blog/2025/04/page-"meeting-the-team"}Meeting the Team} 30 - - {{!/jon-site/blog/2025/04/page-"this-site"}This site} 31 - - {{!/jon-site/blog/2025/03/page-"module-type-of"}The Road to Odoc 3: Module Type Of} 32 - - {{!/jon-site/blog/2025/03/page-"code-block-metadata"}Code block metadata} 5 + - {{!//blog/2025/12/page-"claude-and-dune"}Claude and Dune} 6 + - {{!//blog/2025/12/page-"an-svg-is-all-you-need"}An SVG is all you need} 7 + - {{!//blog/2025/11/page-"foundations-of-computer-science"}Foundations of Computer Science} 8 + - {{!//blog/2025/09/page-"caching-opam-solutions2"}Caching opam solutions - part 2} 9 + - {{!//blog/2025/09/page-"odoc-bugs"}Odoc bugs} 10 + - {{!//blog/2025/09/page-"caching-opam-solutions"}Caching opam solutions} 11 + - {{!//blog/2025/09/page-"build-ids-for-day10"}Build IDs for Day10} 12 + - {{!//blog/2025/09/page-"giving-hub-cl-an-upgrade"}Giving hub.cl an upgrade} 13 + - {{!//blog/2025/08/page-"ocaml-lsp-mcp"}Using ocaml-lsp-server via an MCP server} 14 + - {{!//blog/2025/08/page-"ocaml-mcp-server"}An OCaml MCP server} 15 + - {{!//blog/2025/08/page-week33}Week 33} 16 + - {{!//blog/2025/07/page-retrospective}4 months in, a retrospective} 17 + - {{!//blog/2025/07/page-"odoc-3-live-on-ocaml-org"}Odoc 3 is live on OCaml.org!} 18 + - {{!//blog/2025/07/page-week28}Week 28} 19 + - {{!//blog/2025/07/page-week27}Weeks 24-27} 20 + - {{!//blog/2025/06/page-week23}Week 23} 21 + - {{!//blog/2025/05/page-"docs-progress"}Progress in OCaml docs} 22 + - {{!//blog/2025/05/page-"lots-of-things"}Lots of things have been happening} 23 + - {{!//blog/2025/05/page-"ticks-solved-by-ai"}Solving First-year OCaml exercises with AI} 24 + - {{!//blog/2025/05/page-"oxcaml-gets-closer"}OxCaml is getting closer...} 25 + - {{!//blog/2025/05/page-"ai-for-climate-and-nature-day"}AI for Climate & Nature Community Day} 26 + - {{!//blog/2025/04/page-"ocaml-docs-ci-and-odoc-3"}OCaml-Docs-CI and Odoc 3} 27 + - {{!//blog/2025/04/page-"odoc-3"}Odoc 3: So what?} 28 + - {{!//blog/2025/04/page-"semantic-versioning-is-hard"}Semantic Versioning in OCaml is Hard} 29 + - {{!//blog/2025/04/page-"meeting-the-team"}Meeting the Team} 30 + - {{!//blog/2025/04/page-"this-site"}This site} 31 + - {{!//blog/2025/03/page-"module-type-of"}The Road to Odoc 3: Module Type Of} 32 + - {{!//blog/2025/03/page-"code-block-metadata"}Code block metadata}
+1 -1
site/blog/2026/01/index.mld
··· 1 - @children_order weeknotes-2026-04-05 weeknotes-2026-03 2 1 {0 January} 3 2 3 + @children_order weeknotes-2026-04-05 weeknotes-2026-03 4 4
+1 -1
site/blog/2026/02/index.mld
··· 1 - @children_order weeknotes-2026-08 odoc-js-notebooks-fun weeknotes-2026-06 2 1 {0 February} 3 2 3 + @children_order weeknotes-2026-08 weeknotes-2026-06 4 4
+1
site/blog/2026/03/index.mld
··· 1 1 {0 March} 2 2 3 3 @children_order weeknotes-2026-09 4 +
+6 -7
site/blog/2026/index.mld
··· 1 - @children_order 03/ 02/ 01/ 2 1 {0 2026} 3 2 3 + @children_order 03/ 02/ 01/ 4 4 5 - - {{!/jon-site/blog/2026/03/page-"weeknotes-2026-09"}Weeknotes for week 9} 6 - - {{!/jon-site/blog/2026/02/page-"weeknotes-2026-08"}Weeknotes for weeks 7-8} 7 - - {{!/jon-site/blog/2026/02/page-"odoc-js-notebooks-fun"}Fun with odoc, plugins and javascript} 8 - - {{!/jon-site/blog/2026/02/page-"weeknotes-2026-06"}Weeknotes for week 6} 9 - - {{!/jon-site/blog/2026/01/page-"weeknotes-2026-04-05"}Weeknotes for weeks 4-5} 10 - - {{!/jon-site/blog/2026/01/page-"weeknotes-2026-03"}Weeknotes for week 3} 5 + - {{!//blog/2026/03/page-"weeknotes-2026-09"}Weeknotes 2026 week 9} 6 + - {{!//blog/2026/02/page-"weeknotes-2026-08"}Weeknotes weeks 7-8} 7 + - {{!//blog/2026/02/page-"weeknotes-2026-06"}Weeknotes for week 6} 8 + - {{!//blog/2026/01/page-"weeknotes-2026-04-05"}Weeknotes for weeks 4-5} 9 + - {{!//blog/2026/01/page-"weeknotes-2026-03"}Weeknotes for week 3}
+34 -2
site/blog/index.mld
··· 1 - @children_order 2026 2025 2 1 {0 Blog} 3 2 3 + @children_order 2026/ 2025/ 4 4 5 - Use the sidebar to navigate the blog posts. The most recent posts are listed first. 5 + - {{!//blog/2026/03/page-"weeknotes-2026-09"}Weeknotes 2026 week 9} 6 + - {{!//blog/2026/02/page-"weeknotes-2026-08"}Weeknotes weeks 7-8} 7 + - {{!//blog/2026/02/page-"weeknotes-2026-06"}Weeknotes for week 6} 8 + - {{!//blog/2026/01/page-"weeknotes-2026-04-05"}Weeknotes for weeks 4-5} 9 + - {{!//blog/2026/01/page-"weeknotes-2026-03"}Weeknotes for week 3} 10 + - {{!//blog/2025/12/page-"claude-and-dune"}Claude and Dune} 11 + - {{!//blog/2025/12/page-"an-svg-is-all-you-need"}An SVG is all you need} 12 + - {{!//blog/2025/11/page-"foundations-of-computer-science"}Foundations of Computer Science} 13 + - {{!//blog/2025/09/page-"caching-opam-solutions2"}Caching opam solutions - part 2} 14 + - {{!//blog/2025/09/page-"odoc-bugs"}Odoc bugs} 15 + - {{!//blog/2025/09/page-"caching-opam-solutions"}Caching opam solutions} 16 + - {{!//blog/2025/09/page-"build-ids-for-day10"}Build IDs for Day10} 17 + - {{!//blog/2025/09/page-"giving-hub-cl-an-upgrade"}Giving hub.cl an upgrade} 18 + - {{!//blog/2025/08/page-"ocaml-lsp-mcp"}Using ocaml-lsp-server via an MCP server} 19 + - {{!//blog/2025/08/page-"ocaml-mcp-server"}An OCaml MCP server} 20 + - {{!//blog/2025/08/page-week33}Week 33} 21 + - {{!//blog/2025/07/page-retrospective}4 months in, a retrospective} 22 + - {{!//blog/2025/07/page-"odoc-3-live-on-ocaml-org"}Odoc 3 is live on OCaml.org!} 23 + - {{!//blog/2025/07/page-week28}Week 28} 24 + - {{!//blog/2025/07/page-week27}Weeks 24-27} 25 + - {{!//blog/2025/06/page-week23}Week 23} 26 + - {{!//blog/2025/05/page-"docs-progress"}Progress in OCaml docs} 27 + - {{!//blog/2025/05/page-"lots-of-things"}Lots of things have been happening} 28 + - {{!//blog/2025/05/page-"ticks-solved-by-ai"}Solving First-year OCaml exercises with AI} 29 + - {{!//blog/2025/05/page-"oxcaml-gets-closer"}OxCaml is getting closer...} 30 + - {{!//blog/2025/05/page-"ai-for-climate-and-nature-day"}AI for Climate & Nature Community Day} 31 + - {{!//blog/2025/04/page-"ocaml-docs-ci-and-odoc-3"}OCaml-Docs-CI and Odoc 3} 32 + - {{!//blog/2025/04/page-"odoc-3"}Odoc 3: So what?} 33 + - {{!//blog/2025/04/page-"semantic-versioning-is-hard"}Semantic Versioning in OCaml is Hard} 34 + - {{!//blog/2025/04/page-"meeting-the-team"}Meeting the Team} 35 + - {{!//blog/2025/04/page-"this-site"}This site} 36 + - {{!//blog/2025/03/page-"module-type-of"}The Road to Odoc 3: Module Type Of} 37 + - {{!//blog/2025/03/page-"code-block-metadata"}Code block metadata}
+281
site/dune.inc
··· 44 44 blog/2026/01/weeknotes-2026-03.mld 45 45 blog/2026/01/weeknotes-2026-04-05.mld 46 46 blog/2026/02/index.mld 47 + blog/2026/02/odoc-js-notebooks-fun.mld 47 48 blog/2026/02/weeknotes-2026-06.mld 49 + blog/2026/02/weeknotes-2026-08.mld 50 + blog/2026/03/index.mld 51 + blog/2026/03/weeknotes-2026-09.mld 48 52 blog/2026/index.mld 49 53 blog/index.mld 50 54 drafts/index.mld ··· 62 66 notebooks/foundations/foundations9.mld 63 67 notebooks/foundations/index.mld 64 68 notebooks/index.mld 69 + notebooks/interactive_map.mld 65 70 notebooks/oxcaml/local.mld 66 71 projects/index.mld 67 72 reference/index.mld) ··· 406 411 (run 407 412 odoc 408 413 compile 414 + blog/2026/02/odoc-js-notebooks-fun.mld 415 + --output-dir 416 + _odoc 417 + --parent-id 418 + blog/2026/02) 419 + (run 420 + odoc 421 + compile 409 422 blog/2026/02/weeknotes-2026-06.mld 410 423 --output-dir 411 424 _odoc ··· 414 427 (run 415 428 odoc 416 429 compile 430 + blog/2026/02/weeknotes-2026-08.mld 431 + --output-dir 432 + _odoc 433 + --parent-id 434 + blog/2026/02) 435 + (run 436 + odoc 437 + compile 438 + blog/2026/03/index.mld 439 + --output-dir 440 + _odoc 441 + --parent-id 442 + blog/2026/03) 443 + (run 444 + odoc 445 + compile 446 + blog/2026/03/weeknotes-2026-09.mld 447 + --output-dir 448 + _odoc 449 + --parent-id 450 + blog/2026/03) 451 + (run 452 + odoc 453 + compile 417 454 blog/2026/index.mld 418 455 --output-dir 419 456 _odoc ··· 522 559 odoc 523 560 compile 524 561 notebooks/index.mld 562 + --output-dir 563 + _odoc 564 + --parent-id 565 + notebooks) 566 + (run 567 + odoc 568 + compile 569 + notebooks/interactive_map.mld 525 570 --output-dir 526 571 _odoc 527 572 --parent-id ··· 889 934 (run 890 935 odoc 891 936 link 937 + _odoc/blog/2026/02/page-odoc-js-notebooks-fun.odoc 938 + -P 939 + site:_odoc 940 + -o 941 + _odoc/blog/2026/02/page-odoc-js-notebooks-fun.odocl) 942 + (run 943 + odoc 944 + link 892 945 _odoc/blog/2026/02/page-weeknotes-2026-06.odoc 893 946 -P 894 947 site:_odoc ··· 897 950 (run 898 951 odoc 899 952 link 953 + _odoc/blog/2026/02/page-weeknotes-2026-08.odoc 954 + -P 955 + site:_odoc 956 + -o 957 + _odoc/blog/2026/02/page-weeknotes-2026-08.odocl) 958 + (run 959 + odoc 960 + link 961 + _odoc/blog/2026/03/page-index.odoc 962 + -P 963 + site:_odoc 964 + -o 965 + _odoc/blog/2026/03/page-index.odocl) 966 + (run 967 + odoc 968 + link 969 + _odoc/blog/2026/03/page-weeknotes-2026-09.odoc 970 + -P 971 + site:_odoc 972 + -o 973 + _odoc/blog/2026/03/page-weeknotes-2026-09.odocl) 974 + (run 975 + odoc 976 + link 900 977 _odoc/blog/2026/page-index.odoc 901 978 -P 902 979 site:_odoc ··· 1033 1110 (run 1034 1111 odoc 1035 1112 link 1113 + _odoc/notebooks/page-interactive_map.odoc 1114 + -P 1115 + site:_odoc 1116 + -o 1117 + _odoc/notebooks/page-interactive_map.odocl) 1118 + (run 1119 + odoc 1120 + link 1036 1121 _odoc/notebooks/oxcaml/page-local.odoc 1037 1122 -P 1038 1123 site:_odoc ··· 1103 1188 _odoc/blog/2026/01/page-weeknotes-2026-03.odocl 1104 1189 _odoc/blog/2026/01/page-weeknotes-2026-04-05.odocl 1105 1190 _odoc/blog/2026/02/page-index.odocl 1191 + _odoc/blog/2026/02/page-odoc-js-notebooks-fun.odocl 1106 1192 _odoc/blog/2026/02/page-weeknotes-2026-06.odocl 1193 + _odoc/blog/2026/02/page-weeknotes-2026-08.odocl 1194 + _odoc/blog/2026/03/page-index.odocl 1195 + _odoc/blog/2026/03/page-weeknotes-2026-09.odocl 1107 1196 _odoc/blog/2026/page-index.odocl 1108 1197 _odoc/blog/page-index.odocl 1109 1198 _odoc/drafts/page-index.odocl ··· 1121 1210 _odoc/notebooks/foundations/page-foundations9.odocl 1122 1211 _odoc/notebooks/foundations/page-index.odocl 1123 1212 _odoc/notebooks/page-index.odocl 1213 + _odoc/notebooks/page-interactive_map.odocl 1124 1214 _odoc/notebooks/oxcaml/page-local.odocl 1125 1215 _odoc/projects/page-index.odocl 1126 1216 _odoc/reference/page-index.odocl) ··· 1177 1267 _odoc/blog/2026/01/page-weeknotes-2026-03.odocl 1178 1268 _odoc/blog/2026/01/page-weeknotes-2026-04-05.odocl 1179 1269 _odoc/blog/2026/02/page-index.odocl 1270 + _odoc/blog/2026/02/page-odoc-js-notebooks-fun.odocl 1180 1271 _odoc/blog/2026/02/page-weeknotes-2026-06.odocl 1272 + _odoc/blog/2026/02/page-weeknotes-2026-08.odocl 1273 + _odoc/blog/2026/03/page-index.odocl 1274 + _odoc/blog/2026/03/page-weeknotes-2026-09.odocl 1181 1275 _odoc/blog/2026/page-index.odocl 1182 1276 _odoc/blog/page-index.odocl 1183 1277 _odoc/drafts/page-index.odocl ··· 1195 1289 _odoc/notebooks/foundations/page-foundations9.odocl 1196 1290 _odoc/notebooks/foundations/page-index.odocl 1197 1291 _odoc/notebooks/page-index.odocl 1292 + _odoc/notebooks/page-interactive_map.odocl 1198 1293 _odoc/notebooks/oxcaml/page-local.odocl 1199 1294 _odoc/projects/page-index.odocl 1200 1295 _odoc/reference/page-index.odocl ··· 1630 1725 x-ocaml.universe=/_opam 1631 1726 -o 1632 1727 _html 1728 + _odoc/blog/2026/02/page-odoc-js-notebooks-fun.odocl) 1729 + (run 1730 + odoc 1731 + html-generate 1732 + --shell 1733 + jon-shell 1734 + --config 1735 + x-ocaml.universe=/_opam 1736 + -o 1737 + _html 1633 1738 _odoc/blog/2026/02/page-weeknotes-2026-06.odocl) 1634 1739 (run 1635 1740 odoc ··· 1640 1745 x-ocaml.universe=/_opam 1641 1746 -o 1642 1747 _html 1748 + _odoc/blog/2026/02/page-weeknotes-2026-08.odocl) 1749 + (run 1750 + odoc 1751 + html-generate 1752 + --shell 1753 + jon-shell 1754 + --config 1755 + x-ocaml.universe=/_opam 1756 + -o 1757 + _html 1758 + _odoc/blog/2026/03/page-index.odocl) 1759 + (run 1760 + odoc 1761 + html-generate 1762 + --shell 1763 + jon-shell 1764 + --config 1765 + x-ocaml.universe=/_opam 1766 + -o 1767 + _html 1768 + _odoc/blog/2026/03/page-weeknotes-2026-09.odocl) 1769 + (run 1770 + odoc 1771 + html-generate 1772 + --shell 1773 + jon-shell 1774 + --config 1775 + x-ocaml.universe=/_opam 1776 + -o 1777 + _html 1643 1778 _odoc/blog/2026/page-index.odocl) 1644 1779 (run 1645 1780 odoc ··· 1810 1945 x-ocaml.universe=/_opam 1811 1946 -o 1812 1947 _html 1948 + _odoc/notebooks/page-interactive_map.odocl) 1949 + (run 1950 + odoc 1951 + html-generate 1952 + --shell 1953 + jon-shell 1954 + --config 1955 + x-ocaml.universe=/_opam 1956 + -o 1957 + _html 1813 1958 _odoc/notebooks/oxcaml/page-local.odocl) 1814 1959 (run 1815 1960 odoc ··· 1880 2025 _html/blog/2026/01/weeknotes-2026-03.html 1881 2026 _html/blog/2026/01/weeknotes-2026-04-05.html 1882 2027 _html/blog/2026/02/index.html 2028 + _html/blog/2026/02/odoc-js-notebooks-fun.html 1883 2029 _html/blog/2026/02/weeknotes-2026-06.html 2030 + _html/blog/2026/02/weeknotes-2026-08.html 2031 + _html/blog/2026/03/index.html 2032 + _html/blog/2026/03/weeknotes-2026-09.html 1884 2033 _html/blog/2026/index.html 1885 2034 _html/blog/index.html 1886 2035 _html/drafts/index.html ··· 1898 2047 _html/notebooks/foundations/foundations9.html 1899 2048 _html/notebooks/foundations/index.html 1900 2049 _html/notebooks/index.html 2050 + _html/notebooks/interactive_map.html 1901 2051 _html/notebooks/oxcaml/local.html 1902 2052 _html/projects/index.html 1903 2053 _html/reference/index.html 1904 2054 _html/static/assets/jon.jpg)) 2055 + 2056 + (rule 2057 + (target 2058 + (dir _blog_gen)) 2059 + (deps 2060 + blog/2025/03/code-block-metadata.mld 2061 + blog/2025/03/module-type-of.mld 2062 + blog/2025/04/meeting-the-team.mld 2063 + blog/2025/04/ocaml-docs-ci-and-odoc-3.mld 2064 + blog/2025/04/odoc-3.mld 2065 + blog/2025/04/semantic-versioning-is-hard.mld 2066 + blog/2025/04/this-site.mld 2067 + blog/2025/05/ai-for-climate-and-nature-day.mld 2068 + blog/2025/05/docs-progress.mld 2069 + blog/2025/05/lots-of-things.mld 2070 + blog/2025/05/oxcaml-gets-closer.mld 2071 + blog/2025/05/ticks-solved-by-ai.mld 2072 + blog/2025/06/week23.mld 2073 + blog/2025/07/odoc-3-live-on-ocaml-org.mld 2074 + blog/2025/07/retrospective.mld 2075 + blog/2025/07/week27.mld 2076 + blog/2025/07/week28.mld 2077 + blog/2025/08/ocaml-lsp-mcp.mld 2078 + blog/2025/08/ocaml-mcp-server.mld 2079 + blog/2025/08/week33.mld 2080 + blog/2025/09/build-ids-for-day10.mld 2081 + blog/2025/09/caching-opam-solutions.mld 2082 + blog/2025/09/caching-opam-solutions2.mld 2083 + blog/2025/09/giving-hub-cl-an-upgrade.mld 2084 + blog/2025/09/odoc-bugs.mld 2085 + blog/2025/11/foundations-of-computer-science.mld 2086 + blog/2025/12/an-svg-is-all-you-need.mld 2087 + blog/2025/12/claude-and-dune.mld 2088 + blog/2026/01/weeknotes-2026-03.mld 2089 + blog/2026/01/weeknotes-2026-04-05.mld 2090 + blog/2026/02/odoc-js-notebooks-fun.mld 2091 + blog/2026/02/weeknotes-2026-06.mld 2092 + blog/2026/02/weeknotes-2026-08.mld 2093 + blog/2026/03/weeknotes-2026-09.mld 2094 + blog/2025/03/index.mld 2095 + blog/2025/04/index.mld 2096 + blog/2025/05/index.mld 2097 + blog/2025/06/index.mld 2098 + blog/2025/07/index.mld 2099 + blog/2025/08/index.mld 2100 + blog/2025/09/index.mld 2101 + blog/2025/11/index.mld 2102 + blog/2025/12/index.mld 2103 + blog/2025/index.mld 2104 + blog/2026/01/index.mld 2105 + blog/2026/02/index.mld 2106 + blog/2026/03/index.mld 2107 + blog/2026/index.mld 2108 + blog/index.mld) 2109 + (action 2110 + (run ../scripts/gen_blog_index.exe))) 2111 + 2112 + (rule 2113 + (alias runtest) 2114 + (action 2115 + (diff blog/2025/03/index.mld _blog_gen/blog/2025/03/index.mld))) 2116 + 2117 + (rule 2118 + (alias runtest) 2119 + (action 2120 + (diff blog/2025/04/index.mld _blog_gen/blog/2025/04/index.mld))) 2121 + 2122 + (rule 2123 + (alias runtest) 2124 + (action 2125 + (diff blog/2025/05/index.mld _blog_gen/blog/2025/05/index.mld))) 2126 + 2127 + (rule 2128 + (alias runtest) 2129 + (action 2130 + (diff blog/2025/06/index.mld _blog_gen/blog/2025/06/index.mld))) 2131 + 2132 + (rule 2133 + (alias runtest) 2134 + (action 2135 + (diff blog/2025/07/index.mld _blog_gen/blog/2025/07/index.mld))) 2136 + 2137 + (rule 2138 + (alias runtest) 2139 + (action 2140 + (diff blog/2025/08/index.mld _blog_gen/blog/2025/08/index.mld))) 2141 + 2142 + (rule 2143 + (alias runtest) 2144 + (action 2145 + (diff blog/2025/09/index.mld _blog_gen/blog/2025/09/index.mld))) 2146 + 2147 + (rule 2148 + (alias runtest) 2149 + (action 2150 + (diff blog/2025/11/index.mld _blog_gen/blog/2025/11/index.mld))) 2151 + 2152 + (rule 2153 + (alias runtest) 2154 + (action 2155 + (diff blog/2025/12/index.mld _blog_gen/blog/2025/12/index.mld))) 2156 + 2157 + (rule 2158 + (alias runtest) 2159 + (action 2160 + (diff blog/2025/index.mld _blog_gen/blog/2025/index.mld))) 2161 + 2162 + (rule 2163 + (alias runtest) 2164 + (action 2165 + (diff blog/2026/01/index.mld _blog_gen/blog/2026/01/index.mld))) 2166 + 2167 + (rule 2168 + (alias runtest) 2169 + (action 2170 + (diff blog/2026/02/index.mld _blog_gen/blog/2026/02/index.mld))) 2171 + 2172 + (rule 2173 + (alias runtest) 2174 + (action 2175 + (diff blog/2026/03/index.mld _blog_gen/blog/2026/03/index.mld))) 2176 + 2177 + (rule 2178 + (alias runtest) 2179 + (action 2180 + (diff blog/2026/index.mld _blog_gen/blog/2026/index.mld))) 2181 + 2182 + (rule 2183 + (alias runtest) 2184 + (action 2185 + (diff blog/index.mld _blog_gen/blog/index.mld)))