SPB Git

spb/focale Public

Swift 100%
ZIP
NameLast commitUpdated
Design Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
docs Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
Focale Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
FocaleUITests Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
.gitignore Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
CLAUDE.md Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
ExportOptions.plist Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
project.yml Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
README.md Initial release: manual camera + queryable photo library (0.1.0 build... yesterday
README.md

# Focale

Icône Focale — iris d'obturateur à six lamelles

iOS 26.0+ Swift 6 SwiftUI Strict concurrency Build passing UI tests passing TestFlight 0.1.0 (4) 100% on-device

Appareil photo manuel + photothèque interrogeable. SwiftUI, AVFoundation, PhotoKit, Vision, Foundation Models. Deux produits qui se sauvent mutuellement : une photo prise dans Focale arrive déjà comprise, et la vieille bibliothèque se rattrape en arrière-plan pendant que le produit est déjà utile.

Rien ne quitte l'appareil. Jamais. Aucune photo dupliquée, déplacée ou modifiée. Utile avant que l'index soit complet.

Author : Simon-Pierre Boucher — contact@spboucher.ai

La thèse produit, les contraintes API et les règles d'architecture vivent dans CLAUDE.md — le lire avant toute modification.


# Captures d'écran

Appareil Bibliothèque Visionneuse
Écran de capture Bibliothèque Visionneuse
Recettes, projets, retardateur, grille, dials manuels Albums vivants + progression d'indexation honnête Zoom, favoris, « photos semblables », panneau d'info

(Simulateur iPhone 17 Pro — l'aperçu caméra est noir sans matériel photo.)


# Métriques

Métrique Valeur
Fichiers Swift 37
Lignes de code Swift 4 554
Erreurs de build 0
Concurrence Swift 6, SWIFT_STRICT_CONCURRENCY: complete
Cible de déploiement iOS 26.0 (iPhone)
Tests UI 1 suite (régression crash favoris) — ✅ 22,7 s
Builds TestFlight 4 (0.1.0 (1) → (4))
Étapes CLAUDE.md couvertes 1–2 complètes, fondations 3–5
Module LOC Rôle
Capture/ 1 999 session AVFoundation, contrôles manuels, ProRAW, flash, retardateur, recettes, dispositions, gestes
Index/ 676 étage 1 Vision (tout) + étage 2 Foundation Models (candidats seulement)
Library/ 661 timeline, visionneuse plein écran, albums vivants
Search/ 508 requête naturelle → filtre structuré → classement local
Intent/ 341 CaptureContext — le contexte capturé au déclenchement, cœur du projet
App/ 246 entrée, onboarding accès photos (le risque produit n° 1)
FocaleUITests/ 74 test de régression du flux favoris
Design/ 49 tokens, badge « Générée »

# Fonctionnalités

# 📷 Appareil photo (étape 1 — vendable seul)

  • Contrôles manuels complets : ISO, vitesse d'obturation, mise au point, balance des blancs, compensation d'exposition, zoom — chaque contrôle est vérifié contre le matériel avant d'être affiché (aucun bouton mort).
  • ProRAW (iPhone 12 Pro+), HEIC, choix d'objectif (ultra grand-angle / grand-angle / télé).
  • Flash auto / activé / désactivé (affiché seulement si l'appareil a un flash).
  • Retardateur 3 s / 10 s avec compte à rebours annulable, grille des tiers, retour au déclenchement (clignotement d'écran + haptique immédiate).
  • Le déclencheur est sacré : aucune inférence, aucun disque, aucune allocation dans le chemin du déclenchement — réponse < 50 ms visée.
  • Recettes : presets complets nommés, rappelables en un geste, exportables en fichier .focalerecipe et importables — elles se partagent.
  • Dispositions : trois profils (Simple / Photographe / Expert), gestes entièrement réassignables (glissement gauche = ISO, pincement = zoom…), haptique paramétrable par contrôle.
  • Proposition de recette sur signal de scène (basse lumière) — une proposition, jamais un basculement silencieux.

# 🎯 Contexte de capture (étape 2 — la pièce qui différencie tout)

  • Au déclenchement, Focale enregistre sans coût d'inférence : recette active, projet déclaré, réglages manuels, signal de scène (luminosité EV), lieu grossier (jamais de coordonnées brutes).
  • Écrit dans les métadonnées EXIF de la photo (sans ré-encodage) en plus de la base locale : si l'utilisateur désinstalle, l'information reste dans sa photo.
  • Mode Projet : déclare « chantier cuisine » une fois — tout ce qui suit est marqué. Zéro friction.
  • Note de sujet après la prise (« reçu du garage ») — jamais de saisie obligatoire avant une photo.

# 🔍 Indexation & recherche (étapes 3–5)

  • Étage 1 — Vision, sur tout : OCR (le gagnant silencieux : reçus, tableaux blancs, numéros de série), empreintes visuelles (similarité sans LLM), classification, visages (comptage local seulement, jamais d'identification).
  • Étage 2 — Foundation Models, sur candidats seulement : le calcul à ne jamais oublier — 40 000 photos × 1-3 s = 11-33 h en série. L'étage 2 ne touche que les photos qui le méritent (prises dans Focale, favorites, demandées).
  • Du plus récent au plus ancien, toujours. Photos récentes indexées au premier plan dès l'ouverture ; rattrapage profond la nuit, en charge (BGProcessingTask), reprise incrémentale.
  • Recherche naturelle : « le reçu du garage l'automne passé » → filtre structuré exécuté sur la base locale (instantané). Le modèle analyse la requête, jamais les photos. Repli déterministe complet sur appareils sans A17 Pro.
  • Albums vivants : définis par une requête, ils se remplissent tout seuls. Créables depuis n'importe quelle recherche.
  • « Photos semblables » : distance d'empreinte visuelle, zéro LLM.
  • Honnêteté : contenu généré toujours badgé « Générée », l'OCR prime sur le modèle pour tout chiffre, progression d'indexation transparente.

# Architecture

text
Focale/
├─ App/                entrée, onboarding accès photos, racine
├─ Capture/
│  ├─ Session/         AVCaptureSession (file série dédiée), capacités matérielles
│  ├─ Controls/        modèle ISO/vitesse/focus/BB, dials, mapping de gestes
│  ├─ Output/          délégué de capture, écriture PhotoKit + EXIF
│  ├─ Recipes/         presets, gestionnaire, export/import
│  └─ Layout/          profils d'interface Simple/Photographe/Expert
├─ Intent/             CaptureContext, projets, signal de scène, lieu
├─ Index/
│  ├─ Vision/          étage 1 — OCR, feature prints, classification
│  ├─ Semantic/        étage 2 — Foundation Models (@Generable)
│  ├─ Store/           SwiftData (localIdentifier, jamais de doublon)
│  └─ Scheduler/       pipeline + BGProcessingTask (en charge seulement)
├─ Search/             analyse de requête (FM ou déterministe) + classement
├─ Library/            timeline, visionneuse, albums vivants
└─ Design/             tokens, badge « Générée »
FocaleUITests/         tests de régression UI
Design/AppIcon.svg     icône source (rendue en PNG dans Assets.xcassets)

Le projet Xcode est généré par XcodeGen : Focale.xcodeproj est un artefact, la source de vérité est project.yml.


# Build & tests

bash
xcodegen generate        # regénère Focale.xcodeproj depuis project.yml
open Focale.xcodeproj    # build & run (la caméra exige un iPhone physique)

# Tests UI (simulateur)
xcodebuild test -project Focale.xcodeproj -scheme Focale \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro'
  • Xcode 26+, Swift 6 (concurrence stricte), cible iOS 26.0.
  • Foundation Models exige un appareil A17 Pro+ ; partout ailleurs le chemin Vision-only prend le relais automatiquement.

# TestFlight

bash
xcodebuild -project Focale.xcodeproj -scheme Focale -destination 'generic/platform=iOS' \
  -archivePath build/Focale-N.xcarchive archive -allowProvisioningUpdates
open -a Xcode build/Focale-N.xcarchive   # Organizer → Distribute App
Build Contenu
0.1.0 (1) fondation complète : caméra manuelle, contexte, index, recherche
0.1.0 (2) permission caméra, visionneuse, orientation portrait, indexation au premier plan
0.1.0 (3) projets/recettes dans l'UI, export .focalerecipe, note après prise, pincement, favoris, « photos semblables », albums vivants depuis recherche
0.1.0 (4) crashs d'isolation Swift 6 corrigés (favoris, enregistrement de capture, vignettes), Vision résilient, flash, retardateur, grille, retour au déclenchement, test UI de régression

# Leçons Swift 6 (à lire avant de toucher à PhotoKit)

Les closures passées à PHPhotoLibrary.performChanges ou aux handlers de PHImageManager héritent de l'isolation (MainActor ou acteur) du contexte où elles sont créées. PhotoKit les exécute sur sa propre file → dispatch_assert_queue_fail, crash immédiat. Règle du projet : tout bloc destiné à une file de framework est déclaré @Sendable, récupère ses PHAsset à l'intérieur, et sort ses résultats via une boîte @unchecked Sendable. Voir PhotoLibraryWriter.swift pour le patron.


# Icône

Source : Design/AppIcon.svg — iris d'obturateur à six lamelles ambre sur objectif charbon. Pour régénérer le PNG :

bash
mkdir -p /tmp/focale-icon
qlmanage -t -s 1024 -o /tmp/focale-icon Design/AppIcon.svg
cp /tmp/focale-icon/AppIcon.svg.png Focale/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png

⚠️ Le dégradé des lamelles doit rester gradientUnits="userSpaceOnUse" : un dégradé objectBoundingBox sur une <line> (boîte de surface nulle) ne se rend pas, par spécification SVG.


# Règles non négociables

  1. Rien ne quitte l'appareil. Jamais. Aucune escalade cloud, même optionnelle, même sur les métadonnées.
  2. Aucune photo n'est dupliquée, déplacée ou modifiée. Index par localIdentifier ; désinstaller ne fait rien perdre.
  3. Utile avant que l'index soit complet. Résultats progressifs, du plus récent au plus ancien.