My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Site review fixes: SPA navigation, broken demos, merlin config

Dot extension: Replace per-diagram Js_inline scripts with a single
Js_url support file (dot-init.js) using MutationObserver pattern.
Store DOT source in <script type="text/dot"> elements to avoid
escaping issues. Fixes SPA navigation rendering.

Merlin config: Add stdlib path to build_path so dynamically loaded
packages (e.g. cmdliner via #require) resolve in merlin's type
checker. Previously only stdlib was set, causing red squiggly
errors on library references despite code compiling fine.

Widget_leaflet: Re-export Leaflet_map module from Widget_leaflet
so it's part of the public API. Add leaflet widget demo page.

Interactive extension demos: Move broken demos to .notyet:
- demo2_v2/v3: fake v1/v2 distinction (same cmdliner version)
- demo3_oxcaml: comprehensions extension disabled in compiler
- demo4_crossorigin: requires jon.ludl.am infrastructure
- demo5_multiverse: requires localhost:9090
- demo6/demo7: porting workshops need work

Fix demo_map and demo_widgets: add @x-ocaml.universe and
@x-ocaml.worker tags pointing to /_opam.

Simplify build-site.sh: remove redundant v2/v3 universe builds.

Sidebar: Add odoc-md to 'odoc Core' package group.

Scrollycode: Remove theme references from docs, align titles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+145 -52
+1 -11
build-site.sh
··· 105 105 dune exec -- jtw opam --no-worker --switch=5.2.0+ox -o "$UNIVERSES/default" cmdliner 106 106 cp "$WORKER_JS" "$UNIVERSES/default/worker.js" 107 107 108 - echo " building v3 universe (cmdliner, 5.2.0+ox switch)..." 109 - dune exec -- jtw opam --no-worker --switch=5.2.0+ox -o "$UNIVERSES/v3" cmdliner 110 - cp "$WORKER_JS" "$UNIVERSES/v3/worker.js" 111 - 112 108 echo " building oxcaml universe (5.2.0+ox switch)..." 113 109 dune exec -- jtw opam --no-worker --switch=5.2.0+ox -o "$UNIVERSES/oxcaml" 114 110 cp "$WORKER_JS" "$UNIVERSES/oxcaml/worker.js" 115 111 116 - for d in universe universe-v2 universe-v3 universe-oxcaml; do 112 + for d in universe universe-oxcaml; do 117 113 rm -rf "$DEMO_DIR/$d" 118 114 done 119 115 120 116 cp -r "$UNIVERSES/default" "$DEMO_DIR/universe" 121 117 echo " deployed universe/" 122 - 123 - cp -r "$UNIVERSES/default" "$DEMO_DIR/universe-v2" 124 - echo " deployed universe-v2/" 125 - 126 - cp -r "$UNIVERSES/v3" "$DEMO_DIR/universe-v3" 127 - echo " deployed universe-v3/" 128 118 129 119 cp -r "$UNIVERSES/oxcaml" "$DEMO_DIR/universe-oxcaml" 130 120 echo " deployed universe-oxcaml/"
+69 -1
js_top_worker/doc/js_top_worker-widget-leaflet/index.mld
··· 1 - {0 js_top_worker Leaflet widget} 1 + {0 Leaflet Map Widget} 2 + 3 + An interactive map widget for js_top_worker notebooks, built on 4 + {{:https://leafletjs.com}Leaflet}. Provides a typed OCaml API for 5 + creating maps, placing markers, drawing bounding boxes, and overlaying 6 + images. 7 + 8 + {1 Quick start} 9 + 10 + Register the widget adapter, then create a map: 11 + 12 + {@ocaml kind=setup[ 13 + #require "js_top_worker-widget-leaflet";; 14 + Widget_leaflet.register ();; 15 + ]} 16 + 17 + {@ocaml x[ 18 + let map = Leaflet_map.create 19 + ~center:(51.505, -0.09) ~zoom:13 ~height:"400px" 20 + ~on_click:(fun pt -> 21 + Printf.printf "Clicked: %.4f, %.4f\n" pt.lat pt.lng) 22 + () 23 + ]} 24 + 25 + {1 Adding markers} 26 + 27 + Place coloured markers with optional labels: 28 + 29 + {@ocaml x[ 30 + let () = 31 + Leaflet_map.add_marker map 32 + { lat = 51.505; lng = -0.09 } 33 + ~color:"#e74c3c" ~label:"Hello" (); 34 + Leaflet_map.add_marker map 35 + { lat = 51.51; lng = -0.08 } 36 + ~color:"#2ecc71" ~label:"World" () 37 + ]} 38 + 39 + {1 Navigation} 40 + 41 + Fly to a new location with animation: 42 + 43 + {@ocaml x[ 44 + let () = 45 + Leaflet_map.fly_to map { lat = 48.8566; lng = 2.3522 } ~zoom:12 () 46 + ]} 47 + 48 + Fit the map to a bounding box: 49 + 50 + {@ocaml x[ 51 + let () = 52 + Leaflet_map.fit_bounds map 53 + { south = 51.3; west = -0.5; north = 51.7; east = 0.3 } 54 + ]} 55 + 56 + {1 Drawing bounding boxes} 57 + 58 + Enable interactive rectangle drawing. The callback receives the 59 + bounds when the user finishes drawing: 60 + 61 + {@ocaml x[ 62 + let () = 63 + Printf.printf "Draw a rectangle on the map...\n"; 64 + Leaflet_map.enable_bbox_draw map 65 + ]} 66 + 67 + {1 API reference} 68 + 69 + See {!Widget_leaflet.Leaflet_map} for the full API, and {!Widget_leaflet} for adapter registration.
+4 -1
js_top_worker/lib/impl.cppo.ml
··· 1003 1003 | None -> initial.query 1004 1004 in 1005 1005 { initial with 1006 - merlin = { initial.merlin with stdlib = Some path }; 1006 + merlin = { initial.merlin with 1007 + stdlib = Some path; 1008 + build_path = [path]; 1009 + }; 1007 1010 query } 1008 1011 1009 1012 let make_pipeline ?filename source = Merlin_kernel.Mpipeline.make (config ?filename ()) source
+2
js_top_worker/widget-leaflet/widget_leaflet.ml
··· 131 131 })() 132 132 |js} 133 133 134 + module Leaflet_map = Leaflet_map 135 + 134 136 let register () = 135 137 Widget.register_adapter ~kind:"leaflet-map" ~js:leaflet_adapter_js
+3
js_top_worker/widget-leaflet/widget_leaflet.mli
··· 1 + module Leaflet_map = Leaflet_map 2 + (** Type-safe interface to the Leaflet map widget. *) 3 + 1 4 val register : unit -> unit 2 5 (** Register the Leaflet.js map adapter ([kind = "leaflet-map"]). 3 6 Call this before using [Widget.display_managed ~kind:"leaflet-map"]. *)
+49 -28
odoc-dot-extension/src/dot_extension.ml
··· 115 115 Error (Buffer.contents error_output) 116 116 ) 117 117 118 - (** JavaScript code to render a single diagram (for client-side rendering) *) 119 - let render_script id layout content = 120 - Printf.sprintf {| 118 + (** Runtime JS for dot — follows the MutationObserver pattern so diagrams 119 + render on both direct load and SPA navigation. DOT source and layout 120 + engine are stored in data attributes on the container div. *) 121 + let dot_init_js = {| 121 122 (function() { 122 - function renderDot() { 123 - var container = document.getElementById('%s'); 124 - if (!container) return; 123 + 'use strict'; 125 124 126 - if (typeof Viz === 'undefined') { 127 - container.innerHTML = '<pre style="color: red;">Viz.js not loaded</pre>'; 128 - return; 129 - } 130 - 131 - var viz = new Viz(); 132 - viz.renderSVGElement(%S, { engine: %S }) 133 - .then(function(svg) { 134 - container.innerHTML = ''; 135 - container.appendChild(svg); 136 - }) 137 - .catch(function(error) { 138 - container.innerHTML = '<pre style="color: red;">' + error + '</pre>'; 139 - }); 125 + function renderAll() { 126 + if (typeof Viz === 'undefined') return; 127 + var pending = document.querySelectorAll('.odoc-dot-diagram:not([data-dot-init])'); 128 + if (pending.length === 0) return; 129 + pending.forEach(function(el) { 130 + var srcEl = el.querySelector('script[type="text/dot"]'); 131 + if (!srcEl) return; 132 + el.setAttribute('data-dot-init', '1'); 133 + var src = srcEl.textContent; 134 + var engine = el.getAttribute('data-dot-engine') || 'dot'; 135 + var viz = new Viz(); 136 + viz.renderSVGElement(src, { engine: engine }) 137 + .then(function(svg) { 138 + var pre = el.querySelector('pre'); 139 + if (pre) pre.remove(); 140 + el.appendChild(svg); 141 + }) 142 + .catch(function(error) { 143 + el.innerHTML = '<pre style="color: red;">' + error + '</pre>'; 144 + }); 145 + }); 140 146 } 141 147 142 148 if (document.readyState === 'loading') { 143 - document.addEventListener('DOMContentLoaded', renderDot); 149 + document.addEventListener('DOMContentLoaded', function() { 150 + renderAll(); 151 + observe(); 152 + }); 144 153 } else { 145 - renderDot(); 154 + renderAll(); 155 + observe(); 156 + } 157 + 158 + function observe() { 159 + new MutationObserver(function() { renderAll(); }) 160 + .observe(document.body, { childList: true, subtree: true }); 146 161 } 147 162 })(); 148 - |} id content layout 163 + |} 149 164 150 165 module Dot_handler : Api.Code_Block_Extension = struct 151 166 let prefix = "dot" ··· 221 236 } 222 237 223 238 | None -> 224 - (* Default: client-side JavaScript rendering *) 239 + (* Default: client-side JavaScript rendering. 240 + Store DOT source and engine in data attributes; the 241 + dot-init.js support file renders them via MutationObserver. *) 225 242 let html = Printf.sprintf 226 - {|<div id="%s" class="odoc-dot-diagram"%s><pre>%s</pre></div>|} 227 - id style_attr content 243 + {|<div id="%s" class="odoc-dot-diagram"%s data-dot-engine="%s"><script type="text/dot">%s</script><pre>%s</pre></div>|} 244 + id style_attr layout dot_content content 228 245 in 229 - let script = render_script id layout dot_content in 230 246 let block = Block.[{ 231 247 attr = ["odoc-dot"]; 232 248 desc = Raw_markup ("html", html) ··· 237 253 resources = [ 238 254 Api.Js_url viz_js_url; 239 255 Api.Js_url viz_full_js_url; 240 - Api.Js_inline script; 256 + Api.Js_url "extensions/dot-init.js"; 257 + Api.Css_url "extensions/dot.css"; 241 258 ]; 242 259 assets = []; 243 260 } ··· 289 306 Api.Registry.register_support_file ~prefix:"dot" { 290 307 filename = "extensions/dot.css"; 291 308 content = Inline dot_css; 309 + }; 310 + Api.Registry.register_support_file ~prefix:"dot" { 311 + filename = "extensions/dot-init.js"; 312 + content = Inline dot_init_js; 292 313 }
odoc-interactive-extension/doc/demo2_v2.mld odoc-interactive-extension/doc/demo2_v2.mld.notyet
odoc-interactive-extension/doc/demo2_v3.mld odoc-interactive-extension/doc/demo2_v3.mld.notyet
odoc-interactive-extension/doc/demo3_oxcaml.mld odoc-interactive-extension/doc/demo3_oxcaml.mld.notyet
odoc-interactive-extension/doc/demo4_crossorigin.mld odoc-interactive-extension/doc/demo4_crossorigin.mld.notyet
odoc-interactive-extension/doc/demo5_multiverse.mld odoc-interactive-extension/doc/demo5_multiverse.mld.notyet
odoc-interactive-extension/doc/demo6_porting_workshop.mld odoc-interactive-extension/doc/demo6_porting_workshop.mld.notyet
odoc-interactive-extension/doc/demo7_oxcaml_porting_real.mld odoc-interactive-extension/doc/demo7_oxcaml_porting_real.mld.notyet
+3
odoc-interactive-extension/doc/demo_map.mld
··· 1 1 {0 Interactive Map Demo} 2 2 3 + @x-ocaml.universe /_opam 4 + @x-ocaml.worker /_opam/worker.js 5 + 3 6 This page demonstrates a managed Leaflet map widget with FRP signals 4 7 and commands. 5 8
+3
odoc-interactive-extension/doc/demo_widgets.mld
··· 1 1 {0 Widget Demo} 2 2 3 + @x-ocaml.universe /_opam 4 + @x-ocaml.worker /_opam/worker.js 5 + 3 6 This page demonstrates interactive FRP widgets powered by 4 7 [Widget] and [Note]. 5 8
+1 -1
odoc-jons-plugins/src/odoc_jons_plugins_js.ml
··· 13 13 // Package groups for sidebar organisation 14 14 var PACKAGE_GROUPS = [ 15 15 { name: 'odoc Core', 16 - packages: ['odoc', 'odoc-parser', 'odoc-driver', 'odoc-bench', 'sherlodoc'] }, 16 + packages: ['odoc', 'odoc-parser', 'odoc-driver', 'odoc-md', 'odoc-bench', 'sherlodoc'] }, 17 17 { name: 'odoc Extensions', 18 18 match: function(pkg) { 19 19 return /^odoc-/.test(pkg) && ['odoc-parser', 'odoc-driver', 'odoc-bench'].indexOf(pkg) < 0;
+1 -1
odoc-scrollycode-extension/doc/dark_repl.mld
··· 1 1 {0 Building a REPL} 2 2 3 - @scrolly Building a REPL in OCaml 3 + @scrolly Building a REPL 4 4 {ol 5 5 {li 6 6 {b The Expression Type}
+7 -7
odoc-scrollycode-extension/doc/index.mld
··· 1 - {0 Scrollycode Demos} 1 + {0 Scrollycode Extension for odoc} 2 2 3 - Scrollycoding in OCaml — three odoc extension plugins, each rendering the 4 - same scrollycode pattern with a radically different visual theme. Authored as 5 - [.mld] files using [@scrolly.<theme>] custom tags. 3 + Scroll-driven code tutorials for odoc. Authored as [.mld] files using 4 + [@@scrolly] custom tags — each step reveals new code alongside an 5 + explanation. 6 6 7 7 {ul 8 - {- {{!page-warm_parser}The Warm Workshop} — Building a JSON parser step by step. Warm cream background with a dark navy code panel, Fraunces serif display type, and earthy burnt-sienna accents.} 9 - {- {{!page-dark_repl}The Dark Terminal} — Building a REPL from scratch. Near-black cinematic theme with phosphor-green accents, JetBrains Mono code font, and the code panel as the visual hero.} 10 - {- {{!page-notebook_testing}The Notebook} — Building a test framework incrementally. Clean editorial aesthetic with a soft white background, blue-violet accents, Newsreader serif headings.} 8 + {- {{!page-warm_parser}Building a JSON Parser} — Building a JSON parser step by step.} 9 + {- {{!page-dark_repl}Building a REPL} — Building a REPL from scratch.} 10 + {- {{!page-notebook_testing}Building a Test Framework} — Building a test framework incrementally.} 11 11 }
+1 -1
odoc-scrollycode-extension/doc/notebook_testing.mld
··· 1 1 {0 Building a Test Framework} 2 2 3 - @scrolly Building a Test Framework in OCaml 3 + @scrolly Building a Test Framework 4 4 {ol 5 5 {li 6 6 {b A Single Assertion}
+1 -1
odoc-scrollycode-extension/doc/warm_parser.mld
··· 1 1 {0 Building a JSON Parser} 2 2 3 - @scrolly Building a JSON Parser in OCaml 3 + @scrolly Building a JSON Parser 4 4 {ol 5 5 {li 6 6 {b Defining the Value Type}