Skip to content

Latest commit

 

History

History
216 lines (165 loc) · 10.2 KB

DEV.md

File metadata and controls

216 lines (165 loc) · 10.2 KB

Development Notes

Prerequisites

Install a Rust 1.85+ toolchain with the wasm32-unknown-unknown target, plus:

  • nightly rustfmt (rustup toolchain install nightly and rustup component add rustfmt --toolchain nightly)
  • cargo-nextest (cargo install cargo-nextest)
  • snips (cargo install snips)

ncode tidy applies format and clippy fixes. ncode check verifies format, clippy (-D warnings), snips, and a release compilation without writing. ncode test runs the test lane.

Documentation Comments in .d.luau

Documentation Comments in .d.luau

Good doc comments explain why and how, not what — the signature already says what.

  • Never recapitulate the signature. Don't restate parameter names, types, or return types that are visible from the declaration itself. Instead, explain behavior that isn't obvious from the types alone: semantics, side effects, defaults, interactions between fields, and edge cases.
  • Never include trivial examples. If the usage is obvious from the signature and the comment, an example adds nothing. Only include examples when they clarify a non-obvious interaction or a surprising calling convention.

Documentation Snippets

Code blocks in README.md come from real source files through snips. An HTML comment above each block names the source file and the snippet, and the source file marks that region with snips-start and snips-end comments.

ncode tidy runs snips and rewrites every stale block, so edit the source file and not the Markdown block. Install the tool with cargo install snips.

The README form example comes from smoketest/10_basic_form.luau. ncode test therefore type-checks it, and cargo xtask smoke proves that it still passes.

Tests

Run the test lane from xtask:

ncode test

Only smoketests launch a real app, so only they put a window on the desktop. Tests in this lane must stay headless. Lifecycle tests that need a managed app process launch the edev_test_app binary, which serves the app side of the launcher handshake over a reserved loopback endpoint and opens no window.

On Linux, launcher death kills only the direct child (PR_SET_PDEATHSIG). A cargo run app is a grandchild and survives SIGKILL of edev. Prefer a direct binary path in [app].command for supervised Linux sessions. macOS uses the supervisor and process-group cleanup instead.

Smoketests

Run the default smoke wrapper from xtask:

cargo xtask smoke

That command runs the checked-in Luau smoketest suite through edev smoke and prints one line per test by default.

List or narrow the discovered suite without launching the demo:

cargo xtask smoke --list
cargo xtask smoke --list --json --only '*visual*'

Repeat --only to select more scripts. A script runs when it matches any pattern, so narrow with one more specific glob rather than with a second flag.

Run repeated rounds against one app process when chasing intermittent behavior:

cargo xtask smoke --only '*visual*' --repeat 5
cargo xtask smoke --until-fail 50

Enable extra smoketest debugging output:

cargo xtask smoke --verbose

Verbose smoke runs keep diagnostics in terminal output. edev smoke no longer writes a persistent artifact directory by default.

Record a macOS demo movie while running the smoke suite:

cargo run --locked -p edev -- record tmp/eguidev-smoke.mov --only '*layout*'

edev record OUTFILE ... is the native macOS recording boundary. It records one selected app window to a .mov file with ScreenCaptureKit direct recording, H.264 video at the window's native pixel resolution, and no audio. It does not invoke screencapture, ffmpeg, or another recorder program. The terminal or process running edev needs macOS Screen Recording permission. The root viewport title selects the window by default; pass --window-title <TITLE> to disambiguate or select another single window.

Recording stays in edev. The portable runtime and Luau viewport state do not expose native window ids or ScreenCaptureKit details. The MVP does not record secondary viewports, microphone audio, or system audio. Long --until-fail runs produce one movie for the whole suite and can create large files.

Run the separate edev mcp transport smoke:

cargo xtask smoke-edev

Enable verbose transport logging:

cargo xtask smoke-edev --verbose

edev mcp keeps the launcher alive for the lifetime of the initialized stdio client. The mcp.idle_shutdown_after_secs setting is a pre-client guard for abandoned launches: if no client initializes before the idle window elapses, the launcher exits and cleans up. Once the client is attached, stdin EOF or a process signal owns shutdown, and the status tool reports idle_shutdown.state = "suspended_while_client_attached". Pre-initialize list_tools probes do not extend the guard; initialize is the client lifetime boundary.

Run the full smoke suite with the root viewport occluded:

cargo xtask smoke-occlusion

Smoke scripts should prefer semantic waits and exact visual assertions over frame sleeps. Use Widget:sample_pixels(), Widget:sample_grid(), and Widget:expect({ painted = ... }) for painter checks. Use Viewport:dismiss_popups() or eguidev.fixture() boundaries to isolate transient menus between tests. Use eguidev.widget(id) for a global live reference, or eguidev.viewports({ name = "..." }) and Viewport:widgets(...) for explicit viewport scope. Names and exact titles should be unique because ambiguous discovery is an instrumentation fault. Use hex for exact color equality; rgba channels and geometry values are script-facing numbers and can be mixed in arithmetic when a visual threshold is clearer than a fixed color. On macOS, child-viewport screenshots fall back to Quartz window capture after a fresh child frame fails to fulfill the normal egui screenshot event. The fallback needs a recorded window title match and macOS Screen Recording permission. Root screenshots use the egui event path directly. Use Viewport:native_screenshot() to include AppKit chrome in a capture. Use ViewportState.os_title_visible to assert native title visibility. Use ViewportState.cursor_icon after Widget:hover() to assert cursor feedback.

Run one diagnostic script and keep its return value/images with:

cargo run --locked -p edev -- eval tmp/probe.luau --out-dir tmp/probe-output --arg name=Sky

edev eval launches a one-shot app process from the configured [app] command, uses the same script_eval engine as smoke scripts, prints the structured outcome JSON to stdout, exits non-zero on script failure, and writes returned ImageRef JPEGs to the script directory or --out-dir. It uses [smoke].script_timeout_secs, [smoke].script_max_instructions, and [smoke].args as defaults when the matching eval CLI flags are omitted, then shuts the app down after the eval; it does not attach to an already-running edev mcp app. The instruction limit is a runaway-script backstop. Increase it independently of the wall-clock timeout for intentional long-running stress scripts.

Background automation (occluded windows)

Stock eframe 0.36 stops running App::ui and painting when a window is minimized or occluded (ViewportInfo::visible() gates run_ui), so automation would freeze as soon as the developer's windows fully cover an instrumented app, and no request_repaint() can revive it. On macOS, eguidev_runtime::attach installs the process-wide observation hook, but attachment alone leaves native presentation and occlusion behavior unchanged.

When an Edev client connects, its typed presentation setting controls the session:

  • background, the default, temporarily applies the accessory activation policy, deactivates without raising the app, and makes -[NSWindow occlusionState] report the window visible to winit. Eframe keeps running the UI, painting, and servicing screenshots while another window covers the app.
  • foreground keeps ordinary foreground presentation while retaining Eguidev's real window-state observations and automation support. Configure it with presentation = "foreground" under [app] or the command's --presentation foreground override.
  • Disconnect restores the activation policy that preceded the session and disables the occlusion shim. A later connection starts a fresh session transition.

The original occlusionState and isMiniaturized values are always recorded locally. Script and status surfaces expose them as ViewportState.os_occluded and ViewportState.os_minimized. During background automation, ViewportState.occluded is the spoofed egui/winit value; use os_occluded when a test needs the real platform state. Edev status also reports the requested presentation and, when available, the observed activation policy, executable path, bundle root, and bundle identifier. These identity fields are diagnostics, not launch requirements.

Edev owns managed-process lifetime as well as presentation. On macOS, losing the owning launcher terminates the complete app process group without a later cleanup command. For normal shutdown, Edev calls the app's private app_close tool. An app can register DevMcp::on_shutdown to publish an application-owned shutdown request from the MCP runtime thread. Without this handler, Eguidev queues ViewportCommand::Close for the root viewport. Edev keeps the ownership channel open and waits for the supervisor exit event. [app].shutdown_grace_secs sets the escalation deadline and defaults to 30 seconds. stop and status report the last graceful or forced result. One-shot commands fail if teardown is forced. Applications should therefore launch their ordinary executable, keep a stable installed bundle when they have one, and avoid automation-only or per-run .app identities. App code must not repeatedly force foreground activation while a background session is connected.

The local occlusion workaround is macOS-specific. Linux and Windows background occlusion semantics are out of scope until a concrete downstream workflow needs them. Minimized macOS windows should be treated separately from covered windows: if os_minimized is true and captures stop, scripts should report that automation is paused by minimization rather than trying to raise the window.