···11+# Per-cell filename attribute and message protocol migration
22+33+## Problem
44+55+Two related issues:
66+77+1. **No filename support for Merlin.** All code cells are treated as `.ml`
88+ (implementation) by Merlin. Tutorials that show `.mli` interface syntax
99+ get incorrect error diagnostics because Merlin parses them as
1010+ implementation code. Merlin determines `.ml` vs `.mli` from
1111+ `Mconfig.query.filename` — currently unset, defaulting to `Impl`.
1212+1313+2. **x-ocaml's jtw_client still uses the old RPC layer.** The js_top_worker
1414+ `worker.ml` already speaks the new JSON message protocol (`message.ml`),
1515+ and a matching client library (`js_top_worker_client_msg.ml`) exists.
1616+ But x-ocaml's `jtw_client.ml` still uses `Js_top_worker_client_fut`
1717+ (the old `deriving rpcty` / IDL-based client), creating an unnecessary
1818+ dependency on `js_top_worker-rpc`, `rpclib`, and `ppx_deriving_rpc`.
1919+2020+## Design
2121+2222+### Part 1: Port jtw_client.ml to the message protocol
2323+2424+**x-ocaml/src/dune** — Replace `js_top_worker-client_fut` with
2525+`js_top_worker-client.msg`.
2626+2727+**x-ocaml/src/jtw_client.ml** — Rewrite to use
2828+`Js_top_worker_client_msg` instead of `Js_top_worker_client_fut`. The
2929+new client speaks JSON messages directly — no type conversions from
3030+`Toplevel_api_gen` needed.
3131+3232+The current `jtw_client.ml` converts between three type systems:
3333+- `X_protocol` (x-ocaml's internal protocol)
3434+- `Toplevel_api_gen` (the old RPC types)
3535+- `Protocol` (merlin-js protocol types for completions, errors, etc.)
3636+3737+After the port, `jtw_client.ml` converts only between:
3838+- `X_protocol` (x-ocaml's internal protocol)
3939+- `Js_top_worker_message.Message` (simple record types with string fields)
4040+4141+The `Js_top_worker_client_msg` API:
4242+- `create url` — create worker, returns `t`
4343+- `init t config` — initialise, returns `unit Lwt.t`
4444+- `eval t ?env_id code` — evaluate code, returns `output Lwt.t`
4545+- `eval_stream t ?env_id code` — streaming eval, returns `eval_event Lwt_stream.t`
4646+- `complete t ?env_id source position` — completions, returns `Msg.completions Lwt.t`
4747+- `type_at t ?env_id source position` — types, returns `Msg.type_info list Lwt.t`
4848+- `errors t ?env_id source` — errors, returns `Msg.error list Lwt.t`
4949+- `create_env t env_id` / `destroy_env t env_id` — environment management
5050+- `terminate t` — shut down worker
5151+5252+### Part 2: Add filename to the message protocol
5353+5454+**js_top_worker/idl/message.ml** — Add `filename : string option` to
5555+the three Merlin-facing client messages:
5656+5757+```ocaml
5858+| Complete of { cell_id : int; env_id : string; source : string;
5959+ position : int; filename : string option }
6060+| TypeAt of { cell_id : int; env_id : string; source : string;
6161+ position : int; filename : string option }
6262+| Errors of { cell_id : int; env_id : string; source : string;
6363+ filename : string option }
6464+```
6565+6666+Add JSON serialization (`"filename"` field, omitted when None) and
6767+parsing (with `get_string_opt` for backwards compatibility).
6868+6969+**js_top_worker/idl/js_top_worker_client_msg.ml** — Add optional
7070+`?filename` parameter to `complete`, `type_at`, `errors`.
7171+7272+**js_top_worker/client/ocaml-worker.js** — Add optional `filename`
7373+parameter to the JS client methods.
7474+7575+**js_top_worker/lib/impl.ml** — Accept optional filename in `config`:
7676+7777+```ocaml
7878+let config ?filename () =
7979+ let initial = Merlin_kernel.Mconfig.initial in
8080+ let query = match filename with
8181+ | Some f -> { initial.query with filename = f }
8282+ | None -> initial.query
8383+ in
8484+ { initial with
8585+ merlin = { initial.merlin with stdlib = Some path };
8686+ query }
8787+```
8888+8989+Thread through `wdispatch`, `query_errors`, `complete_prefix`, and
9090+`type_enclosing`.
9191+9292+**js_top_worker/lib/worker.ml** — Extract filename from messages and
9393+pass to `M.query_errors`, `M.complete_prefix`, `M.type_enclosing`.
9494+This requires the `Impl.Make(S)` functions to accept an optional
9595+`?filename` parameter.
9696+9797+### Part 3: x-ocaml integration
9898+9999+**x-ocaml WebComponent** — Read `data-filename` from the element, pass
100100+it through when sending Merlin requests via the backend.
101101+102102+**x-ocaml/src/jtw_client.ml** — Thread filename from x-ocaml to the
103103+`Js_top_worker_client_msg` API.
104104+105105+**Authoring format** — A new per-cell attribute:
106106+107107+```
108108+{@ocaml interactive filename=map.mli [
109109+val map : ('a -> 'b) -> 'a list -> 'b list
110110+]}
111111+```
112112+113113+The odoc plugin emits `<x-ocaml mode="interactive" data-filename="map.mli">`.
114114+115115+## What doesn't change
116116+117117+- `X_protocol` — the builtin merlin-js backend still uses this.
118118+- `Protocol` (merlin-js.protocol) — still used by the builtin backend
119119+ and merlin-codemirror.
120120+- `add_cmi` in impl.ml — cell dependency CMI generation is always `.ml`.
121121+- `exec` / `Eval` messages — execution always goes through `Toploop`
122122+ which is `.ml` toplevel syntax.
123123+124124+## Backwards compatibility
125125+126126+The `filename` field is optional in the JSON protocol. Workers that don't
127127+understand it will ignore it. Clients that don't send it get the existing
128128+behaviour (treated as `.ml`).