- systeme/macos.py : SystemeMacOS (sons afplay, modificateurs Quartz, chemins,
verrou d'instance fcntl) implementant le contrat Systeme.
- systeme/__init__.py : choisir_systeme() fabrique selon sys.platform,
refuse clairement une plateforme inconnue.
- core.py : verrou d'instance (fcntl, LOCK_PATH, acquire_single_instance_lock)
retire ; import os retire aussi, verifie inutilise ailleurs dans le fichier.
- app.py : passe par choisir_systeme().acquerir_verrou_instance(dossier_donnees()
+ instance.lock) au lieu de core.acquire_single_instance_lock. Seul changement
dans ce fichier, nom de fichier inchange.
- tests/test_single_instance.py : reoriente vers choisir_systeme(), memes
assertions et meme nombre de tests.
- tests/test_systeme.py (nouveau) : verrou du test_le_verrou_refuse_une_seconde_prise
garde desormais la reference au premier verrou — sans variable, CPython le
garbage-collectait entre les deux assert (refcount 0 -> fermeture -> verrou
relache), le test passait pour la mauvaise raison.
89 tests verts (84 + 5).
Deux bundles portant l'identifiant cloud.mrtechlab.zonza etaient enregistres
aupres de LaunchServices : /Applications/Zonza.app et un fantome laisse dans la
Corbeille le 13 juin, dont le stub visait l'ancien chemin du depot — valide
jusqu'a aujourd'hui. Les deux etaient donc lancables simultanement. Chaque
instance chargeait son propre modele MLX-Whisper medium et captait la MEME
hotkey globale : un seul appui declenchait deux transcriptions, la machine
saturait.
Le fantome est desenregistre, mais la classe de bug revient au moindre bundle
egare. D'ou un verrou exclusif (flock non bloquant) pris au demarrage : la
seconde instance renonce et sort. Le noyau libere le verrou meme apres un
plantage, donc pas d'orphelin.
Prouve avec le vrai binaire : 2e lancement direct -> "[garde] une autre instance
tourne deja", 1 seul process ; et controle negatif, seule elle demarre bien.
27 tests passent (22 + 5 nouveaux).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AXIsProcessTrustedWithOptions(prompt) : macOS affiche sa popup officielle
et crée l'entrée TCC correctement (l'ajout manuel via « + » ne prenait pas).
Diag de l'état de confiance dans le log.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sur macOS Tahoe (26.5), le NSStatusItem n'est jamais placé (fenêtre h=0) si :
1. l'exécutable du processus n'appartient pas au bundle lancé (le python
framework se ré-exécute → identité dépareillée) → stub C qui dlopen
libpython et exécute app.py DANS le processus du bundle ;
2. l'item est créé par rumps (cause interne non isolée, l'API native
fonctionne) → app.py réécrit en pyobjc pur (NSStatusItem + NSMenu).
Le stub transmet argv à python quand il y en a (workers multiprocessing
de MLX) — sinon bombe à fork (9 instances observées). PYTHONUNBUFFERED
pour que les print atteignent ~/Library/Logs/Zonza.log. Auto-diagnostic
du placement de l'icône au démarrage. rumps retiré des dépendances.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
setActivationPolicy_(Accessory) appelé avant rumps.run() empêche la
création du NSStatusItem sur Tahoe (vérifié par test A/B). Retiré :
LSUIElement dans l'Info.plist suffit pour masquer l'icône Dock.
Le lanceur redirige aussi stdout/stderr vers ~/Library/Logs/Zonza.log.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reconstruction propre de l'ancien projet Dictée. Bundle id neuf
(cloud.mrtechlab.zonza), chemin de build unique vers /Applications
(plus jamais de builds /tmp enregistrés dans LaunchServices).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>