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
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:
parent
f505a03d34
commit
5ca3709b32
98 changed files with 5168 additions and 2093 deletions
|
|
@ -13,9 +13,9 @@ The canonical place for types and cross-surface logic. Both desktop and web/mobi
|
|||
|
||||
| Area | Exports | Notes |
|
||||
|---|---|---|
|
||||
| Types | `./types` | Domain types shared across surfaces |
|
||||
| Types | `./types` | Domain types shared across surfaces, including the record+meeting `UnifiedSearchSource` / `UnifiedSearchMatchedField` / `UnifiedSearchResult` / `UnifiedSearchParams` / `UnifiedSearchResponse` contract |
|
||||
| Errors | `./errors` | `D3ROError`, `ErrorCode` |
|
||||
| IPC channels | `./ipc-channels` | `IPC_CHANNELS` object + `IPCChannel` union. **SSOT** for every desktop IPC channel (VOICE, AUDIO, STT, TTS, LLM, KEYBINDING, CONFIG, HISTORY, DICTIONARY, WINDOW, SYSTEM, STATS, MEMO, VOICE_COMMAND, CONTEXT, CHAIN, CAPTION, FILE_TRANSCRIPTION, MEETING_SUMMARY, DICTATION_TEMPLATE, VOICE_CONVERSATION, RAG, VOICE_ACTION, MEETING_MODE, MEETING_DOC_TEMPLATE, MEETING_CHAT, LICENSE, CLOUD_SYNC, INSTRUCTION, SYSTEM_AUDIO, POPUP_RESULT, POPUP_HISTORY, POPUP_COMMAND, POPUP_CAPTION, VOICE_PARTIAL, CLIPBOARD, APP, ONLINE_AUTH, ADS, SUPPORT, INPUT_TELEMETRY, SUGGESTION, POPUP_SUGGESTION) |
|
||||
| IPC channels | `./ipc-channels` | `IPC_CHANNELS` object + `IPCChannel` union. **SSOT** for every desktop IPC channel (VOICE, AUDIO, STT, TTS, LLM, KEYBINDING, CONFIG, HISTORY, UNIFIED_SEARCH, DICTIONARY, WINDOW, SYSTEM, STATS, MEMO, VOICE_COMMAND, CONTEXT, CHAIN, CAPTION, FILE_TRANSCRIPTION, MEETING_SUMMARY, DICTATION_TEMPLATE, VOICE_CONVERSATION, RAG, VOICE_ACTION, MEETING_MODE, MEETING_DOC_TEMPLATE, MEETING_CHAT, LICENSE, CLOUD_SYNC, INSTRUCTION, SYSTEM_AUDIO, POPUP_RESULT, POPUP_HISTORY, POPUP_COMMAND, POPUP_CAPTION, VOICE_PARTIAL, CLIPBOARD, APP, ONLINE_AUTH, ADS, SUPPORT, INPUT_TELEMETRY, SUGGESTION, POPUP_SUGGESTION) |
|
||||
| Key bindings | `./keybinding` | **SSOT** for every global shortcut in the app: `KeyBinding` (`device`/`code`/`ctrl`/`alt`/`shift`/`meta`), `KEY_CATALOG` (10 selectable groups incl. mouse), `KEYBINDING_ACTIONS` (9 rebindable actions incl. the three suggestion actions), `bindingKey`/`normalizeBinding`/`validateBinding`/`detectBindingConflicts`/`formatBindingSegments`/`searchKeyCatalog`/`parseBindingMap`. Persisted as `AppConfig.keyBindings`. i18n keys are exposed as plain `string` so core stays independent of `@d3ro/i18n`; consumers narrow at the boundary (`asTranslationKey`) and a contract test guards the keys — accepted constraint, `11` §7 CONSTRAINT-I18N-01. Tests: `__tests__/keybinding*.test.ts` via `vitest.config.ts` (`npm run test --workspace=@d3ro/core`), 117 cases as of 2026-09-21 |
|
||||
| Input intelligence | `./input-intelligence` | **SSOT** for the input-telemetry and next-sentence-suggestion domain (added 2026-09-21): key classification (`classifyKeyStroke`), text metrics (`countWords`/`countSentences`/`endsSentence`/`textBeforeCaret`), `computeTypedDelta` (UIA snapshot diff — the IME-safe way to count typed text), `decideSuggestion` + `isAppExcluded` (when to request / skip / clear), `parseSuggestionCandidates`/`sanitizeSuggestionLine` (prompt-leak and prefix-echo defence), `anchorFloatingPanel` (caret-anchored overlay placement), `mergeActivityBucket`/`summarizeActivity`/`pixelsToMeters`, `extractPhrases`/`selectPhraseHints`, `INPUT_TELEMETRY_DEFAULTS`/`SUGGESTION_DEFAULTS` |
|
||||
| Constants | `./constants` | Shared constants |
|
||||
|
|
@ -48,10 +48,11 @@ Peers: React 19, MUI 7, Emotion 11. Depends on `@d3ro/core`.
|
|||
|
||||
| Provides |
|
||||
|---|
|
||||
| `NativeThemeProvider` / `useNativePalette`, native palette/typo/radius/fonts |
|
||||
| Components: `MetalCard`, `PhosphorText`, `Led`, `PhysicalButton`, `ScreenPanel`, `WaveBars`, `AppStatusBar`, `Header`, `FilterChip` |
|
||||
| `NativeThemeProvider` / `useNativePalette`; dark/light/get-palette, raw/status, typo/radius/fonts/space/control/weight/motion/numeral tokens. The v4 semantic colors mirror `packages/ui/src/theme-raw.ts`; `d3roNativeControl.h` is the 48dp control floor |
|
||||
| Components: `MetalCard`, `PhosphorText`, `Led`, `PhysicalButton`, `ScreenPanel`, `WaveBars`, `FilterChip` |
|
||||
|
||||
Peers: `react`, `react-native`. Consumed by `apps/mobile-rn` via file: workspace dependencies.
|
||||
App-specific `ScreenHeader`, `RecordFab`, and `UnifiedSearchResultRow` stay under `apps/mobile-rn/src/components`; they are not shared package exports.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ Singleton + `EventEmitter` pattern (`getXService()` accessors).
|
|||
| Service | Purpose |
|
||||
|---|---|
|
||||
| `HistoryService` | SQLite history CRUD/search/stats |
|
||||
| `UnifiedSearchService` | Read-only SQLite search across history title/content and meeting title/transcript; merges newest-first results without changing either schema |
|
||||
| `DictionaryService` | Custom vocabulary CRUD/search + cloud sync hooks + JSON/CSV import/export (`dictionary:import`/`export`, save/open dialogs) |
|
||||
| `MemoService` | Memo tags over history (`memo_tags`) |
|
||||
| `RAGService` | Local RAG: `nomic-embed-text` embeddings, cosine search over `rag_chunks` |
|
||||
|
|
@ -131,6 +132,7 @@ Registry: `src/main/ipc/index.ts` calls 31 `registerXHandlers()` in fixed order.
|
|||
| `dictionary-handlers` | DICTIONARY |
|
||||
| `file-transcription-handlers` | FILE_TRANSCRIPTION |
|
||||
| `history-handlers` | HISTORY (incl. `history:setFavorite`, `history:getAudio` → local bytes or signed URL) + `stats:getSummary` |
|
||||
| `unified-search-handlers` | UNIFIED_SEARCH (`unifiedSearch:query`), returning `IPCResult<UnifiedSearchResponse>` |
|
||||
| `input-telemetry-handlers` | INPUT_TELEMETRY |
|
||||
| `instruction-handlers` | INSTRUCTION |
|
||||
| `keybinding-handlers` | KEYBINDING |
|
||||
|
|
@ -156,7 +158,7 @@ The **`KEYBINDING`** group replaced the old per-action `HOTKEY` group. `HOTKEY`
|
|||
|
||||
**`LLM.PROCESS` normalizes at the IPC boundary.** The handler runs `buildInstructionInvocation` itself when `action === 'custom'` with a `customPrompt` (`llm-handlers.ts:94-108`), so the renderer passes the **raw instruction text** and never duplicates the substitution or argument-placement rules. This is what makes `VoiceModeService`, `ChainService`, and `LLM.PROCESS` literally share one implementation. No channel or type changed for this; `LLMProcessParams` is unchanged.
|
||||
|
||||
Preload exposes **`window.electronAPI`** with 35 namespaces: `platform, audio, config, voice, stt, keybinding, llm (incl. premium), history, dictionary, stats, window, system, instruction, app, memo, voiceCommand, context, chain, caption, license, fileTranscription, meetingSummary, dictationTemplate, rag, voiceAction, voiceConversation, meetingMode, meetingChat, meetingDocTemplate, cloudSync, onlineAuth, ads, support, inputTelemetry, suggestion`. The `keybinding` bridge is 9 methods mirroring the channels above (`src/preload/index.ts:323`), replacing the 11-method `hotkey` bridge. Envelope: `IPCResult<T>` (success/error); `app.onDataChanged` is the global refresh channel.
|
||||
Preload exposes **`window.electronAPI`** with 37 namespaces: `platform, audio, config, voice, stt, runtime, keybinding, llm (incl. premium), history, unifiedSearch, dictionary, stats, window, system, instruction, app, memo, voiceCommand, context, chain, caption, license, fileTranscription, meetingSummary, dictationTemplate, rag, voiceAction, voiceConversation, meetingMode, meetingChat, meetingDocTemplate, cloudSync, onlineAuth, ads, support, inputTelemetry, suggestion`. The `keybinding` bridge is 9 methods mirroring the channels above (`src/preload/index.ts:323`), replacing the 11-method `hotkey` bridge. Envelope: `IPCResult<T>` (success/error); `app.onDataChanged` is the global refresh channel.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -185,17 +187,18 @@ Vanilla popups (`src/renderer/popups/`):
|
|||
|
||||
## 5. Renderer IA
|
||||
|
||||
Routing is state-based in `AppLayout.tsx` (`Route` union + `NAV_ITEMS`), no react-router.
|
||||
Routing is state-based in `renderer/navigation/app-navigation.tsx` (`AppRoute`, params, history/back), no react-router. `Ctrl+K` opens the search route and `Ctrl+1..3` opens Home/Records/Meetings.
|
||||
|
||||
| Page | Route | Feature |
|
||||
|---|---|---|
|
||||
| `DashboardPage` | dashboard | Voice cockpit: hero, bento tiles, multi-engine hub (STT/LLM), telemetry, recent history, file drop |
|
||||
| `HistoryPage` | history | History & memory timeline; search, tag filter, pagination, export/delete |
|
||||
| `HomePage` | home | Today summary, recording/meeting/caption starts, current blockers, recent records/meetings, file transcription, bounded free usage |
|
||||
| `UnifiedSearchPage` | search | Record+meeting search, newest-first merged results, source-aware detail navigation; source/tests complete, Electron manual proof pending |
|
||||
| `HistoryPage` | records | History & memory timeline; search, tag/mode/favorite filters, pagination, export/delete |
|
||||
| `DictionaryPage` | dictionary | Custom vocabulary editor |
|
||||
| `CommandsPage` | commands | Custom instructions + voice keyword rules + LLM chains + dictation templates |
|
||||
| `VoiceConversationPage` | conversation | Duplex voice assistant (local pipeline vs OpenAI Realtime) |
|
||||
| `KnowledgeBasePage` | knowledge | Local RAG: add/index docs, semantic query, reindex/remove |
|
||||
| `MeetingModePage` | meeting | Meeting studio: live transcript, memos, doc generation/export, diarization |
|
||||
| `MeetingModePage` | meetings | Meeting studio: live transcript, memos, doc generation/export, diarization |
|
||||
|
||||
Modals/components: `SettingsModal` (tabs General/Audio/STT/LLM/Input/License/Cloud/About), `LicenseModal`, `LicenseTab`, `CloudSyncSection`, `InputInsightsPanel` (consent + suggestion policy + weekly insights + learned phrases), `InputConsentPanel` (receipt + current-app exclusion recommendation), `InputInsightsView` (flow, friction and app-quality summaries), `OnboardingModal`, `UpgradePromptModal`, `ProBadge`, `TemplateSection`, `FileDropZone`, `OllamaGuideModal`, `CodexOAuthGuideModal`, `TitleBar`, `StatusBar`, meeting components (9), voice-conversation, payment (`CheckoutModal`, `checkout-flow.ts`), support (`SupportModal`), ads (`AdBanner`, `RewardedQuotaModal`), shared cards.
|
||||
|
||||
|
|
@ -220,6 +223,7 @@ DB schema (`src/main/db/schema.ts`, drizzle SQLite): `history`, `dictionary`, `s
|
|||
- **Local STT is packaged** (`1.3.0`): `electron-builder.yml` `extraResources` copies `sidecar-dist/sidecar` → `resources/sidecar` and `resources/ffmpeg` → `resources/ffmpeg`; `scripts/ci/verify-sidecar-bundle.mjs` gates packaging. Build locally with `npm --prefix apps/desktop run sidecar:setup && npm --prefix apps/desktop run sidecar:build`. The sidecar stays in console mode so `stdout`/`stderr` reach the app log (UTF-8, line-buffered); a packaged sidecar **must** exist or startup fails loudly instead of silently falling back to a system Python.
|
||||
- All local engine URLs (`LocalSTTService`, `LocalLLMService`, `RAGService`, `OnlineLLMService`, `STTManager`) pass through `src/main/utils/loopback.ts`, which rewrites `localhost` to `127.0.0.1`, because some Windows hosts resolve `localhost` to IPv6 only and local engines bind IPv4.
|
||||
- Meeting intelligence, RAG, voice conversation (local + Realtime), captions, file transcription: implemented.
|
||||
- **Unified search (2026-09-28): source and automated tests only.** `UnifiedSearchService` + `unifiedSearch:query` IPC + preload bridge + `UnifiedSearchPage` search records and meetings and route to their detail views. The focused service and IPC tests pass; the renderer is source/type-gated, not headful-tested. No external-terminal Electron run has yet proven `Ctrl+K`, result rendering, or both detail transitions; treat the desktop capability as `[~]` until that run is retained.
|
||||
- **LLM instruction prompts: fixed 2026-09-21 (`9c2b4d4`), not yet verified in a running app.** Running a custom instruction inserted the instruction's own wording instead of the processed result. Two faults stacked: the instruction was passed in the `text` argument with the system-prompt argument left empty, and `BASE_SYSTEM_PROMPTS` has no `custom` key so resolution fell back to `refine` **silently** — the model polished the instruction and the transcript never reached it; separately, only `{{text}}` was substituted and none of the five built-in presets use it (`{{targetLanguage}}`, `{{userPrompt}}`, or no placeholder), so the substitution was a no-op from the day it was written. Introduced in `fea923d` (2026-04-05) and present in every release `v0.1.0-alpha`..`v1.4.0` — **the path never worked; this is not a regression.** Plain actions (`refine`/`summarize`/`grammar`/`expand`) were unaffected and are now pinned by regression cases. The fix routes all three entry paths through `llm-prompts.ts` (see §2) and additionally corrects two things found alongside it: a voice shortcut naming an instruction was nullified by the `defaultLLMAction === 'none'` gate (`VoiceModeService.ts:779`), and the commands-page pipeline bench called `llm.generate`, which preload does not expose, so every run threw and the `catch` displayed the **input** as if it had succeeded — a fail-closed violation that is the reason the bug went unnoticed for five months (`CommandsPage.tsx:180-205`, now on `llm.process` with failures rendered as failures).
|
||||
- **Verification limits — do not read this as verified.** Unit tests pass (`llm-prompts.test.ts` 21, `llm-handlers.test.ts` 7, `VoiceModeService.test.ts` 22, `ChainService.test.ts` 5), and each of the four fixes was reverted individually to confirm the tests actually fail without it. `npm run lint` (apps/desktop scope) passes; `tsconfig.check.json` errors went 36 → 35 (the `llm.generate` error is gone) with no errors in the touched files. But there is **no running-app run**, and `tests/red/{instruction,chain,voice,config}.usecase.test.ts` — precisely the related paths — never executed because of the `better-sqlite3` ABI mismatch. That range is neither passing nor failing; it is untested (`11` GAP-LLM-02, GAP-INFRA-06).
|
||||
- **Key bindings: implemented and verified on Windows.** Every global shortcut now comes from one contract (`@d3ro/core/keybinding`) with multiple bindings per action, mouse-button support, and no hardcoded accelerators left in `bootstrap.ts`. A manual run on 2026-09-21 confirmed legacy migration (custom values preserved), 6 actions loaded, the uiohook keyboard **and** mouse hook active with zero boot errors, and multi-binding working; contract side is `packages/core` 117 tests GREEN with no type errors in the key-binding files (`11` GAP-KEY-01 `[x]`). Two things remain open: `KeyBindingService` has no unit test of its own, and macOS/Linux mouse behavior is unconfirmed (`11` GAP-KEY-02). The rewrite also fixed a dead hands-free double-press path, an order-dependent reserved-combo check, a `globalShortcut.unregisterAll()` that wiped the popup accelerators, and a `setEnabled(true)` that re-enabled hooking with an empty binding set.
|
||||
|
|
@ -252,6 +256,7 @@ DB schema (`src/main/db/schema.ts`, drizzle SQLite): `history`, `dictionary`, `s
|
|||
| LLM prompt / placeholder SSOT | `src/main/services/llm-prompts.ts` (shared by `VoiceModeService`, `ChainService`, `ipc/llm-handlers.ts`) |
|
||||
| DB schema | `src/main/db/schema.ts` |
|
||||
| Renderer shell / routes | `src/renderer/components/AppLayout.tsx` |
|
||||
| Unified search | `src/main/services/UnifiedSearchService.ts`, `src/main/ipc/unified-search-handlers.ts`, `src/renderer/pages/UnifiedSearchPage.tsx` |
|
||||
| Update feed SSOT | `src/main/update-feed.ts` |
|
||||
| Update policy SSOT | `release/update-policy.json` + `src/main/update-policy.ts` |
|
||||
| Path/loopback resolution | `src/main/utils/paths.ts`, `src/main/utils/loopback.ts` |
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]`
|
|||
| MEM-12 | Voice actions (OS automation) | [x] | [x] | [x] | [x] | Desktop `VoiceActionService`; web `ActionRunner` (simulated); mobile `ActionsScreen` |
|
||||
| MEM-13 | Cross-surface data sync | [~] | [x] | [x] | [x] | **Desktop rewritten 2026-09-27** (`services/sync/SyncEngine.ts`): persistent outbox (offline/restart safe), server-clock keyset cursors per user DB with pagination, pending local edits never overwritten by pull, deletions both ways through `sync_tombstones`, per-row failure isolation, Realtime that actually connects (`ws` transport). Covers history (+title/favorite), dictionary, meetings, meeting memos/documents, memo tags, custom commands, dictation + meeting-doc templates; plus device registration. Evidence: `apps/desktop/tests/main/sync/SyncEngine.test.ts` (20) + `apps/desktop/tests/integration/cross-device-sync.supabase.test.ts` (5, local Supabase stack). `[~]` until migration `20260927000034` is deployed to production and a desktop build is exercised against a phone (GAP-SYNC-05); knowledge/audio/settings still local (GAP-SYNC-06) **2026-09-27 second batch:** knowledge base, recordings (upload + remote playback) and the mobile `user_settings` fields that mean the same thing on both sides (ko/en locale, system/light/dark theme, auto-polish on/off, active user command) now sync too; remote setting changes reach the open window (`app:dataChanged`, `config:changed`). Production DB is at migration `0035` and all Edge Functions are redeployed from the repo |
|
||||
| MEM-14 | Memo tag search | [x] | [ ] | [x] | [x] | |
|
||||
| MEM-15 | Unified record + meeting search | [~] | [ ] | [~] | [-] | Shared `@d3ro/core/types` result contract. Desktop: SQLite `UnifiedSearchService` → `unifiedSearch:query` → `UnifiedSearchPage`, source/focused tests GREEN but no Electron manual proof. Mobile: user-scoped Supabase history+meetings search, service/row tests plus 320dp dark and 411dp light runtime journeys (both result types → details) GREEN. `[~]` remains because separate `transcripts` segments are absent, mobile fetches raw text without in-flight cancellation, and neither surface has FTS/index or large-data performance proof (`11` GAP-UX-05; `docs/evidence/wave45-mobile-shell-search-2026-09-28.md`). |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -201,9 +202,9 @@ end-to-end behaviour has **not been verified by typing in a real app** (`11` GAP
|
|||
| SHELL-10 | Download center / releases | [-] | [x] | [-] | [x] | Web `/download`, admin `/releases`, Forgejo |
|
||||
| SHELL-11 | Landing site / legal pages | [-] | [-] | [-] | [-] | `site/` — the only landing/download/legal/invite/assetlinks surface (Wave 3, 2026-09-26: copies in api-server wwwroot and apps/web removed; 404.html; hero loops a copy of the app recording capsule). Prices/URLs from `packages/core` plan-catalog/web-urls — see `design.md` |
|
||||
| SHELL-12 | Notifications (in-app / desktop) | [x] | [ ] | [x] | [x] | Desktop events; mobile push |
|
||||
| SHELL-13 | Desktop information architecture (2026-09-28) | [x] | [-] | [ ] | [-] | 1급 홈·기록·회의 + 2급 "도구"(AI 대화·명령어·사전·지식) + 설정. `renderer/navigation/app-navigation.tsx`(라우트·params·back, Ctrl+1..3 / Ctrl+,), `components/shell/AppSidebar.tsx`(버튼 + aria-current, 아래 한 줄 = 조회한 상태만), 상태바 삭제. 모바일 동일 구조는 물결 ④(`11` GAP-UX-04). 결정 정본: 루트 `design.md` 2026-09-28 |
|
||||
| SHELL-14 | Home ("오늘") | [x] | [-] | [ ] | [-] | 대시보드를 대신한다: 받아쓰기 키 · 새 회의·자막 시작 · 조회된 문제만 · 한 줄 요약 · 최근 기록 6/회의 4 · 파일 전사 · 무료 한도. `pages/HomePage.tsx` |
|
||||
| SHELL-15 | Design system v4 "조용한 계기" | [x] | [~] | [~] | [-] | `packages/ui/src/theme-raw.ts` 원시값 SSOT(렌더러 theme.ts·팝업 theme-vars.ts 가 파생), 평면 표면·텍스트 3단 AA·accent.solid/ink. 웹·관리자는 토큰 파생으로 평면화만 적용, 모바일(ui-native) 동기화는 물결 ④ |
|
||||
| SHELL-13 | Desktop/mobile information architecture (2026-09-28) | [x] | [-] | [x] | [-] | 1급 홈·기록·회의 + 2급 도구. Desktop: `renderer/navigation/app-navigation.tsx`, `AppSidebar`, status bar removed. Mobile: visible Home/Records/Meetings/More tabs, hidden Record route, floating three-action Record FAB, More tool/account grouping; full Jest and the canonical 320dp dark/411dp light runtime journeys are GREEN (`11` GAP-UX-04). `DashScreen`/`WorksHubScreen` were removed; WorksHub-only local filter/pinning was intentionally retired while every destination remains in tabs/More and user content is untouched. Decision SSOT: root `design.md`. |
|
||||
| SHELL-14 | Home ("오늘") | [x] | [-] | [x] | [-] | Desktop `pages/HomePage.tsx`: recording key, starts, blockers, summary, recent 6/4, file transcription, bounded usage. Mobile `HomeScreen`: today summary, recent records 5/meetings 3, unified-search and plan entry; canonical 320dp dark and 411dp light runtime journeys GREEN. |
|
||||
| SHELL-15 | Design system v4 "조용한 계기" | [x] | [~] | [~] | [-] | `packages/ui/src/theme-raw.ts` raw-value SSOT; flat surfaces, three text levels with AA contrast, accent.solid/ink. `packages/ui-native` mirrors dark/light semantics and exposes 48dp control/numeral/motion tokens; focused parity/provider/header tests plus 320dp dark/411dp light runtime are GREEN. `[~]` remains because actual TalkBack, font-scale, and rotation proof is absent; `ScreenHeader` covers new/converted v4 flows, not every historical stack screen. |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res
|
|||
| GAP-UX-01 | Desktop | 회의 "내 메모"(상세 탭)는 이 기기 localStorage 에만 저장된다 — 기기 간 동기화되지 않는다. 끝난 회의에는 addMemo 가 거부된다(`MeetingModeService.ts:383`). | `components/meeting/meeting-notepad-storage.ts`, `MeetingModeService` | `[ ]` 2026-09-28: 끝난 회의의 사용자 노트를 받을 필드(또는 종료 후 addMemo)와 동기화 어댑터가 필요. |
|
||||
| GAP-UX-02 | Desktop | 기록 조회에 모드·즐겨찾기 조건, 태그+검색 조합이 없어 화면이 불러온 페이지 위에서 거른다(조건이 까다로우면 여러 페이지를 불러와야 보인다). 오프셋 페이지라 사이 삽입·삭제 때 누락·중복 가능. 기록 제목·본문 편집 없음. 마크다운 내보내기는 태그 붙은 기록만·저장 위치 선택 불가. | `HistoryService`, `history:getAll`, `MemoService.exportMarkdown` | `[ ]` 2026-09-28 |
|
||||
| GAP-UX-03 | Desktop | 회의 요약 항목 → 전사 구간 근거 링크 없음(요약·전사를 잇는 인용 데이터가 없다). | `MeetingSummaryService`, 회의 문서 생성 | `[ ]` 2026-09-28: 레퍼런스(Granola·Otter)의 핵심 패턴 — 생성 시 구간 인용을 함께 저장해야 한다. |
|
||||
| GAP-UX-04 | Mobile | 모바일 정보 구조 개편(탭 4 홈·기록·회의·더보기 + 떠 있는 녹음 버튼의 시작 메뉴) 미착수. | `apps/mobile-rn/src/navigation/TabNavigator.tsx` | `[ ]` 2026-09-28: 물결 ④. |
|
||||
| GAP-UX-05 | Desktop+Mobile | 기록+회의 통합 검색 없음(회의 전사는 어떤 검색으로도 못 찾는다). | 렌더러·모바일 검색, `MeetingModeService` | `[ ]` 2026-09-28: 물결 ⑤. 스키마 변경 없이 조회 추가. |
|
||||
| GAP-UX-04 | Mobile | 모바일 정보 구조 개편: visible Home/Records/Meetings/More 4탭, 숨은 Record route, 세 동작 FAB, Home/More, 새/전환된 v4 흐름의 `ScreenHeader`와 48dp back target. | `apps/mobile-rn/src/navigation/TabNavigator.tsx`, `components/{RecordFab,ScreenHeader}.tsx`, `screens/{Home,More}Screen.tsx` | `[x]` 2026-09-28: 전체 Jest 86 suites/589 tests, 320dp dark 1/1 PASS(88초), 411dp light 1/1 PASS(100초). Canonical 보고서는 `scratch/design-audit/wave45/navigation-shell-search-{320-dark,411-light}-verified.xml`, 추적 요약은 `docs/evidence/wave45-mobile-shell-search-2026-09-28.md`. `DashScreen`/`WorksHubScreen`은 삭제했고 WorksHub의 로컬 도구 검색·고정은 복잡도 축소를 위해 의도적으로 종료했다. 목적지는 탭/More에 보존됐고 콘텐츠 삭제는 없으며 비활성 `works-hub-pins.v1` 키는 남을 수 있다. TalkBack/font-scale/rotation은 이 셸 gap을 다시 열지 않고 Q-021 `[~]`로 추적한다. |
|
||||
| GAP-UX-05 | Desktop+Mobile | 기록+회의 통합 검색. 두 원본은 분리 유지하고 결과만 `source + id`로 상세 화면에 연결한다. | `@d3ro/core/types`, desktop `UnifiedSearchService`/`UnifiedSearchPage`, mobile `features/search`/`UnifiedSearchScreen` | `[~]` 2026-09-28: 모바일 service/row tests와 320dp dark·411dp light에서 기록·회의 결과→각 상세 경로가 GREEN. 데스크톱은 서비스·IPC·preload·페이지와 focused tests까지만 있으며 Electron `Ctrl+K`/결과 이동 수동 증거가 없다. 회의 검색은 `meetings.raw_transcript`/`edited_transcript` 집계 필드까지라 별도 `transcripts` 구간이 빠져 있다. 모바일은 일치 확인에 원문 필드를 내려받아 과전송하고 실행된 요청 취소가 없다. 두 표면 모두 FTS/index와 실제 대규모 데이터 성능 증거가 없다. Electron 수동 검증, 구간 검색 설계, 모바일 projection/RPC·취소, 검색 규모 기준과 perf proof 뒤에 표면별 `[x]`를 판단한다. |
|
||||
| GAP-UX-06 | Desktop | main 쪽 정리 필요: `STTManager.ts:53-129` 공급자 설명 한국어 하드코딩·마케팅 문구("100% 완전 오프라인", "Industry Benchmark") — 렌더러는 더 이상 쓰지 않는다 / audio TEST_DEVICE 가 deviceId 무시 / 사전 `incrementUsage` 호출처 없음(사용 횟수 항상 0) / 사전 발음 필드가 인식 힌트·치환에 쓰이지 않음 / 대화 세션 다시 듣기 IPC 없음 / preload `instruction.getAll` 이 `unknown[]` / 지식 추가 실패 판정이 문구 비교. | 각 파일 | `[ ]` 2026-09-28 (도구·설정 개편 에이전트 보고) |
|
||||
| GAP-UX-07 | Desktop | 2026-09-28 개편 문구는 ko·en 만 — 다른 8개 로케일은 en 폴백. 온보딩 Ollama 모델 크기 표기(`onboarding.llmModelSize` 9.6GB)와 안내 모달의 크기가 다르다. | `packages/i18n/src/locales/*` | `[ ]` 2026-09-28 |
|
||||
| GAP-TEAM-02 | Team | 브라우저에서 초대를 수락하는 경로가 없다. 웹 `accept-invite` 페이지는 발급 링크가 가리키지 않고 로그인 후 토큰을 읽는 곳이 없어 끊겨 있었으므로 삭제했다(사이트 `/accept-invite/`는 앱 딥링크만). | `site/public/accept-invite/`, `server/supabase/functions/team-accept` | `[ ]` 2026-09-26: 데스크톱 전용 사용자를 위한 웹 수락 흐름이 필요하면 `/app` 아래에 다시 설계. |
|
||||
|
|
@ -119,14 +119,14 @@ These are the mobile SSOT rows still `[ ]` / `[~]`. Do not duplicate the full te
|
|||
|---|---|---|
|
||||
| G (governance) | G-002, G-006, G-007, G-008, G-009, G-010, G-011 | Type/error/identity consolidation, legacy bundle separation, credential rotation |
|
||||
| A (auth/account) | A-001..A-013, A-018; A-014..A-017 `[~]` | Session restore, signup, deep links, OAuth, profile, provider E2E |
|
||||
| O (onboarding/a11y) | O-001..O-012 | Onboarding branches, permissions, tutorial, a11y, full ko/en |
|
||||
| O (onboarding/a11y) | O-001..O-007, O-009, O-011; O-008/O-010/O-012 `[~]` | Onboarding branches, permissions, tutorial; app-wide theme coverage, TalkBack/font-scale/rotation; full-app ko/en audit |
|
||||
| D (data/sync/offline) | D-001..D-014 | Schema/RLS, devices, cross-surface read/write, offline queue, conflict policy |
|
||||
| R (record/transcribe) | R-001..R-009, R-011..R-015, R-017..R-020; R-016 `[x]` | Permissions, recorder, FGS, upload queue, job state, sharing |
|
||||
| H (history/meetings) | H-001..H-005, H-007..H-015; H-006 `[x]` | Pagination, filters, detail, meeting timeline, docs, diarization, cross-app |
|
||||
| F (feature parity) | F-001..F-010, F-013, F-014; F-011/F-012 `[x]` | Dashboard, dictionary, commands, actions, chat, conversation, knowledge |
|
||||
| H (history/meetings) | H-001..H-005, H-007..H-015; H-016 `[~]`; H-006 `[x]` | Pagination, filters, detail, meeting timeline/docs/diarization/cross-app; unified-search query/cancellation/performance gaps |
|
||||
| F (feature parity) | F-003..F-010, F-013, F-014; F-001 `[~]`; F-002/F-011/F-012 `[x]` | Mobile Home dashboard subset, commands/actions/chat/conversation/knowledge |
|
||||
| T (teams/admin/notify) | T-001..T-005, T-009..T-012; T-006..T-008, T-013 `[x]` | Team CRUD/roles, push delivery/deep links, notification settings |
|
||||
| M (monetization) | M-025, M-027; M-013 `[~]` | Play Billing license test, Payple webhook signature |
|
||||
| Q (quality/release) | Q-012..Q-014, Q-019..Q-022, Q-026; Q-010/Q-011/Q-023/Q-027/Q-028 `[~]` | Emulator scripts, OAuth E2E, billing E2E, visual/a11y gates, env unification, production AAB |
|
||||
| Q (quality/release) | Q-012..Q-014, Q-019, Q-020, Q-022, Q-026; Q-010/Q-011/Q-021/Q-023/Q-027/Q-028 `[~]` | Emulator scripts, OAuth/billing E2E, full journey; TalkBack/font-scale/rotation, env unification, production AAB |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue