Skip to content

Interactive Apps

This guide is a live demo of an Elm-style OCaml app compiled to JavaScript and rendering into a web terminal. This guide is for advanced users who want to build their own interactive web applications using dk-based tooling.

The two cells on this page define the Elm-style app; editing and re-running them changes what the terminal (the third cell) shows. The terminal is a ghostty-web VT100 emulator.

This cell defines the Canadian Cabinet data and the animation loop used to drive the app. It can be edited (just expand the Command).

Command — 1187 lines (click to expand)
module Samples = struct
open MlFront_Progress
(** Reusable progress reporting sample data and drivers.
These are shared by demos and tests (for example the DkZero_RuntimeC
SyntheticDemo test and the DkZero_Web browser terminal) so most of the
progress tree animation lives in one place. *)
(** A synthetic "Canadian Cabinet" hierarchy (sovereign -> governor general /
prime minister -> ministers -> deputy ministers) animated month by month
over a {!MlFront_Progress.Progress} tree.
The driver is backend- and pacing-agnostic: {!run} mutates the supplied root
node's subtree once per simulated month and then calls [tick], leaving the
caller to flush, sleep, or capture a frame. *)
module CanadianCabinet = struct
(* ===================================================================== *)
(* Data *)
(* ===================================================================== *)
type 'a title =
| Queen : [ `Sovereign ] title
| King : [ `Sovereign ] title
| GovernorGeneral : [ `First ] title
| PrimeMinister : [ `First ] title
| DeputyPrimeMinister : [ `Second ] title
| MinisterOfFinance : [ `Second ] title
| MinisterOfForeignAffairs : [ `Second ] title
| DeputyMinisterOfFinance : [ `Third ] title
| DeputyMinisterOfForeignAffairs : [ `Third ] title
let rank_title : type a. a title -> int = function
| Queen -> 1
| King -> 2
| GovernorGeneral -> 3
| PrimeMinister -> 4
| DeputyPrimeMinister -> 5
| MinisterOfFinance -> 6
| MinisterOfForeignAffairs -> 7
| DeputyMinisterOfFinance -> 8
| DeputyMinisterOfForeignAffairs -> 9
let show_title : type a. a title -> string = function
| King -> "King"
| Queen -> "Queen"
| GovernorGeneral -> "Governor General"
| PrimeMinister -> "Prime Minister"
| DeputyPrimeMinister -> "Deputy Prime Minister"
| MinisterOfFinance -> "Finance Minister"
| MinisterOfForeignAffairs -> "Foreign Affairs Minister"
| DeputyMinisterOfFinance -> "Deputy Finance Minister"
| DeputyMinisterOfForeignAffairs -> "Deputy Foreign Affairs Minister"
let _ = compare
let compare_title a b = compare (rank_title a) (rank_title b)
let _ = compare_title
type hierarchy =
[ `Sovereign ] title option
* [ `First ] title option
* [ `Second ] title option
* [ `Third ] title option
let show_hierarchy (s, l1, l2, l3) =
let show' n = function
| None -> ""
| Some t ->
let prefix = if n > 1 then " -> " else "" in
prefix ^ show_title t
in
Printf.sprintf "%s%s%s%s" (show' 1 s) (show' 2 l1) (show' 3 l2) (show' 4 l3)
let is_sovereign = function
| Some (King | Queen), None, None, None -> true
| _ -> false
let rank_hierarchy_max = 4
let rank_hierarchy = function
| Some _, Some _, Some _, Some _ -> (4, `Third)
| Some _, Some _, Some _, _ -> (3, `Second)
| Some _, Some _, _, _ -> (2, `First)
| Some _, _, _, _ -> (1, `Sovereign)
| _, _, _, _ -> (0, `None)
let title_hierarchy = function
| Some s, None, None, None -> [ rank_title s ]
| Some s, Some l1, None, None -> [ rank_title s; rank_title l1 ]
| Some s, Some l1, Some l2, None ->
[ rank_title s; rank_title l1; rank_title l2 ]
| Some s, Some l1, Some l2, Some l3 ->
[ rank_title s; rank_title l1; rank_title l2; rank_title l3 ]
| None, None, None, None -> []
| a_s, a_1, a_2, a_3 ->
Printf.eprintf "non-contiguous hierarchy: %s\n"
(show_hierarchy (a_s, a_1, a_2, a_3));
assert false
let compare_hierarchy a b =
let a' = title_hierarchy a in
let b' = title_hierarchy b in
List.compare Int.compare a' b'
let parent_hierarchy = function
| Some s, Some l1, Some l2, Some _l3 -> (Some s, Some l1, Some l2, None)
| Some s, Some l1, Some _l2, None -> (Some s, Some l1, None, None)
| Some s, Some _l1, None, None -> (Some s, None, None, None)
| Some _s, None, None, None -> (None, None, None, None)
| _, _, _, _ -> (None, None, None, None)
type service = Service : { title : 'a title } -> service
type t = {
name : string;
service : service;
startyymm : int * int;
endyymm : int * int;
hierarchy : hierarchy;
}
(** Functions and structures to provide a search structure for the raw data.
*)
module Denorm = struct
type nonrec normalized = t
type serving = { monthno : int; totalmonths : int }
type t = {
dname : string;
dtitle : string;
dyymm : int * int;
dstate : [ `EaseIn of int | `Serving of serving | `EaseOut of int ];
dhierarchy : hierarchy;
dmons : int;
}
(** A time-varying denormalized record. EaseIn and EaseOut are the number of
months remaining and since serving, respectively. *)
(** This is a search structure ["SELECT * FROM denorm WHERE dyymm = ?"]. *)
module YymmMap = Map.Make (struct
type t = int * int
let compare = compare
end)
(* A verified assumption that the hierarchy is sorted with the sovereign first *)
let assume_sorted_by_hierarchy_with_soverign_first () =
let innercmp = compare_hierarchy in
let cmp = fun a b -> innercmp a.dhierarchy b.dhierarchy in
(* test the [innercmp] *)
let a = (Some King, None, None, None) in
let b = (Some Queen, None, None, None) in
let c = (Some Queen, Some GovernorGeneral, None, None) in
let d =
( Some Queen,
Some PrimeMinister,
Some MinisterOfFinance,
Some DeputyMinisterOfFinance )
in
let e =
(Some Queen, Some PrimeMinister, Some DeputyPrimeMinister, None)
in
let testdata = [ a; b; c; d; e ] in
let sorted = List.sort innercmp testdata in
let expected = [ b; c; e; d; a ] in
if sorted <> expected then (
List.iter
(fun h -> Printf.eprintf "unverified: %s\n" (show_hierarchy h))
sorted;
assert false);
(`SamplesVerified, cmp)
let clamplo ~min x = if x < min then min else x
let clamphi ~max x = if x > max then max else x
let tomon (yy, mm) = (yy * 12) + mm
let toyymm mon = ((mon - 1) / 12, 1 + ((mon - 1) mod 12))
let denormalize ~easing_months ~firstyymm ~lastyymm (orig : normalized list)
: t list YymmMap.t =
let firstmon = tomon firstyymm in
let lastmon = tomon lastyymm in
let denormed_records : t list =
List.concat_map
(fun {
name;
service = Service { title };
startyymm;
endyymm;
hierarchy;
} ->
let startmon = clamplo ~min:firstmon (tomon startyymm) in
let endmon = clamphi ~max:lastmon (tomon endyymm) in
let easemon1 = clamplo ~min:(startmon - easing_months) startmon in
let easemon2 = clamphi ~max:(endmon + easing_months) endmon in
let easedurmon = 1 + easemon2 - easemon1 in
let rec aux curmon acc =
if curmon > endmon + easing_months then List.rev acc
else
let current_yymm = toyymm curmon in
let state =
if curmon < startmon then `EaseIn (startmon - curmon)
else if curmon > endmon then `EaseOut (curmon - endmon)
else
`Serving
{
monthno = curmon - startmon + 1;
totalmonths = easedurmon;
}
in
aux (curmon + 1)
({
dname = name;
dtitle = show_title title;
dyymm = current_yymm;
dstate = state;
dhierarchy = hierarchy;
dmons = easedurmon;
}
:: acc)
in
aux (startmon - easing_months) [])
orig
in
(* Must be sorted by hierarchy for a sane display *)
let denormed_records =
let _, cmp = assume_sorted_by_hierarchy_with_soverign_first () in
List.sort cmp denormed_records
in
(* Partition [t list] into YymmMap map of dyymm -> t.
Maintain correct order with fold_right. *)
List.fold_right
(fun record m ->
YymmMap.update record.dyymm
(function
| None -> Some [ record ]
| Some records -> Some (record :: records))
m)
denormed_records YymmMap.empty
end
let data : t list =
(*
Harness: GitHub Copilot v1.0.40
LLM: GPT 5.4 mini
Caution:
The periods of service may overlap! Perhaps change the prompt to
avoid the overlap, which messes with the counters.
Prompt:
Populate the `data` with a list of Canadian government
officials from 1975 - 2025. If someone has discontinuous period of time serving
in the Cabinet, make separate records.
The King and Queen have hierarchy `` (Some King, None, None, ...) `` and ``
(Some Queen, None, None, ...) ``, respectively. The Governor General and Prime
Minister are under the King and Queen; if they served under Queen Elizabeth it
would be `` (Some Queen, Some GovernorGeneral, None, ...) `` and
`` (Some Queen, Some PrimeMinister, None, ...) ``.
Include all levels down to deputy ministers.
Keep the comment you see containing the "Prompt".
*)
[
{
name = "Queen Elizabeth II";
service = Service { title = Queen };
startyymm = (1975, 1);
endyymm = (2022, 9);
hierarchy = (Some Queen, None, None, None);
};
{
name = "King Charles III";
service = Service { title = King };
startyymm = (2022, 9);
endyymm = (2025, 12);
hierarchy = (Some King, None, None, None);
};
{
name = "Jules Leger";
service = Service { title = GovernorGeneral };
startyymm = (1975, 1);
endyymm = (1979, 1);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Edward Schreyer";
service = Service { title = GovernorGeneral };
startyymm = (1979, 1);
endyymm = (1984, 6);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Jeanne Sauve";
service = Service { title = GovernorGeneral };
startyymm = (1984, 6);
endyymm = (1990, 1);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Ray Hnatyshyn";
service = Service { title = GovernorGeneral };
startyymm = (1990, 1);
endyymm = (1995, 2);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Romeo LeBlanc";
service = Service { title = GovernorGeneral };
startyymm = (1995, 2);
endyymm = (1999, 1);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Adrienne Clarkson";
service = Service { title = GovernorGeneral };
startyymm = (1999, 1);
endyymm = (2005, 9);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Michaelle Jean";
service = Service { title = GovernorGeneral };
startyymm = (2005, 9);
endyymm = (2010, 10);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "David Johnston";
service = Service { title = GovernorGeneral };
startyymm = (2010, 10);
endyymm = (2017, 10);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Julie Payette";
service = Service { title = GovernorGeneral };
startyymm = (2017, 10);
endyymm = (2021, 7);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Mary Simon";
service = Service { title = GovernorGeneral };
startyymm = (2021, 7);
endyymm = (2022, 9);
hierarchy = (Some Queen, Some GovernorGeneral, None, None);
};
{
name = "Mary Simon";
service = Service { title = GovernorGeneral };
startyymm = (2022, 9);
endyymm = (2025, 12);
hierarchy = (Some King, Some GovernorGeneral, None, None);
};
{
name = "Pierre Trudeau";
service = Service { title = PrimeMinister };
startyymm = (1975, 1);
endyymm = (1979, 3);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Joe Clark";
service = Service { title = PrimeMinister };
startyymm = (1979, 6);
endyymm = (1980, 3);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Pierre Trudeau";
service = Service { title = PrimeMinister };
startyymm = (1980, 3);
endyymm = (1984, 6);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "John Turner";
service = Service { title = PrimeMinister };
startyymm = (1984, 6);
endyymm = (1984, 9);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Brian Mulroney";
service = Service { title = PrimeMinister };
startyymm = (1984, 9);
endyymm = (1993, 6);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Kim Campbell";
service = Service { title = PrimeMinister };
startyymm = (1993, 6);
endyymm = (1993, 11);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Jean Chretien";
service = Service { title = PrimeMinister };
startyymm = (1993, 11);
endyymm = (2003, 12);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Paul Martin";
service = Service { title = PrimeMinister };
startyymm = (2003, 12);
endyymm = (2006, 2);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Stephen Harper";
service = Service { title = PrimeMinister };
startyymm = (2006, 2);
endyymm = (2015, 11);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Justin Trudeau";
service = Service { title = PrimeMinister };
startyymm = (2015, 11);
endyymm = (2022, 9);
hierarchy = (Some Queen, Some PrimeMinister, None, None);
};
{
name = "Justin Trudeau";
service = Service { title = PrimeMinister };
startyymm = (2022, 9);
endyymm = (2025, 12);
hierarchy = (Some King, Some PrimeMinister, None, None);
};
{
name = "Allan MacEachen";
service = Service { title = DeputyPrimeMinister };
startyymm = (1977, 8);
endyymm = (1979, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some DeputyPrimeMinister, None);
};
{
name = "Erik Nielsen";
service = Service { title = DeputyPrimeMinister };
startyymm = (1984, 9);
endyymm = (1986, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some DeputyPrimeMinister, None);
};
{
name = "Don Mazankowski";
service = Service { title = DeputyPrimeMinister };
startyymm = (1993, 1);
endyymm = (1997, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some DeputyPrimeMinister, None);
};
{
name = "Herb Gray";
service = Service { title = DeputyPrimeMinister };
startyymm = (1997, 6);
endyymm = (2002, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some DeputyPrimeMinister, None);
};
{
name = "Anne McLellan";
service = Service { title = DeputyPrimeMinister };
startyymm = (2006, 1);
endyymm = (2007, 8);
hierarchy = (Some Queen, Some PrimeMinister, Some DeputyPrimeMinister, None);
};
{
name = "Chrystia Freeland";
service = Service { title = DeputyPrimeMinister };
startyymm = (2019, 11);
endyymm = (2022, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some DeputyPrimeMinister, None);
};
{
name = "Chrystia Freeland";
service = Service { title = DeputyPrimeMinister };
startyymm = (2022, 9);
endyymm = (2024, 12);
hierarchy = (Some King, Some PrimeMinister, Some DeputyPrimeMinister, None);
};
{
name = "Donald Macdonald";
service = Service { title = MinisterOfFinance };
startyymm = (1975, 1);
endyymm = (1976, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Jean Chretien";
service = Service { title = MinisterOfFinance };
startyymm = (1976, 9);
endyymm = (1979, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "John Crosbie";
service = Service { title = MinisterOfFinance };
startyymm = (1979, 6);
endyymm = (1980, 3);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Allan MacEachen";
service = Service { title = MinisterOfFinance };
startyymm = (1980, 3);
endyymm = (1984, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Michael Wilson";
service = Service { title = MinisterOfFinance };
startyymm = (1984, 9);
endyymm = (1991, 4);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Don Mazankowski";
service = Service { title = MinisterOfFinance };
startyymm = (1991, 4);
endyymm = (1993, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Paul Martin";
service = Service { title = MinisterOfFinance };
startyymm = (1993, 1);
endyymm = (2002, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "John Manley";
service = Service { title = MinisterOfFinance };
startyymm = (2002, 6);
endyymm = (2003, 12);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Ralph Goodale";
service = Service { title = MinisterOfFinance };
startyymm = (2003, 12);
endyymm = (2006, 2);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Jim Flaherty";
service = Service { title = MinisterOfFinance };
startyymm = (2006, 2);
endyymm = (2014, 3);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Joe Oliver";
service = Service { title = MinisterOfFinance };
startyymm = (2014, 3);
endyymm = (2015, 11);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Bill Morneau";
service = Service { title = MinisterOfFinance };
startyymm = (2015, 11);
endyymm = (2020, 8);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Chrystia Freeland";
service = Service { title = MinisterOfFinance };
startyymm = (2020, 8);
endyymm = (2022, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Chrystia Freeland";
service = Service { title = MinisterOfFinance };
startyymm = (2022, 9);
endyymm = (2024, 12);
hierarchy = (Some King, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Dominic LeBlanc";
service = Service { title = MinisterOfFinance };
startyymm = (2024, 12);
endyymm = (2025, 12);
hierarchy = (Some King, Some PrimeMinister, Some MinisterOfFinance, None);
};
{
name = "Allan MacEachen";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1975, 1);
endyymm = (1976, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Donald Jamieson";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1976, 9);
endyymm = (1979, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Flora MacDonald";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1979, 6);
endyymm = (1980, 3);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Mark MacGuigan";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1980, 3);
endyymm = (1982, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Jean Chretien";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1982, 9);
endyymm = (1984, 6);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Joe Clark";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1984, 9);
endyymm = (1991, 4);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Barbara McDougall";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1991, 4);
endyymm = (1993, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Andre Ouellet";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1993, 1);
endyymm = (1994, 11);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Lloyd Axworthy";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (1996, 1);
endyymm = (2000, 10);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "John Manley";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2000, 10);
endyymm = (2002, 5);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Bill Graham";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2002, 5);
endyymm = (2004, 7);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Pierre Pettigrew";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2004, 7);
endyymm = (2006, 2);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Peter MacKay";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2006, 2);
endyymm = (2007, 8);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Maxime Bernier";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2007, 8);
endyymm = (2008, 5);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Lawrence Cannon";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2008, 5);
endyymm = (2011, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "John Baird";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2011, 1);
endyymm = (2015, 11);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Stephane Dion";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2015, 11);
endyymm = (2017, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Chrystia Freeland";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2017, 1);
endyymm = (2019, 11);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Francois-Philippe Champagne";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2019, 11);
endyymm = (2021, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Marc Garneau";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2021, 1);
endyymm = (2021, 10);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Melanie Joly";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2021, 10);
endyymm = (2022, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Melanie Joly";
service = Service { title = MinisterOfForeignAffairs };
startyymm = (2022, 9);
endyymm = (2025, 12);
hierarchy = (Some King, Some PrimeMinister, Some MinisterOfForeignAffairs, None);
};
{
name = "Peter Harder";
service = Service { title = DeputyMinisterOfForeignAffairs };
startyymm = (2007, 1);
endyymm = (2010, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, Some DeputyMinisterOfForeignAffairs);
};
{
name = "Morris Rosenberg";
service = Service { title = DeputyMinisterOfForeignAffairs };
startyymm = (2010, 1);
endyymm = (2013, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, Some DeputyMinisterOfForeignAffairs);
};
{
name = "Marta Morgan";
service = Service { title = DeputyMinisterOfForeignAffairs };
startyymm = (2016, 1);
endyymm = (2020, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, Some DeputyMinisterOfForeignAffairs);
};
{
name = "David Morrison";
service = Service { title = DeputyMinisterOfForeignAffairs };
startyymm = (2020, 1);
endyymm = (2022, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfForeignAffairs, Some DeputyMinisterOfForeignAffairs);
};
{
name = "David Morrison";
service = Service { title = DeputyMinisterOfForeignAffairs };
startyymm = (2022, 9);
endyymm = (2025, 12);
hierarchy = (Some King, Some PrimeMinister, Some MinisterOfForeignAffairs, Some DeputyMinisterOfForeignAffairs);
};
{
name = "Michael Horgan";
service = Service { title = DeputyMinisterOfFinance };
startyymm = (2009, 1);
endyymm = (2014, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, Some DeputyMinisterOfFinance);
};
{
name = "Paul Rochon";
service = Service { title = DeputyMinisterOfFinance };
startyymm = (2014, 1);
endyymm = (2020, 1);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, Some DeputyMinisterOfFinance);
};
{
name = "Chris Forbes";
service = Service { title = DeputyMinisterOfFinance };
startyymm = (2020, 1);
endyymm = (2022, 9);
hierarchy = (Some Queen, Some PrimeMinister, Some MinisterOfFinance, Some DeputyMinisterOfFinance);
};
{
name = "Chris Forbes";
service = Service { title = DeputyMinisterOfFinance };
startyymm = (2022, 9);
endyymm = (2025, 12);
hierarchy = (Some King, Some PrimeMinister, Some MinisterOfFinance, Some DeputyMinisterOfFinance);
};
] [@@ocamlformat "disable"]
(* ===================================================================== *)
(* Data accessors *)
(* ===================================================================== *)
let firstyymm = (1975, 1)
let lastyymm = (2025, 12)
let base_root_name = "Canadian Cabinet"
let nextmon (yy, mm) = if mm = 12 then (yy + 1, 1) else (yy, mm + 1)
(** Number of inclusive months between two [(year, month)] points. *)
let months_in_range (startyy, startmm) (endyy, endmm) =
((endyy - startyy) * 12) + (endmm - startmm) + 1
let totalmonths = months_in_range firstyymm lastyymm
let monthname : int -> string = function
| 1 -> "January"
| 2 -> "February"
| 3 -> "March"
| 4 -> "April"
| 5 -> "May"
| 6 -> "June"
| 7 -> "July"
| 8 -> "August"
| 9 -> "September"
| 10 -> "October"
| 11 -> "November"
| 12 -> "December"
| _ -> invalid_arg "Invalid month number"
(** Root node label for a given month, e.g. ["Canadian Cabinet 1975 Jan"]. *)
let rootnode_name (curyy, curmm) =
Printf.sprintf "%s %04d %s" base_root_name curyy
(String.sub (monthname curmm) 0 3)
(* ===================================================================== *)
(* Driver *)
(* ===================================================================== *)
type progress_phase = EaseIn | Serving | EaseOut
type hierarchy_key = { hk_hierarchy : hierarchy; hk_name : string }
type nodestate = {
node : Progress.nodeobj;
objid : [ `Object of int ];
mutable phase : progress_phase;
}
let phase_of_dstate :
[ `EaseIn of int | `Serving of Denorm.serving | `EaseOut of int ] ->
progress_phase = function
| `EaseIn _ -> EaseIn
| `Serving _ -> Serving
| `EaseOut _ -> EaseOut
let phase_priority = function Serving -> 0 | EaseIn -> 1 | EaseOut -> 2
let hierarchy_key ~dhierarchy ~dname =
{ hk_hierarchy = dhierarchy; hk_name = dname }
(* Does [needle] occur anywhere in [haystack]? (Replaces Stringext.find_from
so this module has no extra dependencies.) *)
let contains_substring ~needle haystack =
let nl = String.length needle and hl = String.length haystack in
if nl = 0 then true
else
let rec scan i =
if i + nl > hl then false
else if String.sub haystack i nl = needle then true
else scan (i + 1)
in
scan 0
(** [end_without_parent_progress ?parent node] ends a node without updating
its parent's progress. We set parent counters (months in service)
independent of the children. *)
let end_without_parent_progress ?parent node =
let parent_completed = Option.map Progress.completed_items parent in
Progress.end_ node;
match (parent, parent_completed) with
| Some parent, Some completed ->
Progress.set_completed_items parent completed
| _ -> ()
let parent_node ~rootnodestate nodestates dhierarchy =
if is_sovereign dhierarchy then Some rootnodestate.node
else
let parent = parent_hierarchy dhierarchy in
let best_parent =
Hashtbl.fold
(fun { hk_hierarchy = candidate_hierarchy; hk_name = _ }
candidate_state acc ->
if compare_hierarchy candidate_hierarchy parent = 0 then
match acc with
| None -> Some candidate_state
| Some best_state ->
if
phase_priority candidate_state.phase
< phase_priority best_state.phase
then Some candidate_state
else acc
else acc)
nodestates None
in
Option.map (fun { node; _ } -> node) best_parent
(** [set_service_progress node ~dstate ~dmons] sets the progress of a node. We
avoid the default auto-incrementing because it is ugly with our 3-phase
nodes. *)
let set_service_progress node
~(dstate :
[ `EaseIn of int | `Serving of Denorm.serving | `EaseOut of int ])
~dmons =
let completed_items =
match dstate with
| `EaseIn _ -> 0
| `Serving { monthno; totalmonths = _ } -> monthno
| `EaseOut _ -> dmons
in
Progress.set_estimated_total_items node dmons;
Progress.set_completed_items node completed_items
(** find the parent node. it should exist because of the assumption ... unless
the person is missing an immediate leader (which happens in politics).
simplistic: if missing a leader we say the person does not exist *)
let get_parent_node ~rootnodestate
(nodestates : (hierarchy_key, nodestate) Hashtbl.t) dhierarchy =
let `SamplesVerified, _ =
Denorm.assume_sorted_by_hierarchy_with_soverign_first ()
in
if is_sovereign dhierarchy then Some rootnodestate
else
let parent = parent_hierarchy dhierarchy in
let best_parent =
Hashtbl.fold
(fun { hk_hierarchy = candidate_hierarchy; hk_name = _ }
candidate_state acc ->
if compare_hierarchy candidate_hierarchy parent = 0 then
match acc with
| None -> Some candidate_state
| Some best_state ->
if
phase_priority candidate_state.phase
< phase_priority best_state.phase
then Some candidate_state
else acc
else acc)
nodestates None
in
best_parent
(** maintain the tree hierarchy for this month *)
let do_yymm ~trace ~next_objid ~rootnodestate nodestates
(monthdata : Denorm.t list) =
let open Progress in
(* we'll garbage collect later, so we need the live set *)
let live = Hashtbl.create 16 in
(* add nodes / update nodes *)
let rec aux : Denorm.t list -> unit = function
| [] -> ()
| { dname; dtitle; dyymm = _; dstate; dhierarchy; dmons } :: rest ->
let state_phase = phase_of_dstate dstate in
let nodekey = hierarchy_key ~dhierarchy ~dname in
(* create the node name; check if easing in or out *)
let nodename =
match dstate with
| `Serving { monthno = _; totalmonths = _ } ->
Printf.sprintf "%s|%s" dname dtitle
| `EaseIn mon -> String.make mon ' ' ^ "<- " ^ dname
| `EaseOut mon -> String.make mon ' ' ^ "-> " ^ dname
in
begin
(* cleanup:
if [dhierarchy] already present but name has changed,
remove the node first, then proceed as if not present.
*)
match Hashtbl.find_opt nodestates nodekey with
| Some { node; objid; _ } ->
let foundname = Option.value ~default:"" (name node) in
if not (contains_substring ~needle:dname foundname) then begin
(* name changed *)
let parent =
parent_node ~rootnodestate nodestates dhierarchy
in
if trace then
Printf.eprintf
"Name change detected from %s to %s. Cleaning up old \
node...\n\
%!"
foundname dname;
end_without_parent_progress ?parent node;
Hashtbl.remove nodestates nodekey;
Hashtbl.remove live objid
end
| _ -> ()
end;
begin
(* if [dhierarchy] not present, create a new node.
if [dhierarchy] already present, update the node.
*)
match Hashtbl.find_opt nodestates nodekey with
| Some ({ node; objid; _ } as nodestate) ->
(* update cabinet official's name *)
set_name node nodename;
(* progress is based on months in service, not months visible *)
set_service_progress node ~dstate ~dmons;
nodestate.phase <- state_phase;
Hashtbl.add live objid ()
| None -> (
(* find cabinet official's leader (the parent node).
the root node is the "leader" for a sovereign *)
let leader' =
get_parent_node ~rootnodestate nodestates dhierarchy
in
match leader' with
| None ->
(* no leader found *)
if trace then
Printf.eprintf
"Warning: No leader found for %s. Skipping...\n%!"
nodename;
()
| Some { node = leader; objid = _; _ } ->
(* make the cabinet official a new child of the leader *)
let official =
start ~estimated_total:dmons leader nodename
in
let objid = next_objid () in
Hashtbl.add nodestates nodekey
{ node = official; objid; phase = state_phase };
(* progress is based on months in service, not months visible *)
set_service_progress official ~dstate ~dmons;
Hashtbl.add live objid ())
end;
aux rest
in
aux monthdata;
(* garbage collect nodes in nodestate that are no longer alive.
but go through lowest to highest rank so we don't do cascading deletes. *)
let phased_gc rank =
Hashtbl.filter_map_inplace
(fun { hk_hierarchy = h; hk_name = _ } ({ node; objid; _ } as nodestate)
->
match rank_hierarchy h with
| r, _variant when r = rank ->
(* same rank so consider if live *)
if Hashtbl.mem live objid then Some nodestate
else
let parent = parent_node ~rootnodestate nodestates h in
if trace then
Printf.eprintf "GC'ing node for %s in %s\n%!"
(Option.value ~default:"<unnamed node>" (name node))
(Option.value ~default:"<unnamed root>"
(name rootnodestate.node));
end_without_parent_progress ?parent node;
None
| _ ->
(* keep anything out of phase *)
Some nodestate)
nodestates
in
for rank = rank_hierarchy_max downto 0 do
phased_gc rank
done
(* ---- Elm-style animation (model / msg / update + a reconciling view) ----
The model is the pure animation state -- just the current month. [update]
advances it in response to a message. [render] is the "view": it reconciles
the backend tree (the managed runtime state, like a virtual-DOM patch) to
the model's month. It is idempotent for a given month, so it is safe to
call every frame; the backend node handles live in [runtime], not in the
pure model. *)
type model = {
cfg_first : int * int;
cfg_last : int * int;
demodata : Denorm.t list Denorm.YymmMap.t;
cur : int * int; (* current simulated month *)
}
type msg =
| Next_month
| Set_by_elapsed of { elapsed_secs : float; months_per_sec : float }
let init ~firstyymm ~lastyymm ?(easing_months = 6) () =
{
cfg_first = firstyymm;
cfg_last = lastyymm;
demodata = Denorm.denormalize ~easing_months ~firstyymm ~lastyymm data;
cur = firstyymm;
}
let current_yymm model = model.cur
let is_finished model = model.cur >= model.cfg_last
let add_months (yy, mm) n =
let total = (yy * 12) + (mm - 1) + n in
(total / 12, (total mod 12) + 1)
(** Advance the model in response to a message ([Next_month] for a CLI demo
paced by sleeps; [Set_by_elapsed] for a real-time animation loop, where
the caller supplies the demo's months-per-second). Pure. *)
let update msg model =
let clamp ym =
if ym < model.cfg_first then model.cfg_first
else if ym > model.cfg_last then model.cfg_last
else ym
in
match msg with
| Next_month -> { model with cur = clamp (nextmon model.cur) }
| Set_by_elapsed { elapsed_secs; months_per_sec } ->
let idx = int_of_float (elapsed_secs *. months_per_sec) in
let idx = if idx < 0 then 0 else idx in
{ model with cur = clamp (add_months model.cfg_first idx) }
(* The managed runtime: the backend root and the live node handles that
[render] reconciles -- analogous to Elm's real DOM. *)
type runtime = {
rt_root : Progress.nodeobj;
rt_trace : bool;
rt_nodestates : (hierarchy_key, nodestate) Hashtbl.t;
rt_next_objid : unit -> [ `Object of int ];
}
let create_runtime ~root ?(trace = false) () =
let current_objid = ref 0 in
let next_objid () =
let id = !current_objid in
current_objid := id + 1;
`Object id
in
(* the root takes object id 0, matching the original driver *)
ignore (next_objid ());
{
rt_root = root;
rt_trace = trace;
rt_nodestates = Hashtbl.create 16;
rt_next_objid = next_objid;
}
(** The "view": reconcile the backend tree under [runtime] to show [model]'s
current month -- rename the root, add/update/remove the ministers' subtree
([do_yymm] is the patch), and set the root's completed-month count.
Idempotent for a given month; the caller flushes the backend afterwards.
*)
let render rt model =
let open Progress in
let rootnodestate =
{ node = rt.rt_root; objid = `Object 0; phase = Serving }
in
set_name rt.rt_root (rootnode_name model.cur);
let monthdata =
Option.value ~default:[]
(Denorm.YymmMap.find_opt model.cur model.demodata)
in
do_yymm ~trace:rt.rt_trace ~next_objid:rt.rt_next_objid ~rootnodestate
rt.rt_nodestates monthdata;
set_completed_items rt.rt_root (months_in_range model.cfg_first model.cur)
(** [run ...] animates the whole range, calling [tick] after each month --
where the caller flushes the backend and paces the demo (e.g. sleeps).
Built on the Elm-style model above; the caller does any final flush. *)
let run ~root ~firstyymm ~lastyymm ?(easing_months = 6) ?(trace = false) ~tick
() =
let rt = create_runtime ~root ~trace () in
let model = ref (init ~firstyymm ~lastyymm ~easing_months ()) in
let rec loop () =
render rt !model;
tick ();
if is_finished !model then ()
else begin
model := update Next_month !model;
loop ()
end
in
loop ()
end
end

This cell defines cabinet which is an Elm-style (init / update / view / subscriptions) record that drives MlFront_ProgressZig (a built-in progress tree library) and renders its ANSI. Consider changing the cell to use months_per_sec = 25.0 and then running this cell and the next.

(* An Elm-style terminal app (Dk_web.app) wrapping the Canadian Cabinet
animation. The terminal calls init once, then each animation frame folds the
frame's timing through update and displays view; subscriptions stop
requesting frames once the timeline finishes. *)
let cabinet
: ( Samples.CanadianCabinet.runtime * Samples.CanadianCabinet.model,
Dk_web.timing )
Dk_web.app =
let module C = Samples.CanadianCabinet in
let months_per_sec = 5.0 in
{
Dk_web.init =
(fun () ->
(* shutdown lets a fresh root node start on each (re)run *)
MlFront_ProgressZig.Zig.shutdown ();
match
MlFront_ProgressZig.root_node ~initial_delay_ns:0L ~refresh_rate_ns:0L
~root_name:(C.rootnode_name C.firstyymm)
~estimated_total_items:(C.months_in_range C.firstyymm C.lastyymm) ()
with
| Error msg -> failwith ("progress backend: " ^ msg)
| Ok root ->
( C.create_runtime ~root (),
C.init ~firstyymm:C.firstyymm ~lastyymm:C.lastyymm () ));
update =
(fun (t : Dk_web.timing) (rt, cab) ->
( rt,
C.update
(C.Set_by_elapsed
{ elapsed_secs = t.Dk_web.elapsed_secs_since_start; months_per_sec })
cab ));
view =
(fun (rt, cab) ->
Dk_web.capture (fun () ->
C.render rt cab;
MlFront_ProgressZig.Zig.flush ()));
subscriptions =
(fun (_, cab) ->
if C.is_finished cab then [] else [ Dk_web.on_frame (fun t -> t) ]);
}

{% terminal f="cabinet" /%} attaches the cabinet app defined above and runs it:

Loading the terminal…