Lua, in the browser, mounted per route.
Pages that need interactivity declare a data-lua-module attribute on the body. A
wasmoon-compiled Lua engine boots, fetches only that module, and runs it. No virtual DOM, no React — signals +
hyperscript in ~180 lines of Lua.
data-lua-module="..."
mounts library + page
renders into #app
A single signal driving a text node and two button handlers. The whole demo is ~20 lines of Lua.
Run demo →-- pages/counter.lua local count, setCount = reactive.signal(0) local root = h("div", {}, h("p", {}, function() return "Count: "..count() end), h("button", { onClick = function() setCount(count()+1) end, }, "+") ) dom.mount(root, "#app")
A reactive.effect re-renders the list whenever the signal changes. Input wired via dom.on event bridging.
Run demo →-- pages/todolist.lua local items, setItems = reactive.signal({}) local draft, setDraft = reactive.signal("") reactive.effect(function() list.innerHTML = "" for _, v in ipairs(items()) do dom.append(list, h("li", {}, v)) end end)
Pages without a module ship zero wasmoon.
boot.js returns early when the body carries no data-lua-module. The framework JS is
fetched once per origin and cached; every page module after that is under a kilobyte.
Real-time sprint board with AI assist
Live sprint board over Jira Cloud. SignalR streams delta updates so the board reacts the instant tickets move. A D3 force graph lays out tag-association clusters across the backlog. An OpenRouter-backed chat assistant answers sprint-state questions inline.
Fintech, banking, and multilingual WordPress at scale
Angular and .NET applications inside banking environments. Over sixty payment-API integrations spanning fintech rails. Multilingual WordPress at enterprise scale — localized content, custom plugins, integrations against legacy systems. Client names withheld; happy to walk through any of it under NDA.