Reading
tree, node, find, state, errors, lint, frames and snapshot: what is on screen and why.
zgx inspect tree # the whole view (200 lines at most; the rest counted)
zgx inspect tree '#transcript' --depth 3
zgx inspect node '"Copy"' # kind, the script line that made it, role, name, value, state, box, text
zgx inspect find '"Copy"' # every node a target matches
zgx inspect state # named states (ui.state, ui.persist, ui.shared) and their values
zgx inspect state messages --full # one state; long values are cut at 200 bytes unless --full
zgx inspect errors # the script error, type errors, lint findings, layout warnings ("none")
zgx inspect lint # zgx lint over the app's scripts: file:line:col rule message
zgx inspect frames 5 # the last frames: where their time went, what ran and why
zgx inspect snapshot '#messages' --out list.png # what it looks like, as the window last drew it
frames is where each frame’s time went and why the app did what it did: the frame’s time, the states written (named ones by name, the rest counted) and the runs they caused with their total; then its phases, the costliest first (each counted once, with its instructions where the platform counts them); then the costliest runs, each with its time, what it is and the state that queued it:
frame 131 (1.42 ms): state.luau::messages, 8 unnamed → 20 runs, 293 µs
phases: update 310 µs (1840K instr), text 402 µs, layout 260 µs (1210K instr), paint 190 µs, encode 120 µs, present 95 µs
153 µs @67 binding (markdown_view components/transcript.luau:32) ← a state
28 µs @102 binding #transcript (list #transcript components/transcript.luau:73) ← a state
19 µs effect 124 ← state.luau::messages
A binding is a node’s (@67, its kind or the script line that made it); an effect a script’s ui.effect (its line) or the engine’s. The phases are ZGX_PROFILE’s (input, script, update, style, layout, text, damage, paint, encode, submit, present, wait), measured only while devtools serve, so no restart: a frame with no writes (a scroll, an animation’s layout) shows with its phases alone. wait is pacing, not work: on macOS a frame is held to render and hand over at its point in a refresh, up to a refresh long (8.3 ms at 120 Hz, 16.7 at 60), so the frame’s time leaves it out and says it apart (then 6.10 ms waiting for the display). present includes waiting for the GPU to finish the frame. Use it to find what a write costs, what reads too much, and which phase a slow frame spent.
A tree line is @n #id role "name or text" ="value" x,y wxh and flags (focused, disabled, checked), indented two spaces a level, in document order. Boxes with nothing to say (no id, role or text) are left out and their children shown in their place; a button’s own label text is folded into the button. --all shows every node. Same tree, same text: compare runs with diff.
snapshot is a PNG of a node’s box as the window last drew it, at the window’s pixels (--width 600 halves it until it fits). Without --out it stays beside the socket, readable by its user alone, and the answer says where; the app keeps its last 16. As MCP, zgx_snapshot answers the picture itself. Pictures come from what a macOS window shows; elsewhere, and in TestApp, the answer says there is none.
A module’s states are named module.luau::name; give the name alone when only one module has it.