Zum Inhalt

Physarum Native — Parameter Tooltips (paramHelp) Design Spec

Status: Approved · 2026-06-03 · author: Johannes (brainstormed with Claude) Builds on: the registry-driven editor (2026-06-03-authoring-editor-design.md) + adaptive compact UI. Ports the web app's per-parameter help (src/ui/paramHelp.ts) to the native editor, following current Apple HIG / SwiftUI best practices (see memory native-app-apple-hig).

1. Kontext & Ziel

Der native Editor zeigt 45 Slider-Params ohne Erklärung. Die Web-App hat pro Param einen Ein-Satz-Hilfetext (PARAM_HELP in src/ui/paramHelp.ts), eingeblendet per Tooltip. Dieser Increment bringt diese Hilfe nativ — selbsterklärender Editor auf macOS/iPad/iPhone — und richtet das Surface an aktuellen HIG-Best-Practices aus (Discoverability, Popover-Adaptation, Accessibility, Dynamic Type).

2. Festgezurrte Entscheidungen (Brainstorming 2026-06-03)

Achse Entscheidung
Surface Label-Tap → Popover mit dem Hilfetext. macOS zusätzlich Hover-.help(...).
Discoverability Ein dezentes inline-ⓘ direkt hinter dem Label-Text (tertiär, klein) signalisiert „Hilfe vorhanden" — HIG: Interaktives muss interaktiv aussehen. Keine separate Trailing-Spalte (clutter-arm).
Popover (compact) .popover(isPresented:) + .presentationCompactAdaptation(.popover) → auf iPhone eine kleine verankerte Info-Blase, kein Full-Sheet.
Accessibility .accessibilityHint(tooltip) am Slider → VoiceOver liest die Hilfe; nicht nur visuell.
Dynamic Type Popover-Text bricht um, respektiert Schriftgröße, ~260 pt breit, padding.
Daten Texte 1:1 aus src/ui/paramHelp.ts portiert in ein Swift-Dict, keyed by nativem Label (vermeidet Web↔Native-Label-Mismatch). Matrix-Zellen bekommen einen gemeinsamen Text.
Scope Skalare ParamSlider-Zeilen (45, inkl. 9 Matrix). Non-Scalar-Controls (Spawn/Blend-Picker, Farben) v1-out-of-scope.

3. Daten: ParamHelp

Neues Sources/Model/ParamHelp.swift: - static let byLabel: [String: String] — keyed by nativem ParamDescriptor.label (z.B. "Agents", "Population", "Deposit", "Decay", "Diffusion", "Fade", "Trail cap", "Rhythm", "Head glow", "Feedback", "Bloom", sowie die deckungsgleichen "Exposure", "Sensor angle", …). Texte wörtlich aus PARAM_HELP übernommen (eine Quelle zum Portierzeitpunkt; spiegelt die Web-Datei, kein Auto-Sync in v1). - static func text(for label: String) -> String — Matrix-Labels (Präfix "M[") → ein gemeinsamer englischer Text (UI-Sprache der App, konsistent mit den portierten Texten): "How strongly the row species senses the column species' trail. Positive attracts, negative repels; the diagonal is a species' response to its own trail (cohesion)."; sonst byLabel[label] ?? "".

4. Registry-Anbindung

ParamDescriptor bekommt let tooltip: String. Der add(...)-Builder in ParamRegistry.all setzt tooltip: ParamHelp.text(for: label). Anpasspunkt: der manuelle ParamDescriptor(...)-Init in SpeciesSection.broadcast(_:) (Sections.swift) muss tooltip: d.tooltip durchreichen.

5. Surface in ParamSlider

In der Label-HStack: Label-Text + ein kleines inline-Image(systemName: "info.circle") (tertiär, ~10 pt), nur wenn descriptor.tooltip nicht leer. Label+ⓘ in einem Button/.onTapGesture, das showHelp = true setzt (@State private var showHelp). Daran: - .popover(isPresented: $showHelp) mit dem Text (.font(.callout), multilineTextAlignment(.leading), .frame(width: 260), .padding(), .presentationCompactAdaptation(.popover)). - macOS zusätzlich .help(descriptor.tooltip) am Control (Hover). - .accessibilityHint(descriptor.tooltip) am Slider.

Koexistenz: Das Label-Tap (single) liegt auf dem Label-Subview; der bestehende Zeilen-Doppeltap-Reset liegt auf dem VStack — getrennte Subviews, kein Gesten-Konflikt. (Im Plan auf Geräte-Disambiguierung achten.)

6. Testing & Verifikation

  • Unit-testbar (App-Test-Target):
  • Jeder der 45 Descriptors hat einen nicht-leeren tooltip (fängt fehlende/vertippte Label-Keys).
  • ParamHelp.text(for:): bekanntes Label → erwarteter Text; "M[1,2]" → der Matrix-Text; unbekanntes Label → "".
  • Build + visuell (Johannes): macOS Hover-Tooltip + Label-Tap-Popover; iPhone/iPad Label-Tap → kleine verankerte Popover-Blase (kein Sheet); VoiceOver liest den Hint; Dynamic Type vergrößert den Text.
  • Gate: App-Suite grün (jetzt inkl. der neuen Tooltip-Tests); macOS + iOS-Sim BUILD SUCCEEDED.

7. Scope (in) / Out-of-scope

In: ParamHelp (portierte Texte, native-Label-keyed, Matrix-Sammeltext) · ParamDescriptor.tooltip + Registry-Befüllung + broadcast()-Anpassung · ParamSlider inline-ⓘ + Label-Tap-Popover + macOS-.help + .accessibilityHint + Compact-Popover-Adaptation + Dynamic-Type-Sizing · Unit-Tests.

Out-of-scope (spätere Increments): Hilfe für Non-Scalar-Controls (Spawn/Blend/Farben — Picker-Titel nicht sauber antippbar ohne Umbau) · Audio-Param-Hilfe (Audio ist nativ ohnehin nicht editierbar) · Auto-Sync der Texte aus der Web-SSOT (manuelle Portierung reicht; Texte sind stabil).

8. Offene Risiken / im Plan zu klären

  • Gesten-Disambiguierung Label-single-tap vs Zeilen-double-tap-Reset auf echtem Gerät prüfen.
  • .presentationCompactAdaptation ist iOS 16.4+/macOS 13.3+ — bei Deployment-Target (macOS 14/iOS 17) verfügbar.
  • Label-Mapping-Vollständigkeit: der 45-Descriptor-Test ist das Sicherheitsnetz; jedes native Label muss in byLabel stehen (oder ein Matrix-Label sein).