spb/prisme Public MIT
Navigateur iOS intelligent — chaque page comprise localement avant d'être affichée. SwiftUI · WebKit · Foundation Models, 100% on-device.
Swift 96.7%
JavaScript 3.3%
1# CLAUDE.md — Prisme23Navigateur iOS intelligent. SwiftUI + WebKit + Foundation Models.4Ce fichier est la source de vérité du projet. Le lire avant toute modification.56---78## 1. Thèse produit910**Un navigateur n'est pas un afficheur de pages. C'est un lecteur qui comprend ce qu'il affiche.**1112Le 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.1314Trois règles non négociables :15161. **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.172. **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.183. **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.1920### Ce que Prisme n'est pas2122- Pas un chatbot avec une webview collée à côté (Dia, Comet, Atlas occupent déjà ce terrain).23- 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.24- Pas un navigateur qui demande de changer ses habitudes avant de donner de la valeur. Arc est mort de ça.2526---2728## 2. Stack & contraintes2930| Élément | Choix | Note |31|---|---|---|32| UI | SwiftUI, iOS 27+ | `@Observable`, pas d'`ObservableObject` |33| Moteur web | `WKWebView` | pas d'alternative viable sur iOS |34| IA locale | `FoundationModels` | `SystemLanguageModel.default` |35| IA lourde | `PrivateCloudComputeLanguageModel` | 32K contexte, quota par utilisateur |36| Persistance | SwiftData | + fichiers pour les snapshots texte |37| Recherche | Index vectoriel local | voir §5 |38| Tâches fond | `BGTaskScheduler` | favoris vivants, veilles |39| Intégrations | App Intents | export vers Notes/Rappels/Calendrier |4041### Contraintes matérielles à respecter partout4243- **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.44- **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.45- **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é.46- **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).4748### Vérifier avant de coder4950`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.5152```swift53let model = SystemLanguageModel.default54let budget = try await model.contextSize55let cost = try await model.tokenCount(for: prompt)56guard cost < budget - reserveForResponse else { /* condenser */ }57```5859Ré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.6061---6263## 3. Architecture6465```66Prisme/67├─ App/ point d'entrée, scènes, raccourcis68├─ Browser/69│ ├─ Engine/ WKWebView, pool, delegates, règles de contenu70│ ├─ Tabs/ modèle d'onglets, groupes, sessions71│ └─ Chrome/ barre d'adresse, gestes, navigation72├─ Intelligence/73│ ├─ Router/ choix du modèle (on-device / PCC / aucun)74│ ├─ Distiller/ HTML → structure compacte ⚠️ cœur du projet75│ ├─ Schemas/ tous les types @Generable76│ ├─ Tools/ Tool protocol : accès onglets, historique, page77│ └─ Sessions/ gestion du cycle de vie, reprise après dépassement78├─ Memory/79│ ├─ Index/ index sémantique local80│ ├─ Snapshots/ versions texte des pages visitées81│ └─ Recall/ recherche floue, rappels proactifs82├─ Library/ favoris, extraits, collections83├─ Privacy/ conteneurs d'identité, blocage, détection de patterns84└─ Design/ tokens, typographie, thèmes sémantiques85```8687### Le routeur (`Intelligence/Router`)8889Chaque tâche déclare son niveau. Le routeur choisit. Aucun appel direct au modèle ailleurs dans le code.9091```swift92enum Tier {93 case none // heuristique pure, zéro IA94 case local // on-device, gratuit, illimité, hors ligne95 case cloud // PCC : 32K, raisonnement — quota utilisateur, consentement96}97```9899Règles de routage :100101- **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.102- **`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.103- **`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.**104- Contenu marqué sensible (santé, finance, tout ce qui est dans un conteneur privé) : `cloud` interdit, sans exception.105106---107108## 4. Le Distiller — pièce centrale109110**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.111112Pipeline, dans l'ordre. Chaque étape est déterministe sauf la dernière.1131141. **Extraction DOM** (JS injecté, `WKUserScript` au `documentEnd`) — retire nav, footer, pub, scripts, commentaires. Algorithme type Readability, en dur, pas d'IA.1152. **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).1163. **Budgétisation** — mesure les tokens par bloc, alloue le budget par importance (profondeur de titre, position, densité de liens).1174. **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.1185. **Sortie structurée** — `@Generable`, jamais de texte libre à parser.119120```swift121@Generable122struct PageDigest {123 @Guide(description: "Type de page")124 let kind: PageKind // article, doc, forum, boutique, appli, portail, formulaire125126 @Guide(description: "Réponse à la question implicite de la page, max 2 phrases")127 let gist: String128129 @Guide(description: "Sections dans l'ordre du document", .count(3...12))130 let outline: [Section]131132 @Guide(description: "Affirmations chiffrées ou datées, avec leur offset DOM")133 let claims: [Claim]134135 let hostility: HostilityReport // voir §8136}137```138139### Règles de fer du Distiller140141- **Ne jamais envoyer de HTML brut au modèle.** Ça brûle le contexte en balises et dégrade la qualité.142- **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.143- **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.144- **Le digest est un artefact durable**, pas un intermédiaire jetable : il alimente l'historique (§5), les favoris (§5) et le diff temporel.145146---147148## 5. Fonctionnalités par domaine149150Priorité : `P0` = MVP, `P1` = v1, `P2` = après.151152### Affichage153154- **`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.155- **`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.156- **`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.157- **`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.158- **`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.159- **`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.160- **`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.161162### Onglets163164- **`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.165- **`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. »166- **`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.167- **`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.168169### Favoris — à repenser complètement170171Le favori est une relique de 1995 : un pointeur vers une URL, qui pourrit. On le remplace par quatre objets.172173- **`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.174- **`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.175- **`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.176- **`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.177- **`P2` Collections émergentes.** Quand un thème apparaît dans les sauvegardes, Prisme propose une collection. Suggestion, jamais action automatique.178- **`P2` Purge honnête.** « 340 favoris jamais rouverts depuis 2 ans. » Proposition d'archivage groupé.179180### Mémoire181182- **`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.183- **`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.184- **`P1` Rappel proactif.** Retour sur une fiche produit : « vu en mars, c'était 899 $ ». Silencieux, une ligne, jamais un popup.185186### Vie privée187188- **`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.189- **`P0` Blocage de contenu.** `WKContentRuleList` compilé, mis à jour, mesurable. Non négociable pour la performance et la vie privée.190- **`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.191- **`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é.192193### Entrée194195- **`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.196- **`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.197- **`P2` Écoute.** Le digest lu à voix haute. Utile en déplacement, et c'est la seule façon d'« emporter » un long article.198199---200201## 6. Agent (`P2` — pas avant que le reste soit excellent)202203Portée volontairement étroite. Un agent qui échoue une fois sur cinq est pire qu'aucun agent.204205Autorisé : comparer des pages déjà ouvertes ; surveiller des favoris vivants ; extraire et remplir un formulaire avec des données confirmées par l'utilisateur.206207**Interdit :** toute action irréversible sans confirmation explicite — achat, envoi, suppression, publication. Aucune exception, aucun mode « expert » qui la contourne.208209Implémentation par `Tool` :210211```swift212struct OpenTabsTool: Tool {213 let name = "lire_onglets_ouverts"214 let description = "Retourne le digest des onglets actuellement ouverts"215216 @Generable struct Arguments {217 @Guide(description: "Filtre optionnel sur le titre ou le domaine")218 let filter: String?219 }220221 func call(arguments: Arguments) async throws -> String {222 // Retourner les DIGESTS, jamais le HTML.223 // Plafonner : 3 onglets max, ~400 tokens chacun.224 }225}226```227228Rè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.229230---231232## 7. Honnêteté du modèle233234Non négociable. Un navigateur qui invente est un navigateur inutilisable.235236- **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.237- **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.238- **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.239- **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.240- 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.241242---243244## 8. Performance & énergie245246Un navigateur se juge d'abord sur la vitesse d'affichage. **L'IA ne doit jamais retarder le rendu.**247248- La page s'affiche immédiatement. La distillation démarre après `didFinish`, en priorité basse, et l'enrichissement arrive progressivement.249- Pool de `WKWebView` réutilisées. Ne jamais en instancier une par onglet.250- 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.251- File d'inférence à priorité : geste utilisateur > page active > arrière-plan. Annulation immédiate si l'utilisateur quitte l'onglet.252- Cible : **aucune régression perceptible du temps d'affichage** contre Safari. Mesurée à chaque build.253254---255256## 9. Conventions de code257258- SwiftUI uniquement. `@Observable`. Pas de Combine sauf pour les ponts WebKit.259- Swift 6, concurrence stricte. Les états d'onglet sont `@MainActor`. L'inférence et la distillation sont hors du main actor.260- `WKWebView` enveloppée dans `UIViewRepresentable`, une seule fois, dans `Browser/Engine`. Aucun accès direct ailleurs.261- **Jamais de `String` libre en sortie de modèle.** Toujours `@Generable`. Une réponse à parser au regex est un bug.262- Le JS injecté vit dans des fichiers `.js` versionnés, jamais dans des chaînes Swift.263- Chaque fonctionnalité IA a un chemin de repli non-IA testé. La suite de tests tourne avec le modèle désactivé.264- Nommage utilisateur en français ; code, commentaires et commits en anglais.265- **Chaque fichier de code** (Swift, JS, YAML…) commence par un en-tête d'auteur : nom du fichier, projet, puis `Author: Simon-Pierre Boucher <contact@spboucher.ai>`. (Exception : JSON, qui n'accepte pas de commentaires.)266267---268269## 10. Ordre de construction270271Ne pas dévier. Chaque étape doit être solide avant la suivante.2722731. **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.2742. **Distiller + cache.** Rien de visible pour l'utilisateur, mais tout le reste en dépend.2753. **Zoom sémantique + rendu adaptatif.** La démo. Le moment « je ne peux plus revenir en arrière ».2764. **Historique sémantique.** La valeur qui s'accumule et qui enferme l'utilisateur — au bon sens du terme.2775. **Favoris repensés**, dont le favori vivant.2786. **Vie privée avancée** : patterns manipulateurs, traducteur de conditions.2797. **Agent**, seulement là.280281---282283## 11. Risques connus284285| Risque | Réalité |286|---|---|287| 99 % des gens ne changent jamais de navigateur | Le zoom sémantique doit être démontrable en 10 secondes, sans configuration |288| Le contexte de 4-8K | Contrainte permanente. Le Distiller est la réponse ; il ne sera jamais « terminé » |289| Batterie | Un navigateur qui vide la batterie est désinstallé la semaine suivante |290| É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 |291| 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 |292| Revue App Store | Ne pas se présenter comme un moteur alternatif ; c'est une interface au-dessus de WebKit |293| 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 » |294