docs/advanced/devtools
Funktionen · Diagnostics

DevTools

Finden Sie Layout-, Hit-Testing-, Rendering- und Performance-Probleme mit einem einzeiligen Overlay, einem eigenständigen Panel und einer begrenzten, strukturierten Console.

9 Min. Lesezeit

Inspektor im Fenster

Hängen Sie das Overlay während der Entwicklung an Ihre Wurzel. Beim Hover umrandet es das Rect des Knotens gestrichelt und zeigt Größe und Komponentennamen. Das Overlay ist pass-through – es fängt weder Klicks noch Scrollen ab –, und Hover löst nur Updates auf Paint-Ebene aus, niemals ein Relayout.

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;
}

Der Zustand des Overlays hängt am übergebenen scope; wird dieser Scope freigegeben, verschwindet auch das Overlay. Nutzen Sie es im Produktfenster, um zu klären „Wer belegt diesen Platz?“; für die vollständige Diagnose nehmen Sie das Panel. Beide lesen den echten Node-/Cx-Zustand – kein Spiegelmodell, das Sie fürs Debugging pflegen müssten.

Diagnose nach Symptom

SymptomZuerst prüfen
Inhalt falsch ausgerichtetRect, Padding, Gap und Direction des Elternknotens (Elements → Layout)
Leere Bereiche sind anklickbarGröße und hit_behavior des getroffenen Knotens
Textdaten geändert, Anzeige nichtHaben Sie TextProps-Felder geändert, ohne setText / setTextContent aufzurufen? Diese APIs vergleichen selbst und markieren sizing / render dirty
Layout ignoriert neue BreiteEine direkte Zuweisung an node.style markiert nichts als dirty; verwenden Sie node.setStyle(alloc, .width, v), das pro Feld die richtige Dirty-Stufe wählt
Wird mit der Zeit langsamerWiederholte Mounts, nicht mit ihrem Scope freigegebene Effects, Allokationen pro Frame (Performance → Summary / Rebuild)

Das DevTools-Panel

Für die vollständigen Ansichten Elements, Components, Console und Performance verwenden Sie ui.devtools.mountPanel(cx, target, opts). Das Panel hat einen eigenen Cx und beobachtet einen separaten Target-Cx; üblicherweise liegt es mit MultiWindowApp in einem eigenen Fenster, damit es die Produkt-UI nie verdeckt. Der Target muss am Leben bleiben, solange das Panel genutzt wird.

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();
}

So ist zig build devtools-probe im Repo aufgebaut. ui.devtools bietet außerdem setViewMode, setTreeFilter, setConsoleFilter und weitere Funktionen, mit denen Probes und E2E-Tests das Panel programmatisch steuern.

Zenit DevTools Elements panel with a box selected and its layout details
Links der Live-UI-Baum; wählen Sie einen Knoten, zeigt der Layout-Tab rechts sein berechnetes Rect, Padding, Sizing und die Flex-Eigenschaften. Der Hand-Cursor im Bild ist der virtuelle Cursor des harness.

Vier Ansichten, sechs Detail-Tabs

AnsichtBeantwortet
ElementsWie sieht die echte Knotenhierarchie aus – tag / id / text, Layout und Hit-Bereich?
ComponentsWelche Knoten bilden Komponentengrenzen, und wo liegen ihr Zustand und ihr Owner-Scope?
ConsoleWelche strukturierten Logs hat der Target-Cx erfasst, und wurde etwas verdrängt oder verworfen?
PerformanceRendert der Target oder ist er idle? Was kosten Layout / Render / Cache / Interaction?

Bei ausgewählter Elements-/Components-Zeile wechseln Sie zwischen Layout, Style, State, Events, Render und Trace. Einige Style-Werte lassen sich live bearbeiten; Render / Trace zeigen, warum ein Knoten dirty wurde, und seine letzten Events. Die Baumsuche findet Tags, #id und Komponentennamen. Mit konfiguriertem ui.devtools.source_link springen Komponentenzeilen direkt zur Definition in Ihrem Editor.

Console-Logging

Jeder ui.Cx besitzt eine eigene threadsichere, begrenzte Console. Ein Aufruf kann ins Terminal schreiben und zugleich ein strukturiertes Event für DevTools und den E2E-harness behalten; auch vor dem Öffnen des Panels erfasste Einträge werden angezeigt.

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)});

Die Level sind debug, log, info, warn und err. Auch die Gegenstücke der Browser-Konsole sind vorhanden: group / groupEnd, count, time / timeEnd, assert, trace, inspect und table.

Panel-BereichZweck
ClearLeert den Console-Speicher des Targets selbst, nicht nur die Ansicht.
FilterGroß-/Kleinschreibung-unabhängige Suche in message und scope; kombiniert mit dem Level-Filter.
LevelsDebug / Log / Info / Warn / Error beliebig kombiniert ein- und ausschalten.
Log-ListeLevel, scope, Nachricht, group-Einrückung und optionale Quellposition; Auto-Follow pausiert, sobald Sie vom Ende wegscrollen.
Statusleisteshown / captured / evicted / dropped – unterscheidet Filterung, Verdrängung und Verlust.
Zenit DevTools Console with debug, info, warn and error entries
Die Logs landeten vor dem Öffnen des Panels im begrenzten Speicher des Target-Cx; scope, Level und Quellposition bleiben erhalten.
Zenit DevTools Console filtered to the network scope
Filter und Levels wirken lokal in DevTools zusammen; aus dem Speicher des Targets wird nichts entfernt.
Eine echte Zwei-Fenster-App: Der virtuelle Cursor wechselt von Elements zu Console, fokussiert Filter und tippt network. Direkt aus dem Metal-Drawable des DevTools-Fensters aufgezeichnet.

Die einfachen Level-Methoden zeichnen keine Aufrufstelle auf. Um per Klick auf eine Logzeile im Editor zu landen, verwenden Sie writeAt(level, @src(), …) und setzen Quellwurzel und Editor-Befehl mit ui.devtools.source_link.configure (Standard: code --goto).

Erfassung konfigurieren

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,
    },
});

Ohne explizites console wählt zenit_app die Standardwerte nach Build-Modus:

Build-ModusTerminalErfassung
Debug.debug.debug
ReleaseSafe.info.info
ReleaseFast / ReleaseSmall.warnnull (aus)

Die Console bildet die Erfassungs- und Anzeigeseite der Browser-Konsole nach; sie ist kein Zig-/JavaScript-REPL. Groups werden derzeit nur eingerückt, lassen sich aber nicht interaktiv einklappen, und table wird als Text ausgegeben. Vollständige API, Thread- und Lebensdauerregeln sowie harness-Beispiele stehen in docs/CONSOLE.md im Repo.

Performance: idle oder hängend

Die Performance-Ansicht friert nicht auf „den letzten N gerenderten Frames“ ein. Sie beobachtet den Target fortlaufend in 100-ms-Wanduhr-Buckets – 64 davon, ein rollendes Fenster von etwa 6.4 s – und berechnet die FPS als Mittel der letzten 10 Buckets (etwa 1 s). Liefert der Target länger als 0.7 s keinen Frame, zeigt die Anzeige FPS: 0 — idle (not rendering): Das Framework lässt bewusst Frames aus, um Energie zu sparen, und hängt nicht bei 0 FPS.

100MS BUCKETS
Nach dem Frame-Stopp läuft das Diagramm weiter und zeichnet Nullen auf; die Anzeige sinkt mit jedem leeren Bucket und wechselt nach 0.7 s auf idle – ein veralteter Wert wird nie als aktuell ausgegeben.
Zenit DevTools Performance showing FPS 0 idle (not rendering) with timing metrics
Der statische Target rendert nicht mehr; DevTools aktualisiert sich selbst mit etwa 10 Hz, damit das Diagramm weiterläuft, und die Metriken für Layout, Render, Cache, Focus und Interaction bleiben lesbar.
AnzeigeBedeutung
FPS + rollendes DiagrammWanduhr-Buckets; ein Bucket ohne Frames zählt 0 und wird als neutrale Grundlinie gezeichnet, nie als veralteter Wert
TimingCPU-Wanduhrzeit des letzten Target-Frames: Layout, Erzeugung der Render-Befehle usw.
Summary / InteractionHit-Registry, mouse-hit, Redraw-Streak, Focus und Interaction-Rebuilds
Cache / RebuildRetained-Cache-Treffer / -Fehlschläge, Full- / Partial-Rebuilds

Test-Einstiegspunkte

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

Vor dem Release

  • ✓

    Debug-UI abschalten. Entfernen Sie das Inspektor-Overlay oder binden Sie es an eine reine Debug-Konfiguration.

  • ✓

    Eingabe im echten Fenster prüfen. Tastatur, IME, Zwischenablage und VoiceOver.

  • ✓

    Build-Gates ausführen. Die Test-Steps für headless, UI und Render.

  • ✓

    Console-Richtlinie prüfen. Terminal-Level und Erfassung im Release sind wie beabsichtigt, und die Logs enthalten keine sensiblen Daten.

  • ✓

    Versionen festschreiben. Fixieren Sie die zenit-Revision und halten Sie die Ziel-Zig-Version fest.

zenit · DoppellizenzKostenlos für Open-Source-Projekte unter GPL-3.0-only; Closed-Source- oder kommerzielle Produkte benötigen eine kommerzielle Lizenz.Kontakt zum Autor: zongyi.xzy#gmail.com (# durch @ ersetzen)zenit 5f9add5+wip 2026-09-30