Dépannage
Des erreurs de build à la durée de vie de l’état et aux défauts de rendu — le chemin le plus court vers la cause.
Erreurs de build
invalid fingerprint
La fingerprint d’un template ne peut pas être réutilisée. Supprimez la ligne .fingerprint de build.zig.zon, lancez zig build une fois, puis recollez la valeur unique affichée par Zig.
import of file outside module path
N’exécutez pas zig test directement sur des fichiers internes du framework. Utilisez les steps de test du dépôt, comme zig build test-ui (voir Commandes).
@import("ui") / @import("zenit_app") not found
Vérifiez que build.zig.zon déclare la dépendance .zenit et que vous appelez zenit.attach(zenit_dep, exe) après avoir créé l’exécutable — c’est lui qui ajoute les modules ui / zenit_app, compile les ponts macOS et lie les frameworks système.
build API field mismatch
Lancez d’abord zig version. zenit requiert Zig 0.15.2 (minimum_zig_version dans build.zig.zon) ; les build API des autres versions ne sont pas prises en charge.
-Dtest-mode=true has no effect
Zig isole les options des dépendances. Transmettez explicitement .@"test-mode" et .@"e2e-port" dans b.dependency("zenit", ...), comme le fait templates/minimal-app/build.zig.
Exécution et état
error.StateNotFound
Un cx.handler(State, id, method) à id explicite s’est exécuté avant que cet état n’existe. Préférez cx.bindState + cx.on : ils passent directement le pointeur d’état, sans id à gérer.
leaked ArrayList / HashMap at exit
L’état issu de bindState appartient au Cx et est libéré avec lui ; si T déclare un deinit(self: *T) pub, le framework l’appelle automatiquement. Une fuite signifie en général que deinit n’est pas pub, ou que la ressource aurait dû suivre la durée de vie d’une page. Voir la section suivante.
crash after switching pages
Cherchez un *Node ou un *Scope de la page précédente conservé dans un état global. Une fois le Scope de la page libéré, ces pointeurs sont invalides et doivent être effacés avec lui.
Invalid free after updating text
Le texte actuel du nœud peut être une copie qu’il possède. Ne modifiez pas content sur les props issues de getText() pour les repasser à setText ; utilisez try node.setTextContent(cx.allocator, "Updated"), qui copie la chaîne et marque correctement la propriété.
Nettoyer l’état correctement
Gardez un champ allocator dans l’état et laissez un deinit pub faire le nettoyage. La valeur initiale ne doit encore posséder aucune ressource — allouez après 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 });N’utilisez scope.onCleanup que si le nettoyage n’est pas un deinit pub, ou si la ressource doit suivre un Scope (une page) plutôt que le Cx entier.
// 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);Layout et dessin
state changed but the text did not
setText compare déjà l’ancienne et la nouvelle signature et marque sizing / render dirty si nécessaire — pas besoin de markRenderDirty manuel. Un texte figé signifie en général qu’une copie de TextProps a été modifiée sans appeler setText, ou que la valeur affichée ne s’est jamais abonnée à son Signal. Préférez ui.textFmt(cx, scope, fmt, .{ signals }, props) pour vous abonner directement aux Signals / Memos.
some colors ignore a theme switch
boxStyled / textStyled et consorts attachent un hook on_theme, et cx.setTheme ne les rejoue que dans le sous-arbre de cx.root ; les nœuds simples et les styles de composants sont des instantanés pris au mount. Reconstruisez ces arbres lors d’un changement de thème, ou abonnez-vous à cx.themeSignal(scope) dans un Effect et réappliquez les styles ; les nœuds non attachés sous cx.root ne sont pas mis à jour non plus.
clicks fall through an overlay’s empty area
Les conteneurs purement visuels sans interaction sont pass_through par défaut. Définissez hit_behavior = .@"opaque" sur l’extension de style de la racine de l’overlay — ou utilisez Modal / Sheet, qui apportent leur propre barrière.
a huge invisible hit area in the window
Attachez ui.devtools.overlay pour inspecter le nœud touché, et affichez son rect à l’écran avec node.globalRect(). La cause habituelle est un sizing fill / grow du parent ou un positionnement absolu trop grand ; pour les overlays masqués, vérifiez qu’ils sont retirés du hit-testing — un sous-arbre masqué avec node.setDisplay(.none) quitte entièrement le layout, le dessin, le hit-testing et le parcours au 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";Toujours bloqué
Quand vous ouvrez une issue, joignez au minimum :
- ✓
Versions : la révision de zenit,
zig versionet votre version de macOS. - ✓
Reproduction : un exemple minimal et le build step lancé.
- ✓
Sortie : la sortie d’erreur complète, pas seulement la dernière ligne.
- ✓
Problèmes de rendu : une capture d’écran et la taille des nœuds concernés relevée dans DevTools.