# Die App-Seite

## Hallo Welt

Das kleinste AURA-Programm liegt in `examples/hello/` und baut nur gegen `aura-*`:

```cmake
add_executable(aura-hello main.cpp)
aura_add_ui(aura-hello FILES hello.aura)    # Standardkontext
target_link_libraries(aura-hello PRIVATE aura::ui)
```

```cpp
#include <aura.h>
#include "aura_ui.h"	// generiert aus hello.aura

int main()
{
	AuraApplication app({.title = "Hello"});
	if (!app.init()) {
		return 1;
	}
	AuraBasicContext<> ctx(app);
	auto ui = views::Hello::build(app.addFrame(), ctx);
	return app.run();
}
```

`aura-hello --render datei.png` zeichnet ohne Fenster (ctest `aura_hello`). Ohne registrierte Schriften nimmt `Text` die Standardschrift (unter Linux über fontconfig).

## `aura.h`: AURA ohne Namespaces

`#include <aura.h>` (liegt bei aura-core) bringt alle öffentlichen Klassen mit Präfix `Aura`, wie
Qt mit `Q`: `AuraApplication`, `AuraTextNode`, `AuraImage`, `AuraTaskRunner`, `AuraWebApp`,
`AuraScriptContext`, `AuraRemoteController`, ... Eingebunden werden nur die Module, gegen die das
Programm linkt (`__has_include`). Regeln:

- **Klassen** sind Aliase (`using AuraTextNode = aura::ui::TextNode;`), auch Templates
  (`AuraProperty<int>`, `AuraBasicContext<MyTheme>`). Die echten Namen (`aura::ui::...`) gelten
  weiter, beides lässt sich mischen; Compiler-Meldungen zeigen die echten.
- Wo der Name allein zu allgemein ist, steht das Modul dazwischen: `AuraScriptContext`,
  `AuraScriptValue`, `AuraWebCaller`, `AuraRemoteTarget`, `AuraHtmlBlock`.
- **Funktionsgruppen** sind Klassen mit statischen Methoden: `AuraLog::info()`,
  `AuraStrings::trimmed()`, `AuraSystem::uptimeSeconds()`, `AuraTar::folderFiles()`,
  `AuraCrypto::sha256()`, `AuraHttp::send()`, `AuraTls::issueCertificate()`,
  `AuraWeb::jsonResponse()`, `AuraUiThread::post()`, `AuraUi::keyFromName()`, `AuraHtml::parse()`,
  `AuraInspect::render()`, `AuraBarcodes::ean13()`, `AuraRemote::routes()`, `AuraWebView::setup()`.
- **`AuraImage`** (`aura::ui::Image`) ist eine echte Wertklasse wie `QImage`: teilt die Pixel,
  Methoden liefern neue Bilder (`AuraImage::load("a.jpg", 1600).rotated(1).save("b.jpg")`), wird
  als `sk_sp<SkImage>` gezeichnet.
  - **HEIF/HEIC** (iPhone-Fotos) liest es mit libheif, wenn Skia das Format nicht kennt
    (`AURA_WITH_HEIF`, Standard an; libheif und ihr HEVC-Decoder libde265 vom System per
    pkg-config, dynamisch gelinkt, LGPL-3.0; ohne libheif baut AURA ohne HEIF). Die Drehung der
    Datei (irot/imir) wendet libheif an. Bei kleinen Größen nimmt es das eingebettete
    Vorschaubild: unter 400 px oder höchstens 16 px über dem Vorschaubild,
    notfalls hochskaliert (`src/heif.h`). Ein Gerät ohne libheif im System braucht `libheif.so` und
    `libde265.so` (mit dem libheif-Plugin-Verzeichnis) neben dem Programm.
- **`AuraJson`** und **`AuraXml`** sind vollständige Wrapper, keine Ableitungen: nlohmann/json
  und pugixml sind Implementierungsdetails (pugixml taucht in keinem Header auf, nlohmann nur
  privat in `aura/core/json.h`). Lesen wirft nie: Fehlendes oder ein anderer Typ liefert den
  Fallback (`j["a"]["b"].toInt(5)`, `node.intAttr("lanes", 1)`).
  - `AuraJson` ist ein Wert; Elemente sind Referenzen hinein: `AuraJsonRef` (schreibbar, aus
    `j["key"]` und der Iteration eines nicht-konstanten `AuraJson`) und `AuraJsonView` (nur
    lesen). Wie `std::string_view` dürfen sie ihr `AuraJson` nicht überleben; `AuraJson(j["a"])`
    kopiert. Ein fehlendes Element entsteht erst beim Schreiben (`j["a"]["b"] = 1` legt `a` an,
    `j["a"]["b"].toInt()` nicht). `parse()`/`load()` liefern bei Fehlern null und die Meldung
    über `std::string* error`. `{{"key", 1}}` ist ein Objekt, `{1, 2}` ein Array, `AuraJson{x}`
    ein Array mit `x` - `AuraJson(x)` kopiert.
  - YAML ist ein zweites Format desselben Werts (rapidyaml, `aura/core/yaml.cpp`), z. B. für
    Konfigurationsdaten: `AuraJson::parseYaml()`/`loadYaml()` (YAML 1.2 Core-Schema: `no` bleibt
    ein String, Anker und `<<` werden aufgelöst), `j.yaml()` und `j.saveYaml(file)`. `saveYaml`
    ändert eine vorhandene YAML-Datei an Ort und Stelle, wenn sich nur Werte geändert haben:
    Kommentare, Reihenfolge, Einrückung und Quotes bleiben. Kommen Schlüssel oder Einträge hinzu
    oder fallen weg, schreibt es die Datei neu, ohne Kommentare und mit sortierten Schlüsseln
    (`AuraJson`-Objekte sind sortiert). `AuraJson::updateYaml(text, j)` ist der Schritt dazwischen.
  - `AuraXml` besitzt das Dokument, `AuraXmlNode`/`AuraXmlAttribute` sind kleine Handles hinein
    (null, wenn nicht da). `children("name")`, `attributes()`, `select("//xpath")`,
    `toString(indent, Raw | NoEscapes)`; `AuraXml::parse()`/`load()` liefern `std::optional`,
    `read()`/`readFile()` füllen ein vorhandenes Dokument.
- Der Präfix `Aura` gehört AURA: Programme benennen ihre eigenen Klassen anders.
- aura-lang und aura-preview (Werkzeuge) gehören nicht dazu. `tests/test_aura_h.cpp` prüft
  jeden Namen; eine neue öffentliche Klasse kommt gleich in `aura.h`.

## Der Kontext

Der Kontexttyp (`CONTEXT_TYPE`) leitet von `aura::ui::Context` (`AuraContext`) ab (`aura/ui/context.h`). Beide Modi rufen dieselben Namen auf:

| Kontext | AURA | Standard in `Context` |
|---|---|---|
| `width`, `height` | `screen.width`, `screen.height` | 0 |
| `animator` | `animate:`, `enter:`, `exit:`, `spin:`, Rive | keiner: Werte springen |
| `tr(id, text)` | `tr("id", "Text")` | `text` |
| `fontSize(px)` | `font:`, `fit:`, `fontSize()` | `px` |
| `image(name)`, `svg(name)` | `source:` | `file(name)` über `Resources` |
| `file(name)` | `source:` von `Rive` | erste Datei in `assetDirs` |
| `propertyBool/Number/String(id)` | `props.bool/int/string("ID")` | `false`, 0, `""` |
| `themeValue(name)` | `theme.<name>` (nur geladen) | unbekannt |
| `theme` (Member der App) | `theme.<name>` (kompiliert) | – |

- **Kompiliert:** `theme.<name>` greift direkt auf das Member `theme` des Kontexttyps zu; der C++-Compiler prüft es.
- **Geladen:** `theme.<name>` geht über `themeValue()`. Die Namen stehen einmal in einer `aura::ui::ThemeTable<Theme>` (`aura/ui/theme_table.h`):

```cpp
static const aura::ui::ThemeTable<Theme> table{
	{"mainColor", &Theme::mainColor},	// Farbe
	{"textFont", &Theme::textFont},		// Schrift
	{"largeRadius", Theme::largeRadius},	// Konstante
};
```

**Neue Theme-Werte** kommen in `Theme` und in diese Tabelle. Fehlt der Eintrag in der Tabelle, meldet der Loader „unknown theme value“ und nimmt die kompilierte Fassung. Die VS-Code-Erweiterung bekommt die Namen für die Vervollständigung von der laufenden Vorschau (`Host::themeValues()`).

**Ohne eigenen Kontext:** `aura_add_ui` ohne `CONTEXT_INCLUDE` nimmt `aura::ui::BasicContext<>` (`aura/ui/basic_context.h`): Größe und Animator aus der `Application`, Bilder aus `assetDirs`, ein Theme vom Typ des Template-Arguments (`BasicContext<MyTheme>` mit `themeTable`).

```cpp
aura::ui::BasicContext<> ctx(app);
ctx.assetDirs = {"assets"};
```

## Daten aus dem Hintergrund

Was dauert (Netz, Dekodieren), läuft auf einem `aura::TaskRunner` der Anwendung; das Ergebnis kommt im UI-Thread an und geht über einen Setter in die UI. Für Aufgaben, bei denen nur die neueste zählt, gibt es `latest()`:

```cpp
// _orderLookup (aura::Latest): ein neuer Auftrag stoppt den alten
_tasks.latest(_orderLookup,
	// im Pool
	[url](std::stop_token stop) { return fetchHtml(url, stop); },
	// im UI-Thread, nur das neueste Ergebnis
	[this](const std::string& html) { _ui->setResult(html); });
```

Ein „lädt noch“ ist einfach Zustand (`property bool waiting`, `if waiting { … }`). Ein Setter mit unverändertem Wert tut nichts, die App muss also nicht selbst vergleichen.

## Laufzeit: Timer, Prozesse, Zeit, Geräte

Bausteine von aura-core ohne Grafik (auch in `aura.h` als `AuraTimer`, `AuraProcess`, `AuraTime`, …):

- **`aura::ElapsedTimer`** (`aura/core/timer.h`, wie `QElapsedTimer`): `start()`, `elapsed()` in ms, `restart()`, `hasExpired(ms)` auf der monotonen Uhr.
- **`aura::Timer`** (wie `QTimer`): `timeout` im UI-Thread nach dem Intervall, einmalig (`singleShot = true`) oder wiederholt. `start(ms)` beginnt neu, `pause()` hält die Restzeit, `resume()` läuft mit ihr weiter, `stop()` beendet; Löschen stoppt ihn. Ein wiederholter Timer plant vom geplanten Zeitpunkt weiter (kein Wegdriften), hinkt er um mehr als ein Intervall, vom Jetzt. `Timer::after(ms, f)` ist der einmalige ohne Objekt. Timer laufen, wo `aura::runPosted()` läuft; die Application-Schleife wacht pünktlich für den nächsten auf.

  ```cpp
  _updates.timeout = [this]() { updateLists(); };   // AuraTimer _updates (Member: geht mit dem Screen)
  _updates.start(std::chrono::seconds(1));
  ```
- **`aura::runProcess(argv, options)`** (`aura/core/process.h`, wie `QProcess`): `posix_spawn`, die Pipes mit `O_CLOEXEC`, im Kind alle anderen Deskriptoren geschlossen (nichts vom Programm leckt in das Kind), eigene Prozessgruppe, Standard-Signale. Optionen: Eingabe, Umgebung, Arbeitsverzeichnis, Timeout (SIGTERM, 2 s später SIGKILL), Obergrenze der Ausgabe, Ausgabe stückweise (`onOutput`). Es blockiert (Worker-Thread); `runProcessAsync(runner, …, done)` meldet das Ergebnis im UI-Thread.
- **Zeit** (`aura/core/datetime.h`): `setTimeZone("Europe/Berlin")` (prüft die Zone, sonst Europe/Berlin), `localTime()`, `fromLocal()`, `formatDateTime()` mit den Formaten von `QDateTime::toString` (`HH:mm:ss dd.MM.yyyy`, `d. MMMM yyyy` mit `DateNames::german()`, `h:mm AP`, `'Text'`), `toIsoString()`/`parseIsoDateTime()` (lokal, `Z`, `+01:00`). Über `TZ`/`localtime_r`, weil `std::chrono`s Zonen-Datenbank mit der alten String-ABI der Skia-Pakete nicht geht.
- **Geräte** (Linux, ohne udev):
  - `aura::UeventMonitor` (`aura/core/hotplug.h`): Kernel-Uevents über Netlink (`SOCK_CLOEXEC`, großer Puffer, gekürzte Nachrichten erkannt) auf einem eigenen Thread, im UI-Thread `onEvent` mit `action`, `subsystem`, `devtype`, `devname`, `properties`; `usbIdOf()` liest `PRODUCT` (VID/PID). `present("usb")` liefert die schon vorhandenen Geräte aus sysfs.
  - `aura::scsiDevices()`, `testUnitReady()`, `aura::MediumWatcher` (`aura/core/scsi.h`): SCSI-Laufwerke aus sysfs (ein Kartenleser mit vier Slots sind vier LUNs) und ob ein Medium drin ist, per TEST UNIT READY alle 200 ms (100 ms, solange eins erscheint); `onChange` mit Absent, Appearing (gerade eingesteckt), Present.
  - `aura::UsbmuxClient` (`aura/core/usbmux.h`): iPhones über usbmuxd ohne libimobiledevice: `onAttached`/`onDetached` (UDID, Produkt), `list()`, `connect(deviceId, port)` für eine rohe Verbindung zu einem Geräteport (lockdownd 62078). Pairing (TLS) und AFC bauen darauf auf und sind nicht Teil davon.

## Beobachtbare Modelle

Daten, die mehrere Stellen zeigen, hält das Modell beobachtbar (`aura/core/observable.h`, ohne Grafik):

```cpp
// Application data and generated UI setters stay separate.
aura::List<std::string> items;
aura::Subscription changed = items.observe([&](const auto& values) {
    ui->setItems(values); // generated for: property strings items
});
items.erase(index);
```

- **`aura::Property<T>`:** ein Wert mit `get()`/`set()`; `set()` mit gleichem Wert meldet nichts.
- **`aura::List<T>`:** eine Liste; jede Änderung meldet die ganze Liste, wie ein `repeat` in AURA die geänderten Einträge neu baut.
- **`aura::Subscription`:** `observe()` gibt sie zurück; sie beendet die Beobachtung, wenn sie zerstört wird. Als Member des Beobachters hält sie genau so lange wie er; sie darf das Modell überleben.
- **Thread:** Modelle ändert nur der UI-Thread; Ergebnisse aus dem Hintergrund kommen über `TaskRunner::post()` bzw. `latest()` dorthin.

## Was in AURA steht und was in C++

| AURA | C++ (App) |
|---|---|
| Aufbau, Aussehen, Texte, Anordnung, Layouts | woher die Daten kommen, was ein Signal bewirkt |
| Zustände über Properties (gewählt, gedrückt, Fehler) | Ablauf (Arbeitsabläufe und Sitzungen) |
| Listen über Datensätze, Bilder der App (`image`) | was die App selbst zeichnet (Fotos mit Beschnitt), in benannte Slots |
| Rahmen von Dialogen und Flyouts als Komponenten, Animationen (`animate`, `enter`, `loop`, `Ripple`) | mehrstufige Übergänge, Video, Scroll-Griff |

**Listen:** Listen der App sind Datensätze mit `repeat` in AURA; die App liefert die Daten und reagiert auf Signale mit einem Index oder einer Aktion:

Ausgerichtete Spalten ohne Tabellen-Layout: Die Preisliste misst die Spaltentitel unsichtbar mit (`Text { id: titleProbe; visible: false }`) und macht jede Zelle so breit; eine `Row` mit Abstand ergibt die Spalten.
