Skip to content
Open
Show file tree
Hide file tree
Changes from 156 commits
Commits
Show all changes
405 commits
Select commit Hold shift + click to select a range
2076e18
refactor(release): simplify whisper cancellation owner
alichherawalla Aug 30, 2026
e7f0764
feat(remote): select Desktop models by media kind
alichherawalla Aug 30, 2026
db838f1
refactor(ui): compact model and tab surfaces
alichherawalla Aug 30, 2026
efbe3e7
fix(sync): harden mobile pairing and residency contracts
alichherawalla Aug 30, 2026
9890e4b
ci: guard private source checkout
alichherawalla Aug 30, 2026
0f59aa4
chore(pro): update sync and model surfaces
alichherawalla Aug 30, 2026
73b6a67
refactor(models): use verified status icon
alichherawalla Aug 30, 2026
af68364
fix(downloads): keep auto setup jobs in download manager
alichherawalla Aug 30, 2026
ba148c3
fix(models): distinguish verified and downloaded status
alichherawalla Aug 30, 2026
3159a31
style(home): add breathing room between sections
alichherawalla Aug 30, 2026
59702a7
style(home): loosen recent conversation spacing
alichherawalla Aug 30, 2026
dd9754b
feat(home): invite product ideas by email
alichherawalla Aug 31, 2026
6ecc51c
fix(tasks): use shared activity disclosure
alichherawalla Aug 31, 2026
e733d7b
feat(ui): add accordion content preview
alichherawalla Aug 31, 2026
7cfed6e
fix(ios): enable Metro Fast Refresh on devices
alichherawalla Aug 31, 2026
9879d38
fix(chat): render collapsed thinking as markdown
alichherawalla Aug 31, 2026
efa05be
test(tasks): cover completed companion results
alichherawalla Aug 31, 2026
c3065b6
test(tasks): verify desktop replay timing parity
alichherawalla Aug 31, 2026
ce1b3a2
fix(voice): preserve first remote turn
alichherawalla Aug 31, 2026
c136e47
fix(remote): restore selected model capabilities
alichherawalla Aug 31, 2026
1a0ade9
test(voice): cover remote mode readiness
alichherawalla Aug 31, 2026
dc5e1f7
refactor(models): use shared residency service
alichherawalla Aug 31, 2026
f10281d
chore(models): remove mobile residency policy copies
alichherawalla Sep 1, 2026
47208fa
refactor(models): use shared memory budget policy
alichherawalla Sep 1, 2026
3e2c736
refactor(models): compose shared llm service on mobile
alichherawalla Sep 1, 2026
0fb4f94
fix(models): admit text models by calculated memory
alichherawalla Sep 1, 2026
0203ea7
refactor(models): route mobile selections through shared service
alichherawalla Sep 1, 2026
72e0de6
refactor(models): unify mobile media selection
alichherawalla Sep 1, 2026
54a12cf
refactor(models): subscribe to residency changes
alichherawalla Sep 1, 2026
8c931be
refactor(models): project mobile model state from llm service
alichherawalla Sep 1, 2026
42de944
feat(models): publish mobile sidecar routes
alichherawalla Sep 1, 2026
dfaa1b2
feat(models): compose shared mobile generation
alichherawalla Sep 1, 2026
ad9b5cd
refactor(chat): route basic mobile generation through shared service
alichherawalla Sep 1, 2026
8dd2fd1
chore(voice): remove stale engine coverage
alichherawalla Sep 1, 2026
361a952
refactor(chat): share mobile tool generation
alichherawalla Sep 1, 2026
aec5ebe
refactor(image): route mobile generation through shared service
alichherawalla Sep 1, 2026
3e07dc6
chore(image): remove legacy generation bypass
alichherawalla Sep 1, 2026
60e32a0
refactor(reasoning): apply shared mobile policy
alichherawalla Sep 1, 2026
e70ec5a
refactor(voice): route transcription through shared generation
alichherawalla Sep 1, 2026
2e703c9
refactor(voice): route live transcription through shared generation
alichherawalla Sep 1, 2026
04ffd87
refactor(models): route mobile voice generation through shared service
alichherawalla Sep 1, 2026
20ecea7
chore(pro): advance voice generation integration
alichherawalla Sep 1, 2026
710c27b
refactor(models): route mobile sidecars through shared generation
alichherawalla Sep 1, 2026
e6474d1
fix(models): keep sidecars behind shared execution
alichherawalla Sep 1, 2026
f1d68fd
refactor(rag): remove shared logic facades
alichherawalla Sep 1, 2026
09bbd8e
refactor(downloads): use shared orchestration rules
alichherawalla Sep 1, 2026
e3d0c0b
test(rag): exercise shared embedding contract
alichherawalla Sep 1, 2026
68c71ad
refactor(models): use shared vision repair policy
alichherawalla Sep 1, 2026
57bc003
refactor(models): remove mobile residency facade
alichherawalla Sep 1, 2026
9627b8b
refactor(models): use shared selection recovery
alichherawalla Sep 1, 2026
b421a0e
refactor(models): use shared load-policy projection
alichherawalla Sep 1, 2026
9b9ef9e
refactor(models): use shared registry identity
alichherawalla Sep 1, 2026
67f0a3b
fix(models): use residency bootstrap paths
alichherawalla Sep 1, 2026
e035619
refactor(rag): use shared retrieval policy
alichherawalla Sep 1, 2026
6231e75
test(rag): fake the embedding adapter boundary
alichherawalla Sep 1, 2026
de87afa
refactor(llm): share portable generation policy
alichherawalla Sep 1, 2026
028f43b
refactor(models): share mobile transcription policy
alichherawalla Sep 1, 2026
42e16bc
refactor(models): split mobile native lifecycle
alichherawalla Sep 1, 2026
bdbe575
refactor(models): share mobile image prompt policy
alichherawalla Sep 1, 2026
5b46a3e
refactor(downloads): use shared model registry
alichherawalla Sep 1, 2026
d2ad755
refactor(remote): use shared mobile control plane
alichherawalla Sep 1, 2026
5f06f80
test(remote): cover shared mobile projections
alichherawalla Sep 1, 2026
0b4a063
refactor(downloads): use shared projector naming
alichherawalla Sep 1, 2026
b10cad6
refactor(remote): isolate mobile transport adapters
alichherawalla Sep 1, 2026
c2c1e61
test(remote): follow adapter boundaries
alichherawalla Sep 1, 2026
aa8bdd4
refactor(remote): thin mobile composition roots
alichherawalla Sep 1, 2026
3157519
test(remote): follow mobile composition seams
alichherawalla Sep 1, 2026
b50bd45
refactor(models): share mobile tool policy
alichherawalla Sep 1, 2026
0e73491
refactor(tools): use shared core catalog
alichherawalla Sep 1, 2026
ff0133e
feat(downloads): wire shared coordinator to native ports
alichherawalla Sep 1, 2026
f3c64e1
refactor(generation): remove legacy mobile tool loop
alichherawalla Sep 1, 2026
f62e501
refactor(downloads): replace mobile queue with shared coordinator
alichherawalla Sep 1, 2026
88818e6
refactor(models): relocate filesystem adapters
alichherawalla Sep 1, 2026
f033be6
refactor(rag): isolate mobile storage and runtime adapters
alichherawalla Sep 1, 2026
eef9c57
test(downloads): cover native transfer adapter
alichherawalla Sep 1, 2026
9d6ce67
fix(generation): keep voice notes transcript-only
alichherawalla Sep 1, 2026
10acad4
refactor(rag): wire mobile through shared service
alichherawalla Sep 1, 2026
b3ea2ad
chore(pro): update shared RAG consumer
alichherawalla Sep 1, 2026
e81adeb
fix(generation): expose portable tools on local routes
alichherawalla Sep 1, 2026
0dbf9b0
test(generation): assert final tool outcomes
alichherawalla Sep 1, 2026
8e1e379
refactor(models): consume shared image transfer policy
alichherawalla Sep 1, 2026
26dc83d
refactor(models): replace mobile model manager facade
alichherawalla Sep 1, 2026
5013439
test(models): follow mobile model library boundaries
alichherawalla Sep 1, 2026
cb13464
refactor(models): use shared library recovery policy
alichherawalla Sep 1, 2026
74e87c9
refactor(models): use shared registry hydration policy
alichherawalla Sep 1, 2026
96d0cb9
refactor(models): route mobile utility generation through shared service
alichherawalla Sep 1, 2026
5b9ea0c
refactor(models): use shared projector and vision lifecycle
alichherawalla Sep 1, 2026
9dfe05e
refactor(models): use shared download recovery policy
alichherawalla Sep 1, 2026
9fbe689
refactor(models): use shared download finalization gate
alichherawalla Sep 1, 2026
20e1630
refactor(models): use shared image recovery actions
alichherawalla Sep 1, 2026
38b06a2
refactor(models): use shared model library registry
alichherawalla Sep 1, 2026
a2cb34a
test(generation): compose migrated mobile model boundaries
alichherawalla Sep 1, 2026
c57f0e2
fix(downloads): reconcile durable native transfers
alichherawalla Sep 1, 2026
113e2bf
fix(models): preserve guarded LiteRT onboarding
alichherawalla Sep 1, 2026
c777211
fix(classifier): preserve exact model swap routing
alichherawalla Sep 1, 2026
34e6cae
test(models): assert residency by route modality
alichherawalla Sep 1, 2026
3834e34
test(projects): reach post-compaction context overflow
alichherawalla Sep 1, 2026
84de9a8
test(rag): assert exact embedding route and stages
alichherawalla Sep 1, 2026
1ea9fcc
fix(models): restore picker memory guidance
alichherawalla Sep 1, 2026
4e227aa
fix(generation): pass sampler settings to shared requests
alichherawalla Sep 1, 2026
b8f1118
test(models): follow accessible detail back action
alichherawalla Sep 1, 2026
abd0975
fix(chat): keep reasoning segments and clear stale failures
alichherawalla Sep 1, 2026
8aef772
fix(audio): preserve shared voice and transcription boundaries
alichherawalla Sep 1, 2026
3871b3c
test(models): follow new-chat preparation policy
alichherawalla Sep 1, 2026
8ba65ad
test(remote): use complete server and timeout boundaries
alichherawalla Sep 1, 2026
6f1bcf0
fix(image): cancel active generation once
alichherawalla Sep 1, 2026
01b2797
test(generation): compose share prompt streaming boundary
alichherawalla Sep 1, 2026
2010f4d
fix(models): retain facts in dense model cards
alichherawalla Sep 1, 2026
8351540
fix(models): keep curated download callback synchronous
alichherawalla Sep 1, 2026
e342375
fix(stt): preserve completed download and selected route
alichherawalla Sep 1, 2026
ce08a4b
fix(onboarding): identify network model choices
alichherawalla Sep 1, 2026
fcf1e29
test(models): follow shared image selection route
alichherawalla Sep 1, 2026
c41bf16
test(models): target current loading boundary
alichherawalla Sep 1, 2026
4cc73e6
test(models): follow preloader service boundaries
alichherawalla Sep 1, 2026
e54a395
test(runtime): follow current remote and restricted-sync journeys
alichherawalla Sep 1, 2026
9816497
test(models): reconcile override residency state
alichherawalla Sep 1, 2026
a94d3a5
fix(sync): preserve out-of-order live turns
alichherawalla Sep 1, 2026
36dc67d
test(models): project active transcription route
alichherawalla Sep 1, 2026
c88c7e5
test(models): assert current memory guidance
alichherawalla Sep 1, 2026
9f77752
test(audio): follow native lifecycle and network copy
alichherawalla Sep 1, 2026
b3ec83d
test: align mobile heap with CI
alichherawalla Sep 1, 2026
bbc19dd
test(models): ratify image hardware preflight
alichherawalla Sep 1, 2026
4843843
test(chats): select models through shared service
alichherawalla Sep 1, 2026
c974221
test(models): follow shared home route projections
alichherawalla Sep 1, 2026
d16e3ba
test(models): update home model journey contracts
alichherawalla Sep 1, 2026
8f328e7
test(models): compose advanced setup with model library
alichherawalla Sep 1, 2026
8be42a9
test(models): stop async owners before teardown
alichherawalla Sep 1, 2026
8df182b
test(models): follow current mobile selection seams
alichherawalla Sep 1, 2026
20606db
fix(models): make mobile route selection authoritative
alichherawalla Sep 1, 2026
b76e477
fix(models): route whisper lifecycle through shared residency
alichherawalla Sep 1, 2026
3359b9c
refactor(models): centralize classifier residency
alichherawalla Sep 1, 2026
9acf5c0
refactor(image): consume shared generation policy
alichherawalla Sep 1, 2026
0f8173d
refactor(tools): keep mobile tool boundary thin
alichherawalla Sep 1, 2026
22e6c65
refactor(image): remove progress policy facade
alichherawalla Sep 1, 2026
938afa3
fix(models): route remote UI selection through llm service
alichherawalla Sep 1, 2026
1a72cf4
refactor(models): project shared memory policy on mobile
alichherawalla Sep 1, 2026
b1c87bf
fix(models): route image selection through llm service
alichherawalla Sep 1, 2026
cde1ee2
refactor(models): centralize mobile selection intent
alichherawalla Sep 1, 2026
7afa5a2
refactor(models): keep remote capability adapters thin
alichherawalla Sep 1, 2026
8562706
refactor(chat): route mobile text turns through shared session
alichherawalla Sep 1, 2026
4f0cae4
refactor(models): thin mobile generation adapters
alichherawalla Sep 1, 2026
bf1cd6f
refactor(chat): route mobile turns through shared session
alichherawalla Sep 1, 2026
1ca5ab8
refactor(models): migrate app residency callers
alichherawalla Sep 1, 2026
a5a945f
refactor(models): thin mobile download recovery adapters
alichherawalla Sep 1, 2026
fce95b1
refactor(downloads): centralize mobile lifecycle policy
alichherawalla Sep 1, 2026
0c76a39
refactor(downloads): remove lifecycle policy from hooks
alichherawalla Sep 1, 2026
6f0550f
refactor(downloads): route model screen cancellation
alichherawalla Sep 1, 2026
9685d8c
refactor(downloads): persist retry failures in service
alichherawalla Sep 1, 2026
a2275d5
refactor(models): coordinate mobile text artifacts in shared
alichherawalla Sep 1, 2026
a76511a
test(models): enforce mobile architecture boundaries
alichherawalla Sep 1, 2026
9de6f53
refactor(models): thin mobile application policy
alichherawalla Sep 1, 2026
5f28612
refactor: delegate mobile download lifecycle to shared
alichherawalla Sep 1, 2026
943db86
refactor(models): route mobile runtime intents through ports
alichherawalla Sep 1, 2026
591c75e
refactor(models): keep image descriptors below UI
alichherawalla Sep 1, 2026
05fdfa8
refactor(models): remove transcription route facade
alichherawalla Sep 1, 2026
29930fb
refactor(models): remove mobile provider control plane
alichherawalla Sep 1, 2026
122beac
refactor(models): thin mobile residency adapters
alichherawalla Sep 1, 2026
fdbcbcc
docs(models): record control-plane gaps and voice support
alichherawalla Sep 1, 2026
7e875fb
refactor: remove mobile service import cycles
alichherawalla Sep 1, 2026
9c48a62
fix: serialize mobile model lifecycle races
alichherawalla Sep 1, 2026
008343f
refactor(models): route mobile chat through shared session
alichherawalla Sep 1, 2026
4a3829a
refactor(images): delegate lifecycle to shared models
alichherawalla Sep 1, 2026
5061252
refactor(models): use shared image download workflow
alichherawalla Sep 1, 2026
072c549
refactor(models): enforce one mobile selection authority
alichherawalla Sep 1, 2026
3905b18
docs(models): record consolidation verification gaps
alichherawalla Sep 1, 2026
a2aaa51
fix(images): preserve routes across stop and replay
alichherawalla Sep 1, 2026
4071dcd
fix(chat): scope stopped and orphaned turns
alichherawalla Sep 1, 2026
850a0d4
refactor(tools): delegate routing and loops to shared models
alichherawalla Sep 1, 2026
dc48e5f
test(tools): verify shared MCP schema policy
alichherawalla Sep 1, 2026
501c4df
refactor(models): project shared catalog policy
alichherawalla Sep 1, 2026
d5f1e26
docs(models): record consolidation verification gates
alichherawalla Sep 1, 2026
878919e
refactor(models): centralize transcription residency workflow
alichherawalla Sep 1, 2026
303d37e
use shared mobile text runtime workflows
alichherawalla Sep 1, 2026
11a5e2e
docs(models): record platform integration sweep
alichherawalla Sep 1, 2026
26ccd87
refactor(models): route remote control through shared policy
alichherawalla Sep 1, 2026
a994731
refactor(models): route mobile setup through shared policy
alichherawalla Sep 1, 2026
b7ea42d
refactor(audio): enforce shared voice authority
alichherawalla Sep 1, 2026
b7c652f
fix(models): sync only portable model settings
alichherawalla Sep 1, 2026
f55b74a
refactor(models): enforce shared mobile selection authority
alichherawalla Sep 1, 2026
363a629
refactor(models): delegate load policy transitions
alichherawalla Sep 1, 2026
efd262d
refactor(models): delegate failure and download status policy
alichherawalla Sep 1, 2026
454c8f8
refactor(models): use canonical residency lifecycle
alichherawalla Sep 1, 2026
d8290d6
refactor(chat): delegate model readiness to shared
alichherawalla Sep 1, 2026
52ba4e9
refactor(models): project shared text configuration
alichherawalla Sep 1, 2026
c0b79bd
refactor(chat): remove direct model preload bypasses
alichherawalla Sep 1, 2026
904dab7
refactor(chat): remove obsolete load resume seam
alichherawalla Sep 1, 2026
3e74937
refactor(models): delegate mobile library commands
alichherawalla Sep 1, 2026
c93c64f
test(models): guard mobile library boundaries
alichherawalla Sep 1, 2026
1aff37e
refactor(models): project shared native runtime defaults
alichherawalla Sep 1, 2026
d9c1c4c
fix(chat): resume only after forced load succeeds
alichherawalla Sep 1, 2026
3f660c2
route mobile artifact checks through shared
alichherawalla Sep 1, 2026
1783a7d
docs: record model architecture closure gaps
alichherawalla Sep 1, 2026
3733f0d
refactor(models): project catalog filters from shared
alichherawalla Sep 1, 2026
83926bd
docs(voice): remove obsolete TTS runtime guidance
alichherawalla Sep 1, 2026
9aa4660
refactor(models): remove mobile engine policy facade
alichherawalla Sep 1, 2026
23da5de
test(models): prove live LiteRT thinking route
alichherawalla Sep 1, 2026
4cf5303
refactor(models): remove mobile catalog policy copy
alichherawalla Sep 1, 2026
e67ac34
docs: record strict model boundary gaps
alichherawalla Sep 1, 2026
a8fa261
docs: record desktop model boundary gaps
alichherawalla Sep 1, 2026
ddb9be9
refactor(rag): consume shared runtime profile
alichherawalla Sep 1, 2026
61c41a5
docs: prioritize model boundary remediation
alichherawalla Sep 1, 2026
89362f1
refactor(models): centralize mobile model defaults
alichherawalla Sep 1, 2026
271fe5d
refactor(models): delegate image context selection
alichherawalla Sep 1, 2026
6661afa
refactor(models): reduce mobile LLM to native ports
alichherawalla Sep 1, 2026
3b31d36
test(models): guard transfer policy boundary
alichherawalla Sep 1, 2026
22ee946
chore(pro): advance model transfer policy
alichherawalla Sep 1, 2026
3590988
refactor(models): delegate mobile download workflow
alichherawalla Sep 1, 2026
116cbc9
refactor(models): route mobile remote control through shared
alichherawalla Sep 1, 2026
cd13fbe
test(models): guard mobile remote authority
alichherawalla Sep 1, 2026
5e79d86
refactor(models): route import and repair through shared commands
alichherawalla Sep 1, 2026
bc27a31
test(models): guard shared import command ownership
alichherawalla Sep 1, 2026
da0b405
refactor(mobile): consume shared tool orchestration
alichherawalla Sep 1, 2026
6106ba6
refactor(models): route mobile lifecycle through shared
alichherawalla Sep 1, 2026
2027c26
test(models): guard mobile lifecycle boundaries
alichherawalla Sep 1, 2026
cf61a00
refactor(models): lazy selection, typed failures and adapters behind …
alichherawalla Sep 2, 2026
b5652ae
fix(whisper): separate selection from load ownership
alichherawalla Sep 2, 2026
18dd376
fix(downloads): stale download and clear operations cannot overwrite …
alichherawalla Sep 2, 2026
ae70e42
refactor(services): remote discovery, tools and vision capability evi…
alichherawalla Sep 2, 2026
11f8f81
refactor(ui): chat and models screens project canonical model failures
alichherawalla Sep 2, 2026
617609d
docs(mobile): model-control consolidation notes and gaps
alichherawalla Sep 2, 2026
5ca2797
chore(mobile): remaining consolidation files
alichherawalla Sep 2, 2026
79bdd8d
chore(pro): bump mobile-pro to model-control consolidation
alichherawalla Sep 2, 2026
0178864
fix(tools): decode HTML entities in one pass so escaped entities stay…
alichherawalla Sep 2, 2026
8a75bb7
test(image): match the backend label literally instead of a hand-esca…
alichherawalla Sep 2, 2026
e0800d2
chore(e2e): pass page data as CDP arguments instead of splicing it in…
alichherawalla Sep 2, 2026
bdeb019
ci: grant the workflow token read-only contents permission
alichherawalla Sep 2, 2026
06d0683
fix(e2e): keep the WebDriverAgent launcher alive for the server's lif…
alichherawalla Sep 2, 2026
ea86421
fix(chat): persist the conversation history only when it changes, not…
alichherawalla Sep 2, 2026
dcccc52
test(sync): a streaming reply does not rescan shared files; a committ…
alichherawalla Sep 2, 2026
a564f5c
build(ios): bound the Metro probe at launch and allow a reachable Met…
alichherawalla Sep 2, 2026
7404e88
fix(home): subscribe to the conversations Home reads, not the whole c…
alichherawalla Sep 2, 2026
06ac861
fix(generation): restore main's token batching in the chat projection…
alichherawalla Sep 2, 2026
5ecc070
fix(chat): cap the collapsed thinking preview at 80 characters again,…
alichherawalla Sep 2, 2026
e2c66c5
fix(ui): the three-dot loader bounces in a travelling wave instead of…
alichherawalla Sep 2, 2026
c747aff
fix(ui): the loader moves like desktop's (quarter-height bounce, 1s c…
alichherawalla Sep 2, 2026
6433d20
chore(pro): bump mobile-pro for the shared loader
alichherawalla Sep 2, 2026
1fa7ee8
feat(splash): launch screens use the shadowed brand logo, with a dark…
alichherawalla Sep 2, 2026
d2d6ce4
fix(ui): loader rises half a dot so the bounce is visible at mobile s…
alichherawalla Sep 2, 2026
ea8e655
feat(brand): deeper mark shadow in the logo source, re-rendered for s…
alichherawalla Sep 2, 2026
efec8df
fix(ui): the loader is one native loop with phase-shifted dots, so a …
alichherawalla Sep 2, 2026
1f2cc92
docs: mirror the shared doctrine (shared owns model business logic)
alichherawalla Sep 2, 2026
aa8cb26
fix(chat): let the shared operation service decide the route before a…
alichherawalla Sep 2, 2026
f27ca2c
fix(chat): no 'model unloaded' bar after a stopped image turn; only a…
alichherawalla Sep 2, 2026
4547520
docs: refresh the mirrored doctrine notice; bump mobile-pro for the r…
alichherawalla Sep 2, 2026
08b2ccb
feat(settings): prompt enhancement is a first-level image setting, no…
alichherawalla Sep 2, 2026
3423561
ios: drop CoreML diffusion self-unload on memory warning
alichherawalla Sep 2, 2026
c1bcd2c
chat: resend and edit run with the thinking setting in force now
alichherawalla Sep 2, 2026
254da61
docs: mirror the shared doctrine (where the logs live)
alichherawalla Sep 2, 2026
e3174c9
chat: log the policy-to-wire reasoning decision per turn; note Metro'…
alichherawalla Sep 2, 2026
ea263c0
chore: bump mobile-pro (doctrine mirror)
alichherawalla Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 10 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: CI

on:
workflow_dispatch:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
push:
branches:
- main
Expand Down Expand Up @@ -130,14 +131,13 @@ jobs:
# against dependencies nobody committed. The drift it papered over was real (five packages committed to
# shared without regenerating its lock) and is fixed at the source; if it recurs, this should stop.
npm --prefix ../shared ci
# Both packages this app consumes, because npm resolves each file: dep to its dist/ (jest
# reads src/, but tsc and metro read dist/). rag was never built at all before.
# Build every shared package this app consumes, because npm resolves each file: dependency
# to its dist/ (Jest reads src/, but TypeScript and Metro read dist/).
npm --prefix ../shared/packages/sync run build
npm --prefix ../shared/packages/rag run build
# speech too. It is a file: dep like the other two and six src files import it, but it was
# never built here - so every gate failed on "Cannot find module '@offgrid/speech'" (1966
# errors in one run) long before it reached anything real.
npm --prefix ../shared/packages/models run build
npm --prefix ../shared/packages/speech run build
npm --prefix ../shared/packages/ui run build

- name: Install dependencies
id: install
Expand Down Expand Up @@ -183,6 +183,11 @@ jobs:

- name: Run Jest tests
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
env:
# The serial RN + jsdom suite retains more than Node's default 2 GB heap before
# coverage is written. The macOS runner has 14 GB; keep enough headroom for native
# tooling while allowing the complete suite to finish without weakening the gate.
NODE_OPTIONS: --max-old-space-size=8192
run: npx jest --coverage --forceExit --runInBand

- name: Install Android NDK
Expand Down
8 changes: 7 additions & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ if [ -n "$PUSHED_JS" ]; then
npx tsc --noEmit

echo "▶ JS/TS tests (related to changed files)..."
echo "$PUSHED_JS" | tr '\n' '\0' | xargs -0 npx jest --findRelatedTests --passWithNoTests
# Rendered RN journeys share native-boundary state and are memory-heavy. Parallel workers
# starve one another and turn normal 10-second journeys into false multi-minute timeouts.
# CI runs this same graph serially; keep the local merge gate deterministic too.
# Some React Native boundary shims keep native-style handles alive after Jest has completed every
# assertion. The repository's full test command already force-closes those handles. Apply the same
# completion policy here so a fully passing related suite does not return a false non-zero status.
echo "$PUSHED_JS" | tr '\n' '\0' | xargs -0 npx jest --findRelatedTests --passWithNoTests --runInBand --forceExit

echo "▶ Architecture gate (dependency-cruiser)..."
npm run depcruise
Expand Down
52 changes: 43 additions & 9 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@
*/

import 'react-native-gesture-handler';
import React, { useEffect, useState, useCallback } from 'react';
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { ActivityIndicator, View, StyleSheet, LogBox } from 'react-native';
import { SystemBars } from 'react-native-edge-to-edge';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { NavigationContainer } from '@react-navigation/native';
import { AppNavigator } from './src/navigation';
import {
appNavigationRef,
useProExpiryRedirect,
} from './src/navigation/useProExpiryRedirect';
import { useTheme } from './src/theme';
import { hardwareService, modelManager, authService, ragService, remoteServerManager } from './src/services';
import logger from './src/utils/logger';
import { useAppStore, useAuthStore, useRemoteServerStore, useWhisperStore } from './src/stores';
import { useDebugLogsStore } from './src/stores/debugLogsStore';
import { initDebugLogFile, appendDebugLine } from './src/utils/debugLogFile';
import { initDebugLogFile, appendDebugLine, stopDebugLogFile } from './src/utils/debugLogFile';
import { startStartupMemoryProbe } from './src/services/startupMemoryProbe';
import { loadProFeatures } from './src/bootstrap/loadProFeatures';
import { hydrateDownloadStore } from './src/services/downloadHydration';
import { initActiveDownloadPersistence } from './src/services/activeDownloadPersistence';
import { restoreQueuedDownloads } from './src/services/restoreQueuedDownloads';
import { startLoadPolicySync } from './src/services/loadPolicySync';
import { startNetworkReconnectWatcher, stopNetworkReconnectWatcher } from './src/services/networkReconnect';
import { registerCoreDownloadProviders } from './src/services/modelDownloadService/registerProviders';
import { useDownloadListeners } from './src/hooks/useDownloads';
import { KeyboardProvider } from 'react-native-keyboard-controller';
Expand All @@ -34,6 +39,7 @@ import { ErrorBoundary } from './src/components/ErrorBoundary';

LogBox.ignoreAllLogs(); // Suppress all logs

let stopStartupProbe: (() => void) | null = null;
// Dev-only: mirror logger output into the in-app Debug Logs viewer. The whole block
// is behind __DEV__, so release builds keep main's no-op logger (zero logging cost).
if (__DEV__) {
Expand All @@ -60,7 +66,7 @@ if (__DEV__) {
// Immediately after the sink exists, so the first sample lands before anything heavy runs. The app
// was being killed by iOS at launch with the log going silent half a second in; this says where it
// stops and what memory was doing when it did.
startStartupMemoryProbe();
stopStartupProbe = startStartupMemoryProbe();
}

const ensureRemoteServerStoreHydrated = async () => {
Expand All @@ -72,12 +78,20 @@ const ensureRemoteServerStoreHydrated = async () => {
};

function App() {
useEffect(() => () => {
stopStartupProbe?.();
stopStartupProbe = null;
stopDebugLogFile();
}, []);

useDownloadListeners();
// Reactive: when Pro is activated at runtime (license key → loadProFeatures),
// the appRoot slot (TTS engine bridge) registers and this re-renders to mount
// it live — no restart needed.
const AppRoot = useSlot(SLOTS.appRoot);
const applyPendingProRedirect = useProExpiryRedirect();
const [isInitializing, setIsInitializing] = useState(true);
const startupGeneration = useRef(0);
const setDeviceInfo = useAppStore((s) => s.setDeviceInfo);
const setModelRecommendation = useAppStore((s) => s.setModelRecommendation);
const setDownloadedModels = useAppStore((s) => s.setDownloadedModels);
Expand Down Expand Up @@ -205,9 +219,13 @@ function App() {
})().catch((error) => {
logger.error('[App] Download-state recovery failed:', error);
});
}, [setDownloadedModels, setDownloadedImageModels]);
}, [
reattachTextDownloadRecovery,
setDownloadedModels,
setDownloadedImageModels,
]);

const initializeApp = useCallback(async () => {
const initializeApp = useCallback(async (generation: number) => {
try {
// Ensure persisted download metadata is loaded before restore logic reads it.
logger.log('[BOOT] app store hydrate');
Expand Down Expand Up @@ -258,9 +276,18 @@ function App() {

// Initialize remote server providers in the background — don't block
// the home screen while fetching models from potentially unreachable servers.
remoteServerManager.initializeProviders().catch((err) => {
logger.error('[App] Failed to initialize remote server providers:', err);
});
remoteServerManager
.initializeProviders()
.catch((err) => {
logger.error('[App] Failed to initialize remote server providers:', err);
})
.finally(() => {
if (generation !== startupGeneration.current) return;
// Recovery and provider initialization both update the registry and remote-server store.
// Start recovery only after initialization releases those owners. A failed initialization
// must still start the watcher so a later network recovery can repair the connection.
startNetworkReconnectWatcher();
});

// Check if passphrase is set and lock app if needed
logger.log('[BOOT] auth passphrase check');
Expand Down Expand Up @@ -315,7 +342,12 @@ function App() {
]);

useEffect(() => {
initializeApp();
const generation = ++startupGeneration.current;
initializeApp(generation);
return () => {
startupGeneration.current += 1;
stopNetworkReconnectWatcher();
};
}, [initializeApp]);

const handleUnlock = useCallback(() => {
Expand Down Expand Up @@ -353,6 +385,8 @@ function App() {
<SystemBars style={isDark ? 'light' : 'dark'} />
{AppRoot ? <AppRoot /> : null}
<NavigationContainer
ref={appNavigationRef}
onReady={applyPendingProRedirect}
theme={{
dark: isDark,
colors: {
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Pro is an optional, additive tier. It gives the assistant a voice that talks bac
- **Voice mode** - the free app transcribes your speech; Pro adds on-device Kokoro text-to-speech, so it talks back and you run the whole thing hands-free. The voice runs in your phone's RAM.
- **Custom personas** - give each assistant its own system prompt, voice, and persistent memory, so it stays in character across conversations.
- **Draft, then approve** - connect Calendar, email, and MCP servers like Linear, Notion, and GitHub. It drafts the reply or files the ticket and waits. Nothing sends without your tap.
- **Sync, landing through July** - your phone and your Mac merge into one picture over your own network, never a relay. Your license includes it the day it ships.
- **Personal Mesh sync** - your phone and your Mac share chats, files, and settings over your own network. There is no relay. See the [Personal Mesh guide](docs/PERSONAL_MESH.md).

**[→ Get Pro access](https://offgridmobileai.co/pay/)** - $69 once and it is yours forever (the price climbs as more people join, never down), or $49/year.

Expand Down Expand Up @@ -95,7 +95,7 @@ Most "local LLM" apps give you a text chatbot and call it a day. Off Grid AI is

**Vision AI** — Point your camera at anything and ask questions. SmolVLM, Qwen3-VL, Gemma 3n — analyze documents, describe scenes, read receipts. ~7s on flagship devices.

**Voice Input** On-device Whisper speech-to-text. Hold to record, auto-transcribe. No audio ever leaves your phone.
**Voice Input** - On-device Whisper speech-to-text. Tap the microphone to start and stop, or use Auto and Hands-free modes. No audio ever leaves your phone.

**Document Analysis** — Attach PDFs, code files, CSVs, and more to your conversations. Native PDF text extraction on both platforms.

Expand Down
67 changes: 65 additions & 2 deletions __tests__/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ const mockModelManager = {
refreshModelLists: jest.fn(() => Promise.resolve({ textModels: [], imageModels: [] })),
watchDownload: jest.fn(),
};
const mockInitializeProviders = jest.fn(() => Promise.resolve());
const mockStartNetworkReconnectWatcher = jest.fn();

jest.mock('../src/navigation', () => ({
AppNavigator: () => null,
Expand Down Expand Up @@ -135,9 +137,14 @@ jest.mock('../src/hooks/useDownloads', () => ({
jest.mock('../src/services/loadPolicySync', () => ({
startLoadPolicySync: jest.fn(() => jest.fn()),
}));
jest.mock('../src/services/networkReconnect', () => ({
startNetworkReconnectWatcher: mockStartNetworkReconnectWatcher,
stopNetworkReconnectWatcher: jest.fn(),
}));
jest.mock('../src/utils/debugLogFile', () => ({
initDebugLogFile: jest.fn(),
appendDebugLine: jest.fn(),
stopDebugLogFile: jest.fn(),
}));

jest.mock('../src/services', () => ({
Expand All @@ -153,7 +160,7 @@ jest.mock('../src/services', () => ({
ensureReady: jest.fn(() => Promise.resolve()),
},
remoteServerManager: {
initializeProviders: jest.fn(() => Promise.resolve()),
initializeProviders: mockInitializeProviders,
},
}));

Expand All @@ -167,12 +174,14 @@ jest.mock('../src/services', () => ({
describe('App', () => {
beforeEach(() => {
jest.clearAllMocks();
mockInitializeProviders.mockResolvedValue(undefined);
});

it('restores in-flight downloads at startup and watches each to completion', async () => {
const App = require('../App').default;
let renderer: ReactTestRenderer.ReactTestRenderer;
await ReactTestRenderer.act(async () => {
ReactTestRenderer.create(<App />);
renderer = ReactTestRenderer.create(<App />);
// Flush the async startup chain (hydrate → reattach → restore → watch).
for (let i = 0; i < 20; i++) await Promise.resolve();
});
Expand All @@ -188,5 +197,59 @@ describe('App', () => {
expect.any(Function),
expect.any(Function),
);
await ReactTestRenderer.act(async () => {
renderer!.unmount();
});
});

it('starts reconnect recovery only after remote provider initialization settles', async () => {
let finishInitialization: (() => void) | undefined;
mockInitializeProviders.mockImplementationOnce(() => new Promise<void>((resolve) => {
finishInitialization = resolve;
}));

const App = require('../App').default;
let renderer: ReactTestRenderer.ReactTestRenderer;
await ReactTestRenderer.act(async () => {
renderer = ReactTestRenderer.create(<App />);
for (let i = 0; i < 20; i++) await Promise.resolve();
});

expect(mockInitializeProviders).toHaveBeenCalledTimes(1);
expect(mockStartNetworkReconnectWatcher).not.toHaveBeenCalled();

await ReactTestRenderer.act(async () => {
finishInitialization?.();
await Promise.resolve();
});

expect(mockStartNetworkReconnectWatcher).toHaveBeenCalledTimes(1);
await ReactTestRenderer.act(async () => {
renderer!.unmount();
});
});

it('does not start reconnect recovery when provider initialization settles after unmount', async () => {
let finishInitialization: (() => void) | undefined;
mockInitializeProviders.mockImplementationOnce(() => new Promise<void>((resolve) => {
finishInitialization = resolve;
}));

const App = require('../App').default;
let renderer: ReactTestRenderer.ReactTestRenderer;
await ReactTestRenderer.act(async () => {
renderer = ReactTestRenderer.create(<App />);
for (let i = 0; i < 20; i++) await Promise.resolve();
});

await ReactTestRenderer.act(async () => {
renderer!.unmount();
});
await ReactTestRenderer.act(async () => {
finishInitialization?.();
await Promise.resolve();
});

expect(mockStartNetworkReconnectWatcher).not.toHaveBeenCalled();
});
});
Loading
Loading