Commit Graph
76 Commits
Author SHA1 Message Date
ScarriffleandClaude Opus 5 b3ad9c49d6 Den Tastenabgriff näher an die Hardware setzen
An der Sitzung angesetzt fing er synthetische Ereignisse zuverlässig ab — die
echten Lautstärketasten aber nicht: nachgemessen läuft der Abgriff, und ein
selbst erzeugter Tastendruck wird erkannt und verbraucht, während der Druck auf
die Taste weiterhin Apples Einblendung auslöste. Auf neueren macOS-Fassungen
werden die Tasten offenbar weiter vorn behandelt, bevor die Sitzung sie sieht.

`cghidEventTap` sitzt davor. Ob das reicht, zeigt erst die echte Taste — synthetisch
ist beides nicht zu unterscheiden.

Die Protokollzeilen bleiben vorerst drin: ohne sie ist „es geht immer noch nicht"
nicht von „der Abgriff läuft gar nicht" zu trennen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 21:05:25 +02:00
ScarriffleandClaude Opus 5 91b83928ca Der Einblendung zuvorkommen, statt sie abschalten zu wollen
Der Schluss im letzten Commit war falsch. Aus „der Dienst liegt in der
Benutzer-Domäne" hatte ich geschlossen, dass `launchctl bootout` ihn dort
aushängt. Nachgemessen, diesmal ausführend:

    Boot-out failed: 150: Operation not permitted while
    System Integrity Protection is engaged

SIP schützt den Dienst, nicht nur den Dateipfad; die Domäne spielt keine Rolle.
Ein rein lesender Spike konnte das nicht zeigen — nur der Versuch. `SystemOSD`
fliegt deshalb wieder raus: Code, der behauptet zu wirken und es nicht tut, ist
schlimmer als keiner.

Stattdessen ein `CGEventTap` auf die Lautstärketasten. Onyx regelt selbst und
verbraucht das Ereignis — was das System nie erreicht, blendet es auch nicht
ein. Preis ist die Berechtigung „Bedienungshilfen"; ohne sie bleibt alles wie
vorher, und der Dialog wird angefragt statt stumm zu scheitern.

Vorteil gegenüber dem Aushängen: es verschwindet nur die Einblendung für die
Tasten, die Onyx auch bedient. Die Feststelltaste behält ihre — beim Aushängen
wäre sie mit verschwunden, und dafür hat Onyx keinen Ersatz.

Der Rückruf steht als eigene Funktion und nicht als Verschluss in `start()`: als
Verschluss brachte er Swift 6.3.3 im Durchgang `SendNonSendable` zum Absturz.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:02:35 +02:00
ScarriffleandClaude Opus 5 a3399d3a96 Zwei Anzeigen für einen Handgriff sind eine zu viel
Apples eigene Einblendung lässt sich aushängen, ohne Root und ohne SIP
anzufassen: `com.apple.OSDUIHelper` liegt in der GUI-Domäne des angemeldeten
Benutzers, nicht im System. `launchctl bootout` dort hinein genügt.

Standardmäßig aus, denn es verschwindet **jede** Einblendung — auch Helligkeit
und Feststelltaste, und für die Feststelltaste hat Onyx keinen Ersatz. Beim
Beenden wird wieder eingehängt: eine Einstellung darf nicht überleben, was sie
eingestellt hat.

Dazu zwei Fehler im Anlernen behoben. Der Posenname stand als roher Schlüssel da
— die Zeichenketten liegen im Bündel von `FaceProvider`, gesucht wurden sie im
Hauptbündel. Und „Gesicht vergleichen" blieb nach dem Anlernen grau, weil
`isEnrolled` bei jedem Aufruf den Schlüsselbund las: richtig, aber für SwiftUI
unsichtbar, weil es nichts gab, woran eine Ansicht die Änderung hätte bemerken
können.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 22:26:41 +02:00
ScarriffleandClaude Opus 5 fd29df51cf Im Band ist Platz für mehr als einen Wert
Die Begründung für „ein Wert je Seite" war falsch gerechnet: der Arm ist bei
fünf Kacheln je 376 Punkte breit, und ein Wert braucht davon knapp sechzig. Die
Zahl im Kopf war die der schmalen Insel, nicht die des ausgefahrenen Arms.

Jetzt bis zu vier Elemente je Seite, in wählbarer Reihenfolge — Uhr, Wetter,
Akku, CPU, Speicher, Temperatur. Kein Ziehen zum Umsortieren: bei höchstens vier
Einträgen ist Herausnehmen und wieder Hinzufügen schneller als eine Ziehgeste,
die man erst finden muss.

„Nichts" ist keine Wahl mehr, sondern die leere Liste — zwei Wege, dasselbe zu
sagen, waren einer zu viel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:32:24 +02:00
ScarriffleandClaude Opus 5 e3b215a1dd Ein eigener Sperrbildschirm — und ohne gespeichertes Passwort
Kameraaufnahme, Anlernen über drei Posen, Fenster je Bildschirm auf
Bildschirmschoner-Ebene, Passwortrückfall.

**Das Anmeldepasswort wird nicht gespeichert.** Ein Spike hat den Weg
freigeräumt: `ODRecord.verifyPassword` ist öffentliche API, die Eingabe geht
direkt hinein, das Ergebnis ist ein `Bool`, danach ist die Zeichenkette wieder
weg. Damit fällt der Grund weg, aus dem vergleichbare Programme sich das
Passwort geben lassen und in die Keychain legen.

Angelernt wird über drei Posen mit je fünf Aufnahmen. Der Merkmalsvektor ist ein
allgemeiner Bildmerkmalsvektor und kein Gesichtsmerkmal — er reagiert auf
Kopfhaltung und Beleuchtung mit. Eine einzige Aufnahme beschreibt genau eine
Haltung bei genau einem Licht.

Die Vektoren liegen in der Keychain, nicht in einer Datei: aus ihnen lässt sich
kein Bild zurückgewinnen, aber wer sie hat, kann prüfen, ob eine bestimmte
Person vor der Kamera stand.

Die Einschränkung steht auf dem Sperrbildschirm selbst und nicht im
Kleingedruckten: Bequemlichkeit, keine Sicherheitsgrenze; der Vergleich läuft
über die Webcam und lässt sich mit einem Foto täuschen. Wer sich darauf
verlässt, tut es dort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:22:32 +02:00
ScarriffleandClaude Opus 5 edb327cabe Kacheln und Fenster sprechen dieselbe Sprache
Alle Kacheln, Popovers und Fenster auf die Designschicht umgestellt: rund
hundert gesetzte Schriftgrößen und 159 gesetzte Abstände und Radien sind jetzt
Token. Übrig bleibt `spacing: 0` — das ist eine Aussage, keine geratene Zahl.

Die Einstellungen bekommen eine Seitenleiste statt sieben Reitern, deren
deutsche Beschriftungen zusammen 880 Punkte brauchten. Panel-Token stehen nicht
mehr in Fenstern: dort gilt Systemsemantik, damit der Hellmodus nicht schwarz
auf schwarz endet.

Neu ist ein Schaufenster, das alle Bauteile nebeneinander zeigt — ohne das
lässt sich eine Änderung an einem Token nur prüfen, indem man die App bedient,
bis jede Kachel einmal zu sehen war.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:05:51 +02:00
ScarriffleandClaude Opus 5 9b600de9a9 Live Activities unter der Notch, nicht daneben
Neben ihr wären die Arme zwangsläufig gleich breit — die Silhouette zeichnet den
Steg mittig um die Hardware. Ein Symbol links und ein Balken rechts hieße dann
132 Punkte Schwarz für ein Symbol. Darunter hängt die Fläche frei und wird so
breit, wie ihr Inhalt es braucht.

Lautstärke, Strom, Titelwechsel und Helligkeit. Gemeldet wird nur, was sich
**geändert** hat: `PowerWatcher` schweigt beim ersten gelesenen Zustand, sonst
begrüßte Onyx jeden Start mit „am Netz".

Die Helligkeit kostete einen Spike. Von drei Wegen trägt nur `DisplayServices`,
und nur für den eingebauten Bildschirm. `CoreDisplay` — der Weg, den jede
Anleitung empfiehlt — gibt für alle Bildschirme 1.0 zurück, auch für den, der
auf 62 % stand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:05:13 +02:00
ScarriffleandClaude Opus 5 ecb39fbe5f Eine Designschicht statt hundertfach gesetzter Zahlen
Token für Schrift, Symbolgröße, Abstand, Radius und Bewegung, dazu sieben
Bauteile, die vorher in jeder Kachel neu gebaut wurden — Karte, Abschnittstitel,
Hinweis, Pillenknopf, Regler, Ablegefläche, Haarlinie.

Symbole bekommen eine **eigene** Staffel und nicht die der Schrift: ein
9-Punkt-Symbol neben 9-Punkt-Text wirkt kleiner, nicht gleich groß. Beide in
eine Staffel zu zwingen hieße, an jeder Stelle gegen sie zu arbeiten.

Das Flächenrezept — Tint, Bänderung, Kante — stand zweimal da, einmal für das
Panel und einmal für Fenster. Es ist jetzt `OnyxStone`, generisch über die Form,
damit auch die animierende Notch-Silhouette es benutzen kann.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:04:51 +02:00
ScarriffleandClaude Opus 5 49a3ff0548 Panel je Bildschirm messen, nicht am schmalsten
Die Breite kam vom **schmalsten** aktiven Bildschirm — mit der Begründung,
dasselbe Panel erscheine überall. Ein Fernseher widerlegt das: bei 720 Punkten
Breite bleiben drei Spalten, vier Widgets brechen auf zwei Reihen um, und das
Panel wird hoch statt breit. Auf dem Notebook, wo achtmal Platz gewesen wäre.

Jetzt rechnet jeder Bildschirm für sich. Der Koordinator kennt seine
Bildschirme ohnehin und reicht die Kennung schon durch — sie wurde nur nicht
benutzt.

Die zweite Hälfte war, dass es nach dem Abstecken so blieb: die Größe wurde
einmal gerechnet und nie wieder. `NSScreen` meldet sich nicht bei SwiftUI,
also gibt es jetzt einen mitgezählten Bildschirmwechsel, den die Ansicht
mitliest, und beim Wechsel werden die Fenstergrößen neu gesetzt — verzögert um
einen Durchlauf, damit der Koordinator seine Fenster vorher neu aufgebaut hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:59:46 +02:00
ScarriffleandClaude Opus 5 ee848b86cc Das Aufnahmesymbol kam vom Mixer, nicht vom Pegel
Gemessen über kAudioProcessPropertyIsRunningInput: mit laufendem Mixer nimmt
Onyx auf, ohne ihn nicht. Ein Process Tap ist für macOS eine Tonaufnahme —
auch wenn damit nur die Lautstärke geregelt wird. Solange der Mixer Chrome und
Firefox abgreift, steht das orange Symbol in der Leiste.

Der Mixer stand seit der Fehlersuche am Wochenende auf „automatisch starten",
deshalb war es dauerhaft. Das Häkchen steht jetzt wieder aus.

Ein Hinweis unter dem Häkchen sagt das jetzt, wo die Entscheidung fällt: wer
das Symbol ohne Erklärung sieht, sucht den Fehler bei sich oder hält die App
für unsauber.

Dazu ein echter Fund an derselben Stelle: der Mikrofonpegel hing an
`onDisappear` der View — und das bleibt bei einem NSPopover aus. Dessen
Controller lebt nach dem Schließen weiter, die View verschwindet nie aus der
Hierarchie. Die Messung wäre also beim ersten Öffnen angegangen und nie wieder
aus. Sie hängt jetzt am Popover selbst: `MenuBarModule` bekommt
`popoverDidOpen`/`popoverDidClose`, gemeldet auch dann, wenn das Popover von
selbst zugeht — beim Wechsel in eine andere App etwa.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 15:20:07 +02:00
ScarriffleandClaude Opus 5 c94c6166f5 Der Farbwechsel kommt jetzt auch an
Farbton und Symbolfarbe standen beim **Erzeugen** der Fenster, also außerhalb
jeder View. Damit wird der Wert genau einmal gelesen und SwiftUI erfährt nie,
dass er sich geändert hat: die Reiter, Knöpfe und Symbole blieben in der
Farbe stehen, die beim Öffnen galt, während der Farbwähler daneben schon die
neue zeigte.

Jetzt stehen beide im Rumpf der jeweiligen View — Panel, Einstellungen,
Einrichtung. Dort registriert SwiftUI den Zugriff und zeichnet neu.

Bei den Popovers blieb es beim Erzeugen: die entstehen bei jedem Öffnen neu,
lesen also ohnehin den aktuellen Wert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:18:06 +02:00
ScarriffleandClaude Opus 5 1432ef3a6f Der Systemfarbknopf zeigt, welche Farbe er nimmt
Er tut, was draufsteht — nur weiß niemand auswendig, welche Farbe das System
gerade führt. Dann wirkt das Ergebnis wie ein Fehler statt wie eine Übernahme.
Jetzt sitzt der Farbfleck daneben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:00:56 +02:00
ScarriffleandClaude Opus 5 0df566d531 Symbole folgen der Akzentfarbe, nicht der des Systems
SwiftUI zeichnet Symbole in Listen und Formularen auf macOS in der
Akzentfarbe des Systems — nicht in der des Fensters. `.tint` an der Wurzel
half deshalb nur der Reiterleiste; die Widget-Liste blieb systemgrün neben
einer blauen Leiste, obwohl beides derselben Einstellung folgen soll.

Ein eigener Label-Stil setzt die Farbe am Symbol. Er vererbt sich über die
Umgebung, also genügt eine Zeile an denselben vier Wurzeln, an denen auch der
Farbton gesetzt wird: Panel, Popovers, Einstellungen, Einrichtung.

Ein Symbol, das seine Farbe selbst mitbringt, behält sie — die innere Angabe
schlägt die äußere. Der grüne Haken für „erteilt" bringt sie jetzt am Symbol
mit statt am ganzen Label: Grün heißt dort „erteilt" und ist keine
Geschmacksfrage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:48:57 +02:00
ScarriffleandClaude Opus 5 fde3e864c0 Zwei-Faktor-Anmeldung bei Calendarr
Deine Zugangsdaten stimmten. Der Fehler lag im gewählten Endpunkt:

  auth_router.py:81   if user.totp_enabled:
                          raise HTTPException(401, detail="2fa_required")

/api/auth/token nimmt nur ein Formular mit Benutzername und Passwort entgegen
und weist Konten mit zweitem Faktor grundsätzlich ab — mit demselben 401 wie
ein falsches Passwort. Deshalb stand da „Benutzername oder Passwort stimmen
nicht", obwohl beides stimmte.

Angemeldet wird jetzt über /api/auth/login: der Weg nimmt den Code entgegen
und kennt remember_me. Das ist hier keine Bequemlichkeit, sondern die
Bedingung — mit zweitem Faktor kann Onyx sich nicht selbst neu anmelden, den
Code hat nur der Mensch. Ein gewöhnliches Token hielte sieben Tage,
remember_me hält hundertachtzig.

Damit wandert auch das Token in den Schlüsselbund. Bei einem Konto mit
zweitem Faktor ist es das einzige, womit Onyx noch hineinkommt; ginge es beim
Beenden verloren, müsste man sich bei jedem Start neu anmelden.

Die drei Fälle sind jetzt auseinandergehalten und einzeln beschriftet:
falsches Passwort, fehlender Code, falscher Code. Der Unterschied ist der
zwischen „Passwort neu tippen" und „App aufmachen" — und er steckt beim
Server nur im Begleittext eines 401.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:44:26 +02:00
ScarriffleandClaude Opus 5 e2b86c0216 Calendarr direkt vom Server
Bisher las Onyx nur den Schnappschuss aus dem gemeinsamen Ordner. Der ist so
aktuell wie der letzte Start der Calendarr-App — wer sie eine Woche nicht
öffnet, sieht wochenalte Termine, und Onyx kann nichts dafür und nichts
dagegen. Der Live-Weg stand im Plan als „bewusst weggelassen, später ohne
Umbau nachrüstbar". Das stimmte: das Quellenprotokoll hat gehalten.

Ein einziger Aufruf genügt. /api/caldav/events fasst CalDAV-Konten, lokale
Kalender, Geburtstage, iCal-Abos und Google bereits zusammen — nachgebaut wird
nichts.

Angemeldet wird mit Adresse, Benutzername und Passwort statt mit einem Token,
und der Grund steht im Backend:

  auth.py:17   ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7   # 7 days

Ein eingetragenes Token wäre nach einer Woche tot. Onyx holt sich seines
deshalb selbst und erneuert es, bevor es abläuft — die Gültigkeit liest es aus
dem Token, statt sieben Tage anzunehmen; die stehen heute so im Server und
morgen vielleicht nicht mehr. Kommt trotzdem ein 401, wird einmal neu
angemeldet und der Abruf wiederholt.

Adresse und Benutzername stehen in den Einstellungen, das Passwort im
Schlüsselbund — `defaults read` ist keine Stelle, an der ein Passwort
mitgelesen werden soll. Geprüft wird beim Verbinden, nicht erst beim nächsten
Laden: ein Tippfehler, den man am Folgetag bemerkt, ist ein vermeidbarer.

Der Schnappschuss bleibt das Netz darunter. Ohne Zugangsdaten ändert sich
nichts, und ohne Netzwerk hält er, was Calendarr zuletzt geschrieben hat.
Nachgesehen wird am Server alle fünf Minuten statt alle zwei: ein Dateizugriff
kostet nichts, eine Anfrage über das Netz schon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:34:57 +02:00
ScarriffleandClaude Opus 5 3c36092712 Knopf zum Nachsehen bei Calendarr
Onyx holt sich nichts, es liest den Schnappschuss und erfährt neue über eine
Darwin-Nachricht. Wer Calendarr gerade erst gestartet hat, will aber nicht auf
den nächsten Blick warten, sondern jetzt nachsehen.

Der Knopf steht in jedem Zustand, nicht nur wenn Termine da sind: gerade wenn
„noch nichts geschrieben" oder „bitte anmelden" dasteht, will man es nach dem
Beheben sofort noch einmal versuchen. Beim Laden zeigt er einen Kreisel, in
fester Breite — sonst zuckt die Zeile, weil der Kreisel schmaler ist als der
Pfeil.

Dabei aufgefallen: die Statustexte für „noch nichts geschrieben", „abgemeldet"
und „zu neues Format" standen ohne Übersetzung im Katalog. Wer in einen dieser
Zustände geriet, las den rohen Schlüssel. Jetzt übersetzt, dazu die zwei
Zustände, die gar nicht behandelt waren — kein Kalenderzugriff und „wird
gelesen".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:23:34 +02:00
ScarriffleandClaude Opus 5 102c870835 Helfer nach dem Umzug: die App repariert ihre Anmeldung selbst
macOS bindet einen privilegierten Dienst beim Anmelden an das Programm, das
ihn angemeldet hat. Zieht das Programm um — aus „Downloads" nach „Programme",
oder vom Bauverzeichnis in ein installiertes Bundle —, ist das ein anderes
Bundle. Der alte Dienst läuft weiter und redet nicht mit dem neuen Programm.

Von außen ist das die verwirrendste Kombination, die es gibt: der Zustand
meldet „eingerichtet", und trotzdem kommt bei jedem Zugriff „Kommunikation
mit der Hilfs-App nicht möglich". Ohne Reparatur bleibt nur, den Helfer von
Hand zu entfernen und neu einzurichten — und darauf muss man erst kommen.

Scheitert die Verbindung, während der Zustand „eingerichtet" meldet, meldet
die App den Dienst jetzt einmal ab und wieder an. Einmal je Programmlauf:
öfter wäre eine Schleife. Danach steht er auf „wartet auf Freigabe" — das
Verschieben eines Programms mit Root-Dienst darf niemand still hinnehmen,
auch diese App nicht.

Dazu ein Fehler im DMG-Bau, der beim Nachbauen auffiel: war ein gleichnamiges
Volume noch eingehängt, hängte macOS das neue als „Onyx 1" ein, während das
AppleScript weiter „Onyx" einrichtete. Das Abbild kam ohne Fenstergestaltung
heraus, ohne dass irgendetwas fehlschlug. Jetzt wird vorher abgehängt und der
Einhängepunkt aus der Ausgabe gelesen statt geraten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:08:45 +02:00
ScarriffleandClaude Opus 5 152d36c904 Vorschau: Notch zurück, und der eigene Hintergrund wird wirklich versucht
Die Notch verschwand, sobald zum ersten Mal ein Bild geladen wurde: ein
skalierbares Bild meldet seine eigene Größe als Wunschgröße, und die tausend
Bildpunkte der Vorlage haben den Stapel auf tausend Punkte aufgeblasen. Notch,
Menüleiste und Auslösefläche saßen dann am Rand dieser unsichtbaren Fläche und
lagen außerhalb des Ausschnitts. Vorher fiel es nicht auf, weil nie ein Bild
ankam. Das Bild wird jetzt auf Maß gebracht, bevor es in den Stapel geht.

Und der eigentliche Wunsch: dein Hintergrundbild statt eines Ersatzes. Es
liegt auf dem Schreibtisch, und dorthin fragt macOS beim ersten Lesen einmal
nach. Die Prüfung mit isReadableFile, die ich vorher davorgesetzt hatte,
beantwortet macOS in diesen Ordnern mit „nein", ohne zu fragen — die Vorschau
blieb damit dauerhaft beim Ersatzbild, obwohl eine Freigabe möglich gewesen
wäre. Jetzt wird gelesen statt geprüft, mit den drei nötigen Erklärungen in
der Info.plist. Wird die Freigabe verweigert, bleibt es beim Systembild.

Ein Tahoe-Hintergrund als Ersatz ist nicht drin: unter den Systembildern liegt
davon nur „Tahoe Day.mov", ein Video von 170 Megabyte. Deshalb weiter Sonoma.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:54:11 +02:00
ScarriffleandClaude Opus 5 ca9e272ad8 Einstellungen: die Reiter schalten wieder, und die Leiste passt ins Fenster
Zwei Folgen der eigenen Reiterleiste, beide meine Schuld.

Der Reiter oben sprang um, der Inhalt blieb stehen: der gewählte Reiter liegt
im Fenster-Controller — er muss dort liegen, damit ein erneutes Öffnen ihn
setzen kann, etwa auf „Berechtigungen", wenn eine fehlt. Der Controller war
aber nicht beobachtbar. `TabView` fiel das nie auf, weil es seinen Zustand
selbst führte und die Bindung nur benachrichtigte. Ein `switch` im Rumpf
braucht dagegen eine Änderungsmeldung, und die kam nie. Jetzt ist der
Controller `@Observable`, und beide Richtungen funktionieren.

Zu breit war sie wegen `fixedSize`: sieben deutsche Beschriftungen brauchen
rund 880 Punkte, und die nahm sich die Leiste auch dann, wenn das Fenster 780
breit war — sie ragte links und rechts über die Kante hinaus, der erste und
der letzte Reiter waren nicht mehr erreichbar. Ohne feste Breite bleibt sie
im Fenster, und das Fenster ist jetzt 920 breit statt 780, mit 900 als
Mindestmaß. Darunter würde gestaucht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:38:40 +02:00
ScarriffleandClaude Opus 5 2c3b3cdd55 Einstellungen: die Linie unter den Reitern ist weg, und eine Fläche für alles
Die Linie kam von `TabView` selbst: es zieht unter seiner Leiste einen Trenner
quer durchs Fenster, und der lässt sich nicht abschalten. Der Abstand nach
oben, den ich beim letzten Mal eingebaut habe, hat deshalb nichts geändert —
der Trenner ist mitgewandert. Jetzt ist die Reiterleiste selbst gebaut, ein
Auswahlfeld über dem Inhalt, dann gibt es die Linie gar nicht erst.

Der zweite Punkt hat dieselbe Wurzel: die Formulare bringen ihren eigenen
Untergrund mit, der Rest des Fensters hat den des Systems. In Dunkelgrau
liegen die beiden ein, zwei Prozent auseinander — sichtbar als Kante quer
durchs Fenster, genau dort, wo das Formular anfängt. Der Untergrund der
Formulare ist jetzt abgeschaltet, und das ganze Fenster hat einen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:34:47 +02:00
ScarriffleandClaude Opus 5 6c92e7fed6 Auslösefläche: die Vorschau zeigt wieder einen Bildschirm
Dein Hintergrundbild liegt in ~/Desktop, und dorthin lässt macOS keine App
ohne Freigabe. NSWorkspace gibt den Pfad trotzdem heraus, NSImage gibt danach
still nil zurück — und die Vorschau blieb schwarz, ohne jeden Hinweis worauf.
Gemessen: isReadableFile meldet false, das Laden schlägt fehl.

Um eine Ordnerfreigabe zu bitten, nur um ein Vorschaubildchen zu malen, wäre
die falsche Frage. Also wird jetzt vorher geprüft, ob sich die Datei lesen
lässt, und wenn nicht, kommt ein Standardhintergrund von macOS —
/System/Library/Desktop Pictures, von neu nach alt durchgesehen, hier landet
es bei Sonoma. Die Bilder liegen auf jedem Mac und werden nur gelesen.

Geladen über CGImageSource statt NSImage: die Vorlagen sind 6016 Punkte im
Quadrat und zwanzig Megabyte schwer, gebraucht werden tausend Punkte. Und ein
Hintergrundbild kann mehrere Bilder enthalten, hell und dunkel im selben HEIC.

Das Dekodieren dauert gut hundert Millisekunden, deshalb läuft es neben dem
Zeichnen statt davor — sonst hätte das Fenster beim Öffnen so lange gestanden.
Danach liegt das Bild im Cache.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:30:43 +02:00
ScarriffleandClaude Opus 5 2aa60eedf9 Farben einstellbar — und erst mal überhaupt eine Farbe
Das Grün, das überall auftauchte, war die System-Akzentfarbe: `.tint` und
`.accentColor` werden ohne eigene Angabe dorthin durchgereicht, und das
betraf alle Popovers und das ganze Einstellungsfenster. Die Notch-Widgets
dagegen benutzten Onyx' eigenes Blau. Zwei Farben, ungewollt.

Jetzt eine, und die ist einstellbar. Ein neuer Reiter „Farben": Akzentfarbe
mit sechs Vorschlägen und einem Knopf, der die Systemfarbe übernimmt, dazu
das Paar für Herunter- und Hochladen samt Tauschknopf.

Die Signalfarben bleiben fest und stehen nur zur Ansicht dort. Gelb heißt
Warnung, Rot heißt kritisch, Grün heißt in Ordnung — bei Temperatur, Akku,
Lüfter und Fehlern. Wer sie umstellen kann, kann ihre Bedeutung zerstören;
ein rotes „alles gut" liest niemand richtig. Sichtbar sind sie trotzdem,
sonst sucht man die Warnfarbe im Reiter und findet sie nirgends.

Umgesetzt über die Tokens statt über vierzig Fundstellen: `Onyx.Color.accent`
ist keine Konstante mehr, sondern liest bei jedem Zugriff aus `OnyxTheme`.
Damit merkt SwiftUI die Abhängigkeit und zeichnet neu, sobald die Farbe sich
ändert. Dazu ein `.tint` an den vier Wurzeln — Panel, Popovers, Einstellungen,
Einrichtung —, damit auch Haken, Regler und Auswahlfelder mitziehen.

Nebenbei die Beschriftung: „Je Programm" heißt jetzt „Programme". Großgesetzt
las sich das als englisches „J-E".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:24:40 +02:00
ScarriffleandClaude Opus 5 7a3d2c1c39 Pegel auch für Ausgabe und Mikrofon
Die Programmzeilen hatten ihren mitlaufenden Ausschlag, die beiden Systemregler
nicht. Jetzt beide, in derselben Form: der Pegel läuft im Regler mit, nicht
daneben.

Der Ausgangspegel kommt über einen globalen Tap — denselben Weg wie der Mixer,
nur ohne Stummschalten und ohne Ausgabe. Ausgenommen werden die Programme, die
der Mixer bereits abgreift: ein Tap sieht das Signal vor dem Stummschalten,
sie zählten sonst doppelt — einmal als Original, das niemand hört, und einmal
als das, was Onyx daraus macht. Ändert sich diese Liste, wird der Tap neu
aufgebaut.

Der Mikrofonpegel geht nur mit echter Aufnahme. Es gibt keine Eigenschaft am
Gerät, die den Ausschlag verrät — einen Pegel bekommt nur, wer das Signal
liest, und genau das meldet macOS mit dem orangen Punkt. Das ist richtig so,
also läuft die Messung nur, solange die Ansicht offen ist, und hört beim
Schließen sofort auf. Ein Zähler statt eines Schalters, weil Panel und Popover
gleichzeitig offen sein können.

Geräte, die keine Fließkommawerte liefern, bleiben ohne Pegel: als Fließkomma
gelesene Ganzzahlen ergeben keinen falschen Ausschlag, sondern Unsinn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:17:40 +02:00
ScarriffleandClaude Opus 5 c990ea558b Blitz blendet zwischen zwei fertigen Fassungen über
Deine Idee war die bessere. Eine helle Fläche mit dunklem Rand ließ sich aus
einem einzelnen Symbol nur nachbauen, indem dasselbe Zeichen zweimal in
verschiedener Größe übereinander gezeichnet wurde — und die
Größenverhältnisse sahen nie stimmig aus, wie man an den drei Anläufen
sieht. Zwei fertige Fassungen ineinander zu blenden ist der geradere Weg:
gezeichnet wird genau das, was in der Datei steht.

Erst die dunkle voll deckend, dann die helle mit steigender Deckkraft
darüber. Zeichnete man beide anteilig, schiene dazwischen der Hintergrund
durch und der Blitz wirkte blass.

Ohne eigenen Rand bekommt der Blitz den vollen Innenraum des Akkus statt
Innenraum minus Rand — er ist damit wieder etwas größer, bleibt aber
innerhalb des Rahmens. Der Test hält die neue Grenze fest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 14:20:06 +02:00
ScarriffleandClaude Opus 5 e2f2ea3427 Blitz gefüllt
Die Vorlage war reine Kontur (`fill:none`) — als Umriss im Akku war der
Blitz zu zurückhaltend. Jetzt trägt sie dieselbe Farbe auch als Fläche; der
Strich bleibt stehen und macht die Form kräftiger.

Gezeichnet wird weiter zweifach: einmal größer in der Gegenfarbe als Rand,
einmal in der fadenden Farbe darüber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 14:06:23 +02:00
ScarriffleandClaude Opus 5 2490a90519 Blitz aus der mitgelieferten Vorlage, weiterhin fadend
`bolt.fill` aus SF Symbols ist eine massive Fläche; die neue Datei ist eine
Kontur mit kräftigem Strich und trifft die Form besser.

Gezeichnet wird sie zweifach wie zuvor: einmal etwas größer in der
Gegenfarbe als Rand, einmal in der fadenden Farbe darüber. Damit bleibt der
Blitz in jedem Moment des Fadens lesbar — über der hellen Füllung trägt ihn
der Rand, über dem leeren Teil er sich selbst.

Fehlt die Datei, bleibt das Systemsymbol. Dieselbe Vorlage auch in der
Akku-Kachel, damit nicht zwei verschiedene Blitze in einer App stehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 14:05:34 +02:00
ScarriffleandClaude Opus 5 49e1a46928 Widget-Optionen statt „Quellen", Reiterleiste von der Trennlinie herunter
Die Leiste saß mittig auf der Trennlinie unter dem Titelbalken.
`titlebarAppearsTransparent` allein reichte nicht — macOS zieht darunter
zusätzlich einen Trenner quer durchs Fenster. Der ist jetzt aus. Man setzt
keinen Titel auf eine Trennlinie.

„Quellen" war die Sicht des Programmierers: für den Nutzer ist die
Datenquelle eine Einstellung **des Kalenderwidgets** unter mehreren. Der
Bereich heißt jetzt „Widget-Optionen" und hat einen Abschnitt je Widget.

Kalender: woher die Daten kommen, Monatsraster oder Terminliste — und
endlich, welche Kalender überhaupt mitzählen. Gespeichert werden die
**abgewählten**, nicht die gewählten: kommt ein neuer Kalender dazu, ist er
damit automatisch dabei, statt dass man ihn suchen müsste, ohne zu wissen,
dass es ihn gibt. Der Filter lässt die Abdeckung unberührt — welche Kalender
man sehen will, sagt nichts darüber aus, für welchen Zeitraum Daten
vorliegen.

Wetter: dem Standort folgen oder einen festen Ort setzen, per Stadt oder
Postleitzahl. WeatherKit will Koordinaten, der Nutzer kennt Ortsnamen — die
Umsetzung passiert einmal beim Einstellen statt bei jedem Abruf. Der Ort
überlebt jetzt auch den Neustart; vorher stand nach jedem Start wieder der
Aufenthaltsort da.

Die Ablage-Einstellungen sind vom Widget-Reiter hierher gewandert, wo die
anderen Widget-Optionen stehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 14:00:48 +02:00
ScarriffleandClaude Opus 5 ab96086db7 Reiterleiste liegt nicht mehr auf der Trennlinie
Sie saß genau auf der Kante unter dem Titelbalken. `TabView` in einem
gewöhnlichen Fenster bekommt den Abstand nicht von selbst — in Apples
`Settings`-Szene schon, und die benutzt Onyx bewusst nicht, weil sie über
einen privaten Selektor öffnet und bei Apps ohne Menüleiste unzuverlässig
ankommt.

Zwölf Punkte Abstand nach oben, und der Titelbalken geht durchsichtig in den
Inhalt über, statt ihn mit einer Linie abzuschneiden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:53:35 +02:00
ScarriffleandClaude Opus 5 784b210bc2 Vorschau mit Schreibtischhintergrund, Fenster breit genug für die Reiter
Die Vorschau der Auslösefläche war eine schwarze Fläche — als Bildschirm
kaum zu erkennen. Jetzt liegt der echte Schreibtischhintergrund darunter.
Apples eigene Bilder lassen sich nicht mitliefern (Lizenz, und ein
Tahoe-Hintergrund wiegt zweistellige Megabyte); der eigene ist ohnehin der
bessere Anker, weil man genau ihn wiedererkennt. Fehlt er, bleibt ein
Verlauf in Onyx-Farben.

Dabei fiel auf, dass Notch und Auslösefläche oberhalb des Bildschirms
schwebten statt in ihm zu sitzen: die Rundung lag auf dem Hintergrund
allein, nicht auf dem Ganzen. Sie schneidet jetzt Hintergrund, Menüleiste
und Fläche gemeinsam ab.

Und das Einstellungsfenster war zu schmal für sechs Reiter — die Leiste
wurde rechts abgeschnitten, „Berechtigungen" war nicht mehr erreichbar. 780
statt 620 Punkte, Mindestbreite 740. Beim Nachrechnen der reinen Textbreiten
kommt man auf 560 und liegt damit deutlich daneben: macOS legt je Reiter
Innenabstand und Trenner dazu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:51:37 +02:00
ScarriffleandClaude Opus 5 cfbbf91068 Mixer kann beim Start von Onyx mitlaufen
Ein Häkchen im Mixer selbst. Standardmäßig aus: der Mixer greift in den
Tonweg der geregelten Programme ein, und das soll niemandem ungefragt
passieren — beim ersten Start soll nichts umgeleitet werden, was der Nutzer
nicht angeschaltet hat.

Das Häkchen steht in beiden Zuständen: eingeschaltet, um es wieder
abzuwählen, ausgeschaltet, um es gleich mit zu setzen. Sonst müsste man den
Mixer erst einschalten, um sagen zu können, dass er von selbst einschalten
soll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:47:17 +02:00
ScarriffleandClaude Opus 5 d7125e28f6 Ablage und AirDrop teilen sich eine Kachel
Zwei eigene Kacheln waren zu viel des Guten: beide zeigen im Ruhezustand
nichts als eine leere Fläche mit einem Satz darin, und zusammen nahmen sie
ein Drittel des Panels.

Jetzt übereinander in einer Kachel — oben die Ablegefläche fürs Senden,
darunter die Ablage mit ihrer Liste. AirDrop steht dabei nebeneinander statt
übereinander: in 52 Punkten Höhe ist für Symbol über Text kein Platz.

Das eigenständige AirDrop-Widget entfällt. Gespeicherte Layouts, die es noch
nennen, verlieren den Eintrag beim Laden — dafür ist der Filter da.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:42:39 +02:00
ScarriffleandClaude Opus 5 4c96aced5a Diagramme rechnen mit der vollen Länge, eigene Symbole für CPU und GPU
Frisch gestartet hat der Verlauf fünf Messwerte statt sechzig. Gerechnet
wurde aber „Breite geteilt durch Anzahl" — also war jeder Balken ein
Fünftel breit, und mit jedem weiteren wurden sie schmaler. Das Diagramm sah
nach einem Ausschlag aus und bedeutete nichts.

Jetzt kommt die Schrittweite aus der vollen Länge. Fehlende Werte bleiben
als freie Fläche links stehen; der Verlauf wächst nach links weg, wie bei
jedem Zeitdiagramm.

Das steckte an **drei** Stellen, alle mit derselben Rechnung: dem
Balkendiagramm im Netzwerk-Popover, der Sparkline in den Kacheln und der
Linie in den Menüleisten-Modulen. Die gemeinsame Rechnung liegt jetzt in
`GraphLayout` und ist geprüft, statt dreimal danebenzugehen.

Nebenbei: die Fläche unter der Sparkline begann am linken Rand statt am
ersten Messwert — ein Keil über die ganze Breite, wo noch nichts gemessen
war.

CPU und GPU bekommen eigene Symbole aus chip.svg und graphic-card.svg. SF
Symbols hat für beides nur denselben Chip, und „cpu" neben „cpu.fill" ist
kein Unterschied, den man in der Menüleiste erkennt.

Die Beschriftung unter den Ringen klebte am Rand und sah aus, als gehörte
sie noch hinein — sechs Punkte Abstand statt zwei. Und die Popover für CPU
und GPU tragen jetzt einen Titel: bei drei gleich aussehenden Ringen sieht
man sonst nicht, vor welchem man steht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:33:28 +02:00
ScarriffleandClaude Opus 5 43ce40676b Eigenes Symbol für den Mixer in der Menüleiste
Die Datei lag im Projektstamm; von dort wird nichts mitgeliefert. Jetzt in
Onyx/ und damit im Bundle.

Gezeichnet wird sie in `labelColor`, nicht in Weiß: unter einer hellen
Menüleiste wäre ein weißes Symbol unsichtbar. Auf dunklem Grund ist
labelColor weiß, also genau das Gewünschte — und auf hellem Grund schwarz,
statt zu verschwinden.

Eigene Vorlagen lassen sich nicht wie SF-Symbole einfärben: dort greift die
Symbolkonfiguration nicht, und `color.set()` allein färbt ein Template-Bild
ohnehin nicht. Der Weg ist, das Bild zu zeichnen und die Farbe mit
`sourceAtop` darüberzulegen — das trifft die deckenden Bildpunkte und lässt
den Rest frei.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:23:21 +02:00
ScarriffleandClaude Opus 5 c44bb3af30 Menüleiste springt nicht mehr, Mixer fasst Hilfsprozesse zusammen
Die Breite folgte der Stellenzahl des aktuellen Werts. Aus „14 KB/s" wird
„431 KB/s", und mit der zusätzlichen Stelle wanderte die halbe Leiste. Jetzt
wieder feste Breiten, bemessen am breitesten Wert, der vorkommen kann.

Der frühere Fehler war nicht die feste Breite, sondern der falsche
Bezugswert: gezeichnet wird „63°", bemessen wurde „100 °C" — fünfzehn Punkte
für zwei Zeichen, die nie erscheinen. Nachgemessen: zwei Aufnahmen im
Abstand von sechs Sekunden mit geänderten Werten ergeben identische Breiten.

Der Mixer fasst Hilfsprozesse mit ihrem Programm zusammen. „Google Chrome"
und „Google Chrome Helper" standen als zwei Zeilen nebeneinander, mit
getrennten Reglern für dasselbe Programm — zieht man den einen, hört man
den anderen. Chrome hat je Tab einen Helfer; fünfzehn Regler für einen
Browser sind keine Auskunft.

Abgegriffen wird weiterhin je Prozess, geregelt aber je Programm. Der
Schlüssel der Taps muss deshalb den Prozess mitführen: mehrere Helfer teilen
sich eine Kennung und überschrieben sich sonst gegenseitig. Der Pegel einer
Zeile ist der lauteste ihrer Prozesse.

Die Zeilen sind einzeilig statt zweizeilig: Symbol, Stummschalter, Regler,
Wert. Der Name ist weg — bei einem erkennbaren Programmsymbol ist er
redundant, und die gewonnene Zeile macht die Liste halb so hoch. Dafür ist
das Symbol größer, und wer den Namen braucht, hält den Zeiger darauf.
Popover 420 statt 330 Punkte breit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:15:53 +02:00
ScarriffleandClaude Opus 5 8f0831391e Blitz fadet, Einstellungsfenster mittig und veränderbar
Der Blitz fadet beim Laden zwischen Weiß und Schwarz — so gewünscht, und
ich hätte vorher fragen sollen statt eine eigene Lösung zu bauen und sie
hinterher zu begründen.

Über den Kosinus statt linear: ein linearer Ping-Pong knickt an den
Umkehrpunkten sichtbar. Der Zeitgeber läuft mit 30 Bildern je Sekunde und
nur, solange geladen wird — steht das Kabel nicht drin, läuft gar nichts.
Der Stecker für „am Netz, lädt nicht" bleibt ausgestanzt; dort bewegt sich
ohnehin nichts.

Das Einstellungsfenster lässt sich jetzt vergrößern und startet mit
620 × 620 statt 520 × 420. Der Bereich „Displays" passt mit der Vorschau
der Auslösefläche nicht mehr in die alte Höhe, und ein Fenster, in dem man
scrollt, obwohl der Bildschirm frei ist, ist eine Zumutung. Die feste Größe
in der View musste dafür weichen — sonst wächst das Fenster und der Inhalt
bleibt stehen.

Und es öffnet jetzt wirklich mittig. Mein erster Versuch mit 60 Punkten
Abstand von oben war zu zaghaft: der Titelbalken lag weiter so hoch, dass
man auf dem Weg zu seinen Knöpfen durch die Auslösefläche fuhr.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 12:52:21 +02:00
ScarriffleandClaude Opus 5 a84e5a0c8e Auslösefläche einstellbar, Blitz immer lesbar, Menüleiste schmaler
Der Blitz war ein Loch in der Füllung — über dem gefüllten Teil liest man
ihn, über dem leeren ist es Transparenz auf Transparenz. Bei wenig Ladung
sitzt er fast ganz im Leeren und verschwindet.

Statt zwischen Schwarz und Weiß zu faden geht er den Weg von macOS: geteilt
gezeichnet, über der Füllung ausgestanzt, daneben ausgemalt. Damit ist er
bei jedem Ladestand lesbar, ohne Bewegung in der Menüleiste. Ein Test
prüft, dass sich die beiden Teile an der Kante treffen statt zu überlappen —
sonst wäre der Blitz an der Nahtstelle dicker.

Die Auslösefläche der Notch lässt sich jetzt einstellen, mit Vorschau. Wie
genau jemand die Notch trifft, hängt an Hand und Zeigegerät; feste Werte im
Code waren dafür die falsche Antwort. Die Vorschau zeigt einen
nachgebildeten Bildschirm mit Notch und eingefärbter Fläche — „24 Punkte
unter der Notch" ist keine Vorstellung, die jemand im Kopf hat.

Die Menüleistenbreiten folgen jetzt dem, was dasteht. „Temp 53°" stand in
einer Box für „Temp 100 °C", und in der Menüleiste ist Platz das knappste
Gut. Dieselbe Hysterese wie beim Netzwerk: sofort wachsen, zögernd
schrumpfen.

Das Mixer-Symbol war ausgeschaltet auf 55 % abgeblendet. Das war meine Idee
und ein Fehlgriff: über einem hellen Schreibtischhintergrund ist es damit
praktisch weg. Ob der Mixer läuft, erfährt man beim Klick — dafür muss man
das Symbol aber erst finden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 12:46:02 +02:00
ScarriffleandClaude Opus 5 bf799d57d9 Systemlautstärke und Gerätewahl im Mixer
Ausgabe und Eingabe stehen jetzt über den Programmen — mit Lautstärke,
Stummschalter und der Gerätewahl direkt am Namen. Ein eigenes Auswahlfeld
daneben wäre eine zweite Stelle für dieselbe Sache.

Sie stehen **außerhalb** des Mixer-Schalters, und das ist der Punkt: hier
wird kein Ton abgegriffen, sondern es werden die Regler des Systems bedient.
Das braucht keinen Tap, kein Aggregate-Gerät und keine Berechtigung — wer
nur die Lautstärke sucht, soll dafür nichts einschalten müssen.

Zwei Dinge, die beim Bauen nicht offensichtlich waren:

Viele Geräte führen keinen Regler für Element 0, sondern nur je Kanal.
Fragt man bloß den Hauptregler, hält man ein regelbares Gerät für
unregelbar. Also erst der Hauptregler, dann die Kanäle — und beim Setzen
beide Kanäle, sonst wandert das Stereobild.

Manche Geräte bringen gar keinen Regler mit, HDMI etwa. Dort steht „hier
nicht regelbar" statt eines Reglers, der nichts tut.

Warntöne folgen dem Ausgabegerät mit. Zwei getrennte Wahlen sind die
Quelle für „warum kommt der Systemton aus dem Notebook".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:28:39 +02:00
ScarriffleandClaude Opus 5 eff3c30a71 Mixer im Zeilenlayout, Akku zeigt „am Netz, lädt nicht"
Am Netz und trotzdem kein Blitz sah aus wie Akkubetrieb — dabei ist es der
Normalfall, sobald das Ladelimit greift. Jetzt steht in diesem Zustand ein
Stecker im Akku, ausgestanzt wie der Blitz. Drei Zustände, drei Zeichen:
lädt, hängt am Netz, läuft auf Akku.

Der Mixer folgt jetzt der Anordnung von SoundSource, weil sie dort gut
gelöst ist: eine Zeile je Programm, links das Symbol als Kachel, dann der
Stummschalter, dann der Regler, rechts der Wert. Vorher stand der
Stummschalter rechts zwischen den Zusatzelementen, obwohl er zur Lautstärke
gehört, und die Prozentzahl sprang zwischen zwei Zeilen.

Die Farben folgen SoundSource **nicht**. Dort ist jeder Regler grün; in Onyx
ist Farbe ein Signal — die Verstärkung über 100 % ist eines, eine
gewöhnliche Lautstärke nicht.

Nebenbei zwei Fehler im Regler: der Knopf stand an den Enden über die Bahn
hinaus, und die Ziehposition rechnete ohne seine Breite. Am rechten Anschlag
ließen sich die letzten Prozent deshalb nicht einstellen.

Das Popover ist die Hauptfläche und jetzt 330 statt 280 Punkte breit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 16:05:37 +02:00
ScarriffleandClaude Opus 5 4b5e86b3f4 Akkusymbol folgt dem Ladestand
Ein Symbol, das bei 5 % genauso aussieht wie bei 95 %, ist Dekoration und
keine Anzeige. Wer in der Menüleiste nur das Glyph stehen hat, will es ohne
die Zahl daneben lesen können.

Fünf Füllstufen, gerundet zur nächsten: 60 % soll nicht halbleer aussehen
und 40 % nicht dreiviertel voll.

Beim Bauen fiel auf, dass die naheliegende Lösung nicht geht. Die erste
Fassung hängte beim Laden ein „.bolt" an den Füllstand — von diesen
kombinierten Symbolen existiert aber allein `battery.100percent.bolt`, und
`battery.slash` gibt es auch nicht. Die übrigen wären leere Felder in der
Menüleiste gewesen. Nachgemessen statt angenommen, und ein Test prüft jetzt
für jeden erzeugten Namen, dass es ihn wirklich gibt.

Den Füllstand beim Laden gegen einen Blitz einzutauschen wäre die falsche
Wahl gewesen: dass geladen wird, sieht man am Netzstecker; wie weit es ist,
sieht man nirgends sonst. Also bleibt der Füllstand, und der Blitz kommt als
kleines Zeichen darüber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 13:30:13 +02:00
ScarriffleandClaude Opus 5 667fb61c96 Absturz beim XPC-Rückruf, Menü zurück, Fenster weg von der Notch
Der Absturzbericht war eindeutig:

  Thread 8: com.apple.NSXPCConnection.m-user…onyx.helper
    swift_task_isCurrentExecutorWithFlags
    closure #3 in FanControl.proxy()
    EXC_BREAKPOINT

Die Rückrufe der XPC-Verbindung erben die MainActor-Isolation der Methode,
in der sie stehen. XPC ruft sie aber auf seiner eigenen Warteschlange auf,
Swift 6 prüft das zur Laufzeit und beendet den Prozess. Das
`Task { @MainActor in … }` im Rumpf half nicht: die Prüfung geschieht beim
Betreten des Abschlusses, nicht beim Zugriff.

Alle sieben Rückrufe sind jetzt `@Sendable`. Und weil das eine Fehlerklasse
ist und kein Einzelfall, dieselbe Behandlung für die übrigen Stellen, an
denen ein MainActor-Typ einen Abschluss an eine Systemschnittstelle gibt:
Papierkorb, Vorschaubilder, Adapter-Ende, Darwin-Nachricht.

Dazu drei Dinge aus dem Bericht von eben:

Der Linksklick aufs Menüleistensymbol fuhr das Panel aus — und ging dabei
als Fixieren durch. Danach stand das Panel offen und reagierte auf nichts
mehr. Das war schlechter als das Problem, das es lösen sollte. Ein
Statuselement zeigt bei einem Klick sein Menü; alles andere überrascht.
„Panel öffnen" bleibt draußen.

Ein Klick daneben schließt jetzt auch ein fixiertes Panel. „Klick fixiert"
ist eine gute Regel, aber wer sie nicht kennt, sitzt sonst vor etwas, das
offen steht und nicht reagiert — und sucht den Fehler in der App.

Einstellungs- und Einrichtungsfenster gehen nicht mehr direkt unter der
Notch auf. `center()` setzt oberhalb der Mitte; der Schließknopf landete
damit so weit oben, dass man auf dem Weg dorthin die Notch auslöste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 12:54:32 +02:00
ScarriffleandClaude Opus 5 70a665f532 Menüleiste: Breite mitwachsend, Symbole unverzerrt, Sensor wählbar
Die Netzwerkkachel war auf „↓ 999,9 MB/s" dimensioniert und stand damit
neben „↓ 13 KB/s" zur Hälfte leer. Auf den schlimmsten Fall auszulegen ist
für einen Wert, der sich um den Faktor tausend ändert, die falsche Antwort.
Gemessen wird jetzt, was dasteht — mit unsymmetrischer Hysterese: sofort
wachsen, denn abgeschnittene Messwerte sind falsche Messwerte, und erst nach
zehn ruhigeren Messungen schrumpfen, damit ein Lastausschlag die Leiste nicht
zum Pumpen bringt.

Das Akkusymbol war zerquetscht. Ein Batteriesymbol ist doppelt so breit wie
hoch und wurde in ein Quadrat gezeichnet. Symbole behalten jetzt ihr
Seitenverhältnis, und die Breite des Elements richtet sich nach dem, was das
Symbol tatsächlich braucht — sonst hätte ein breites Glyph dieselbe Spalte
wie ein schmales.

Bei den Sensoren stand stumm das Maximum über alle Punkte: eine Zahl, von
der niemand weiß, woher sie kommt. Jetzt wählbar, und der gewählte Sensor
gibt sein eigenes Kürzel — „Temp 46°" sagt weniger als „Heatpipe 46°".
Voreinstellung bleibt der wärmste Punkt, weil das die häufigste Frage ist.

Und die Lüfterkachel zeigte „2500" ohne Einheit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 12:46:06 +02:00
ScarriffleandClaude Opus 5 386eb6ae70 Ladelimit ist eine Anzeige, Akku-Kachel sagt etwas, Menüleiste bekommt Kürzel
Der Regler war eine Pseudobedienung. Der SMC lehnt Schreibzugriffe auf CHLT
ab — der Helfer hat es mit korrekter Größe versucht und „geschrieben (false),
zurückgelesen 85" gemeldet. Das Sicherheitsnetz hat also die Wahrheit gesagt;
die Oberfläche hat sie nur nicht gezeigt.

Weiter zu raten hieße, im Lade-Subsystem Keys durchzuprobieren, und das ist
die eine Operation in diesem Projekt, die Hardware dauerhaft beschädigen
kann. Also: das Limit wird gelesen und angezeigt, mit einem Weg in die
Systemeinstellungen, wo es tatsächlich geht. Der Schreibpfad ist aus dem
privilegierten Helfer entfernt — ein Root-Dienst trägt nur, was benutzt wird.

Die Akku-Kachel zeigte „40 %" und einen Verlauf über die letzten Sekunden.
Der Ladestand ändert sich in Minuten, nicht in Sekunden; der Graph war eine
waagerechte Linie. Jetzt: verbleibende Zeit bzw. Zeit bis voll, Leistung in
Watt mit Richtung, Ladelimit, Zustand, Zyklen — und der Energiesparmodus.
Der erklärt, warum die Maschine langsamer wirkt, und ohne diesen Hinweis
sucht man den Grund woanders.

In der Menüleiste standen fünf nackte Prozentzahlen nebeneinander. Man sah
Zahlen und wusste nicht, welche wozu gehört. Zwei neue Darstellungen: Kürzel
und Wert („CPU 34 %") sowie Symbol und Wert. Beim Akku stehen sie zuoberst in
der Auswahl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 12:37:04 +02:00
ScarriffleandClaude Opus 5 5ba9e551a4 Ladelimit gehört zum Akku, nicht zu den Lüftern
Der Regler stand in der Lüfterkachel, weil dort der Helfer schon angebunden
war. Das ist die Sicht des Programmierers, nicht die des Nutzers: gesucht
wird er beim Akku, und zwar genau dann, wenn man auf den Ladestand schaut.

Er steht jetzt in der Akku-Kachel des Panels und im Akku-Popover der
Menüleiste. Angebunden über ein Protokoll statt über einen direkten Zugriff:
geschrieben wird vom privilegierten Helfer, und der gehört zur App, nicht zu
MetricsProvider — die Akku-Ansicht soll ihn bedienen können, ohne ihn zu
kennen. Ohne laufenden Helfer ist der Regler sichtbar, aber gesperrt, mit dem
Hinweis, wo er herkommt. Ein Regler, der still nichts tut, ist schlimmer als
einer, der sagt warum.

Dazu Protokollierung, warum das Panel schließt: Zeigerposition,
Auslösefläche, Fensterrahmen. „Es ging zu, obwohl ich noch drin war" ist
ohne diese drei Zahlen eine Behauptung gegen eine andere — und meine
automatisierten Proben halten den Zeiger perfekt still, treffen den Fall
also nie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 12:20:00 +02:00
ScarriffleandClaude Opus 5 a54b332723 Notch: Toleranz für zitternde Hände, „Panel öffnen" fliegt raus
Warum meine Proben grün waren und deine Hand nicht: jedes einzelne
Abtastbild außerhalb der Auslösefläche hat das Aufziehen sofort
abgebrochen. Ein Zeiger, den eine Hand an den oberen Rand führt, steht
dort aber nicht still — er wandert um ein paar Punkte. Damit fing die
Entprellzeit von 220 ms dauernd von vorn an und kam nie ans Ziel.
`CGWarpMouseCursorPosition` setzt den Zeiger auf einen Punkt und hält ihn
absolut still; deshalb war jede automatisierte Probe 19 von 19 grün.

Kurzes Herausrutschen bricht jetzt nicht mehr ab: die Entprellzeit läuft
weiter, parallel läuft die Abbruchfrist von 300 ms. Kommt der Zeiger
zurück, wird die Frist gestoppt — die Entprellzeit setzt bewusst nicht neu
an, sonst dauerte es bei jedem Grenzübertritt wieder von vorn.

Das allein hätte einen neuen Fehler gebracht, und ein vorhandener Test hat
ihn gefunden: beim beiläufigen Streifen der Notch wäre die Entprellzeit
abgelaufen, während der Zeiger längst weg war — das Panel wäre für ein
Zehntel aufgeblitzt. Die Maschine merkt sich deshalb, wo der Zeiger steht,
und öffnet nur, wenn er da ist. Ist die Entprellzeit abgelaufen, während er
draußen war, geht es beim Zurückkommen ohne weiteres Warten auf.

Die Auslösefläche reicht außerdem 14 statt 4 Punkte unter die Notch. Vier
Punkte sind keine Fläche, die eine Hand trifft.

„Panel öffnen" ist aus dem Menü verschwunden. Ein Menüeintrag für etwas,
das beim Berühren der Notch von selbst passiert, beschreibt einen Umweg um
die eigentliche Bedienung herum. Als Notausgang bleibt es erreichbar, nur
ohne eigene Zeile: Linksklick auf das Symbol fährt das Panel aus,
Rechtsklick zeigt Einstellungen und Beenden.

Dazu Protokollierung jedes Zustandswechsels und jeder Unterdrückung. Ohne
diese Spur ist ein Panel, das sich „manchmal nicht öffnet", nicht zu
untersuchen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 12:12:31 +02:00
ScarriffleandClaude Opus 5 b2f69f34ae Ladelimit: Key gemessen, Schreibpfad gebaut
Der Key heißt CHLT, drei Bytes. Identifiziert per Differenzmessung gegen
macOS' eigene Ladebegrenzung, ohne einen einzigen Schreibzugriff:

  80 %  →  50 01 00
  95 %  →  5F 01 00
  85 %  →  55 01 00

Erstes Byte ist die Prozentzahl, zweites offenbar „Limit aktiv", drittes
unbenutzt. Drei Messpunkte, weil einer Zufall sein kann: BACC hatte beim
ersten Vergleich ebenfalls gepasst — ein Zähler, in dem gerade 50 stand.

ChargerConfiguration aus ioreg sah zunächst wie eine zweite Quelle aus
(niedriges Byte 0x50 = 80), blieb aber unverändert, als CHLT längst auf
95 stand. Also Zufall, und gut, dass darauf nichts gebaut wurde.

Geschrieben wird nur das erste Byte, und nur zwischen 80 und 100 — dem
Bereich, den macOS selbst anbietet und in dem gemessen wurde. Darunter ist
ungemessenes Gebiet. Bei den Lüftern liefert die Firmware mit F0Mn/F0Mx
eigene Grenzen mit, an denen sich ein Sicherheitsnetz festhalten kann; hier
gibt es das nicht, hier gibt es nur die Messung. Ein falscher Wert im
Lade-Subsystem ist die eine Operation in diesem Projekt, die den Akku
dauerhaft beschädigen kann.

Nach jedem Schreiben wird zurückgelesen und gemeldet, was tatsächlich
steht — nicht, was gewünscht war. „Kein Limit" heißt 100 Prozent und nicht
das Aktiv-Byte auf null: diese Kodierung hat macOS nie geschrieben, sie
wäre also ungemessen.

Bedienung im Lüfter-Bereich der Einstellungen und in der Lüfterkachel,
also auch im Menüleisten-Popover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 12:01:50 +02:00
ScarriffleandClaude Opus 5 453353de58 Notch öffnet zuverlässig, Einstellungen beenden Onyx nicht mehr
Das Öffnen hing allein an Mausereignissen. Der Auslösebereich liegt aber in
der Menüleiste, und dorthin stellt macOS Bewegungen nicht verlässlich an
fremde Programme zu — mal kommen sie an, mal nicht, je nachdem, was gerade
den Zeiger führt. Ein Verfahren, das eine Zustellung voraussetzt, die es
nicht gibt, kann nicht zuverlässig sein.

Die Zeigerposition wird deshalb jetzt immer abgefragt, im Ruhezustand
zehnmal je Sekunde, bei offenem Panel sechzigmal. Die Monitore sind nur noch
Beschleunigung. Kosten: 0,1 % CPU im Leerlauf, gemessen. Nachgeprüft mit
fünf Anläufen aus verschiedenen Richtungen — fünfmal offen.

Die Unterdrückung war zu grob: jedes Vollbildfenster galt als Grund, das
Panel wegzuhalten. Damit war die Notch tot, sobald Xcode im Vollbild lief.
Gemeint war immer nur der Film über den ganzen Bildschirm, und den erkennt
man daran, dass etwas den Bildschirm wachhält — ein Textfenster tut das
nicht. Beide Bedingungen zusammen, nicht eine davon.

Dazu ein Loch, das dabei auffiel: fiel die Unterdrückung weg, während der
Zeiger schon in der Notch stand, wartete die Maschine auf ein Ereignis, das
erst bei der nächsten Bewegung kam.

Der rote Knopf blendet Fenster jetzt aus, statt sie zu schließen. Onyx hat
kein Hauptfenster; „Fenster zu" heißt hier nie „fertig". Manche
Hilfsprogramme erzwingen aber genau diese Gleichung und beenden eine App,
sobald ihr letztes Fenster verschwindet — dann war Onyx nach einem Blick in
die Einstellungen weg, samt Menüleiste und Notch. Ein Fenster, das nie
wirklich schließt, gibt dafür keinen Anlass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:21:00 +02:00
ScarriffleandClaude Opus 5 b22a44ba70 Mediensteuerung hochkant, Symbole wieder sichtbar, „Auto" als Zustand
Die Mediensteuerung stand nebeneinander: Cover links, alles andere in den
verbleibenden gut hundert Punkten. Daher „Bernhard H…" statt eines Titels.
Jetzt übereinander, wie es zu einer hochkanten Kachel passt.

Das Cover ragte zusätzlich über die Kachel hinaus, weil `clipShape` vor dem
Rahmen stand — beschnitten wurde damit auf die Eigengröße des Bildes, und
die ist bei einem Podcast-Cover ein Vielfaches. Erst Rahmen, dann
beschneiden.

Menüleistensymbole waren kaum zu sehen. `color.set()` vor `image.draw(in:)`
färbt ein Template-Bild nicht: es wird schwarz gezeichnet, auf einer dunklen
Menüleiste also unsichtbar. AppKit färbt Template-Bilder nur ein, wenn sie
als `image` eines Buttons gesetzt sind — in einer selbst gezeichneten Ansicht
muss die Farbe in die Symbolkonfiguration. Betraf alle fünf Zeichenstellen,
nicht nur die Lüfter.

„fans.auto" stand als roher Schlüssel da: Xcode hatte ihn extrahiert, gefüllt
wurde er nie. Beim Nachsehen waren es 35 solche Einträge über alle Kataloge
hinweg — die meisten reine Formatschlüssel, die als Durchreicher trotzdem
einen Wert brauchen.

Der Automatik-Knopf ist jetzt eine gefüllte Pille, wenn sie greift. Blauer
Text sieht aus wie ein Link und nicht wie ein Zustand.

Und ein Absturz, der noch niemandem passiert ist: meldet der Helfer für einen
stehenden Lüfter keine Grenzen, hätte `Slider(in: 0...0)` die App beendet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:12:33 +02:00
ScarriffleandClaude Opus 5 93fe95827f Panel wächst in die Breite, Menüleiste wird schmaler
Das Raster ist weg. Widgets stehen nebeneinander, alle gleich groß, in
der Reihenfolge aus den Einstellungen. Das Panel sitzt am oberen Rand —
nach unten zu wachsen bedeckt den Bildschirm, nach rechts und links legt
es sich in den ohnehin leeren Streifen neben der Notch. Eine zweite Reihe
entsteht erst, wenn der Bildschirm keine weitere Kachel mehr hergibt.

Die Größenauswahl fällt damit weg, und das ist kein Verlust: ein 2×1
neben einem 2×2 lässt oben rechts eine Lücke, die niemand füllen kann und
die aussieht wie ein Fehler. Genau daher kam auch die Überlappung. Ein
Test prüft jetzt für ein bis zwölf Kacheln, dass sich keine zwei
schneiden und alle vollständig im Panel liegen — außerhalb heißt oben:
hinter der Notch. Kacheln werden zusätzlich beschnitten, damit
überquellender Inhalt nicht über den Nachbarn zeichnet.

Der Kalender wählt jetzt zwischen Monatsraster und Terminliste. Das war
vorher an die Kachelgröße gekoppelt und ist in Wahrheit eine Frage
dessen, was man sehen will.

In der Menüleiste wird die Breite gemessen statt geschätzt. Vorher stand
je Darstellungsart eine feste Zahl im Code, großzügig gewählt — mit dem
Ergebnis, dass neben jedem Wert Platz für ein weiteres Symbol blieb, in
dem nichts stand. Gemessen wird die breiteste vorkommende Zeichenfolge,
nicht die gerade angezeigte, sonst springt die Leiste bei jedem Messwert.
Die Balkendarstellung bekommt ihre Breite aus der Kernzahl, die erst nach
der ersten Messung feststeht.

Popover schließen jetzt beim Klick daneben. `behavior = .transient`
genügt nicht: es greift, solange die eigene App aktiv ist, aber Onyx läuft
als .accessory und wird durch einen Klick auf ein Statuselement nicht
aktiviert. Ein Klick in ein fremdes Fenster erreichte das Popover gar
nicht. Ein globaler Beobachter sieht solche Klicks, ein lokaler die in der
eigenen App, Escape schließt ebenfalls — und es ist immer nur eines offen.

Mixer und Lüfter haben eigene Menüleistenelemente. Beides regelt man
mitten in etwas anderem; der Weg über ein Einstellungsfenster war zu weit.
Die Lüfter sind damit auch als Panel-Kachel echt statt Platzhalter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:07:20 +02:00
ScarriffleandClaude Opus 5 a0342205fd Phase 9: Einrichtung, Autostart, Notarisierung — und die Mixer-Optik
Die Einrichtung beim ersten Start erklärt jede der vier Sachen einzeln und
fragt jede einzeln an. Vier Systemdialoge hintereinander wären der übliche
Weg und der falsche: wer nicht weiß wofür, klickt viermal „Nicht erlauben",
und danach fragt macOS nie wieder. Sie kommt beim ersten Start immer, auch
wenn zufällig schon alles erlaubt ist — sie erklärt auch den Helfer und den
Autostart.

Beim Autostart ist `requiresApproval` der Zustand, der weh tut. Wer das
Anmeldeobjekt in den Systemeinstellungen abgeschaltet hat, kann in Onyx auf
den Schalter drücken, so oft er will. Als „aus" angezeigt führt das in eine
Schleife, also steht dort jetzt, wo es weitergeht.

Der Mixer verliert den Schalter je Programm. Zehn Mini-Schalter untereinander
sind das, was den Eindruck macht; am Regler zu ziehen ist ohnehin die
Entscheidung, dieses Programm zu regeln, und der große Ausschalter stellt
alles zurück. Der Regler ist selbst gezeichnet — der Systemregler bringt sein
eigenes Erscheinungsbild mit und kann nicht zeigen, worauf es hier ankommt:
den Pegel und die Grenze bei 100 %.

Der Pegel wird im selben Durchlauf gemessen, in dem verstärkt wird, und über
`Atomic` weitergereicht. Eine gewöhnliche Eigenschaft wäre ein Wettlauf, eine
Sperre im Audiothread der klassische Weg zu Aussetzern. Dabei fällt das
`Date()` aus dem Echtzeitpfad, das dort nie hingehört hat.

Zwei Funde beim Bauen des Release-Wegs, beide hätten erst nach dem Hochladen
zur Ablehnung geführt: der Helfer wurde ohne sicheren Zeitstempel signiert,
und das Adapter-Framework ad-hoc. Beides gilt jetzt nur noch im Debug.
Scripts/release.sh prüft Zertifikat und Notarisierungsprofil vorweg, statt
nach zehn Minuten Bauen zu scheitern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:36:26 +02:00
ScarriffleandClaude Opus 5 8308536227 Phase 8: Ablage, AirDrop und Calendarr
Calendarr liefert Termine über den Gruppencontainer. Streng lesend: keine
eigene Anmeldung, kein Netz, kein Schreiben zurück.

Die Abdeckung ist der Kern und kein Detail. Der Schnappschuss reicht 49
Tage nach vorne, und das steht nirgends in der Datei — es ist zugesagtes
Verhalten des Schreibers. Außerhalb liegt keine Information vor, nicht
keine Termine; das Monatsraster graut solche Tage aus, statt „frei" zu
behaupten. Verlockend wäre, die Abdeckung aus dem frühesten Termin
abzuleiten. Das ist falsch, und ein Test hält es fest: mehrtägige Termine
ragen ins Fenster hinein und werden mitgeschrieben, obwohl ihr Anfang
nicht abgedeckt ist.

Bei „beide Quellen" ist die Abdeckung der Schnitt, nicht die Vereinigung.
Ein Tag gilt nur als bekannt, wenn ihn jede Quelle abdeckt. Umgekehrt
darf ein Ausfall einer Quelle die andere nicht mitreißen: Calendarr nie
geöffnet zu haben knipst den Apple-Kalender nicht aus.

Die Ablage kopiert, statt zu verweisen. Anhänge aus Mail oder WhatsApp
liegen in temporären Verzeichnissen — ein Verweis wäre in Stunden tot,
und die Ablage wäre leer, wenn man sie braucht. Aus demselben Grund nimmt
die Fläche UTType.item statt public.file-url an: solche Anhänge kommen
als Dateiversprechen, und loadFileRepresentation löst beides ein. Die
übergebene Datei existiert nur innerhalb des Rückrufs, also wird sie dort
sofort gesichert statt später auf dem MainActor.

Gelöscht wird in den Papierkorb. Automatisches Aufräumen ist standardmäßig
aus — eine Ablage, die ungefragt löscht, ist ein Papierkorb mit
Zeitschaltuhr.

Nebenbei: „mixer.on" und „network.vpn.active" standen als rohe Schlüssel
in der Oberfläche. Label(_:systemImage:) kennt kein Bundle und sucht immer
im Hauptbundle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:16:19 +02:00