---
title: "DevTools — zenit Zig UI 문서"
description: "한 줄짜리 overlay, 독립 패널, 용량 제한이 있는 구조화된 Console로 레이아웃, 히트 테스트, 렌더링, 성능 문제를 빠르게 찾아냅니다."
url: https://zenit.z.express/ko/docs/advanced/devtools
language: ko
alternate_en: https://zenit.z.express/docs/advanced/devtools.md
alternate_zh: https://zenit.z.express/zh/docs/advanced/devtools.md
alternate_es: https://zenit.z.express/es/docs/advanced/devtools.md
alternate_ja: https://zenit.z.express/ja/docs/advanced/devtools.md
alternate_fr: https://zenit.z.express/fr/docs/advanced/devtools.md
alternate_de: https://zenit.z.express/de/docs/advanced/devtools.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# DevTools

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

## 창 내 인스펙터

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

`main.zig`

```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](https://zenit.z.express/ko/docs/advanced/multi-window)으로 독립 창에 띄워 제품 UI를 가리지 않게 합니다. 패널을 사용하는 동안 target은 살아 있어야 합니다.

`main.zig`

```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](https://zenit.z.express/media/devtools-elements.png?v=1c99ec3edc)](https://zenit.z.express/media/devtools-elements.png?v=1c99ec3edc)

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

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

| 뷰 | 답하는 질문 |
| --- | --- |
| **Elements** | 실제 노드 계층, tag / id / text, 레이아웃과 히트 영역은 어떤가? |
| **Components** | 어떤 노드가 컴포넌트 경계를 이루며, 그 상태와 소유 Scope는 어디에 있는가? |
| **Console** | target Cx가 어떤 구조화 로그를 캡처했고, 밀려나거나 버려진 것이 있는가? |
| **Performance** | target이 렌더링 중인가, 유휴 상태인가? 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`

```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 저장소 자체를 비웁니다. |
| **Filter** | message와 scope를 대소문자 구분 없이 매칭하며, 레벨 필터와 함께 적용됩니다. |
| **Levels** | Debug / Log / Info / Warn / Error를 원하는 조합으로 켜고 끕니다. |
| **로그 목록** | 레벨, scope, 메시지, group 들여쓰기, 선택적 소스 위치를 표시합니다. 맨 아래에서 벗어나 스크롤하면 자동 따라가기가 멈춥니다. |
| **상태 표시줄** | shown / captured / evicted / dropped로 필터링, 밀려남, 유실을 구분합니다. |

[![Zenit DevTools Console with debug, info, warn and error entries](https://zenit.z.express/media/devtools-console.png?v=1411f611cd)](https://zenit.z.express/media/devtools-console.png?v=1411f611cd)

로그는 패널을 열기 전에 이미 target Cx의 용량 제한 저장소에 들어갔으며, scope, 레벨, 소스 위치가 모두 보존됩니다.

[![Zenit DevTools Console filtered to the network scope](https://zenit.z.express/media/devtools-console-filter.png?v=acfa3d9446)](https://zenit.z.express/media/devtools-console-filter.png?v=acfa3d9446)

Filter와 Levels는 DevTools 안에서 로컬로 조합되며, target 저장소에서는 아무것도 삭제되지 않습니다.

[Video](https://zenit.z.express/media/devtools-console.mp4?v=05e8c163e3)

실제 두 창 앱: 가상 커서가 Elements에서 Console로 전환하고 Filter에 포커스한 뒤 network를 입력합니다. DevTools 창의 Metal drawable에서 바로 녹화했습니다.

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

### 캡처 설정

`main.zig`

```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` | `.warn` | `null` (끔) |

> WARNING
> 
> **Release 빌드는 기본적으로 캡처하지 않습니다.** ReleaseFast / ReleaseSmall에서는 DevTools가 기록을 볼 수 없습니다. 프로덕션 빌드에서 필요하다면 `capture_level`을 명시적으로 설정하고, 토큰, 비밀번호, 개인 데이터가 로그에 남지 않도록 하십시오.

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](https://zenit.z.express/media/devtools-performance.png?v=41f18a12a0)](https://zenit.z.express/media/devtools-performance.png?v=41f18a12a0)

정적인 target은 렌더링을 멈췄습니다. DevTools는 약 10 Hz로 스스로 갱신해 차트를 계속 움직이며, layout, render, cache, focus, interaction 지표는 계속 읽을 수 있습니다.

| 판독값 | 의미 |
| --- | --- |
| FPS + 롤링 차트 | 벽시계 버킷. 프레임이 없는 버킷은 0을 기록하고 중립 기준선으로 그려지며, 오래된 값을 쓰지 않습니다 |
| Timing | target 직전 프레임의 CPU 벽시계 시간: layout, render 명령 생성 등 |
| Summary / Interaction | hit registry, mouse-hit, redraw streak, focus와 interaction rebuild |
| Cache / Rebuild | retained cache 적중 / 미스, full / partial rebuild |

## 테스트 진입점

```sh
zig build test-headless
zig build test-ui
zig build test-render
zig build hello-button
```

> WARNING
> 
> **내부 파일에 zig test를 단독으로 실행하지 마십시오.** zenit 모듈은 디렉터리를 넘나들며 서로 import하므로 `build.zig`에 정의된 테스트 step을 사용하십시오. 그렇지 않으면 `import of file outside module path`를 만날 수 있습니다. 전체 목록은 [명령어](https://zenit.z.express/ko/docs/reference/commands)를, 실제 창 테스트는 [E2E harness](https://zenit.z.express/ko/docs/advanced/e2e)를 참고하세요.

## 릴리스 전 점검

-   **디버그 UI 끄기.** 인스펙터 overlay를 제거하거나 debug 전용 설정으로만 마운트되게 합니다.
    
-   **실제 창에서 입력 검증.** 키보드, IME, 클립보드, VoiceOver.
    
-   **빌드 게이트 실행.** headless, UI, render 테스트 step.
    
-   **Console 정책 확인.** Release의 터미널 레벨과 캡처 설정이 의도한 대로이고, 로그에 민감한 데이터가 없어야 합니다.
    
-   **버전 고정.** zenit revision을 고정하고 대상 Zig 버전을 기록합니다.
