diff --git a/CHANGES.md b/CHANGES.md
index d8db6872..c75692b0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,31 @@
+- Added a `duplicate` button for elements within auto-generated list forms.
+
+- Added a function to resize prt colum widths.
+
+- Add `Bonsai.Value.of_opt` to new `Bonsai.Cont` api.
+
+- Migrated bonsai examples to new `Bonsai.Cont` API.
+
+- Add documentation about changes and upgrade strategies between proc and cont
+
+- Add 3 new APIs to close Bonsai notifications:
+ * `close_all_notifications` closes all currently open notifications
+ * `close_oldest_notification` closes the oldest currently open notification
+ * `close_newest_notification` closes the newest currently open notification
+
+- Rename `Bonsai.Cont.yoink` to `Bonsai.Cont.peek` to mirror the peek operation in
+other abstract data types
+
+- Made the `close_when_clicked_outside` argument to `Bonsai_web_ui_popover` dynamic.
+
+- Changed Bonsai path generation so that paths are only extended at branch points where
+multple children might reference the paths.
+
+- Balance long chains of Sub nodes to prevent stack overflows and suboptimal
+linear chains of incremental model values.
+
+- Added effects to lock and unlock focus in the `Bonsai_web_ui_partial_render_table`
+
- `Bonsai_web_ui_form` and `Bonsai_web_ui_form2` were merged into `Bonsai_web_ui_form` under
submodules `With_automatic_view` and `With_manual_view`, respectively.
diff --git a/README.md b/README.md
index 0b643238..bec59a36 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,6 @@
# Bonsai
-
Bonsai is a library for building interactive browser-based UI.
-The [Getting Started with Bonsai](./docs/getting_started/index.md)
-guide is good if you're new to web development entirely or just want to see a
-walkthrough of a couple simple example apps.
-
-Examples of using Bonsai in a web browser can be found in the `examples`
-directory.
-
-## Docs
-
Documentation can be found in the [docs](./docs) directory, and API documentation
can be found in [src/bonsai.mli](./src/bonsai.mli).
diff --git a/bindings/dygraph/src/default_legend.ml b/bindings/dygraph/src/default_legend.ml
index e5545f1b..b4dbfef3 100644
--- a/bindings/dygraph/src/default_legend.ml
+++ b/bindings/dygraph/src/default_legend.ml
@@ -5,6 +5,7 @@ module Model = struct
module Series = struct
type t =
{ label : string
+ ; override_label_for_visibility : string option
; value : Raw_html.t option
; dash : Raw_html.t option
; color : string option
@@ -15,7 +16,21 @@ module Model = struct
let toggle_visibility t = { t with is_visible = not t.is_visible }
- let view { label; value; dash; color; is_visible; is_highlighted } ~on_toggle =
+ let label_for_visibility t =
+ Option.value t.override_label_for_visibility ~default:t.label
+ ;;
+
+ let view
+ { label
+ ; override_label_for_visibility = _
+ ; value
+ ; dash
+ ; color
+ ; is_visible
+ ; is_highlighted
+ }
+ ~on_toggle
+ =
let dash =
match dash with
| None -> Vdom.Node.none
@@ -64,12 +79,12 @@ module Model = struct
{ x_label : string
; x_value : Raw_html.t option
; series : Series.t list
- ; past_series : Series.t Map.M(String).t
+ ; past_series_visibility : bool Map.M(String).t
}
[@@deriving equal, sexp]
let view
- { x_label; x_value; series; past_series = _ }
+ { x_label; x_value; series; past_series_visibility = _ }
~on_toggle
~select_all
~select_none
@@ -109,7 +124,8 @@ module Model = struct
select_all_or_none
:: x
:: List.map series ~f:(fun series ->
- Series.view series ~on_toggle:(fun () -> on_toggle series.label))
+ Series.view series ~on_toggle:(fun () ->
+ on_toggle (Series.label_for_visibility series)))
in
(* Mostly copied from vdom_input_widgets *)
Vdom.Node.div
@@ -127,7 +143,7 @@ end
module Action = struct
type t =
| From_graph of Legend_data.t
- | Toggle_visibility of string
+ | Toggle_visibility of { label_for_visibility : string }
| Select_none
| Select_all
[@@deriving equal, sexp]
@@ -179,15 +195,18 @@ let apply_action
{ model with x_value; series }
| Select_none -> map_series ~f:(fun series -> { series with is_visible = false })
| Select_all -> map_series ~f:(fun series -> { series with is_visible = true })
- | Toggle_visibility label ->
+ | Toggle_visibility { label_for_visibility } ->
map_series ~f:(fun series ->
- if String.(series.label = label)
+ if String.(Model.Series.label_for_visibility series = label_for_visibility)
then Model.Series.toggle_visibility series
else series)
;;
-let series_from_info { Per_series_info.label; visible_by_default } =
+let series_from_info
+ { Per_series_info.label; override_label_for_visibility; visible_by_default }
+ =
{ Model.Series.label
+ ; override_label_for_visibility
; is_visible = visible_by_default
; is_highlighted = false
; value = None
@@ -207,7 +226,7 @@ let create ~x_label ~per_series_info
{ Model.x_label
; x_value = None
; series = List.map per_series_info ~f:series_from_info
- ; past_series = String.Map.empty
+ ; past_series_visibility = String.Map.empty
}
| Some (model : Model.t) ->
let existing_y_labels = List.map model.series ~f:Model.Series.label in
@@ -216,24 +235,40 @@ let create ~x_label ~per_series_info
&& [%equal: string list] model_y_labels existing_y_labels
then { model with x_label }
else (
- let past_series =
- (* Every time the [model_y_labels] changes, we want to remember the visibility
- status of all the series labels we know about so far. This will help in the
- case where we toggle visibility on series A, flip to a graph which does not
- have that series, and then flip back to the original graph. Without
- remembering, the visibility status of series A revert back to the default
- status. *)
- List.fold ~init:model.past_series model.series ~f:(fun past_series series ->
- Map.set past_series ~key:series.label ~data:series)
+ (* Every time the [model_y_labels] changes, we want to remember the visibility
+ status of all the series labels we know about so far. This will help in the
+ case where we toggle visibility on series A, flip to a graph which does not
+ have that series, and then flip back to the original graph. Without
+ remembering, the visibility status of series A revert back to the default
+ status. *)
+ let past_series_visibility =
+ List.fold
+ ~init:model.past_series_visibility
+ model.series
+ ~f:(fun past_series_visibility series ->
+ Map.set
+ past_series_visibility
+ ~key:(Model.Series.label_for_visibility series)
+ ~data:series.is_visible)
in
let series =
List.map per_series_info ~f:(fun per_series_info ->
- let { Per_series_info.label; visible_by_default = _ } = per_series_info in
- match Map.find past_series label with
- | None -> series_from_info per_series_info
- | Some series -> series)
+ let { Per_series_info.label
+ ; override_label_for_visibility
+ ; visible_by_default = _
+ }
+ =
+ per_series_info
+ in
+ let series = series_from_info per_series_info in
+ let label_for_visibility =
+ Option.value override_label_for_visibility ~default:label
+ in
+ match Map.find past_series_visibility label_for_visibility with
+ | None -> series
+ | Some is_visible -> { series with is_visible })
in
- { model with x_label; series; past_series })
+ { model with x_label; series; past_series_visibility })
in
let%sub state =
Bonsai_extra.state_machine0_dynamic_model
@@ -250,7 +285,8 @@ let create ~x_label ~per_series_info
let view =
Model.view
model
- ~on_toggle:(fun label -> inject_action (Toggle_visibility label))
+ ~on_toggle:(fun label_for_visibility ->
+ inject_action (Toggle_visibility { label_for_visibility }))
~select_all:(fun () -> inject_action Select_all)
~select_none:(fun () -> inject_action Select_none)
in
diff --git a/bindings/dygraph/src/default_legend.mli b/bindings/dygraph/src/default_legend.mli
index 8a3301f9..6f1da8c6 100644
--- a/bindings/dygraph/src/default_legend.mli
+++ b/bindings/dygraph/src/default_legend.mli
@@ -13,6 +13,7 @@ module Model : sig
module Series : sig
type t =
{ label : string
+ ; override_label_for_visibility : string option
; value : Raw_html.t option
; dash : Raw_html.t option
; color : string option
@@ -20,17 +21,19 @@ module Model : sig
; is_highlighted : bool
}
[@@deriving equal, fields ~getters, sexp]
+
+ val label_for_visibility : t -> string
end
type t =
{ x_label : string
; x_value : Raw_html.t option
; series : Series.t list
- ; past_series : Series.t Map.M(String).t
- (** [past_series] remembers all the series (by series label) that we've ever seen.
- This means that if someone makes a change to a particular series (e.g. toggles
- visibility), moves to a graph without that series, and then moves back to the
- original graph, the information will not be lost.
+ ; past_series_visibility : bool Map.M(String).t
+ (** [past_series_visibility] remembers all the series (by [label_for_visibility]) that
+ we've ever seen. This means that if someone makes a change to a particular series
+ (e.g. toggles visibility), moves to a graph without that series, and then moves
+ back to the original graph, the information will not be lost.
This may sound like a memory leak, and it kind of is, but the hope is that the
total number of unique series labels that one sees over the lifetime of a graph is
@@ -42,7 +45,7 @@ end
module Action : sig
type t =
| From_graph of Legend_data.t
- | Toggle_visibility of string
+ | Toggle_visibility of { label_for_visibility : string }
| Select_none
| Select_all
[@@deriving equal, sexp]
diff --git a/bindings/dygraph/src/per_series_info.ml b/bindings/dygraph/src/per_series_info.ml
index e01ceb73..5e63a304 100644
--- a/bindings/dygraph/src/per_series_info.ml
+++ b/bindings/dygraph/src/per_series_info.ml
@@ -3,10 +3,16 @@ open! Import
type t =
{ label : string
+ ; override_label_for_visibility : string option
; visible_by_default : bool
}
[@@deriving fields ~getters]
+let create ?override_label_for_visibility label ~visible_by_default =
+ { label; override_label_for_visibility; visible_by_default }
+;;
+
let create_all_visible labels =
- List.map labels ~f:(fun label -> { label; visible_by_default = true })
+ List.map labels ~f:(fun label ->
+ { label; override_label_for_visibility = None; visible_by_default = true })
;;
diff --git a/bindings/dygraph/src/per_series_info.mli b/bindings/dygraph/src/per_series_info.mli
index cda48516..d6f35ed7 100644
--- a/bindings/dygraph/src/per_series_info.mli
+++ b/bindings/dygraph/src/per_series_info.mli
@@ -3,8 +3,25 @@ open! Import
type t =
{ label : string
+ ; override_label_for_visibility : string option
+ (** It may be helpful to distinguish the series label from the "label for visibility".
+
+ For example, in some graphs we encode information about the symbol we are looking at
+ in the series label. That information changes as you look at different symbols.
+ However, the second series always semantically means the same thing as you move from
+ symbol to symbol. If you uncheck the second series to disable visibility, you may
+ want to remember that change even if the series label changes.
+
+ If you want to just use the [label] as the semantic identifier for persisting
+ visilibity, then just set [override_label_for_visibility] to None. *)
; visible_by_default : bool
}
[@@deriving fields ~getters]
+val create
+ : ?override_label_for_visibility:string
+ -> string
+ -> visible_by_default:bool
+ -> t
+
val create_all_visible : string list -> t list
diff --git a/changelog.md b/changelog.md
index 6cfdf2ea..94ba41e1 100644
--- a/changelog.md
+++ b/changelog.md
@@ -134,7 +134,7 @@ degree than otherwise available.
## 2020-06-10
`Bonsai.Proc` module added. To read more, check out
-[this document](./docs/blogs/proc.md).
+[this document](./docs/blog/proc.md).
## 2020-03-17
- Model type removed from `('input, 'model, 'result) Bonsai.t`.
diff --git a/docs/advanced/how_bonsai_works.md b/docs/advanced/how_bonsai_works.md
new file mode 100644
index 00000000..f68f01d8
--- /dev/null
+++ b/docs/advanced/how_bonsai_works.md
@@ -0,0 +1,411 @@
+How Bonsai Works
+
+This article talks about how Bonsai works. It is probably most useful to
+Bonsai maintainers, although it can provide useful context for power
+users as well. We'll cover:
+
+- How Bonsai's internals work
+- How the `local_ graph` API works
+
+```{=html}
+
+```
+# `Computation.t` and `Value.t`
+
+One of Bonsai's invariants is that the computation graph is a static
+DAG. This offers [a lot of benefits](./why_no_bind.mdx).
+
+Bonsai's internal representation of this graph is based around 2 types.
+
+`Value.t` is effectively a wrapper around Incremental's `Incr.t`, and
+ultimately gets compiled down to `Incr.t`s.
+
+`Computation.t` represents the "structure" of the computation graph.
+It's implemented as a variant of types such as:
+
+- `Leaf0` and `Leaf1`: structure around state nodes
+- `Store` and `Fetch`: powers Bonsai's `Dynamic_scope` tools
+- `Assoc`: allows creating a dynamic number of copies of some internal
+ `Computation.t`, each with their own state / lifecycle.
+- `Switch`: allows selecting one of several `Computation.t`s to
+ evaluate.
+- `Path`: returns the path to a new node in the computation graph,
+ which is useful as a unique ID.
+- `Lifecycle`: allows running `on_activate` / `on_deactivate` /
+ `after_display` lifecycle events
+- `Return`: a very important one: just return an incremental
+ `Value.t`.
+- `Sub`: possibly the most important one: allows a computation to use
+ the output of **any other** computation in the graph. This is what
+ makes the computation graph a DAG, and not a tree.
+
+## How do `Computation.t` and `Value.t` interact?
+
+Many of the variants of `Computation.t` are records, that store some
+`Value.t`. For example, `Switch` stores an `int Value.t` representing
+which of the `Computation.t`s is currently active. And `Assoc` stores an
+input `Map.t`. Of particular importance, `Return` stores some arbitrary
+`Value.t`.
+
+## How was the graph constructed before?
+
+Up until late 2023, Bonsai's public-facing API directly constructed a
+`Computation.t` graph, which provides structure for `Value.t`
+incremental calculations. Let's go over the key parts.
+
+### sub
+
+`val sub: 'a Computation.t -> f:('a Value.t -> 'b Computation.t) -> 'b Computation.t`.
+This was commonly used via the `let%sub` syntax. It:
+
+- Creates a new "named" `Value.t`, which essentially reads
+ `'a Computation.t`.
+- Applies the provided `f` function to that value, generating a new
+ `'b Computation.t`.
+- Creates a new `Sub` node for the graph, which contains these 2
+ `Computation.t`s.
+
+Essentially, this allows us to compute a `'a Value.t`, use it in 2
+separate computations, and then `map` them together.
+
+If we only had a tree, we would have to clone that `'a Value.t` for each
+of the 2 subcomputations. We would have to redo all calculations twice,
+and we couldn't share state.
+
+But with `sub`, we essentially create a symlink / reference / pointer to
+the shared `'a Value.t`, so we can use it in multiple places without
+issue. This is why `Sub` is so important.
+
+### arr
+
+`val arr : 'a Value.t -> f:('a -> 'b) -> 'b Computation.t`. There are
+also `arr2`, `arr3` versions, which combine several `Value.t`s into one
+`Computation.t`.
+
+This was commonly used via the `let%arr` syntax. All `arr` does is call
+`Value.map`, and wrap the result in a `Return` node.
+
+The entire point of `arr` is to force you to use `sub`, so that our new
+`Value.t` is defined in terms of references/symlinks to any shared work,
+and we don't accidentially clone that work / lose shared state.
+
+### And the rest?
+
+We've talked about the `Sub` node via `sub`, and how new `Value.t`s get
+created (and then wrapped in `Return`) via `arr`. What about the other
+`Computation.t`s?
+
+This is where all the `Bonsai.state_machine`, `Bonsai.Dynamic_scope.*`,
+`Bonsai.assoc`, etc primitives come in; they create `Leaf0`,
+`Fetch`/`Store`, `Assoc`, and other nodes. Then, using `sub` on those
+nodes adds them into the graph.
+
+## Ok, but how do we get an `Incr.t` from all of this?
+
+Before Bonsai boots up your web UI, it performs a compilation pass.
+First, it recursively "evaluates" your computation graph, resulting in a
+single `Computation.info` record.
+
+```{=html}
+
+```
+### What is Computation.info
+
+For every `Computation.t`, we recursively create this
+`Computation.info`, which includes:
+
+- `'model Meta.Model.t`, which contains the default value / type id
+ structure for the `Computation`.
+ - For many `Computation.t`s, such as `Path`, `Lifecycle`, and
+ `Return`, this is just `Meta.Model.unit`.
+ - For the "state" nodes `Leaf0` and `Leaf1`, this is the
+ user-provided model metadata.
+ - For nodes defined in terms of other `Computation.t`s, such as
+ `Sub`, `Assoc`, `Switch`, and `Store`, we recursively combine
+ the `Meta.Model.t` of subcomputations.
+- `'input Meta.Input.t`, which contains a type id for inputs to the
+ `Computation.t`. For `Leaf1`, which is a state machine with input,
+ this is the user-provided input metadata. For recursive
+ `Computation.t`s, this combines inputs of subcomputations.
+ Otherwise, it's `Meta.Input.unit`.
+- `'action Action.id`, which is a type id for a state machine's
+ `Action.t`, is assembled similarly.
+- As is `reset`, which is a `'model -> 'model` function used to
+ implement `with_model_resetter`.
+- And so is `apply_action`, which users provide when creating state
+ machines, and is the state machine transition, with the added
+ ability to dispatch events.
+
+### `run` and `Snapshot.t`
+
+The final, and most interesting part of `Computation.info` is `run`,
+which takes:
+
+- An "environment", which contains all "named" `Value.t`s memoized via
+ `sub`, andthe available dynamically scoped variables.
+- A `Time_source.t`
+- The current computation path
+- An `Incr.t` with the current state
+- An `inject` function, which creates `Effect.t`s that apply
+ `'action`s
+
+and outputs a `Snapshot.t`. which represents the state of the
+computation, and all its subcomputations.
+
+`Snapshot.t` packages an `Input.t`, which is all `Incr.t` (and empty
+`unit`) inputs to the computation and its subcomputations, some
+lifecycle state, and a result, which is an `Incr.t` with the dynamic
+value produced by the computation.
+
+So essentially, `run` is the "logic" of the `Computation.t`. Let's
+discuss a few examples:
+
+For `Return`, `run` creates a trivial snapshot with no input and no
+lifecycle. It calls `Value.eval` on its internal `Value.t`, and uses
+that for the `Snapshot.t`'s result. `Value.eval` is provided the
+"environment", so that `Value.t`s built in terms of "named"
+symlink/reference/pointer `Value.t`s can be evaluated.
+
+For `Leaf0`, `run` is similar, except that it returns the current state
+`Incr.t` and the `inject` function for `result`. `Leaf1` is almost the
+same, except that it uses the `input : 'input Value.t` field of its
+`Computation.t` node for the `Snapshot.t`'s input.
+
+`Store`'s `run` is probably the simplest out of computations with
+subcomputations; it adds whatever dynamically scoped value it is storing
+into the environment, then calls the inner computation's `run` with this
+new environment.
+
+For the rest, read the implementation in
+[bonsai/src/eval](https://github.com/janestreet/bonsai/blob/master/src/eval.ml).
+
+### So where do models + actions live?
+
+The top-level `Computation.info` for our whole web UI now contains a
+`'model Meta.Model.t`, which represents the structure + default values
+of the state of everything. We can use that to create a
+`'model Incr.Var.t`, which will be a single variable tracking the state
+of our whole web UI!
+
+It's shaped roughly like the `Computation.t` graph itself: every `Sub`
+is a tuple of the `'model` of its `from` and the `'model` of its `into`;
+assocs are `Map.t`s of the inner's `'model`, and leaf `'model`s are what
+you'd expect.
+
+There are similar tree-like structures storing the `apply_action` and
+`reset` functions for everything. And the "actual" representation of an
+action is a path in that tree.
+
+You can think of `gather` as a pass up the tree, which collects the
+structure of everything and assembles these whole-app state/action/etc
+trees, and a second pass down the tree, which provides `Incr.t`
+accessors for recursive sub-parts of those trees.
+
+### And then what?
+
+The private `bonsai.driver` library uses `eval` to construct this
+`Computation.info`, instantiates the environment, default model,
+`Effect.t` queue, and some other things, calls `run` on the top-level
+computation, extracts the `result Incr.t` and `input Incr.t` from the
+resulting snapshot, and puts these all together in a `Bonsai_driver.t`
+type that provides an API for running the Bonsai computation.
+
+This driver is mostly used for testing.
+
+### Testing??? I want a website!
+
+`Bonsai_web.Start` pretty much does the same thing, except that it does
+some web specific stuff, and expects a `Vdom.Node.t Computation.t`. Once
+it has a `Vdom.Node.t Incr.t`, it uses `virtual_dom` to generate DOM
+from the vdom / attach it to some element, and then run `virtual_dom`'s
+diff-and-patch algorithm when the `Incr.t` changes.
+
+`Bonsai_web.Start` actually mostly does this through
+`Incr_dom.Start_app`.
+
+# The `local_ graph` API
+
+In late 2023, we released a new implementation of Bonsai's API, called
+`Cont`. That's because it is implemented in the continuation-passing
+style.
+
+As covered above, in the previous API, calls to `sub`, `arr`, and
+`Bonsai.*` primitives directly created `Computation.t` nodes (and the
+underlying `Value.t`s).
+
+The new API doesn't actually change the internals! As before,
+`Computation.t` is the static structure of the computation graph, and
+`Value.t` is a wrapper around `Incr.t`, adding support for
+"reference/symlink" values, which enable `sub` and work-sharing, and a
+bunch of other stuff.
+
+In the new API, we have one `Bonsai.t` type, which is actually just
+`Value.t`. Instead of `Computation.t`s, we pass around an
+abstractly-typed `Bonsai.graph` value. This `graph` value is used to
+construct Bonsai's computation graph, as we'll discuss in a bit.
+
+## Why do we need `graph`, and what is `local_`?
+
+There are two "phases" for Bonsai apps:
+
+1. "`Computation.t` building", where we build up the static computation
+ graph.
+2. "Runtime", which starts when `Bonsai_web.Start.start` is run. At
+ this point, the Computation.t templates are instantiated and flush
+ with data. Runtime code is all in functions provided to
+ `Bonsai.map`; `let%arr` hides this somewhat, but the pure OCaml
+ "contents" of `let%arr` calls are actually de-sugared to anonymous
+ functions.
+
+Once "runtime" starts, you can no longer do any `Computation.t`
+building; there aren't any `Computation.t`s left to build! It's all just
+one big `Incr.t`! And in fact, Bonsai's type signatures force separation
+of "computation building" code and "runtime" code, even though all this
+code lives together in the same files and functions.
+
+But with a new API, where there's just one `Bonsai.t` type, how do we
+enforce this separation? What's stopping us from referring to `graph` in
+runtime code, inside of some `let%arr`, and blowing up our application?
+
+The new [local\_
+mode](https://blog.janestreet.com/oxidizing-ocaml-locality/) gives us a
+new way to have the type-checker guarantee the separation of phases! To
+dramatically oversimplify `local_`: when a function annotates an
+argument with `local_`, it's promising not to close over it, partially
+apply it to any variables, put it in a ref, or otherwise stash it away.
+
+Because all our runtime code is put in closures, use of `local_` means
+that we just can't use `graph`! And so, we get a "phase witness"
+property in `graph` through `local_`.
+
+As a result, Bonsai can enforce the same invariants as before, but
+provide a simpler API and provide even more power and flexibility to UI
+authors!
+
+But what's going on under the surface? And what is `graph`? And why can
+the new Bonsai API use `Bonsai.map`?
+
+## How `local_ graph` Works
+
+### Background on `Sub`
+
+Recall that the `sub` is the memoization-like pattern that powers
+work-sharing for incrementality: we can store a reusable value with a
+unique name (via Type_equal.id), which downstream incremental `Value.t`s
+can reference (and reuse!) instead of repeating work. The "Sub"
+`Computation.t` node has 3 fields:
+
+- `from` is the computation being memoized
+- `via` is the `Type_equal.id` identifier
+- `into` is the downstream computation that uses the value of `from`.
+
+Let's say we want to use the output of `comp1` and `comp2` (both
+`Computation.t`s) in another computation called
+`thing_we_want_to_compute`. This is what the corresponding computation
+graph looks like:
+
+```{=html}
+
+```
+ Sub { from=comp1; via=; into =
+ Sub { from=comp2; via=; into =
+ ... into = thing_we_want_to_compute ...
+ }
+ }
+
+### What is `graph`, and how is `Cont` implemented?
+
+The new `Cont` implementation has a very primitive API (not exposed to
+end-users), which simulates algebraic effects:
+
+- `perform` takes a `local_ graph -> Computation.t`, evaluates it, and
+ adds it into the computation graph. It returns a `Value.t` memoized
+ alias to this new computation.
+- `handle` places a `Value.t` in the context of a fresh computation
+ graph. It then wraps the result in a `Computation.t`. This is
+ generally used to construct a portion of the computation graph in
+ isolation. The subgraph is then added to the main graph via
+ `perform`.
+
+`graph` is the mechanism for composing / combining `Computation.t`s via
+`Sub`. It is implemented as a ref holding a
+`Computation.t -> Computation.t` function. `graph` starts as `Fn.id`. At
+each invocation of `perform`, we overwrite it to still return its input,
+but nested in a new layer of `Sub`.
+
+```{=html}
+
+```
+Let's look at how this works in practice. The top part is the code, and
+the bottom is the value of `graph` after every line:
+
+```{=html}
+
+```
+ 1.let computation graph =
+ 2. let model1, _inject = Bonsai.state 5 graph in
+ 3. let model2, _inject = Bonsai.state 6 graph in
+ 4. model2
+
+ ---
+
+ 1. graph = { f = fun r -> r }
+ 2. graph = { f = fun r -> Sub { from=state 5; via=; into=r } }
+ 3. graph = { f = fun r -> Sub { from=state 5; via=; into=Sub { from=state 6; via=; into=r } } }
+ 4. graph.f () ==> Sub { from=state 5; via=; into=Sub { from=state 6; via=; into= } }
+
+Instead of `state 5/6`, we'd actually have something more like
+`Leaf0 { ... }`, but that's not really relevant. We also do some
+optimizations if `sub`ing isn't actually necessary.
+
+Handle is much simpler: we run our `Value.t` construction function (`f`)
+on a fresh `graph`, which becomes constructed via calls to `perform`
+presumably inside `f`. Then, we call `graph.f` on that result, so that
+it is inside the nested `Sub`s, giving it access to those memoized
+aliases. Finally, we wrap this in a `Computation.t`, which can be
+`perform`ed into other computations.
+
+```{=html}
+
+```
+A consequence of `perform`'s implementation is that the shape of
+`Computation.t` under the Cont API is very linear: with the exception of
+`Switch`/`Assoc` nodes, it is a chain of `Sub`s, each with more-or-less
+a single node on the `From` side. You can think of this as a list of all
+the things that have been instantiated and memoized, in the order of
+instantiation.
+
+### Why is `Bonsai.map` safe now?
+
+But why can we now use `Bonsai.map` instead of `let%sub` and `let%arr`
+combos? After all, we're not passing `graph` to `let%map`... How could
+it construct `Sub` nodes and add them into the graph?
+
+In short, We cheat and make `graph` global. We still require users to
+pass it when possible, so that creating costly stateful nodes is an
+explicit operation.
diff --git a/docs/advanced/readme.md b/docs/advanced/readme.md
new file mode 100644
index 00000000..52e0e9db
--- /dev/null
+++ b/docs/advanced/readme.md
@@ -0,0 +1,3 @@
+# Bonsai Docs: \[Advanced Topics\]
+
+Learn how to do advanced things in Bonsai
diff --git a/docs/advanced/why_no_bind.md b/docs/advanced/why_no_bind.md
new file mode 100644
index 00000000..5f0b3efe
--- /dev/null
+++ b/docs/advanced/why_no_bind.md
@@ -0,0 +1,49 @@
+Why No Bind?
+
+Many have asked the question "`Bonsai.t` is an Applicative, why isn't it
+a Monad?" Certainly, for the users of a library, having a Monad gives
+the user much more power. For example, take a look at this code using
+monadic bind:
+
+```{=html}
+
+```
+``` ocaml
+val x: bool t
+val y: 'a t
+val z: 'a t
+
+val bind: 'a t -> f:('a -> 'b t) -> 'b t
+
+bind x ~f:(fun x -> if x then y else z)
+```
+
+Look at that, we've built a `'a t` that dynamically chooses between
+either `y` or `z` depending on the value of `x`. Try to do the same with
+a type that is only an Applicative! (Do not try to do the same with an
+Applicative, it is impossible.)
+
+The dynamism on display is quite useful, but sadly, it's dynamism that
+would hurt Bonsai if `Value.t` were a Monad. It would prevent
+"whole-program analysis" of the app, which would make some nice Bonsai
+features impossible:
+
+1. Bonsai runs an optimization pass on the program, resulting in a
+ seriously condensed incremental graph. If an app were to dynamically
+ generate bonsai values, we would potentially have to run the
+ optimization many times a second, slowing things down considerably.
+2. We can attach instrumentation for debugging to nodes in the
+ computation graph, or even add specialized "debugging" nodes.
+3. With `bind`-based dynamism, it would become difficult to reason
+ about the "stateful" bits of Bonsai with models. If a `match%sub`
+ node were to toggle between arms, state of the inactive subtrees
+ would be lost.
+
+And from a performance perspective, `bind` would likely make Bonsai apps
+considerably slower: dynamism is fairly slow in Incremental, the library
+that Bonsai is built on top of.
+
+In practice, preventing Bonsai.t from implementing the Monad interface
+hasn't blocked much: using the `match%sub` and `Bonsai.assoc`
+primitives, pretty much anything you'd want to do with `bind` is
+possible, and we get to keep all the benefits listed above.
diff --git a/docs/blogs/history.md b/docs/blog/history.md
similarity index 100%
rename from docs/blogs/history.md
rename to docs/blog/history.md
diff --git a/docs/blogs/proc.md b/docs/blog/proc.md
similarity index 100%
rename from docs/blogs/proc.md
rename to docs/blog/proc.md
diff --git a/docs/blog/readme.md b/docs/blog/readme.md
new file mode 100644
index 00000000..1eb983b0
--- /dev/null
+++ b/docs/blog/readme.md
@@ -0,0 +1,4 @@
+# Bonsai Docs: \[Blog Posts\]
+
+A collection of blog posts about Bonsai, mostly focusing on the history
+of its API.
diff --git a/docs/blogs/letsub.md b/docs/blogs/letsub.md
deleted file mode 100644
index 4c640fa8..00000000
--- a/docs/blogs/letsub.md
+++ /dev/null
@@ -1,546 +0,0 @@
-```{=html}
-
-```
-... and I want you to as well.
-
-Although the goal of this post is to explain `let%sub` *in Bonsai*, I
-think I'll do better by explaining the same idea in the context of a
-much simpler example.
-
-Let's say we're building a tiny little language for doing simple
-arithmetic:
-
-```{=html}
-
-```
-``` ocaml
-type t =
- | Const of int
- | Add of t * t
- | Map of
- { t : t
- ; f : int -> int
- }
-
-let const i = Const i
-let add x y = Add (x, y)
-let map t ~f = Map { t; f }
-
-let rec eval : t -> int = function
- | Const i -> i
- | Add (x, y) -> eval x + eval y
- | Map { t; f } -> f (eval t)
-;;
-```
-
-Now we use it to compute something like so:
-
-```{=html}
-
-```
-``` ocaml
-let print_and_return i =
- printf "got here\n";
- i
-;;
-
-let x = map (add (const 5) (const 1)) ~f:(fun i -> print_and_return (i / 2))
-let doubled = map x ~f:(fun i -> i * 2)
-let tripled = map x ~f:(fun i -> i * 3)
-let y = add doubled tripled
-let () = printf "y = %d\n" (eval y)
-```
-
-**Here's the question:** how many times does "got here" print? Put
-another way, how many times do we evaluate `x`?
-
-I don't think anyone will be too surprised in this case that the answer
-is two. But still, let's break it down. Let's first visualize the
-computatation that `x` represents:
-
-``{=html}
-
-Each box represents a `t`. We start with two `const` boxes that we
-create from hard-coded ints. We combine them using an `add`. Finally, we
-map over that.
-
-Now what do you think `y` looks like? Does it look like this?
-
-``{=html}
-
-or this?
-
-``{=html}
-
-As much as you might *want* it to look like the first, it doesn't. It
-looks like the second. If you use `x` twice, you're duplicating the
-entire computation of `x`. **There is no sharing.**
-
-# How to add sharing
-
-So how can we extend the language to enable us to share computation?
-There's more than one way.
-
-## Eager evaluation
-
-The obvious way in our completely trivial language is to just eagerly
-evaluate the ints as we construct them, but I claim that's cheating. In
-Bonsai, often the values depend on user action and cannot, even in
-principle, be eagerly computed. So what other options do we have?
-
-## Mutability
-
-To me, this feels a lot like trying to improve the performance of the
-following function
-
-``` ocaml
-let rec fib n =
- match n with
- | 0 | 1 -> n
- | n -> fib (n - 1) + fib (n - 2)
-```
-
-This function suffers the same problem as `y` - we recompute the exact
-same values over and over again. How do we solve the problem?
-Memoization! We cache previously computed values so that if we're asked
-to compute the same thing again, we just return the cached value.
-
-Let's apply this idea to our language. A lot of this is repeat. The
-interesting change is that we add a new function `cache` which takes a
-`t` and returns a new `t` that's really just a mutable version of that
-`t`. The mutability allows us to replace the unevaluated `t` with an
-evaluated int the first time we compute it.
-
-```{=html}
-
-```
-``` ocaml
- type t =
- | Const of int
- | Add of t * t
- | Map of
- { t : t
- ; f : int -> int
- }
- | Cached of { mutable value : [ `Not_yet_evaluated of t | `Evaluated of int ] }
-
- let const i = Const i
- let add x y = Add (x, y)
- let map t ~f = Map { t; f }
- let cache t = Cached { value = `Not_yet_evaluated t }
-
- let eval t =
- let rec eval = function
- | Const i -> i
- | Add (x, y) -> eval x + eval y
- | Map { t; f } -> f (eval t)
- | Cached cached_value ->
- (match cached_value.value with
- | `Evaluated i -> i
- | `Not_yet_evaluated t ->
- let i = eval t in
- cached_value.value <- `Evaluated i;
- i)
- in
- eval t
- ;;
-```
-
-And here's a test that shows that by caching `x` we now only print "got
-here" once, even though we mapped on `x` twice:
-
-```{=html}
-
-```
-``` ocaml
- let%expect_test "" =
- let x = map (add (const 5) (const 1)) ~f:(fun i -> print_and_return (i / 2)) in
- let x = cache x in
- let doubled = map x ~f:(fun i -> i * 2) in
- let tripled = map x ~f:(fun i -> i * 3) in
- let y = add doubled tripled in
- printf "y = %d\n" (eval y);
- [%expect
- {|
- got here
- y = 15
- |}]
- ;;
-```
-
-### By the way, this is how deferred and incremental work
-
-In the first section, I claimed that `map` does not share work. If you
-map on a thing twice, you compute it twice.
-
-This may have come as a pretty big surprise to you (it did for me)
-because I'm accustomed to using things that absolutely do not work that
-way. Incremental is one case, but the more common case is deferred.
-Consider the following snipped of code:
-
-`ocaml skip let x : unit Deferred.t = Clock.after (sec 1.) >>| fun () -> print_endline "x" in let y = x >>| fun () -> print_endline "y" in let z = x >>| fun () -> print_endline "z" in let%map () = Deferred.all_unit [x;y;z] in`
-
-Will "x" get printed once or twice? Once! So wait... we mapped on a
-thing twice and it got computed once. What gives?
-
-Deferred uses mutability - like our `Cached` variant - to share
-computation between multiple maps. So let me revise my claims from
-above: *without mutability* `map` does not share work.
-
-For example, haskell has no mutability and therefore "x" will definitely
-print twice in the following code:
-
- main :: IO ()
- main = do
- let x = print "x"
- y = x >> print "y"
- z = x >> print "z"
- y
- z
-
-## Sub
-
-Bonsai doesn't use mutability to achieve sharing - it uses `sub`. How
-does that work? To see, let's add `sub` to our language:
-
-```{=html}
-
-```
-``` ocaml
- module Uid = Unique_id.Int ()
-
- type t =
- | Const of int
- | Precomputed_value of Uid.t
- | Add of t * t
- | Map of
- { t : t
- ; f : int -> int
- }
- | Sub of
- { thing_to_precompute : t
- ; name : Uid.t
- ; body_that_uses_precomputed_thing : t
- }
-
- let const i = Const i
- let add x y = Add (x, y)
- let map t ~f = Map { t; f }
-
- let sub t ~f =
- let name = Uid.create () in
- Sub
- { thing_to_precompute = t
- ; name
- ; body_that_uses_precomputed_thing = f (Precomputed_value name)
- }
- ;;
-
- let eval t =
- let rec eval ~scope = function
- | Const i -> i
- | Precomputed_value name -> Map.find_exn scope name
- | Add (x, y) -> eval ~scope x + eval ~scope y
- | Map { t; f } -> f (eval ~scope t)
- | Sub { thing_to_precompute; name; body_that_uses_precomputed_thing } ->
- let i = eval ~scope thing_to_precompute in
- let scope = Map.set scope ~key:name ~data:i in
- eval ~scope body_that_uses_precomputed_thing
- in
- eval ~scope:Uid.Map.empty t
- ;;
-
- let%expect_test "" =
- let x = map (add (const 5) (const 1)) ~f:(fun i -> print_and_return (i / 2)) in
- let y =
- sub x ~f:(fun x ->
- let doubled = map x ~f:(fun i -> i * 2) in
- let tripled = map x ~f:(fun i -> i * 3) in
- add doubled tripled)
- in
- printf "y = %d\n" (eval y);
- [%expect
- {|
- got here
- y = 15
- |}]
- ;;
-```
-
-How does `sub` work? It takes an expression `thing_to_precompute` and a
-function which wants to use that expression while only evaluating it
-once. The trick is that instead of passing `thing_to_precompute`
-directly to the function, it instead mints a unique name for that
-expression and passes `(Precomputed_value name)` to `f`.
-
-Later when evaluating the `sub` expression, we evaluate
-`thing_to_precompute` once and then store the evaluated int in a map
-called `scope`, using the key `name`. Then we evaluate the body which,
-remember, doesn't refer directly to `thing_to_precompute` but instead on
-`Precomputed_value name`. Every time the body needs to find the value of
-`thing_to_precompute`, it just looks up the value for `name` in `scope`.
-
-QED!
-
-### A bit of polishing
-
-From a conceptual standpoint, the previous section really does explain
-how `sub` works. But you might still wonder why Bonsai has such an
-unusual signature for `sub`:
-
-`ocaml skip val sub : 'a Computation.t -> f:('a Value.t -> 'b Computation.t) -> 'b Computation.t`
-
-Why do we need these two types? The real answer is we don't *need* them,
-but it makes things a little cleaner.
-
-Notice that in the last version of our language (the one with sub), the
-`f` in `sub t ~f` always gets a version of `t` that is constructed via
-the `Precomputed_value of Uid.t` constructor. Can we encode that fact in
-the types?
-
-Sure - let's give that case its own type:
-
-```{=html}
-
-```
-``` ocaml
- type precomputed_value = Uid.t
-
- type t =
- | Const of int
- | Add of t * t
- | Arr of
- { value : precomputed_value
- ; f : int -> int
- }
- | Sub of
- { bound : t
- ; name : Uid.t
- ; body : t
- }
-
- let const i = Const i
- let add x y = Add (x, y)
- let arr value ~f = Arr { value; f }
-
- let sub t ~f =
- let name = Uid.create () in
- Sub { bound = t; name; body = f name }
- ;;
-```
-
-Now what's the type of sub?
-
-`ocaml skip val sub : t -> f:(value -> t) -> t`
-
-Huh... that's starting to look a lot like Bonsai's version of sub. And
-if you want to see what `eval` looks like now, it's really not very
-different:
-
-```{=html}
-
-```
-``` ocaml
- let eval t =
- let rec eval ~scope = function
- | Const i -> i
- | Add (x, y) -> eval ~scope x + eval ~scope y
- | Arr { value; f } ->
- let i = Map.find_exn scope value in
- f i
- | Sub { bound; name; body } ->
- let i = eval ~scope bound in
- let scope = Map.set scope ~key:name ~data:i in
- eval ~scope body
- in
- eval ~scope:Uid.Map.empty t
- ;;
-```
-
-You might notice that I snuck in one last change, which is that I
-removed `map` completely and replaced it with `arr`. The important
-difference is that `map` mapped over a `t` whereas `arr` maps over a
-`precomputed_value`. What does that mean from a practical perspective?
-It means that `arr` is only allowed to map over precomputed values. In
-other words, mapping over a non-precomputed thing twice won't even
-compile. If you want to map, first `sub` to get a precomputed
-`precomputed_value` and then `arr` to map over that.
-
-Here it is in action:
-
-```{=html}
-
-```
-``` ocaml
- let%expect_test "" =
- let x =
- sub
- (add (const 5) (const 1))
- ~f:(fun sum -> arr sum ~f:(fun i -> print_and_return (i / 2)))
- in
- let y =
- sub x ~f:(fun x ->
- let doubled = arr x ~f:(fun i -> i * 2) in
- let tripled = arr x ~f:(fun i -> i * 3) in
- add doubled tripled)
- in
- printf "y = %d\n" (eval y);
- [%expect
- {|
- got here
- y = 15
- |}]
- ;;
-```
-
-### Why does Bonsai have map then?
-
-If `map` let's you do "a bad thing", why does Bonsai have it? Historical
-accident. If Bonsai devs could remove it, they would.
-
-## Applicatives vs. Arrows
-
-If the above was just way to easy to understand, I can make it a bit
-more abstract for you. Everything we've talked about so far is
-essentially a case study in understanding applicatives vs. arrows.
-
-An applicative is something that satisfies this interface:
-
-```{=html}
-
-```
-``` ocaml
-module type Applicative = sig
- type 'a t
-
- val return : 'a -> 'a t
- val map : 'a t -> f:('a -> 'b) -> 'b t
- val both : 'a t -> 'b t -> ('a * 'b) t
-end
-```
-
-or in picture form:
-
-``{=html}
-
-Note: this isn't the typical way to describe an applicative but it's
-equivalent to the typical version and it's way easier to understand. Our
-simple arithmetic language is actually an applicative in disguise with
-`const` and `add` serving as `return` and `both`.
-
-What types of computation graphs can you represent with an applicative?
-They key is to realize that two branches can be combined via `both` but
-a single branch can never split back into two parallel branches.
-
-In other words, assuming your graph is connected left to right, you can
-see a shape that looks something like this:
-
-``{=html}
-
-but never this:
-
-``{=html}
-
-But the latter shape is often what we want! In particular, when we want
-to compute a single value and reuse it in two different later
-computations (without necessarily using mutability).
-
-This ability to share a value with two later computations is exactly
-what arrow gives you on top of applicative.
-
-```{=html}
-
-```
-``` ocaml
-module type Arrow = sig
- type ('a, 'b) t
-
- val arr : ('a -> 'b) -> ('a, 'b) t
-
- (* Combine two arrows in series *)
- val compose : ('a, 'b) t -> ('b, 'c) t -> ('a, 'c) t
-
- (* Combine two arrows in parallel *)
- val ( &&& ) : ('a, 'b1) t -> ('a, 'b2) t -> ('a, 'b1 * 'b2) t
-end
-```
-
-``{=html}
-
-If you're willing to squint past the fact that an arrow type has two
-type parameters and the oh-so-descriptive name of `&&&`, you can see
-that `&&&` gives us the exact shape that we were missing. It lets us
-compute some value `'a` and then pass it to two later computations, in
-effect **sharing the value of `'a`**.
-
-It's no coincidence that Bonsai *is an arrow*. It might not expose an
-interface that looks like `module type Arrow`, but it's actually
-equivalent to that interface. The key is realizing that the following
-type definitions is the right one:
-
-`ocaml skip type ('a, 'b) t = 'a Value.t -> 'b Computation.t`
-
-Using that as your primary arrow type, it's not hard to define `arr`,
-`compose`, and `(&&&)` using Bonsai's `let%sub` and `let%arr`:
-
-```{=html}
-
-```
-``` ocaml
-module Arrow_from_bonsai : sig
- type ('a, 'b) t = 'a Value.t -> 'b Computation.t
-
- val arr : ('a -> 'b) -> ('a, 'b) t
- val compose : ('a, 'b) t -> ('b, 'c) t -> ('a, 'c) t
- val ( &&& ) : ('a, 'b1) t -> ('a, 'b2) t -> ('a, 'b1 * 'b2) t
-end = struct
- type ('a, 'b) t = 'a Value.t -> 'b Computation.t
-
- let arr f value =
- let%arr value = value in
- f value
- ;;
-
- let compose t1 t2 value =
- let%sub result = t1 value in
- t2 result
- ;;
-
- let ( &&& ) t1 t2 value =
- let%sub result1 = t1 value in
- let%sub result2 = t2 value in
- let%arr result1 = result1
- and result2 = result2 in
- result1, result2
- ;;
-end
-```
-
-## tl;dr: Can you summarize?
-
-Sure, here are my main takeaways:
-
-- `let%sub` enables sharing. You can compute a value once and use it
- in multiple sub-computations.
-
-- Avoid `let%map`. If you limit yourself to only using `let%sub` and
- `let%arr` you won't be able to accidentally duplicate work.
-
-- Thinking of `type ('a, 'b) t = 'a Value.t -> 'b Computation.t` as
- the "real" bonsai type (in some sense) was a huge conceptual
- simplification for me. For some reason, wrapping my head around what
- a `Value` *is* and what a `Computation` *is* was quite hard.
- However, it's immediately obvious how values of `type ('a, 'b) t`
- can be composed into arbitrarily large and complicated graphs, and
- being able to visualize the graph makes it more clear what I'm
- doing. That said, actually operating on that type is awkward for
- reasons outlined [in this post](./proc.md), which is why we have
- `Value` and `Computation`.
diff --git a/docs/blogs/rpc_effect.md b/docs/blogs/rpc_effect.md
deleted file mode 100644
index a792b841..00000000
--- a/docs/blogs/rpc_effect.md
+++ /dev/null
@@ -1,293 +0,0 @@
-Historically, Bonsai and Async haven't gotten along too well. Because
-Bonsai only works with pure programs and immutable values, the
-side-effectful and mutable nature of Async operations meant that the
-best architecture for web apps involved splitting the program into "pure
-bonsai" and "impure async" worlds. These two worlds were then connected
-by using specific APIs, like `Effect.of_deferred_fun` (which turns an
-Deferred-returning function into an Effect-returning function) and
-`Bonsai.Var.t` (which is used to track live data and inject it into
-Bonsai)
-
-The split was certainly annoying, but it did come with a side-benefit:
-pure Bonsai components are really easy to test because mocking out
-communication with the outside world is really easy.
-
-This year the team has been busy making Bonsai powerful enough to
-robustly deal with mutable values and impure functions, culminating in
-`Bonsai_web.Rpc_effect`, a module exposing first-class Bonsai components
-for performing RPCs, tracking their responses, and monitoring the status
-of your connections.
-
-Highlights of the [module's
-mli](https://ocaml.org/p/bonsai/v0.16.0/doc/Bonsai_web/Rpc_effect/index.html)
-include:
-
-```{=html}
-
-```
-- **Boilerplate-free:** Connection information is passed implicitly
- through the Bonsai computation graph, which means you don't need to
- thread rpc-sending effect parameters (or a `Connection.t`) through
- your whole app.
-- **Powerful combinators:** `Polling_state_rpc.poll` turns a polling
- state-RPC into a Bonsai component which automatically fetches new
- data when an input `'query Value.t` changes (and on a timer so that
- the server can forward updates).
-- **No compromises for testability:** Testing components that use this
- module is as easy as providing implementations for any of the RPCs
- invoked during each test. If your server's RPC implementations are
- in a `js_of_ocaml`-compatible library, you could even use those for
- tests.
-- **Helpful utilities:** `Status.state` is a computation for tracking
- whether the UI is connected to its server. If it has been
- disconnected, it also tracks how long ago it was last connected.
-
-# Migrating an existing app
-
-Suppose you have a Bonsai app whose main computation has the following
-type:
-
-``` ocaml
-val app
- : counters:int Map.M(String).t Value.t
- -> send_increment_rpc:(int -> unit Or_error.t Effect.t)
- -> send_decrement_rpc:(int -> unit Or_error.t Effect.t)
- -> Vdom.Node.t Computation.t
-```
-
-To migrate this signature to use the `Rpc_effect` module, follow these
-steps. The diffs below include just the essense of the migration; merely
-making these changes will yield type errors that should be
-straightforward enough to address.
-
-## Step 1 - Move all RPC dispatches into the main Bonsai computation.
-
-The goal in this step is to remove all the parameters to `app`.
-
-``` diff
- let app
-- ~counters
- ~send_increment_rpc
- ~send_decrement_rpc
- =
-+ let%sub counters =
-+ Rpc_effect.Polling_state_rpc.poll
-+ (module Unit)
-+ (module Counter_state)
-+ Protocol.Counter_state.t
-+ ~where_to_connect:Self
-+ ~every:(Time_ns.Span.of_ms 16.0)
-+ (Value.return ())
-+ in
-```
-
-The new `counters` variable has a different type than the old one, since
-`Rpc_effect.Polling_state_rpc.poll` has extra cases for handling when no
-response has yes been received or when the last response was an error.
-
-``` diff
- let app
-- ~send_increment_rpc
-- ~send_decrement_rpc
- =
-+ let%sub send_decrement_rpc =
-+ Rpc_effect.Rpc.dispatcher Protocol.Decrement_request.t ~where_to_connect:Self
-+ in
-+ let%sub send_increment_rpc =
-+ Rpc_effect.Rpc.dispatcher Protocol.Increment_request.t ~where_to_connect:Self
-+ in
-```
-
-## Step 2 - Delete the old connection logic.
-
-The `Rpc_effect` module creates its own connection for sending RPCs to
-`Self`. Thus, we should get rid of the old logic that connects and sends
-RPC connections. While most of the diff below consists of removing code
-made unused by previous changes, we include it all to demonstrate how
-much untestable code we get to remove. In addition, we also now get an
-opportunity to replace the `eprint_s` call below with a better error
-handling mechanism, such as giving the user a notification that they
-aren't connected to the server.
-
-``` diff
--let counters conn =
-- let client = Polling_state_rpc.Client.create Protocol.Counter_state.t in
-- let%map.Deferred.Or_error first_response =
-- let%bind conn = Rpc_connection.connected conn in
-- Polling_state_rpc.Client.dispatch client conn ()
-- in
-- let world_state_var = Bonsai.Var.create first_response in
-- Async_kernel.Clock_ns.every'
-- ~continue_on_error:true
-- (Time_ns.Span.of_sec (1.0 /. 30.0))
-- (fun () ->
-- match%map
-- let%bind conn = Rpc_connection.connected conn in
-- Polling_state_rpc.Client.dispatch client conn ()
-- with
-- | Ok () -> ()
-- | Error error ->
-- eprint_s [%message "Failed to poll counter state" (error : Error.t)]);
-- let (_ : _ Bus.Subscriber.t) =
-- Bus.subscribe_exn (Polling_state_rpc.Client.bus client) [%here] ~f:(fun _ ->
-- Bonsai.Var.set world_state_var)
-- in
-- Bonsai.Var.value world_state_var
--;;
--
--let send_increment_rpc conn =
-- Effect.of_deferred_fun (fun how_much ->
-- let%bind conn = Rpc_connection.connected conn in
-- Rpc.Rpc.dispatch Protocol.Increment_request.t conn how_much)
--;;
--
--let send_decrement_rpc conn =
-- Effect.of_deferred_fun (fun how_much ->
-- let%bind conn = Rpc_connection.connected conn in
-- Rpc.Rpc.dispatch Protocol.Increment_request.t conn how_much)
--;;
-
- let run () =
- Async_js.init ();
-- let conn =
-- Rpc_connection.create
-- ~server_name:"ws-server"
-- ~connect:(fun () -> Rpc.Connection.client ())
-- ~address:(module Unit)
-- Deferred.Or_error.return
-- in
-- let%bind counters = counters conn |> Deferred.Or_error.ok_exn in
-- let counters = Bonsai.Value.map counters ~f:Counter_state.counters in
-- let (_ : (unit, Nothing.t) Start.Handle.t) =
-- Start.start
-- Start.Result_spec.just_the_view
-- ~bind_to_element_with_id:"app"
-- (App.app
-- ~counters
-- ~send_increment_rpc:(send_increment_rpc conn)
-- ~send_decrement_rpc:(send_decrement_rpc conn))
-+ let (_ : (unit, Nothing.t) Start.Handle.t) =
-+ Bonsai_web.Start.start Start.Result_spec.just_the_view ~bind_to_element_with_id:"app" App.app
- in
- Deferred.never ()
-```
-
-## Step 3 - Provide RPC implementations for tests
-
-Since `Rpc_effect` causes tests to use `Async` machinery, we have to
-include this line near the top of each test file.
-
-``` ocaml
-open! Async_js_test
-```
-
-The main changes to test code include:
-
-- Pass RPC implementations to `Handle.create` instead of passing
- effect implementations to the app computation.
-- Use `yield_until_no_jobs_remain` to flush the async scheduler so
- that side-effects happen at the time you expect.
-- Use `return ()` at the end of your test, since the test uses async.
-
-``` diff
- let%expect_test "Click on the buttons" =
- let handle =
- Handle.create
-- (Result_spec.vdom Fn.id)
-- (App.app
-- ~counters:(Value.return String.Map.empty)
-- ~send_increment_rpc:(fun how_much ->
-- let%map.Effect () = Effect.print_s [%message "incremented" (how_much : int)] in
-- Ok ())
-- ~send_decrement_rpc:(fun how_much ->
-- let%map.Effect () = Effect.print_s [%message "decremented" (how_much : int)] in
-- Ok ()))
-+ ~rpc_implementations:
-+ [ Rpc.Rpc.implement' Protocol.Increment_request.t (fun _state how_much ->
-+ print_s [%message "incremented" (how_much : int)])
-+ ; Rpc.Rpc.implement' Protocol.Decrement_request.t (fun _state how_much ->
-+ print_s [%message "decremented" (how_much : int)])
-+ ]
-+ (Result_spec.vdom Fn.id)
-+ App.app
- in
- Handle.click_on handle ~get_vdom:Fn.id ~selector:"button.increment";
-+ let%bind () = Async_kernel_scheduler.yield_until_no_jobs_remain () in
- Handle.click_on handle ~get_vdom:Fn.id ~selector:"button.decrement";
-+ let%bind () = Async_kernel_scheduler.yield_until_no_jobs_remain () in
- [%expect {|
- (incremented (how_much 1))
-- (decremented (how_much 1)) |}]
-+ (decremented (how_much 1)) |}];
-+ return ()
- ;;
-```
-
-# Unconventional apps
-
-The assumption built into the steps above is that your app uses a single
-`Persistent_connection` to the host server through which all RPCs are
-sent. We have also assumed that there is no special connection logic.
-
-For apps that don't fit this pattern, the usage of `Rpc_effect` is
-mostly the same, except that you have to specify the connection
-yourself, rather than relying on the one created by `Rpc_effect` itself.
-
-## Step 1 - Extend the `Where_to_connect.Custom` variant.
-
-This variant case is the constructor used by the code that dispatches
-the RPC and also the code that provides the connection (as you'll see
-later).
-
-``` ocaml
-type Rpc_effect.Where_to_connect.Custom.t += Conn
-```
-
-## Step 2 - Use the new variant case whenever dispatching an RPC
-
-In the previous example, we used `Self` for all the `~where_to_connect`
-parameters, but since we're providing our own connection, we use
-`Custom Conn`.
-
-``` diff
- let%sub send_decrement_rpc =
-- Rpc_effect.Rpc.dispatcher Protocol.Decrement_request.t ~where_to_connect:Self
-+ Rpc_effect.Rpc.dispatcher Protocol.Decrement_request.t ~where_to_connect:(Custom Conn)
- in
- let%sub send_decrement_rpc =
-- Rpc_effect.Rpc.dispatcher Protocol.Decrement_request.t ~where_to_connect:Self
-+ Rpc_effect.Rpc.dispatcher Protocol.Decrement_request.t ~where_to_connect:(Custom Conn)
- in
-```
-
-## Step 3 - Provide your connection using a `Connector.t`
-
-The `Connector` module abstracts over different ways of obtaining
-connections, including `async_durable` and `persistent_connection`.
-
-In the app's startup code, provide a connector constructed with either
-`Rpc_effect.Connector.async_durable` or
-`Rpc_effect.Connector.persistent_connection`.
-
-``` ocaml
-let run () =
- let conn =
- Rpc_connection.create
- ~server_name:"ws-server"
- ~connect:(fun () -> Rpc.Connection.client ())
- ~address:(module Unit)
- Deferred.Or_error.return
- in
- let (_ : (unit, Nothing.t) Start.Handle.t) =
- Start.start
- Start.Result_spec.just_the_view
- ~bind_to_element_with_id:"app"
- ~custom_connector:(function
- | Conn -> Rpc_effect.Connector.persistent_connection (module Rpc_connection) conn
- | _ -> assert false)
- App.app
- in
- Deferred.never ()
-```
diff --git a/docs/blogs/testing.md b/docs/blogs/testing.md
deleted file mode 100644
index 604b9ed1..00000000
--- a/docs/blogs/testing.md
+++ /dev/null
@@ -1,377 +0,0 @@
-Traditional approaches for testing web applications can be infuriating.
-With tools like selenium or puppeteer, there's an entire headless
-browser running in the background, and not only do you need to find a
-way to reconfigure the app or library for testing, slow test execution
-and race condition-related bugs are a constant companion.
-
-Fortunately, Bonsai is built with functional purity in mind:
-
-- **Virtual_dom:** A type that you're likely familiar with,
- `Vdom.Node.t` is a pure representation of the impure DOM.
-
-- **Incremental:** While Incremental is primarily an implementation
- detail, the library itself is used to accelerate pure functions.
-
-Bonsai itself is all about building a DAG of components in a declarative
-manner, with statically tracked inputs to the graph, as well as a state
-machine for user interactivity. State machines and pure functions are
-fantastic for structuring programs, but they also lend themselves
-particularly well to testing.
-
-Instead of running an entire browser, the `Bonsai_web_test` library
-makes the assumption that the `Virtual_dom` is the source of truth, and
-this lets us run all of our tests in just OCaml, permitting us to use
-other utilities like `ppx_expect_test`.
-
-## Getting Ready For Testing
-
-Testing a program built using Js_of_ocaml involves a few changes to your
-normal workflow.
-
- (library (
- (name my_ui_test)
- (js_of_ocaml ()) ; Test library must be marked with js_of_ocaml
- (libraries (core my_ui))
- (inline_tests ( ; Native tests must be disabled
- (native dont_build_dont_run)
- (javascript build_and_run)))))
-
-Your jenga start-file also needs to specify the `javascript-runtest`
-alias for the project.
-
- (alias ((name build) (deps (
- (alias %{root}/app/my-app/test/javascript-runtest)
- ; ... your other build targets here...
- ))))
-
-## Basics of testing: printing the VDOM for a component
-
-Let's see what a test for the simplest Bonsai component would look like.
-
-First the component:
-
-```{=html}
-
-```
-``` ocaml
-let hello_world : Vdom.Node.t Computation.t =
- Bonsai.const (Vdom.Node.span [ Vdom.Node.text "hello world" ])
-;;
-```
-
-And now the test:
-
-```{=html}
-
-```
-``` ocaml
-module Handle = Bonsai_web_test.Handle
-module Result_spec = Bonsai_web_test.Result_spec
-
-let%expect_test "it shows hello world" =
- let handle = Handle.create (Result_spec.vdom Fn.id) hello_world in
- Handle.show handle;
- [%expect {| hello world |}]
-;;
-```
-
-With this very basic test, we can see two important aspects of testing
-using Bonsai: creating a `Handle.t`, and using it to print the contents
-of the component. The first argument to `Handle.create` is a value
-returned by `Bonsai_web_testing.Result_spec.vdom`, which finds a value
-of type `Vdom.Node.t` inside of the result of the component. For many
-applications and components that you'll want to test, the component
-simply has the type `Vdom.Node.t Computation.t`, so passing the identity
-function to `Result_spec.vdom` is sufficient. (We'll see later that more
-complex result specs can be used to test more complex components).
-
-Finally, `Handle.show` will print the contents of the current
-virtual-dom returned by the component.
-
-## A more dynamic component
-
-`Bonsai.const` can be useful, but it's certainly not the most exciting.
-To spice things up, let's build a component that operates on its input:
-
-```{=html}
-
-```
-``` ocaml
-let hello_user (name : string Value.t) : Vdom.Node.t Computation.t =
- let%arr name = name in
- Vdom.Node.span [ Vdom.Node.textf "hello %s" name ]
-;;
-```
-
-Now, in your app, the `name` parameter may be a `Value.t` that comes
-from the result of another computation, or it may originate from a
-`Bonsai.Var.t` that is updated from an RPC (or something similar to an
-RPC). To make things easier for us, let's use `Bonsai.Var.t` to get a
-mutable handle on a `Value.t` and see what happens when we change the
-Var.
-
-```{=html}
-
-```
-``` ocaml
-let%expect_test "shows hello to a user" =
- let user_var = Bonsai.Var.create "Bob" in
- let user = Bonsai.Var.value user_var in
- let handle = Handle.create (Result_spec.vdom Fn.id) (hello_user user) in
- Handle.show handle;
- [%expect {| hello Bob |}];
- Bonsai.Var.set user_var "Alice";
- Handle.show handle;
- [%expect {| hello Alice |}]
-;;
-```
-
-As expected, after changing the `Var.t`, the contents in the DOM are
-updated!
-
-For tests like this, where the contents of a component are printed more
-than once, we have a helper function that will print the diff between
-two versions of the view: `Handle.show_diff`. This is how you'd write
-the example above:
-
-```{=html}
-
-```
-``` ocaml
-let%expect_test "shows hello to a user" =
- let user_var = Bonsai.Var.create "Bob" in
- let user = Bonsai.Var.value user_var in
- let handle = Handle.create (Result_spec.vdom Fn.id) (hello_user user) in
- Handle.show handle;
- [%expect {| hello Bob |}];
- Bonsai.Var.set user_var "Alice";
- Handle.show_diff handle;
- [%expect
- {|
- -| hello Bob
- +| hello Alice
- |}]
-;;
-```
-
-While the diff in this instance isn't particularly illuminating, when
-testing components that produce hundreds of lines of output, it can be
-*much* easier to only review the diff.
-
-## A more interactive component
-
-Having values flow into the computation from outside is only one aspect
-of Bonsai's dynamism. Bonsai also permits components to maintain a state
-machine that can be transitioned by actions stemming from a user
-interacting with the view. Before talking about testing these
-components, let's first build one.
-
-Here, we actually use the `hello_user` component defined previously, but
-the `string Value.t` comes from the component-local state instead of a
-`Var`:
-
-```{=html}
-
-```
-``` ocaml
-let hello_textbox : Vdom.Node.t Computation.t =
- let%sub state, set =
- Bonsai.state "" ~sexp_of_model:[%sexp_of: String.t] ~equal:[%equal: String.t]
- in
- let%sub message = hello_user state in
- let%arr message = message
- and set = set in
- Vdom.Node.div
- [ Vdom.Node.input ~attrs:[ Vdom.Attr.on_input (fun _ text -> set text) ] (); message ]
-;;
-```
-
-This component is fully self-contained -- its type is
-`Vdom.Node.t Computation.t`, but that interior state is changeable by
-typing into the `` text-box.
-
-Testing it is similar to the first component that we tested. We'll start
-out with just printing its starting state, but after that,
-`Handle.input_text` makes an appearance, allowing us to trigger the
-`on_input` event listener.
-
-```{=html}
-
-```
-``` ocaml
-let%expect_test "shows hello to a specified user" =
- let handle = Handle.create (Result_spec.vdom Fn.id) hello_textbox in
- Handle.show handle;
- [%expect
- {|
-
- |}]
-;;
-```
-
-The parameters to `Handle.input_text` are
-
-1. The `handle`.
-2. `~get_vdom`: for some components, the result of the computation will
- be a record. A function is needed to pick out the vdom node that you
- actually care about interacting with. In our case, the computation
- returned just a vdom node, so we can use the identity function.
-3. `~selector`: this is a css selector that can be used to find the
- actual element in the vdom that we are going to type into.
-4. `~text`: the text to type into the text box.
-
-## A component that exposes an injection function
-
-Many components return a computation whose result contains "inject"
-functions (a function which returns `unit Vdom.Effect.t`). These
-functions can be used to provide nearby components access to a
-principled way of interacting with the state machine internal to that
-component.
-
-A great example of this would be the `Bonsai.state` component, which
-returns a value of this type:
-
-```{=html}
-
-```
-``` ocaml
-('model * ('model -> unit Ui_effect.t)) Computation.t
-```
-
-The second part of the tuple inside of the result is what we'd call an
-"injection function", and it can be called to set the internal state.
-
-Testing `Bonsai.state` (or really, any component that exposes an
-injection function) will require a custom view spec and a new `Handle`
-function.
-
-Without further ado, the test:
-
-```{=html}
-
-```
-``` ocaml
-module State_view_spec = struct
- type t = string * (string -> unit Vdom.Effect.t)
- type incoming = string
-
- let view (view, _) = view
- let incoming (_, incoming) = incoming
-end
-
-let%expect_test "test Bonsai.state" =
- let component : (string * (string -> unit Vdom.Effect.t)) Computation.t =
- Bonsai.state "hello" ~sexp_of_model:[%sexp_of: String.t] ~equal:[%equal: String.t]
- in
- let handle = Handle.create (module State_view_spec) component in
- Handle.show handle;
- [%expect {| hello |}];
- Handle.do_actions handle [ "world" ];
- Handle.show handle;
- [%expect {| world |}]
-;;
-```
-
-Instead of using the `Result_spec.vdom` helper function like before, we
-need to define our view-spec module that caters specifically to the type
-returned by `state`. Of note are the functions that extract the "view",
-and the inject function.
-
-Then, in the actual test, we call the new `do_actions` function, which
-passes its arguments on to the value setter function that the view spec
-extracted.
-
-## A component which depends on time
-
-Sometimes the behavior of a component depends on time. For example, a
-double-click component will want to check whether two clicks happened
-close together. An even simpler example is a basic clock component that
-might be implemented in the following way:
-
-```{=html}
-
-```
-``` ocaml
-let _untestable_component =
- let now = Incr.Clock.watch_now Incr.clock |> Bonsai.Incr.to_value in
- return (now >>| Time_ns.to_string_utc >>| Vdom.Node.text)
-;;
-```
-
-A large problem with this implementation is that there is no way to
-write a test for it. The rendered DOM output will contain a timestamp
-from when the test was run. Each time the test runs, the output
-timestamp will be different, thus causing the test to always fail.
-
-The correct solution is to use `Incr.Clock.with_clock` to gain access to
-the incremental clock that was passed to the Bonsai graph.
-
-```{=html}
-
-```
-``` ocaml
-let component =
- let%sub now = Bonsai.Incr.with_clock Bonsai.Time_source.watch_now in
- return (now >>| Time_ns.to_string_utc >>| Vdom.Node.text)
-;;
-```
-
-To test this component reliably, we must use \[Handle.advance_clock_by\]
-for the Bonsai graph to use.
-
-```{=html}
-
-```
-``` ocaml
-let%expect_test _ =
- let handle = Handle.create (Result_spec.vdom Fn.id) component in
- Handle.show handle;
- [%expect {| 1970-01-01 00:00:00.000000000Z |}];
- Handle.advance_clock_by handle (Time_ns.Span.of_sec 2.0);
- Handle.show handle;
- [%expect {| 1970-01-01 00:00:02.000000000Z |}]
-;;
-```
-
-The `with_clock` function has the following signature:
-
- val with_clock : (Incr.Clock.t -> 'a Incr.t) -> 'a Computation.t
-
-It allows for the incremental computation of some value that depends on
-the current time, while still keeping the logic testable.
-
-## Summary
-
-So far we've learned how to test a number of different aspects of Bonsai
-components:
-
-1. Components that have dynamic input
-2. Stateful components that have an interactive view
-3. Stateful components that return an inject function
-4. Components that depend on time
-
-It should go without saying that you could have a component with all
-four, and you'd be able to write comprehensive and deterministic tests.
diff --git a/docs/blogs/why_no_bind.md b/docs/blogs/why_no_bind.md
deleted file mode 100644
index f51ba8cd..00000000
--- a/docs/blogs/why_no_bind.md
+++ /dev/null
@@ -1,47 +0,0 @@
-Many have asked the question "`Value.t` is an Applicative, why isn't it
-a Monad?" Certainly, for the users of a library, having a Monad gives
-the user much more power. For example, take a look at this code using
-monadic bind:
-
-``` ocaml
-val x: bool t
-val y: 'a t
-val z: 'a t
-
-val bind: 'a t -> f:('a -> 'b t) -> 'b t
-
-bind x ~f:(fun x -> if x then y else z)
-```
-
-Look at that, we've built a `'a t` that dynamically chooses between
-either `y` or `z` depending on the value of `x`. Try to do the same with
-a type that is only an Applicative! (Do not try to do the same with an
-Applicative, it is impossible.)
-
-The dynamism on display is quite useful, but sadly, it's dynamism that
-would hurt Bonsai if `Value.t` were a Monad. The main issue is that
-dynamism would prevent "whole-program analysis" of the app, and this
-would hurt a few neat features of Bonsai:
-
-1. Because we have "whole-program analysis," Bonsai runs an
- optimization pass on the program, resulting in a seriously condensed
- incremental graph. If an app were to dynamically generate bonsai
- values, we would potentially have to run the optimization many times
- a second, slowing things down considerably.
-2. Another benefit (that we haven't taken advantage of yet) is that if
- we know about the whole program, we could insert "debug nodes" into
- the graph and present a "bonsai debugger" that shows the live values
- of any component's model or input.
-
-Another issue that comes with dynamism is that it becomes difficult to
-reason about the "stateful" bits of Bonsai with components that have
-models. If a dynamic node were to toggle between components, the models
-for the entire dynamic sub-tree would be lost during the transition.
-Going even further, dynamism is fairly slow in Incremental, the library
-that Bonsai is built on top of.
-
-In practice, preventing Bonsai.t from implementing the Monad interface
-has worked out pretty well. Every time that someone has wanted a feature
-that they could get via Monads, the Bonsai-dev team has found an
-alternative API that solved their need. For example `Bonsai.if_` is a
-great substitute for the code provided at the top of this post.
diff --git a/docs/getting_started/counters.md b/docs/getting_started/counters.md
deleted file mode 100644
index b770a346..00000000
--- a/docs/getting_started/counters.md
+++ /dev/null
@@ -1,272 +0,0 @@
-# Bonsai by Example: Web Counters
-
-This app just puts counters on the page, each with buttons to increment
-and decrement the number.
-
-## Building the example
-
-You'll notice that there are two directories there: `bin/`, where the
-`main.bc.js` file that you're building lives, and `lib/`, which houses
-the important application code. As in [the "hello world"
-example](./hello_world.mdx), the "app" consists of an *index.html* page
-that includes the JS file for the app; the app is attached to the "app"
-div on that page.
-
-To run:
-
-`sh skip $ cd lib/bonsai/examples/counters/bin; python3 -m http.server`
-
-Then navigate to http://localhost:8000
-
-## Your first components
-
-In Bonsai, you'll hear a lot about "components." What is a component?
-
-A component is an encapsulated bit of UI logic. It has **input** --
-immutable data that comes from "outside" that component, say from
-another component or from an RPC. It has a **model**, which is the
-mutable internal state of that component. And it has a **result**, which
-can be of any type, but eventually, for the top-level component of your
-app, will include a `Vdom.Node.t`.
-
-The clearest API for building a component is via `Bonsai.of_module`.
-Inspecting its type, you can see that it takes a module requiring these
-three parts: an input, a model, and a result:
-
-``` ocaml
-# Bonsai.of_module0
-- : ?sexp_of_model:('m -> Sexplib0.Sexp.t) ->
- ?equal:('m -> 'm -> bool) ->
- (unit, 'm, 'a, 'r) Bonsai__Import.component_s ->
- default_model:'m -> 'r Bonsai.Computation.t
-=
-```
-
-As examples, in the Counters app, the "Add Another Counter" button is a
-component, and each counter--a number, plus two buttons to increment and
-decrement it--is another component.
-
-### Counter_component
-
-```{=html}
-
-```
-``` sh
-$ sed -n -e '/\[CODE_EXCERPT_BEGIN 1\]/,/\[CODE_EXCERPT_END 1\]/p' ../../examples/counters/lib/bonsai_web_counters_example.ml | tail -n +2 | head -n -2
-
-module Action = struct
- type t =
- | Increment
- | Decrement
- [@@deriving sexp_of]
-end
-
-let single_counter =
- let%sub counter_state =
- Bonsai.state_machine0
- ()
- ~sexp_of_model:[%sexp_of: Int.t]
- ~equal:[%equal: Int.t]
- ~sexp_of_action:[%sexp_of: Action.t]
- ~default_model:0
- ~apply_action:(fun (_ : _ Bonsai.Apply_action_context.t) model -> function
- | Action.Increment -> model + 1
- | Action.Decrement -> model - 1)
- in
- let%arr state, inject = counter_state in
- let button label action =
- Vdom.Node.button
- ~attrs:[ Vdom.Attr.on_click (fun _ -> inject action) ]
- [ Vdom.Node.text label ]
- in
- Vdom.Node.div
- [ button "-1" Action.Decrement
- ; Vdom.Node.text (Int.to_string state)
- ; button "+1" Action.Increment
- ]
-;;
-```
-
-Its model -- the component's state machine -- is an int. That's the
-number we increment and decrement. Finally, its result is a
-`Vdom.Node.t`, the little bit of DOM that renders that individual
-counter:
-
-``` html
-
-
- 4
-
-
-```
-
-All of the interesting code here has to do with actions, which you can
-think of as defining the state machine's transitions: here, just
-`Increment` and `Decrement`.
-
-**The compute function**
-
-The body of the `let%arr` expression is the heart of the component. In
-this component, it looks at the state and injection function and builds
-the view out of them.
-
-**The inject function**
-
-`compute`'s inject function is just a callback that converts actions
-into DOM events. These events are how Bonsai communicates to the browser
-to actually do something when, say, a user clicks a button. Here, we
-hook up the `"on_click'` attribute of the "+1" and "-1" buttons to the
-corresponding DOM events:
-
-`ocaml skip let button label action = let on_click = Vdom.Attr.on_click (fun _ -> inject action) in Vdom.Node.button [ on_click ] [ Vdom.Node.text label ] in`
-
-**The apply_action function**
-
-When an action is raised by a component via a `unit Ui_effect.t`, Bonsai
-will eventually pass that action back to the component's `apply_action`
-function. This function is responsible for looking at the model and the
-incoming action and producing a new model.
-
-`ocaml skip val apply_action : inject:(Action.t -> unit Ui_effect.t) -> schedule_event:(unit Ui_effect.t -> unit) -> Input.t -> Model.t -> Action.t -> Model.t`
-
-During the transformation, the component can also emit more actions via
-`schedule_event` or use Async to arrange for `schedule_event` to be
-called later. (For this it will use the same `inject` callback as
-before.) This enables quite a bit of UI dynamism. Here, we don't emit
-any further actions; we just increment or decrement the model:
-
-`ocaml skip let apply_action ~inject:_ ~schedule_event:_ () model = function | Action.Increment -> model + 1 | Action.Decrement -> model - 1 ;;`
-
-### Add_counter_component
-
-```{=html}
-
-```
-``` sh
-$ sed -n -e '/\[CODE_EXCERPT_BEGIN 2\]/,/\[CODE_EXCERPT_END 2\]/p' ../../examples/counters/lib/bonsai_web_counters_example.ml | tail -n +2 | head -n -2
-module Model = struct
- type t = unit Int.Map.t [@@deriving sexp, equal]
-end
-
-let add_counter_component =
- let%sub add_counter_state =
- Bonsai.state_machine0
- ()
- ~sexp_of_model:[%sexp_of: Model.t]
- ~equal:[%equal: Model.t]
- ~sexp_of_action:[%sexp_of: Unit.t]
- ~default_model:Int.Map.empty
- ~apply_action:(fun (_ : _ Bonsai.Apply_action_context.t) model () ->
- let key = Map.length model in
- Map.add_exn model ~key ~data:())
- in
- let%arr state, inject = add_counter_state in
- let view =
- Vdom.Node.button
- ~attrs:[ Vdom.Attr.on_click (fun _ -> inject ()) ]
- [ Vdom.Node.text "Add Another Counter" ]
- in
- state, view
-;;
-```
-
-In Bonsai, "everything is a component," including the app itself. That
-is, the app that we'll attach to this example's *index.html* page is a
-component made of other components. In that component, the model is a
-map from ints to units. The keys are just indexes: 0, 1, 2, etc. And
-each value in the map is a placeholder for one of the little gizmos
-defined by the Counter_component and added one at a time by pressing the
-button defined by the Add_counter_component.
-
-Notice that in the `apply_action` function immediately above, we just
-initialize a new counter's model to `()` and set its index to the
-current length of the map (a trick for getting auto-incrementing
-indexes).
-
-#### Using assoc_model to make a single component out of a map
-
-Turning a map of individual counters into a single component that
-governs all of them is easy in Bonsai. Indeed, this is where you start
-seeing Bonsai's comparative advantage over Incr_dom, our previous web
-framework. Bonsai was designed precisely with this sort of "projection"
-in mind.
-
-All it takes is this bit of code:
-
-`ocaml skip let%sub counters = Bonsai.assoc (module Int) map ~f:(fun _key _data -> single_counter) in`
-
-let%sub is a ppx for variable substitution, very similar to the standard
-monadic `bind`, but with this signature:
-
-``` ocaml
-# Bonsai.Let_syntax.Let_syntax.sub
-- : ?here:Lexing.position ->
- 'a Bonsai.Computation.t ->
- f:('a Bonsai.Cont.t -> 'b Bonsai.Computation.t) ->
- 'b Bonsai.Computation.t
-=
-```
-
-Then the `assoc` function is used to "project" the int map over the
-counter component to yield a map of counter components. Its signature
-is:
-
-``` ocaml
-# Bonsai.assoc
-- : ('key, 'cmp) Bonsai.comparator ->
- ('key, 'data, 'cmp) Base.Map.t Bonsai.Cont.t ->
- f:('key Bonsai.Cont.t ->
- 'data Bonsai.Cont.t -> 'result Bonsai.Computation.t) ->
- ('key, 'result, 'cmp) Base.Map.t Bonsai.Computation.t
-=
-```
-
-You can think of it as taking a single inner component and "projecting"
-it into the map---i.e., returning a map-ish supercomponent. The
-supercomponent can be used to easily add new copies of the inner
-component (here, the individual counters), remove them, count over them,
-etc.
-
-Here, all we do with `counters` is take its `Map.data`--a list of
-`Vdom.Node.t`'s--and plunk them in a div:
-
-`ocaml skip Vdom.Node.div [] [ add_button; Vdom.Node.div [] (Map.data counters) ]`
-
-#### Composing components using Bonsai.map2, sub, and the Let_syntax
-
-It all comes together in the last few lines of the program, which
-produce our toplevel component:
-
-```{=html}
-
-```
-``` sh
-$ sed -n -e '/\[CODE_EXCERPT_BEGIN 3\]/,/\[CODE_EXCERPT_END 3\]/p' ../../examples/counters/lib/bonsai_web_counters_example.ml | tail -n +2 | head -n -2
-let application =
- let open Bonsai.Let_syntax in
- let%sub map, add_button = add_counter_component in
- let%sub counters =
- Bonsai.assoc (module Int) map ~f:(fun _key _data -> single_counter)
- in
- let%arr add_button = add_button
- and counters = counters in
- Vdom.Node.div [ add_button; Vdom.Node.div (Map.data counters) ]
-;;
-```
-
-The application component is what we ultimately plug into the "app" div
-of our *index.html* file. It's a `Vdom.Node.t Computation.t`, i.e., its
-result is the DOM for the whole app.
-
-You can get a better sense of how Bonsai's composition works by looking
-at a de-sugared version of the `application` function:
-
-`ocaml skip let application_sugar_free = let open Bonsai.Let_syntax in Let_syntax.sub (Bonsai.of_module0 (module Add_counter_component) ~default_model:Model.default) ~f:(fun add_counter -> let map = Value.map add_counter ~f:(fun (map, _) -> map) in let add_button = Value.map add_counter ~f:(fun (_, add_button) -> add_button) in Let_syntax.sub (Bonsai.assoc (module Int) map ~f:(fun _key _data -> single_counter)) ~f:(fun counters -> return (Value.map2 add_button counters ~f:(fun add_button counters -> Vdom.Node.div [] [ add_button; Vdom.Node.div [] (Map.data counters) ])))) ;;`
-
-The `Value.map` is used twice on the `add_counter` to destructure its
-parts--the add button itself, and the map of counters. The
-`Bonsai.assoc` projects the `single_counter` component over this map.
-And finally, the `Value.map2` allows us to combine components.
diff --git a/docs/getting_started/hello_world.md b/docs/getting_started/hello_world.md
deleted file mode 100644
index 04504f22..00000000
--- a/docs/getting_started/hello_world.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# Building the "Hello world" Bonsai app
-
-Most OCaml programs result in an .exe file that you run via the command
-line. But Bonsai uses
-[js_of_ocaml](https://github.com/ocsigen/js_of_ocaml) to produce a
-JavaScript file with the extension `.bc.js` (the "bc" for "bytecode")
-that is then included in an HTML page.
-
-To see this in action, let's walk through the process of running the
-simple example in ../../examples/hello_world.
-
-## Step 1: The build
-
-Follow the Dune instructions for [building javascript
-executables](https://dune.readthedocs.io/en/stable/jsoo.html)
-
-Once you mark a library or executable with `js_of_ocaml`, you are in
-effect declaring that your only dependencies will also be
-`js_of_ocaml`-compatible libraries. In particular, you're disallowed
-from using libraries that won't work in a Javascript runtime, like
-Core_unix or Async -- instead, you have to use their non-Unix versions
-Core and Async_kernel.
-
-## Step 2: The HTML scaffold
-
-Remember that Bonsai emits a Javascript file with your app's logic. To
-get this Javascript to run, we have to include it on the HTML page that
-will ultimately be served to your browser. We do that by putting a
-`
-
-
-
-
-