Development

Lazydev Mode

Understand the development-only build tag, control-plane endpoints, and panel runtime used by lazy.

By Guillermo Alvarez - Published Updated

What lazydev means

The default lazy development command builds your app with the lazydev build tag. That tag enables local-only behavior such as disk-backed views, logical asset paths, detailed error pages, editor opening from error frames, route inspection, and view-cache reloads.

Production builds do not include lazydev handlers.

Development control plane

When lazy starts the app, it also passes CONTROL_PLANE_ADDR so the app can serve development endpoints on an internal control-plane address:

GET  /routes
GET  /cache
POST /cache/on
POST /cache/off
POST /views
POST /_golazy/views/reload
POST /_golazy/open-editor

GET /routes returns the registered route table. GET /cache returns cache enabled state, stats, and any backend keys exposed for development inspection. POST /cache/on and POST /cache/off toggle the app cache. POST /views reloads disk-backed views without rebuilding or restarting the app. The /_golazy/views/reload route remains as a compatibility alias.

Detailed error pages call POST /_golazy/open-editor when you click a frame. The request includes an absolute file path and line number, and GoLazy starts $EDITOR without waiting.

Development panel

The public development address serves the panel under /_golazy/ and proxies normal app traffic to the running app process. The panel remains available while the app is building, failed to compile, running, or crashed.

The panel JavaScript is served as a normal external asset. GoLazy does not inline development-panel JavaScript into app HTML. Proxied app pages receive a fixed bottom GoLazy development panel iframe that loads /_golazy/. The client reserves the same height as bottom padding on the html element and marks the panel host with data-turbo-permanent so Turbo navigation does not remove it. The panel proxies cache inspection and cache on/off requests to the app control plane so browser requests stay on the public development origin.

The served panel uses a DevTools-style shell with compact tabs, toolbar rows, dense status panes, and a bottom status bar. Existing lazy behavior is available inside that shell: App Logs shows build/run state, latest output, lifecycle events, and changed files; Actions provides rebuild, restart, open-app, and view-cache controls. Requests, Console, Traces, Routes, and Assets are present as disabled or empty states until their APIs are wired.

Development request traces

The lazy development proxy assigns request correlation before forwarding app traffic. It preserves safe incoming X-Request-ID and valid W3C traceparent headers, or generates both when they are missing. The app telemetry middleware uses those headers for its request id, request span, logs, metrics labels, and the response X-Request-ID.

lazy starts the child app with OTEL trace and log exporters set to otlp and OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf. In this first implementation there is no OTLP receiver; lazydev builds write local files instead:

.tmp/traces/<request-id>.trace
.tmp/traces/<request-id>.spans
.tmp/traces/<request-id>.log.json

The .trace file is a Go runtime trace. The .spans file is JSON with request and span metadata plus allocation counts, memory deltas, system memory, stack, and GC counters. The .log.json file is JSONL for request-local logs emitted through lazytelemetry or lazylogs. Runtime tracing is process-wide, so lazydev serializes traced requests while each .trace file is recorded.

Chrome DevTools extension

The chrome-extension directory contains an unpacked Chrome extension for the development panel. It adds a GoLazy icon to the Chrome extension bar and a GoLazy panel in Chrome DevTools.

The current extension is UI-only. Its requests, console, app logs, and traces views use static data and do not connect to the GoLazy development backend yet. The request view uses a Network-style toolbar with record and disable-cache controls, type filters, App/Assets/All scope filters that default to App, a compact request list, and detail tabs for headers, preview, response, initiator, timing, params, rendering, database, cache, logs, tracing, and request-scoped actions. Chrome-like dark and light surfaces use the default DevTools theme colors. The panel must fit inside the DevTools viewport; table columns should collapse or truncate instead of pushing the extension wider than the available panel space.

Chrome does not expose DevTools' private stylesheet to extension panels. The extension vendors a small adapted subset of DevTools token and class names, keeps the Chromium BSD-style license beside that CSS, and switches light/dark values through the public DevTools theme API.

The future request feed should start with Chrome's DevTools extension network API: seed from chrome.devtools.network.getHAR(), listen to chrome.devtools.network.onRequestFinished, and call a request's getContent() only when response or preview content is needed. Lower-level chrome.debugger/Chrome DevTools Protocol access should stay out of the default extension path unless a later feature needs data the DevTools network API cannot provide.

Read Run With lazy for the everyday workflow and Control Plane for production control-plane configuration.