spb/focale Public
Swift 100%
1# CLAUDE.md — Focale23Appareil photo manuel + photothèque interrogeable. SwiftUI + AVFoundation + PhotoKit + Vision + Foundation Models.4Ce fichier est la source de vérité du projet. Le lire avant toute modification.56---78## 1. Thèse produit910**Deux produits qui se sauvent mutuellement.**1112Un appareil photo pro seul, c'est un marché saturé (Halide, Kino, Obscura, Moment). Une photothèque IA seule, c'est 40 000 photos à rattraper — des dizaines d'heures d'inférence avant la première valeur.1314Ensemble, ils règlent le problème de l'autre : **une photo prise dans Focale arrive déjà comprise.** Le contexte est capturé au moment du déclenchement — intention, sujet, projet, réglages, lieu — gratuitement et sans ambiguïté. Pas de devinette, pas d'inférence coûteuse. La vieille bibliothèque se rattrape tranquillement en arrière-plan pendant que le produit est déjà utile.1516La promesse : *tout ce que tu photographies à partir d'aujourd'hui est parfaitement retrouvable.*1718Trois règles non négociables :19201. **Rien ne quitte l'appareil. Jamais.** La photothèque de quelqu'un est ce qu'il possède de plus intime. Aucune escalade cloud, même optionnelle, même sur les métadonnées. C'est l'argument de vente central — le renier une fois tue le produit.212. **Aucune photo n'est dupliquée, déplacée ou modifiée.** Focale indexe la photothèque système par `localIdentifier`. L'utilisateur peut désinstaller demain sans rien perdre.223. **Utile avant que l'index soit complet.** Les résultats arrivent progressivement, du plus récent au plus ancien.2324### Positionnement de l'appareil photo2526**Contrôle et intention, jamais « meilleures photos ».** Le mode Nuit n'est pas accessible aux apps tierces (voir §2) — en basse lumière, l'app Camera d'Apple gagnera toujours. Ne jamais promettre le contraire. Focale s'adresse à qui veut décider, pas à qui veut que ça marche tout seul.2728---2930## 2. Contraintes — lire avant de promettre quoi que ce soit3132### Ce qui est accessible3334| Capacité | API | Note |35|---|---|---|36| ISO, obturation, focus, WB manuels | `AVCaptureDevice` | verrouillage complet, contrôle continu |37| Choix d'objectif, zoom | `AVCaptureDevice.DiscoverySession` | incl. ultra grand-angle, télé |38| ProRAW | `isAppleProRAWSupported` → `isAppleProRAWEnabled` | iPhone 12 Pro+, DNG linéaire |39| RAW Bayer | `availableRawPhotoPixelFormatTypes` | vérifier le format, pas garanti partout |40| Apple Log, ProRes | `AVCaptureDevice.activeFormat` | Pro seulement |41| Bouton Camera Control | iPhone 16+ | lancement + réglage assignable |42| Bouton Action | iPhone 15 Pro+ | lancement direct |43| Interface de capture | 100 % personnalisable | aucune contrainte |4445### Ce qui n'est PAS accessible4647- **Mode Nuit.** Jamais exposé aux apps tierces. Aucun contournement. Assumer ce déficit dans le positionnement.48- **Mode Portrait complet** d'Apple. La profondeur est disponible (`AVCaptureDepthDataOutput`), le rendu d'Apple ne l'est pas.49- **Deep Fusion / Smart HDR** s'appliquent dans le pipeline standard, mais sans contrôle fin. Le contourner est un *choix* de positionnement, comme Halide — pas un défaut à cacher.50- **Remplacer Photos comme app par défaut.** Impossible.5152### Foundation Models5354- **A17 Pro minimum.** Sur appareil non compatible, Focale reste un excellent appareil photo manuel avec une recherche Vision-only. **Ce mode dégradé doit être bon, pas frustrant.**55- Contexte 4096 / 8192 tokens. On ne passe jamais de longues listes de photos au modèle. Voir §5.56- **Inférence sérialisée sur le Neural Engine.** Les sessions parallèles s'exécutent en série. C'est LA contrainte du projet.57- Vision on-device (iOS 27) : image en entrée directe dans le prompt.5859### PhotoKit — le risque produit le plus sous-estimé6061Focale exige `.authorized` (**Accès complet**). En accès limité, le produit ne fonctionne pas du tout.6263iOS relance périodiquement l'utilisateur pour l'inciter à réduire l'accès. Conséquences obligatoires :6465- L'onboarding explique **pourquoi** avant d'afficher le dialogue système. Un dialogue nu = permission refusée = installation perdue.66- Détecter `.limited` et afficher un écran explicatif dédié, pas un message d'erreur.67- Le fait que rien ne sorte de l'appareil est l'argument qui débloque cette permission. Le dire à cet endroit précis.6869---7071## 3. Architecture7273```74Focale/75├─ App/76├─ Capture/77│ ├─ Session/ AVCaptureSession, configuration, cycle de vie78│ ├─ Controls/ ISO, obturation, focus, WB — modèle + gestes79│ ├─ Output/ ProRAW, HEIC, écriture PhotoKit80│ ├─ Recipes/ presets utilisateur ⚠️ voir §681│ └─ Layout/ dispositions d'interface personnalisables ⚠️ voir §682├─ Intent/ contexte capturé au déclenchement ⚠️ cœur du projet83├─ Index/84│ ├─ Vision/ étage 1 — OCR, visages, feature prints85│ ├─ Semantic/ étage 2 — Foundation Models, sur candidats seulement86│ ├─ Store/ SwiftData + vecteurs87│ └─ Scheduler/ BGProcessingTask, budget d'énergie88├─ Search/ requête naturelle → filtre → classement89├─ Library/ navigation, albums vivants, timeline90└─ Design/ tokens, typographie, thèmes91```9293---9495## 4. L'appareil photo9697### Contexte de capture — la pièce qui différencie tout le projet9899Au déclenchement, Focale enregistre bien plus que les EXIF, et **sans coût d'inférence** :100101```swift102struct CaptureContext: Codable {103 let recipe: Recipe.ID? // preset actif104 let project: Project.ID? // projet déclaré par l'utilisateur105 let subjectHint: String? // dicté ou tapé avant/après la prise106 let settings: ManualSettings // ISO, vitesse, objectif, focus107 let scene: SceneSignal // luminosité, mouvement, distance (LiDAR si dispo)108 let burstRole: BurstRole? // sélection dans une rafale109 let place: PlaceRef? // lieu, pas coordonnées brutes110}111```112113Règles :114115- **Le contexte est écrit dans les métadonnées de l'actif** en plus de la base locale. Si l'utilisateur désinstalle, l'information reste dans sa photo.116- **Le mode Projet** : l'utilisateur déclare « chantier cuisine », « voyage Gaspésie », « inventaire assurance ». Tout ce qui est pris ensuite est marqué. Un geste, une fois — puis zéro friction.117- **Jamais de saisie obligatoire avant une photo.** Le déclencheur doit toujours répondre instantanément. Le contexte s'ajoute autour, jamais devant.118119### Règles de fer de la capture120121- **Le déclencheur est sacré.** Aucune inférence, aucun disque, aucune allocation dans le chemin du déclenchement. Tout est différé.122- `AVCaptureSession` configurée hors du main thread, une seule fois. Reconfiguration en `beginConfiguration`/`commitConfiguration`.123- Toujours interroger les capacités avant d'exposer un contrôle : `isAppleProRAWSupported`, `activeFormat.maxISO`, plages d'exposition. **Aucun contrôle affiché s'il n'est pas supporté par l'appareil** — pas de bouton mort.124- Écriture PhotoKit via `PHAssetCreationRequest`, dans la photothèque système. Pas de bibliothèque parallèle, pas de sandbox privé.125126---127128## 5. L'indexeur — deux étages, obligatoirement129130**Le calcul à ne jamais oublier : 40 000 photos × 1 à 3 s d'inférence = 11 à 33 heures, en série.** Une passe Foundation Models sur toute la bibliothèque est impossible. L'architecture entière découle de ça.131132### Étage 1 — Vision framework, sur tout133134Rapide, mature, faible coût énergétique. Couvre environ 80 % des besoins de recherche réels.135136- `VNRecognizeTextRequest` — OCR. **Le gagnant silencieux** : reçus, factures, tableaux blancs, captures d'écran, panneaux, cartes d'affaires. C'est ce que les gens cherchent le plus et que Photos trouve le plus mal.137- `VNGenerateImageFeaturePrintRequest` — empreinte visuelle, gratuite. Donne la similarité (« des photos comme celle-ci ») et le regroupement de quasi-doublons sans aucun LLM.138- `VNClassifyImageRequest` — classification grossière.139- Détection de visages — **regroupement local uniquement, jamais d'identification nommée sans action explicite de l'utilisateur.**140141### Étage 2 — Foundation Models, sur candidats seulement142143Déclenché uniquement quand :144- la photo est prise dans Focale (contexte déjà présent → coût minime, valeur maximale) ;145- l'utilisateur ouvre une photo et demande explicitement ;146- une recherche ne trouve rien à l'étage 1 et il faut approfondir un sous-ensemble ;147- la photo est marquée importante (favori, partagée, souvent ouverte).148149```swift150@Generable151struct PhotoSemantics {152 @Guide(description: "Ce que montre l'image, une phrase, factuelle")153 let gist: String154155 @Guide(description: "Type de contenu")156 let kind: PhotoKind // document, reçu, personne, lieu, objet, écran, nourriture, animal, autre157158 @Guide(description: "Éléments cherchables présents dans l'image", .count(0...8))159 let entities: [String]160161 @Guide(description: "Vrai si l'image contient une information à conserver (montant, date, code, adresse)")162 let hasActionableInfo: Bool163}164```165166### Ordonnancement167168- **Du plus récent au plus ancien.** Toujours. Les gens cherchent surtout dans les 2 dernières années.169- `BGProcessingTask` : en charge, Wi-Fi, écran éteint. Jamais sur batterie.170- Reprise incrémentale après interruption, sans jamais repartir de zéro.171- **Honnêteté dans l'interface** : « 12 400 / 41 000 photos indexées — les plus récentes sont déjà cherchables. Prêt dans environ 2 nuits. » Avec des résultats utilisables tout de suite. Un écran de chargement opaque de 20 heures est une désinstallation garantie.172173---174175## 6. Personnalisation — la promesse « ultra personnalisable »176177C'est la raison d'être de l'appareil photo. Elle doit aller plus loin que des sliders.178179### Recettes180181Un preset complet, nommé, rappelable en un geste : réglages manuels, format (ProRAW/HEIC), objectif, look, comportement du déclencheur, contexte à appliquer. Exemples : « Documents » (correction de perspective, OCR prioritaire), « Nuit longue pose », « Chantier » (projet auto, grand-angle, horodatage visible).182183- Recettes exportables et importables en fichier. Elles se partagent — c'est un canal d'acquisition gratuit.184- Une recette peut se déclencher automatiquement sur signal de scène (basse lumière, document détecté), **avec une proposition, jamais un basculement silencieux.**185186### Disposition d'interface187188L'utilisateur place ses propres contrôles : quels réglages sont visibles, où, à quelle taille, quels gestes les pilotent. Trois profils fournis (Simple / Photographe / Expert), tous modifiables.189190- **Mapping des gestes** : glissement vertical gauche = ISO, horizontal = exposition, molette = focus… entièrement réassignable.191- **Bouton Camera Control et bouton Action** assignables à une recette précise.192- Retour haptique paramétrable par contrôle (crans à chaque tiers d'IL, par exemple).193194### Looks195196Rendus appliqués **en non-destructif**, jamais cuits dans l'original. Courbes, teinte, grain, virage. Toujours réversible, toujours avec l'original intact.197198### Règle qui encadre tout ça199200**Les valeurs par défaut doivent être excellentes.** La personnalisation est une profondeur pour ceux qui la cherchent, pas un devoir à l'installation. Le premier lancement doit donner une bonne photo en trois secondes, sans réglage. Une app qui exige d'être configurée avant d'être bonne perd la majorité de ses utilisateurs le premier jour.201202---203204## 7. Recherche205206- Entrée en langage naturel. Le modèle **analyse la requête** (petit prompt, coût dérisoire), il ne parcourt pas les photos.207- La requête est convertie en filtre structuré : période, lieu, type, entités, texte OCR, similarité visuelle. Le filtre s'exécute sur la base locale — instantané, sur 40 000 photos comme sur 400.208- Le classement combine : correspondance OCR, distance de feature print, contexte de capture, récence.209- **Foundation Models n'intervient qu'au bout**, pour départager un petit ensemble de candidats si nécessaire.210- Requêtes cibles à faire fonctionner parfaitement (à tester en continu) : « le reçu du garage l'automne passé », « la photo du tableau blanc de la réunion budget », « où j'ai stationné la semaine passée », « les photos comme celle-ci », « le numéro de série de la laveuse ».211212### Albums vivants213214Un album défini par une requête, pas par une sélection. « Tous mes reçus 2026 », « chantier cuisine », « documents avec une date d'expiration ». Il se remplit tout seul à mesure que tu photographies.215216---217218## 8. Honnêteté du modèle219220- **Une description générée est visuellement distincte d'une donnée réelle** (EXIF, OCR, lieu). Toujours.221- **L'OCR prime sur le modèle** pour tout ce qui est chiffre, date, montant, code. Le modèle 3B peut halluciner un montant ; l'OCR non. Ne jamais afficher un chiffre venu du LLM comme un fait.222- Sur incertitude, ne rien dire. Une photo mal comprise vaut mieux qu'une photo faussement étiquetée — l'utilisateur perdrait confiance dans tout l'index.223- Aucune identification de personne sans action explicite. Aucun regroupement de visages nommé automatiquement.224- Les garde-fous du modèle peuvent refuser des images légitimes. Traiter le refus comme un état normal : la photo reste indexée par l'étage 1, sans message d'erreur.225226---227228## 9. Performance & énergie229230- **Le déclencheur répond en moins de 50 ms, toujours.** C'est la métrique numéro un du produit. Mesurée à chaque build.231- Aperçu à 60 fps sans chute pendant l'indexation. Si l'indexeur tourne, il est suspendu dès l'ouverture de la caméra.232- File d'inférence à priorité : geste utilisateur > photo qu'on regarde > arrière-plan. Annulation immédiate au changement d'écran.233- Budget thermique : surveiller `ProcessInfo.thermalState`, suspendre l'étage 2 dès `.fair`.234- Taille de l'index : viser moins de 500 Mo pour 50 000 photos. Vignettes via PhotoKit, jamais stockées en double.235236---237238## 10. Conventions de code239240- SwiftUI, iOS 27+, `@Observable`. Swift 6, concurrence stricte.241- `AVCaptureSession` sur sa propre file série, jamais sur le main actor. L'UI de capture est `@MainActor`.242- **Jamais de `String` libre en sortie de modèle.** Toujours `@Generable`.243- Toute capacité matérielle est vérifiée avant d'être exposée. Aucune supposition sur le modèle d'iPhone.244- Chaque fonctionnalité IA a un chemin de repli Vision-only testé. La suite de tests tourne avec Foundation Models désactivé.245- Nommage utilisateur en français ; code, commentaires et commits en anglais.246247---248249## 11. Ordre de construction2502511. **Appareil photo manuel excellent.** Contrôles, ProRAW, recettes, écriture PhotoKit. Zéro IA. Vendable seul dès cette étape — et c'est le test : si personne ne le veut ici, l'IA ne le sauvera pas.2522. **Contexte de capture + projets.** Invisible, mais c'est la fondation de la recherche.2533. **Étage 1 Vision sur les nouvelles photos**, puis rétroactif. L'OCR d'abord : c'est le gain le plus spectaculaire pour l'effort le plus faible.2544. **Recherche naturelle + albums vivants.** Le moment « je ne peux plus revenir à Photos ».2555. **Étage 2 Foundation Models** sur candidats.2566. **Personnalisation avancée** : dispositions, mapping de gestes, looks, partage de recettes.257258---259260## 12. Risques connus261262| Risque | Réalité |263|---|---|264| Permission Accès complet refusée | Le risque numéro un. L'onboarding est un chantier produit à part entière, pas un écran |265| Mode Nuit inaccessible | Ne jamais se positionner sur la basse lumière. Contrôle et intention |266| 20-30 h d'indexation initiale | Transparence + valeur immédiate sur les photos récentes. Jamais d'attente opaque |267| Marché des apps photo saturé | La photothèque est la différence. L'appareil photo seul ne suffit pas à se démarquer |268| A17 Pro requis | Le mode Vision-only doit être un bon produit à part entière |269| Batterie et chaleur | Une app photo qui chauffe pendant qu'on shoote est désinstallée le jour même |270| Attentes sur la reconnaissance de personnes | Terrain sensible. Rester en retrait volontairement, et le dire |271