feat(mobile): wire up AI-foundation Phase 1 (#122) #444

Merged
admin-mrrm merged 11 commits from wireup/ai-foundation-phase1 into main 2026-06-06 17:57:24 +02:00
Collaborator

Summary

Composes the five Phase-1 feature branches (#439–#443) into one integration branch and wires the real native stack behind the Suche route. Native bindings (op-sqlite + onnxruntime) are constructed lazily at route render so vitest never touches them.

  • search-ports.ts: lazy singleton getOrInitSearchPorts(api) building EmbeddingService + VectorStore + DataSourceRegistry (Mail/ShoppingList/Note) + IndexingService, with sync→async adapters and indexingService.start() for incremental indexing. Initial-index enumeration is a no-op until #438 lands.
  • vector-store: adds getChunkTextsForSource (needed by IndexingService hash-dedup) + 3 spec tests.
  • search.tsx: pulls ApiClient via useApiClient(), constructs ports inside useMemo with try/catch so missing native bindings surface as an inline error.
  • docs/ai-foundation-verification.md: on-device smoke-test checklist for Phase 1.

Test plan

  • pnpm --filter @mrrmlab/mobile exec tsc --noEmit — clean
  • pnpm --filter @mrrmlab/mobile exec vitest run — 177/177 passing
  • Physical-device pass required before merge:
    • expo prebuild --clean && expo run:android
    • sqlite-vec linked (no vec_version() error)
    • ONNX model loads on first indexing event
    • Edit a shopping list → progress strip ticks
    • Suche Käse returns the matching list entry
    • Suche Mozzarella returns same list (semantic, no lexical hit)
    • Filter chips narrow results correctly
    • Privacy notice renders

See apps/mobile/docs/ai-foundation-verification.md for the full checklist.

Closes #439, #440, #441, #442, #443. Refs #122.

## Summary Composes the five Phase-1 feature branches (#439–#443) into one integration branch and wires the real native stack behind the Suche route. Native bindings (op-sqlite + onnxruntime) are constructed lazily at route render so vitest never touches them. - `search-ports.ts`: lazy singleton `getOrInitSearchPorts(api)` building EmbeddingService + VectorStore + DataSourceRegistry (Mail/ShoppingList/Note) + IndexingService, with sync→async adapters and `indexingService.start()` for incremental indexing. Initial-index enumeration is a no-op until #438 lands. - `vector-store`: adds `getChunkTextsForSource` (needed by IndexingService hash-dedup) + 3 spec tests. - `search.tsx`: pulls `ApiClient` via `useApiClient()`, constructs ports inside `useMemo` with try/catch so missing native bindings surface as an inline error. - `docs/ai-foundation-verification.md`: on-device smoke-test checklist for Phase 1. ## Test plan - [x] `pnpm --filter @mrrmlab/mobile exec tsc --noEmit` — clean - [x] `pnpm --filter @mrrmlab/mobile exec vitest run` — 177/177 passing - [ ] **Physical-device pass** required before merge: - [ ] `expo prebuild --clean && expo run:android` - [ ] sqlite-vec linked (no `vec_version()` error) - [ ] ONNX model loads on first indexing event - [ ] Edit a shopping list → progress strip ticks - [ ] Suche `Käse` returns the matching list entry - [ ] Suche `Mozzarella` returns same list (semantic, no lexical hit) - [ ] Filter chips narrow results correctly - [ ] Privacy notice renders See `apps/mobile/docs/ai-foundation-verification.md` for the full checklist. Closes #439, #440, #441, #442, #443. Refs #122.
feat(mobile): EmbeddingService (multilingual-e5-small ONNX) — P1.1 / #439
All checks were successful
continuous-integration/drone/push Build is passing
a075eecfcc
EmbeddingService analog zu nli-classifier: lazy load von e5-small Q8 ONNX (~118 MB,
download-on-first-run), AutoTokenizer + InferenceSession, mean-pool mit attention-mask,
L2-norm → unit Float32Array. E5-Prefix-Konvention (query:/passage:) gekapselt.

Refs #439. Benchmark + on-device-Verifikation in Folge-Commit (braucht Device).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(mobile): VectorStore + op-sqlite-Adapter (sqlite-vec) — P1.2 / #440
All checks were successful
continuous-integration/drone/push Build is passing
88400015b3
VectorStore-Service mit upsert/search/remove + idempotenter Schema-Init über
`vector_index` (Metadaten) und `vector_index_vec` (vec0 virtual table, FLOAT[384]).
Bindung-agnostisch via VectorStoreDb-Interface; Adapter für @op-engineering/op-sqlite
(sqlite-vec statisch gelinkt via "sqliteVec":true). vector-store-mobile öffnet die
DB und macht vec_version()-Smoke-Test beim Open — wirft laut bei Misskonfig.

Refs #440. App-Boot-Hook + Maestro-Smoke-Assertion in Folge-Commit
(braucht on-device-Run).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(shared-types,mobile): IDataSource interface + 3 Adapter — P1.3 / #441
All checks were successful
continuous-integration/drone/push Build is passing
1221dbb848
IDataSource/Chunk/UpdateEvent in shared-types (types-only). Runtime-Registry
+ drei Adapter im Mobile-App: MailDataSource (Body-Chunking ~500 chars mit
Overlap, HTML-Fallback), ShoppingListDataSource (Item-Gruppen pro Chunk),
NoteDataSource (Markdown-Sections, char-Chunking-Fallback bei Übergröße).
Jeder Adapter publishert UpdateEvents; Registry fan-out via subscribe.

Refs #441. Production-Wiring der publish()-Calls aus Mutations-Handlers /
TanStack-Cache-Invalidation folgt mit P1.4 (Background-Indexer).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(ai): IndexingService — initial + incremental indexing — P1.4 / #442
All checks were successful
continuous-integration/drone/push Build is passing
7509a21c29
- runInitialIndex() walks every registered DataSource, hash-dedups
  existing chunks, embeds new ones, upserts into VectorIndexPort
- start() subscribes to DataSourceRegistry; created/updated re-index a
  single sourceId, deleted removes it; serialized via an internal queue
- per-chunk retry with exponential backoff (default 100 → 200 → 400ms),
  emits error events with willRetry flag
- FNV-1a 32-bit hash for content-equality dedup; collisions cost at most
  one redundant embed
- Progress event-emitter feeds the P1.5 UI panel
- Ports (EmbedderPort, VectorIndexPort) kept structural so this branch
  builds independently of feat/439 and feat/440 until app-bootstrap
  wireup
- 10 Vitest cases with mocked DataSources, embedder, and store; full
  mobile suite 131/131 green; tsc clean

Refs #122. expo-task-manager registration + on-device perf check
deferred to the cross-branch wireup.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(ai): SearchScreen — drawer route /search + semantic-search hook — P1.5 / #443
All checks were successful
continuous-integration/drone/push Build is passing
54b4cf0092
- /search route added to the (drawer) group; Drawer entry "Suche" sits at
  the top of the nav list
- useSemanticSearch hook: 300 ms debounce (default), monotonic generation
  counter to drop stale results, source-type filter passed straight through
  to the vector port; covers empty/whitespace short-circuit, errors, and
  in-flight state
- useIndexingProgress hook: subscribes to IndexingService.onProgress (#442),
  flips isActive on start/complete, counts indexed chunks for the UI bar
- SearchScreen renders input, filter chips (Alle / Mails / Listen /
  Notizen), result cards with source-type badge + snippet (240-char
  truncation), the privacy hint "🔒 Suche läuft lokal auf deinem Gerät",
  and the indexing-progress strip when active
- resolveSearchHref pure-function maps hits to routes
  (/mail/{acc}/reader?folder=&uid=, /lists/{id}); preserves nested IMAP
  folders, returns null on malformed sourceIds
- search-ports.ts factory currently returns null → the route shows a
  placeholder until the cross-branch wireup lands (P1.1-P1.4 still on
  feature branches, on-device verification pending)
- 15 vitest cases across the new hook + resolver (full mobile suite
  146/146 green, tsc clean)
- .maestro/search-smoke.yaml auto-runs in the existing maestro-test Drone
  step; will be extended with a real-hit assertion after the wireup

Refs #122.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(mobile): wire up AI-foundation Phase 1 (#122)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
866f5efd69
Compose EmbeddingService + VectorStore + DataSourceRegistry +
IndexingService behind a lazy singleton (`getOrInitSearchPorts`) and
mount it on the Suche route. Native bindings (op-sqlite, onnxruntime)
are constructed only at route render time so vitest never touches them.

- search-ports.ts: build adapters that bridge sync VectorStore methods
  to the async ports, register Mail/ShoppingList/Note data sources,
  start incremental indexing. Initial-index sourceId enumeration is
  intentionally a no-op until the expo-task-manager "Ladung + Wifi"
  trigger (#438) lands.
- vector-store: add `getChunkTextsForSource` (needed by IndexingService
  for hash-dedup) + 3 spec tests.
- search.tsx: pull ApiClient via useApiClient(), construct ports inside
  useMemo with a try/catch so a missing native binding surfaces as an
  inline error instead of crashing the drawer.
- docs/ai-foundation-verification.md: on-device smoke-test checklist
  for the Phase 1 stack.

tsc clean, vitest 177/177 green. No PR yet — physical-device pass on a
clean prebuild is the next gate.
admin-mrrm deleted branch wireup/ai-foundation-phase1 2026-06-06 17:57:24 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
admin-mrrm/mrrmlabapp!444
No description provided.