NovaTimePuncher — Stempeluhr für macOS
APP·Juni 2026

NovaTimePuncher — Stempeluhr für macOS

Ein-, aus- und Pause stempeln, als echte macOS-App mit Glass-UI, animierten SF Symbols und Actor-Architektur. Und unter der Haube ein kompletter Stack-Wechsel, den niemand bemerken durfte

PROJEKT

NovaTimePuncher
Stempeluhr für macOS

TECHNOLOGIEN

SwiftSwiftUImacOSSwift ConcurrencyCryptoKitFlutterMigration

Herausforderung

NovaTimePuncher ist ein Freizeitprojekt in zwei Akten. Akt eins: eine kleine Flutter-Desktop-App fürs Ein- und Ausstempeln gegen die NovaTime-Zeiterfassung, mit Punch-In/Out, Pausen-Tracking und Auto-Logout. Geschrieben lange vor meinem KI-nativen Workflow, komplett von Hand. Sie lief jahrelang produktiv, aber sie fühlte sich auf dem Mac nie richtig zu Hause: generisches UI, schwergewichtige Runtime, und mit Swifts moderner Concurrency-Welt hatte der alte Singleton-und-Streams-Code nichts zu tun.

Akt zwei, wieder in der Freizeit: die App nativ in SwiftUI neu bauen, diesmal KI-nativ. Der Haken: Sie ist im produktiven Einsatz, und wenn eine Migration etwas Vertrautes bricht, erzeugt das Rückfragen und Misstrauen. Die Messlatte war deshalb zweigeteilt: Verhalten, Daten und Farblogik exakt gleich, während Optik und Bediengefühl endlich richtig macOS werden durften.

Zwei Paritäts-Anforderungen waren dabei hart, der Rest Produktentscheidung:

  • Bestehende, verschlüsselt gespeicherte Zugangsdaten müssen transparent weitergelesen werden können, das Verschlüsselungsformat der Flutter-App ist damit bit-genau gesetzt
  • Die Farblogik der Zeitanzeigen muss identisch bleiben, denn eine abweichende Tagesfarbe ist für die Nutzerschaft ein Bug
  • Alles andere, von Texten über Abstände bis zum Auto-Logout-Verhalten, bleibt aus Prinzip 1:1, damit der Umstieg unsichtbar ist

Lösung

Der Kern der Umsetzung ist weniger ein UI-Rewrite als eine Methodik: Die alte App wird zur unantastbaren Referenz, und jede Abweichung braucht einen Grund. Vier Bausteine tragen das.

Blueprint statt Bauchgefühl

Die komplette Flutter-App liegt als Read-only-Referenz im Repo. Sie wird nie editiert, sondern liefert die Sollwerte: Business-Regeln, Zustandsübergänge, Texte, Farbberechnungen. Eine Migrations-Spec beschreibt das Soll im Detail, und Vergleiche gegen den Blueprint ersetzen Diskussionen. Die Zustandsmaschine des Stempelns (vier Zustände, vier Action-Codes, inklusive der Randfälle wie „Ausstempeln während der Pause“) wurde eins zu eins übernommen statt neu erfunden.

Bit-genaue Krypto-Kompatibilität

Die Flutter-App speichert Zugangsdaten AES-GCM-verschlüsselt in einem eigenen Format: Nonce, Ciphertext und Auth-Tag als Hex-Strings, per Schrägstrich getrennt. Die Swift-Seite implementiert exakt dieses Format mit CryptoKit: Bestandsdaten werden transparent entschlüsselt, neue kompatibel geschrieben. Es ist die einzige Stelle im Projekt, an der Bit-Genauigkeit gegenüber einem Fremdformat garantiert sein muss, und damit die Stelle, an der ein stiller Fehler am teuersten wäre.

Actors statt Locks

Die neue App nutzt Swifts Approachable Concurrency konsequent: UI-Code ist per Default MainActor-isoliert, und alles Nebenläufige (API-Controller, State-Service, Logging, Auto-Logout-Scheduler, Notifications) ist ein eigener Actor. Kein einziges Lock, keine Dispatch-Queues; Daten überqueren Isolationsgrenzen ausschließlich sichtbar per await. Der alte Ansatz aus Singletons und Streams wäre unter Swifts Sendable-Prüfung schlicht nicht mehr durch den Compiler gekommen, die Migration war so auch ein erzwungenes Architektur-Upgrade.

Nativ heißt auch: fühlt sich nativ an

Hier durfte die Neuauflage sichtbar besser werden: der macOS-Frosted-Glass-Look (NSVisualEffectView für ältere Systeme, ab macOS 26 die native glassEffect-API), animierte SF Symbols für Zustandswechsel und durchgängige Design-Tokens hinter gemeinsamen View-Modifiern. Was Flutter nur imitieren konnte, liefert die Plattform jetzt selbst. Und für die Entwicklung ohne erreichbares Zielsystem gibt es einen vollständigen Mock-Layer als eigenen Actor: identisches Protokoll, realistische Zustandsübergänge, ohne dass echte Stempel-Requests das produktive System erreichen.

Code-Beispiele

Ergebnisse

Die native App hat den Flutter-Vorgänger vollständig abgelöst. Nichts Vertrautes brach, und das Neue fühlt sich sofort richtig an:

  • 1:1-Ablösung im produktiven Einsatz: Bestands-Zugangsdaten, Farben, Texte und Verhalten identisch
  • Verschlüsselungsformat des Vorgängers bit-genau in CryptoKit nachgebaut, kein Migrationsschritt für Nutzerdaten nötig
  • Durchgängige Actor-Architektur ohne Locks: Thread-Sicherheit garantiert der Compiler, nicht die Konvention
  • Optik nativ weiterentwickelt: Glass-Look mit Versions-Fallback, animierte SF Symbols, echtes macOS-Bediengefühl
  • Mock-Layer für vollständige Offline-Entwicklung gegen realistische Zustandsübergänge

Die eigentliche Erkenntnis ist übertragbar: Eine Migration ist dann gelungen, wenn nichts Vertrautes bricht und sich das Neue sofort richtig anfühlt. Der Blueprint-Ansatz, also alte App als unantastbare Referenz, Parität als Standard und Abweichung nur mit Grund, macht aus einem riskanten Rewrite einen kontrollierten, überprüfbaren Prozess. Und das Projekt markiert für mich eine Zäsur: Der Flutter-Vorgänger entstand noch komplett ohne KI, die Neuauflage KI-nativ. Gleiche App, gleicher Entwickler, zwei Werkzeug-Epochen.

Visuelle Eindrücke

NovaTimePuncher Hauptansicht mit Punch-Out-Button, Pausen-Steuerung und Tagesübersicht

Punch-Out, Pausen-Steuerung und Tagesübersicht im Frosted-Glass-Look.

NovaTimePuncher Hauptansicht mit Punch-Out-Button, Pausen-Steuerung und Tagesübersicht
NovaTimePuncher Wochenansicht mit Balkendiagramm pro Tag und Wochensumme