module public import LeanUI public import LeanUI.Router public import LeanUI.Json public import Counter public import TodoMVC meta import LeanUI.Json.Derive meta import LeanUI.Syntax /-! # The LeanUI site A real multi-page app, and the page LeanUI presents itself with: a home page, the examples, and the benchmark results. It is one `Program`, served by a native Lean server (`examples/SiteServer.lean`) that renders every page to HTML first and then lets the browser's wasm build hydrate it, or, where WebAssembly Memory64 is missing (Safari), drives it from the server. What it exercises: * **routing** on real paths (`/`, `/examples`, `/benchmarks`) with `data-link` links, back/forward, the page title, and a server that renders whichever page the URL names; * **components**: the counter and TodoMVC are whole programs embedded with `Html.map`, `Cmd.map` and `Sub.map`. TodoMVC keeps its own fragment routing (`/examples#/active`) next to the site's path routing: each subscribes to URL changes, and one browser listener serves both; * **data**: the benchmarks page fetches `/data/benchmarks.json`, decodes it with derived `FromJson`, and draws an SVG chart (templates set SVG namespaces by themselves); * **templates** throughout. -/ @[expose] public section open LeanUI Attr namespace Site /-! ## Data -/ structure Op where name : String desc : String /-- CPU time per operation, LeanUI and the vanilla reference (Chromium, traced). -/ leanui : Float vanilla : Float deriving ToJson, FromJson, Inhabited structure Ratios where browser : String /-- LeanUI ÷ vanilla per operation, in `ops` order. -/ ratios : Array Float deriving ToJson, FromJson structure Results where machine : String chromium : String ops : Array Op release : Array Ratios := #[] /-- Native Lean cost of the same operations (update, view, diff, encode), ms. -/ native : Array Float := #[] deriving ToJson, FromJson /-! ## Pages -/ inductive Page | home | examples | benchmarks | notFound deriving BEq, Repr, Inhabited def Page.title : Page → String | .home => "LeanUI" | .examples => "Examples" | .benchmarks => "Benchmarks" | .notFound => "Not found" def pages : Route Page := Route.oneOf [ Route.top |>.to (fun _ => .home) (fun | .home => some () | _ => none), Route.lit "examples" |>.to (fun _ => .examples) (fun | .examples => some () | _ => none), Route.lit "benchmarks" |>.to (fun _ => .benchmarks) (fun | .benchmarks => some () | _ => none)] def router : Router Page := { route := pages } /-! ## Model -/ inductive Load (α : Type) where | idle | loading | ok (a : α) | failed (why : String) structure Model where page : Page counter : Int := 0 todo : TodoMVC.Model results : Load Results := .idle inductive Msg where | urlChanged (url : String) | counter (m : Counter.Msg) | todo (m : TodoMVC.Msg) | got (r : Response) def dataUrl := "/data/benchmarks.json" /-- Arrive on a page: set the title, and load the results the first time they are shown. -/ def arrive (m : Model) (p : Page) : Model × Cmd Msg := let title : Cmd Msg := .title (if p == .home then "LeanUI: web UIs in Lean 4" else s!"{p.title} · LeanUI") match p, m.results with | .benchmarks, .idle => ({ m with page := p, results := .loading }, .batch #[title, .fetch { url := dataUrl } .got]) | _, _ => ({ m with page := p }, if p == m.page then .none else title) def update (msg : Msg) (m : Model) : Model × Cmd Msg := match msg with | .urlChanged url => arrive m ((router.parse url).getD .notFound) | .counter cm => let (n, c) := Counter.update cm m.counter; ({ m with counter := n }, c.map .counter) | .todo tm => let (t, c) := TodoMVC.update tm m.todo; ({ m with todo := t }, c.map .todo) | .got r => let results := if !r.ok then .failed s!"HTTP {r.status}" else match Json.decode Results r.body with | .ok d => .ok d | .error e => .failed e ({ m with results }, .none) /-- Flags: the page's URL (path and fragment). -/ def init (url : String) : Model × Cmd Msg := let (todo, tc) := TodoMVC.init url let page := (router.parse url).getD .notFound let m : Model := { page, todo } -- the server renders the page as it is before any data arrives: "loading" for the results let (m, c) := if page == .benchmarks then ({ m with results := .loading }, Cmd.fetch { url := dataUrl } Msg.got) else (m, .none) -- no focus on arrival: the TodoMVC example should not steal the page's focus let tc := match tc with | .batch cs => Cmd.batch (cs.filter fun (c : Cmd TodoMVC.Msg) => match c with | .focus _ => false | _ => true) | c => c (m, .batch #[c, tc.map .todo]) /-- The site follows the URL's path, the embedded TodoMVC its fragment: two subscriptions to URL changes, served by one browser listener. -/ def subscriptions (m : Model) : LeanUI.Sub Msg := .batch #[.urlChanges .urlChanged, (TodoMVC.subscriptions m.todo).map .todo] /-! ## View -/ /-- The source repository (an ordinary link: it leaves the site). -/ def sourceUrl := "https://github.com/FawadHa1der/leanui" def navLink (current p : Page) (label : String) : Html Msg := router.link p #[cls (if current == p then "active" else "")] #[text label] def header (m : Model) : Html Msg := def counterSource : String := "def view (n : Int) : Html Msg :=

LeanUI counter

{n}

.set (s.toInt?.getD n)} />

" def feature (title body : String) : Html Msg :=

{title}

{body}

def home (m : Model) : Html Msg :=

Web UIs, written in Lean 4.

A model, an update function and a view: plain Lean values, type-checked like the rest of your code. LeanUI compiles them to WebAssembly (wasm64), drives the DOM with a compact command buffer, and renders the same program on a Lean server.

{router.link .examples #[cls "button primary"] #[text "See the examples"]} {router.link .benchmarks #[cls "button"] #[text "How fast is it?"]}

{counterSource}

That is the view of examples/Counter.lean, running here:

{(Counter.view m.counter).map Msg.counter}
{feature "Pure and typed" "Views are values of type Html Msg. Templates are checked by Lean's elaborator and expand to the constructors you would write by hand, so they cost nothing at run time."} {feature "Fast where it counts" "A keyed, exact-lazy diff that recognises unchanged subtrees without allocating; about 1.3× the CPU time of hand-written JavaScript on the standard benchmark."} {feature "Proved, then tested" "The keyed reconciliation planner is proved correct and optimal in Lean; property tests and browser fuzzing check the rest against a reference model."} {feature "Everywhere" "This page was rendered to HTML by a Lean server, then hydrated by wasm. Browsers without Memory64 get the same app driven from the server."}
def examples (m : Model) : Html Msg :=

Examples

Counter

A program of four lines of logic, embedded here as a component with Html.map.

{(Counter.view m.counter).map Msg.counter}

TodoMVC

The whole TodoMVC specification: editing, filters in the URL fragment, the list kept in {" "}localStorage as JSON. Its model logic is proved to keep the list's keys unique under every message.

{(TodoMVC.view m.todo).map Msg.todo}

Roster

Can we staff this? An untrusted solver answers, and a checker proved correct in Lean decides what the app shows, down to why a day cannot be staffed. A roster comes with a proof, which an independent kernel checks again in your browser.{" "} Open the Roster demo

/-- A non-negative number with `digits` decimals. -/ def fmt (x : Float) (digits : Nat := 2) : String := let k := 10 ^ digits let n := (x * k.toFloat).round.toUInt64.toNat let frac := toString (n % k) if digits == 0 then toString n else s!"{n / k}." ++ "".pushn '0' (digits - frac.length) ++ frac /-- Horizontal bars: LeanUI and vanilla per operation, on a log-free linear scale per row (each row scaled to its own maximum, so small and large operations are both readable). -/ def chart (ops : Array Op) : Html Msg := let rowH := 34 let h := ops.size * rowH + 10; {...ops.mapIdx fun i o => let y := i * rowH + 6 let mx := max o.leanui o.vanilla let wl := if mx > 0 then 380 * o.leanui / mx else 0 let wv := if mx > 0 then 380 * o.vanilla / mx else 0; {o.desc} {fmt o.leanui ++ " ms"} {fmt o.vanilla ++ " ms"} } def resultsView (r : Results) : Html Msg :=

CPU time per operation (script and the rendering work it causes), median of fresh page loads, in {r.chromium} on {r.machine}. Both implementations build identical tables.

LeanUI vanilla JavaScript

{chart r.ops} {...r.release.map fun b => } {...r.ops.mapIdx fun i o => {...r.release.map fun b => } }
operation Lean (native){b.browser}
{o.desc} {(r.native[i]?.map fun x => fmt x ++ " ms").getD "–"}{(b.ratios[i]?.map fun x => fmt x ++ "×").getD "–"}

Release-browser columns: LeanUI ÷ vanilla (script and layout). "Lean (native)" is the Lean side alone, compiled natively: update, view, diff and command encoding, without the DOM.

def benchmarks (m : Model) : Html Msg :=

Benchmarks

The js-framework-benchmark keyed operations, against hand-written vanilla JavaScript with the same markup, stylesheet and data.

The race runs this benchmark and a Game of Life side by side in your browser, timed live: vanilla JavaScript, LeanUI compiled to wasm64, and LeanUI interpreted by lean-vir. It checks that all three end in the same state.

{match m.results with | .ok r => resultsView r | .failed why =>

Could not load the results: {why}

| _ =>

Loading the results…

}
def notFound : Html Msg :=

Not found

{router.link .home #[] #[text "Back to the home page"]}

def view (m : Model) : Html Msg :=
{header m} {match m.page with | .home => home m | .examples => examples m | .benchmarks => benchmarks m | .notFound => notFound}
def program : Program Model Msg := { init, update, view, subscriptions } end Site