commits
- layer.mld: add Layer module, update description for new core type
- batch.mld: add Profile and Snapshot modules
- doc.mld: add Doc_build module
- generate.mli: add doc comment for run function
- patches.mli: new interface file for Patches module
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sync with checkout - dependency was removed for OCaml 5.4 compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename `effect` variable to `eff` in editor.ml (reserved keyword in 5.4)
- Add `first_crc` cppo-guarded helper for cmi_crcs API difference
(Import_info.t array in oxcaml vs (string * Digest.t option) list in 5.4)
- Remove `source_rendering` from dune-workspace (not in upstream dune/odoc)
All extension packages, tessera, zarr, and odoc now build with both
oxcaml 5.2.0+ox and standard OCaml 5.4.1. The js_top_worker library
compiles on both; linking requires matching merlin-lib.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cm_len-1 clamp placed decorations one character before the
document end. Use cm_len directly — positions at document length
are valid for block widget decorations in CodeMirror.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for "Position N is out of range for changeset" errors:
1. js_top_worker: clamp output_at loc to input length — the ";;"
appended for parsing made pos_cnum extend past the original source
2. x-ocaml/editor: clamp decoration positions against CM document
length (Text.length) not OCaml String.length — they differ for
non-ASCII text (UTF-16 vs bytes). Also combine doc replacement +
decoration clear into a single transaction in set_source, and
read actual CM doc in build_range_set
3. jsoo-code-mirror/decoration: defensive safe_map that catches
JS RangeError in RangeSet.map and returns empty
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Compartment+reconfigure approach caused "Position out of range for
changeset" RangeErrors because decorations weren't mapped through
document change transactions. A StateField maps decorations via
RangeSet.map(tr.changes) synchronously within each transaction,
keeping positions consistent with the document state.
- Add StateEffect and StateField OCaml bindings to jsoo-code-mirror
- Add Decoration.Range_set.empty and .map
- Convert editor.ml messages from Compartment to StateField+StateEffect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
set_source now reads back the document from CodeMirror's state
after setting it, rather than using the raw string from textContent.
This ensures current_doc matches CodeMirror's internal representation,
which may differ due to line ending normalization or HTML entity
handling.
The HTML textContent can be 2 characters longer than what CodeMirror
stores internally, causing RangeError when lint diagnostic positions
(computed relative to the raw string) exceed CodeMirror's doc length.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three changes to address CodeMirror RangeError and cm-gap issues:
1. jtw_client: Accumulate per-phrase output and emit as single
Top_response at cell end, instead of per-phrase Top_response_at.
Eliminates mid-document widget decorations that cause cm-gap
artifacts in CodeMirror's virtual rendering.
2. merlin_codemirror: Clamp lint diagnostic positions to current
document length. The doc can change between query send and
response arrival (e.g., formatter), making positions stale.
3. cell.ml: Skip formatter for run_on=load cells. The formatter
can change document length, creating changesets that invalidate
decoration positions from concurrent eval responses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
editor.ml: Clamp widget decoration positions to doc_len-1 to avoid
CodeMirror RangeError when mapping decorations through changesets.
Also catch exceptions in refresh_messages to prevent cascade failures
in cell execution. The root cause is a discrepancy between the cached
document length and CodeMirror's actual document — needs further
investigation.
interactive_map.mld: Fix forward reference to `map` in on_click
callback. Use a ref cell (map_ref) to break the self-referential
binding, since `let map = Leaflet_map.create ~on_click:(... map ...)`
is an unbound-value error in OCaml.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor x_ocaml.ml so that the Page/Backend are created lazily
and keyed on the current universe configuration (read from <meta>
tags). When a new <x-ocaml> element connects and the universe has
changed (e.g. after SPA navigation to a page with a different
@x-ocaml.universe), a fresh backend and page are created.
Previously, the backend was created once at script load time and
captured in the custom element's closure. SPA navigation would
update the <meta> tags (via the interactive extension's Js_inline
scripts) but the old backend/worker stayed alive, causing cells
on the new page to use the wrong universe's libraries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build-site.sh: Use dune-built worker.js (--no-worker + cp) so the
worker always matches the source tree. Previously jtw compiled against
the installed js_top_worker-web package via ocamlfind, which could be
stale. Also make @site and @doc steps fail on errors instead of
swallowing them.
js_top_worker: Add blank_directives to strip #require lines from source
before sending to merlin. The pre_source context prepends previous cells'
source (including #require directives) which causes Menhir's incremental
parser to throw force_reduction errors.
x-ocaml: Add bounds checking to Typed_enclosings response in
merlin_ext.ml, matching the existing pattern for Errors. Prevents
CodeMirror RangeError when merlin returns positions outside the cell's
document range.
odoc-mermaid-extension: Fix SPA navigation by replacing Js_inline init
script with Js_url support file using MutationObserver pattern. The old
startOnLoad:true approach only worked on full page loads; now mermaid.run()
is called explicitly whenever new pre.mermaid elements appear in the DOM.
site/dune.inc: Regenerated with --warn-error on odoc compile/link/
html-generate, and -L/-P flags for library/package cross-references
from @doc build output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New blog posts (monopam-madness, open-source-and-ai, weeknotes-2026-10),
notebook showcase with card layout and screenshots, Atom feed generator,
foundations notebook fixes, ONNX test improvements, widget interaction
tests, deploy script updates for oxcaml switch, and .gitignore for
build artifacts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Interactive mode was incorrectly set to read-only, preventing users
from typing in code cells. Interactive cells should be editable with
ephemeral changes (lost on refresh), unlike Exercise cells which
persist edits to localStorage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of requiring every .mld file to specify @x-ocaml.universe and
@x-ocaml.worker tags individually, configure the default universe path
once in dune-workspace (--config x-ocaml.universe=/_opam). The shell
emits <meta> tags from config values, and per-page @x-ocaml tags can
still override them.
Changes:
- dune-workspace: add --config x-ocaml.universe=/_opam to html_flags
- gen_rules.ml: pass --config to odoc html-generate for @site build
- odoc_jon_shell.ml: emit <meta> tags from x-ocaml.* config values
- odoc generator.ml: pass config to shell page_creator
- interactive_extension.ml: upsert meta tags (update existing or create)
- x_ocaml.ml: infer jtw backend from x-ocaml-universe meta tag
- Remove @x-ocaml.universe/@x-ocaml.worker from 14 .mld files using
the default /_opam universe
- deploy-site.sh: add dune install x-ocaml, chmod fix, widget-leaflet
- findlibish.ml: module detection fallback via jsoo runtime
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add index.mld files for all packages that were missing them, which
caused odoc to generate default pages titled "<package> index". Also
fix leaf item indentation by always adding a spacer span regardless
of the node kind.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add cppo preprocessing for merlin-js and x-ocaml workers to support
conditional compilation with OXCAML flag
- Guard day10 packages with enabled_if >= 5.3.0 since they need recent OCaml
- Remove fatal odoc warnings from dune-workspace (handled per-package now)
- Bump merlin-js dune lang to 3.17
- Add warning suppression flags where needed (-w -58, -w -67)
- Add interactive extension exercise pages (FOCS 2020/2024/2025, OxCaml
stack allocation)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add managed widget protocol (display_managed, command, register_adapter)
for external widget adapters like Leaflet maps
- Extract Leaflet map adapter into separate js_top_worker-widget-leaflet
library, loadable via #require
- Add widget rendering to x-ocaml web component (widget_render.ml) so
widgets work in .mld documentation pages
- Wire widget callbacks through OCaml client (js_top_worker_client_msg.ml)
- Complete the preloaded list in findlibish.ml to match all transitive
worker dependencies, preventing "file already exists" errors when
#require loads packages already compiled into the worker
- Add demo_widgets.mld and demo_map.mld documentation pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three features for x-ocaml interactive cells:
1. Worker busy indicator + stop/reset: When a cell is running (e.g. infinite
loop), the run button transforms into a spinning stop button. Clicking it
terminates the web worker, creates a fresh one, replays init messages, and
resets all cells to Not_run. Both builtin and jtw worker backends support
reset.
2. localStorage persistence: Exercise cells with data-id save their source
to localStorage (debounced 300ms). On reload, saved source is restored.
A small Reset button (bottom-left) appears when saved state exists,
allowing revert to the original HTML source.
3. Colab-style circular run/stop button: Replaces the old boxy text button
with a filled circle + white play triangle (CSS mask + ::after pseudo-
element). Running state shows a spinning arc with red accent and stop
square. Test cells get collapsible details with pass/fail/error indicators.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix sidebar kindBadge: map leaf-page→pg, module-type→MT, etc. (was
showing "L" for leaf-page entries)
- Stop escaping sidebar content HTML (entries contain <code> tags)
- Fix inline sidebar JSON: use Html.cdata_script instead of Html.txt
to prevent HTML-escaping inside <script> tags
- Add --xo-* CSS custom properties for x-ocaml cells in light/dark themes
- Fix support file registration: use 'opam var share' instead of
'opam var x-ocaml:share' (works without x-ocaml being an opam package)
- Suppress empty <li> from config-only extension tags (@x-ocaml.*)
- Add worker_url field to jtw opam findlib_index.json
- Refactor x-ocaml.js worker URL discovery to support both direct
worker_url and day10-style version/content_hash paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
backend_name used read_meta before it was defined. Move it after
read_meta and add fallback to <meta name="x-ocaml-backend"> for when
x-ocaml.js is loaded via a plain <script src> (Js_url) without custom
attributes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inline script loader hardcoded ./_x-ocaml/x-ocaml.js relative to the
page, but support files live under odoc.support/ at the doc root. Switch
to Js_url which odoc resolves against support_uri (like odoc.css). The
backend config is now communicated via a <meta> tag instead of a script
attribute.
Also: add x-ocaml dependency on merlin-js, add odoc-interactive-extension
dependency on x-ocaml, and remove unused js_top_worker-rpc.message dep.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Install x_ocaml.bc.js into the opam share directory and register it as
an odoc support file so that `odoc support-files -o DIR` copies
_x-ocaml/x-ocaml.js automatically, removing the need for manual
deployment in deploy.sh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- findlibish.mli: use Js_top_worker.Impl.dynamic_cmis (RPC module removed)
- worker.ml: use Impl.dcs_url instead of Toplevel_api_gen.dcs_url
- x-ocaml/src/jtw_client: convert to cppo for OxCaml-only
ppx_template_generated field in Query_protocol.Compl
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Consolidate js_top_worker and odoc dual-compiler stanzas into single
library stanzas with cppo rules generating impl.ml from impl.cppo.ml
- Per-package findlib_index.json with relative universe paths (../dep)
and implicit stdlib dependency injection
- Add find_stdlib_dcs to Impl.S interface for stdlib CMI lookup via
findlib metadata instead of hardcoded URLs
- Replace jsoo Json.output/Json.unsafe_input with plain JSON.stringify/
JSON.parse for cross-jsoo-version compatibility (6.0.1+ox vs 6.2.0)
- Cross-origin worker support: set __global_rel_url in blob worker,
skip URL rewriting for absolute http(s) URLs
- Fix odoc doc comments and ocamlformat-ignore for cppo files
- Add demo docs, helper scripts, and x-ocaml package-lock.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a findlib_index.json contains a "compiler" field with version
and content_hash, x-ocaml now constructs the worker URL automatically.
This means .mld files only need @x-ocaml.universe — no separate
@x-ocaml.worker tag is required. The explicit x-ocaml-worker meta
tag still takes priority when present.
Uses a synchronous XHR pre-fetch of the findlib_index.json (~200 bytes)
before creating the worker, with browser JSON.parse for extraction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enable loading .cma.js files from a different origin (e.g., ocaml.org)
by working around Chrome's CORB which blocks cross-origin importScripts
for files with embedded binary CMI data.
worker.ml: detect cross-origin URLs in import_scripts and use
synchronous XHR + eval() instead of importScripts. Same-origin
URLs continue to use the standard path.
jtw_client.ml: create blob: URL wrapper for cross-origin worker.js
(browsers block cross-origin Worker construction), derive stdlib_dcs
URL from findlib_index base, switch to eval_stream for streaming
phrase-by-phrase output.
interactive_extension.ml: always load x-ocaml.js and worker.js from
local _x-ocaml/ path (same-origin), communicate universe URL via
<meta> tag. Set backend to "jtw" when a universe is configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add --copy-file flag to jtw opam/opam-all for including x-ocaml.js
and other assets in universe output
- Pass findlib_requires and findlib_index from meta tags through to
the JTW worker init, so @x-ocaml.requires packages are loaded
during setup
- x_ocaml.ml reads <meta name="x-ocaml-packages"> and
<meta name="x-ocaml-universe"> before creating the backend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Add_cmis to X_protocol.request so pages can register external
package CMIs with the merlin worker. Page.create reads
<meta name="x-ocaml-packages" content="pkg1,pkg2,..."> and optional
<meta name="x-ocaml-cmis-url" content="./cmis/"> from the document
head. For each package, dynamic CMIs are registered so merlin can
provide completions, type-on-hover, and error checking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract page-level orchestration into a new Page module, separating
cell registry, test linking, message routing, and auto-run from the
WebComponent definition in x_ocaml.ml.
Key changes:
- Page module: cell registry, test linking (positional + data-for),
backend message routing, universe discovery stub, format config
- Cell: add ?merlin param, set_on_completed hook, has_completed,
string_of_mode, cell_for/cell_env accessors
- Editor: improved docstrings
- x_ocaml.ml: simplified to thin WebComponent + Page delegation
- Playwright browser tests: 17 assertions across 7 test scenarios
covering hidden/interactive/exercise/test modes, read-only state,
default mode, merlin disable, and full assessment chain
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prepare for merlin-js to be a top-level monopam subtree instead of
a vendored submodule inside x-ocaml.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add mode type and data attributes (data-id, data-for, data-env) to cells
- Hidden cells: no editor/UI, still execute in linked list chain
- Exercise cells: editable (default CodeMirror behaviour)
- Interactive/Test cells: read-only via EditorState.readOnly facet
- Read mode attribute from <x-ocaml mode="..."> elements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Read data-filename attribute from x-ocaml elements. Store in cell
record and Merlin_ext context. Pass through Protocol.action to both
builtin and jtw backends for correct .mli handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Js_top_worker_client_fut (old RPC/IDL layer) with
Js_top_worker_client_msg (JSON message protocol). This removes the
dependency on rpclib, ppx_deriving_rpc, and toplevel_api_gen types
from the x-ocaml frontend.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three-layer architecture separating content, theme, and renderer:
Phase 1 — Scrollycode refactor:
- Strip ~1030 lines of embedded theme CSS from scrollycode_extension.ml
- Create scrollycode_css.ml with structural CSS using CSS custom properties
- Create scrollycode_themes.ml with three theme files (warm/dark/notebook)
registered as support files
- Simplify @scrolly tag: theme suffix now ignored (CSS concern, not content)
- Playground overlay styled via --xo-* custom properties on x-ocaml element
Phase 2 — Extra CSS support:
- Add --extra-css flag to odoc html-generate for injecting additional
<link> tags (used for per-page theme selection)
Phase 3 — Shell plugin system:
- Add Html_shell module with Shell interface and hashtable registry
- Register "default" shell in html_page.ml, "json" shell in
html_fragment_json.ml
- Replace hardcoded if/else in generator.ml with shell registry lookup
- Add --shell NAME flag (--as-json kept as backward-compat alias)
x-ocaml theming:
- Replace hardcoded colors in style.css with var(--xo-*, fallback)
covering editor, gutter, buttons, tooltips, and output areas
- CSS custom properties inherit through shadow DOM boundary, so
consumers theme x-ocaml with pure CSS — no JS injection needed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add comprehensive JS stubs for OxCaml-specific primitives (domain TLS,
arch detection, blocking sync, basement/capsule) needed by the OxCaml
js_of_ocaml runtime
- Fix demo.js to load rpc_worker.bc.js (JSON-RPC) instead of the
non-RPC _opam/worker.js, fixing a protocol mismatch bug
- Fix exec RPC method never resolving: rename phrase_p parameter from
~name:"string" to unnamed, preventing rpclib from incorrectly treating
the phrase as a named parameter in the dispatch dict
- Remove unused cbort, zarith, bytesrw dependencies from opam and
dune-project
- Bump dune version in x-ocaml opam files to 3.21 for OxCaml support
- Update cram test expected output for current package list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add support for building js_top_worker and odoc with both the standard
OCaml 5.4 compiler and the OxCaml 5.2.0+ox compiler using cppo
conditional compilation and dual dune stanzas.
js_top_worker changes:
- Bump dune-project to 3.21 for %{ocaml-config:ox} support
- Add dual library stanzas gated by (enabled_if %{ocaml-config:ox})
- Add cppo guards for OxCaml API differences:
- Compilation_unit.Name.t vs string for persistent loader
- Env.report_error ~level:0 (extra parameter)
- Language_extension.set_universe_and_enable_all (oxcaml-only)
- Unit_info.make ~for_pack_prefix (extra parameter)
- Typemod.type_implementation (extra Compilation_unit arg)
- Gate ppx_deriving_rpc with (not %{ocaml-config:ox})
odoc changes:
- Apply upstream oxcaml PR #1399 (art-w/upstream-oxcaml)
- Bump dune-project to 3.21
- Add dual stanzas in loader, model, xref2, odoc, syntax_highlighter
- Add cppo OXCAML guards for compiler API differences
- Support OxCaml features: modes, layouts, labeled tuples, iarray,
unboxed records, module type strengthening, polymorphic arguments,
call position arguments, Import_info.t, Compilation_unit.t
Verified end-to-end: scrollycode demos generate HTML and the
interactive playground evaluates OCaml code in the browser with
both compiler switches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix client.ml absolute_url to handle root-relative paths (/ prefix)
using window.location.origin instead of page directory path
- Abstract merlin_ext.ml to use a post function closure instead of
concrete Client.t, decoupling from specific backend
- Update cell.ml/mli to accept eval/fmt/post function closures
instead of Client.t, and split init/start to avoid synchronous
response race condition with jtw backend
- Wire x_ocaml.ml to read backend attribute and dispatch through
Backend module (jtw or builtin)
- Add W.setup call after W.init in jtw_client to load stdlib
- Create rpc_worker.ml: full-featured JSON-RPC worker combining
the complete S module (findlibish, stdlib) with JSON-RPC protocol
Verified: code execution and Merlin type-on-hover both work in the
scrollycode playground overlay (hovering shows type tooltips like
"type bool = false | true").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add jtw_client.ml that bridges x-ocaml's X_protocol with js_top_worker's
JSON-RPC protocol. This enables using js_top_worker as an alternative
backend for code evaluation, type checking, and completion.
The bridge translates:
- Eval requests to W.exec RPC calls
- Complete_prefix/Type_enclosing/All_errors to corresponding W.* calls
- Merlin position types (polymorphic variants) to js_top_worker's
msource_position (regular variants)
- js_top_worker result types back to Protocol/X_protocol response types
Also add backend.ml abstraction layer that dispatches between the built-in
x-ocaml worker and the js_top_worker backend based on configuration.
Add vendored mime_printer library required by js_top_worker-rpc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- layer.mld: add Layer module, update description for new core type
- batch.mld: add Profile and Snapshot modules
- doc.mld: add Doc_build module
- generate.mli: add doc comment for run function
- patches.mli: new interface file for Patches module
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename `effect` variable to `eff` in editor.ml (reserved keyword in 5.4)
- Add `first_crc` cppo-guarded helper for cmi_crcs API difference
(Import_info.t array in oxcaml vs (string * Digest.t option) list in 5.4)
- Remove `source_rendering` from dune-workspace (not in upstream dune/odoc)
All extension packages, tessera, zarr, and odoc now build with both
oxcaml 5.2.0+ox and standard OCaml 5.4.1. The js_top_worker library
compiles on both; linking requires matching merlin-lib.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for "Position N is out of range for changeset" errors:
1. js_top_worker: clamp output_at loc to input length — the ";;"
appended for parsing made pos_cnum extend past the original source
2. x-ocaml/editor: clamp decoration positions against CM document
length (Text.length) not OCaml String.length — they differ for
non-ASCII text (UTF-16 vs bytes). Also combine doc replacement +
decoration clear into a single transaction in set_source, and
read actual CM doc in build_range_set
3. jsoo-code-mirror/decoration: defensive safe_map that catches
JS RangeError in RangeSet.map and returns empty
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Compartment+reconfigure approach caused "Position out of range for
changeset" RangeErrors because decorations weren't mapped through
document change transactions. A StateField maps decorations via
RangeSet.map(tr.changes) synchronously within each transaction,
keeping positions consistent with the document state.
- Add StateEffect and StateField OCaml bindings to jsoo-code-mirror
- Add Decoration.Range_set.empty and .map
- Convert editor.ml messages from Compartment to StateField+StateEffect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
set_source now reads back the document from CodeMirror's state
after setting it, rather than using the raw string from textContent.
This ensures current_doc matches CodeMirror's internal representation,
which may differ due to line ending normalization or HTML entity
handling.
The HTML textContent can be 2 characters longer than what CodeMirror
stores internally, causing RangeError when lint diagnostic positions
(computed relative to the raw string) exceed CodeMirror's doc length.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three changes to address CodeMirror RangeError and cm-gap issues:
1. jtw_client: Accumulate per-phrase output and emit as single
Top_response at cell end, instead of per-phrase Top_response_at.
Eliminates mid-document widget decorations that cause cm-gap
artifacts in CodeMirror's virtual rendering.
2. merlin_codemirror: Clamp lint diagnostic positions to current
document length. The doc can change between query send and
response arrival (e.g., formatter), making positions stale.
3. cell.ml: Skip formatter for run_on=load cells. The formatter
can change document length, creating changesets that invalidate
decoration positions from concurrent eval responses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
editor.ml: Clamp widget decoration positions to doc_len-1 to avoid
CodeMirror RangeError when mapping decorations through changesets.
Also catch exceptions in refresh_messages to prevent cascade failures
in cell execution. The root cause is a discrepancy between the cached
document length and CodeMirror's actual document — needs further
investigation.
interactive_map.mld: Fix forward reference to `map` in on_click
callback. Use a ref cell (map_ref) to break the self-referential
binding, since `let map = Leaflet_map.create ~on_click:(... map ...)`
is an unbound-value error in OCaml.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor x_ocaml.ml so that the Page/Backend are created lazily
and keyed on the current universe configuration (read from <meta>
tags). When a new <x-ocaml> element connects and the universe has
changed (e.g. after SPA navigation to a page with a different
@x-ocaml.universe), a fresh backend and page are created.
Previously, the backend was created once at script load time and
captured in the custom element's closure. SPA navigation would
update the <meta> tags (via the interactive extension's Js_inline
scripts) but the old backend/worker stayed alive, causing cells
on the new page to use the wrong universe's libraries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build-site.sh: Use dune-built worker.js (--no-worker + cp) so the
worker always matches the source tree. Previously jtw compiled against
the installed js_top_worker-web package via ocamlfind, which could be
stale. Also make @site and @doc steps fail on errors instead of
swallowing them.
js_top_worker: Add blank_directives to strip #require lines from source
before sending to merlin. The pre_source context prepends previous cells'
source (including #require directives) which causes Menhir's incremental
parser to throw force_reduction errors.
x-ocaml: Add bounds checking to Typed_enclosings response in
merlin_ext.ml, matching the existing pattern for Errors. Prevents
CodeMirror RangeError when merlin returns positions outside the cell's
document range.
odoc-mermaid-extension: Fix SPA navigation by replacing Js_inline init
script with Js_url support file using MutationObserver pattern. The old
startOnLoad:true approach only worked on full page loads; now mermaid.run()
is called explicitly whenever new pre.mermaid elements appear in the DOM.
site/dune.inc: Regenerated with --warn-error on odoc compile/link/
html-generate, and -L/-P flags for library/package cross-references
from @doc build output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New blog posts (monopam-madness, open-source-and-ai, weeknotes-2026-10),
notebook showcase with card layout and screenshots, Atom feed generator,
foundations notebook fixes, ONNX test improvements, widget interaction
tests, deploy script updates for oxcaml switch, and .gitignore for
build artifacts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of requiring every .mld file to specify @x-ocaml.universe and
@x-ocaml.worker tags individually, configure the default universe path
once in dune-workspace (--config x-ocaml.universe=/_opam). The shell
emits <meta> tags from config values, and per-page @x-ocaml tags can
still override them.
Changes:
- dune-workspace: add --config x-ocaml.universe=/_opam to html_flags
- gen_rules.ml: pass --config to odoc html-generate for @site build
- odoc_jon_shell.ml: emit <meta> tags from x-ocaml.* config values
- odoc generator.ml: pass config to shell page_creator
- interactive_extension.ml: upsert meta tags (update existing or create)
- x_ocaml.ml: infer jtw backend from x-ocaml-universe meta tag
- Remove @x-ocaml.universe/@x-ocaml.worker from 14 .mld files using
the default /_opam universe
- deploy-site.sh: add dune install x-ocaml, chmod fix, widget-leaflet
- findlibish.ml: module detection fallback via jsoo runtime
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add cppo preprocessing for merlin-js and x-ocaml workers to support
conditional compilation with OXCAML flag
- Guard day10 packages with enabled_if >= 5.3.0 since they need recent OCaml
- Remove fatal odoc warnings from dune-workspace (handled per-package now)
- Bump merlin-js dune lang to 3.17
- Add warning suppression flags where needed (-w -58, -w -67)
- Add interactive extension exercise pages (FOCS 2020/2024/2025, OxCaml
stack allocation)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add managed widget protocol (display_managed, command, register_adapter)
for external widget adapters like Leaflet maps
- Extract Leaflet map adapter into separate js_top_worker-widget-leaflet
library, loadable via #require
- Add widget rendering to x-ocaml web component (widget_render.ml) so
widgets work in .mld documentation pages
- Wire widget callbacks through OCaml client (js_top_worker_client_msg.ml)
- Complete the preloaded list in findlibish.ml to match all transitive
worker dependencies, preventing "file already exists" errors when
#require loads packages already compiled into the worker
- Add demo_widgets.mld and demo_map.mld documentation pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three features for x-ocaml interactive cells:
1. Worker busy indicator + stop/reset: When a cell is running (e.g. infinite
loop), the run button transforms into a spinning stop button. Clicking it
terminates the web worker, creates a fresh one, replays init messages, and
resets all cells to Not_run. Both builtin and jtw worker backends support
reset.
2. localStorage persistence: Exercise cells with data-id save their source
to localStorage (debounced 300ms). On reload, saved source is restored.
A small Reset button (bottom-left) appears when saved state exists,
allowing revert to the original HTML source.
3. Colab-style circular run/stop button: Replaces the old boxy text button
with a filled circle + white play triangle (CSS mask + ::after pseudo-
element). Running state shows a spinning arc with red accent and stop
square. Test cells get collapsible details with pass/fail/error indicators.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix sidebar kindBadge: map leaf-page→pg, module-type→MT, etc. (was
showing "L" for leaf-page entries)
- Stop escaping sidebar content HTML (entries contain <code> tags)
- Fix inline sidebar JSON: use Html.cdata_script instead of Html.txt
to prevent HTML-escaping inside <script> tags
- Add --xo-* CSS custom properties for x-ocaml cells in light/dark themes
- Fix support file registration: use 'opam var share' instead of
'opam var x-ocaml:share' (works without x-ocaml being an opam package)
- Suppress empty <li> from config-only extension tags (@x-ocaml.*)
- Add worker_url field to jtw opam findlib_index.json
- Refactor x-ocaml.js worker URL discovery to support both direct
worker_url and day10-style version/content_hash paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inline script loader hardcoded ./_x-ocaml/x-ocaml.js relative to the
page, but support files live under odoc.support/ at the doc root. Switch
to Js_url which odoc resolves against support_uri (like odoc.css). The
backend config is now communicated via a <meta> tag instead of a script
attribute.
Also: add x-ocaml dependency on merlin-js, add odoc-interactive-extension
dependency on x-ocaml, and remove unused js_top_worker-rpc.message dep.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- findlibish.mli: use Js_top_worker.Impl.dynamic_cmis (RPC module removed)
- worker.ml: use Impl.dcs_url instead of Toplevel_api_gen.dcs_url
- x-ocaml/src/jtw_client: convert to cppo for OxCaml-only
ppx_template_generated field in Query_protocol.Compl
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Consolidate js_top_worker and odoc dual-compiler stanzas into single
library stanzas with cppo rules generating impl.ml from impl.cppo.ml
- Per-package findlib_index.json with relative universe paths (../dep)
and implicit stdlib dependency injection
- Add find_stdlib_dcs to Impl.S interface for stdlib CMI lookup via
findlib metadata instead of hardcoded URLs
- Replace jsoo Json.output/Json.unsafe_input with plain JSON.stringify/
JSON.parse for cross-jsoo-version compatibility (6.0.1+ox vs 6.2.0)
- Cross-origin worker support: set __global_rel_url in blob worker,
skip URL rewriting for absolute http(s) URLs
- Fix odoc doc comments and ocamlformat-ignore for cppo files
- Add demo docs, helper scripts, and x-ocaml package-lock.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a findlib_index.json contains a "compiler" field with version
and content_hash, x-ocaml now constructs the worker URL automatically.
This means .mld files only need @x-ocaml.universe — no separate
@x-ocaml.worker tag is required. The explicit x-ocaml-worker meta
tag still takes priority when present.
Uses a synchronous XHR pre-fetch of the findlib_index.json (~200 bytes)
before creating the worker, with browser JSON.parse for extraction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enable loading .cma.js files from a different origin (e.g., ocaml.org)
by working around Chrome's CORB which blocks cross-origin importScripts
for files with embedded binary CMI data.
worker.ml: detect cross-origin URLs in import_scripts and use
synchronous XHR + eval() instead of importScripts. Same-origin
URLs continue to use the standard path.
jtw_client.ml: create blob: URL wrapper for cross-origin worker.js
(browsers block cross-origin Worker construction), derive stdlib_dcs
URL from findlib_index base, switch to eval_stream for streaming
phrase-by-phrase output.
interactive_extension.ml: always load x-ocaml.js and worker.js from
local _x-ocaml/ path (same-origin), communicate universe URL via
<meta> tag. Set backend to "jtw" when a universe is configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add --copy-file flag to jtw opam/opam-all for including x-ocaml.js
and other assets in universe output
- Pass findlib_requires and findlib_index from meta tags through to
the JTW worker init, so @x-ocaml.requires packages are loaded
during setup
- x_ocaml.ml reads <meta name="x-ocaml-packages"> and
<meta name="x-ocaml-universe"> before creating the backend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Add_cmis to X_protocol.request so pages can register external
package CMIs with the merlin worker. Page.create reads
<meta name="x-ocaml-packages" content="pkg1,pkg2,..."> and optional
<meta name="x-ocaml-cmis-url" content="./cmis/"> from the document
head. For each package, dynamic CMIs are registered so merlin can
provide completions, type-on-hover, and error checking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract page-level orchestration into a new Page module, separating
cell registry, test linking, message routing, and auto-run from the
WebComponent definition in x_ocaml.ml.
Key changes:
- Page module: cell registry, test linking (positional + data-for),
backend message routing, universe discovery stub, format config
- Cell: add ?merlin param, set_on_completed hook, has_completed,
string_of_mode, cell_for/cell_env accessors
- Editor: improved docstrings
- x_ocaml.ml: simplified to thin WebComponent + Page delegation
- Playwright browser tests: 17 assertions across 7 test scenarios
covering hidden/interactive/exercise/test modes, read-only state,
default mode, merlin disable, and full assessment chain
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add mode type and data attributes (data-id, data-for, data-env) to cells
- Hidden cells: no editor/UI, still execute in linked list chain
- Exercise cells: editable (default CodeMirror behaviour)
- Interactive/Test cells: read-only via EditorState.readOnly facet
- Read mode attribute from <x-ocaml mode="..."> elements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three-layer architecture separating content, theme, and renderer:
Phase 1 — Scrollycode refactor:
- Strip ~1030 lines of embedded theme CSS from scrollycode_extension.ml
- Create scrollycode_css.ml with structural CSS using CSS custom properties
- Create scrollycode_themes.ml with three theme files (warm/dark/notebook)
registered as support files
- Simplify @scrolly tag: theme suffix now ignored (CSS concern, not content)
- Playground overlay styled via --xo-* custom properties on x-ocaml element
Phase 2 — Extra CSS support:
- Add --extra-css flag to odoc html-generate for injecting additional
<link> tags (used for per-page theme selection)
Phase 3 — Shell plugin system:
- Add Html_shell module with Shell interface and hashtable registry
- Register "default" shell in html_page.ml, "json" shell in
html_fragment_json.ml
- Replace hardcoded if/else in generator.ml with shell registry lookup
- Add --shell NAME flag (--as-json kept as backward-compat alias)
x-ocaml theming:
- Replace hardcoded colors in style.css with var(--xo-*, fallback)
covering editor, gutter, buttons, tooltips, and output areas
- CSS custom properties inherit through shadow DOM boundary, so
consumers theme x-ocaml with pure CSS — no JS injection needed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add comprehensive JS stubs for OxCaml-specific primitives (domain TLS,
arch detection, blocking sync, basement/capsule) needed by the OxCaml
js_of_ocaml runtime
- Fix demo.js to load rpc_worker.bc.js (JSON-RPC) instead of the
non-RPC _opam/worker.js, fixing a protocol mismatch bug
- Fix exec RPC method never resolving: rename phrase_p parameter from
~name:"string" to unnamed, preventing rpclib from incorrectly treating
the phrase as a named parameter in the dispatch dict
- Remove unused cbort, zarith, bytesrw dependencies from opam and
dune-project
- Bump dune version in x-ocaml opam files to 3.21 for OxCaml support
- Update cram test expected output for current package list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add support for building js_top_worker and odoc with both the standard
OCaml 5.4 compiler and the OxCaml 5.2.0+ox compiler using cppo
conditional compilation and dual dune stanzas.
js_top_worker changes:
- Bump dune-project to 3.21 for %{ocaml-config:ox} support
- Add dual library stanzas gated by (enabled_if %{ocaml-config:ox})
- Add cppo guards for OxCaml API differences:
- Compilation_unit.Name.t vs string for persistent loader
- Env.report_error ~level:0 (extra parameter)
- Language_extension.set_universe_and_enable_all (oxcaml-only)
- Unit_info.make ~for_pack_prefix (extra parameter)
- Typemod.type_implementation (extra Compilation_unit arg)
- Gate ppx_deriving_rpc with (not %{ocaml-config:ox})
odoc changes:
- Apply upstream oxcaml PR #1399 (art-w/upstream-oxcaml)
- Bump dune-project to 3.21
- Add dual stanzas in loader, model, xref2, odoc, syntax_highlighter
- Add cppo OXCAML guards for compiler API differences
- Support OxCaml features: modes, layouts, labeled tuples, iarray,
unboxed records, module type strengthening, polymorphic arguments,
call position arguments, Import_info.t, Compilation_unit.t
Verified end-to-end: scrollycode demos generate HTML and the
interactive playground evaluates OCaml code in the browser with
both compiler switches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix client.ml absolute_url to handle root-relative paths (/ prefix)
using window.location.origin instead of page directory path
- Abstract merlin_ext.ml to use a post function closure instead of
concrete Client.t, decoupling from specific backend
- Update cell.ml/mli to accept eval/fmt/post function closures
instead of Client.t, and split init/start to avoid synchronous
response race condition with jtw backend
- Wire x_ocaml.ml to read backend attribute and dispatch through
Backend module (jtw or builtin)
- Add W.setup call after W.init in jtw_client to load stdlib
- Create rpc_worker.ml: full-featured JSON-RPC worker combining
the complete S module (findlibish, stdlib) with JSON-RPC protocol
Verified: code execution and Merlin type-on-hover both work in the
scrollycode playground overlay (hovering shows type tooltips like
"type bool = false | true").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add jtw_client.ml that bridges x-ocaml's X_protocol with js_top_worker's
JSON-RPC protocol. This enables using js_top_worker as an alternative
backend for code evaluation, type checking, and completion.
The bridge translates:
- Eval requests to W.exec RPC calls
- Complete_prefix/Type_enclosing/All_errors to corresponding W.* calls
- Merlin position types (polymorphic variants) to js_top_worker's
msource_position (regular variants)
- js_top_worker result types back to Protocol/X_protocol response types
Also add backend.ml abstraction layer that dispatches between the built-in
x-ocaml worker and the js_top_worker backend based on configuration.
Add vendored mime_printer library required by js_top_worker-rpc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>