Skip to main content

lui-reactive

LUI reactive API rules for view/example code (OCaml + lui_ppx). Use whenever writing or editing code that builds Lui_elements views: reactive sugar, dyn ban, if_/keyed usage, signal conventions.

Source facts

Repository
logseq/lui
Last source activity
October 9, 2026 at 07:27
Detected SKILL.md language
English
Stars
5
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
lui-reactive
description
LUI reactive API rules for view/example code (OCaml + lui_ppx). Use whenever writing or editing code that builds Lui_elements views: reactive sugar, dyn ban, if_/keyed usage, signal conventions.
# LUI Reactive API Rules `lui_ppx` is enabled for view code. `reactive` is the single reactive form; `dyn` is a ppx expansion target and is never called directly. ## Hard rules 1. **Never call `Lui_elements.dyn` directly.** It exists only as the ppx expansion target for children-position `reactive`. Writing `dyn` in view code is a bug. 2. **Never write `Signal.map` / `~p_signal:` / `Signal.value` plumbing by hand for reactive props or test/source signals** — `reactive` covers all of it. ## The reactive vocabulary ```ocaml (* prop position: derived signals are created and owned by the mounted view *) button ~icon:(reactive (fun v -> if v then `eye_off else `eye) visible) [] (* children position: model -> subtree (this IS dyn, via ppx) *) column [ reactive (fun m -> pane_of m.tab) model_s ] (* custom comparator — ~equal leads, matching `dyn ~equal f s` order. Omit ~equal when (=) suffices (the default). *) reactive ~equal:(fun a b -> a.id = b.id) (fun m -> view_of m) model_s (* signal of elements / multi-signal tuple *) reactive elements_s reactive (fun (a, b) -> combined a b) s1 s2 ``` ## Structural primitives (only when signal-driven) ```ocaml if_ ~test:(reactive (fun v -> v <> "") value) (button ...) (* mount/unmount *) keyed ~source:items_s ~key:(fun it -> it.id) ~cmp:Int.compare ~mount:(fun item_s -> row item_s) (* list identity *) ``` - `if_ ~test` / `keyed ~source` take signals directly; no `_signal` suffix (they have no static twin). - **Static structure uses plain OCaml**: `if` for static branches, `List.map`/`for` for static lists. Never wrap static branching in `if_`/`reactive`. ## Decision rules - Value changes (text, icon, disabled, class, attrs) → `reactive` prop. - Whole subtree switches shape on a signal → `reactive` in children. - Mount/unmount on a bool signal → `if_ ~test`. - List membership/order changes on a signal → `keyed ~source`. - Anything else → plain OCaml. - `dyn`-inside-`dyn`/`if_` nesting is almost always wrong: the inner layer usually changes only props — demote it to `reactive` props. ## Signals and ownership - Caller-provided signals are borrowed by `dyn`/`if_`/`keyed` and property bindings; their owner must keep them alive for the view's lifetime. - PPX-created `reactive` derivations are created per mount and owned by that view scope, including derived `if_ ~test` and `keyed ~source` signals. Library-created property conversions follow the same ownership rule. - Do not dispose a shared caller-owned source when one consumer unmounts. Handwritten derivations in lifecycle callbacks must explicitly register ownership with the appropriate scope. ## Comments All code comments and PR descriptions must be in English.
View on GitHub