d3ro-voice/docs/map/10-feature-catalog.md
Yun Chan 5c11ee2fde
Some checks failed
deploy-site / deploy (push) Failing after 33s
portable-unsigned / portable-windows (push) Failing after 4m7s
release / release-windows (push) Failing after 3m16s
release: ship v1.5.0 with on-device writing suggestions
Adds next-sentence suggestions while typing, weekly input insights and a
personal phrase memory to the desktop app, and fixes custom instructions so
they process the text instead of inserting the instruction's own wording.
Local model requests are now bounded and individually cancellable.

Bumps the product version to 1.5.0 (Android/iOS build 1050000), refreshes the
landing and web download links, and records the new INPUT feature rows and the
open verification gaps in the infrastructure map.
2026-09-23 16:04:27 +09:00

240 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 10 — Feature Catalog (Feature Map)
> The canonical feature map. One row = one user-facing capability.
> Status is per surface. Legend in [`00-index.md`](./00-index.md) §2.
> Surfaces: D = desktop (`apps/desktop`), W = web (`apps/web`), M = mobile (`apps/mobile-rn`), B = backend (Supabase/.NET).
**How to use an ID:** cite it in commits, plans, and backlog. Example: "CAP-03 is `[~]` on mobile (external OAuth pending)".
Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]` planned/absent · `[!]` blocked externally · `[-]` N/A.
---
## CAP — Capture & Transcribe
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| CAP-01 | Push-to-talk dictation (hold/release) | [x] | [-] | [x] | [-] | Desktop `VoiceModeService`; the trigger is now the rebindable `dictation` action of CAP-16 (several bindings per action, keyboard or mouse) rather than a single stored shortcut. The pipeline itself is unchanged and tested; the rewritten entry layer was confirmed in the 2026-09-21 manual run (CAP-16). Mobile RecordScreen via app CTA/notification action (no global hotkey) |
| CAP-02 | Hands-free toggle dictation | [x] | [-] | [x] | [-] | Desktop double-press shares the `dictation` binding and is split by the action's `doublePress` flag (`KeyBindingService.ts:680`). This path was **dead in shipped builds**: the previous lookup returned only the first matching action, so with both actions on the same binding double-press never reached hands-free. Fixed and confirmed in the 2026-09-21 manual run (CAP-16); core tests cover the contract side (same binding is not a conflict, `keybinding.test.ts:499`/`:734`). `KeyBindingService` still has no unit test of its own (GAP-KEY-01 evidence). Mobile toggle |
| CAP-03 | Live partial transcript while recording | [x] | [ ] | [ ] | [-] | Desktop `voice:partialTranscript` + recording-tip; producer added in `1.3.0` (`VoiceModeService._runPartial` → `LocalSTTService.transcribePartial`, 1.5 s cadence / 7.5 s window, never inserted). The row was `[x]` before any producer existed. |
| CAP-04 | Recording waveform + level meter | [x] | [x] | [x] | [-] | Desktop 9-bar cos distribution; mobile audio level; the recording-tip popup bundle and its on-disk assets are verified by `scripts/ci/verify-desktop-renderer-bundles.mjs` |
| CAP-05 | Device/mic selection | [x] | [ ] | [~] | [-] | Desktop config; mobile uses system default |
| CAP-06 | System/loopback audio capture | [x] | [-] | [ ] | [-] | Desktop only (caption source); mobile policy-limited |
| CAP-07 | Local Whisper STT | [x] | [-] | [x] | [-] | Desktop ships the faster-whisper sidecar (`resources/sidecar`, built by `sidecar:build`, verified by `scripts/ci/verify-sidecar-bundle.mjs`), warms it up at app start, and connects over IPv4 loopback; mobile on-device Whisper (supported devices) |
| CAP-08 | Cloud STT (multi-provider) | [x] | [x] | [x] | [x] | Desktop 6 providers + D3RO Cloud; web/mobile via `stt-proxy`; .NET internal gateway |
| CAP-09 | STT auto-fallback + fail-closed | [x] | [x] | [x] | [x] | `STTManager`; SSOT R-021/R-022 GREEN |
| CAP-10 | STT model download/management UI | [x] | [-] | [~] | [-] | Desktop model manager + onboarding; mobile bundled model |
| CAP-11 | File transcription (audio/video) | [x] | [ ] | [x] | [~] | Desktop ffmpeg chunking; mobile import picker; web deferred |
| CAP-12 | Audio import from other apps (share intent) | [-] | [-] | [x] | [-] | Mobile Android `ACTION_SEND`/`ACTION_VIEW` (SSOT R-016 GREEN) |
| CAP-13 | Live captions overlay | [x] | [-] | [-] | [-] | Desktop `CaptionService` + caption-overlay popup; popup assets verified in the packaged build (`scripts/ci/verify-desktop-renderer-bundles.mjs`); GAP-INFRA-05 |
| CAP-14 | Recording persistence / crash recovery | [x] | [ ] | [x] | [-] | Desktop WAV persist; mobile durable queue + process-kill WAV recovery |
| CAP-15 | Android foreground recording service | [-] | [-] | [x] | [-] | Mobile API 34 FGS + persistent notification (SSOT R-005 GREEN) |
| CAP-16 | Rebindable global key bindings (keyboard + mouse) | [x] | [-] | [-] | [-] | Contract SSOT `packages/core/src/keybinding.ts`: `KEY_CATALOG` (10 groups, `:615`), `KEYBINDING_ACTIONS` (6 actions, `:719`), `validateBinding` (`:953`), `detectBindingConflicts` (`:1016`). Multiple bindings per action persist as one `AppConfig.keyBindings` map (`packages/core/src/types.ts:459`), replacing the four singular `*Shortcut` fields; `ConfigService` migrates legacy values once (`ConfigService.ts:142`). `KeyBindingService` hooks keyboard **and** mouse via uiohook (`KeyBindingService.ts:387`) — MB1 is not bindable, MB2/MB3 need a modifier, MB4/MB5 are free, and no mouse button can be suppressed, so the original click still fires (warning surfaced in the UI). Selection is either key-recording or a searchable grouped dropdown (`KeyBindingPicker.tsx:536`). `history-popup`/`command-popup` were hardcoded in `bootstrap.ts` and are now rebindable actions (`bootstrap.ts:159`). **Verified 2026-09-21 on Windows by a manual run** (`%APPDATA%/d3ro-voice/logs/main.log`, 12:53–13:06): `ConfigService` migrated the four legacy shortcuts with the user's non-default values preserved exactly, `KeyBindingService` loaded 6 bindings for 6 actions and started the uiohook keyboard **and** mouse hook with zero boot errors, and keyboard plus mouse (MB4/MB5) bindings were exercised through the UI. A `Loaded 7 key binding(s) … for 6 action(s)` line later in the same session shows multi-binding working end to end. The migrated map was read back from `d3ro-voice-config.json`: legacy `*Shortcut` fields gone, no `displayLabel` left. Contract evidence: `packages/core` 117 tests GREEN, no renderer type errors in the key-binding files. **Still open:** `KeyBindingService` has no unit test of its own, macOS/Linux mouse behavior is unconfirmed (`11` GAP-KEY-02), and `command` still falls back to the dictation pipeline (GAP-KEY-03). W/M `[-]`: no OS-level global binding surface exists there (browser sandbox; mobile has no global hotkey, see CAP-01). B `[-]`: device-local setting, nothing server-side. See `11` GAP-KEY-02/03 (open), GAP-KEY-01 (`[x]`), and `11` §7 CONSTRAINT-I18N-01. |
---
## AI — AI Processing
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| AI-01 | Local LLM (Ollama) | [~] | [-] | [ ] | [-] | Desktop bundled Ollama. 2026-09-22 guard contract: no boot warmup; suggestion `keep_alive: 2m`; request-owned cancellation; generate/stream 2048 tokens / 120 s and chat 512 / 60 s; `done` frame required with incomplete-stream cleanup; deduplicated Ollama spawn/poll plus lifecycle disposal. GAP-LLM-03 has targeted evidence (6 test files / 69 passed / 0 failed; changed code/tests ESLint and diff check exit 0). Raw Ollama confirms cold timeout cleanup, a 553 ms `num_predict=1`/`keep_alive='2m'` response, and after the about-119.9 s expiry a 19:48:59 +09:00 `/api/ps` HTTP 200 / 45.8 ms `{models:[]}` observation with no intervening generate/unload/kill/retry; `/api/version` was HTTP 200 / 7.3 ms / `0.32.13`. This is raw API expiry/unload evidence, not app-restart or GUI/runtime proof (GAP-LLM-04). |
| AI-02 | Cloud LLM (Claude/OpenAI) | [x] | [x] | [x] | [x] | Desktop `PremiumLLMService`; web/mobile via `llm-proxy`; .NET `LlmProxyService` |
| AI-03 | Auto Polish (cleanup/filler removal) | [x] | [~] | [~] | [x] | Desktop built-in; web/mobile via commands. Desktop Auto Polish is the plain `refine` action (`llm-prompts.ts:14`), not a custom instruction, so it was **not** affected by the 2026-09-21 instruction-prompt fix (AI-05); regression cases now pin `refine`/`summarize`/`grammar`/`expand` (`VoiceModeService.test.ts:435`, `llm-prompts.test.ts:153`) |
| AI-04 | Translate / summarize / rephrase | [~] | [x] | [x] | [x] | Built-in instructions. **Desktop has two paths and only one of them worked.** The plain-action path (Settings → `defaultLLMAction`, `SettingsModal.tsx:653`) reads `BASE_SYSTEM_PROMPTS` directly and was always correct. The built-in *instruction* presets (`CustomInstructionService.ts:26/35/44/53/62`) ran through the custom-instruction path and inserted the instruction's own wording instead of the result — see AI-05. Fixed in `9c2b4d4` (2026-09-21), **not verified in a running app** (`11` GAP-LLM-02). Translate still always targets English: `AppConfig` has no target-language key and neither `language` (UI locale) nor `sttLanguage` (source language) can stand in (`llm-prompts.ts:37-52`, `11` GAP-LLM-01) |
| AI-05 | Custom instructions (user commands) | [~] | [x] | [x] | [x] | Web `commands`; mobile `CommandsScreen` — both go through Edge Functions and are unaffected. **Desktop: the custom-instruction path never worked in any shipped release.** The instruction was passed in the `text` argument of `processText(text, action, targetLanguage, customPrompt)` with the system-prompt argument left empty; `BASE_SYSTEM_PROMPTS` has no `custom` key, so resolution fell back to `refine` silently and the model polished the instruction it was handed — the transcript never reached it. Separately, only `{{text}}` was substituted and **none of the five built-ins use it** (`{{targetLanguage}}`, `{{userPrompt}}`, or no placeholder), so the substitution was a no-op from the day it was written. Introduced `fea923d` (2026-04-05); present `v0.1.0-alpha`..`v1.4.0`; **not a regression**. Three entry points were affected: commands-page activation (`CommandsPage.tsx:102-117`), command-popup selection (`bootstrap.ts:377-381`), voice keyword match (`VoiceModeService.ts:681-686`). Fixed in `9c2b4d4` (2026-09-21) — `llm-prompts.ts` is now the SSOT for placeholder substitution and argument placement (`renderInstructionPrompt:78`, `buildInstructionInvocation:101`, `resolveSystemPrompt:118`). The earlier "Red Team RT-03 verified" claim did not catch this and its cited evidence file (`red_team_log.md`) is not in the repo. `[~]` because unit tests pass (`llm-prompts.test.ts` 21, `llm-handlers.test.ts` 7, `VoiceModeService.test.ts` 22, `ChainService.test.ts` 5) but there is **no running-app verification** and the related `tests/red/{instruction,chain,voice,config}.usecase.test.ts` could not execute (`11` GAP-INFRA-06). See `11` GAP-LLM-01/02 |
| AI-06 | Voice keyword commands | [~] | [-] | [ ] | [-] | Desktop `VoiceCommandService` + command popup. Keyword matching itself works (`VoiceModeService.ts:681-686`), but execution went through the broken custom-instruction path (AI-05), and a **second, separate defect** made the shortcut skip LLM processing entirely whenever `defaultLLMAction === 'none'` — which is exactly the value the commands UI and the command popup write when no command is active (`CommandsPage.tsx:116`, `bootstrap.ts:385`), so an explicitly named instruction was nullified by the default setting. Both fixed in `9c2b4d4` (`VoiceModeService.ts:779`/`:785`); **not verified in a running app** (`11` GAP-LLM-02) |
| AI-07 | LLM Chains (multi-step pipelines) | [~] | [ ] | [ ] | [-] | Desktop `ChainService`. The argument placement was already correct here, but chain steps **never substituted placeholders**, so `{{targetLanguage}}` / `{{userPrompt}}` reached the model verbatim as the system prompt. Now shares the same resolution function as the other two paths (`ChainService.ts:196`). Fixed in `9c2b4d4` (2026-09-21); 5 unit tests GREEN, but `tests/red/chain.usecase.test.ts` could not execute (`11` GAP-INFRA-06) and there is **no running-app verification** (`11` GAP-LLM-02) |
| AI-08 | Screen/context capture for prompts | [x] | [-] | [ ] | [-] | Desktop `ScreenContextService` |
| AI-09 | Streaming responses | [x] | [x] | [x] | [x] | SSE/NDJSON streaming |
| AI-10 | Dictation templates (voice form fill) | [x] | [ ] | [x] | [~] | Desktop `DictationTemplateService`; mobile `TemplatesScreen` |
---
## MEM — Memory & Knowledge
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| MEM-01 | History list + search | [x] | [x] | [x] | [x] | Desktop SQLite; web/mobile Supabase |
| MEM-02 | History detail + edit | [x] | [x] | [x] | [x] | Mobile `HistoryDetailScreen` |
| MEM-03 | History favorites | [x] | [x] | [x] | [x] | |
| MEM-04 | History export / share | [x] | [~] | [x] | [x] | Desktop export; web limited; mobile share sheet |
| MEM-05 | History audio playback | [x] | [x] | [x] | [x] | Signed URLs on web/mobile |
| MEM-06 | Dictionary (custom vocabulary) | [x] | [x] | [x] | [x] | All surfaces CRUD; Desktop Red Team RT-02 & RT-18 fuzzed/verified |
| MEM-07 | Dictionary import/export | [x] | [x] | [x] | [-] | Desktop `dictionary:import/export` JSON+CSV (file dialogs, `DictionaryService`); web `serializeDictionary`/`importDictionaryFile` + header buttons; mobile CSV/TXT export + CSV/JSON/TXT import via `data-portability` |
| MEM-08 | Memos (tags over history) | [x] | [ ] | [x] | [x] | Desktop `MemoService`; mobile `MemosScreen`; web none |
| MEM-09 | Knowledge base / RAG add+index | [x] | [x] | [x] | [x] | Desktop local RAG (DEF-008 infinite chunking loop resolved, RT-08 verified); web/mobile cloud RAG |
| MEM-10 | Semantic search over knowledge | [x] | [x] | [x] | [x] | Web `KnowledgeSearch` calls `search-knowledge` (was mislabeled deferred); mobile + Edge `search-knowledge` |
| MEM-11 | Knowledge file upload | [x] | [x] | [x] | [x] | Web `.txt`/`.md` picker + newline-aware chunking + `embed-chunks`; desktop txt/md/pdf/docx; mobile file picker |
| 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 Supabase sync (V2-4); web/mobile native |
| MEM-14 | Memo tag search | [x] | [ ] | [x] | [x] | |
---
## MTG — Meetings
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| MTG-01 | Meeting recording + live transcript | [x] | [~] | [x] | [x] | Desktop `MeetingModeService`; web realtime view; mobile |
| MTG-02 | Meeting creation (title/attendees/language/template) | [x] | [x] | [x] | [x] | SSOT H-006 GREEN |
| MTG-03 | Timestamped memos during meeting | [x] | [x] | [x] | [x] | |
| MTG-04 | AI summary generation | [x] | [x] | [~] | [x] | Desktop `MeetingSummaryService`; web `generate-document-button`; mobile via Edge |
| MTG-05 | Document generation (minutes/report/idea-note/mindmap) | [x] | [x] | [x] | [x] | Edge `generate-meeting-document`; SSOT F-011 GREEN |
| MTG-06 | Document edit (Markdown) | [x] | [x] | [x] | [x] | Desktop editor; web `document-editor` |
| MTG-07 | Export PDF/DOCX/TXT/Markdown | [x] | [~] | [x] | [x] | Desktop `ExportMenu`; web markdown; mobile print/DOCX chooser (SSOT data portability GREEN) |
| MTG-08 | Speaker diarization | [~] | [ ] | [ ] | [ ] | Desktop `phase-15.5` (LLM estimate + pyannote prep); mobile SSOT H-014 pending |
| MTG-09 | Audio seek ↔ transcript timestamp | [ ] | [ ] | [ ] | [ ] | SSOT H-011 pending |
| MTG-10 | Meeting list search/filter/sort | [~] | [ ] | [~] | [x] | Basic lists; advanced filters pending |
| MTG-11 | Meeting AI chat over transcript | [x] | [ ] | [ ] | [x] | Desktop `MeetingChatPanel` + `MEETING_CHAT` |
| MTG-12 | Content reporting for generated docs | [-] | [ ] | [x] | [x] | `content-report` Edge + generation receipts |
---
## CV — Conversation
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| CV-01 | Local duplex voice conversation (STT→LLM→TTS) | [x] | [ ] | [x] | [-] | Desktop `VoiceConversationService`; mobile Talk |
| CV-02 | Realtime voice (OpenAI gpt-realtime, Premium) | [x] | [ ] | [ ] | [x] | Desktop `useRealtimeConversation` + `realtime-token` Edge |
| CV-03 | Text AI chat | [x] | [x] | [x] | [x] | Desktop chat, web `chat-panel`, mobile `TalkScreen` |
| CV-04 | TTS playback + controls | [x] | [ ] | [x] | [-] | Desktop SAPI/`say`; mobile Android TTS |
| CV-05 | Voice selection / backend selection | [x] | [ ] | [~] | [-] | Desktop settings `conversationBackend` |
---
## ACC — Accounts, Sync, Devices, Portability
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| ACC-01 | Email sign-up/login | [ ] | [x] | [x] | [x] | Desktop uses separate online-auth; SSOT A-001..A-005 `[ ]` rows are mobile checklist granularity |
| ACC-02 | OAuth Google | [~] | [x] | [~] | [x] | Mobile full consent→callback pending (external) |
| ACC-03 | OAuth GitHub / Apple | [~] | [~] | [~] | [~] | GitHub/Apple provider secrets pending |
| ACC-04 | Password reset / recovery deep link | [ ] | [ ] | [x] | [x] | Mobile ForgotPassword/UpdatePassword |
| ACC-05 | Account profile / identity management | [~] | [~] | [x] | [x] | Desktop `LicenseTab`; mobile `AccountScreen` |
| ACC-06 | Logout + local sensitive purge | [x] | [x] | [x] | [x] | Mobile central purge GREEN |
| ACC-07 | Account deletion (server cascade + local purge) | [ ] | [ ] | [~] | [x] | `account-delete` Edge; mobile device E2E pending |
| ACC-08 | Cloud sync (per-user data) | [x] | [x] | [x] | [x] | Desktop `CloudSyncService`; SSOT D-* largely `[ ]` granular |
| ACC-09 | Device registration + revocation | [-] | [ ] | [x] | [x] | Mobile `DevicesScreen` |
| ACC-10 | Offline queue + retry | [~] | [ ] | [x] | [x] | Mobile durable queue |
| ACC-11 | Data export/import (portability) | [~] | [ ] | [x] | [x] | Mobile canonical JSON E2E GREEN; desktop has export files |
| ACC-12 | Notification / push | [ ] | [~] | [~] | [x] | Backend transports for FCM + Web Push (VAPID) + APNs (.p8) + outbox cron drain implemented. Web/mobile client registration for webpush/apns still pending; Android delivery needs FCM project. |
---
## TEAM — Teams & Admin
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| TEAM-01 | Team create / rename / delete | [-] | [x] | [x] | [x] | Desktop N/A |
| TEAM-02 | Invite by email / link + accept deep link | [-] | [x] | [x] | [x] | `team-invite`/`team-accept` |
| TEAM-03 | Members + roles (admin/member/viewer) + leave | [-] | [x] | [x] | [x] | |
| TEAM-04 | Team meetings/docs sharing + RLS isolation | [-] | [x] | [~] | [x] | Cross-user isolation tested |
| TEAM-05 | Team comments / activity feed | [-] | [x] | [x] | [x] | `team_activities` migration + `create_team_activity` RPC + realtime; web `ActivityFeed`, mobile TeamDetail activity card (2026-09-13) |
| TEAM-06 | Admin back office (users/subs/models/usage/audit) | [-] | [-] | [x] | [x] | `apps/admin` + mobile `AdminScreen` |
| TEAM-07 | Role-based destructive action confirm + audit | [-] | [x] | [x] | [x] | SSOT T-006..T-008 GREEN |
| TEAM-08 | Desktop admin surface | [ ] | [-] | [-] | [-] | None; N/A by design |
---
## MON — Monetization & Ads
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| MON-01 | Tier gating (Free/Pro/Pro+/Team/Enterprise) | [x] | [x] | [x] | [x] | `LicenseService`, entitlement provider |
| MON-02 | Usage quotas (daily_usage) | [x] | [x] | [x] | [x] | |
| MON-03 | Desktop offline license (Ed25519) | [x] | [-] | [-] | [x] | `crypto-license` + admin issuer |
| MON-04 | Web checkout (Stripe) | [-] | [x] | [~] | [x] | Stripe checkout/portal/webhook |
| MON-05 | Web checkout (Payple) | [-] | [x] | [~] | [~] | Payple checkout/manage/renew/webhook; webhook signature pending |
| MON-06 | Paywall / upgrade prompts | [x] | [x] | [x] | [x] | `UpgradePromptModal`, `ProPaywallScreen` |
| MON-07 | Mobile IAP purchase + restore | [-] | [-] | [~] | [x] | `iap-verify` + `billing-context`; live store E2E blocked |
| MON-08 | Billing catalog / pricing display | [x] | [x] | [x] | [x] | Server catalog SSOT; hardcoded prices removed |
| MON-09 | Free-tier banner ads | [~] | [-] | [x] | [x] | Desktop adapters fail-closed; mobile AdMob test GREEN, prod serving blocked |
| MON-10 | Rewarded ads → quota credits | [~] | [-] | [x] | [x] | Desktop `RewardedQuotaModal` (stub adapters); mobile SSV GREEN |
| MON-11 | Ad mediation engine + settlement | [~] | [-] | [~] | [x] | Engine + settlement built. `DirectHouseSponsorAdapter` is now a **real configurable REST adapter** (bid/impression/click/reward via `endpointUrl`, fail-closed when unconfigured, unit-tested). Other 9 networks remain `UnavailableAdAdapter` stubs pending official SDKs. |
| MON-12 | Subscription management (portal/store) | [-] | [x] | [x] | [x] | Stripe portal / Payple manage / Play manage |
---
## INPUT — Input Intelligence (typing telemetry & next-sentence suggestions)
Everything here is **desktop-only** (Windows today) and **opt-in**: `inputTelemetryEnabled` and
`inputLearnTypedText` default to `false`. D=desktop, W=web, M=mobile, B=backend.
Status is `[~]` on desktop for one shared reason: the code and its 48 unit cases exist, but the
end-to-end behaviour has **not been verified by typing in a real app** (`11` GAP-INPUT-01).
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| INPUT-01 | Keyboard/mouse telemetry capture (opt-in) | [~] | [-] | [-] | [-] | `InputTelemetryService` — keystroke/click/scroll counters, mouse travel as the Manhattan sum of per-axis pixel deltas, active time, per-hour×app buckets flushed every 5 s. Key **contents** are never stored (ActivityWatch `aw-watcher-input` data-minimisation policy, adopted deliberately — see `11` §7). Hook ownership is ref-counted so `KeyBindingService` keeps working (`global-input-hook.ts`). |
| INPUT-02 | Foreground-app attribution | [~] | [-] | [-] | [-] | `utils/win32-foreground.ts` via `koffi` FFI (title/pid/exe/bounds), sampled at most 1×/s. `get-windows` was rejected: it needs an install script this repo does not run. |
| INPUT-03 | Weekly input insights | [~] | [-] | [-] | [-] | `INPUT_TELEMETRY.getSummary` aggregates `input_activity` into totals, daily series, top hours and top apps; rendered in Settings → Input and as a dashboard card. Daily average mouse travel is converted px → m using the display scale factor. |
| INPUT-04 | Typed-text learning (UIA, password-excluded) | [~] | [-] | [-] | [-] | Text is read from the focused field via `GET /uia/focus` (sidecar UIA bridge) and diffed longest-common-prefix/suffix, so **IME-committed Hangul/kana is counted correctly** — keycodes cannot reconstruct CJK text. UIA sends `hasSelection` only, derived by TextPattern range Start/End comparison without calling `GetText` on the selection range or adding a selected-text payload; the existing focused-field text can still include a selection. A non-collapsed selection immediately clears suggestions as `selection-active`. `IsPassword` is checked before any read (fail-closed); IME composition suppresses both stats and suggestions. |
| INPUT-05 | Personal phrase corpus (typed + voice) | [~] | [-] | [-] | [-] | Sentence-level phrases from typed text and from voice history (`HistoryService.create` feeds `recordExternalText`), ranked by frequency/recency as prompt hints; users can delete individual phrases. |
| INPUT-06 | Next-sentence suggestion (ghost text) | [~] | [-] | [-] | [-] | `SuggestionService` + `buildSuggestionPrompt` (`llm-prompts.ts` SSOT, instruction stays in the system prompt). The 2026-09-22 guard contract is 600 ms debounce, ≥5 s between requests, 6 requests/min by default (hard-config maximum 12), 3 candidates, 64 output tokens, 12-character growth before regeneration, 8 s request timeout and `keep_alive: 2m`; boot warmup is removed. Each request has its own cancellation signal. Presentation-active includes candidates, `generating`, `warmingUp` and `partialText`; clear/dismiss aborts, invalidates the generation token, clears TTL state and emits `cleared`/hide, and a final success resets `generating=false`/`partialText=null`. Focused evidence for the lifecycle and Windows-child-process change: five test files / 80 tests passed; desktop typecheck/lint, Python `py_compile`, and `git diff --check` exited 0 (core 131-test pass was independently verified earlier). Status remains `[~]`: this is not app-restart, GUI overlay, or real automatic-typing evidence. |
| INPUT-07 | Caret-anchored suggestion overlay | [~] | [-] | [-] | [-] | `suggestion-overlay` popup placed by `anchorFloatingPanel` (caret → element → cursor fallback, flip above when the caret is near the bottom, clamped to the work area). Non-focusable; click-through unless `suggestionOverlayInteractive`. While actually visible it continues periodic UIA validation after 5 s and revalidates 120 ms after mouse-up; unavailable/non-editable focus or a non-collapsed selection aborts and hides it. X first hides the renderer panel, then main IPC directly hides `BrowserWindow` and dismisses the service, so late tokened results cannot revive it. Accept/next/prev/dismiss are four global key bindings (`suggestion-accept`/`next`/`prev`/`dismiss`, default `Ctrl+Alt+→/↓/↑/←`), and the overlay has a mouse close button. Up to three candidates are shown in a scrollable list with a warm-up/generating spinner. |
| INPUT-08 | Per-app exclusions & consent controls | [~] | [-] | [-] | [-] | `inputExcludedApps` (executable names, case-insensitive) blocks both collection context and suggestions; telemetry master switch, pause, text-learning toggle and "delete collected data" all live in Settings → Input. 30-day retention prune runs on start. |
| INPUT-09 | Flow Radar | [~] | [-] | [-] | [-] | `rankFlowWindows` ranks hourly aggregate activity density, character volume and edit stability into potential-focus time windows. It is not a real-session detector or session record. |
| INPUT-10 | Edit Friction | [~] | [-] | [-] | [-] | `calculateFrictionInsight` derives friction from char/backspace quantities and reports edits per 100 chars; it does not infer sentiment or productivity. |
| INPUT-11 | App DNA | [~] | [-] | [-] | [-] | Personal phrase ranking passes `appName` context and applies a 1.75× same-app ranking bonus (`selectPhraseHints`); learned text remains opt-in. |
| INPUT-12 | App Quality | [~] | [-] | [-] | [-] | `InputTelemetryService.getSummary` aggregates per-app suggestion total, accepted count, accept rate and average latency from local suggestion history. |
| INPUT-13 | Privacy Receipt | [~] | [-] | [-] | [-] | Local-only receipt shows actual row counts. Raw individual key events/key codes/content stream are not retained; opt-in learned text can reside in `typing_samples`/`personal_phrases`. `input_activity`, `typing_samples` and `suggestions` retain 30 days; `personal_phrases` has no age-based automatic expiry and is removed by individual deletion, delete-all, or consent withdrawal. Receipt read/delete storage failures are IPC errors. |
| INPUT-14 | Smart Exclusion | [~] | [-] | [-] | [-] | Password fields add no evidence. Only the current app may receive a one-click recommendation after ≥4 observations, readable=0 and problematic ratio ≥75%; it never auto-excludes. |
| INPUT-15 | Why This Suggestion | [~] | [-] | [-] | [-] | Overlay exposes only local-model/local-memory provenance and continuation/related/phrase/appPhrase counts; raw evidence text is not shown. |
| INPUT-16 | Memory Decay | [~] | [-] | [-] | [-] | Phrase ranking combines a 30-day half-life, frequency and the App DNA 1.75× same-app bonus. |
| INPUT-17 | Instant Recall | [~] | [-] | [-] | [-] | Local-memory fallback (`provenance=local-memory`) occurs only for unavailable model, non-cancellation error, actual timeout or empty model result; it spends no additional model budget and is blocked on dismiss, new typing, token/context mismatch or staleness. |
| INPUT-18 | Shortcut Safety Audit | [~] | [-] | [-] | [-] | Settings UI applies core `auditKeyBindingMap` to surface invalid/conflict issues while hold/double-press exceptions remain unchanged. |
---
## SHELL — Platform Shell, Settings, Onboarding, Support
| ID | Feature | D | W | M | B | Anchors / notes |
|---|---|---|---|---|---|---|
| SHELL-01 | Settings / preferences | [x] | [~] | [x] | [x] | Desktop tabbed modal; the General tab hosts the whole key-binding editor (CAP-16: global on/off switch + one `KeyBindingField` per action, grouped voice/window — `SettingsModal.tsx:239`), which is also the first settings entry point the `command` action ever had; web theme/i18n; mobile `SettingsScreen` |
| SHELL-02 | Theme system (6 themes) | [x] | [x] | [x] | [-] | `theme.ts` SSOT |
| SHELL-03 | i18n (12 locales) | [x] | [x] | [x] | [-] | `@d3ro/i18n`; ko/en fully translated, others partial. Measured 2026-09-21: `ko` 1716 keys / `en` 1709 / the other ten 327 each, so ~1,380 keys fall back for non-English locales — tracked as `11` GAP-I18N-01 |
| SHELL-04 | Onboarding / first-run | [x] | [ ] | [x] | [-] | Desktop model bootstrap; mobile audience/theme/locale |
| SHELL-05 | Accessibility / reduced motion | [~] | [~] | [~] | [-] | Desktop reduced-motion honored; mobile a11y rows pending |
| SHELL-06 | System tray / background | [x] | [-] | [-] | [-] | Desktop tray |
| SHELL-07 | Auto-launch on login | [x] | [-] | [-] | [-] | Desktop only |
| SHELL-08 | Auto-update | [x] | [-] | [!] | [-] | Desktop electron-updater; Forgejo canonical feed; channels + mandatory/major-vs-delta policy (`release/update-policy.json`); mobile store updates |
| SHELL-09 | Support / diagnostics | [x] | [ ] | [ ] | [~] | Desktop `SupportModal`; admin `/support` stub |
| SHELL-10 | Download center / releases | [-] | [x] | [-] | [x] | Web `/download`, admin `/releases`, Forgejo |
| SHELL-11 | Landing site / legal pages | [-] | [-] | [-] | [-] | `site/` — privacy/terms/delete-account live |
| SHELL-12 | Notifications (in-app / desktop) | [x] | [ ] | [x] | [x] | Desktop events; mobile push |
---
## INFRA — Build, CI, Release, Quality
| ID | Feature | Status | Anchors |
|---|---|---|---|
| INFRA-01 | Monorepo + workspaces + turbo | [x] | `package.json`, `turbo.json` |
| INFRA-14 | Unified entitlement resolver (`@d3ro/core/entitlement`) | [x] | `EntitlementSnapshot` + `resolveEntitlement` map Supabase/desktop-license/.NET sources to one contract; desktop `isPro` fixed, `syncFromCloud` normalized; mobile/web adoption incremental (see `11` GAP-ID-02) |
| INFRA-02 | Shared packages | [x] | `packages/*` |
| INFRA-03 | Desktop build + signed packaging | [x] | `electron-builder.yml`, GitLab `package-windows/macos` |
| INFRA-04 | Mobile CI (debug/E2E/release) | [~] | `.github/workflows/ci.yml`, `.gitlab-ci.yml`; production AAB external |
| INFRA-05 | .NET API tests | [x] | `apps/api-server.Tests` |
| INFRA-06 | Edge function tests (Deno) | [x] | `server/supabase/functions`, `tests/` |
| INFRA-07 | E2E desktop (playwright) | [x] | `apps/desktop/tests`, `apps/desktop/playwright.config.ts` |
| INFRA-08 | E2E web (playwright) | [x] | `apps/web/e2e` |
| INFRA-09 | E2E mobile (Maestro + instrumentation) | [~] | `.maestro/`, `androidTest`; emulator API 35 gate in CI |
| INFRA-10 | Secret scanning / release boundaries | [x] | `scripts/ci/check-no-hardcoded-secrets.mjs`, `verify-mobile-release-*.mjs` |
| INFRA-11 | Docker + NAS deploy | [x] | `docker-compose.nas.yml`, `scripts/deploy-nas.*` |
| INFRA-12 | Cloudflare edge + tunnel | [x] | `server/cloudflare-worker`, Cloudflare Tunnel `kd-nas` |
| INFRA-13 | Site deploy (Cloudflare Pages + GitHub Pages) | [x] | `.forgejo/workflows/deploy-site.yml`, `.github/workflows/deploy-site.yml` |
| INFRA-15 | Update & release system | [x] | Canonical Forgejo feed + channels/policy (`release/update-policy.json`, `src/main/update-policy.ts`), canonical publisher `scripts/ci/publish-forgejo-release.mjs`, legacy GitLab mirror; `npm run release:metadata:test`. v1.1.0 was published to Forgejo on 2026-09-15; product version moved to `1.2.0` as a forward-fix with CI-only publication, a same-version re-release guard, and download centers that link the feed instead of repository paths. `1.3.0` (2026-09-18) carries the local-STT fixes; Windows publication still needs the CI signing secrets (`11` GAP-REL-02). |
| INFRA-16 | Desktop STT engine packaging | [x] | `apps/desktop/scripts/setup-sidecar.mjs` + `build-sidecar.mjs`, `electron-builder.yml` `extraResources` (`sidecar-dist/sidecar` → `resources/sidecar`, `resources/ffmpeg`), and `scripts/ci/verify-sidecar-bundle.mjs` run in `package-windows`/`package-macos` before electron-builder. Verified on the real bundle: `sidecar.exe` + `_internal` including `faster_whisper/assets/silero_vad_v6.onnx`, plus a packaged-engine transcription round-trip on GPU. |
| INFRA-17 | 서명 없는 배포 채널 (portable + Scoop) | [x] | `scripts/ci/build-portable.mjs` (95MiB 7z 분할 볼륨 + Scoop 매니페스트), `scripts/ci/publish-portable-release.mjs`, `scripts/local/install-d3ro-voice.ps1`, `bucket/` 버킷, `.forgejo/workflows/portable.yml`; 7z 분할 볼륨(Scoop, 162MiB) + zip 분할 부품(수동 설치, 243MiB, 7-Zip 불필요); updater feed와 분리. 2026-09-18 `portable-1.3.1` 게시 + 실제 설치 검증. |
| INFRA-18 | 로컬 런타임 온디맨드 설치 | [x] | `RuntimeProvisioner`(부품 다운로드 + SHA-256 검증 + tar 해제, `%APPDATA%/d3ro-voice/runtime`), `POST runtime:ensure` / `runtime:progress` IPC, 설정 > STT 상태/내려받기 UI. 설치본에서 엔진/ffmpeg를 분리해 189MB → 90.6MiB, 업데이트 피드 게시 복구. 검증은 전부 디스크에 기록된 파일 기준이며(부품 크기·해시 → 결합본 크기·해시), 부품 다운로드는 최대 3회 재시도한다. 2026-09-18 실제 feed 통합 검증(엔진 94.4MiB/18초, ffmpeg 21.7MiB/5초). |
| INFRA-19 | 네이티브 ABI + updater 설정 게이트 | [x] | `scripts/ci/verify-native-abi.mjs`(패키징된 `better_sqlite3.node`가 Electron ABI인지 호스트 Node 로드 거부로 판별) + `scripts/ci/fix-native-abi.mjs`(로컬 잠금 우회용 주입). GitLab/Forgejo/GitHub 패키징 단계에 검증 삽입. 2026-09-18: Node ABI 모듈로 앱이 시작 즉시 죽은 사고 + 누락으로 자동 업데이트가 죽은 사고를 함께 방지(). |
---
## Coverage summary (by surface)
| Surface | `[x]` | `[~]` | `[ ]` | Notable strength | Notable weakness |
|---|---|---|---|---|---|
| Desktop | ~40 | 3 | ~8 | Local AI pipeline, meetings, RAG, conversation, key bindings | Ads stubs, no team admin, no email account; custom-instruction/chain LLM path (AI-04..07) fixed 2026-09-21 but unverified in a running app |
| Web | ~22 | 6 | ~14 | Server-shared data UX, billing, meetings, teams | No local AI, limited knowledge upload/search |
| Mobile | ~40 | 12 | ~18 | Cloud + native recording, portability, admin, IAP/ads | External store/console gates, a11y, deep E2E pending |
| Backend | ~45 | 6 | ~4 | RLS, Edge functions, billing, fail-closed AI | Payple webhook signature, some external provider keys |