SPB Git

spb/focale Public

Swift 100%
16.1 KB

# CLAUDE.md — Focale

Appareil photo manuel + photothèque interrogeable. SwiftUI + AVFoundation + PhotoKit + Vision + Foundation Models. Ce fichier est la source de vérité du projet. Le lire avant toute modification.


# 1. Thèse produit

Deux produits qui se sauvent mutuellement.

Un 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.

Ensemble, 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.

La promesse : tout ce que tu photographies à partir d'aujourd'hui est parfaitement retrouvable.

Trois règles non négociables :

  1. 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.
  2. 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.
  3. Utile avant que l'index soit complet. Les résultats arrivent progressivement, du plus récent au plus ancien.

# Positionnement de l'appareil photo

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.


# 2. Contraintes — lire avant de promettre quoi que ce soit

# Ce qui est accessible

Capacité API Note
ISO, obturation, focus, WB manuels AVCaptureDevice verrouillage complet, contrôle continu
Choix d'objectif, zoom AVCaptureDevice.DiscoverySession incl. ultra grand-angle, télé
ProRAW isAppleProRAWSupportedisAppleProRAWEnabled iPhone 12 Pro+, DNG linéaire
RAW Bayer availableRawPhotoPixelFormatTypes vérifier le format, pas garanti partout
Apple Log, ProRes AVCaptureDevice.activeFormat Pro seulement
Bouton Camera Control iPhone 16+ lancement + réglage assignable
Bouton Action iPhone 15 Pro+ lancement direct
Interface de capture 100 % personnalisable aucune contrainte

# Ce qui n'est PAS accessible

  • Mode Nuit. Jamais exposé aux apps tierces. Aucun contournement. Assumer ce déficit dans le positionnement.
  • Mode Portrait complet d'Apple. La profondeur est disponible (AVCaptureDepthDataOutput), le rendu d'Apple ne l'est pas.
  • 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.
  • Remplacer Photos comme app par défaut. Impossible.

# Foundation Models

  • 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.
  • Contexte 4096 / 8192 tokens. On ne passe jamais de longues listes de photos au modèle. Voir §5.
  • 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.
  • Vision on-device (iOS 27) : image en entrée directe dans le prompt.

# PhotoKit — le risque produit le plus sous-estimé

Focale exige .authorized (Accès complet). En accès limité, le produit ne fonctionne pas du tout.

iOS relance périodiquement l'utilisateur pour l'inciter à réduire l'accès. Conséquences obligatoires :

  • L'onboarding explique pourquoi avant d'afficher le dialogue système. Un dialogue nu = permission refusée = installation perdue.
  • Détecter .limited et afficher un écran explicatif dédié, pas un message d'erreur.
  • Le fait que rien ne sorte de l'appareil est l'argument qui débloque cette permission. Le dire à cet endroit précis.

# 3. Architecture

text
Focale/
├─ App/
├─ Capture/
│  ├─ Session/          AVCaptureSession, configuration, cycle de vie
│  ├─ Controls/         ISO, obturation, focus, WB — modèle + gestes
│  ├─ Output/           ProRAW, HEIC, écriture PhotoKit
│  ├─ Recipes/          presets utilisateur  ⚠️ voir §6
│  └─ Layout/           dispositions d'interface personnalisables  ⚠️ voir §6
├─ Intent/              contexte capturé au déclenchement  ⚠️ cœur du projet
├─ Index/
│  ├─ Vision/           étage 1 — OCR, visages, feature prints
│  ├─ Semantic/         étage 2 — Foundation Models, sur candidats seulement
│  ├─ Store/            SwiftData + vecteurs
│  └─ Scheduler/        BGProcessingTask, budget d'énergie
├─ Search/              requête naturelle → filtre → classement
├─ Library/             navigation, albums vivants, timeline
└─ Design/              tokens, typographie, thèmes

# 4. L'appareil photo

# Contexte de capture — la pièce qui différencie tout le projet

Au déclenchement, Focale enregistre bien plus que les EXIF, et sans coût d'inférence :

swift
struct CaptureContext: Codable {
    let recipe: Recipe.ID?          // preset actif
    let project: Project.ID?        // projet déclaré par l'utilisateur
    let subjectHint: String?        // dicté ou tapé avant/après la prise
    let settings: ManualSettings    // ISO, vitesse, objectif, focus
    let scene: SceneSignal          // luminosité, mouvement, distance (LiDAR si dispo)
    let burstRole: BurstRole?       // sélection dans une rafale
    let place: PlaceRef?            // lieu, pas coordonnées brutes
}

Règles :

  • 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.
  • 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.
  • Jamais de saisie obligatoire avant une photo. Le déclencheur doit toujours répondre instantanément. Le contexte s'ajoute autour, jamais devant.

# Règles de fer de la capture

  • Le déclencheur est sacré. Aucune inférence, aucun disque, aucune allocation dans le chemin du déclenchement. Tout est différé.
  • AVCaptureSession configurée hors du main thread, une seule fois. Reconfiguration en beginConfiguration/commitConfiguration.
  • 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.
  • Écriture PhotoKit via PHAssetCreationRequest, dans la photothèque système. Pas de bibliothèque parallèle, pas de sandbox privé.

# 5. L'indexeur — deux étages, obligatoirement

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.

# Étage 1 — Vision framework, sur tout

Rapide, mature, faible coût énergétique. Couvre environ 80 % des besoins de recherche réels.

  • 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.
  • VNGenerateImageFeaturePrintRequest — empreinte visuelle, gratuite. Donne la similarité (« des photos comme celle-ci ») et le regroupement de quasi-doublons sans aucun LLM.
  • VNClassifyImageRequest — classification grossière.
  • Détection de visages — regroupement local uniquement, jamais d'identification nommée sans action explicite de l'utilisateur.

# Étage 2 — Foundation Models, sur candidats seulement

Déclenché uniquement quand :

  • la photo est prise dans Focale (contexte déjà présent → coût minime, valeur maximale) ;
  • l'utilisateur ouvre une photo et demande explicitement ;
  • une recherche ne trouve rien à l'étage 1 et il faut approfondir un sous-ensemble ;
  • la photo est marquée importante (favori, partagée, souvent ouverte).
swift
@Generable
struct PhotoSemantics {
    @Guide(description: "Ce que montre l'image, une phrase, factuelle")
    let gist: String

    @Guide(description: "Type de contenu")
    let kind: PhotoKind   // document, reçu, personne, lieu, objet, écran, nourriture, animal, autre

    @Guide(description: "Éléments cherchables présents dans l'image", .count(0...8))
    let entities: [String]

    @Guide(description: "Vrai si l'image contient une information à conserver (montant, date, code, adresse)")
    let hasActionableInfo: Bool
}

# Ordonnancement

  • Du plus récent au plus ancien. Toujours. Les gens cherchent surtout dans les 2 dernières années.
  • BGProcessingTask : en charge, Wi-Fi, écran éteint. Jamais sur batterie.
  • Reprise incrémentale après interruption, sans jamais repartir de zéro.
  • 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.

# 6. Personnalisation — la promesse « ultra personnalisable »

C'est la raison d'être de l'appareil photo. Elle doit aller plus loin que des sliders.

# Recettes

Un 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).

  • Recettes exportables et importables en fichier. Elles se partagent — c'est un canal d'acquisition gratuit.
  • 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.

# Disposition d'interface

L'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.

  • Mapping des gestes : glissement vertical gauche = ISO, horizontal = exposition, molette = focus… entièrement réassignable.
  • Bouton Camera Control et bouton Action assignables à une recette précise.
  • Retour haptique paramétrable par contrôle (crans à chaque tiers d'IL, par exemple).

# Looks

Rendus appliqués en non-destructif, jamais cuits dans l'original. Courbes, teinte, grain, virage. Toujours réversible, toujours avec l'original intact.

# Règle qui encadre tout ça

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.


# 7. Recherche

  • Entrée en langage naturel. Le modèle analyse la requête (petit prompt, coût dérisoire), il ne parcourt pas les photos.
  • 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.
  • Le classement combine : correspondance OCR, distance de feature print, contexte de capture, récence.
  • Foundation Models n'intervient qu'au bout, pour départager un petit ensemble de candidats si nécessaire.
  • 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 ».

# Albums vivants

Un 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.


# 8. Honnêteté du modèle

  • Une description générée est visuellement distincte d'une donnée réelle (EXIF, OCR, lieu). Toujours.
  • 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.
  • 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.
  • Aucune identification de personne sans action explicite. Aucun regroupement de visages nommé automatiquement.
  • 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.

# 9. Performance & énergie

  • 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.
  • Aperçu à 60 fps sans chute pendant l'indexation. Si l'indexeur tourne, il est suspendu dès l'ouverture de la caméra.
  • File d'inférence à priorité : geste utilisateur > photo qu'on regarde > arrière-plan. Annulation immédiate au changement d'écran.
  • Budget thermique : surveiller ProcessInfo.thermalState, suspendre l'étage 2 dès .fair.
  • Taille de l'index : viser moins de 500 Mo pour 50 000 photos. Vignettes via PhotoKit, jamais stockées en double.

# 10. Conventions de code

  • SwiftUI, iOS 27+, @Observable. Swift 6, concurrence stricte.
  • AVCaptureSession sur sa propre file série, jamais sur le main actor. L'UI de capture est @MainActor.
  • Jamais de String libre en sortie de modèle. Toujours @Generable.
  • Toute capacité matérielle est vérifiée avant d'être exposée. Aucune supposition sur le modèle d'iPhone.
  • Chaque fonctionnalité IA a un chemin de repli Vision-only testé. La suite de tests tourne avec Foundation Models désactivé.
  • Nommage utilisateur en français ; code, commentaires et commits en anglais.

# 11. Ordre de construction

  1. 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.
  2. Contexte de capture + projets. Invisible, mais c'est la fondation de la recherche.
  3. É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.
  4. Recherche naturelle + albums vivants. Le moment « je ne peux plus revenir à Photos ».
  5. Étage 2 Foundation Models sur candidats.
  6. Personnalisation avancée : dispositions, mapping de gestes, looks, partage de recettes.

# 12. Risques connus

Risque Réalité
Permission Accès complet refusée Le risque numéro un. L'onboarding est un chantier produit à part entière, pas un écran
Mode Nuit inaccessible Ne jamais se positionner sur la basse lumière. Contrôle et intention
20-30 h d'indexation initiale Transparence + valeur immédiate sur les photos récentes. Jamais d'attente opaque
Marché des apps photo saturé La photothèque est la différence. L'appareil photo seul ne suffit pas à se démarquer
A17 Pro requis Le mode Vision-only doit être un bon produit à part entière
Batterie et chaleur Une app photo qui chauffe pendant qu'on shoote est désinstallée le jour même
Attentes sur la reconnaissance de personnes Terrain sensible. Rester en retrait volontairement, et le dire