feat: add mobile shell and unified search
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s

This commit is contained in:
Yun Chan 2026-09-28 23:23:36 +09:00
parent f505a03d34
commit 5ca3709b32
98 changed files with 5168 additions and 2093 deletions

View file

@ -13,23 +13,25 @@ Root stack (`App.tsx` `RootNavigator`) branches on auth/onboarding:
- `recovery` → `UpdatePassword`
- `onboarding` → Onboarding + InviteAccept + Login
- unauthenticated → Login, SignUp, ForgotPassword, InviteAccept
- authenticated → `Main` (`TabNavigator`) + ~22 stack screens
- authenticated → `Main` (`TabNavigator`) + 23 stack screens
Bottom tabs (`TabNavigator.tsx`): **Dash / Works / Record (center raised FAB) / Talk / Settings**.
Bottom tabs (`TabNavigator.tsx`): **Home / Records / Meetings / More**. `Record` remains a hidden tab route so an active recording stays mounted; it never occupies a visible tab slot. A floating `RecordFab` appears on Home/Records/Meetings (not More/Record) and routes **dictation → Record**, **meeting recording → Meetings + `createRequest`**, **audio import → Record + `importRequest`**. Android back, scrim press, and action selection close its menu; tab back behavior is history-based.
Home opens `UnifiedSearch` on the root stack. New and converted v4 flows use the app-level `ScreenHeader` with a 48dp back action; tab roots such as Records and Meetings show a title without a back action. Historical stack screens that were not part of this conversion may still use their own header.
Deep links: `d3ro-voice://` and `https://d3ro.chanpaca.net` (`accept-invite`).
Provider stack: `GestureHandlerRootView → SafeAreaProvider → AuthProvider → EntitlementProvider → BillingProvider → MobileAdsProvider → MobilePreferencesProvider → DeviceProvider → LocalizedApplication (I18n + StatusBar)`.
---
## 2. Screens (`src/screens/`, 30)
## 2. Screens (`src/screens/`: 31 active source files)
| Screen | Feature |
|---|---|
| `RecordScreen` | Recording + local-whisper/cloud transcription, import audio, share |
| `DashScreen` | Dashboard: usage stats, tier, streaks, trends |
| `WorksHubScreen` | WORKS hub: pinned feature drill-down (desktop-parity) |
| `TalkScreen` | Voice/text AI chat: STT, LLM streaming, TTS |
| `HomeScreen` | Today summary, recent 5 records / 3 meetings, unified-search entry, Free plan entry |
| `MoreScreen` | Tools and account routes; Admin entry is role-gated |
| `UnifiedSearchScreen` | Authenticated record+meeting search, newest-first merged results, source-aware detail navigation |
| `RecordScreen` | Recording + local-whisper/cloud transcription, import audio, share; entered from the hidden Record route/FAB |
| `TalkScreen` | Voice/text AI chat: STT, LLM streaming, TTS; opened from More on the root stack |
| `HistoryScreen` / `HistoryDetailScreen` | History list/detail: sync, search, filters, audio playback, edit/share/favorite/delete |
| `MeetingsScreen` / `MeetingDetailScreen` | Meeting workspaces: create/rename/delete, transcript, memos, documents, export, content report |
| `MemosScreen` | Memos: list, tags, search, share |
@ -46,16 +48,17 @@ Provider stack: `GestureHandlerRootView → SafeAreaProvider → AuthProvider
| `AdminScreen` / `AdminUserDetailScreen` | Role-gated admin: users, subscriptions, audit, role/tier edits |
| `ProPaywallScreen` | Pro/Pro+ paywall: Play IAP, restore, rewarded ads |
| `OnboardingScreen` | First-run audience/theme/locale |
| `SettingsScreen` | Preferences, locale/theme, privacy links, logout |
| `SettingsScreen` | Preferences, locale/theme, privacy links, logout; opened from More on the root stack |
| `LoginScreen` / `SignUpScreen` / `ForgotPasswordScreen` / `UpdatePasswordScreen` / `InviteAcceptScreen` | Auth flows |
---
## 3. Features (`src/features/`, 20 modules)
## 3. Features (`src/features/`, 23 modules)
| Module | Purpose |
|---|---|
| `recording/` | Durable offline recording queue, retry processing, language handling |
| `audio/` | Linked recording/audio cleanup |
| `import/` | Audio pick/decode/validate, local Whisper fallback, resumable multipart upload, Android share-intent intake |
| `history/` | History service + local cache + sync hook |
| `meetings/` | Meeting workspace CRUD, realtime channels, processing jobs, transcripts/memos/docs |
@ -70,11 +73,13 @@ Provider stack: `GestureHandlerRootView → SafeAreaProvider → AuthProvider
| `dashboard/` | Dashboard stats + recent entries |
| `templates/` | Templates + generation idempotency |
| `auth/` | OAuth sign-in (in-app browser, PKCE) + identity linking |
| `account/` | Account deletion service and server-confirmed exit boundary |
| `admin/` | Admin user/subscription/audit APIs + session/role |
| `data-portability/` | Canonical JSON export/import, schema validation, legacy import, meeting export, file sharing |
| `chat/` | Chat LLM normalization/streaming, reportable generation IDs |
| `notifications/` | Notification contract/native registration/service/runtime hook |
| `reporting/` | AI content reporting (reason codes, Edge calls, idempotency) |
| `search/` | User-scoped record title/original/polished/summary + meeting title/raw/edited transcript search; sanitization, parallel queries, newest-first merge |
Core libs (`src/lib/`): `auth-context`, `auth-redirect`, `auth-capabilities`, `secure-auth-storage` (Keychain + legacy migration), `logout`, `account-exit`, `account-local-data` (central purge), `supabase`, `billing-context` (Play/App Store IAP), `entitlement-context`, `device-context`, `preferences-context`, `mobile-ads-context` (UMP consent + rewarded), `native-config`, `pkce-s256`, `random-id`, `audio-recorder`, `e2e-runtime-bootstrap`.
@ -87,10 +92,12 @@ Source of truth for status is `docs/v3/MOBILE_APP_COMPLETION_SSOT.md` sections 0
| Area | Status |
|---|---|
| Auth / account | `[~]` Implemented + Jest GREEN; external OAuth consent→callback and logout/delete/provider device E2E pending |
| Onboarding / theme / accessibility | `[x]` device journey GREEN (some checklist rows still `[ ]` for a11y specifics) |
| Onboarding | `[x]` previous first-run device journey GREEN; this does not verify the new v4 shell on every viewport/theme |
| v4 shell / theme / accessibility | `[x]` shell journey at 320dp dark and 411dp light; `[~]` broader design-system/a11y proof because TalkBack/font-scale/rotation remain |
| Recording / transcription / Talk | `[x]` core path GREEN on API 34 (FGS, recovery, local Whisper, cloud fail-closed, TTS) |
| Data portability | `[x]` export/restore + share E2E GREEN |
| History / meetings | `[x]` real data + meeting creation contract GREEN; audio timestamp/speaker + cross-app E2E pending |
| Record+meeting unified search | `[~]` service/row tests and both canonical runtime journeys (both source results → details) GREEN; mobile raw-text overfetch/no cancellation and FTS/index/large-data performance remain unproved |
| Teams / push | `[~]` teams GREEN; real FCM delivery needs Firebase/FCM credentials |
| Billing / ads | `[~]` test ads + Google Play verification code GREEN; live store purchase/restore/settlement and production AdMob serving **RED (external)** |
| Admin | `[x]` mobile admin role E2E GREEN (ordinary/manager/admin/super_admin, stale JWT) |
@ -104,11 +111,16 @@ Source of truth for status is `docs/v3/MOBILE_APP_COMPLETION_SSOT.md` sections 0
The Expo skeleton (`apps/mobile`) was deleted on 2026-09-13: it duplicated auth/data logic, shipped a dev-bypass login, and was outside the workspace and CI. `scripts/ci/sync-version.mjs` and `package-lock.json` no longer reference it. `apps/mobile-rn` is the sole mobile runtime; git history retains the old app.
The v4 shell also removed `DashScreen` and `WorksHubScreen`. Home replaces the dashboard entry; first-class tabs and More preserve the former WorksHub destinations. The WorksHub-only local tool filter and pinning were intentionally retired to avoid a second navigation taxonomy and local ordering state. This removed navigation UI, not record/meeting/tool content; an unused `works-hub-pins.v1` AsyncStorage key may remain on upgraded installs.
---
## 6. Tests & evidence
- Jest: `apps/mobile-rn/__tests__/` — per SSOT 57 suites / 353 tests (auth, privacy purge, admin, billing, recording language, generation idempotency, templates/memos, STT quota integration).
- Jest: `npm --prefix apps/mobile-rn test` — current full tree **86 suites / 589 tests PASS** (2026-09-28). v4-focused coverage includes `navigation-shell.test.tsx`, `screen-header.test.tsx`, `theme-parity.test.ts`, `ui-native-theme.test.tsx`, `unified-search-service.test.ts`, and `unified-search-result-row.test.tsx`.
- Maestro E2E: `apps/mobile-rn/.maestro/` + evidence in `.maestro-output/`.
- v4 local runtime: `apps/mobile-rn/.maestro/navigation-shell-search-local.yaml`; 320dp dark **1/1 PASS, 88 s** at `scratch/design-audit/wave45/navigation-shell-search-320-dark-verified.xml`, and 411dp light **1/1 PASS, 100 s** at `scratch/design-audit/wave45/navigation-shell-search-411-light-verified.xml`.
- Runtime APK: `com.d3ro.voice`, versionName `1.9.0-e2e.20260928.2`, versionCode `202609282`, 147063583 bytes, SHA-256 `96EBA2A47E54033EB0DEFCE4E4D282F1D06F162A5F7DF9457E99609D98745D58`. This is a local E2E artifact, not a production-signed APK/AAB or Play release.
- Committed evidence summary: [`docs/evidence/wave45-mobile-shell-search-2026-09-28.md`](../evidence/wave45-mobile-shell-search-2026-09-28.md).
- Android instrumentation: `apps/mobile-rn/android/app/src/androidTest` (CSPRNG + incoming share).
- Build verifiers: `scripts/ci/verify-android-artifact.mjs`, `verify-android-app-links.mjs`, `verify-mobile-build-config.mjs`, `verify-mobile-release-boundary.mjs`.