docs/advanced/devtools
기능 · Diagnostics

DevTools

한 줄짜리 overlay, 독립 패널, 용량 제한이 있는 구조화된 Console로 레이아웃, 히트 테스트, 렌더링, 성능 문제를 빠르게 찾아냅니다.

9분 분량

창 내 인스펙터

개발 중에는 overlay를 루트에 붙입니다. 호버하면 노드의 rect를 점선 상자로 표시하고 크기와 컴포넌트 이름을 보여 줍니다. overlay 자체는 pass-through라 클릭이나 스크롤을 가로채지 않으며, 호버는 페인트 수준의 업데이트만 일으키고 재레이아웃은 절대 일으키지 않습니다.

main.zig
fn mountUi(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    const root = try mountProductUi(cx, scope);

    // Development only: hover highlight with size + component name.
    _ = try ui.devtools.overlay.attach(cx, scope, root, .{});
    return root;
}

overlay의 상태는 전달한 scope에 저장되며, 그 Scope가 해제되면 overlay도 함께 제거됩니다. 제품 창에서 “이 공간을 누가 차지하는가?”를 빠르게 확인할 때 쓰고, 전체 진단에는 독립 패널을 사용합니다. 둘 다 실제 Node / Cx 상태를 읽으므로 디버깅용 미러 모델을 따로 유지할 필요가 없습니다.

증상별 진단

증상먼저 확인
정렬이 어긋남부모 노드의 rect, padding, gap, direction (Elements → Layout)
빈 영역도 클릭됨히트된 노드의 크기와 hit_behavior
텍스트 데이터는 바뀌었지만 화면은 그대로TextProps 필드를 직접 바꾸고 setText / setTextContent를 호출하지 않았나요? 이 API들은 스스로 비교해 sizing / render dirty를 표시합니다
너비를 바꿔도 레이아웃이 그대로node.style에 직접 대입하면 아무것도 dirty로 표시되지 않습니다. 필드별로 올바른 dirty 수준을 고르는 node.setStyle(alloc, .width, v)를 사용하세요
점점 느려짐반복되는 mount, Scope와 함께 해제되지 않는 Effect, 프레임마다의 할당 (Performance → Summary / Rebuild)

DevTools 패널

Elements, Components, Console, Performance 전체 뷰가 필요하면 ui.devtools.mountPanel(cx, target, opts)를 사용합니다. 패널은 자체 Cx를 가지고 별도의 target Cx를 관찰합니다. 보통 MultiWindowApp으로 독립 창에 띄워 제품 UI를 가리지 않게 합니다. 패널을 사용하는 동안 target은 살아 있어야 합니다.

main.zig
const std = @import("std");
const ui = @import("ui");
const zenit_app = @import("zenit_app");

// mountProductUi: your app's ordinary mount function (see above).
var g_target_cx: ?*ui.Cx = null;

fn mountDevTools(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    _ = scope;
    const target = g_target_cx orelse return error.TargetNotReady;
    return ui.devtools.mountPanel(cx, target, .{ .title = "My App DevTools" });
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var application = zenit_app.MultiWindowApp.init(gpa.allocator(), .{});
    defer application.deinit();

    const product = try application.createWindowWith(.{
        .window = .{ .width = 900, .height = 640, .title = "My App" },
    }, mountProductUi);
    g_target_cx = product.cx;

    const tools = try application.createWindowWith(.{
        .window = .{ .width = 760, .height = 560, .title = "DevTools" },
    }, mountDevTools);
    _ = ui.devtools.setViewMode(tools.cx, "performance"); // optional start tab

    try application.run();
}

저장소의 zig build devtools-probe가 바로 이 구조입니다. ui.devtools는 setViewMode, setTreeFilter, setConsoleFilter 등의 함수도 제공하므로 프로브와 E2E 테스트가 패널을 코드로 조작할 수 있습니다.

Zenit DevTools Elements panel with a box selected and its layout details
왼쪽은 실시간 UI 트리이며, 노드를 선택하면 오른쪽 Layout 탭에 computed rect, padding, sizing, flex 속성이 표시됩니다. 스크린샷의 손 모양 커서는 harness의 가상 커서입니다.

네 개의 뷰, 여섯 개의 상세 탭

뷰답하는 질문
Elements실제 노드 계층, tag / id / text, 레이아웃과 히트 영역은 어떤가?
Components어떤 노드가 컴포넌트 경계를 이루며, 그 상태와 소유 Scope는 어디에 있는가?
Consoletarget Cx가 어떤 구조화 로그를 캡처했고, 밀려나거나 버려진 것이 있는가?
Performancetarget이 렌더링 중인가, 유휴 상태인가? layout / render / cache / interaction 비용은?

Elements / Components 행을 선택하면 Layout, Style, State, Events, Render, Trace 사이를 전환할 수 있습니다. 일부 Style 값은 실시간으로 편집할 수 있고, Render / Trace는 노드가 dirty가 된 이유와 최근 이벤트를 보여 줍니다. 트리 검색은 tag, #id, 컴포넌트 이름과 일치합니다. ui.devtools.source_link를 설정하면 컴포넌트 행에서 에디터의 정의 위치로 바로 이동할 수 있습니다.

Console 로그

모든 ui.Cx는 스레드 안전하고 용량 제한이 있는 자체 Console을 가집니다. 한 번의 호출로 터미널에 출력하면서 DevTools와 E2E harness를 위한 구조화 이벤트로도 보관하며, 패널을 열기 전에 캡처된 기록도 표시됩니다.

logging.zig
const log = cx.console();

log.info("application ready", .{});
log.scoped("network").warn("retry {d}", .{attempt});

// Plain level methods don't record a call site; writeAt does.
log.writeAt(.err, @src(), "save failed: {s}", .{@errorName(err)});

레벨은 debug, log, info, warn, err입니다. 브라우저 콘솔에 대응하는 group / groupEnd, count, time / timeEnd, assert, trace, inspect, table도 있습니다.

패널 영역용도
Clear뷰만이 아니라 target의 Console 저장소 자체를 비웁니다.
Filtermessage와 scope를 대소문자 구분 없이 매칭하며, 레벨 필터와 함께 적용됩니다.
LevelsDebug / Log / Info / Warn / Error를 원하는 조합으로 켜고 끕니다.
로그 목록레벨, scope, 메시지, group 들여쓰기, 선택적 소스 위치를 표시합니다. 맨 아래에서 벗어나 스크롤하면 자동 따라가기가 멈춥니다.
상태 표시줄shown / captured / evicted / dropped로 필터링, 밀려남, 유실을 구분합니다.
Zenit DevTools Console with debug, info, warn and error entries
로그는 패널을 열기 전에 이미 target Cx의 용량 제한 저장소에 들어갔으며, scope, 레벨, 소스 위치가 모두 보존됩니다.
Zenit DevTools Console filtered to the network scope
Filter와 Levels는 DevTools 안에서 로컬로 조합되며, target 저장소에서는 아무것도 삭제되지 않습니다.
실제 두 창 앱: 가상 커서가 Elements에서 Console로 전환하고 Filter에 포커스한 뒤 network를 입력합니다. DevTools 창의 Metal drawable에서 바로 녹화했습니다.

일반 레벨 메서드는 호출 위치를 기록하지 않습니다. 로그 줄을 클릭해 에디터로 이동하려면 writeAt(level, @src(), …)를 사용하고, ui.devtools.source_link.configure로 소스 루트와 에디터 명령(기본값: code --goto)을 설정하세요.

캡처 설정

main.zig
const app = try zenit_app.App.init(allocator, .{
    .console = .{
        .terminal_level = .info, // null disables the terminal sink
        .capture_level = .debug, // null disables in-memory capture
        .max_entries = 10_000,
        .max_bytes = 8 * 1024 * 1024,
        .max_entry_bytes = 64 * 1024,
    },
});

console을 명시하지 않으면 zenit_app이 빌드 모드에 따라 기본값을 고릅니다.

빌드 모드터미널메모리 캡처
Debug.debug.debug
ReleaseSafe.info.info
ReleaseFast / ReleaseSmall.warnnull (끔)

Console은 브라우저 콘솔의 로그 수집·조회 기능에 대응하며, Zig / JavaScript REPL이 아닙니다. 현재 group은 들여쓰기만 되고 대화형으로 접을 수 없으며, table은 텍스트로 출력됩니다. 전체 API, 스레드 및 수명 규칙, harness 예제는 저장소의 docs/CONSOLE.md에 있습니다.

Performance: 유휴와 멈춤 구분

Performance 뷰는 “최근 N개의 렌더링 프레임”에서 멈춰 버리지 않습니다. 100 ms 벽시계 버킷 단위로 target을 계속 관찰하며, 버킷은 64개로 약 6.4초의 롤링 윈도우를 이루고, FPS는 최근 10개 버킷(약 1초)의 평균입니다. target이 0.7초 넘게 새 프레임을 만들지 않으면 판독값이 FPS: 0 — idle (not rendering)로 표시됩니다. 이는 프레임워크가 전력을 아끼려고 의도적으로 프레임을 건너뛰는 것이지 0 FPS로 멈춘 것이 아닙니다.

100MS BUCKETS
프레임이 멈춰도 차트는 계속 움직이며 0을 기록합니다. 판독값은 빈 버킷이 들어오면서 떨어지고 0.7초 후 idle로 바뀌며, 오래된 값을 현재 값처럼 보여 주지 않습니다.
Zenit DevTools Performance showing FPS 0 idle (not rendering) with timing metrics
정적인 target은 렌더링을 멈췄습니다. DevTools는 약 10 Hz로 스스로 갱신해 차트를 계속 움직이며, layout, render, cache, focus, interaction 지표는 계속 읽을 수 있습니다.
판독값의미
FPS + 롤링 차트벽시계 버킷. 프레임이 없는 버킷은 0을 기록하고 중립 기준선으로 그려지며, 오래된 값을 쓰지 않습니다
Timingtarget 직전 프레임의 CPU 벽시계 시간: layout, render 명령 생성 등
Summary / Interactionhit registry, mouse-hit, redraw streak, focus와 interaction rebuild
Cache / Rebuildretained cache 적중 / 미스, full / partial rebuild

테스트 진입점

Terminal
zig build test-headless
zig build test-ui
zig build test-render
zig build hello-button

릴리스 전 점검

  • ✓

    디버그 UI 끄기. 인스펙터 overlay를 제거하거나 debug 전용 설정으로만 마운트되게 합니다.

  • ✓

    실제 창에서 입력 검증. 키보드, IME, 클립보드, VoiceOver.

  • ✓

    빌드 게이트 실행. headless, UI, render 테스트 step.

  • ✓

    Console 정책 확인. Release의 터미널 레벨과 캡처 설정이 의도한 대로이고, 로그에 민감한 데이터가 없어야 합니다.

  • ✓

    버전 고정. zenit revision을 고정하고 대상 Zig 버전을 기록합니다.

zenit · 이중 라이선스오픈 소스 프로젝트는 GPL-3.0-only로 무료 사용할 수 있으며, 비공개 소스나 상용 제품에는 상용 라이선스가 필요합니다.작성자 연락처: zongyi.xzy#gmail.com (#을 @로 바꾸세요)zenit 5f9add5+wip 2026-09-30