web: port the LUI web backend to pure OCaml/Melange - #63
Merged
Merged
Conversation
…e stubs Porting foundation for the LG web.cljc backend to OCaml/Melange: - core/lui_web_types.ml: renderer, retained mirror, simulator, extension types - core/lui_web_util.ml: shared DOM helpers and DOM-type coercions - core/lui_web_store.ml: retained mirror + batch validation port - Feature-module stubs (nodes/render/widgets/focus/popup/events/shell) so parallel porting compiles; PORTING.md documents ownership and conventions. - dune: include_subdirs unqualified + lui/melange.dom deps (needed for the feature-folder layout requested for this port)
… dom lookups, dropdown anchors
Ports web.cljc 3137-3345 and 5439-5597 to OCaml/Melange: clamp-popup-axis, resolved-popup-side, position-anchored!, position-tooltip!, begin-popup-open!/close!, prefers-reduced-motion?, transition-event-from?, after-transition!, finish-popup-close-after-transition!, align-select-item-with-trigger!, position-dropdown!, point-in-triangle?, submenu-corridor?. Picker lookup helpers are replicated locally since position sits below menu in the module layering.
- shell/lui_web_apply.ml: apply_dom_op/apply_dom_batch covering CreateNode/CreateExtension/DropNode/SetProp/RemoveProp/ SetExtensionProp/RemoveExtensionProp/InsertChild/RemoveChild/MoveChild, portal routing (dropdown/modal/tooltip/context-menu/toast), bottom-tabs page+trigger handling, visible-child-index hiding popup kinds, focus save/restore across moves, post-batch roving refresh. - shell/lui_web_extensions.ml: extension adapter lookup, platform node creation, prop apply/remove, cleanup (split out so apply can use it without a lui_web cycle; lui_web re-exports for API parity). - Stub arity/return-type fixes so callers compile clean under warnings-as-errors.
…-focus from web.cljc
…elange Ported from web.cljc: - 866-1055 picker/combobox plumbing (picker-dropdown ... activate-menu-item!) - 1261-1322 attach-picker-press-event! / attach-picker-trigger-events! - 1694-1710 dropdown-group-contains-event? - 2047-2210 attach-dropdown-events! (pointer/click/resize/key handlers, typeahead) - 2806-3131 context-menu (direct-context-menu ... attach-context-menu-events!) - 5597-5794 set-dropdown-open! / mount-dropdown! (submenu hover wiring) - 5843-5860 update-picker-expanded! / 5894-5907 remove-dropdown-after-exit! Popup transition helpers (begin_popup_open/close, after_transition, finish_popup_close_after_transition) implemented locally since Lui_web_position does not expose them yet.
…ass refresh, accordion open/close, alignment and select-display helpers
Port web.cljc split (split-base-fraction … attach-split-events) and widgets (update-icon-name … select-display-text) ranges to OCaml: fraction reconciliation with resize-origin animation, divider drag and keyboard adjust, image/media-surface registries, avatar/image crop math, stepper/timeline/bottom-tabs/progress updaters.
…n/lui-web-backend # Conflicts: # platform/web/melange/popup/lui_web_overlay.ml
Replaces the LG-compiled demo entry with OCaml: web_main.ml ports the web entry from web_main.cljc (simulator-map/camera/native-card/gallery-accent adapters, simulator toolbar outside #app, gallery shell nav by root section), and web_bootstrap.ml mounts it on #app. The gallery itself reuses examples/gallery's gallery_app library (model/view/extension_schemas) instead of re-porting gallery.cljc; gallery_app gains the melange mode so melange.emit can link it. index.html keeps the same shell and e2e hooks, with an import map over the lui-components-web emit node_modules (no lg.* entries).
…n/lui-web-backend
… on tree rows - store: absent Enabled means enabled (only Some(false) disables) - nodes/props: Dialog|Sheet get heading+description slots in title div - runtime: ToggleChanged echo check consults Expanded on tree rows, restoring ArrowLeft collapse in trees - web demo: backtrace on mount error; web gallery shows native extension + tweaks sections via simulator extensions - simulator: JS float formatting for --lui-device-scale; e2e waits 300ms for accent transition
…n class
- vite plugin: watch examples/**/*.ml(i), wait for dune emit to settle,
invalidate module graph, send full-reload (replaces LG runtime
root-swap which no longer exists in pure OCaml)
- dev_web.mjs: opam exec --switch=default so melange-webapi resolves
- hot-reload.e2e.mjs: rewritten for the Melange pipeline (source edit →
reload instead of LG HMR; CSS/JS HMR semantics unchanged)
- nodes/css: dialog|sheet description slot renamed to
.lui-modal-description (tailwind contract bans
.lui-{dialog,sheet}-{header,content,footer,description})
The e2e composition tests assert on .lui-combobox-query-value to read the model-owned query; the OCaml gallery port dropped the class the original cljc gallery set on that text element.
…n/lui-web-backend
…rols Port fidelity fixes matching the original gallery.cljc: - button_group/toggle_group/breadcrumb/pagination used ~accessibility_identifier (renders id=) where the original used :accessibility-label (renders aria-label=). Switch to ~label so the roving-group containers expose their accessible names. - The vertical Insert toolbar's checkbox/select/input were wired to model.disabled (initially false); the original passed a constant-true toolbar-disabled-source, so the controls are always-disabled showcase cases. Use ~disabled:true.
…evin/lui-web-backend
…task MenuTrigger nodes render as .lui-menu-item but every menu-row check only matched MenuItem, so submenu triggers never got data-submenu-trigger/ aria-expanded, press/highlight events, menuitem roles, or inclusion in keyboard-nav item lists — submenu mouseenter left aria-expanded null. Add Store.menu_item_row covering MenuItem | MenuTrigger and use it in menu item collection, role refresh, context-menu focus items, submenu trigger detection, dropdown anchor resolution, and submenu cleanup. Route MenuTrigger visible text to its label span so the icon child is not wiped. Nested dropdowns hanging off a menu row inside another menu default to data-anchor=right when no explicit anchor is set, matching the original gallery's explicit right-anchored submenus. mount_picker_dropdown deferred its item-focus/active-descendant work with setTimeout(0) because item DOMs are inserted by later ops in the same batch; the timer task could lose to a subsequent scripted assertion. Use a microtask instead — still after the batch unwinds but before any later-queued task.
…vin/lui-web-backend
…pages - accordion: add ?selected_signal so open state round-trips reactively; gallery binds ~selected:(reactive open_) instead of a one-shot sample - lui_runtime: event_is_value_echo falls back to Selected for ToggleChanged when Checked/Expanded are absent (accordion, drawer, toggle-button) - gallery: BottomTabs panel titles use styled text instead of role=heading - combine empty_state title uses styled text instead of role=heading - gallery: wrap platform tweak demo in a Tweaks section so its page has a title - e2e: nav count 66 -> 69 in overlay and firefox suites (kept in sync)
…in/lui-web-backend
Dune replaces the whole emit tree on rebuild; the changed module settles before sibling entries reappear, so the previous version could trigger a reload into a transiently-incomplete tree.
Contributor
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the deleted LG web backend (
platform/web/lg/lui/backend/web.cljc, ~6400 lines) with a pure OCaml/Melange backend inplatform/web/melange/(~9400 lines), soluicompiles the whole UI stack to JavaScript without any ClojureScript. The port preserves the LG-produced DOM contract exactly — same tags, class names, attributes, and child order — soplatform/web/src/lui.cssand the web e2e suites needed almost no changes.Module layering (see
platform/web/melange/PORTING.md, dependency order bottom→top):core/(types, util, retained store mirror) →nodes/(element factories incl. modal/tooltip/split DOM) →popup/(position, overlay, menu incl. submenu corridor) →widgets/(incl.lui_web_split) →focus/(tree/horizontal/toolbar/tabs roving) →render/props(apply_property/remove_property) →events/→shell/(apply_dom_batch, simulator, extension plumbing,Lui_webentry). Modules may only call layers below them.Web demo restored as
examples/components/web/— a thin Melange entry that mounts the existing shared OCaml gallery (examples/gallery, already used by the iOS/macOS demos) into#app, with the gallery's navigation shell (web_main.ml). OnWebOSthe gallery additionally mounts the NativeExtension and Tweaks pages via web simulator extensions (map/camera).Hot reload: the Vite plugin's LG root-swap (
Lg_runtime__Runtime_reference) no longer exists in pure OCaml, soplatform/web/vite.config.mjsnow watchesexamples/**/*.ml(i), waits for the dune-wemit to settle with all entry modules present (the emit tree is replaced wholesale mid-build), invalidates the module graph, and sendsfull-reload.tooling/dev_web.mjsruns opam with--switch=defaultsomelange-webapiresolves.Runtime fixes uncovered by e2e:
Lui_runtime.event_is_value_echoforToggleChangedonly consultedChecked, which suppressed collapse (ToggleChanged(_, false)) on nodes that bindExpanded(tree rows) orSelected(accordion) instead — both fallbacks added.accordiongained?selected_signal(matchingmenu_item's precedent) so the gallery can bind a reactive signal instead of a one-shotsample.enabled_node: absentEnabledmeans enabled (onlySome falsedisables).lui-modal-descriptionslot inside the title div; the class name avoids the stylesheet contract's bannedlui-{dialog,sheet}-*layout suffixes.MenuTriggeris now treated as a menu row (Store.menu_item_row) so it getsdata-submenu-trigger/aria-expanded, hover-open wiring,menuitemrole, and inclusion in roving/keyboard item lists; nested dropdowns defaultdata-anchor="right".setTimeout(0)so it lands before any later-queued task.Test results (all green):
platform/webunit/contract suites 44/44,overlay.e2e.mjs38/38,simulator.e2e.mjs15/15,hot-reload.e2e.mjs1/1,firefox.e2e.mjs1/1.dune buildclean (warnings-as-errors honored).Link to Devin session: https://app.devin.ai/sessions/5d6b198dceb54bb2b8fa02c8a412f45c
Open in Devin Desktop: https://app.devin.ai/desktop/session/5d6b198dceb54bb2b8fa02c8a412f45c?variant=devin
Requested by: @tiensonqin