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

registry

The single registry every editor surface is built from — builtin tabs and world panels alike. The engine's own tools register through the same public API a world uses. Two surfaces consume the registry: the dock service (`system_tools.module`) renders whatever panels are register…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

editorRegistry

The single registry every editor surface is built from — builtin tabs and world panels alike. The engine's own tools register through the same public API a world uses. Two surfaces consume the registry: the dock service (system_tools.module) renders whatever panels are registered, sorted by order, and the top bar (EditorTopBar.component) builds its menu bar from the registered menus and each panel's menu/order. Both subscribe to onChanged so add/remove is live.

Registrations run through two paths, both ending in addPanel: builtin tabs register from modules.api.editor.system_tools.panels (the require IS the registration), and world panels register via an editorPanel asset whose onRegister(self) hook calls addPanel. State lives in a fixed _G slot so it survives hot-reload and edit↔play mode flips. A duplicate id replaces the prior registration, which is how a world overrides a builtin tab and how hot-reload re-registers cleanly.

Panels and menus

  • M.addPanel(spec) -> boolean — add (or replace) a dock panel. spec: id (required, unique), build (required, (state) -> widget), label?, menu? (default "window"), order?, refresh?, onCallback?, onMount?, tick?, float?.
  • M.removePanel(id) -> boolean — remove a registered panel by id.
  • M.addMenuItem(spec) -> boolean — add (or replace) a clickable action item that runs onClick, with no associated panel. spec: id (required), onClick (required), label?, menu?, order?.
  • M.removeMenuItem(id) -> boolean — remove a registered action item.
  • M.addMenu(spec) -> boolean — add (or replace) a top-level menu in the bar. spec: id (required), label?, order?.
  • M.removeMenu(id) -> boolean — remove a top-level menu and cascade-drop every item/panel that targeted it.

Introspection and subscription

  • M.list() -> { panels, items, menus } — snapshot copies of current registrations for introspection.
  • M.onChanged(fn) -> handle — subscribe to any add/remove mutation; fn runs with no args.
  • M.offChanged(handle) -> boolean — unsubscribe.

Host accessors (dock service + top bar)

  • M.setDock(controller?) — publish (or clear with nil) the live DockController.
  • M.dock() -> DockController? — the live dock controller, or nil when no dock is mounted.
  • M.panelsSorted() -> {spec} — live dock panels sorted by (order, id), including build.
  • M.menuModel() -> {menu} — the full top-bar menu model: every menu in bar order, each carrying its entries ({kind="panel", id, label} or {kind="action", id, label}) in order.

The DockController type is { open, close, toggle, isOpen } plus optional getLayout / restoreLayout for layout persistence.

Usage

local editor = require("@builtin::modules.api.editor.registry")
editor.addMenu({ id = "mygame", label = "My Game", order = 70 })
editor.addPanel({
    id = "my_tools", label = "My Tools", menu = "scene", order = 50,
    build = function(state) return Z.lbl("hello") end,
})
editor.addMenuItem({ id = "act", menu = "scene", label = "Go",
    onClick = function() 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

EditorRegistry Module THE single registry every editor surface is built from — builtin tabs and world panels alike. There is no privileged hardcoded path: the engine's own tools register through the exact same public API a world uses, so the API is always exercised and always discoverable (read any builtin tab module to see the pattern in use). Two surfaces consume the registry: * `system_tools.module` (the dock) is a generic SERVICE — it renders whatever panels are registered, sorted by `order`, with no hardcoded tab list. It subscribes to `onChanged` so add/remove is live. * `EditorTopBar.component` (the top bar) builds its whole menu bar from the registered menus + each panel's `menu`/`order`, registers callback handlers, and rebuilds on `onChanged`. WHAT RUNS THE REGISTRATIONS — two paths, both ending in `addPanel`: * Builtin tabs — `modules.api.editor.system_tools.panels` registers the engine's own menus + tabs. The default editor entrypoint REQUIRES that module, and the require IS the registration (require-cached → runs once per VM). Remove the require → the builtin tool set is gone. Read that one module to see the addPanel/addMenu pattern applied across the whole set. * World panels — an `editorPanel` asset whose `onRegister(self)` hook calls `addPanel`. The engine fires that hook live on `asset.create` and in a batched world-load sweep (`@builtin::assetTypes.assetType.shared.onRegister`), so "write a panel asset into the world → it appears, no restart" holds. See `assetTypes/editorPanel.assetType`. That is the whole discovery + removability story; there is no boot-time module-loading step this module performs itself. Usage (a panel module — builtin or world — self-registers at load): local editor = require("@builtin::modules.api.editor.registry") editor.addPanel({ id = "my_tools", label = "My Tools", menu = "scene", order = 50, build = function(state) return Z.lbl("hello") end, }) editor.addMenuItem({ id = "act", menu = "scene", label = "Go", onClick = function() ... end }) editor.addMenu({ id = "mygame", label = "My Game", order = 70 }) State lives in a module-local upvalue: the module loads once per VM, so the table lives for the VM lifetime and registrations persist until removed or the world unloads. A duplicate id REPLACES the prior registration (warn) — that is how a world overrides a builtin tab, and how a re-require re-registers cleanly.

S( ) → void

logger( ) → void

warn(msg: ?) → void

argtypedescription
msg?

commandsModule( ) → any

Command-registry bridge: every menu item is mirrored into the command registry (menu bar, palette, keymap, context menus all read from there). Guarded so a load-order edge case degrades gracefully instead of erroring.

fireChanged( ) → void

Fire every change listener. Listener errors are isolated (a broken subscriber can't break the registry or other subscribers).

addPanel(spec: ?) →

Add (or replace) a dock panel, optionally placed under a menu that opens it. Registration persists across hot-reload / mode flips until removed. Builtin tabs register through this exact call. label?, menu?=default "window", order?=sort key within its menu/dock, refresh?, onCallback?, onMount?, tick?, float? }. A duplicate id REPLACES the prior registration (warn) — this is how a world overrides a builtin panel and how hot-reload re-registers cleanly.

argtypedescription
spec?{ id (REQUIRED, unique), build (REQUIRED, (state)->widget),

removePanel(id: ?) →

Remove a registered dock panel by id. Consumers close it if open.

argtypedescription
id?The panel id passed to addPanel.

addMenuItem(spec: ?) →

Add (or replace) an action menu item — a clickable item under a menu that runs `onClick`, with no associated panel. function) }. Duplicate id replaces the prior item.

argtypedescription
spec?{ id (REQUIRED), label?, menu?=default "window", onClick (REQUIRED,

run( ) → void

removeMenuItem(id: ?) →

Remove a registered action menu item by id.

argtypedescription
id?The item id passed to addMenuItem.

addMenu(spec: ?) →

Add (or replace) a top-level menu in the top bar. Panels and items target it via their `menu` field. Builtin menus register through this exact call — there is no privileged menu set. Duplicate id replaces the prior menu (warn).

argtypedescription
spec?{ id (REQUIRED), label?, order?=position in the menu bar }.

removeMenu(id: ?) →

Remove a registered top-level menu and every registry item/panel that targeted it (so no orphaned items linger pointing at a gone menu). registry-owned and cannot be removed).

argtypedescription
id?The menu id passed to addMenu.

list( ) →

Snapshot the current registrations for introspection. Returns plain arrays (copies) so callers can iterate without touching live state.

onChanged(fn: ?) →

Subscribe to registry mutations. The callback runs (no args) after any add/remove. Consumers (dock, top bar) use this to stay in sync.

argtypedescription
fn?The listener function.

offChanged(handle: ?) →

Unsubscribe a listener registered via onChanged.

argtypedescription
handle?The handle returned by onChanged.

byOrderThenId(a: ?, b: ?) → void

These return the LIVE spec tables (with `build`/`onCallback`/`tick`), unlike `list()` which returns copies for introspection. The dock service and top bar are the privileged consumers; world code uses `list()`.

argtypedescription
a?
b?

setDock(controller: DockController?) → void

Publish (or clear, with nil) the live dock controller the dock service exposes. Called by the dock at mount and cleared at destroy so a torn-down dock can't be driven into a dead handle.

argtypedescription
controllerDockController?A table of { open, close, toggle, isOpen }, or nil to clear.

dock( ) → DockController

The live dock controller, or nil when no dock is mounted. Editor surfaces route panel open/close and query open-state through this.

panelsSorted( ) →

Live dock panels sorted by (order, id). Each entry is the registered spec including `build`, so the dock service renders directly from it.

menuModel( ) →

The full top-bar menu model: every menu in bar order, each carrying its entries (panels that open + plain action items) in `order`. The top bar renders straight from this; nothing about the menu structure is hardcoded — it is entirely what has been registered.

⌬ Types
DockController = {

Sub-parts

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

8items
▣
module · born here
❒asset
# editorCommands The command registry — the single place an editor action is declared. One declaration is rendered by four surfaces: the menu bar, DataView context menus, the command palette, and the keymap. `EditorRegistry`'s `addMenuItem` is a thin wrapper over `declare`, so every menu action is a command. A command is `{ id, title, category, menu?, order?, keys?, enabledWhen?, run }`. `ctx` passed to `enabledWhen`/`run` is the selection service's `context()` augmented by the caller: `{ scope, refs, primary, view?, item? }`. ## Exports - `M.declare(cmd) -> boolean` — register/replace by id (duplicate replaces). - `M.get(id) -> Command?` - `M.list() -> { Command }` — sorted by (category, title). - `M.remove(id) -> boolean` - `M.isEnabled(id, ctx) -> boolean` — true when no predicate, else its result. - `M.run(id, ctx) -> boolean` — runs only when enabled; returns whether it ran. - `M.conflicts() -> { { keys, ids } }` — keys claimed by more than one command. - `M.commandForKey(keys) -> id?` — the last declarer wins a contested key. ## Usage ```luau local Commands = require("@builtin::modules.api.editor.commands") Commands.declare({ id = "asset.delete", title = "Delete Asset", category = "Assets", keys = "Delete", enabledWhen = function(ctx) return #ctx.refs > 0 end, run = function(ctx) ... end, }) ``` ## Notes - State lives in a fixed `_G` slot, seeded pre-seal by the boot chain; it survives hot-reload and edit↔play flips. Mutations after boot write into the nested `byId` / `keyClaims` tables only. - A duplicate id replaces the prior declaration and rebinds its key. - Conflicts are surfaced (`conflicts()`), never silently dropped; the last declarer wins the live binding via `commandForKey`.
▲ 0↑ born
▣
module · born here
❒asset
# editor_observe What an editor action committed, and why it committed less than it was asked for. Every editor action — a gizmo drag, a delete, a duplicate, a command dispatch, a selection gesture — closes by publishing one record here, and returns that same record to its caller. Nothing is measured per frame: a record is written where the work happens and read lazily. ```lua local Obs = require("@builtin::modules.api.editor.editor_observe") Obs.observe() -- the whole document: last of each kind, history, counts Obs.last() -- the most recent action of any kind Obs.last("drag") -- the most recent drag Obs.history() -- every retained action, oldest first ``` The same reading is a tool: `tools.use("editor", "observe")`. ## What every record carries - `action` — `drag`, `grab`, `delete`, `duplicate`, `command` or `select`. - `outcome` — `committed`, `partial`, `refused`, `cancelled` or `noop`. - `reason` — the nearest cause, from the closed set below, when the outcome is anything but a clean commit. - `detail` — the engine's own words for that cause. - `entities` — one row per entity the action touched or tried to, each with `before`, `requested` and `after`, and its own `reason` when it is not a clean commit. - `committed` / `changed` / `refused` — how many rows fall in each. - `seq`, `atMs`, `durationMs` — which action this is and how long it was open. ## The reason set `selectionEmpty`, `entityMissing`, `writeRefused`, `writeDiverged`, `pivotLost`, `userCancelled`, `commitFailed`, `duplicateRefused`, `despawnRefused`, `unchanged`, `commandMissing`, `commandDisabled`, `predicateRaised`, `bodyRaised`, `hitNothing`, `pointerBlocked`. `Obs.REASONS` maps each to its one-line meaning, so a caller can enumerate the set rather than guess at it. ## A drag that moved less than asked The drag record separates the three quantities that are usually conflated: - `pointerAsked` — what the pointer's position asked for, before snapping. - `applied` — what the gizmo handed the engine, after snapping. `snapping` and `snapIncrement` say why the two differ. - each entity's `after` — the transform the engine **holds**, read back from the engine rather than recomputed from the drag's own arithmetic. An entity whose `after` differs from its `requested` reads `writeDiverged`; one whose write raised reads `writeRefused` with the message; one despawned mid-drag reads `entityMissing`. A drag that ended on Esc reads `cancelled` / `userCancelled`, and one whose selection emptied under it reads `cancelled` / `pivotLost` — three terminal states a single "the object did not move" cannot tell apart. ## A drag that never began A press that lands on a handle and starts no drag publishes a `grab` record instead — `Obs.last("grab")`, or `Gizmo.lastGrab()`. `pointerBlocked` says the UI layer held pointer focus, so the press never reached the handle. A press away from every handle is a selection click rather than a grab, and records nothing. One record per press: a frame that ticks the gizmo more than once reports the press once. `unit` names what `pointerAsked` and `applied` are measured in: `metres` for a translate or plane drag (a world-space `{x,y,z}` delta), `radians` for a rotate (`{radians}`), `factor` for a scale (`{factor}`). ## Per-frame cost The editor's own per-frame work is named in the profiler rather than pooled into `lua_update`: `script.editor.gizmo.tick`, `script.editor.viewport.select`, `script.editor.selection.highlight`, and `script.editor.panel.<id>` for each dock panel's rebuild. Read them with `profiler.stats()`.
▲ 0↑ born

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.