Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit61a33237e0

README.md

app

Layer 3 lifecycle wrapper for Z.app. Collapses the register-or-update dance, owns multiple named screens, ticks reactive state, and routes callbacks via an embedded Router. Per docs/specs/ui-v3-architecture.md Layer 3. The instance is a callable { buildFn } plus a State reactive store and a Router callback dispatcher; mount → tick → unmount drives ui.registerScreen / ui.updateScreen / ui.unregisterScreen under the hood.

Exports

  • App.create(name: string, builderFn: BuilderFn) -> AppInstance — construct an App.
  • App.current() -> AppInstance? — the App whose builder is currently rendering (used by widget builders that auto-stash state).

Per-instance methods (on AppInstance):

  • :mount(ctx: any) -> AppInstance — initial register; idempotent across re-mounts.
  • :tick(dt: number?) — re-render dirty screens from the host's update(dt).
  • :unmount() — unregister every owned screen and drop the token claims.
  • :markDirty() / :isDirty() -> boolean — dirty bit control.
  • :on(idOrPattern, handler) -> AppInstance — router subscription (chainable).
  • :cb(id, handler) -> id — router callback registration; returns the id.
  • :dispatch(callbackId, data?) — router dispatch.
  • :set(key, value) -> AppInstance / :update(updates) -> AppInstance — state shortcuts.
  • :_screensForTest() / :_routerForTest() — test introspection.
  • Internals (_nextAnonId, _runBuilder, _register, _renderOne, _isRegistered, _isFirstRegister, _forgetScreen, _showAll) are exposed for use by Z.app's helpers but should not be called externally.

Types:

  • AppInstance — opaque App table carrying name, state, the embedded router, and registered-screen bookkeeping.
  • BuilderFn = (state: any, ctx: any) -> { [screenName] = buildFn | dynamic }
  • RenderOpts = { layer: number?, tags: { string }? }

Usage

local Z = require("@builtin::modules.zui")

local app = Z.app("inventory", function(state, ctx)
    state:default("count", 0)
    return {
        main = function()
            return Z.btn("count=" .. state:get("count"), "inc")
        end,
    }
end)

app:on("inc", function() app:set("count", app.state:get("count") + 1) end)
app:mount(self)
-- in update(dt):
app:tick(dt)
-- on destroy:
app:unmount()

Notes

  • A per-process token-based stale-screen guard keeps a second App with the same screen name from doubling-up ui.updateScreen pushes. The newest mounted App wins; older instances skip their tick silently.
  • Closure-as-callback widgets (Z.btn("Save", fn)) get stable per-screen ids via _nextAnonId so re-ticks update the existing router entry instead of accumulating handlers.
  • Z.dynamic(list, fn) values in the builder's return table are exploded into per-item screens named <base>-<item.id>; vanished items are unregistered automatically on the next tick.
  • { build, layer, tags } is the supported sugar for screens that need ui.registerScreen's optional layer or Z.tags.set entries. Tags are written immediately after register so the editor toggles and Z.tags.findByTag see them on the same frame.
  • A build that returns nil means "this screen has nothing to draw this frame": the App hides the screen and shows it again on the first tick the build produces a tree. That is the whole of the App's claim on visibility — a screen hidden by anyone else (ui.hideScreen, Z.screens, the editor's F1 toggle via Z.tags.hideByTag("editor")) stays hidden while the App keeps pushing tree updates on its refresh clock, and becomes visible again when that caller shows it.
  • Errors thrown by a builder propagate through pcall and re-raise with error(..., 0) so the screen name is preserved in the traceback.