| 4097 |
4097 |
"Adding an animation is adding one file: 84 pages, search index, and filters regenerate at build time", |
| 4098 |
4098 |
"The site eats its own dog food — vanilla CSS and custom properties, no Tailwind, no third-party trackers" |
| 4099 |
4099 |
] |
|
4100 |
+ }, |
|
4101 |
+ { |
|
4102 |
+ "slug": "prisme", |
|
4103 |
+ "hero": { |
|
4104 |
+ "headline": "A browser is not a page viewer — it's a reader that understands", |
|
4105 |
+ "subheadline": "An intelligent iOS browser where every page is understood locally before it is displayed — SwiftUI, WebKit, and Apple Foundation Models, with zero servers, zero accounts, and zero telemetry." |
|
4106 |
+ }, |
|
4107 |
+ "overview": [ |
|
4108 |
+ "The modern web is hostile: consent walls, 2,000 words of SEO filler around a 40-word answer, manipulative patterns, tracking. Ordinary browsers render that hostility faithfully. Prisme interposes itself: each page is distilled locally before display, then re-presented according to the reader's intent — while the raw page always stays one gesture away.", |
|
4109 |
+ "The signature feature is semantic zoom: pinching doesn't change the text size, it changes the level of detail — Text, Sections, Outline, Essence — and pinching out past Text returns the raw page. A comprehension strip appears after every load (\"Article · ~4 min read\") with a one-tap entry into the native reader, whose typography adapts to the page type.", |
|
4110 |
+ "Everything is on-device. The Distiller pipeline extracts typed DOM blocks with deterministic JavaScript, budgets tokens, and only then asks the ~3B Foundation Models for a guided @Generable digest — cached by content hash, anchored back to source blocks, and fully degradable: on devices without Apple Intelligence, every feature still works through deterministic paths. Identity containers (Personal, Work, Shopping, Sensitive) isolate cookies and sessions, and the Sensitive universe is never written to history at all." |
|
4111 |
+ ], |
|
4112 |
+ "stats": [ |
|
4113 |
+ { "value": "35", "label": "Swift files, ~4,100 lines" }, |
|
4114 |
+ { "value": "0", "label": "third-party dependencies" }, |
|
4115 |
+ { "value": "6/6", "label": "end-to-end UI tests green" }, |
|
4116 |
+ { "value": "4", "label": "semantic zoom levels" }, |
|
4117 |
+ { "value": "144", "label": "lines of injected JavaScript" }, |
|
4118 |
+ { "value": "100%", "label": "on-device AI" } |
|
4119 |
+ ], |
|
4120 |
+ "features": [ |
|
4121 |
+ { |
|
4122 |
+ "icon": "Eye", |
|
4123 |
+ "title": "Semantic zoom", |
|
4124 |
+ "description": "Pinch to change the level of detail, not the font size: Text, Sections, Outline, Essence — and back out to the raw page." |
|
4125 |
+ }, |
|
4126 |
+ { |
|
4127 |
+ "icon": "Brain", |
|
4128 |
+ "title": "The Distiller", |
|
4129 |
+ "description": "A 5-step pipeline — DOM extraction, typed blocks, token budgeting, deterministic condensation, guided @Generable generation — with a content-hash cache." |
|
4130 |
+ }, |
|
4131 |
+ { |
|
4132 |
+ "icon": "Search", |
|
4133 |
+ "title": "Semantic history", |
|
4134 |
+ "description": "Every visited page is distilled and indexed with on-device sentence embeddings — natural-language recall directly in the address bar." |
|
4135 |
+ }, |
|
4136 |
+ { |
|
4137 |
+ "icon": "BookOpen", |
|
4138 |
+ "title": "Structured bookmarks", |
|
4139 |
+ "description": "Pages saved as native data (title, essence, outline, type) and text selections saved with their DOM anchor and section context — not links that rot." |
|
4140 |
+ }, |
|
4141 |
+ { |
|
4142 |
+ "icon": "Fingerprint", |
|
4143 |
+ "title": "Identity containers", |
|
4144 |
+ "description": "Personal, Work, Shopping, and Sensitive universes with isolated cookie stores — and Sensitive visits are never written to memory at all." |
|
4145 |
+ }, |
|
4146 |
+ { |
|
4147 |
+ "icon": "ShieldCheck", |
|
4148 |
+ "title": "Honest AI, always anchored", |
|
4149 |
+ "description": "Generated content is visually distinct, anchored to its source block, and never replaces the page. A summary without an anchor doesn't render." |
|
4150 |
+ } |
|
4151 |
+ ], |
|
4152 |
+ "techStack": [ |
|
4153 |
+ { |
|
4154 |
+ "category": "Platform", |
|
4155 |
+ "items": ["Swift 6 (strict concurrency)", "SwiftUI", "iOS 26.4+", "XcodeGen"] |
|
4156 |
+ }, |
|
4157 |
+ { |
|
4158 |
+ "category": "Engine & intelligence", |
|
4159 |
+ "items": ["WebKit (WKWebView pool)", "Apple Foundation Models", "@Generable guided output", "NaturalLanguage embeddings", "SwiftData"] |
|
4160 |
+ }, |
|
4161 |
+ { |
|
4162 |
+ "category": "Privacy", |
|
4163 |
+ "items": ["WKWebsiteDataStore identity containers", "WKContentRuleList tracker blocking", "DuckDuckGo default search", "Zero telemetry"] |
|
4164 |
+ } |
|
4165 |
+ ], |
|
4166 |
+ "architecture": [ |
|
4167 |
+ { |
|
4168 |
+ "title": "Extract deterministically", |
|
4169 |
+ "description": "144 lines of versioned JavaScript injected at documentEnd pull typed blocks (headings, paragraphs, code, tables) with their DOM paths — zero AI in the extraction path." |
|
4170 |
+ }, |
|
4171 |
+ { |
|
4172 |
+ "title": "Budget and condense", |
|
4173 |
+ "description": "A 30,000-token page must fit a 4–8K context: token budgeting reserves 30% for the response and condensation never cuts mid-sentence." |
|
4174 |
+ }, |
|
4175 |
+ { |
|
4176 |
+ "title": "Generate guided digests", |
|
4177 |
+ "description": "The on-device model produces a typed PageDigest via @Generable — never free text to parse — cached by content hash so no page is distilled twice." |
|
4178 |
+ }, |
|
4179 |
+ { |
|
4180 |
+ "title": "Route by tier", |
|
4181 |
+ "description": "Every task declares its tier: heuristics first, the local model for frequent free work, and cloud only for explicit actions — an LLM is the last resort, not the first." |
|
4182 |
+ }, |
|
4183 |
+ { |
|
4184 |
+ "title": "Degrade totally", |
|
4185 |
+ "description": "On devices without Apple Intelligence, the reader, zoom, history, and bookmarks all work through deterministic fallbacks — the test suite runs with the model unavailable." |
|
4186 |
+ } |
|
4187 |
+ ], |
|
4188 |
+ "highlights": [ |
|
4189 |
+ "Semantic zoom: the pinch gesture changes the level of detail, not the text size — the signature interaction", |
|
4190 |
+ "A pooled WKWebView architecture preserves per-tab session state without one webview per tab", |
|
4191 |
+ "Six end-to-end XCUITests over real network and real gestures — zero mocks", |
|
4192 |
+ "Zero third-party dependencies: SwiftUI, WebKit, Foundation Models, SwiftData, NaturalLanguage only", |
|
4193 |
+ "The Sensitive research container is never stored-then-filtered — its visits are never seen by memory at all", |
|
4194 |
+ "Every generated element carries the index of its source block; unanchored summaries simply don't display" |
|
4195 |
+ ] |
|
4196 |
+ }, |
|
4197 |
+ { |
|
4198 |
+ "slug": "poche", |
|
4199 |
+ "hero": { |
|
4200 |
+ "headline": "Your agent, on your device — offline, private, yours", |
|
4201 |
+ "subheadline": "A personal agent living entirely on the iPhone: streaming chat over Apple Foundation Models that creates reminders, events, notes, and tasks behind explicit confirmation cards. No API, no server, no account." |
|
4202 |
+ }, |
|
4203 |
+ "overview": [ |
|
4204 |
+ "Poche is a personal agent whose only generative engine is Apple Foundation Models — the ~3B-parameter Apple Intelligence model running on the device's Neural Engine. The founding rule: if a feature can't be done on-device, it isn't done. There is no cloud fallback, no degraded promise.", |
|
4205 |
+ "It's a streaming chat that acts: creating reminders and calendar events through EventKit, saving notes and tasks in SwiftData, and recalling your data through local semantic search (NLEmbedding). The model never causes a side effect — it proposes a tool call, and the app validates, displays a confirmation card, and executes only after the user confirms. No code path writes without traversing the confirmation layer.", |
|
4206 |
+ "The central engineering problem is the 4,096-token context window: system instructions, tool schemas, history, and the response all share it. Poche measures continuously, condenses the conversation at 70% through a separate @Generable summarization call, and recycles the session invisibly — the user never sees an error. A tripwire test registers a URLProtocol spy and fails if a single network request leaves during agent exercises." |
|
4207 |
+ ], |
|
4208 |
+ "stats": [ |
|
4209 |
+ { "value": "41", "label": "Swift files, ~2,930 lines" }, |
|
4210 |
+ { "value": "21/21", "label": "automated tests green" }, |
|
4211 |
+ { "value": "7", "label": "tools exposed to the model" }, |
|
4212 |
+ { "value": "0", "label": "network requests in the AI path" }, |
|
4213 |
+ { "value": "4,096", "label": "token context, actively managed" }, |
|
4214 |
+ { "value": "~3B", "label": "parameters, on the Neural Engine" } |
|
4215 |
+ ], |
|
4216 |
+ "features": [ |
|
4217 |
+ { |
|
4218 |
+ "icon": "Bot", |
|
4219 |
+ "title": "An agent that acts", |
|
4220 |
+ "description": "Creates reminders, calendar events, notes, and tasks through EventKit and SwiftData — from natural conversation, in streaming." |
|
4221 |
+ }, |
|
4222 |
+ { |
|
4223 |
+ "icon": "CheckCircle2", |
|
4224 |
+ "title": "Non-bypassable confirmation", |
|
4225 |
+ "description": "The model proposes; the user disposes. Every write crosses a confirmation card with dates resolved and validated in Swift — no expert mode to disable it." |
|
4226 |
+ }, |
|
4227 |
+ { |
|
4228 |
+ "icon": "Gauge", |
|
4229 |
+ "title": "Context budget engine", |
|
4230 |
+ "description": "Continuous token measurement, condensation at 70% via a separate @Generable call, and invisible session recycling — a local chat that never breaks." |
|
4231 |
+ }, |
|
4232 |
+ { |
|
4233 |
+ "icon": "Search", |
|
4234 |
+ "title": "Local semantic search", |
|
4235 |
+ "description": "Notes, tasks, and conversation summaries indexed with NLEmbedding — long-term memory retrieved on demand, entirely on-device." |
|
4236 |
+ }, |
|
4237 |
+ { |
|
4238 |
+ "icon": "Lock", |
|
4239 |
+ "title": "Zero network, proven", |
|
4240 |
+ "description": "A tripwire test registers a URLProtocol spy and fails if a single request leaves the device while the agent's tools and search are exercised." |
|
4241 |
+ }, |
|
4242 |
+ { |
|
4243 |
+ "icon": "MessageSquare", |
|
4244 |
+ "title": "On-device dictation", |
|
4245 |
+ "description": "SFSpeechRecognizer with on-device recognition required — if the device can't transcribe locally, the mic button doesn't exist. No server fallback." |
|
4246 |
+ } |
|
4247 |
+ ], |
|
4248 |
+ "techStack": [ |
|
4249 |
+ { |
|
4250 |
+ "category": "Platform", |
|
4251 |
+ "items": ["Swift 6 (strict concurrency)", "SwiftUI + @Observable", "iOS 26+", "XcodeGen"] |
|
4252 |
+ }, |
|
4253 |
+ { |
|
4254 |
+ "category": "Intelligence", |
|
4255 |
+ "items": ["Apple Foundation Models (~3B)", "@Generable / @Guide schemas", "NLEmbedding semantic index", "On-device SFSpeechRecognizer"] |
|
4256 |
+ }, |
|
4257 |
+ { |
|
4258 |
+ "category": "Data & system", |
|
4259 |
+ "items": ["SwiftData", "EventKit", "App Intents", "Share Extension", "Data Protection encryption"] |
|
4260 |
+ } |
|
4261 |
+ ], |
|
4262 |
+ "architecture": [ |
|
4263 |
+ { |
|
4264 |
+ "title": "Stream the conversation", |
|
4265 |
+ "description": "A LanguageModelSession streams responses with a first-token target under 400 ms, backed by versioned system instructions costing a measured ~230 tokens." |
|
4266 |
+ }, |
|
4267 |
+ { |
|
4268 |
+ "title": "Propose, never execute", |
|
4269 |
+ "description": "Tools validate arguments hostilely (dates resolved in Swift, titles truncated, bounds checked) and emit proposals to the ConfirmCenter — the only write path is the ActionExecutor, after user confirmation." |
|
4270 |
+ }, |
|
4271 |
+ { |
|
4272 |
+ "title": "Manage the 4,096-token budget", |
|
4273 |
+ "description": "A pessimistic estimator drives a discreet context gauge; at 70% the conversation is condensed by a separate structured call and the session recycled with the summary plus the last 3 turns." |
|
4274 |
+ }, |
|
4275 |
+ { |
|
4276 |
+ "title": "Externalize long memory", |
|
4277 |
+ "description": "Summaries persist in SwiftData and come back on demand through searchMyData — the agent remembers across sessions without ever growing the context." |
|
4278 |
+ }, |
|
4279 |
+ { |
|
4280 |
+ "title": "Handle every model state", |
|
4281 |
+ "description": "Available, device-not-eligible, Apple Intelligence off, model downloading — each gets its own screen and action, and guardrail refusals are calm UI states, never raw errors." |
|
4282 |
+ } |
|
4283 |
+ ], |
|
4284 |
+ "highlights": [ |
|
4285 |
+ "No code path writes without traversing the confirmation layer — verified by the tool-pattern test suite", |
|
4286 |
+ "21/21 tests green under Swift 6 strict concurrency, including the zero-network tripwire", |
|
4287 |
+ "Tool outputs are capped (~200 tokens) because one tool returning 30 events kills a 4K session", |
|
4288 |
+ "Date resolution happens in Swift, never in the model — displayed in clear text on every confirmation card", |
|
4289 |
+ "TestFlight 1.0.0 uploaded to App Store Connect (ai.spboucher.poche)", |
|
4290 |
+ "The whole agent — chat, tools, memory, dictation — fits in ~2,930 lines of Swift" |
|
4291 |
+ ] |
|
4292 |
+ }, |
|
4293 |
+ { |
|
4294 |
+ "slug": "focale", |
|
4295 |
+ "hero": { |
|
4296 |
+ "headline": "A manual camera and a photo library that answers questions", |
|
4297 |
+ "subheadline": "Two products that save each other: photos taken in Focale arrive already understood, and your old library catches up in the background — Vision on everything, Foundation Models on candidates only, nothing ever leaving the device." |
|
4298 |
+ }, |
|
4299 |
+ "overview": [ |
|
4300 |
+ "Focale is two products in one. First, a manual camera worth using on its own: ISO, shutter speed, focus, white balance, exposure compensation, and zoom — every control verified against the hardware before it is shown, so there is never a dead button. ProRAW, lens selection, self-timer, recipes (named presets exportable as .focalerecipe files), and three reassignable gesture layouts. The shutter is sacred: no inference, no disk, no allocation in the trigger path.", |
|
4301 |
+ "Second, a searchable photo library. At capture time Focale records context at zero inference cost — active recipe, declared project, manual settings, scene signal, coarse location — and writes it into the photo's EXIF metadata without re-encoding, so the information survives even if the app is uninstalled. Indexing is two-tier: Vision runs on everything (OCR, visual fingerprints, classification), while Foundation Models touches only photos that earn it — because 40,000 photos at 1–3 s each is 11–33 hours of compute.", |
|
4302 |
+ "Search is natural language — \"the garage receipt from last fall\" becomes a structured filter executed instantly against the local database. The model parses the query, never the photos. Living albums are defined by a query and fill themselves; \"similar photos\" uses fingerprint distance with zero LLM. Generated content is always badged, OCR beats the model for any number, and indexing progress is honest. Nothing leaves the device — no photo duplicated, moved, or modified." |
|
4303 |
+ ], |
|
4304 |
+ "stats": [ |
|
4305 |
+ { "value": "37", "label": "Swift files, 4,554 lines" }, |
|
4306 |
+ { "value": "2", "label": "indexing tiers (Vision + FM)" }, |
|
4307 |
+ { "value": "< 50 ms", "label": "shutter-path target" }, |
|
4308 |
+ { "value": "100%", "label": "on-device — no cloud path" }, |
|
4309 |
+ { "value": "4", "label": "TestFlight builds shipped" }, |
|
4310 |
+ { "value": "0", "label": "photos duplicated or modified" } |
|
4311 |
+ ], |
|
4312 |
+ "features": [ |
|
4313 |
+ { |
|
4314 |
+ "icon": "Camera", |
|
4315 |
+ "title": "True manual controls", |
|
4316 |
+ "description": "ISO, shutter, focus, white balance, EV, zoom — each verified against the hardware before display. ProRAW, lens choice, flash, timer, rule-of-thirds grid." |
|
4317 |
+ }, |
|
4318 |
+ { |
|
4319 |
+ "icon": "Settings2", |
|
4320 |
+ "title": "Recipes & layouts", |
|
4321 |
+ "description": "Complete named presets recallable in one gesture and shareable as .focalerecipe files, plus three interface profiles with fully reassignable gestures." |
|
4322 |
+ }, |
|
4323 |
+ { |
|
4324 |
+ "icon": "MapPin", |
|
4325 |
+ "title": "Capture context in EXIF", |
|
4326 |
+ "description": "Recipe, project, settings, scene signal, and coarse location recorded at zero inference cost — written into the photo's own metadata, not just a database." |
|
4327 |
+ }, |
|
4328 |
+ { |
|
4329 |
+ "icon": "Eye", |
|
4330 |
+ "title": "Two-tier indexing", |
|
4331 |
+ "description": "Tier 1 Vision on everything: OCR, visual fingerprints, classification. Tier 2 Foundation Models only on candidates — the calculation that keeps 40,000 photos tractable." |
|
4332 |
+ }, |
|
4333 |
+ { |
|
4334 |
+ "icon": "Search", |
|
4335 |
+ "title": "Natural-language search", |
|
4336 |
+ "description": "\"The garage receipt from last fall\" → a structured filter run instantly on the local index. The model parses the query, never the photos." |
|
4337 |
+ }, |
|
4338 |
+ { |
|
4339 |
+ "icon": "Sparkles", |
|
4340 |
+ "title": "Living albums", |
|
4341 |
+ "description": "Albums defined by a query that fill themselves, creatable from any search — plus similar-photos by fingerprint distance, zero LLM." |
|
4342 |
+ } |
|
4343 |
+ ], |
|
4344 |
+ "techStack": [ |
|
4345 |
+ { |
|
4346 |
+ "category": "Platform", |
|
4347 |
+ "items": ["Swift 6 (strict concurrency)", "SwiftUI + @Observable", "iOS 26.0+", "XcodeGen"] |
|
4348 |
+ }, |
|
4349 |
+ { |
|
4350 |
+ "category": "Camera & photos", |
|
4351 |
+ "items": ["AVFoundation (dedicated serial queue)", "PhotoKit (localIdentifier, no duplication)", "ProRAW", "EXIF writing without re-encode"] |
|
4352 |
+ }, |
|
4353 |
+ { |
|
4354 |
+ "category": "Intelligence", |
|
4355 |
+ "items": ["Vision (OCR, feature prints, classification)", "Apple Foundation Models (@Generable)", "SwiftData index", "BGProcessingTask night catch-up"] |
|
4356 |
+ } |
|
4357 |
+ ], |
|
4358 |
+ "architecture": [ |
|
4359 |
+ { |
|
4360 |
+ "title": "Keep the shutter sacred", |
|
4361 |
+ "description": "No inference, no disk write, no allocation in the trigger path — screen blink and immediate haptic, with a sub-50 ms response target." |
|
4362 |
+ }, |
|
4363 |
+ { |
|
4364 |
+ "title": "Record context for free", |
|
4365 |
+ "description": "At capture, the active recipe, project, manual settings, scene brightness, and coarse location are stored — in the local base and in the photo's EXIF." |
|
4366 |
+ }, |
|
4367 |
+ { |
|
4368 |
+ "title": "Index newest-first, always", |
|
4369 |
+ "description": "Recent photos index in the foreground on launch; the deep back-catalog catches up at night, on charge, via BGProcessingTask with incremental resume." |
|
4370 |
+ }, |
|
4371 |
+ { |
|
4372 |
+ "title": "Spend the model wisely", |
|
4373 |
+ "description": "Vision runs on every photo; Foundation Models only on photos taken in Focale, favorites, or explicit requests — with a full deterministic fallback below A17 Pro." |
|
4374 |
+ }, |
|
4375 |
+ { |
|
4376 |
+ "title": "Answer instantly", |
|
4377 |
+ "description": "Natural queries compile to structured filters over the SwiftData index and rank locally — search feels instant because no model ever touches the photo at query time." |
|
4378 |
+ } |
|
4379 |
+ ], |
|
4380 |
+ "highlights": [ |
|
4381 |
+ "Every camera control is hardware-verified before display — no dead buttons, ever", |
|
4382 |
+ "Capture context lives in the photo's EXIF: uninstall the app, keep the information", |
|
4383 |
+ "OCR is the silent winner: receipts, whiteboards, serial numbers — and it beats the model for any digit", |
|
4384 |
+ "Faces are counted locally, never identified; coarse location only, never raw coordinates", |
|
4385 |
+ "Swift 6 isolation crashes in PhotoKit closures diagnosed and fixed — with the pattern documented in the repo", |
|
4386 |
+ "Useful before the index is complete: progressive results, honest progress, newest-first" |
|
4387 |
+ ] |
|
4388 |
+ }, |
|
4389 |
+ { |
|
4390 |
+ "slug": "vrai-prix-ios", |
|
4391 |
+ "hero": { |
|
4392 |
+ "headline": "The true value of your property, without the black box — in your pocket", |
|
4393 |
+ "subheadline": "The native iOS companion to Vrai-Prix: full estimates with P10–P90 ranges, dollar-adjusted comparables, map search, and bank-grade PDF reports — straight from the production API over 3.7M Quebec properties." |
|
4394 |
+ }, |
|
4395 |
+ "overview": [ |
|
4396 |
+ "Vrai-Prix iOS brings Quebec's transparent property-valuation engine to the iPhone as a fully native SwiftUI app. It consumes the production API — 3,747,008 assessment units and 745,119 real sales, MdAPE 11.0% — with no embedded data: the Vrai-Prix server remains the single source of truth.", |
|
4397 |
+ "The Estimate tab pairs a debounced FTS5 address autocomplete with the complete estimate: value, P10–P90 range, A–D confidence grade, the 65% hedonic / 35% comparables fusion, a 2021→2026 price history drawn with Swift Charts, and the property's assessment-roll characteristics. Every comparable shows its dollar adjustments — market conditions, living area, age — and its adjusted price: the engine's full transparency, faithful to the website.", |
|
4398 |
+ "A MapKit tab searches by location with nearby properties and preview cards; a Manual tab estimates from municipality, type, and optional characteristics without an exact address; and a Reports tab renders the standard 3-page PDF or the 6-page bank-grade professional report in a PDFKit viewer with sharing. The app carries the site's editorial-sharp identity — paper, ink, vivid red — with embedded Space Grotesk, Inter, and JetBrains Mono." |
|
4399 |
+ ], |
|
4400 |
+ "stats": [ |
|
4401 |
+ { "value": "3.7M", "label": "assessment units served" }, |
|
4402 |
+ { "value": "745K", "label": "real sales behind estimates" }, |
|
4403 |
+ { "value": "11.0%", "label": "median absolute % error" }, |
|
4404 |
+ { "value": "P10–P90", "label": "range on every estimate" }, |
|
4405 |
+ { "value": "6 pages", "label": "bank-grade PDF report" }, |
|
4406 |
+ { "value": "3/3", "label": "end-to-end UI tests green" } |
|
4407 |
+ ], |
|
4408 |
+ "features": [ |
|
4409 |
+ { |
|
4410 |
+ "icon": "Search", |
|
4411 |
+ "title": "Address search & full estimate", |
|
4412 |
+ "description": "Debounced FTS5 autocomplete straight to the complete estimate: value, P10–P90 range, A–D confidence grade, and the hedonic/comparables fusion." |
|
4413 |
+ }, |
|
4414 |
+ { |
|
4415 |
+ "icon": "DollarSign", |
|
4416 |
+ "title": "Dollar-adjusted comparables", |
|
4417 |
+ "description": "Every comparable displays its adjustments — market conditions, area, age — and its adjusted price. Total transparency, faithful to the engine." |
|
4418 |
+ }, |
|
4419 |
+ { |
|
4420 |
+ "icon": "Map", |
|
4421 |
+ "title": "Map-based search", |
|
4422 |
+ "description": "MapKit search with nearby properties, geolocation, and preview cards carrying the 2026 estimate — one tap from pin to full valuation." |
|
4423 |
+ }, |
|
4424 |
+ { |
|
4425 |
+ "icon": "LineChart", |
|
4426 |
+ "title": "Price history in Swift Charts", |
|
4427 |
+ "description": "The property's 2021→2026 estimated-value trajectory, drawn natively — the market's path, not just today's number." |
|
4428 |
+ }, |
|
4429 |
+ { |
|
4430 |
+ "icon": "FileText", |
|
4431 |
+ "title": "PDF reports", |
|
4432 |
+ "description": "Standard 3-page and professional 6-page bank-grade reports rendered by the production API, viewed in PDFKit and shared from the phone." |
|
4433 |
+ }, |
|
4434 |
+ { |
|
4435 |
+ "icon": "Palette", |
|
4436 |
+ "title": "Editorial-sharp design", |
|
4437 |
+ "description": "The site's identity ported natively: paper, ink, vivid red accents, hard offset shadows, uppercase display type, and mono micro-labels." |
|
4438 |
+ } |
|
4439 |
+ ], |
|
4440 |
+ "techStack": [ |
|
4441 |
+ { |
|
4442 |
+ "category": "Platform", |
|
4443 |
+ "items": ["Swift", "SwiftUI (iOS 17+)", "XcodeGen", "TestFlight (ai.spboucher.vraiprix)"] |
|
4444 |
+ }, |
|
4445 |
+ { |
|
4446 |
+ "category": "Apple frameworks", |
|
4447 |
+ "items": ["MapKit", "Swift Charts", "PDFKit", "CoreGraphics + CoreText icon pipeline"] |
|
4448 |
+ }, |
|
4449 |
+ { |
|
4450 |
+ "category": "Backend", |
|
4451 |
+ "items": ["Vrai-Prix production API", "/api/search · /api/nearby · /api/estimate", "/api/report · /api/report/pro"] |
|
4452 |
+ } |
|
4453 |
+ ], |
|
4454 |
+ "architecture": [ |
|
4455 |
+ { |
|
4456 |
+ "title": "Stay thin, stay honest", |
|
4457 |
+ "description": "No embedded data and no local model: the app is a native window onto the production valuation engine, so phone and website never disagree." |
|
4458 |
+ }, |
|
4459 |
+ { |
|
4460 |
+ "title": "Search, then estimate", |
|
4461 |
+ "description": "Debounced FTS5 autocomplete resolves the address; one call returns the full estimate with range, confidence grade, comparables, and roll characteristics." |
|
4462 |
+ }, |
|
4463 |
+ { |
|
4464 |
+ "title": "Search by map when the address is unknown", |
|
4465 |
+ "description": "MapKit surfaces nearby properties with preview estimates; the Manual tab covers municipality + type + characteristics without an exact address." |
|
4466 |
+ }, |
|
4467 |
+ { |
|
4468 |
+ "title": "Report like a professional", |
|
4469 |
+ "description": "The server renders the same 3-page and 6-page PDFs as the website; the app displays them in PDFKit and hands them to the share sheet." |
|
4470 |
+ }, |
|
4471 |
+ { |
|
4472 |
+ "title": "Prove it end to end", |
|
4473 |
+ "description": "Three XCUITests run against the production API — search → estimate, map → pin → card → estimate, and manual estimate — with screenshots generated automatically." |
|
4474 |
+ } |
|
4475 |
+ ], |
|
4476 |
+ "highlights": [ |
|
4477 |
+ "The same transparent engine as vrai-prix.com: every comparable, every dollar adjustment, every confidence interval", |
|
4478 |
+ "3,747,008 assessment units and 745,119 real sales behind every estimate — MdAPE 11.0%", |
|
4479 |
+ "End-to-end UI tests run against the production API, not mocks", |
|
4480 |
+ "The app icon is generated from the wordmark by a CoreGraphics + CoreText Swift script", |
|
4481 |
+ "Law 25 compliance and IAAO validation figures surfaced in the About tab", |
|
4482 |
+ "On TestFlight with automatic signing and App Store-ready metadata" |
|
4483 |
+ ] |
|
4484 |
+ }, |
|
4485 |
+ { |
|
4486 |
+ "slug": "lou-ka-ios", |
|
4487 |
+ "hero": { |
|
4488 |
+ "headline": "Every Quebec rental in your pocket — and a swipe that learns your taste", |
|
4489 |
+ "subheadline": "The native SwiftUI companion to lou-ka.com: 9,000+ live listings from 190+ sources with full filters, a province-wide map, market stats — and a Discover mode driven by an on-device recommender." |
|
4490 |
+ }, |
|
4491 |
+ "overview": [ |
|
4492 |
+ "Lou·Ka iOS is the 100% native companion to Quebec's rental aggregator — the same 9,000+ live listings, the same paper/ink/lime editorial design, connected directly to the production API. Five tabs cover the search: filterable listings, a swipe-based Discover mode, a MapKit province map, market statistics, and the registry of 255 property-manager sources.", |
|
4493 |
+ "Discover is the app's signature: one full-screen listing at a time with story-style photos — swipe right to love, left to pass. Each swipe feeds an on-device recommender that decomposes listings into features (city, neighbourhood, size, price band, furnished, pets, balcony, parking, manager), maintains Laplace-smoothed appreciation rates weighted by confidence, and re-ranks the stack every 8 decisions — with one card in six drawn from the wider pool to avoid a filter bubble. The taste profile is persisted locally and never leaves the device.", |
|
4494 |
+ "The full listing sheet carries everything the website shows: paginated gallery, facts, inclusions, structured description digest, map, nearby points of interest, neighbourhood statistics, and a direct link to the original ad. Decoding is deliberately tolerant — one malformed source never makes other listings disappear — and the app has zero external dependencies: SwiftUI, MapKit, and Observation are all it needs." |
|
4495 |
+ ], |
|
4496 |
+ "stats": [ |
|
4497 |
+ { "value": "9,000+", "label": "live listings" }, |
|
4498 |
+ { "value": "190+", "label": "sources aggregated" }, |
|
4499 |
+ { "value": "255", "label": "managers in the registry" }, |
|
4500 |
+ { "value": "5", "label": "native tabs" }, |
|
4501 |
+ { "value": "0", "label": "external dependencies" }, |
|
4502 |
+ { "value": "1/6", "label": "exploration cards vs. filter bubble" } |
|
4503 |
+ ], |
|
4504 |
+ "features": [ |
|
4505 |
+ { |
|
4506 |
+ "icon": "Filter", |
|
4507 |
+ "title": "Full filtering", |
|
4508 |
+ "description": "Debounced search, size chips, and complete filters — city, city-dependent neighbourhood, rent range, availability, pets, furnished, area, manager — with one-tap removable filter pills." |
|
4509 |
+ }, |
|
4510 |
+ { |
|
4511 |
+ "icon": "Sparkles", |
|
4512 |
+ "title": "Discover mode", |
|
4513 |
+ "description": "One full-screen listing at a time, story-style photos, swipe right to love — and the stack re-ranks continuously as the recommender learns." |
|
4514 |
+ }, |
|
4515 |
+ { |
|
4516 |
+ "icon": "Brain", |
|
4517 |
+ "title": "On-device recommender", |
|
4518 |
+ "description": "Laplace-smoothed appreciation rates per feature, confidence weighting, re-ranking every 8 decisions, and deliberate exploration to escape the filter bubble." |
|
4519 |
+ }, |
|
4520 |
+ { |
|
4521 |
+ "icon": "Map", |
|
4522 |
+ "title": "Province-wide map", |
|
4523 |
+ "description": "Lime pins on MapKit with city presets — Québec, Lévis, Montréal, Gatineau — and mini-cards that open the full listing." |
|
4524 |
+ }, |
|
4525 |
+ { |
|
4526 |
+ "icon": "BarChart3", |
|
4527 |
+ "title": "Market stats", |
|
4528 |
+ "description": "KPI tiles (median, mean, available now), rent histogram, region/city/size leaderboards, offer inclusions, and price drops." |
|
4529 |
+ }, |
|
4530 |
+ { |
|
4531 |
+ "icon": "ShieldCheck", |
|
4532 |
+ "title": "Robust by design", |
|
4533 |
+ "description": "Field-by-field tolerant decoding with fallbacks: a malformed source never makes the other listings disappear." |
|
4534 |
+ } |
|
4535 |
+ ], |
|
4536 |
+ "techStack": [ |
|
4537 |
+ { |
|
4538 |
+ "category": "Platform", |
|
4539 |
+ "items": ["Swift 5.9", "SwiftUI (iOS 17+)", "Observation", "XcodeGen", "TestFlight (com.spboucher.louka)"] |
|
4540 |
+ }, |
|
4541 |
+ { |
|
4542 |
+ "category": "Apple frameworks", |
|
4543 |
+ "items": ["MapKit", "UserDefaults persistence", "Zero SPM/Pods dependencies"] |
|
4544 |
+ }, |
|
4545 |
+ { |
|
4546 |
+ "category": "Design & data", |
|
4547 |
+ "items": ["Lou-Ka production API", "Editorial-sharp design system", "Space Grotesk + JetBrains Mono (static instances via fontTools)"] |
|
4548 |
+ } |
|
4549 |
+ ], |
|
4550 |
+ "architecture": [ |
|
4551 |
+ { |
|
4552 |
+ "title": "One API, one truth", |
|
4553 |
+ "description": "The app talks HTTPS to the production lou-ka.com API with the site's full filter set — same data, same prices, nothing invented." |
|
4554 |
+ }, |
|
4555 |
+ { |
|
4556 |
+ "title": "Decompose every listing", |
|
4557 |
+ "description": "The recommender breaks each listing into features: city, neighbourhood, size, $300 price band, furnished, pets, balcony, parking, and manager." |
|
4558 |
+ }, |
|
4559 |
+ { |
|
4560 |
+ "title": "Learn from every swipe", |
|
4561 |
+ "description": "Each feature keeps a Laplace-smoothed appreciation rate (👍+1)/(👍+👎+2), weighted by confidence 1+log(1+n); a listing's score is the weighted mean." |
|
4562 |
+ }, |
|
4563 |
+ { |
|
4564 |
+ "title": "Re-rank, but explore", |
|
4565 |
+ "description": "The stack re-sorts every 8 decisions while ~1 card in 6 comes from the wider pool — recommendations without a filter bubble." |
|
4566 |
+ }, |
|
4567 |
+ { |
|
4568 |
+ "title": "Keep taste on the device", |
|
4569 |
+ "description": "The whole profile persists locally and never leaves the phone; loved listings live under the heart button." |
|
4570 |
+ } |
|
4571 |
+ ], |
|
4572 |
+ "highlights": [ |
|
4573 |
+ "After 4 likes on ~$900 studios, the header already reads \"Your taste: Studio · ~$900\" and the stack adapts", |
|
4574 |
+ "The full listing sheet matches the website feature-for-feature, down to the link to the original ad", |
|
4575 |
+ "Forced light theme so the phone's dark mode never renders unreadable text over the paper/ink design", |
|
4576 |
+ "Space Grotesk static instances generated with fontTools because the variable font exposes no PostScript names on iOS", |
|
4577 |
+ "Zero external dependencies — SwiftUI, MapKit, Observation, and nothing else", |
|
4578 |
+ "On TestFlight (1.2), uploading straight from xcodebuild with automatic signing" |
|
4579 |
+ ] |
| 4100 |
4580 |
} |
| 4101 |
4581 |
]; |