トラブルシューティング
ビルドエラーから状態のライフタイム、描画の不具合まで、最短経路で原因を特定します。
ビルドエラー
invalid fingerprint
テンプレートの fingerprint はそのまま再利用できません。build.zig.zon の .fingerprint 行を削除して zig build を一度実行し、Zig が出力した固有の値を書き戻してください。
import of file outside module path
フレームワーク内部のファイルに対して zig test を直接実行しないでください。zig build test-ui などリポジトリで定義されたテスト step を使います(コマンド一覧を参照)。
@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 の依存オプションは分離されています。b.dependency("zenit", ...) で .@"test-mode" と .@"e2e-port" を明示的に転送してください。templates/minimal-app/build.zig が参考になります。
実行時と状態
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 の後で確保してください。
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 を使うのは 2 つの場合だけです。クリーンアップ関数が pub の deinit でないとき、またはリソースが Cx 全体ではなく特定の Scope(ページ)のライフタイムに従うべきときです。
// 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);レイアウトと描画
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 / Sheet を使ってください。
a huge invisible hit area in the window
ui.devtools.overlay をアタッチしてヒットしているノードを調べ、node.globalRect() でその画面上の rect を出力します。原因はたいてい親コンテナの fill / grow 設定か、大きすぎる絶対配置の範囲です。オーバーレイが非表示のときも、ヒットテストから外れているか確認してください。node.setDisplay(.none) で隠したサブツリーは、レイアウト・描画・ヒットテスト・Tab 移動からまとめて外れます。
// 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";それでも解決しない場合
issue を報告するときは、少なくとも以下を添えてください。
- ✓
バージョン:zenit の revision、
zig version、macOS のバージョン。 - ✓
再現:最小の再現コードと実行した build step。
- ✓
出力:最後の 1 行だけでなく、完全なエラー出力。
- ✓
描画の問題:スクリーンショットと、DevTools で確認した関連ノードのサイズ。