Prisme
Un navigateur n'est pas un afficheur de pages. C'est un lecteur qui comprend ce qu'il affiche.
SwiftUI · WebKit · Foundation Models · SwiftData · NaturalLanguage — zéro serveur, zéro compte, zéro télémétrie.
Table des matières
- Vision
- Captures d'écran
- Fonctionnalités
- Intelligence locale
- Architecture
- Métriques
- Compiler et lancer
- Tests
- Vie privée
- Feuille de route
- Auteur
🔭 Vision
Le web moderne est hostile : bannières, murs de consentement, 2 000 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 :
| # | Règle | Concrètement |
|---|---|---|
| 1 | Rien ne quitte l'appareil par défaut | Distillation, embeddings, historique, digest : tout est calculé on-device. Aucun serveur opéré, aucun compte. |
| 2 | Utile dès la première session, pour un seul utilisateur | Aucun effet de réseau requis. Le bandeau de compréhension démontre la valeur en 10 secondes. |
| 3 | Le modèle ne remplace jamais la page | Contenu généré toujours distinct visuellement (violet ✨), toujours ancré vers sa source DOM, page brute toujours à un geste. |
📱 Captures d'écran
| Page d'accueil | Barre à intention | Bandeau de compréhension |
|---|---|---|
![]() |
![]() |
![]() |
| Univers isolés, logo dessiné en code | URL, moteurs au choix, moteur par défaut | « ~1 min de lecture » + entrée lecteur |
| Lecteur — Texte | Lecteur — Essentiel | Rappel d'historique | Bibliothèque |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Rendu natif typé | Zoom sémantique max | « Déjà visité » | Données natives |
✨ Fonctionnalités
Légende : ✅ implémenté · 🚧 prévu (P1) · 🔮 plus tard (P2)
Affichage
| État | Fonctionnalité | Description |
|---|---|---|
| ✅ | Zoom sémantique | Le pincement ne change pas la taille du texte : il change le niveau de détail. 4 niveaux — Texte · Sections · Plan · Essentiel — et écarter au-delà du texte ramène la page brute. La fonctionnalité signature. |
| ✅ | Rendu adaptatif par type | Le kind du digest sélectionne la typographie : un article passe en serif de lecture, une documentation garde son code monospace défilant. |
| ✅ | Bandeau de compréhension | Après chaque chargement : « Article · ~4 min de lecture » + l'essentiel généré. Un tap → lecteur. Jamais un popup. |
| 🚧 | Barre de défilement sémantique | La scrollbar devient une carte de la page. |
| 🚧 | Thème sémantique | Le mode sombre par rôle de bloc, pas par inversion. |
| 🚧 | Tiroir du bruit | Tout ce qui a été retiré, consultable. |
| 🔮 | Diff temporel | Ce qui a changé depuis la dernière visite, surligné. (Les snapshots texte sont déjà archivés à chaque visite.) |
Onglets & entrée
| État | Fonctionnalité | Description |
|---|---|---|
| ✅ | Barre à intention | Un champ unique qui propose — jamais ne devine en silence : adresse directe, recherche DuckDuckGo, recherche Google, pages déjà visitées. Moteur par défaut commutable dans la carte même. |
| ✅ | Pool de WKWebView | Jamais une webview par onglet : pool réutilisé, état de session (interactionState) préservé au changement d'onglet — historique arrière/avant et défilement intacts. |
| ✅ | Sélecteur d'onglets par univers | Cartes sectionnées par conteneur, bandeau de couleur, badge de site. |
| 🚧 | Regroupement par intention | Proposé, jamais imposé. |
| 🚧 | Reprise narrative | À la réouverture : un paragraphe, pas 47 vignettes. |
| 🚧 | Onglets périssables | Durée de vie estimée, purge proposée. |
Favoris repensés
| État | Fonctionnalité | Description |
|---|---|---|
| ✅ | L'extrait | Sélectionner un passage → « Sauver l'extrait » dans le menu d'édition natif. Gardé : le texte, la source, la date, le contexte de section, l'ancre DOM. |
| ✅ | Le favori structuré | La page sauvée en données natives : titre, essentiel, plan, type — pas un pointeur qui pourrit. Enrichi par le modèle quand disponible (marqué ✨). |
| 🚧 | Le favori vivant | Surveille sa page en tâche de fond (BGTaskScheduler), notifie au changement. |
| 🚧 | Le favori-question | « Combien coûte le passeport » plutôt qu'une URL — immunisé contre le lien mort. |
| 🔮 | Collections émergentes · Purge honnête | Suggestions, jamais d'action automatique. |
Mémoire
| État | Fonctionnalité | Description |
|---|---|---|
| ✅ | Historique sémantique | Chaque page visitée est distillée et indexée localement (embeddings de phrases NaturalLanguage, par langue). Recherche en langage naturel, rappel directement dans la barre à intention (« Déjà visité »). |
| ✅ | Snapshots texte | Le texte extrait de chaque visite est archivé — la matière première du futur diff temporel. |
| 🚧 | Ligne du temps de sujet | Toutes les visites autour d'un thème. |
| 🚧 | Rappel proactif | « Vu en mars, c'était 899 $ » — une ligne, jamais un popup. |
Vie privée
| État | Fonctionnalité | Description |
|---|---|---|
| ✅ | Conteneurs d'identité | Perso · Travail · Magasinage · Recherche sensible — cookies, sessions et empreinte cloisonnés par WKWebsiteDataStore(forIdentifier:). Changement d'univers en un geste. |
| ✅ | Blocage de contenu | WKContentRuleList compilée (14 règles anti-traqueurs), appliquée à chaud dès compilation, hors du chemin critique de rendu. |
| ✅ | Exclusion du sensible | Les visites de l'univers « Recherche sensible » ne sont jamais écrites dans l'historique — pas stockées-puis-filtrées : jamais vues. |
| 🚧 | Détecteur de patterns manipulateurs | Faux compte à rebours, consentement pré-coché : nommés à l'écran. |
| 🚧 | Traducteur de conditions | Les CGU en trois lignes avant d'accepter. |
🧠 Intelligence locale
Un LLM n'est pas une réponse à tout — c'est le dernier recours, pas le premier. Chaque tâche déclare son niveau ; le routeur décide. Aucun appel direct au modèle ailleurs.
| Tier | Quand | Exemples dans Prisme |
|---|---|---|
none |
Une heuristique suffit | Détection URL/recherche, extraction DOM, temps de lecture, plan par titres, résumés de repli (première phrase), embeddings de recherche |
local |
Fréquent, gratuit, illimité, hors ligne | Digest de page (type, essentiel, plan, affirmations chiffrées) via SystemLanguageModel |
cloud |
Action explicite uniquement — pas encore câblé | Comparaisons multi-onglets, synthèses longues (Private Cloud Compute) |
Le Distiller — pièce centrale
Une page fait 30 000 tokens ; le modèle en accepte 4–8 000. Pipeline en 5 étapes, déterministe sauf la dernière :
HTML ──▶ ① Extraction DOM (extractor.js, injecté à documentEnd, zéro IA)
──▶ ② Blocs typés (titre, paragraphe, code, tableau…) + chemins DOM
──▶ ③ Budgétisation (contextSize / tokenCount, 30 % réservés à la réponse)
──▶ ④ Condensation déterministe (jamais de coupe en pleine phrase)
──▶ ⑤ Génération guidée (@Generable PageDigest — jamais de texte libre à parser)- Cache par hash de contenu : une même page n'est jamais distillée deux fois.
- File d'inférence : max 2 requêtes en vol, priorité geste > page active > arrière-plan.
- Honnêteté structurelle : chaque élément généré porte l'index de son bloc source ; un résumé sans ancre ne s'affiche pas ; les refus du modèle sont des états calmes, jamais des erreurs système brutes.
- Dégradation totale : sur un appareil sans Foundation Models (< A17 Pro), tout fonctionne — lecteur, zoom, historique, favoris — via les chemins déterministes. La suite de tests tourne avec le modèle indisponible.
🏛 Architecture
Prisme/
├─ App/ point d'entrée, modèle racine
├─ Browser/
│ ├─ Engine/ WKWebView (pool, proxy, délégués, règles) — seul accès WebKit
│ ├─ Tabs/ onglets, insight de page
│ ├─ Chrome/ barre à intention, bandeau, toolbar, sélecteur
│ └─ Reader/ zoom sémantique (4 niveaux, pincement)
├─ Intelligence/
│ ├─ Router/ choix du tier + disponibilité du modèle
│ ├─ Distiller/ extractor.js, blocs, budgets, cache ⚠️ cœur du projet
│ ├─ Schemas/ types @Generable (PageDigest…)
│ └─ Sessions/ file d'inférence à priorité
├─ Memory/
│ ├─ Index/ embeddings de phrases (NaturalLanguage)
│ ├─ Snapshots/ visites SwiftData + textes archivés
│ └─ Recall/ recherche naturelle, vue historique
├─ Library/ extraits + favoris structurés (SwiftData)
├─ Privacy/ conteneurs d'identité
└─ Design/ tokens, logo PrismMark (Canvas)Règles d'or du code :
WKWebViewenveloppée une seule fois (Browser/Engine) ; le reste de l'app parle àWebPageProxy.- Jamais de
Stringlibre en sortie de modèle — toujours@Generable. - Le JS injecté vit dans des fichiers
.jsversionnés, jamais dans des chaînes Swift. - Chaque store SwiftData a son fichier (
history.store,library.store). - Chaque fichier commence par l'en-tête d'auteur.
📊 Métriques
| Métrique | Valeur |
|---|---|
| Fichiers Swift | 35 |
| Lignes Swift | ~4 100 |
| JavaScript injecté | 144 lignes (extraction + sélection + scroll-to-source) |
| Dépendances tierces | 0 — uniquement les frameworks Apple |
| Tests UI de bout en bout | 6/6 ✓ |
| Cible de déploiement | iOS 26.4+ (requis par tokenCount(for:)) |
| Concurrence | Swift 6, SWIFT_STRICT_CONCURRENCY = complete |
| Règles de blocage | 14 familles de traqueurs (troisième partie) |
Répartition par module (fichiers Swift) :
| Module | Fichiers | Rôle |
|---|---|---|
Browser/ |
14 | moteur, onglets, chrome, lecteur |
Intelligence/ |
7 | routeur, distiller, schémas, sessions |
Memory/ |
4 | index, snapshots, rappel |
Library/ |
3 | extraits, favoris structurés |
App/ · Design/ · Privacy/ |
6 | racine, tokens, conteneurs |
PrismeUITests/ |
1 | 6 tests E2E |
🛠 Compiler et lancer
Prérequis : Xcode 26.6+ (SDK iOS 26.4+), XcodeGen.
git clone https://git.spboucher.ai/prisme.git
cd prisme
xcodegen generate # génère Prisme.xcodeproj depuis project.yml
open Prisme.xcodeproj # ⌘R sur un simulateur iPhoneEn ligne de commande :
xcodebuild -project Prisme.xcodeproj -scheme Prisme \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
build CODE_SIGNING_ALLOWED=NOL'icône se régénère depuis le SVG source :
rsvg-convert -w 1024 -h 1024 Design/icon/prisme-icon.svg \
-o Prisme/App/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.pngNote simulateur :
SystemLanguageModels'y déclare disponible mais échoue à l'inférence (assets absents). Les chemins déterministes prennent le relais — c'est le comportement attendu. Pour les digests réels : iPhone A17 Pro+ avec Apple Intelligence activée.
✅ Tests
Six tests XCUITest de bout en bout — réseau réel, gestes réels, zéro mock :
| Test | Ce qu'il prouve |
|---|---|
testNavigateToURLLoadsPage |
Saisie → proposition « Aller sur » → la page charge, cadenas + domaine |
testSearchProposalsOfferBothEngines |
Chaque requête propose DuckDuckGo et Google |
testGoogleSearchLoadsGoogle |
La recherche Google aboutit sur Google |
testReaderSemanticZoomLevels |
Bandeau → lecteur → 4 niveaux → retour page brute |
testSemanticHistoryRecallInAddressBar |
Visite → distillation → rappel « Déjà visité » → réouverture |
testStructuredFavoriteFromReaderAppearsInLibrary |
Signet lecteur → favori en données natives dans la bibliothèque |
xcodebuild -project Prisme.xcodeproj -scheme Prisme \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
test CODE_SIGNING_ALLOWED=NO🔒 Vie privée
- Aucun serveur. Prisme n'opère aucun backend ; rien à héberger, rien qui fuite.
- Aucun compte, aucune télémétrie, aucun identifiant.
- IA 100 % on-device ; l'escalade cloud (Private Cloud Compute) sera explicite, visible, et interdite sans exception sur le contenu des conteneurs sensibles.
- Recherche par défaut : DuckDuckGo ; Google proposé à chaque requête, jamais imposé.
- L'univers « Recherche sensible » n'existe pas pour la mémoire : ni historique, ni index, ni snapshot.
🗺 Feuille de route
Ordre de construction strict — chaque étape solide avant la suivante :
- 1. Navigateur nu, excellent — onglets, pool, gestes, blocage, conteneurs
- 2. Distiller + cache — le cœur invisible dont tout dépend
- 3. Zoom sémantique + rendu adaptatif — la démo
- 4. Historique sémantique — la valeur qui s'accumule
- 5. Favoris repensés (P0) — l'extrait, le favori structuré
- 5bis. Favori vivant (
BGTaskScheduler) + favori-question - 6. Vie privée avancée — patterns manipulateurs, traducteur de conditions
- 7. Agent — portée étroite, jamais d'action irréversible sans confirmation
👤 Auteur
Simon-Pierre Boucher — contact@spboucher.ai
Licence MIT © 2026 Simon-Pierre Boucher






