this repo has no description

fix: resolve module availability and import paths for widget test

- Add Widget.View re-export of Widget_view for toplevel access
- Copy ocaml-worker.js to build output via dune rule
- Use worker.bc.js for local dev testing
- Update test code to use Widget.View instead of Widget_view
- Add diagnostic eval and error logging to test page

All four widget tests now pass in browser: static widget,
interactive counter, slider, and cross-cell signal propagation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+44 -16
+6
example/dune
··· 41 41 (libraries js_top_worker-web logs.browser mime_printer tyxml js_top_worker-widget note)) 42 42 43 43 (rule 44 + (target ocaml-worker.js) 45 + (deps ../client/ocaml-worker.js) 46 + (action (copy ../client/ocaml-worker.js %{target}))) 47 + 48 + (rule 44 49 (targets 45 50 (dir _opam)) 46 51 (action ··· 63 68 index4.html 64 69 index5.html 65 70 widget_test.html 71 + ocaml-worker.js 66 72 _opam 67 73 server.py 68 74 (alias_rec all)))
+22 -9
example/widget_test.html
··· 17 17 <div id="log"></div> 18 18 19 19 <script type="module"> 20 - import { OcamlWorker } from '../client/ocaml-worker.js'; 20 + import { OcamlWorker } from './ocaml-worker.js'; 21 21 22 22 const logEl = document.getElementById('log'); 23 23 const widgetsEl = document.getElementById('widgets'); ··· 86 86 87 87 async function run() { 88 88 log('Creating worker...'); 89 - worker = new OcamlWorker('_opam/worker.js', { 89 + worker = new OcamlWorker('worker.bc.js', { 90 90 onWidgetUpdate: (msg) => { 91 91 log('WidgetUpdate: id=' + msg.widget_id); 92 92 renderWidget(msg.widget_id, msg.view); ··· 108 108 }); 109 109 log('Worker ready.'); 110 110 111 + // Helper to log eval results with errors 112 + function logResult(label, r) { 113 + if (r.caml_ppf) log(label + ': ' + r.caml_ppf); 114 + if (r.stderr) log('STDERR: ' + r.stderr); 115 + } 116 + 117 + // Diagnostic: check module availability 118 + log('\n--- Diagnostic: module availability ---'); 119 + const d1 = await worker.eval('module W = Widget;;'); 120 + logResult('Widget', d1); 121 + const d2 = await worker.eval('let _ = Widget.View.Text "test";;'); 122 + logResult('Widget.View', d2); 123 + 111 124 // Test 1: Static widget 112 125 log('\n--- Test 1: Static widget ---'); 113 126 const r1 = await worker.eval( 114 127 'Widget.display ~id:"hello" ~handlers:[] ' + 115 - '(Widget_view.Element { tag = "div"; attrs = []; ' + 116 - 'children = [Widget_view.Text "Hello from OCaml!"] });;' 128 + '(Widget.View.Element { tag = "div"; attrs = []; ' + 129 + 'children = [Widget.View.Text "Hello from OCaml!"] });;' 117 130 ); 118 - log('Eval: ' + r1.caml_ppf); 131 + logResult('Eval', r1); 119 132 120 133 // Test 2: Interactive counter with Note 121 134 log('\n--- Test 2: Interactive counter ---'); ··· 130 143 ' Note.S.accum 0 delta;;', 131 144 '', 132 145 'let counter_view n =', 133 - ' let open Widget_view in', 146 + ' let open Widget.View in', 134 147 ' Element { tag = "div"; attrs = [Class "counter"]; children = [', 135 148 ' Element { tag = "button";', 136 149 ' attrs = [Handler ("click", "dec")];', ··· 155 168 ' (Widget.update ~id:"counter");;', 156 169 'Note.Logr.hold _logr;;', 157 170 ].join('\n')); 158 - log('Eval: ' + r2.caml_ppf); 171 + logResult('Eval', r2); 159 172 160 173 // Test 3: Slider with cross-cell signal 161 174 log('\n--- Test 3: Slider ---'); ··· 164 177 'let x = Note.S.hold 50 x_e;;', 165 178 '', 166 179 'let slider_view v =', 167 - ' let open Widget_view in', 180 + ' let open Widget.View in', 168 181 ' Element { tag = "div"; attrs = []; children = [', 169 182 ' Element { tag = "label"; attrs = [];', 170 183 ' children = [Text (Printf.sprintf "X: %d" v)] };', ··· 192 205 log('\n--- Test 3b: Derived widget (uses x from cell above) ---'); 193 206 await worker.eval([ 194 207 'let doubled_view v =', 195 - ' let open Widget_view in', 208 + ' let open Widget.View in', 196 209 ' Element { tag = "div"; attrs = []; children = [', 197 210 ' Text (Printf.sprintf "2x = %d" (v * 2))', 198 211 ' ] };;',
+3
widget/widget.ml
··· 1 1 open Js_top_worker_message.Message 2 2 3 + (** Re-export Widget_view so user code can write [Widget.View.Element] etc. *) 4 + module View = Js_top_worker_message.Widget_view 5 + 3 6 (* --- Send function, injected by worker at startup --- *) 4 7 5 8 let sender : (string -> unit) ref = ref (fun _ -> ())
+13 -7
widget/widget.mli
··· 1 1 (** Interactive widget support for the OCaml toplevel. 2 2 3 3 Widgets are rendered in the client as HTML elements built from 4 - {!Js_top_worker_message.Widget_view.node} trees. Event handlers in the view 5 - are symbolic string identifiers — when the user interacts with a widget, 6 - the client sends the handler ID and input value back to the worker, 7 - where the registered callback is invoked. 4 + {!View.node} trees. Event handlers in the view are symbolic string 5 + identifiers — when the user interacts with a widget, the client sends 6 + the handler ID and input value back to the worker, where the registered 7 + callback is invoked. 8 8 9 9 Typical usage with Note FRP: 10 10 {[ ··· 12 12 let s = Note.S.hold 50 e 13 13 14 14 let () = 15 + let open Widget.View in 15 16 Widget.display ~id:"my-slider" 16 17 ~handlers:["x", (fun v -> 17 18 send (int_of_string (Option.get v)))] 18 - (Js_top_worker_message.Widget_view.Element { tag = "input"; 19 + (Element { tag = "input"; 19 20 attrs = [Property ("type", "range")]; 20 21 children = [] }) 21 22 ··· 25 26 (Widget.update ~id:"my-slider") 26 27 ]} *) 27 28 29 + (** Re-export of {!Js_top_worker_message.Widget_view} for convenient access 30 + from toplevel code. Use [let open Widget.View in ...] to access 31 + constructors like [Element], [Text], [Property], [Handler], etc. *) 32 + module View = Js_top_worker_message.Widget_view 33 + 28 34 val display : 29 35 id:string -> 30 36 handlers:(string * (string option -> unit)) list -> 31 - Js_top_worker_message.Widget_view.node -> 37 + View.node -> 32 38 unit 33 39 (** [display ~id ~handlers view] registers a widget with the given [id], 34 40 installs [handlers] for routing incoming events, and sends the 35 41 initial [view] to the client. If a widget with this [id] already 36 42 exists, it is replaced. *) 37 43 38 - val update : id:string -> Js_top_worker_message.Widget_view.node -> unit 44 + val update : id:string -> View.node -> unit 39 45 (** [update ~id view] sends an updated view for an existing widget. 40 46 The handler map is not changed. *) 41 47