---
title: "Schnellstart — zenit Zig UI Doku"
description: "Erstellen Sie eine native macOS-App, die außerhalb des zenit-Repos liegt und eigenständig baut und läuft."
url: https://zenit.z.express/de/docs/guide/getting-started
language: de
alternate_en: https://zenit.z.express/docs/guide/getting-started.md
alternate_zh: https://zenit.z.express/zh/docs/guide/getting-started.md
alternate_es: https://zenit.z.express/es/docs/guide/getting-started.md
alternate_ja: https://zenit.z.express/ja/docs/guide/getting-started.md
alternate_ko: https://zenit.z.express/ko/docs/guide/getting-started.md
alternate_fr: https://zenit.z.express/fr/docs/guide/getting-started.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Schnellstart

Erstellen Sie eine native macOS-App, die außerhalb des zenit-Repos liegt und eigenständig baut und läuft.

Sie entwickeln mit Claude Code oder einem anderen Agenten? Installieren Sie zuerst den zenit UI dev Skill.

Er bündelt dieses Handbuch, die Entscheidungstabelle für Komponenten und Fallstricke aus echten Projekten zu Anweisungen, die Ihr Agent bei Bedarf lädt – so folgt der Code, den er schreibt, den zenit-Konventionen.

[Skill herunterladen.zip · 54 KB](https://zenit.z.express/downloads/zenit-ui-dev.zip)[Installationsanleitung](https://zenit.z.express/de/docs/guide/ai-skill)

## Voraussetzungen

Diese Version zielt auf macOS, Apple Silicon wird laufend verifiziert. Stellen Sie sicher, dass Zig 0.15.2 und die Xcode Command Line Tools installiert sind; Bun wird nur für End-to-End-Tests benötigt.

```sh
zig version
xcode-select -p
```

> WARNING
> 
> **Die Versionen müssen übereinstimmen.** Das Template deklariert `minimum_zig_version = "0.15.2"` in `build.zig.zon`; andere Versionen können auf verwirrende Weise an Build-APIs oder Paket-Fingerprints scheitern.

## Projekt anlegen

Starten Sie mit `templates/minimal-app` im Repo. Es ist ein vollständiges Downstream-Projekt:

`templates/minimal-app`

```
minimal-app/
├── README.md
├── build.zig          # zenit.attach() + run / app steps
├── build.zig.zon      # declares the zenit dependency
├── e2e/
│   └── record-demo.ts # drives and records the app through the Harness
└── src/
    └── main.zig       # counter-button app
```

1.  **Template kopieren**
    
    Kopieren Sie templates/minimal-app in ein neues Verzeichnis außerhalb des zenit-Repos.
    
2.  **Abhängigkeitspfad anpassen**
    
    Das Template verwendet standardmäßig `.path = "../../"`, was nur innerhalb des Repos auflöst. Richten Sie es nach dem Kopieren relativ zu build.zig.zon auf Ihren zenit-Checkout aus oder pinnen Sie eine veröffentlichte Revision mit `zig fetch --save=zenit <url>`.
    
3.  **Fingerprint erzeugen**
    
    Übernehmen Sie nicht den .fingerprint des Templates – zwei Projekte mit demselben Wert kollidieren im Paket-Cache von Zig. Löschen Sie die Zeile, führen Sie zig build einmal aus und fügen Sie den Wert aus der Fehlermeldung ein. Benennen Sie dabei auch .name = .myapp um.
    

```sh
git clone https://github.com/version-next/zenit.git
cp -R zenit/templates/minimal-app ./myapp
cd myapp   # then set .zenit = .{ .path = "../zenit" } in build.zig.zon
zig build
```

## zenit einbinden

`build.zig.zon` deklariert die Abhängigkeit; `build.zig` erhält über `@import("zenit")` die Build-API von zenit.

`build.zig.zon`

```zig
.{
    .name = .myapp,
    .version = "0.1.0",
    .fingerprint = 0x8798022a7f8e220e, // replace: delete this line, run zig build once
    .minimum_zig_version = "0.15.2",

    .dependencies = .{
        // Relative to this file. Absolute paths are rejected by Zig 0.15.2.
        .zenit = .{ .path = "../zenit" },
    },

    .paths = .{
        "README.md",
        "build.zig",
        "build.zig.zon",
        "e2e",
        "src",
    },
}
```

Hier ist das build.zig des Templates im Wortlaut. Es ist mehr als ein attach-Aufruf: Es leitet die Test-Schalter weiter, installiert den Harness-Client und definiert den run-Step und das .app-Packaging:

`build.zig`

```zig
const std = @import("std");
const zenit = @import("zenit");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const test_mode = b.option(bool, "test-mode", "Enable the Zenit automation harness") orelse false;
    const e2e_port = b.option(u16, "e2e-port", "Zenit Harness RPC directory suffix") orelse 19816;

    const zenit_dep = b.dependency("zenit", .{
        .target = target,
        .optimize = optimize,
        // Dependency build options are isolated in Zig. Forward these
        // explicitly so `zig build -Dtest-mode=true` reaches Zenit.
        .@"test-mode" = test_mode,
        .@"e2e-port" = e2e_port,
    });

    const exe = b.addExecutable(.{
        .name = "myapp",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    zenit.attach(zenit_dep, exe);
    zenit.installHarnessClient(b, zenit_dep);

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    // Optional: `zig build app` packages a double-clickable .app bundle.
    if (target.result.os.tag == .macos) {
        const bundled = zenit.bundleApp(b, .{
            .exe = exe,
            .display_name = "My App",
            .bundle_id = "com.example.myapp",
            .version = "0.1.0",
            .signing = .ad_hoc,
        });
        const app_step = b.step("app", "Build the .app bundle");
        app_step.dependOn(bundled.final_step);
    }
}
```

| Aufruf | Funktion |
| --- | --- |
| `zenit.attach(zenit_dep, exe)` | Fügt die Modul-Imports ui und zenit\_app hinzu, kompiliert die fünf macOS-ObjC-Bridges und linkt Cocoa, Metal, CoreText und die übrigen Frameworks |
| `.@"test-mode" / .@"e2e-port"` | Abhängigkeitsoptionen sind in Zig isoliert; leiten Sie sie explizit weiter, damit zig build -Dtest-mode=true zenit erreicht |
| `zenit.installHarnessClient(b, zenit_dep)` | Installiert den typisierten Harness-Client nach zig-out/share/zenit/harness/client.ts für e2e/record-demo.ts |
| `zenit.bundleApp(b, .{ ... })` | Erzeugt ein per Doppelklick startbares zig-out/<display\_name>.app, hier ad-hoc signiert |

## Den ersten UI-Baum mounten

`App.runWith` erzeugt den Root-Scope, ruft Ihre Mount-Funktion einmal auf und übernimmt die Event-Loop. Die Mount-Funktion muss die Signatur `fn (cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node` haben; Ihre App gibt nur den Wurzelknoten zurück.

`src/main.zig`

```zig
const std = @import("std");
const ui = @import("ui");
const App = @import("zenit_app").App;

const Counter = struct {
    n: u32 = 0,
    label: ?*ui.Node = null,
    buf: [32]u8 = undefined,

    pub fn increment(self: *Counter) void {
        self.n += 1;
        const node = self.label orelse return;
        const content = std.fmt.bufPrint(&self.buf, "Clicked {d} times", .{self.n}) catch return;
        if (node.getText()) |old| {
            var t = old;
            t.content = content;
            node.setText(t);
        }
        node.markRenderDirty();
    }
};

fn mountUi(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    const allocator = cx.allocator;

    const counter = try cx.bindState(Counter, .{});
    const click = cx.on(Counter, counter, Counter.increment);

    const root = try ui.box(cx, .{
        .width = .fill(),
        .height = .fill(),
        .direction = .column,
        .gap = 16,
        .padding = ui.Padding.all(40),
        .background = cx.tokens.color.bg_primary,
        .align_items = .center,
        .justify = .center,
    }, .{});

    try root.appendChild(allocator, try ui.text(cx, "Hello, zenit!", .{
        .font_size = 24,
        .font_weight = 600,
        .color = cx.tokens.color.fg_primary,
    }));

    const button = try ui.widgets.Button(.{
        .label = "Click me",
        .variant = .primary,
        .on_click = click,
    }).mount(scope, cx);
    button.meta.ownership.meta.test_id = "counter.increment";
    try root.appendChild(allocator, button);

    const label = try ui.text(cx, "Clicked 0 times", .{
        .font_size = 14,
        .color = cx.tokens.color.fg_secondary,
    });
    try root.appendChild(allocator, label);
    counter.label = label;

    return root;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const app = try App.init(gpa.allocator(), .{
        .window = .{ .width = 640, .height = 480, .title = "Hello, zenit" },
    });
    defer app.deinit();

    try app.runWith(mountUi);
}
```

`cx.bindState` übergibt `Counter` an den Cx, und `cx.on` macht eine seiner Methoden zum Button-Callback; mit `test_id` finden E2E-Skripte den Button über seine Bedeutung.

> NOTE
> 
> **Das Template aktualisiert den Text von Hand, um minimal zu bleiben.** Der Puffer liegt in `Counter` an einer stabilen Adresse, daher ist es sicher, ihn an `setText` zu verleihen. Echte Apps halten den Zähler meist in einem Signal und lassen `ui.textFmt` das Label aktualisieren – siehe [Reaktivität](https://zenit.z.express/de/docs/guide/reactivity).

## Bauen und ausführen

```sh
zig build run   # build and run the unbundled executable
zig build app   # package zig-out/My App.app (macOS)
```

[Video](https://zenit.z.express/media/hello-button.mp4?v=b4d2b8f8a0)

Die echte Hello Button.app, gestartet aus der Build-Ausgabe (examples/hello\_button, derselbe Zähler-Button wie im Template): Der harness klickt dreimal mit dem Hand-Cursor, und das Label zeigt „Clicked 3 times“. Fenster, Widget und Reaktion stammen alle aus der öffentlichen API oben.

Das Template enthält auch ein Aufnahmeskript: Bauen Sie mit `zig build -Dtest-mode=true`, starten Sie die App und führen Sie dann `e2e/record-demo.ts` aus, um einen ähnlichen Clip zu erhalten – siehe [E2E harness](https://zenit.z.express/de/docs/advanced/e2e).

> TIP
> 
> **Als Nächstes.** Sobald das Fenster erscheint, lesen Sie als Nächstes Projektstruktur und den UI-Baum. Bauen Sie nicht vorschnell eine eigene Komponentenschicht – zenit bringt bereits Widgets, Themes und reaktive Primitive mit.
