Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e6cbe8c
schema + apple: edge-inset, overlay, view-that-fits layout primitives
RCmerci Sep 28, 2026
bbf1db5
media: file-image, file-preview (QuickLook), and link elements
RCmerci Sep 28, 2026
e0e0e68
elements: number-stepper kind + sheet detents/sizing props
RCmerci Sep 28, 2026
ef52d08
Add file-picker element with Apple file/photo/camera backends
RCmerci Sep 28, 2026
2034b75
Add list suite: sections, disclosure rows, swipe actions, scroll and …
RCmerci Sep 28, 2026
4ff32f9
gallery: demo pages for edge-inset, overlay, view-that-fits
RCmerci Sep 28, 2026
72dada9
review fixes: web link/file props, rootID-scoped previews, qt/winui e…
RCmerci Sep 28, 2026
497a9bb
fix: non-Apple backend bugs in edge-inset/overlay/view-that-fits
RCmerci Sep 28, 2026
328cb01
winui: fix LUIProperty.Grow -> GrowValue enum name
RCmerci Sep 28, 2026
e40bf98
Address review: lifecycle-safe picker operations, streamed photo import
RCmerci Sep 28, 2026
cb2e08a
Fix review findings: visible-range on reorder, cross-platform swipe-a…
RCmerci Sep 28, 2026
9955048
address review: ship Qt number-stepper, sanitize web value events, wi…
RCmerci Sep 28, 2026
39525ba
merge pr-70 (edge-chrome)
RCmerci Sep 28, 2026
11d1651
merge main+edge-chrome into list file media branch
RCmerci Sep 28, 2026
bbf04a7
regenerate schema after merge
RCmerci Sep 28, 2026
7b3b3b7
merge media+edge-chrome; regenerate schema
RCmerci Sep 28, 2026
20ccb43
merge file-picker+media+edge-chrome+controls; regenerate schema
RCmerci Sep 28, 2026
7a56fb0
merge list-suite+everything; regenerate schema; unify edge prop; renu…
RCmerci Sep 28, 2026
a5f4a5e
fix merged .edge prop support on apple backend
RCmerci Sep 28, 2026
69b3ffd
fix merged .edge prop support on winui backend
RCmerci Sep 28, 2026
9da8526
apple: anchor file-picker presentation modifiers on a Group
RCmerci Sep 28, 2026
6b0286a
apple: render 1pt placeholder so file-picker modifiers install with n…
RCmerci Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .agents/skills/testing-lui-demos/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,18 @@ Gotchas:
edge drops, but chip/strip targets are more reliable with an explicit
`left_mouse_down` → several `mouse_move` steps → pause (`wait` ~0.7s,
pane highlight/ghost visible) → `left_mouse_up` sequence.

### Exercising new/ordinary element kinds end-to-end

The split demo is the fastest vehicle even for non-split elements: scratch-edit
`examples/split/model.ml` + `view.ml` (uncommitted) so `view` returns a plain
`column` of the elements under test, add model fields/actions for any state you
need to observe (e.g. a press counter rendered via `dyn`/`text` so event
delivery is proven by an OCaml-rendered value, not just pixels), then rerun
`build-app.sh` — it rebuilds `liblui_split.dylib` and the bundle. Revert with
`git checkout` afterwards. `if_ ~test:(model_source >|= ...)` is the way to
mount/unmount presence-driven kinds (dialog/sheet/file-preview).

QuickLook panels (`file-preview`, `.quickLookPreview`) on macOS: the panel's
close affordance is small/hover-only; **Escape** reliably closes it and drives
the Dismiss binding. Clicking the panel title bar does nothing.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ private func luiOCamlTextChanged(_ node: Int64, _ text: UnsafePointer<CChar>?) -
private func luiOCamlSubmit(_ node: Int64) -> Int32
@_silgen_name("lui_ocaml_dismiss")
private func luiOCamlDismiss(_ node: Int64) -> Int32
@_silgen_name("lui_ocaml_picked")
private func luiOCamlPicked(_ node: Int64, _ payload: UnsafePointer<CChar>) -> Int32
@_silgen_name("lui_ocaml_double_press")
private func luiOCamlDoublePress(_ node: Int64) -> Int32
@_silgen_name("lui_ocaml_toggle_changed")
Expand Down Expand Up @@ -73,6 +75,8 @@ private final class GalleryHost {
text.withCString { _ = luiOCamlTextChanged(Int64(node), $0) }
case let .submit(node): _ = luiOCamlSubmit(Int64(node))
case let .dismiss(node): _ = luiOCamlDismiss(Int64(node))
case let .picked(node, payload):
payload.withCString { _ = luiOCamlPicked(Int64(node), $0) }
case let .doublePress(node): _ = luiOCamlDoublePress(Int64(node))
case let .toggleChanged(node, checked):
_ = luiOCamlToggleChanged(Int64(node), checked ? 1 : 0)
Expand Down
3 changes: 3 additions & 0 deletions examples/components/native/components_bridge.ml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ let submit node = dispatch (Submit node)

let dismiss node = dispatch (Dismiss node)

let picked node payload = dispatch (Picked (node, payload))

let double_press node = dispatch (DoublePress node)

let toggle_changed node checked = dispatch (ToggleChanged (node, checked))
Expand All @@ -104,6 +106,7 @@ let register prefix =
register (prefix ^ "_text_changed") text_changed;
register (prefix ^ "_submit") submit;
register (prefix ^ "_dismiss") dismiss;
register (prefix ^ "_picked") picked;
register (prefix ^ "_double_press") double_press;
register (prefix ^ "_toggle_changed") toggle_changed;
register (prefix ^ "_radio_changed") radio_changed;
Expand Down
66 changes: 66 additions & 0 deletions examples/gallery/view.ml
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,69 @@ let card_section model_source : t =
]
]

let inset_rows n =
List.init n (fun i ->
card ~padding:12
[ text ~value:(Printf.sprintf "Inset demo row %d" (i + 1)) [] ])

let edge_inset_section model_source send : t =
let checked = model_source >|= Model.checked in
section "Edge Inset"
[ switch_ ~text:"Show pinned bar" ~checked:(reactive checked)
~on_toggle:(on_toggle send (fun v -> Model.SetChecked v)) []
; edge_inset ~edge:`top ~background:"bar" ~max_width:560 ~height:240
~visible:(reactive checked)
[ scroll ~height:200 [ list ~gap:8 ~cross:`stretch (inset_rows 12) ]
; row ~padding:8 ~gap:8 ~cross:`center
[ text ~value:"Pinned top bar" []
; spacer []
; text ~value:"edge-inset · top" ~foreground:"muted-foreground" []
]
]
; edge_inset ~edge:`bottom ~background:"bar" ~max_width:560 ~height:180
[ scroll ~height:150 [ list ~gap:8 ~cross:`stretch (inset_rows 10) ]
; row ~padding:8 ~gap:8 ~cross:`center
[ text ~value:"Pinned bottom bar" []
; spacer []
; text ~value:"edge-inset · bottom" ~foreground:"muted-foreground" []
]
]
]

let overlay_section : t =
section "Overlay"
[ overlay ~max_width:560
[ card ~width:560 ~height:160 ~padding:16
[ column ~gap:8
[ text ~value:"Base card — this content sizes the overlay" []
; paragraph
~value:"Floating children do not shift this layout." []
]
]
; align `top_trailing
(card ~padding:8 [ text ~value:"Float · top-trailing" [] ])
; align `bottom_leading
(card ~padding:8 [ text ~value:"Float · bottom-leading" [] ])
]
]

let view_that_fits_section : t =
section "View That Fits"
[ paragraph
~value:"Widen the window for the expanded layout; shrink it below ~700pt wide for the compact fallback."
[]
; view_that_fits
[ card ~width:640 ~padding:16
[ row ~gap:8 ~cross:`center
[ text ~value:"EXPANDED — wide layout" []
; spacer []
; text ~value:"640pt" ~foreground:"muted-foreground" []
]
]
; text ~value:"COMPACT — narrow fallback" ~foreground:"accent" []
]
]

let alert_section : t =
section "Alert"
[ alert ~text:"Sync paused" ~variant:`secondary
Expand Down Expand Up @@ -1577,6 +1640,9 @@ let view context model_source send : t =
; row_section
; column_section
; grid_section
; edge_inset_section model_source send
; overlay_section
; view_that_fits_section
; text_section
; spacer_section
; toggle_button_section model_source send
Expand Down
12 changes: 12 additions & 0 deletions examples/markdown/macos-appkit/Sources/LUIMarkdown/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ private typealias StopFunction = @convention(c) () -> Int32
private typealias NodeFunction = @convention(c) (Int64) -> Int32
private typealias TextChangedFunction =
@convention(c) (Int64, UnsafePointer<CChar>?) -> Int32
private typealias PickedFunction =
@convention(c) (Int64, UnsafePointer<CChar>?) -> Int32
private typealias ToggleChangedFunction = @convention(c) (Int64, Int32) -> Int32
private typealias SliderChangedFunction = @convention(c) (Int64, Double) -> Int32
private typealias ExtensionEventFunction =
Expand Down Expand Up @@ -38,6 +40,7 @@ private final class NativeMarkdownRuntime {
private let textChangedFunction: TextChangedFunction
private let submitFunction: NodeFunction
private let dismissFunction: NodeFunction
private let pickedFunction: PickedFunction
private let doublePressFunction: NodeFunction
private let toggleChangedFunction: ToggleChangedFunction
private let radioChangedFunction: NodeFunction
Expand All @@ -57,6 +60,7 @@ private final class NativeMarkdownRuntime {
textChangedFunction = try Self.load("lui_ocaml_text_changed", from: handle)
submitFunction = try Self.load("lui_ocaml_submit", from: handle)
dismissFunction = try Self.load("lui_ocaml_dismiss", from: handle)
pickedFunction = try Self.load("lui_ocaml_picked", from: handle)
doublePressFunction = try Self.load("lui_ocaml_double_press", from: handle)
toggleChangedFunction = try Self.load("lui_ocaml_toggle_changed", from: handle)
radioChangedFunction = try Self.load("lui_ocaml_radio_changed", from: handle)
Expand Down Expand Up @@ -86,6 +90,12 @@ private final class NativeMarkdownRuntime {

func submit(node: Int) { _ = submitFunction(Int64(node)) }
func dismiss(node: Int) { _ = dismissFunction(Int64(node)) }

func picked(node: Int, payload: String) {
payload.withCString { source in
_ = pickedFunction(Int64(node), source)
}
}
func doublePress(node: Int) { _ = doublePressFunction(Int64(node)) }

func toggleChanged(node: Int, checked: Bool) {
Expand Down Expand Up @@ -166,6 +176,8 @@ private final class MarkdownHost: NSObject, NSApplicationDelegate, NSWindowDeleg
self?.runtime?.sliderChanged(node: node, value: value)
case let .dismiss(node):
self?.runtime?.dismiss(node: node)
case let .picked(node, payload):
self?.runtime?.picked(node: node, payload: payload)
case let .doublePress(node):
self?.runtime?.doublePress(node: node)
case let .appear(node):
Expand Down
3 changes: 3 additions & 0 deletions examples/markdown/native/markdown_bridge.ml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ let submit node = dispatch (Submit node)

let dismiss node = dispatch (Dismiss node)

let picked node payload = dispatch (Picked (node, payload))

let double_press node = dispatch (DoublePress node)

let toggle_changed node checked = dispatch (ToggleChanged (node, checked))
Expand Down Expand Up @@ -106,6 +108,7 @@ let () =
Callback.register "lui_ocaml_text_changed" text_changed;
Callback.register "lui_ocaml_submit" submit;
Callback.register "lui_ocaml_dismiss" dismiss;
Callback.register "lui_ocaml_picked" picked;
Callback.register "lui_ocaml_double_press" double_press;
Callback.register "lui_ocaml_toggle_changed" toggle_changed;
Callback.register "lui_ocaml_radio_changed" radio_changed;
Expand Down
12 changes: 12 additions & 0 deletions examples/split/macos-appkit/Sources/LUISplitDemo/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ private typealias TextChangedFunction =
@convention(c) (Int64, UnsafePointer<CChar>?) -> Int32
private typealias SubmitFunction = @convention(c) (Int64) -> Int32
private typealias DismissFunction = @convention(c) (Int64) -> Int32
private typealias PickedFunction =
@convention(c) (Int64, UnsafePointer<CChar>?) -> Int32
private typealias DoublePressFunction = @convention(c) (Int64) -> Int32
private typealias ToggleChangedFunction = @convention(c) (Int64, Int32) -> Int32
private typealias RadioChangedFunction = @convention(c) (Int64) -> Int32
Expand Down Expand Up @@ -83,6 +85,7 @@ private final class NativeSplitRuntime {
private let textChangedFunction: TextChangedFunction
private let submitFunction: SubmitFunction
private let dismissFunction: DismissFunction
private let pickedFunction: PickedFunction
private let doublePressFunction: DoublePressFunction
private let toggleChangedFunction: ToggleChangedFunction
private let radioChangedFunction: RadioChangedFunction
Expand All @@ -102,6 +105,7 @@ private final class NativeSplitRuntime {
textChangedFunction = try Self.load("lui_ocaml_text_changed", from: handle)
submitFunction = try Self.load("lui_ocaml_submit", from: handle)
dismissFunction = try Self.load("lui_ocaml_dismiss", from: handle)
pickedFunction = try Self.load("lui_ocaml_picked", from: handle)
doublePressFunction = try Self.load("lui_ocaml_double_press", from: handle)
toggleChangedFunction = try Self.load("lui_ocaml_toggle_changed", from: handle)
radioChangedFunction = try Self.load("lui_ocaml_radio_changed", from: handle)
Expand Down Expand Up @@ -135,6 +139,12 @@ private final class NativeSplitRuntime {

func dismiss(node: Int) { _ = dismissFunction(Int64(node)) }

func picked(node: Int, payload: String) {
payload.withCString { source in
_ = pickedFunction(Int64(node), source)
}
}

func doublePress(node: Int) { _ = doublePressFunction(Int64(node)) }

func toggleChanged(node: Int, checked: Bool) {
Expand Down Expand Up @@ -237,6 +247,8 @@ private final class SplitDemoHost: NSObject, NSApplicationDelegate, NSWindowDele
self?.runtime?.sliderChanged(node: node, value: value)
case let .dismiss(node):
self?.runtime?.dismiss(node: node)
case let .picked(node, payload):
self?.runtime?.picked(node: node, payload: payload)
case let .doublePress(node):
self?.runtime?.doublePress(node: node)
case let .`extension`(node, identifier, name, values):
Expand Down
3 changes: 3 additions & 0 deletions examples/split/native/split_bridge.ml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ let submit node = dispatch (Submit node)

let dismiss node = dispatch (Dismiss node)

let picked node payload = dispatch (Picked (node, payload))

let double_press node = dispatch (DoublePress node)

let toggle_changed node checked = dispatch (ToggleChanged (node, checked))
Expand Down Expand Up @@ -106,6 +108,7 @@ let () =
Callback.register "lui_ocaml_text_changed" text_changed;
Callback.register "lui_ocaml_submit" submit;
Callback.register "lui_ocaml_dismiss" dismiss;
Callback.register "lui_ocaml_picked" picked;
Callback.register "lui_ocaml_double_press" double_press;
Callback.register "lui_ocaml_toggle_changed" toggle_changed;
Callback.register "lui_ocaml_radio_changed" radio_changed;
Expand Down
12 changes: 12 additions & 0 deletions examples/todos/macos-appkit/Sources/LUITodos/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ private typealias TextChangedFunction =
@convention(c) (Int64, UnsafePointer<CChar>?) -> Int32
private typealias SubmitFunction = @convention(c) (Int64) -> Int32
private typealias DismissFunction = @convention(c) (Int64) -> Int32
private typealias PickedFunction =
@convention(c) (Int64, UnsafePointer<CChar>?) -> Int32
private typealias DoublePressFunction = @convention(c) (Int64) -> Int32
private typealias ToggleChangedFunction = @convention(c) (Int64, Int32) -> Int32
private typealias RadioChangedFunction = @convention(c) (Int64) -> Int32
Expand All @@ -39,6 +41,7 @@ private final class NativeTodosRuntime {
private let textChangedFunction: TextChangedFunction
private let submitFunction: SubmitFunction
private let dismissFunction: DismissFunction
private let pickedFunction: PickedFunction
private let doublePressFunction: DoublePressFunction
private let toggleChangedFunction: ToggleChangedFunction
private let radioChangedFunction: RadioChangedFunction
Expand All @@ -56,6 +59,7 @@ private final class NativeTodosRuntime {
textChangedFunction = try Self.load("lui_ocaml_text_changed", from: handle)
submitFunction = try Self.load("lui_ocaml_submit", from: handle)
dismissFunction = try Self.load("lui_ocaml_dismiss", from: handle)
pickedFunction = try Self.load("lui_ocaml_picked", from: handle)
doublePressFunction = try Self.load("lui_ocaml_double_press", from: handle)
toggleChangedFunction = try Self.load("lui_ocaml_toggle_changed", from: handle)
radioChangedFunction = try Self.load("lui_ocaml_radio_changed", from: handle)
Expand Down Expand Up @@ -94,6 +98,12 @@ private final class NativeTodosRuntime {
_ = dismissFunction(Int64(node))
}

func picked(node: Int, payload: String) {
payload.withCString { source in
_ = pickedFunction(Int64(node), source)
}
}

func doublePress(node: Int) {
_ = doublePressFunction(Int64(node))
}
Expand Down Expand Up @@ -161,6 +171,8 @@ private final class TodosHost: NSObject, NSApplicationDelegate, NSWindowDelegate
self?.runtime?.sliderChanged(node: node, value: value)
case let .dismiss(node):
self?.runtime?.dismiss(node: node)
case let .picked(node, payload):
self?.runtime?.picked(node: node, payload: payload)
case let .doublePress(node):
self?.runtime?.doublePress(node: node)
}
Expand Down
3 changes: 3 additions & 0 deletions examples/todos/native/todos_bridge.ml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ let submit node = dispatch (Submit node)

let dismiss node = dispatch (Dismiss node)

let picked node payload = dispatch (Picked (node, payload))

let double_press node = dispatch (DoublePress node)

let toggle_changed node checked = dispatch (ToggleChanged (node, checked))
Expand All @@ -94,6 +96,7 @@ let () =
Callback.register "lui_ocaml_text_changed" text_changed;
Callback.register "lui_ocaml_submit" submit;
Callback.register "lui_ocaml_dismiss" dismiss;
Callback.register "lui_ocaml_picked" picked;
Callback.register "lui_ocaml_double_press" double_press;
Callback.register "lui_ocaml_toggle_changed" toggle_changed;
Callback.register "lui_ocaml_radio_changed" radio_changed;
Expand Down
Loading
Loading