Zum Inhalt

Native App — Geräte-Sign-off-Checkliste

Stand: 2026-06-08 · Commit a78e3f6 · App-Suite 98 + Engine 25 grün, iOS-Sim-Build grün. Audio-Feel-Rework (Loudness-Response + globale Config) gelandet; Per-Band-Balance noch offen. Zweck: Was die XCTest-Suite nicht abdeckt — alles, was nur per Auge, Ohr, Touch, echter Fotos-Bibliothek und echtem Mikrofon verifizierbar ist. Jeder Punkt ist aus dem apple/-Code abgeleitet (siehe Beleg-Zeile).

So nutzen: Pro Gerät ein Block. 🔴 = kritisch (neu/riskant), zuerst durchgehen. ⚠ = bekanntes Risiko, da besonders hinschauen. Bei Abweichung nennt die Beleg-Zeile die Code-Stelle zum Nachfassen.


📱 iPhone (JK) — voller Durchlauf

Fundament & Rotation

  • [x] 🔴 App in Portrait starten, einmal drehen, zurückdrehen. Simulation füllt das Display randlos bis in alle Ecken (kein Letterbox, kein kleiner zentrierter 320×480-Block, keine schwarzen Balken). Beim Drehen bleibt der Player randlos; nach ~0,3 s passt sich das Feld dem neuen Seitenverhältnis an (kurzes Re-Seed sichtbar). ⚠ Fällt der Player in den 320×480-Compat-Mode (kleiner upscalter Block mit Rändern), fehlt der UILaunchScreen-Key im gebauten Bundle — nur auf echter HW/Sim sichtbar. Beleg: Resources/Info.plist:UILaunchScreen; RootView.swift fullW/fullH+ignoresSafeArea; RenderCoordinator.setField/recreate

Audio-Reaktivität (Schwerpunkt — neu)

Update 2026-06-08: Auto-gain ist jetzt eine Loudness-Response (leise=sanft / laut=voll / Stille=ruhig), und die Audio-Config ist global (überlebt Preset-Wechsel). Band-Balance (high soll mehr ausschlagen) ist noch offen → eigene gerätegetunte Runde; der Meter bleibt vorerst Bass-lastig. - [ ] 🔴 Bei jungfräulichem Preset (keine Routen) den „Enabled"-Toggle einschalten, Mic-Permission erlauben. Dialogtext exakt „Physarum uses the microphone so the animation can react to sound." Danach reagiert die Animation sofort (ohne erst „Default" drücken zu müssen): 5 Routen werden automatisch angelegt, Auto-gain auf AN. ⚠ Bei Ablehnung startet die Engine nicht (kein Crash, aber keine Reaktion, Meter bleibt leer). Beleg: AppState.setAudioEnabled (routes.isEmpty → defaultAudioRoutes + autoGain=true); AudioInput.requestPermission - [ ] 🔴 Die 5 Routen-Karten ablesen (Reihenfolge + Werte). Exakt: 1) Bass→Bloom · Trans · +1.2 — 2) Bass→Move · Trans · +0.8 — 3) High→Expo · Trans · +0.6 — 4) Mid→Chaos · Trans · +0.7 — 5) Mid→Sat · Lvl · +0.4. Quell-Labels kapitalisiert (Bass/Mid/High), Depth mit Vorzeichen + 1 Nachkommastelle. ⚠ Karte 5 ist als einzige Lvl, die anderen 4 Trans. Sehen alle 5 gleich aus, stimmt die Mode-Zuordnung nicht. Beleg: AppState.swift:103-109 defaultAudioRoutes; AudioReactiveSection.swift:89/128-129/144 - [ ] Ziel-Picker einer Route öffnen. Sektion „Agent": Move, Turn, Deposit, Sensor, Rhythm, Chaos. Sektion „Post": Expo, Bloom, Sat, Contr (bewusst gekürzt). Beleg: AudioReactiveSection.swift:153-164 shortTarget - [ ] 🔴 Musik mit klarem Beat abspielen (Lautsprecher nah ans Mic). Die 3 Meter (Bass/Mid/High) füllen sich pegelabhängig 0..1. Mit den 4 Transient-Routen bekommt die Animation einen sichtbaren Punch genau auf den Beat (Bloom + Move zucken auf Bass-Hits, Exposure auf Highs), kein konstanter Dauer-Boost. Mid→Sat (Lvl) gibt dagegen eine ruhigere, gehaltene Sättigungs-Anhebung. ⚠ Punch muss beat-synchron sein, nicht träge nachziehen (Release ~0,25 s). Nur per Ohr/Auge prüfbar. Beleg: AudioAnalyzer.transientValue; AudioFactors.evaluateAudioFactors (transient vs level) - [ ] 🔴 Musik komplett stoppen, ~5–10 s beobachten. Loudness-Response fährt die Gesamt-Reaktion bei Stille gegen 0 → Animation kehrt in Ruhe zurück, Meter fallen fast auf 0. ⚠ Loudness-Response ersetzt das alte ×20-Auto-Gain (kein Silence-Over-Boost mehr). Achte: bleibt Stille wirklich ruhig? Zappelt Grundrauschen noch → gateLo (0.04) anheben; schneidet es zu abrupt ab → gateLo senken. Beleg: AudioFactors.swift smoothstep + loudnessEnv (gateLo 0.04 / gateHi 0.22 / envCoef 0.15) - [ ] Sehr leise Musik abspielen. Loudness-Response: leise = sanfte, proportionale Reaktion (nicht mehr über-geboostet). ⚠ Der frühere Low-Loudness-Over-Boost ist mit der Loudness-Response behoben. Fühlt sich leise Musik proportional an? Reagiert zu spät/zu wenig → gateHi (0.22) senken; flasht zu früh voll → gateHi anheben. Beleg: AudioFactors.swift evaluateAudioFactors (resp = smoothstep(gateLo, gateHi, loudnessEnv)) - [ ] Auto-gain AUS, gleichen Loop abspielen, dann Gain-Slider verschieben. Mit Auto-gain AUS kein Loudness-Gating → immer volle Reaktion; Stärke hängt nur am manuellen Gain (0.1×..10×, Anzeige z.B. „1.00x"). Beleg: AudioReactiveSection Gain-Slider; AudioFactors effGain = gain · (autoGain ? resp : 1) - [ ] Depth-Slider einer Route ans negative Ende ziehen (−2.0). Bereich −2.0..+2.0; bei negativem Depth invertiert sich die Modulation (Ziel sinkt auf Hits statt zu steigen). Wert zeigt „−2.0". ⚠ Prüfen, dass das segmentierte Lvl/Trans-Picker (116 pt fest) + Depth-Slider + Wert in Zeile 2 auf dem schmalen iPhone-Layout nicht abgeschnitten werden. Beleg: AudioReactiveSection.swift:141/131 - [ ] 🔴 Bei bestehenden (selbst geänderten) Routen Audio AUS- und wieder EINschalten. Vorhandene Routen werden nicht überschrieben (Defaults nur bei leerer Liste), Auto-gain nicht zwangsweise erzwungen. Beim Disable stoppt das Mic, Animation kehrt sofort in unmodulierten Zustand zurück. ⚠ Nicht verwechseln mit dem „Default"-Button, der die Routen immer auf die 5 Defaults zurücksetzt. Der Enable-Toggle darf bestehende Routen nicht anfassen. Beleg: AppState.setAudioEnabled / applyDefaultAudioWiring; AudioInput.stop() - [ ] 🔴 Globale Config: Audio einrichten, dann mehrere Presets durchschalten (und App neu starten). Die Audio-Verdrahtung (Routen/Gain/Auto-gain/Enabled) bleibt über Preset-Wechsel + Neustart hinweg erhalten — nicht mehr pro Preset. Genau das zum Durchtesten verschiedener Looks mit einem Audio-Setup. ⚠ Neu in dieser Runde (früher überschrieb ein Preset-Wechsel die Audio-Config). Beleg: AppState.audioReactive (global, persistiert via PresetStore _audio.json)

Capture / Export (neu)

  • [ ] Capture öffnen (Share-Icon Topbar), „Photo" gewählt lassen, Defaults prüfen. Resolution default 2048 (Optionen 1080 / 2048 / „4K · 4096"), Format default PNG (PNG / HEIC / Live Photo). Button „Capture". ⚠ Default-Resolution ist 2048, nicht der kleinste Wert — steht 1080 vorgewählt, ist der State-Default verrutscht. Beleg: CaptureConfig.stillSizes/defaultStill; PhotoFormat.label
  • [ ] 🔴 PNG „Capture", Fortschritt abwarten, Vorschau-Karte ansehen. Linearer ember-Progress mit „n %", danach Done-Karte mit echtem Thumbnail (kein Platzhalter) + Dateiname physarum-<preset>-YYYYMMDD-HHmmss.png. Look muss visuell mit dem Live-Bild übereinstimmen (Farben, Trail-Dichte, Post-FX/Feedback-Glow). ⚠ Wegen 180 Warmup-Frames + density-skaliertem agentCount kann der Export anfangs dünner/blasser/dichter wirken — auf Farbtreue + Trail-Dichte achten (Presets sind auf Feld=512 getuned). Beleg: CaptureService warmupFrames=180; CaptureMath.captureFilename
  • [ ] 🔴 „Save to Photos" tippen (erstes Mal: Foto-Berechtigung erteilen). Button → „Saving…" → „Saved" (deaktiviert), grünes „Saved to Photos" mit Häkchen, Erfolgs-Haptik spürbar. PNG liegt in der Fotos-Bibliothek. ⚠ Bei verweigerter Berechtigung kein Crash — stattdessen „Couldn't save — check permissions." Beleg: CaptureSaver.saveToPhotos (.addOnly); CaptureSheet.swift:178 (success haptic)
  • [ ] 🔴 Direkt danach erneut „Save to Photos" (schnell mehrfach tippen). Button bleibt deaktiviert/„Saved", kein zweites Speichern. In Fotos liegt das Bild genau einmal, kein Duplikat. ⚠ Doppel-Guard ist UI-State-basiert; bei sehr schnellem Doppeltipp vor .saving auf echtes Duplikat prüfen. Beleg: CaptureSheet.swift:142-143
  • [ ] Foto mit Format „HEIC" aufnehmen + speichern, Datei-Info in Fotos prüfen. Endung .heic, in Fotos als HEIF/HEIC geführt, visuell identisch zum PNG. ⚠ Auf Geräten ohne HEIC-Encoder fällt der Writer still auf .jpg zurück — dann ist die Endung .jpg (kein Fehler, aber notieren). Beleg: ImageWriter.writeStill (heicSupported, JPEG-Fallback q0.9)
  • [ ] 🔴 Foto mit Format „Live Photo" aufnehmen + speichern, Asset in Fotos öffnen. Render ~2 s @ 60 fps. In Fotos erscheint ein Asset mit LIVE-Badge (oben links) — nicht getrenntes Foto + Video. Standbild = mittlerer Frame (Frame 60). ⚠ Paarung hängt an 3 Metadaten-Haken (content.identifier, still-image-time-Track, Maker-Dict „17", gleiche assetID). XCTest prüft nur das .mov; ob Fotos die Paarung akzeptiert, ist nur hier sichtbar. Beleg: CaptureService.captureLivePhoto (keyFrameIndex frames/2); CaptureSaver.saveLivePhoto
  • [ ] 🔴 Das Live Photo in Fotos lange gedrückt halten. Die ~2 s Physarum-Bewegung spielt flüssig ab (Trails bewegen sich), Standbild davor = Bewegungsmitte. ⚠ Animiert der Long-Press nichts, ist die Still↔Video-Paarung gebrochen (assetID-Mismatch / Timing) — das ist der Kernbeweis des Live-Photo-Pfads. Beleg: VideoClipRecorder stillImageTimeItem/contentIdentifierItem; ImageWriter Maker-Dict „17"
  • [ ] Auf „Video" umschalten, Defaults prüfen. Resolution default 1920 (1280/1920/2560), Duration default „10 s" (5/10/15/30), Codec default „HEVC (.mov)" (oder „H.264 (.mp4)"). Button „Render clip". Beleg: CaptureConfig.videoSizes/videoDurations; VideoCodec.label
  • [ ] 🔴 HEVC-Clip 5 s rendern + speichern + in Fotos abspielen. Fortschritt über 300 Frames. Done-Karte mit Video-Thumbnail + *.mov. In Fotos ~5 s flüssig @ 60 fps, gleicher Aspekt + Look wie Live (keine Farb-/Helligkeitsverschiebung). ⚠ Recorder taggt BT.709 — bei sichtbarer Farb-/Gamma-Verschiebung ist die Color-Pipeline schief. Beleg: VideoClipRecorder (HEVC→.mov, BT.709)
  • [ ] Zweiten Clip als „H.264 (.mp4)" rendern + abspielen. Endung .mp4, spielt in Fotos, visuell gleichwertig zum HEVC. Beleg: VideoClipRecorder.begin (h264→.mp4)
  • [ ] Fill/Crop testen: je ein Foto mit „Fill (crop edges)" AUS (fit) und AN (cover), Ränder vergleichen. fit: ganzer Sim-Inhalt sichtbar, Export-Aspekt = Feld-Aspekt, kein Beschnitt, kein Letterbox. cover: view-aspekt-beschnittener Ausschnitt, exakt wie das Live-Composite (gleiche Crop-Kanten). ⚠ Default ist fit. Export muss exakt denselben Ausschnitt wie das Live-Bild zeigen. Beleg: CaptureService.swift:54 (cover?viewAspect:fieldAspect); RootView renderCover default false
  • [ ] format-Menü auf „Portrait" bzw. „Square", je ein Foto, Seitenverhältnis prüfen. Square: quadratisch (720×720). Portrait: hochkant 9:16 (720×1280). Export-Aspekt folgt dem SimFormat, stimmt mit Live überein. ⚠ Bei fit bestimmt das Feld-Aspekt, bei cover das View-Aspekt — Format + Fill-Schalter zusammen denken. Beleg: SimFormat.targetFieldSize; CaptureService.swift:49

Editor & Interaktion (Touch)

  • [ ] 🔴 Editor öffnen (Slider-Icon slider.horizontal.3 rechts in der Topbar), in Portrait UND Landscape. Editor erscheint immer als Bottom-Sheet (nie Inspector-Spalte), Detents medium/large, Player läuft sichtbar weiter. Beim App-Start war das Sheet zu (player-first). ⚠ Erschiene der Editor auf iPhone als Inspector, ist die Size-Class-Erkennung kaputt. Beleg: EditorPresentation (isCompactWidth→.sheet); RootView .sheet(presentationDetents/backgroundInteraction)
  • [ ] 🔴 Slider verschieben; Zahlenwert rechts antippen + Wert eintippen + Return; dann Slider-Zeile doppelt antippen. Wert-Tap öffnet 64-pt-Textfeld mit aktuellem Wert; Eingabe + Return übernimmt geclampt. Doppeltap auf die Zeile setzt auf Preset-Default zurück (mit Undo). Int-Params ganze Zahlen, sonst %.3g. ⚠ Doppeltap-Reset darf nicht mit dem Single-Tap aufs Wertfeld kollidieren — gezielter Wert-Tap muss das Textfeld öffnen, nicht versehentlich resetten. Beleg: ParamSlider.swift valueLabel/onTapGesture(count:2)/formatted
  • [ ] Kleines ⓘ neben einem Param-Label antippen. Popover (260 pt) mit Hilfetext per Tap. Labels ohne Hilfetext zeigen kein ⓘ. ⚠ Auf iPhone compact muss presentationCompactAdaptation(.popover) es wirklich als Popover (nicht Full-Sheet) zeigen — visuell prüfen. Beleg: HelpLabel.swift onTapGesture/.popover(260)
  • [ ] 🔴 Library öffnen (Preset-Name-Button in der Topbar) bei geöffnetem Editor-Sheet. Zuerst schließt das Editor-Sheet, dann öffnet die Library (verzögert über pendingLibrary). Überschrift „PICK A PRESET", Suchfeld „Search…", Tag-Pills, Karten-Grid. ⚠ Bekanntes iOS-17-Risiko (im Code dokumentiert): Sheet-dismiss + sofortiges present verschluckt die Library — prüfen, dass sie wirklich erscheint und nicht „nichts passiert". Beleg: RootView.openLibrary (pendingLibrary); LibraryView.swift:49/53
  • [ ] 🔴 Mehrere Tag-Pills aktivieren, Tag-Leiste scrollen, im Suchfeld tippen. Aktive Pills orange, inaktive grau. Mehrere Tags = UND-Filter (nur Presets mit allen aktiven Tags). Grid auf iPhone = eine Spalte (vertikal gestapelt). ⚠ UND, nicht ODER — bei vielen Tags kann das Grid leer werden (erwartet). Beleg: LibraryView.swift:41 (activeTags.isSubset); :20-28 columns
  • [ ] 🔴 Vollbild aktivieren (Icon arrow.up.left.and.arrow.down.right), dann einmal auf die Simulation tippen. Topbar verschwindet, Player vollformatig. Ein Tap blendet unten zwei Glass-Pills ein: „Capture" + „Exit fullscreen", die nach 2,5 s wieder verschwinden. (Editor offen, nicht-Vollbild: Canvas-Tap schließt den Editor.) ⚠ Der Reveal-Tap ist die einzige Art, Vollbild zu verlassen — trifft der Tap auf dem MetalView nicht, sitzt man fest. Pills müssen unten thumb-erreichbar sitzen (auch Landscape). Beleg: RootView.handleCanvasTap/revealFsHint (2.5s)
  • [ ] iPhone Landscape mit langem (Custom-)Preset-Namen — Topbar ansehen. Name 1-zeilig, am Ende mit … abgeschnitten; Format-Menü + Capture + Vollbild + Editor-Icon bleiben als 34×30-Touchtargets sichtbar. Name truncatet, statt die Icons rauszudrücken. ⚠ Zwischen Name und den 4 Controls steht nur ein Spacer — bei sehr schmaler Breite prüfen, dass keine Icons abgeschnitten werden. Beleg: RootView.swift:134-135 (lineLimit(1)/truncationMode); :140 Spacer

📱 iPad — Layout-Deltas + Smoke

Audio + Capture nutzen dieselben Code-Pfade wie iPhone — hier reicht ein Smoke-Test. Der echte iPad-Fokus ist die adaptive Präsentation (Inspector ↔ Sheet) und das Grid.

  • [ ] 🔴 Vollbild (kein Split-View): Editor öffnen, Landscape → Portrait → zurück drehen. In Landscape (Breite > Höhe): Editor als Inspector-Spalte rechts (Icon sidebar.right, min 300 / ideal 340 pt). In Portrait (Höhe ≥ Breite): derselbe Editor als Bottom-Sheet. Beim Drehen live umgeschaltet, Editor-Inhalt bleibt erhalten. ⚠ Heikelste Stelle: beide Bindings (Inspector & Sheet) hängen am selben showEditor/mode. Beim Rotieren mit offenem Editor muss genau eine Präsentation aktiv sein — Risiko: kurz doppelt, leer, oder Sheet klappt sofort wieder zu (iOS-Sheet-dismiss-vor-present). Beleg: EditorPresentation.swift:14; RootView .inspector(...)/.sheet(...)
  • [ ] 🔴 iPad in Split-View/Slide-Over ziehen (Physarum schmal), dann zurück auf Vollbild. Bei kompakter Breite: Editor wechselt zwingend auf Sheet, die PHYSARUM-Eyebrow in der Topbar verschwindet. Zurück auf Vollbild-Landscape: Inspector + Eyebrow kehren zurück. ⚠ Beim Compact-Werden mit offenem Inspector muss der Inspector verschwinden und das Sheet übernehmen (gleicher mode-Handoff wie bei Rotation). Beleg: EditorPresentation.swift:13; RootView.showEyebrow (hSizeClass != .compact)
  • [ ] 🔴 Library öffnen, iPad drehen. Karten-Grid auf iPad-regular = adaptives Mehrspalten-Grid (min 150 pt); bei Rotation schaltet die Spaltenzahl um. (Auf compact/Split eine Spalte.) Beleg: LibraryView.swift:20-28 columns (.adaptive(min:150))
  • [ ] Format-Menü + Editor-Segmented-Picker synchron? Topbar-Menü (4 Optionen Auto/Square/Portrait/Landscape + „Fill"-Toggle) und der Segmented-Picker oben im Editor zeigen dieselbe Wahl (gleiche @AppStorage-Quelle); Änderung an einer Stelle ändert die andere + das Feld live. ⚠ Auf schmaler Sheet-Breite prüfen, dass „Landscape" im Segmented-Picker nicht abgeschnitten wird. Beleg: EditorView.swift:16-23 @AppStorage('simFormat')
  • [ ] Smoke Audio: Enable → 5 Routen + Reaktion auf Musik (wie iPhone), Meter laufen.
  • [ ] Smoke Capture: je ein PNG + ein Live Photo + ein Clip aufnehmen, in Fotos speichern, Live-Photo-Long-Press animiert.

🖥 macOS — voll (bisher nur build-verifiziert)

Fenster, Format & Viewport

  • [ ] 🔴 Fenster von quadratisch auf breit und auf schmal-hoch ziehen, Look beobachten; dann „Fill (crop edges)" einschalten. Format „Auto": Simulation füllt das Fenster ohne Verzerrung. Fill AUS (Default): ganzes Feld sichtbar, Überstand zeigt Preset-Hintergrundfarbe (Letterbox, kein Schwarz). Fill AN: Feld füllt das Fenster, Ränder werden abgeschnitten, kein Verzerren/Stauchen beim Resize. ⚠ cover-Viewport wurde nie auf einem echten macOS-Fenster geprüft — auf Verzerrung beim Live-Resize + korrekte Crop- statt Stretch-Geometrie achten. Beleg: MetalView.sync (cover/format); Renderer.encodeFrame(cover:)
  • [ ] Format-Menü (aspectratio) Square / Portrait / Landscape durchgehen. Square → 720×720. Portrait → 720×1280. Landscape → 1280×720. Ohne Fill erscheint bei abweichendem Fensterformat die Letterbox in Preset-Bg. Auswahl überlebt App-Neustart (@AppStorage). Beleg: SimFormat.targetFieldSize; RootView.formatMenu

Slider-Präzision (Maus + Tastatur)

  • [ ] 🔴 Mauszeiger über einen Param-Slider, Scrollrad/Zwei-Finger hoch/runter. Wert nudged pro Tick um base (baseStep ?? range/100). Der Editor-ScrollView scrollt dabei nicht mit (Event konsumiert). Nudge wirkt nur, wenn der Zeiger über dem Slider-Frame im selben Fenster steht. ⚠ macOS-only NSEvent-Monitor, nie auf HW geprüft. (1) Slider-Drag per Maus muss weiter funktionieren (Monitor liegt hinter dem Slider), (2) Containment korrekt, (3) Zeiger nicht über Slider → Liste scrollt normal. Beleg: SliderPrecision.SliderScrollMonitor (addLocalMonitorForEvents)
  • [ ] 🔴 Beim Scrollen über einem Slider Shift halten, dann Option, dann beide. Shift = fein ×0.1, Option = grob ×10, beide = fein hat Vorrang (×0.1). Inkrement pro Tick ändert sich sichtbar. ⚠ Prüfen, dass fein wirklich Vorrang vor grob hat. Beleg: SliderPrecision.precisionStep (fine?0.1:(coarse?10:1))
  • [ ] 🔴 Slider per Maus/Tab fokussieren, Pfeil-Links/Rechts nudgen, Home/End drücken (auch mit Shift/Option); danach Cmd+Z. Rechts = +step, Links = −step (Shift fein, Option grob). Home → Minimum, End → Maximum. Jede Aktion ist undo-bar. ⚠ .focusable() auf Slidern ist auf macOS heikel — prüfen, ob der Slider Fokus annimmt und Pfeiltasten nicht vom umgebenden ScrollView abgefangen werden. Beleg: SliderPrecision.precisionControls (.focusable/.onKeyPress); ParamSlider (onEditStart: pushUndo)

Hilfe, Library-Kontextmenü

  • [ ] Über ein ⓘ-Label hovern (nicht klicken), dann anklicken. Hover → natives macOS-Tooltip (.help). Klick → zusätzlich Popover (260 pt, .callout) mit demselben Text. Labels ohne Hilfetext zeigen kein ⓘ. ⚠ Doppel-Affordanz (Hover-Tooltip UND Klick-Popover) — beide müssen unabhängig erscheinen, sich nicht blockieren. Beleg: HelpLabel.swift #if os(macOS) .help + .popover
  • [ ] 🔴 Library öffnen, auf einer eigenen (Custom-)Karte rechtsklicken. Kontextmenü nur bei Custom-Presets: „Rename", „Move Up", „Move Down", ─, „Delete" (rot). Built-ins zeigen kein Menü. Oberste Karte: „Move Up" ausgegraut; unterste: „Move Down" ausgegraut. ⚠ Rechtsklick ist der macOS-Hauptpfad (kein Long-Press) — prüfen, dass es nur auf Customs erscheint. Beleg: LibraryView.swift .contextMenu
  • [ ] 🔴 „Move Up"/„Move Down" ohne Filter ausführen; dann Such-/Tag-Filter setzen, Menü erneut öffnen. Ohne Filter: Reihenfolge ändert sich sichtbar (eine Position pro Klick), bleibt nach Neustart erhalten. Mit aktivem Filter (Suche nicht leer ODER ≥1 Tag): beide Move-Einträge ausgegraut. ⚠ Verifizieren, dass beide Move-Buttons im gefilterten Zustand wirklich nicht klickbar sind. Beleg: LibraryView.isFiltering; AppState.moveCustom→reorderedIDs/store.reorder
  • [ ] „Rename" wählen, neuen Namen + Enter; separat Dialog öffnen + „Cancel". Dialog (360 pt, „RENAME PRESET") vorbefüllt mit aktuellem Namen. Enter/„Rename" committet (getrimmt, max 64; leer → kein Rename). „Cancel" schließt ohne Änderung. Neuer Name sofort auf Karte + Topbar-Pille. Beleg: RenameDialog.commit (trim + prefix(64))

Capture & Audio auf macOS

  • [ ] 🔴 Share-Icon → Capture-UI öffnen, „Photo", „Capture". Auf macOS ein Popover (kein Sheet, kein .fileExporter), am Share-Button verankert, minWidth 280. Photo-Defaults wie iOS (Res 2048, PNG). Nach „Capture" Linear-Progress + Done-Karte mit Thumbnail + Dateiname. Nur „Share" (ShareLink) + „Done" — kein „Save to Photos". ⚠ Popover-Capture nie geprüft: (1) Popover-Größe/Clipping, (2) GPU-Still sieht aus wie der Live-Preview, (3) ShareLink öffnet das Share-Sheet mit gültiger Datei-URL. Beleg: RootView .popover(showCapture); CaptureSheet #if os(iOS) save-button
  • [ ] In der Capture-UI auf „Video", kurzen Clip rendern (1920 / 10 s / HEVC). Video-Defaults wie iOS. Button „Render clip". Done-Karte mit Video-Frame-Thumbnail, Endung .mov (bzw. .mp4). Share teilt die Datei. ⚠ Render-Performance/GPU-Look des Video-Pfads auf echter macOS-GPU bestätigen. Beleg: CaptureConfig.videoSizes/Durations; CaptureSheet.videoFrame
  • [ ] 🔴 Audio-Sektion → „Enabled", macOS-Mic-Permission erlauben, ins Mic sprechen/Musik, Meter beobachten. macOS-Permission via AVCaptureDevice.requestAccess(.audio). Leere Routen → 5 Defaults + autoGain. Meter Bass/Mid/High füllen sich live, fallen bei Stille auf 0. ⚠ macOS-Mic-Flow nur per HW prüfbar (anderer Pfad als iOS): (1) Dialog genau beim ersten Enable, (2) Ablehnung → kein Crash, (3) Meter-Cadence (nur jeder 4. Tick) ruckelt nicht sichtbar. Beleg: AudioInput.requestPermission #else AVCaptureDevice; AppState.setAudioEnabled
  • [ ] „Default" klicken (5 Routen prüfen), dann Route per + hinzufügen + editieren. „Default" = die 5 bekannten Routen. Neue Route per „+" = Bass→Move depth 1 Trans. Pro Block: Source-Menü → Target-Menü (Sektionen Agent/Post) + minus zum Entfernen; Zeile 2 Mode-Segmented Lvl/Trans + Depth-Slider (−2..2) + Wert. Max 16 Routen, dann „+" ausgegraut. ⚠ Picker-Menü mit Sektionen Agent/Post per Maus prüfen; Segmented Lvl/Trans (116 pt) klickbar. Beleg: AppState.defaultAudioRoutes; AudioReactiveSection.routeRow

Befund-Notizen (für die nächste Code-Runde)

Hier eintragen, was abweicht — dann kann ich es gezielt fixen.

  • Audio-Feel: (Stille-Zappeln? Leise-Musik-Übersteuern? → Silence-Gate / Transient-Peak-Normalisierung sind die zwei bekannten Hebel)
  • iPad Inspector↔Sheet-Handoff: (doppelt / leer / klappt zu beim Rotieren?)
  • macOS Slider-Präzision: (Drag noch ok? Fokus/Pfeiltasten? Scroll-Containment?)
  • macOS cover-Viewport: (Verzerrung beim Resize?)
  • Sonstiges: