Zum Inhalt

macOS-DMG-Cutover → native App (v2.0.0) — Design

Datum: 2026-06-15 Scope: Distributions-/Packaging-Wechsel: die native SwiftUI-App (apple/PhysarumApp, macOS-Target) wird die offizielle macOS-DMG und löst den WKWebView-Wrapper (macos/Physarum.swift) ab. App-Code bleibt fast unberührt (neu: eine macOS-Entitlements-Datei; Version-Bump). Engine unberührt. Web-PWA (Codeberg Pages) unangetastet. Status: Approved (Brainstorm 2026-06-15)

Problem / Kontext

Die ausgelieferte macOS-DMG ist der WKWebView-Wrapper (macos/Physarum.swift + gebündelte Web-App, gebaut von scripts/build-macos-app.sh) — sie kann nur, was die Web-App kann. Die gesamte native Politur (Authoring-Editor, nativer Audio-Stack, Capture/Export, iPad/Mac-Workspace) steckt in der nativen App unter apple/ und ist nie in der DMG gelandet. Das Cockpit führt den „Cutover" als Plan („native App löst die WKWebView-DMG bei Editor-Reife ab"); Editor/Audio/Capture sind nativ portiert → Editor-Reife erreicht.

Verifizierter Ausgangsstand (2026-06-15): - Die native App ist ein Multiplatform-Target (project.yml supportedDestinations: [macOS, iOS, tvOS], macOS-Deployment 14.0) und baut/läuft heute auf macOS (xcodebuild -scheme Physarum -destination 'platform=macOS' build → BUILD SUCCEEDED, diese Session mehrfach). - macOS-Signing in project.yml ist ad-hoc (CODE_SIGN_IDENTITY[sdk=macosx*]: "-"), App ist unsandboxed, kein Hardened Runtime gesetzt, keine *.entitlements-Datei vorhanden. - Mic-/Photos-Usage-Strings sind da (NSMicrophoneUsageDescription, NSPhotoLibraryAddUsageDescription). - Developer-ID-Cert vorhanden: Developer ID Application: Johannes Kaindl (U9X7M39R56) im Keychain → PHYSARUM_SIGN_IDENTITY=auto. - project.yml MARKETING_VERSION: "0.1.0" (der DMG-relevante CFBundleShortVersionString) ist nicht mit package.json (1.5.0) synchron. - Native Bundle-ID ist bereits de.jkaindl.physarum; der Wrapper nutzte lab.physarum.app. - Der Kaleidoskop-Aspect-Fix (381334d) ist über die geteilte WGSL→Metal-Transpilation bereits nativ enthalten.

Entscheidungen (aus dem Brainstorm)

  1. Bundle-ID de.jkaindl.physarum überall — bereits gesetzt, keine Änderung; lab.physarum.app entfällt mit dem Wrapper. Bewusst kein In-place-Update für Alt-DMG-Nutzer (winzige Nutzerbasis, Konsistenz > Continuity).
  2. Version 2.0.0 — SemVer-Major signalisiert den Architektur-Bruch (native App statt Wrapper). Eine gemeinsame Versionslinie (Web-PWA + DMG); in beiden Quellen gesetzt.
  3. Release-Build via Script + xcodebuild-Overrides + ExportOptions.plist (Developer-ID); Dev-Builds bleiben ad-hoc. scripts/build-macos-app.sh wird repurposed (nicht zweites Script).
  4. WKWebView-Wrapper wird gelöscht (nicht nur ungenutzt gelassen).
  5. Web-PWA unangetastet — der Cutover betrifft nur die DMG.

Architektur

① Packaging-Pipeline — scripts/build-macos-app.sh (neu verdrahtet)

Ersetzt den swiftc-Wrapper-/Web-Embed-/Icon-SVG-Pfad durch den nativen Archive→Export→Notarize→DMG-Fluss:

  1. cd apple/PhysarumApp && xcodegen generate
  2. xcodebuild archive -scheme Physarum -destination 'generic/platform=macOS' -archivePath <build>/Physarum.xcarchive mit Release-Signing-Overrides: CODE_SIGN_STYLE=Manual, CODE_SIGN_IDENTITY="$SIGN_IDENTITY", DEVELOPMENT_TEAM=U9X7M39R56, ENABLE_HARDENED_RUNTIME=YES, CODE_SIGN_ENTITLEMENTS=<…>/Physarum.macOS.entitlements, CODE_SIGNING_REQUIRED=YES. Universal: ARCHS="arm64 x86_64" + ONLY_ACTIVE_ARCH=NO — matcht die Reichweite des alten Wrappers (macOS 14 läuft noch auf Intel); am Build verifizieren, dass PhysarumEngine für beide Slices baut.
  3. xcodebuild -exportArchive -archivePath … -exportOptionsPlist scripts/ExportOptions.plist -exportPath <out> mit method: developer-id, teamID: U9X7M39R56.
  4. notarisieren + stapeln der .app (ditto -c -k → xcrun notarytool submit -p "$NOTARY_PROFILE" --wait → xcrun stapler staple).
  5. DMG zusammenbauen (.app + /Applications-Symlink + macos/INSTALL.txt, hdiutil create … UDZO) — wiederverwendet die bewährte Plumbing aus den alten Schritten 8–9.
  6. DMG signieren + notarisieren + stapeln, dann xcrun stapler validate + spctl -a -vvv -t open / -t exec.

Env-gated wie bisher: PHYSARUM_SIGN_IDENTITY (auto nimmt die Developer-ID-Application-Identity), PHYSARUM_NOTARY_PROFILE. Beide unset → ad-hoc (Dev). Output: dist-macos/Physarum.app + dist-macos/Physarum.dmg.

Neue Datei scripts/ExportOptions.plist (committet): method=developer-id, teamID=U9X7M39R56, signingStyle=manual.

② Signing & Entitlements

Neue Datei apple/PhysarumApp/Resources/Physarum.macOS.entitlements: - unsandboxed (KEIN com.apple.security.app-sandbox). - com.apple.security.device.audio-input = true — Mic-Zugriff unter Hardened Runtime (Audio-Reaktivität).

Hardened Runtime wird per Signing-Flag aktiviert (ENABLE_HARDENED_RUNTIME=YES), nicht per Entitlement. Photos-Add läuft bei der unsandboxed Developer-ID-App über die vorhandene NSPhotoLibraryAddUsageDescription (TCC-Grant, kein Entitlement) — am notarisierten Build zu verifizieren (Capture→Save-to-Photos auf echtem Mac). Wichtig: die Release-Signing-Settings (CODE_SIGN_IDENTITY, CODE_SIGN_STYLE=Manual, ENABLE_HARDENED_RUNTIME=YES, CODE_SIGN_ENTITLEMENTS, DEVELOPMENT_TEAM) werden als xcodebuild-CLI-Overrides im Script gesetzt — project.yml-macOS-Signing bleibt unverändert ad-hoc, damit lokale Dev-Builds unberührt bleiben (kein Cert-Zwang, kein versehentliches Signieren).

③ Version

2.0.0 in beiden Quellen: package.json und project.yml MARKETING_VERSION. Der Build trägt MARKETING_VERSION als CFBundleShortVersionString; CURRENT_PROJECT_VERSION/Build-No bleibt wie gehabt. (Adressiert AGENTS-Punkt PROF-NAT-02 minimal — voller Sync-Script ist Folgearbeit.)

④ Bundle-ID

de.jkaindl.physarum (unverändert). lab.physarum.app entfällt.

⑤ Wrapper-Retirement

Entfernt: macos/Physarum.swift (WKWebView) + die Wrapper-spezifischen Build-Schritte (swiftc-Compile, Web-dist-Embed, Icon-SVG→icns) im Script. Bleibt: macos/INSTALL.txt (im native DMG). Native App liefert ihr AppIcon aus Assets.xcassets. assets/icon-macos.svg BLEIBT — es wird von apple/scripts/build-viewer-app.sh (Ambiance-Viewer) genutzt; nur seine Verwendung im repurposed build-macos-app.sh entfällt.

⑥ Web-PWA unangetastet

Codeberg-Pages-Deploy (PWA) läuft unverändert; der Kaleidoskop-Fix geht beim nächsten Web-Build mit. Kein src/-Eingriff durch den Cutover.

⑦ Docs & Gatekeeper-Notice

  • docs/MACOS-APP.md umschreiben: native App statt Wrapper (Build-Befehl, Signing/Notarize, Feature-Set).
  • README-DMG-Sektion + CHANGELOG [2.0.0] (Cutover beschreiben: native macOS-App mit Editor/Audio/Capture löst den Web-Wrapper ab).
  • DMG-Gatekeeper-Notice an allen 4 Stellen (README, MACOS-APP.md, INSTALL.txt, Codeberg-Release-Body) synchron halten (AGENTS-Pflicht). Notarisiert+stapled ⇒ Gatekeeper akzeptiert i.d.R. ohne Workaround; die „Trotzdem öffnen"-Anleitung bleibt als Fallback.

⑧ Verifikation & Geräte-Sign-off

  • Script-Gates: stapler validate = valid, spctl -a -t exec = accepted (sonst nicht ausliefern).
  • Build muss grün bleiben: xcodebuild -scheme Physarum -destination 'platform=macOS' build + bestehende App-Tests.
  • Geräte-Sign-off (Johannes): notarisierte DMG auf einem (idealerweise frischen) Mac öffnen → startet ohne Gatekeeper-Block; Editor/Audio (Mic-Permission)/Capture (Save-to-Photos/Export) laufen.

⑨ Creds-Handoff (gehören Johannes)

  • PHYSARUM_NOTARY_PROFILE — notarytool-Keychain-Profilname (nicht non-interaktiv ermittelbar).
  • Codeberg-Release — API-Token (write:release) als Env-Var, oder Johannes publisht den Release; CC liefert DMG + Release-Body fertig zu.

Release-DoD (Cockpit-Politik)

Version-Bump (2.0.0, beide Quellen) → CHANGELOG [2.0.0] → notarisierte native DMG → Tag v2.0.0 → Codeberg-Release mit DMG → 4 Notice-Stellen synchron. Tag/Publish sind die outward-facing Schritte (Creds-gated).

Non-Goals

  • Kein nativer iOS/tvOS-Release, kein App Store (Developer-ID-Direktvertrieb).
  • Kein In-place-Update für Alt-lab.physarum.app-Nutzer (bewusster Bruch).
  • Keine Engine-/Shader-/Param-Layout-Änderung; kein src/-Eingriff (Web-PWA unverändert).
  • Kein Sandboxing (Direktvertrieb, unsandboxed wie der Wrapper).

Offene Punkte

  • Notary-Profil-Name + Codeberg-Token (Creds, s. ⑨).
  • Photos-Add unter Hardened Runtime ohne Entitlement — am notarisierten Build verifizieren; falls nötig, Entitlement nachrüsten.
  • Universal-Build (arm64+x86_64) am Build verifizieren (lipo -archs auf dem Binary; entschieden in ①).
  • Frischer-Mac-Sign-off (Gatekeeper-Verhalten der notarisierten DMG).