OCaml Weekly News

Previous Week Up Next Week

Hello

Here is the latest OCaml Weekly News, for the week of December 02 to 09, 2025.

Table of Contents

editors dev-meeting: 15th of December about the ocaml.nvim plugin

Charlène_Gros announced

Hi!

We are organizing the next public dev meeting about editors for OCaml!

It will take place on December 15th at 5 pm.

The focus will be on the new NeoVim plugin for OCaml, which adds extra features to the classical lspconfig for standard LSP support, to provide all of the Merlin features in Neovim.

We will explore why having this type of plugin is beneficial for maintaining OCaml editor tools, and we will dive into the codebase to understand how it works and see what happens in Neovim.

For the agenda:

  • A tour-de-table to allow the participants who wish to do so to present themselves and mention issues / PRs they are interested in.
  • Talk and Q&A
  • Discuss issues and pull requests that were tagged in advance or mentioned during the tour-de-table.

Feel free to join!

Meeting link: https://meet.google.com/qxr-zxge-fvg

Mosaic - A Modern Terminal User Interface Framework for OCaml (Early Preview)

Thibaut Mattio announced

I'm excited to share an early preview of Mosaic, a modern terminal user interface framework for OCaml.

What is Mosaic?

Mosaic is a high-level framework for building terminal user interfaces in OCaml. It provides a TEA (The Elm Architecture) runtime with a CSS-compatible flexbox layout engine and a rich set of composable components. It handles text shaping (styling, wrapping, selection), focus and event bubbling, z-ordering, and responsive layout.

Under the hood, it builds on two libraries that can also be used independently:

  • Matrix: a terminal runtime focused on performance and modern protocols. Highlights: near-zero-allocation diffed rendering, immediate- mode API, Kitty keyboard, SGR/URXVT/X10 mouse, bracketed paste, focus tracking, inline/alt/split display modes, and built-in debug overlay/frame dumps. It also provides a virtual terminal emulator (VTE) and pseudo-terminal (PTY) management subsystems.
  • Toffee: a CSS-compatible layout engine. It's a port of Rust’s Taffy with Flexbox, CSS Grid, and Block layout; gap/padding/borders/titles; and layout caching for efficiency.

Why Mosaic?

Terminal UIs are seeing a renaissance. Tools like Claude Code and OpenCode have gotten people excited about what can be built in the terminal and the TUI community is gaining momentum in other ecosystems.

OCaml has had LambdaTerm and Notty for terminal graphics for years, but there's been a gap when it comes to performance and high-level abstractions for building complex UIs.

Mosaic aims to fill that gap by providing Matrix as a solid terminal foundation, and building a high-level TEA framework with layout and components on top.

On a personal side, I'm building Mosaic to power the two projects I'm currently working on:

  • It will be the basis for a TUI dashboard for monitoring model training in Raven. We're starting an Outreachy internship to build this out this Monday.
  • It powers Spice, the upcoming OCaml coding agent I announced at FunOCaml 2025.

Try It Now

The libraries aren't on opam yet, but you can try them today:

Option 1: Pin from GitHub

opam pin add https://github.com/tmattio/mosaic.git

Option 2: Build from source

git clone https://github.com/tmattio/mosaic
cd mosaic
opam install . --deps-only
dune build

Then run some examples:

# Interactive Game of Life (ported from Notty examples)
dune exec ./matrix/examples/02-life/main.exe

# Particles simulation with multiple display modes
dune exec ./matrix/examples/14-particles/main.exe

# High-level TEA counter
dune exec ./mosaic/examples/01-counter/main.exe

Have a look at the examples directories (Matrix and Mosaic) for more demos to explore!

As a bonus, we also have more complete demos for both projects:

Quick Examples

  • Mosaic: The Elm Architecture

    Mosaic follows TEA for building declarative UIs:

    open Mosaic_tea
    
    type msg = Increment | Decrement | Quit
    
    let init () = (0, Cmd.none)
    
    let update msg model =
      match msg with
      | Increment -> (model + 1, Cmd.none)
      | Decrement -> (model - 1, Cmd.none)
      | Quit -> (model, Cmd.quit)
    
    let view model =
      box ~align_items:Center ~justify_content:Center
        ~size:{ width = pct 100; height = pct 100 }
        [
          box ~flex_direction:Column ~align_items:Center ~gap:(gap 1)
            ~border:true ~padding:(padding 2) ~title:"Counter"
            [
              text ~content:(Printf.sprintf "Count: %d" model) ();
              text ~content:"Press + or - to change, q to quit" ();
            ];
        ]
    
    let subscriptions _model =
      Sub.on_key (fun ev ->
          match (Mosaic_ui.Event.Key.data ev).key with
          | Char c when Uchar.equal c (Uchar.of_char '+') -> Some Increment
          | Char c when Uchar.equal c (Uchar.of_char '-') -> Some Decrement
          | Char c when Uchar.equal c (Uchar.of_char 'q') -> Some Quit
          | Escape -> Some Quit
          | _ -> None)
    
    let () = run { init; update; view; subscriptions }
    
  • Matrix: Low-Level Power

    For direct terminal control, Matrix provides an immediate-mode API:

    open Matrix
    
    let () =
      run
        ~on_render:(fun _ctx ->
          Image.(
            string "Hello from Matrix!" |> bg Color.blue |> pad ~l:2 ~t:1))
        ~on_input:(fun ctx -> function
          | Key { key = Escape; _ } -> quit ctx
          | _ -> ())
        ()
    

Coming Soon: TUI for ML Training

We're starting an Outreachy internship to build a TUI for monitoring model training with Raven, the scientific computing ecosystem for OCaml. It will provide a TensorBoard experience in the terminal, built entirely with Mosaic.

A good example of what we're aiming to build is Wandb's newly released TUI:

1a0303418fba64bb4a168e4ae1488592d3cb96c0_2_1380x814.jpeg

Mosaic vs Notty

Notty is the current go-to terminal UI library for OCaml, with a well-designed declarative image API. Mosaic sits a level above: it’s a TEA runtime with flexbox layout, rich components, focus/event bubbling, and diffed rendering via Matrix. In scope, Notty is closer to Matrix (the terminal infrastructure under Mosaic) than to Mosaic itself.

Matrix covers the low-level rendering, modern terminal protocols, and immediate-mode API that Notty doesn’t. For a detailed Matrix vs Notty comparison, see our comparison table.

Acknowledgements

Mosaic stands on the shoulders of great work:

  • Bubble Tea - inspiration for the high-level TEA runtime and app structure.
  • Notty - Matrix's declarative Image API is directly copied from Notty's to provide a familiar interface for OCaml users.
  • OpenTUI - the biggest influence on Mosaic UI internals (render tree, text buffer, events, selection). Mosaic's UI internals have been rewritten to mirror OpenTUI's following its release, if you're working in TypeScript, I can't recommend it enough, it's a fantastic piece of engineering.
  • Rich and Textual - for ideas on rich text, diagnostics, and polished terminal UX.

Feedback Welcome

This is an early preview. APIs are stabilizing but may change. I'd love your feedback on:

  • API ergonomics
  • Missing components or features you need
  • Performance on your terminal
  • Bugs (please open issues!)

Give it a try and let me know what you think!

Making html_of_jsx ~10x faster

David Sancho announced

Hello ocamlers,

I wrote a blog post about some optimizations I made in html_of_jsx

https://sancho.dev/blog/making-html-of-jsx-10x-faster

I wanted to share it since those tricks are only possible at the preprocess stage, and a library would fall short. html_of_jsx is a small library that is used in a few places, but also a playground for server-reason-react (a bigger project).

Anyway, I'm still getting used to share some of the long forms of content, so feel free to give me some feedback/corrections or ask any question.

Tyre - type-safe regular expressions 1.0

Emile Trotignon announced

I am happy to announce the release of Tyre 1.0.

This release makes big changes to the typing: there is a whole host of far more practical combinators allowing you to write code like in a parser combinator library :

let url =
  let+ scheme = opt (const Http (str "http") <|> const Https (str "http") <* str "://") 
  and+ host = rep_any
  and+ port = opt (str ":" *> pos_int)
  and+ path = list (str "/" *> rep_any)
  in
  (scheme, host, port, path)

(this is not a correct url parser by any means, its just to give an idea of how you can use this lib)

This does not allow you to use eval, because only one direction of the conversion is given (there is no code to convert from a url tuple). However in the cases where you don't need eval this is far easier to write. The difference is typed, you can't get runtime error by calling eval on the wrong regex.

There are also convencience changes such as charset and the matched_string function, that reduce the need to insert Re bits in your regexp.

Here is the full changelog:

  • Introduce charsets: contrary to Re, they have a different type from regex.
  • Type the difference between regexps that can be evaluated reversed and the ones that cannot: (evaluable, 'a) Tyre.t and (non_evaluable, 'a) Tyre.t.
  • Introduce alias type pattern for (non_evaluable, 'a) Tyre.t.
  • Introduce val lift : ('a -> string) -> 'a pattern -> ('e, 'a) t to transform a pattern into an expression by giving an explicit conversion function. Also liftpp that does the same with better performance by using Format.
  • Introduce val unlift : (evaluable, 'a) t -> 'a pattern.
  • Introduce val either: ('e, 'a) Tyre.t -> ('e, 'b) Tyre.t -> ('e, ('a, 'b) Either.t) Tyre.t.
  • Change the type of alt to (_, 'a) t -> (_, 'a) t -> 'a pattern. Previous users of alt should switch to either.
  • Introduce ~val alt_eval: ('a -> [~Left | ~Right]) -> ('e, 'a) t -> ('e, 'a) t -> ('e, 'a) t~ This has flat typing but is compatible with eval.
  • Operators: <|> is alt, <||> is either.
  • Introduce val map : ('a -> 'b) -> (_, 'a) t -> 'b pattern and its corresponding operators: let+ and <$>.
  • Introduce (and+) which is an alias of seq.
  • Introduce val app: ('e, 'a -> 'b) t -> ('e, 'a) t -> 'b pattern and its corresponding operator <*>
  • Introduce val matched_string : (_, 'a) t -> (_, string) t that discards the computed value and just return the string that was matched.
  • Drop dependency on Result library. Stdlib is now used.
  • Introduce val rep_charset: Charset.t -> (_, string) t, and shortcut val rep_any: (_, string) t.

Porting an OxCaml Project to Dune Package Management

Sudha Parimala announced

I recently worked on porting the OxCaml tutorial to build with Dune Package Management. What was initially anticipated as a straightforward change ended up uncovering a few issues and requiring several patches. I wrote about the experience here: https://tensors.ink/posts/oxcaml-dune-pkg. Any questions or feedback welcome!

Other OCaml News

Old CWN

If you happen to miss a CWN, you can send me a message and I'll mail it to you, or go take a look at the archive or the RSS feed of the archives.

If you also wish to receive it every week by mail, you may subscribe to the caml-list.