Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·part ofpackage edui.package·originates fromworld 07158574-5…

app

The `edui` reactive core. An `App` owns one `ui.*` screen and turns the string-callback-id protocol into ordinary closures.

by◐lumi·posted 19d ago
What it does

edui.app (module)

The edui reactive core. An App owns one ui.* screen and turns the string-callback-id protocol into ordinary closures.

ui.* dispatches interaction through string ids because closures cannot cross the FFI boundary: a widget carries props.onClick = "<id>", and the screen's registered env receives onCallback(id, data). An App hides that — during a build it allocates an id per closure and records id→closure, and its single onCallback looks the id up, calls the closure, and rebuilds.

Surface

  • new(screenName, opts?) -> App — opts.layer is a render-layer mask applied via ui.setScreenRenderLayer on first register; opts.callbackKey overrides the env key.
  • App:mount(builderFn) — register the callback env, run the first build. builderFn(app) -> widgetTree is re-run on every rebuild.
  • App:cb(closure, key?) -> id — the id to place in a widget's on* prop. Pass a stable key so the id survives a rebuild, which is required for anything the renderer tracks by id (focus, drag, ui.widgetState).
  • App:dispatch(id, data) -> handled — what the broadcast calls. Returns false for an id belonging to another surface so a host can keep routing.
  • App:markDirty() / App:rebuild() — a rebuild requested during a build is coalesced into a single follow-up, so a handler that mutates state and a builder that reads it never recurse.
  • App:unmount() — release the env and unregister the screen.
  • current() — the app whose builder is running right now, or nil outside a build. The edui primitives read this, which is why an author passes a closure and never threads the app through every call.

Builder errors are logged rather than thrown, so one bad build never wedges the editor.

local edui = require("modules.edui")
local app = edui.createApp("myPanel")
app:mount(function(a)
    return {
        type = "vertical",
        children = {
            { type = "button", props = {
                text = "Save",
                onClick = a:cb(function() save() end, "save"),
            } },
        },
    }
end)

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

module edui.app The edui reactive core: an `App` owns one `ui.*` screen, maps author closures to `ui.*` string callback ids, dispatches the broadcast that `ui.registerCallbackEnv` delivers, and rebuilds the widget tree on state change. This is the layer that lets editor chrome be written with closures (`onClick = function() ... end`) instead of hand-managed string ids — the footgun the deprecated `zui` grew three shapes for.

new(screenName: string, opts: { [string]: any }?) → any

Create an editor app that owns the `ui.*` screen `screenName`. `layer` is a render-layer membership mask (e.g. the EditorUI bit) applied via `ui.setScreenRenderLayer` on first register; omit for the default. `order` is the screen's Z-ORDER — higher paints on top; a floating surface (the command palette) states one to stand over the dock.

argtypedescription
screenNamestringUnique screen id (also the callback-id namespace prefix).
opts{ [string]: any }?`{ layer? = <renderLayerMask>, order? = <number>, callbackKey? = <string> }`.

examples

local app = edui.app.new("myPanel", { layer = editorMask })

collectHandlers(self: any, fn: () →

Collect every callback registered while `fn` runs into a named bucket, returned alongside `fn`'s result. A host that CACHES the subtree `fn` built re-adopts the bucket on later builds (`adoptHandlers`), so the cached tree's callback ids stay live across rebuilds that skipped it.

argtypedescription
selfany
fn(The builder to run.

examples

local tree, bucket = app:collectHandlers(function() return panel.build() end)

adoptHandlers(self: any, bucket: { [string]: (any) → void

Re-register a bucket of handlers collected by `collectHandlers` into the current build, keeping a cached subtree's callbacks live.

argtypedescription
selfany
bucket{ [string]: (anyThe handler bucket to adopt.

examples

app:adoptHandlers(cached.bucket)

cb(self: any, closure: (any, key: ?) →

Allocate (or reuse) a callback id bound to `closure` for this build. Pass a stable `key` so the id is identical across rebuilds — required for anything the renderer tracks by id (focus, drag, `ui.widgetState`). Without a key the id is a per-build sequence number (fine for a fire-and-forget button).

argtypedescription
selfany
closure(anyThe handler `(data) -> ()` the id dispatches to.
key?Optional stable key (unique within this screen).

examples

props = { onClick = app:cb(function() doThing() end, "save") }

cbRaw(self: any, id: string, closure: (any) →

Register `closure` under the EXACT id `id`, without the screen-name prefix `cb` adds. For wiring engine-emitted interaction ids that a widget publishes itself (e.g. a dockArea's `<panelId>-close`), which arrive unprefixed. Re-issue it each build, like `cb`.

argtypedescription
selfany
idstringThe exact callback id the engine will deliver.
closure(anyThe handler `(data) -> ()`.

examples

app:cbRaw(panelId .. "-close", function() closePanel(panelId) end)

dispatch(self: any, id: string, data: any) → boolean

Route a callback id to its closure and schedule a rebuild. This is what the screen's `onCallback` broadcast calls. Returns true when the id was handled by this app (an id from another surface returns false so a host can keep routing). mouseX, mouseY }`. The handler receives its `value`: a scalar for `onChange` (checkbox bool, input string, select value), a `{ dx, dy, shift, ctrl, alt }` table for a canvas `onDrag`/`onScroll`, `nil` for a bare `onClick`.

argtypedescription
selfany
idstringThe callback id the broadcast delivered.
dataanyThe engine event envelope `{ value, eventType, widgetId, button,

examples

function onCallback(id, data) app:dispatch(id, data) end

markDirty(self: any) → void

Mark the app dirty and rebuild its tree. Coalesces a rebuild requested WHILE a build is running into a single follow-up build (so a handler that mutates state and a builder that reads it never recurse), and one requested WHILE a dispatch's handler runs into the single rebuild that dispatch performs after the handler returns.

argtypedescription
selfany

rebuild(self: any) → void

Run the builder and push the resulting tree to the screen. First call registers the screen (+ render layer); later calls update it. Builder errors are logged, never thrown, so one bad build never wedges the editor.

argtypedescription
selfany

unchanged(self: any, sig: string) → boolean

Whether the tree this build would produce is the one the app's screen already shows. `sig` names every piece of state the build reads: an equal signature means the published tree already says it, so the builder returns nil and the App keeps what it published, at O(1) cost however large the tree. The signature belongs to the App that published the tree, so an App with nothing published reports a change and gets a tree to register.

argtypedescription
selfany
sigstringThe signature of the state this build reads.

examples

if app:unchanged(buildSignature()) then return nil end

mount(self: any, builderFn: (any) →

Mount the app: register the broadcast callback env and do the first build. `builderFn(app) -> widgetTree` is called now and on every rebuild; inside it, `app:cb(...)` (or the `edui.*` primitives) wire closures.

argtypedescription
selfany
builderFn(any`(app) -> widgetTree`.

examples

app:mount(function(a) return { type = "vertical", children = { ... } } end)

onCallback(id: string, data: any) → void

argtypedescription
idstring
dataany

unmount(self: any) → void

Tear the app down: release the callback env and unregister the screen.

argtypedescription
selfany

current( ) → any

The app whose builder is currently running, or nil outside a build. The `edui.*` primitive builders read this to register their closures, so authors don't thread the app through every call.

examples

local app = edui.app.current()

unchanged(sig: string) → boolean

`unchanged` for the build that is running right now: the ambient form a panel builder uses, the way the `edui.*` primitives read `current()`. Called outside a build it reports a change, so a builder invoked directly still produces a tree.

argtypedescription
sigstringThe signature of the state this build reads.

examples

if edui.app.unchanged(buildSignature()) then return nil end

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

2items
This part has no composite children. See the Files segment for its leaf payloads.
backing path · editor/edui.package/app.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.