What's Changed in v0.0.101
Features
- feat(chat): auto-send a standalone chat-mode voice dictation (2ed4fcb)
- feat(chat): auto-send a standalone voice note in Chat mode (ffed564)
- feat(chat): render voice-note transcription in the sent message (ba32700)
- feat(downloads): single-source queued vs downloading classifier (fd3f731)
- feat(app): top-level ErrorBoundary so a render error recovers instead of white-screening (d01869a)
- feat(downloads): clock icon for the Queued state via one status->icon helper (F24) (9a4c720)
- feat(device-info): show per-process memory — Available, Footprint, Process Limit (7dab9f1)
- feat(downloads): show queued (capped) downloads as "Queued" in the Download Manager (a11eac5)
- feat(downloads): cap concurrent downloads at 3 (FIFO queue) to stop the freeze (98fe071)
- feat(home): copy-link action on the Off Grid AI Desktop card (d878f40)
- feat(home): dismissable 'Off Grid AI Desktop' promo card (14d2ddb)
- feat(downloads): shared startModelDownload action — onboarding + Models screen, one mechanism (a684d8d)
- feat(chat): GenerationSession owns 'which conversation is generating' (refactor slice 1) (8453c4a)
- feat(dev): persistent on-device log file sink for device debugging (2f958d7)
- feat(chat): Eject All on the chat models sheet (was stubbed) via shared projection (45d1146)
- feat(models): single ejectAll side-effect owned by activeModelService (88b6199)
- feat(routing): log every intent-classifier verdict ([ROUTE-SM]) (eccd198)
- feat(routing): permanent [ROUTE-SM] logs on the image-vs-text decision (2d8bb73)
- feat(downloads): useModelDownloads — one reactive hook for every screen (749d72d)
- feat(downloads): register core providers + reconcile on boot (5accce5)
- feat(downloads): image provider — service-level list/remove/reconcile, injected cancel/retry (c08042e)
- feat(downloads): text provider — service-level wrap (incl. platform-branched retry) (89833a3)
- feat(downloads): STT provider — UI-free reference wrapping the working bridge (bbd580d)
- feat(downloads): downloadStore→ModelDownloadStatus mapper (single source) (c082c42)
- feat(downloads): app-kill reconciliation in the download state machine (cbba6ea)
- feat(downloads): ModelDownloadService — single owner + [DL-SM] state machine (8382c91)
- feat(recording): single owning record controller (fixes hero tap-to-stop) (aedb487)
- feat(audio): voice mode speaks the image-gen result (turnSpeech tests + pro bump) (c351f9f)
- feat(home): big per-type model count to the right of icon+label (c8d6476)
- feat(image): show the ~120s first-run warm-up notice on iOS too (9e4aeb2)
- feat(failures): dismissible ModelFailureCard surface (763e545)
- feat(failures): single owner for model failures (store + handler) (ef7d324)
- feat(diag): [IMG-SM] + [MEM-SM] state-machine traces (kept forever) (47650a0)
- feat(downloads): show in-progress voice downloads as active items in the Download Manager (987d996)
- feat(memory): kill-apps prompt + retry when eviction still can't fit a model (5b5636a)
- feat(home): per-type model counts in the Models card, chat count by See all (75d8fbe)
- feat(memory): smart-routing residency — co-residency, priority eviction, no OOM (48bdf3d)
- feat(memory): real per-process device budget (os_proc_available_memory), not a guess (a0ec55d)
- feat(llm): surface llama.cpp's real load error via native-log passthrough (571de95)
- feat(memory): strict-sequential model residency on ≤4GB (one heavy model at a time) (d6d713d)
- feat(residency): own the memoryWarning + a canEvict veto for in-use models (7f86ffc)
- feat(audio): add AudioSessionManager — single owner of the iOS AVAudioSession (757fec0)
- feat(image): honor preferGpu in CoreMLDiffusionModule (GPU<->ANE) (df6d39b)
- feat(image): pass the RAM-tier compute choice to the Core ML loader (bd30278)
- feat(image): pick iOS Core ML compute path by RAM tier (bea0e1b)
- feat: cross-platform showToast util + bump pro (voice-note streaming gate) (13eee9c)
- feat: faster iOS downloads, gateway discovery, STT-retry, model-switcher + Desktop links (27884cd)
Bug Fixes
- fix(downloads): route in-flight voice downloads to Active, not Downloaded Models (b07f673)
- fix(chat): make tool accordion tappable during active streaming (#37) (830c105)
- fix(image-download): publish queued store row before native start so the card shows queued (#33) (d8f2dfb)
- fix(kokoro): live-lifecycle download state (supersedes sentinel) + bump pro pointer (4895e0c)
- fix(chat): persist tool-accordion expand state across streaming→finalized remount (cf40369)
- fix(downloads): route every model-list surface through the shared classifier (51b4062)
- fix(downloads): mmproj sidecar no longer consumes a concurrency slot (single-download bug) + icon-only Queued (2f4e645)
- fix: address CodeRabbit review on #425 (stability hardening batch) (49a43d8)
- fix(android): declare dataSync foregroundServiceType on WorkManager's FGS host (F8) (54061a4)
- fix(remote): LM Studio always accepts enable_thinking (decouple from probe) (1a125a9)
- fix(litert): adopt the native-clamped context budget in JS (F20) (8bd20a6)
- fix(android): run model downloads as a foreground service so they survive backgrounding (F8) (78d77a2)
- fix(pro): make the Settings section registry reactive so Pro unlocks live (F6) (03c8392)
- fix(remote): address Qodo review of /props probe (dedup, timeout, logging) (0656a68)
- fix(audio): restore playback after realtime STT stop so TTS isn't silenced (F4) (ba13aa8)
- fix(remote): detect Gateway vision/thinking via /props and send thinking flag (ffb00ec)
- fix(chat): resume the turn after 'Load Anyway' on insufficient memory (F16) (8f0c7c4)
- fix(remote): trust gateway kind:'vision' so remote vision models read images (F7) (f8bd7d9)
- fix(models): serialize STT reclaim + memory-warning through the residency lock (F3) (710347e)
- fix(chat): open the model selector on the tab the manager row selected (F-SHEET) (c41c28c)
- fix(models): budget LiteRT text models as dirty memory to avoid OOM (F9) (26d34c8)
- fix(chat): skip the image classifier when no image model is selected (F12) (1fb969f)
- fix(downloads): purge finalized native record without a synthetic error (F2) (f6fa536)
- fix(downloads): re-reserve the concurrency slot on Android retry (F1) (7dcf708)
- fix(downloads): self-heal leaked concurrency slots so downloads don't collapse to 1 (F-SLOT-LEAK) (00f6072)
- fix(downloads): drop the obsolete 'Starting more can affect performance' gate (F24) (eba11f6)
- fix(downloads): route queued-cancel on the modelKey-derived id (F24) (11e05ff)
- fix(chat): show 'No Model Selected' alert on resend/edit when no model is loaded (94df143)
- fix(chat): treat an undefined snapshot field as 'not changed' (kills the residual false banner) (8dfca14)
- fix(llm): don't retry-without-tools if the failed attempt already streamed (7eb04aa)
- fix(downloads): address review — queued-cancel, mmproj name, list-card queued state (5f03685)
- fix(llm): recover from a remote tool-grammar 400 (retry without tools) + surface the error body (3297394)
- fix(downloads): show queued state everywhere + dedup double-tap (6eb2a39)
- fix(chat): stop the false reload banner on LiteRT model load (4a05af5)
- fix(chat): address qodo review — drop the duplicated reload memory gate (a0ac8d4)
- fix(chat): reload model without stranding the chat on OOM (5147946)
- fix(chat): tell the model not to comment on the injected date/time (1e9a8d6)
- fix(android): apply subproject NDK via plugins.withId, not afterEvaluate (adc3a2c)
- fix(remote): only basename-strip a model id when it is a real path (124b4b3)
- fix(downloads): unify the text download id on the modelKey (one identity per model) (e6c4f18)
- fix(remote): show a clean model name, not the raw file path (45fa624)
- fix(chat): memoize MessageRenderer to stop the chat-screen freeze (Bugs 1 + 4) (f503172)
- fix(chat): single now() snapshot so system date + exact-time note never disagree (34beecc)
- fix(downloads): idempotent finalize breaks the every-foreground re-finalize loop (bb8fed8)
- fix(downloads): classifier download clears its in-flight row (phantom "100%" fix, Bug 5) (5936d4a)
- fix(downloads): address qodo review on failed Kokoro voice items (7f16a53)
- fix(tools): validate hydrated embedding vectors, not just that they are arrays (515f113)
- fix(memory): register the embedding model inside the load lock (no unbudgeted window) (e6c2aa2)
- fix(image): cpuOnly maps to .cpuOnly, not .cpuAndGPU (CoreML compute-unit contract) (294b6ae)
- fix(ios): correct the DownloadInfo initializer in tests (iOS build was red) (056634d)
- fix(downloads): surface + retry failed Kokoro voice downloads (e9d5a7d)
- fix(downloads): STT retry restores a failed row if the re-download fails before registering (4110ecb)
- fix(memory): hydrate real device RAM before the image compute-path decision (18ecba3)
- fix(llm): memory guard uses the EFFECTIVE f16 cache + full dynamic context step-down (4090301)
- fix(chat): input controls no longer sit under the Android nav bar (22bfb84)
- fix(downloads): start the mmproj sidecar before the main to close the vision finalize-hang (73eace7)
- fix(image): cancel a queued multi-file image part immediately, and type it as image (36eb775)
- fix(chat): stop TTS when the user hits Stop, not just the LLM (d6f2b19)
- fix(downloads): cancellable queued downloads via the single queue owner (74ff0e9)
- fix(downloads): unified completion catch-up covers main GGUF, not just mmproj (fd3b413)
- fix(downloads): adopt only in-flight restored downloads (completed = permanent slot leak) (bfdc4cf)
- fix(memory): register image model as dirtyMemory so the resident-dirty gate fires (08acaf3)
- fix(image): "Free memory & Retry" actually frees memory before retrying (1dd64cd)
- fix(memory): gate every load on live os_proc RAM under dirty-memory pressure (OOM/jetsam) (8c349f2)
- fix(chat): close the model picker before loading, not after (5313ecd)
- fix(downloads): one entry per model — never show a model as both Active and Downloaded (56b3400)
- fix(downloads): re-queue interrupted iOS downloads on relaunch instead of failing them (a36399f)
- fix(pro): never upsell a Pro user — gate on real entitlement, not hasRegisteredPro (d2412a5)
- fix(ci): exclude all pro-importing tests from typecheck and test jobs (43735e0)
- fix(ui): tighten the 'Set Up Your AI' footer padding (3b009a5)
- fix(downloads): Kokoro sync - Download Manager reads TTS state from the service (fbf6716)
- fix(downloads): classify download state with the shared isActiveStatus (9579b3b)
- fix(downloads): single-source download id + in-progress predicate (93b5413)
- fix(downloads): validate multi-file image parts before registering (cc80f23)
- fix(downloads): clamp combined download progress to [0,1] (eab36ff)
- fix(downloads): platform retry hidden behind the provider; stable retry capability (baeef62)
- fix(audio): realtime Whisper routes the iOS session through audioSessionManager (6222a65)
- fix(tools): LiteRT tool path honors the never-throw contract (no silent lies/crashes) (e9cfc32)
- fix(routing): image intent matches article-less visual requests ('draw dog') (d3d1b67)
- fix(llm): drop missing image attachments before native generation (kills 'File does not exist') (e88ebdf)
- fix(models): tighten the Models sheet bottom padding (d893e18)
- fix(models): Eject All evicts from RAM but KEEPS the selection (526e7cd)
- fix(chat): bottom-bar loader sized to the mic + bottom gap matches the top (49edeec)
- fix(downloads): don't self-list (disk scan) when no consumer — fixes download-time tab lag (01a4738)
- fix(memory): budget mmap'd GGUF against physical RAM, not the dirty-memory limit (a593bd5)
- fix(residency): revert makeRoomFor to predictive — measure-after-evict refused valid loads (a4548b3)
- fix(downloads): correct case-sensitive huggingface import (broke Metro/Xcode build) (a39c5bf)
- fix(downloads): close the [DL-SM] log gaps so nothing changes state silently (ec2edb0)
- fix(downloads): authoritative op routing + honest image retry capability (SOLID audit) (8d23684)
- fix(downloads): don't run service reconcile at boot yet (races existing recovery) (d7a50b1)
- fix(tools): never let a tool failure silently lie to the model (typed result contract) (963404e)
- fix(stt): strip whisper no-speech markers so [BLANK_AUDIO] never becomes text (e8b472f)
- fix(audio): shorter bottom bar + mic-sized stop control (6c1efea)
- fix(home): tone down the model-count numeral (was too loud) (55d0421)
- fix(routing): image intent routes on a SELECTED model, not a resident one (3ca83fc)
- fix(residency): measure real freed RAM after eviction instead of predicting (e5551a2)
- fix(imagegen): single _fail owner surfaces errors to the user (no silent fail) (3eadb34)
- fix(ios): wire CODE_SIGN_ENTITLEMENTS into the project + fix launcher name (10846d6)
- fix(llm): bump llama.rn 0.12.4 → 0.12.5 (llama.cpp b9769 with Gemma MTP support) (eec6a05)
- fix(llm): stop forcing kv_unified so gemma3n (gemma-4) loads on GPU (e125952)
- fix(llm): apply GPU-init timeout on iOS too so a hung Metal load falls back to CPU (22eae12)
- fix(ios): add extended-virtual-addressing entitlement so >4GB models can mmap (d663f11)
- fix(memory): device + platform aware budget so 12GB phones aren't capped like 6GB (59aa177)
- fix(generation): typed model-readiness outcome instead of opaque boolean (3ffa243)
- fix(residency): reclaimSttForGeneration must never throw into the generation path (d7f4e8e)
- fix(memory): evict idle Whisper (STT) before generation — fixes voice-record OOM (89bde21)
- fix(llm): cap INITIAL context by device RAM (fixes 4GB jetsam kill on generate) (d865240)
- fix(residency): a sidecar must fit the budget by evicting PEER sidecars (e534852)
- fix(audio): stop killing streaming TTS mid-answer on tool-call rounds (7afbef9)
- fix(ios): cap Metal GPU offload by model size + free RAM (R3 — the #1 crash) (b82e65a)
- fix(audio): re-base stored Documents paths onto the current container (c2c6e28)
- fix(audio): re-assert the playback session every call (preserve old behavior) (6952c6e)
- fix(android): LiteRT vision-GPU gate + token-floor overcommit (CodeRabbit) (bdb582e)
- fix(downloads): mark orphaned downloads failed after app relaunch (5d49431)
- fix(memory): let resolveSafeContext settle above 4096 (11a4592)
- fix(models): don't highlight a local model while a remote model is current (9fb602b)
- fix(settings): only un-stick the EXACT boosted context values, not >= (1043af3)
- fix(image): show the generating card + enhance under one-at-a-time residency (2b64096)
- fix(image): load Core ML diffusion on GPU on iOS 26 (ANE degraded) (1dd9484)
- fix(image): right-size iOS Core ML image RAM estimate (96b82e5)
- fix(stt): single source of truth for transcription download state (c2173e6)
- fix(chat): make model switcher reflect the selected model under deferred loading (421be2f)
- fix(android): harden LiteRT engine load against OOM + Mali GPU crashes (R7, F8) (a7ee32f)
- fix(ios): marshal background-downloader event emission to main thread (F10) (94f27d8)
- fix(memory): correct KV estimate + downgrade context instead of crashing (R3, R4) (a802efd)
- fix(memory): budget the embedding model + bound its load (R2, R5) (ccbb7fc)
- fix(settings): un-stick users pinned at the boosted 32k context (10236e3)
- fix(mcp): remove context auto-boost (primary stability/perf regression) (da072bd)
Refactors
- refactor(remote): gate enable_thinking on a discovered capability, not the port (fb01509)
- refactor(pro): retire the monthly tier, single-source tier semantics as data (7f72fdc)
- refactor(image): single-source the memory-pressure detection for "Free memory & Retry" (b9aee47)
- refactor(home): extract DesktopPromoCard so it owns its copy/dismiss state (c506cce)
- refactor(models): one recommended-models source both screens filter from (11f4c21)
- refactor(llm): extract hasVisionInputs() — named, testable vision-mode seam (2c9254e)
- refactor(downloads): Download Manager delegates to the service; remove dead retry code (094a277)
- refactor(failures): extract UI-free model-failure reasons module (99600a8)
- refactor(residency): extract selectEvictionVictim as the single eviction-order rule (4f3c564)
- refactor(image): route failures through the common card, not chat (89ce7b6)
- refactor(imagegen): explicit phase state machine as the single source of truth (dc50419)
- refactor(downloads): model capability gaps as data, not stubbed methods (b6aee1e)
- refactor(audio): route the recorder's AVAudioSession through AudioSessionManager (3450c9f)
- refactor(downloads): extract retry handlers into their own module (9af76d5)
Chores
- chore: bump version to 0.0.101 [skip ci] (c46002d)
- chore(pro): bump pointer to c38eb900 (kokoro runtime-vs-completeness fix) (3ebefd2)
- chore(android): rebuilt Hexagon HTP libs for Gemma 4 NPU support (c61ac3a)
- chore(lint): mark useChatGenerationActions max-lines-exempt (matches litert/download convention) (15a5340)
- chore(lint): drop the now-unused uniformDownloadId import + stale eslint-disable (1ae4739)
- chore(pro): bump submodule to eacda784 (pro#8 + pro#13) (f651bd1)
- chore(lint): trim comments to keep both files under the 500-line cap (8d25072)
- chore(pro): bump submodule to 5ed8e4b9 (audio view-model + Kokoro download-state fixes) (d20d006)
- chore(memory): log raw os_proc available/total in the [MEM-SM] fit decision (f94e9b4)
- chore(lint): clear unused test vars and prefer-template in the DL-SM log (ae7b321)
- chore(pro): bump submodule to 63d1cf52 (Kokoro download-completeness fix + audio sync) (66ea85c)
- chore(desktop): use the exact http scheme for the Off Grid AI Desktop link (d9b9a81)
- chore(desktop): point Off Grid AI Desktop link at getoffgridai.co/desktop (c2b9dd3)
- chore: bump pro to bottom-bar fixed-slot fix (no height fluctuation) (9140a1d)
- chore: bump pro to recording-controller hero mic + bridge removal (2e6ce8a)
- chore(brand): rename app name to 'Off Grid AI' everywhere (iOS+Android launcher, splash, in-app, README) (96c5bd9)
- chore: bump pro submodule to latest audio fixes (canEvict seam, budget-aware streaming) (63c545e)
- chore: bump pro submodule to audio stability + memory fixes (681cde0)
- chore(diag): [MEM-SM] resident-model + RAM dump on resend; residency STT-reclaim helper (5a8bdab)
- chore(chat): [RESEND-SM] trace on the resend/regenerate path (7fb1ed9)
- chore(audio): log every iOS AVAudioSession (re)assert in the [TTS-SM] trace (9a9e0d3)
- chore(ios): auto-detect the connected device in ios-device.sh (dab5e88)
- chore: gitignore marketing/ and untrack the devto drafts (a0443b6)
- chore: bump pro — gate voice-note play from generation start (b348a45)
- chore: bump pro — stop file playback when streaming TTS starts (c7c8944)
- chore: bump pro — auto-resume voice-note playback after screen lock (72f4e77)
Tests
- test(downloads): edge cases for voice section routing + engine cold-start (ee113c8)
- test(kokoro): pin runtime-readiness is not download-completeness (7a28e16)
- test(generation): pin remote-failure loading clear + queued-send feedback (#29) (1727895)
- test(kokoro): fresh download discards stale hydrated completion, reads downloading (b44fc75)
- test(kokoro): integration — DM/Voice-tab read one source, mid-download never reads completed (85a5a6f)
- test(kokoro): cover sentinel-gated download completeness; bump pro pointer (1fc74fa)
- test(downloads): assert finalize purge uses purgeNativeRecord, not cancelDownload (F2) (68e470d)
- test(kokoro): cover the benign-collision phase reconcile + bump pro pointer (F23) (fa81bcc)
- test(ci): run pro-dependent suites against the real pro submodule when present (30019a7)
- test(chat): assert TTFT precise-time note lands on latest user message (5226d40)
- test(downloads): complete the backgroundDownloadService mock with the queue contract (7d8885b)
- test: mock adoptActive + setProActive in the two suites that exercise them (52a0da7)
- test(audio): a paused clip maps to mic, not tts-stop (dd47aad)
- test(audio): resetForEngineLoss cancels an in-flight voice switch (3f8d443)
- test(audio): deriveAudioActivity precedence (generation > playback > mic) (ffeb86b)
- test(tts): live download wins over on-disk presence (Kokoro 3% vs completed) (c02b1dc)
- test(tts): Kokoro disk-completeness covers the full active-voice asset set (8c1a4dd)
- test: refresh stale RNTL tests after component evolution (ff8abc4)
- test(downloads): assert service-boundary dispatch in Download Manager; cover Android retry in the provider (c0e1725)
- test(integration): startModelDownload through the real stores (7ee885c)
- test: integration coverage for this session's changes (eject + generation-session projection) (fb071ec)
- test(audio): deleteModels clears a stuck isSwitchingVoice flag (19af749)
- test(audio): direct unit coverage for the playback state machine (single owner) (eb39a3e)
- test(audio): bridge re-register mid-playback keeps phase 'processing' (7d96744)
- test(audio): Kokoro speak() resolves at audio end, not synthesis end (21fe133)
- test(audio): voice switch clears the flag on success/error/timeout (hang regression) (632f157)
- test(audio): Voice panel reflects the single download service (mismatch regression) (eb8c374)
- test(audio): cover single-owner playback machine (a4a18a1)
- test(mcp): never-throw execute contract + bump pro to MCP error-surfacing fixes (724a3bf)
- test(memory): image-gen-in-audio eviction sequence (the litmus test) (db2af49)
- test(audio): caption labels (Waiting for {model} / Streaming voice response) + bump pro (c729940)
- test(audio): budget-aware streaming warm-up (warm if budget, speak-after if not, once per turn) (1035cca)
- test(audio): state-machine + log-sequence tests (so we're not surprised on device) (4ada14f)
- test(audio): PlayButton stays touchable when paused/playing while loading (9c81b32)
- test(audio): cover Kokoro on-demand reload + streaming-drain wedge recovery (2599f31)
Documentation
- docs(claude): mocks-sparingly + SOLID real-abstraction-layers as standing test/design expectations (f3d403e)
- docs: manual test plan for the branch - SOLID/abstraction/service changes + what to test on device (4755fe1)
- docs(architecture): three pre-edit questions — abstract only with a real seam (YAGNI), apply SOLID, single source of truth (d7d5520)
- docs(architecture): platform abstraction principle — no iOS-only/Android-only bugs (4e0a969)
- docs: SOLID / first-principles / fix-the-seam principles for shared-resource subsystems (654b695)
- docs(audio): refine AudioPlaybackService plan from the device-trace findings (a3080aa)
- docs: note that all Pro feature code lives in the pro submodule (f2ace2a)
- docs: capture session-2 device-testing findings (TTS jetsam, downloads, kokoro playback) (0741c8a)
- docs: record deferred-load crash + model-switcher fixes (b3b9932)
- docs: mark F8/F10 done, F9 dropped; record image-gen root-cause analysis (7c8bf53)
- docs: mark stability plan Phases 0-2 complete (5e398d5)
- docs: add iOS+Android stability & performance fix plan (3586e7b)
- docs: point mobile privacy policy to canonical getoffgridai.co/privacy (e65db82)
CI/CD
- ci: fix red Android jobs — align NDK across modules, install it, reclaim disk (66bdcd4)
Other
- perf(downloads): stop the Download Manager re-subscribing every progress tick (F14) (6e575ab)
- perf(chat): keep tool/date prompt prefix cacheable, fixing TTFT (cbf9ad9)
- style(remote-servers): design tokens for the desktop link, not hardcoded values (44f7634)
- content(pro): pricing is $49/yr or $69 lifetime, not the retired monthly plan (d208244)
- content(home): tighten the Desktop card copy (~half length) (b4d34ed)
- content(home): outcome-led copy for the Off Grid AI Desktop card (e0dc6de)
- docs+scaffold(downloads): ModelDownloadService reference architecture + uniform contracts (788eeea)
- Revert "feat(memory): strict-sequential model residency on ≤4GB (one heavy model at a time)" (ee9ff69)
- revert(image): restore flat 2.5x image RAM estimate (2.0x caused OOM) (76fcf1f)
- perf(tools): persist tool-embedding cache to cut first-message TTFT (R6) (df6619f)
Full Changelog: v0.0.100...v0.0.101