# CLAUDE.md — Prisme Navigateur iOS intelligent. SwiftUI + WebKit + Foundation Models. Ce fichier est la source de vérité du projet. Le lire avant toute modification. --- ## 1. Thèse produit **Un navigateur n'est pas un afficheur de pages. C'est un lecteur qui comprend ce qu'il affiche.** Le web moderne est hostile : bannières, murs de consentement, 2000 mots de remplissage SEO pour une réponse de 40 mots, patterns manipulateurs, pistage. Les navigateurs actuels rendent fidèlement cette hostilité. Prisme s'interpose : chaque page est comprise localement avant d'être affichée, puis re-présentée selon l'intention de l'utilisateur. Trois règles non négociables : 1. **Rien ne quitte l'appareil par défaut.** Le contenu des pages est traité on-device. L'escalade vers Private Cloud Compute est explicite, visible, et jamais automatique sur du contenu marqué sensible. 2. **Utile pour un seul utilisateur, dès la première session.** Aucune fonctionnalité ne dépend d'un effet de réseau, d'un compte, ou d'un serveur qu'on opère. 3. **Le modèle ne remplace jamais la page.** Il l'augmente, la résume, la range. L'utilisateur peut toujours accéder au HTML brut en un geste. Une hallucination ne doit jamais être indiscernable du contenu réel — voir §7. ### Ce que Prisme n'est pas - Pas un chatbot avec une webview collée à côté (Dia, Comet, Atlas occupent déjà ce terrain). - Pas un agent qui navigue à ta place pendant que tu regardes. L'agent existe (§6) mais il est en arrière-plan, pas au centre. - Pas un navigateur qui demande de changer ses habitudes avant de donner de la valeur. Arc est mort de ça. --- ## 2. Stack & contraintes | Élément | Choix | Note | |---|---|---| | UI | SwiftUI, iOS 27+ | `@Observable`, pas d'`ObservableObject` | | Moteur web | `WKWebView` | pas d'alternative viable sur iOS | | IA locale | `FoundationModels` | `SystemLanguageModel.default` | | IA lourde | `PrivateCloudComputeLanguageModel` | 32K contexte, quota par utilisateur | | Persistance | SwiftData | + fichiers pour les snapshots texte | | Recherche | Index vectoriel local | voir §5 | | Tâches fond | `BGTaskScheduler` | favoris vivants, veilles | | Intégrations | App Intents | export vers Notes/Rappels/Calendrier | ### Contraintes matérielles à respecter partout - **Foundation Models exige A17 Pro ou plus récent.** Sur iPhone 14 et antérieur, `SystemLanguageModel.default.availability` renvoie indisponible. **Toute fonctionnalité IA doit dégrader proprement** : Prisme reste un excellent navigateur sans IA. Jamais d'écran d'erreur, jamais de bouton mort. - **Contexte : 4096 tokens (iOS 26) / 8192 (iOS 27, appareils récents).** C'est la contrainte structurante du projet. Une page web fait couramment 30 000 tokens. Voir §4 : on ne passe jamais une page brute au modèle. - **L'inférence est sérialisée sur le Neural Engine.** Des sessions parallèles sont permises par l'API mais s'exécutent en série. Budgéter en conséquence : jamais plus de 2 requêtes en vol, file d'attente avec priorité. - **Batterie.** L'inférence continue tue un téléphone. Toute tâche non déclenchée par l'utilisateur passe par le budget d'énergie (§8). ### Vérifier avant de coder `contextSize` et `tokenCount(for:)` existent depuis iOS 26.4. **Toujours** faire un preflight de tokens avant d'appeler le modèle — l'échec de dépassement est abrupt et tue la session. ```swift let model = SystemLanguageModel.default let budget = try await model.contextSize let cost = try await model.tokenCount(for: prompt) guard cost < budget - reserveForResponse else { /* condenser */ } ``` Réserver systématiquement ~30 % du contexte pour la réponse. Une erreur classique : un prompt à 4092 tokens échoue quand même, parce que le modèle n'a plus la place de répondre. --- ## 3. Architecture ``` Prisme/ ├─ App/ point d'entrée, scènes, raccourcis ├─ Browser/ │ ├─ Engine/ WKWebView, pool, delegates, règles de contenu │ ├─ Tabs/ modèle d'onglets, groupes, sessions │ └─ Chrome/ barre d'adresse, gestes, navigation ├─ Intelligence/ │ ├─ Router/ choix du modèle (on-device / PCC / aucun) │ ├─ Distiller/ HTML → structure compacte ⚠️ cœur du projet │ ├─ Schemas/ tous les types @Generable │ ├─ Tools/ Tool protocol : accès onglets, historique, page │ └─ Sessions/ gestion du cycle de vie, reprise après dépassement ├─ Memory/ │ ├─ Index/ index sémantique local │ ├─ Snapshots/ versions texte des pages visitées │ └─ Recall/ recherche floue, rappels proactifs ├─ Library/ favoris, extraits, collections ├─ Privacy/ conteneurs d'identité, blocage, détection de patterns └─ Design/ tokens, typographie, thèmes sémantiques ``` ### Le routeur (`Intelligence/Router`) Chaque tâche déclare son niveau. Le routeur choisit. Aucun appel direct au modèle ailleurs dans le code. ```swift enum Tier { case none // heuristique pure, zéro IA case local // on-device, gratuit, illimité, hors ligne case cloud // PCC : 32K, raisonnement — quota utilisateur, consentement } ``` Règles de routage : - **Tout ce qui est fréquent va en `local`.** Classification, extraction, résumé de section, titre d'onglet, tag de favori. C'est gratuit et illimité — c'est là qu'est l'avantage concurrentiel : un concurrent sur API cloud ne peut pas se permettre ce volume. - **`cloud` uniquement sur action explicite** : comparaison multi-onglets, question complexe, synthèse d'un long document. Toujours avec une indication visuelle que ça sort de l'appareil. - **`none` dès qu'une heuristique suffit.** Ne pas appeler un LLM pour détecter un mur de cookies : un sélecteur CSS le fait mieux, en 0 ms, avec 0 % d'hallucination. **Un LLM n'est pas une réponse à tout — c'est le dernier recours, pas le premier.** - Contenu marqué sensible (santé, finance, tout ce qui est dans un conteneur privé) : `cloud` interdit, sans exception. --- ## 4. Le Distiller — pièce centrale **Problème : une page fait 30 000 tokens, le modèle en accepte 8 000.** Tout le projet tient sur la qualité de cette réduction. Pipeline, dans l'ordre. Chaque étape est déterministe sauf la dernière. 1. **Extraction DOM** (JS injecté, `WKUserScript` au `documentEnd`) — retire nav, footer, pub, scripts, commentaires. Algorithme type Readability, en dur, pas d'IA. 2. **Structuration** — produit un arbre de blocs typés : titre, section, paragraphe, code, tableau, image, formulaire. Conserve les offsets DOM pour pouvoir remonter à l'élément d'origine (indispensable pour §5 et le zoom sémantique). 3. **Budgétisation** — mesure les tokens par bloc, alloue le budget par importance (profondeur de titre, position, densité de liens). 4. **Condensation hiérarchique** — si dépassement : résumer les sections les moins prioritaires en `local`, garder intactes les prioritaires. Jamais de troncature brutale au milieu d'une phrase. 5. **Sortie structurée** — `@Generable`, jamais de texte libre à parser. ```swift @Generable struct PageDigest { @Guide(description: "Type de page") let kind: PageKind // article, doc, forum, boutique, appli, portail, formulaire @Guide(description: "Réponse à la question implicite de la page, max 2 phrases") let gist: String @Guide(description: "Sections dans l'ordre du document", .count(3...12)) let outline: [Section] @Guide(description: "Affirmations chiffrées ou datées, avec leur offset DOM") let claims: [Claim] let hostility: HostilityReport // voir §8 } ``` ### Règles de fer du Distiller - **Ne jamais envoyer de HTML brut au modèle.** Ça brûle le contexte en balises et dégrade la qualité. - **Toujours conserver l'offset DOM** de chaque élément produit. Sans ça, impossible de lier un résumé à sa source — et donc impossible de vérifier une hallucination. C'est la contrainte la plus facile à oublier et la plus coûteuse à rattraper. - **Cache agressif.** Un digest est indexé par hash du contenu extrait. Une même page ne doit jamais être distillée deux fois. Les mêmes pages reviennent constamment. - **Le digest est un artefact durable**, pas un intermédiaire jetable : il alimente l'historique (§5), les favoris (§5) et le diff temporel. --- ## 5. Fonctionnalités par domaine Priorité : `P0` = MVP, `P1` = v1, `P2` = après. ### Affichage - **`P0` Zoom sémantique.** Le pincement ne change pas la taille du texte : il change le **niveau de détail**. Écarté au max = page complète. Pincé d'un cran = paragraphes condensés. Deux crans = plan de la page. Trois = une phrase. Le geste le plus familier de l'iPhone, remappé sur la compréhension. *C'est la fonctionnalité signature. Si une seule chose doit être parfaite, c'est celle-là.* Toutes les transitions sont interpolées, pas des sauts d'écran — l'utilisateur doit voir le texte se contracter. - **`P0` Rendu adaptatif par type.** Le `kind` du digest sélectionne un gabarit SwiftUI natif. Un article devient une vraie page de lecture ; une doc technique garde son code et sa nav ; un forum devient un fil hiérarchisé. Pas un « mode lecture » unique appliqué de force. - **`P1` Barre de défilement sémantique.** La scrollbar devient une carte de la page : sections nommées, position des tableaux/code/images. On saute à une idée, pas à un pourcentage. - **`P1` Thème sémantique.** Le mode sombre n'inverse pas les couleurs : le modèle attribue un rôle à chaque bloc (corps, citation, code, avertissement) et applique le thème natif de Prisme. Fin des sites illisibles la nuit. - **`P1` Tiroir du bruit.** Tout ce qui a été retiré est empilé dans un tiroir consultable. Transparence totale : l'utilisateur voit ce que la page voulait lui faire faire. Aussi une soupape de sécurité quand l'extraction rate. - **`P2` Diff temporel.** Snapshot texte à chaque visite. Au retour : ce qui a changé est surligné. Puissant sur les pages de prix, les politiques, les docs. - **`P2` Lecture d'images.** Vision on-device (iOS 27) : un graphique en image, une capture d'écran, un menu en photo deviennent du texte interrogeable. ### Onglets - **`P0` Regroupement par intention.** Les onglets se rangent par ce que tu es en train de faire (« tu magasines un vélo », « tu débogues du Swift »), pas par domaine. Le regroupement est **proposé, jamais imposé** — un onglet qui bouge tout seul est une trahison. - **`P0` Reprise narrative.** À la réouverture : pas 47 vignettes, un paragraphe. « Tu comparais trois assurances. Tu avais retenu X. Il te restait à vérifier les franchises. » - **`P1` Onglets périssables.** Le modèle estime la durée de vie utile de chaque onglet. Une recette meurt à la fermeture ; une doc de travail survit. Purge proposée, jamais silencieuse. - **`P2` Préchargement spéculatif.** Les 2-3 liens les plus probables sont préchargés et pré-distillés. Le clic devient instantané. **Plafonné par le budget d'énergie (§8)** et désactivé sur données cellulaires. ### Favoris — à repenser complètement Le favori est une relique de 1995 : un pointeur vers une URL, qui pourrit. On le remplace par quatre objets. - **`P0` L'extrait.** On sélectionne un paragraphe, c'est *ça* qui est sauvé — avec sa source, sa date, et son contexte de section. La plupart du temps on ne veut pas la page, on veut le passage. - **`P0` Le favori structuré.** Une recette sauvée devient ingrédients + étapes en données natives (guided generation), pas une page. Une fiche produit devient prix + specs + vendeur. Le contenu est libéré de sa mise en page. - **`P1` Le favori vivant.** Le favori surveille sa page en tâche de fond et notifie au changement : prix, disponibilité, mise à jour d'une doc, modification d'une politique. **La fonctionnalité la plus vendeuse du lot** — un favori qui travaille pour toi. - **`P1` Le favori-question.** On enregistre « combien coûte le renouvellement du passeport » plutôt qu'une URL. Si l'URL meurt, le favori se re-résout tout seul. Immunisé contre le lien mort. - **`P2` Collections émergentes.** Quand un thème apparaît dans les sauvegardes, Prisme propose une collection. Suggestion, jamais action automatique. - **`P2` Purge honnête.** « 340 favoris jamais rouverts depuis 2 ans. » Proposition d'archivage groupé. ### Mémoire - **`P0` Historique sémantique.** Chaque page visitée est distillée et indexée localement. Recherche en langage naturel : « le site avec la recette de ramen vu au printemps ». C'est ici que le modèle local gratuit écrase toute solution cloud : le volume serait impayable en API, et l'intimité de l'historique rend l'envoi hors appareil inacceptable. - **`P1` Ligne du temps de sujet.** Toutes les visites autour d'un thème, regroupées chronologiquement. On retrace une recherche étalée sur des semaines. - **`P1` Rappel proactif.** Retour sur une fiche produit : « vu en mars, c'était 899 $ ». Silencieux, une ligne, jamais un popup. ### Vie privée - **`P0` Conteneurs d'identité.** Travail / perso / magasinage / recherche sensible. Cookies, sessions et empreinte entièrement cloisonnés via `WKWebsiteDataStore(forIdentifier:)`. Changement d'univers en un geste. Un site ne peut pas relier tes vies. - **`P0` Blocage de contenu.** `WKContentRuleList` compilé, mis à jour, mesurable. Non négociable pour la performance et la vie privée. - **`P1` Détecteur de patterns manipulateurs.** Faux compte à rebours, désabonnement caché, prix barré mensonger, consentement pré-coché : nommés à l'écran. Éducatif et défensif. - **`P1` Traducteur de conditions.** Le mur de cookies et les CGU résumés en trois lignes **avant** d'accepter, avec ce qui est réellement cédé. ### Entrée - **`P0` Barre à intention.** Un champ unique qui distingue URL, recherche, question, et commande. Il ne devine pas en silence : il propose l'interprétation, l'utilisateur confirme d'un geste. - **`P1` Export structuré.** Une page → Rappels, Calendrier, Notes, avec les bons champs remplis (App Intents). Une page d'événement devient une entrée d'agenda correcte, pas un lien. - **`P2` Écoute.** Le digest lu à voix haute. Utile en déplacement, et c'est la seule façon d'« emporter » un long article. --- ## 6. Agent (`P2` — pas avant que le reste soit excellent) Portée volontairement étroite. Un agent qui échoue une fois sur cinq est pire qu'aucun agent. Autorisé : comparer des pages déjà ouvertes ; surveiller des favoris vivants ; extraire et remplir un formulaire avec des données confirmées par l'utilisateur. **Interdit :** toute action irréversible sans confirmation explicite — achat, envoi, suppression, publication. Aucune exception, aucun mode « expert » qui la contourne. Implémentation par `Tool` : ```swift struct OpenTabsTool: Tool { let name = "lire_onglets_ouverts" let description = "Retourne le digest des onglets actuellement ouverts" @Generable struct Arguments { @Guide(description: "Filtre optionnel sur le titre ou le domaine") let filter: String? } func call(arguments: Arguments) async throws -> String { // Retourner les DIGESTS, jamais le HTML. // Plafonner : 3 onglets max, ~400 tokens chacun. } } ``` Règle : **un outil retourne toujours du contenu déjà budgété.** Un outil qui renvoie une page entière fait exploser le contexte et tue la session. --- ## 7. Honnêteté du modèle Non négociable. Un navigateur qui invente est un navigateur inutilisable. - **Distinction visuelle permanente** entre le contenu de la page et le contenu généré. Un traitement typographique et chromatique dédié, cohérent partout. Jamais de texte généré qui ressemble au texte source. - **Tout résumé est cliquable vers sa source** dans le DOM. C'est à ça que servent les offsets du §4. Un résumé sans ancre ne s'affiche pas. - **Sur incertitude, on dit qu'on ne sait pas.** Pas de comblement. Le modèle 3B ne connaît pas le monde — il traite le texte qu'on lui donne. Toute question dépassant la page doit être routée ou refusée, jamais devinée. - **Un geste, toujours disponible, ramène la page brute.** Si l'utilisateur ne fait pas confiance à ce qu'il voit, il doit pouvoir vérifier en une seconde. - Les garde-fous de Foundation Models peuvent refuser un contenu légitime (faux positifs, améliorés en iOS 26.4 mais présents). Gérer le refus comme un état normal : afficher la page brute, sans message d'erreur alarmant. --- ## 8. Performance & énergie Un navigateur se juge d'abord sur la vitesse d'affichage. **L'IA ne doit jamais retarder le rendu.** - La page s'affiche immédiatement. La distillation démarre après `didFinish`, en priorité basse, et l'enrichissement arrive progressivement. - Pool de `WKWebView` réutilisées. Ne jamais en instancier une par onglet. - Budget d'énergie global : compteur d'inférences par heure, seuil bas en mode économie, préchargement spéculatif coupé en premier, tâches de fond suspendues sous 20 % de batterie. - File d'inférence à priorité : geste utilisateur > page active > arrière-plan. Annulation immédiate si l'utilisateur quitte l'onglet. - Cible : **aucune régression perceptible du temps d'affichage** contre Safari. Mesurée à chaque build. --- ## 9. Conventions de code - SwiftUI uniquement. `@Observable`. Pas de Combine sauf pour les ponts WebKit. - Swift 6, concurrence stricte. Les états d'onglet sont `@MainActor`. L'inférence et la distillation sont hors du main actor. - `WKWebView` enveloppée dans `UIViewRepresentable`, une seule fois, dans `Browser/Engine`. Aucun accès direct ailleurs. - **Jamais de `String` libre en sortie de modèle.** Toujours `@Generable`. Une réponse à parser au regex est un bug. - Le JS injecté vit dans des fichiers `.js` versionnés, jamais dans des chaînes Swift. - Chaque fonctionnalité IA a un chemin de repli non-IA testé. La suite de tests tourne avec le modèle désactivé. - Nommage utilisateur en français ; code, commentaires et commits en anglais. - **Chaque fichier de code** (Swift, JS, YAML…) commence par un en-tête d'auteur : nom du fichier, projet, puis `Author: Simon-Pierre Boucher `. (Exception : JSON, qui n'accepte pas de commentaires.) --- ## 10. Ordre de construction Ne pas dévier. Chaque étape doit être solide avant la suivante. 1. **Navigateur nu, excellent.** Onglets, gestes, blocage de contenu, conteneurs d'identité. Zéro IA. S'il n'est pas déjà agréable ici, l'IA ne le sauvera pas. 2. **Distiller + cache.** Rien de visible pour l'utilisateur, mais tout le reste en dépend. 3. **Zoom sémantique + rendu adaptatif.** La démo. Le moment « je ne peux plus revenir en arrière ». 4. **Historique sémantique.** La valeur qui s'accumule et qui enferme l'utilisateur — au bon sens du terme. 5. **Favoris repensés**, dont le favori vivant. 6. **Vie privée avancée** : patterns manipulateurs, traducteur de conditions. 7. **Agent**, seulement là. --- ## 11. Risques connus | Risque | Réalité | |---|---| | 99 % des gens ne changent jamais de navigateur | Le zoom sémantique doit être démontrable en 10 secondes, sans configuration | | Le contexte de 4-8K | Contrainte permanente. Le Distiller est la réponse ; il ne sera jamais « terminé » | | Batterie | Un navigateur qui vide la batterie est désinstallé la semaine suivante | | Éditeurs de sites hostiles au retrait de pub | Prisme réorganise à l'affichage, ne republie rien, ne contourne pas les paywalls durs. Ne pas franchir cette ligne | | Appareils sans Foundation Models | Base installée réelle limitée. Le mode sans IA doit être un bon produit, pas une version dégradée | | Revue App Store | Ne pas se présenter comme un moteur alternatif ; c'est une interface au-dessus de WebKit | | Arc est mort | Sa leçon : la nouveauté d'interface sans bénéfice immédiat ne retient personne. Chaque fonctionnalité doit répondre à « qu'est-ce que ça me donne aujourd'hui » |