---
title: "문제 해결 — zenit Zig UI 문서"
description: "빌드 오류부터 상태 수명, 렌더링 이상까지, 가장 빠른 경로로 원인을 찾습니다."
url: https://zenit.z.express/ko/docs/reference/troubleshooting
language: ko
alternate_en: https://zenit.z.express/docs/reference/troubleshooting.md
alternate_zh: https://zenit.z.express/zh/docs/reference/troubleshooting.md
alternate_es: https://zenit.z.express/es/docs/reference/troubleshooting.md
alternate_ja: https://zenit.z.express/ja/docs/reference/troubleshooting.md
alternate_fr: https://zenit.z.express/fr/docs/reference/troubleshooting.md
alternate_de: https://zenit.z.express/de/docs/reference/troubleshooting.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# 문제 해결

빌드 오류부터 상태 수명, 렌더링 이상까지, 가장 빠른 경로로 원인을 찾습니다.

## 빌드 오류

**invalid fingerprint**

템플릿의 fingerprint는 재사용할 수 없습니다. `build.zig.zon`에서 `.fingerprint` 줄을 삭제하고 `zig build`를 한 번 실행한 뒤, Zig가 출력한 고유 값을 다시 붙여 넣으십시오.

**import of file outside module path**

프레임워크 내부 파일에 `zig test`를 직접 실행하지 마십시오. `zig build test-ui` 같은 저장소의 테스트 step을 사용하세요([명령어](https://zenit.z.express/ko/docs/reference/commands) 참고).

**@import("ui") / @import("zenit_app") not found**

`build.zig.zon`에 `.zenit` 의존성이 선언되어 있는지, executable을 만든 뒤 `zenit.attach(zenit_dep, exe)`를 호출하는지 확인하십시오. 이 호출이 `ui` / `zenit_app` 모듈을 추가하고, macOS 브리지를 컴파일하고, 시스템 프레임워크를 링크합니다.

**build API field mismatch**

먼저 `zig version`을 실행하십시오. zenit은 Zig 0.15.2가 필요하며(`build.zig.zon`의 `minimum_zig_version`), 다른 버전의 build API는 지원하지 않습니다.

**-Dtest-mode=true has no effect**

Zig는 의존성 옵션을 격리합니다. `templates/minimal-app/build.zig`처럼 `b.dependency("zenit", ...)`에서 `.@"test-mode"`와 `.@"e2e-port"`를 명시적으로 전달하십시오.

## 런타임과 상태

**error.StateNotFound**

명시적 id를 쓰는 `cx.handler(State, id, method)`가 해당 state가 생기기 전에 실행되었습니다. `cx.bindState` + `cx.on`을 우선 사용하십시오. 상태 포인터를 직접 전달하므로 관리할 id가 없습니다.

**leaked ArrayList / HashMap at exit**

`bindState`의 상태는 Cx가 소유하며 Cx가 해제될 때 함께 해제됩니다. T가 **pub** `deinit(self: *T)`를 선언하면 프레임워크가 자동으로 호출합니다. 누수는 보통 `deinit`이 pub이 아니거나, 리소스가 페이지 수명을 따라야 했다는 뜻입니다. 다음 섹션을 참고하세요.

**crash after switching pages**

전역 상태에 이전 페이지의 `*Node`나 `*Scope`를 보관하고 있지 않은지 확인하십시오. 페이지의 Scope가 dispose되면 이 포인터들은 모두 무효가 되므로 함께 지워야 합니다.

**Invalid free after updating text**

노드의 현재 텍스트는 노드가 소유한 복사본일 수 있습니다. `getText()`로 얻은 props의 `content`를 수정해 `setText`로 되돌려 넣지 마십시오. 문자열을 복사하고 소유권을 올바르게 표시하는 `try node.setTextContent(cx.allocator, "Updated")`를 사용하세요.

## 상태를 올바르게 정리하기

상태에 allocator 필드를 두고 pub `deinit`이 정리를 맡게 하십시오. 초기값은 아직 리소스를 소유해서는 안 되며, `bindState` 이후에 할당합니다.

`state_deinit.zig`

```zig
const Editor = struct {
    allocator: std.mem.Allocator,
    lines: std.ArrayList([]const u8) = .empty,

    // pub: the Cx calls this automatically when it frees the state.
    pub fn deinit(self: *Editor) void {
        self.lines.deinit(self.allocator);
    }
};

// The initial value must not own resources yet; allocate after binding.
const editor = try cx.bindState(Editor, .{ .allocator = cx.allocator });
```

`scope.onCleanup`은 두 경우에만 사용합니다. 정리 함수가 pub `deinit`이 아닐 때, 또는 리소스가 Cx 전체가 아니라 특정 Scope(페이지)를 따라야 할 때입니다.

`scope_cleanup.zig`

```zig
// Resources that must follow a page (Scope), not the whole window (Cx).
const PageCache = struct {
    allocator: std.mem.Allocator,
    map: std.StringHashMapUnmanaged(u32) = .empty,

    // Not named `pub fn deinit`: the Cx would call it again and double-free.
    fn release(self: *PageCache) void {
        self.map.deinit(self.allocator);
    }
};

const cache = try cx.bindState(PageCache, .{ .allocator = cx.allocator });
try scope.onCleanup(PageCache, cache, PageCache.release);
```

> WARNING
> 
> **deinit을 두 번 등록하지 마십시오.** T에 이미 pub `deinit`이 있는데 같은 함수를 `scope.onCleanup`으로 등록하면 이중 해제가 발생합니다. Scope가 dispose될 때 한 번, Cx가 상태를 해제할 때 또 한 번 호출되기 때문입니다.

## 레이아웃과 그리기

**state changed but the text did not**

`setText`는 이미 이전/새 시그니처를 비교하고 필요에 따라 sizing / render dirty를 표시하므로 수동 `markRenderDirty`가 필요 없습니다. 텍스트가 갱신되지 않는다면 보통 `TextProps` 복사본을 고치고 `setText`를 호출하지 않았거나, 표시 값이 해당 Signal을 구독하지 않은 경우입니다. `ui.textFmt(cx, scope, fmt, .{ signals }, props)`로 Signal / Memo를 직접 구독하는 방식을 권장합니다.

**some colors ignore a theme switch**

`boxStyled` / `textStyled` 등의 생성자는 `on_theme` hook을 붙이며, `cx.setTheme`는 `cx.root` 서브트리에서만 이를 다시 실행합니다. 일반 노드와 컴포넌트 스타일은 mount 시점의 스냅샷입니다. 테마가 바뀔 때 해당 트리를 다시 만들거나, Effect에서 `cx.themeSignal(scope)`를 구독해 스타일을 갱신하십시오. `cx.root` 아래에 붙지 않은 노드도 갱신되지 않습니다.

**clicks fall through an overlay’s empty area**

상호작용이 없는 순수 시각 컨테이너는 기본적으로 `pass_through`입니다. 오버레이 루트의 스타일 확장에 `hit_behavior = .@"opaque"`를 설정하거나, 자체 barrier를 갖춘 [Modal](https://zenit.z.express/ko/components/modal) / [Sheet](https://zenit.z.express/ko/components/sheet)를 사용하십시오.

**a huge invisible hit area in the window**

`ui.devtools.overlay`를 붙여 히트되는 노드를 검사하고, `node.globalRect()`로 화면상의 rect를 출력하십시오. 흔한 원인은 부모의 fill / grow 크기 설정이나 지나치게 큰 절대 위치입니다. 숨긴 오버레이라면 히트 테스트에서 빠졌는지 확인하세요. `node.setDisplay(.none)`로 숨긴 서브트리는 레이아웃, 그리기, 히트 테스트, Tab 순회에서 통째로 빠집니다.

`opaque_overlay.zig`

```zig
// Plain visual containers are pass-through by default.
// Make an overlay root swallow clicks on its empty area:
(try panel.style.ensureExtFallible(cx.allocator)).hit_behavior = .@"opaque";
```

## 그래도 해결되지 않을 때

이슈를 등록할 때는 최소한 다음을 포함하십시오.

-   **버전**: zenit revision, `zig version`, macOS 버전.
    
-   **재현**: 최소 재현 코드와 실행한 build step.
    
-   **출력**: 마지막 줄만이 아닌 전체 오류 출력.
    
-   **렌더링 문제**: 스크린샷과 DevTools에서 확인한 관련 노드의 크기.
