SPB Git

Repository Public

9 entries at the root
History

Latest commit Initial release: manual camera + queryable photo library (0.1.0 build 4) 05ead37

NameLast commitUpdated
Design Initial release: manual camera + queryable photo library (0.1.0 build 4)
docs Initial release: manual camera + queryable photo library (0.1.0 build 4)
Focale Initial release: manual camera + queryable photo library (0.1.0 build 4)
FocaleUITests Initial release: manual camera + queryable photo library (0.1.0 build 4)
.gitignore Initial release: manual camera + queryable photo library (0.1.0 build 4)
CLAUDE.md Initial release: manual camera + queryable photo library (0.1.0 build 4)
ExportOptions.plist Initial release: manual camera + queryable photo library (0.1.0 build 4)
project.yml Initial release: manual camera + queryable photo library (0.1.0 build 4)
README.md Initial release: manual camera + queryable photo library (0.1.0 build 4)
README.md View source

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

Clone focale

Anonymous clone and fetch. Pushing requires the owner's personal access token.

Snapshot of main

Files main
Menu

Theme