docs(map): production caught up to 0035, knowledge/recordings/settings sync, remaining GUI check
This commit is contained in:
parent
9a8f7e6aa6
commit
8d6b166de5
6 changed files with 20 additions and 13 deletions
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
> Status: ACTIVE
|
> Status: ACTIVE
|
||||||
> Last full audit: 2026-09-13
|
> Last full audit: 2026-09-13
|
||||||
> Last update: 2026-09-24 — **v1.6.0 릴리스.** 버전 SSOT를 1.6.0(android/iOS 1060000)으로 올렸다. 다음 문장 제안을 1개씩 순차 생성해 최대 12개·3개씩 페이지로 보여 주고(Ctrl+Alt+↑↓ 이동, ←→ 페이지, Enter 수락, Esc 닫기; 기존 기본 단축키는 revision 5에서 이관), 오버레이를 재설계했다(`INPUT-06`/`INPUT-07`). 개인 문구 코퍼스는 터미널·에디터·코딩 에이전트와 비문장·플레이스홀더를 학습하지 않고 기존 데이터도 같은 규칙으로 정리한다(`INPUT-04`/`INPUT-05`). 제안 단축키가 받아쓰기를 켜던 버그, 구버전 사이드카가 `/uia/focus` 없이 남던 버그(런타임 최소 버전 1.5.0), Ollama 러너 콘솔 창, 사이드카 이중 기동을 고쳤다. 실시간 자막 창은 끌어서 옮길 수 있고 첫 자막 전까지 준비 안내를 보인다. 무서명 로컬 게시 예외(GAP-REL-06)를 유지한다.
|
> Last update: 2026-09-27 — **v1.7.0 릴리스 + 기기 간 동기화 재작성 + 운영 DB 정렬.** 데스크톱↔모바일·웹 동기화를 outbox·서버시각 커서·삭제 기록(`sync_tombstones`)·실시간(`ws`) 엔진으로 다시 만들고, 이력·사전·회의·메모 태그·명령·템플릿·지식베이스·녹음·공통 설정·기기 등록까지 넓혔다(MEM-13, ACC-08/09, MEM-05). 운영 Supabase가 `0028`에 멈춰 있던 것을 발견해 `0029`–`0035`를 적용하고 함수 23개를 재배포했다(GAP-OPS-01 — 사용자가 자기 tier를 올릴 수 있던 권한 구멍 포함). 모바일 기기 목록·데이터 내보내기 결함도 고쳤다(GAP-SYNC-03/04). 무서명 로컬 게시 예외(GAP-REL-06) 유지.
|
||||||
|
>
|
||||||
|
> Previous update: 2026-09-24 — **v1.6.0 릴리스.** 버전 SSOT를 1.6.0(android/iOS 1060000)으로 올렸다. 다음 문장 제안을 1개씩 순차 생성해 최대 12개·3개씩 페이지로 보여 주고(Ctrl+Alt+↑↓ 이동, ←→ 페이지, Enter 수락, Esc 닫기; 기존 기본 단축키는 revision 5에서 이관), 오버레이를 재설계했다(`INPUT-06`/`INPUT-07`). 개인 문구 코퍼스는 터미널·에디터·코딩 에이전트와 비문장·플레이스홀더를 학습하지 않고 기존 데이터도 같은 규칙으로 정리한다(`INPUT-04`/`INPUT-05`). 제안 단축키가 받아쓰기를 켜던 버그, 구버전 사이드카가 `/uia/focus` 없이 남던 버그(런타임 최소 버전 1.5.0), Ollama 러너 콘솔 창, 사이드카 이중 기동을 고쳤다. 실시간 자막 창은 끌어서 옮길 수 있고 첫 자막 전까지 준비 안내를 보인다. 무서명 로컬 게시 예외(GAP-REL-06)를 유지한다.
|
||||||
>
|
>
|
||||||
> Previous update: 2026-09-22 — **Gemma/Ollama 폭주 방어 계약을 기록했다.** 19:14:12 부팅 워밍업이 `keep_alive: 30m`으로 `gemma4:e4b`를 19:44:12까지 VRAM 3,226,342,521 bytes / context 4096으로 강제 상주시킨 것이 관측됐으며, 같은 시점 Windows GPU Engine PID 표본에는 Ollama의 활성 compute가 없었다. 즉 당시 상태는 무한 추론이 아니라 강제 residency였다. 19:11:17~19:11:58의 자동 제안 연속 생성은 기존 900 ms·12/min·5 candidates·128 tokens·한 글자 재생성 정책이 허용한 burst였다. 현재 구현 계약은 부팅 warmup 제거, `keep_alive: 2m`, 제안 600 ms / 최소 5 s 간격 / 기본 6회·hard max 12회 per min / 3 candidates / 64 tokens / 12-char growth / 8 s timeout, 그리고 요청별 취소·상한·종료 정리다. 독립 표적 검증은 6 test files / 69 tests passed / 0 failed, 변경 코드·테스트 ESLint와 `git diff --check`도 exit 0이다. Raw Ollama에서는 cold bounded 요청이 client hard timeout 15.044 s에 취소된 뒤 `/api/ps`가 비었고 `/api/version`은 80 ms에 회복했다. 명시 warmup은 HTTP 200 / 16.639 s, 후속 warm 요청은 body `options.num_predict=1`, `keep_alive='2m'`로 553 ms HTTP 200 / `done:true` / `eval_count:1` / `done_reason:length`였고 `/api/ps` expiry는 약 119.9 s였다. **19:48:59 +09:00에는 새 generate/unload/kill/retry 없이 충분히 지난 뒤 한 번의 `/api/ps`가 HTTP 200 / 45.8 ms / `{models:[]}`였고 `/api/version`은 HTTP 200 / 7.3 ms / `0.32.13`이었다.** 이는 raw API 수준의 expiry 뒤 unload 확인일 뿐 앱 재시작·GUI·실제 타이핑 증거는 아니므로 상태는 `[~]`로 유지한다 (`11` GAP-LLM-04, GAP-INPUT-06).
|
> Previous update: 2026-09-22 — **Gemma/Ollama 폭주 방어 계약을 기록했다.** 19:14:12 부팅 워밍업이 `keep_alive: 30m`으로 `gemma4:e4b`를 19:44:12까지 VRAM 3,226,342,521 bytes / context 4096으로 강제 상주시킨 것이 관측됐으며, 같은 시점 Windows GPU Engine PID 표본에는 Ollama의 활성 compute가 없었다. 즉 당시 상태는 무한 추론이 아니라 강제 residency였다. 19:11:17~19:11:58의 자동 제안 연속 생성은 기존 900 ms·12/min·5 candidates·128 tokens·한 글자 재생성 정책이 허용한 burst였다. 현재 구현 계약은 부팅 warmup 제거, `keep_alive: 2m`, 제안 600 ms / 최소 5 s 간격 / 기본 6회·hard max 12회 per min / 3 candidates / 64 tokens / 12-char growth / 8 s timeout, 그리고 요청별 취소·상한·종료 정리다. 독립 표적 검증은 6 test files / 69 tests passed / 0 failed, 변경 코드·테스트 ESLint와 `git diff --check`도 exit 0이다. Raw Ollama에서는 cold bounded 요청이 client hard timeout 15.044 s에 취소된 뒤 `/api/ps`가 비었고 `/api/version`은 80 ms에 회복했다. 명시 warmup은 HTTP 200 / 16.639 s, 후속 warm 요청은 body `options.num_predict=1`, `keep_alive='2m'`로 553 ms HTTP 200 / `done:true` / `eval_count:1` / `done_reason:length`였고 `/api/ps` expiry는 약 119.9 s였다. **19:48:59 +09:00에는 새 generate/unload/kill/retry 없이 충분히 지난 뒤 한 번의 `/api/ps`가 HTTP 200 / 45.8 ms / `{models:[]}`였고 `/api/version`은 HTTP 200 / 7.3 ms / `0.32.13`이었다.** 이는 raw API 수준의 expiry 뒤 unload 확인일 뿐 앱 재시작·GUI·실제 타이핑 증거는 아니므로 상태는 `[~]`로 유지한다 (`11` GAP-LLM-04, GAP-INPUT-06).
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ The existing `input-telemetry-handlers` and `suggestion-handlers` IPC extensions
|
||||||
| `ConfigService` | electron-store `AppConfig` (`configGet/Set`, defaults) |
|
| `ConfigService` | electron-store `AppConfig` (`configGet/Set`, defaults) |
|
||||||
| `LicenseService` | Freemium tiers, quotas (`daily_usage`), activation, upgrade prompts |
|
| `LicenseService` | Freemium tiers, quotas (`daily_usage`), activation, upgrade prompts |
|
||||||
| `CloudSyncService` | Supabase auth + lifecycle: per-user DB switching, local-mode import on first sign-in, Realtime (`ws` transport) + 5-min heartbeat, device check-in, debounced flush/pull triggers. The sync itself lives in `services/sync/` |
|
| `CloudSyncService` | Supabase auth + lifecycle: per-user DB switching, local-mode import on first sign-in, Realtime (`ws` transport) + 5-min heartbeat, device check-in, debounced flush/pull triggers. The sync itself lives in `services/sync/` |
|
||||||
| `services/sync/` | `SyncEngine` (backfill once per user DB → push outbox → pull by server-clock keyset cursor → tombstones → memo-tag reconcile), `sync-outbox` (`sync_outbox`/`sync_state` tables), `sync-adapters` (history, dictionary, meetings, meeting memos/documents, custom commands, templates), `memo-tag-sync`, `supabase-sync-remote`, `device-registration`, `realtime-transport`. Services record changes with `getCloudSyncService().pushOne/pushDelete(entity, id)`; pulled rows are applied without re-queuing |
|
| `services/sync/` | `SyncEngine` (backfill once per user DB → push outbox → pull by server-clock keyset cursor → tombstones → memo-tag reconcile), `sync-outbox` (`sync_outbox`/`sync_state` tables), `sync-adapters` (history, dictionary, meetings, meeting memos/documents, custom commands, templates), `memo-tag-sync`, `supabase-sync-remote`, `device-registration`, `realtime-transport`, `settings-sync` (mobile `user_settings` ↔ language/theme/defaultLLMAction/activeInstructionId), `audio-sync` (recording upload to the `audio` bucket, remote cleanup on delete), knowledge adapter (source-text chunks). Services record changes with `getCloudSyncService().pushOne/pushDelete(entity, id)`; pulled rows are applied without re-queuing |
|
||||||
| `CloudSTTService` | Thin cloud STT wrapper over `D3ROCloudDriver` |
|
| `CloudSTTService` | Thin cloud STT wrapper over `D3ROCloudDriver` |
|
||||||
| `UpdateService` | electron-updater (canonical Forgejo feed, channels, mandatory/full-vs-delta policy, staged rollout, restart dialog) |
|
| `UpdateService` | electron-updater (canonical Forgejo feed, channels, mandatory/full-vs-delta policy, staged rollout, restart dialog) |
|
||||||
| `AutoLaunchService` | OS login-item auto-start |
|
| `AutoLaunchService` | OS login-item auto-start |
|
||||||
|
|
@ -130,7 +130,7 @@ Registry: `src/main/ipc/index.ts` calls 31 `registerXHandlers()` in fixed order.
|
||||||
| `context-handlers` | CONTEXT |
|
| `context-handlers` | CONTEXT |
|
||||||
| `dictionary-handlers` | DICTIONARY |
|
| `dictionary-handlers` | DICTIONARY |
|
||||||
| `file-transcription-handlers` | FILE_TRANSCRIPTION |
|
| `file-transcription-handlers` | FILE_TRANSCRIPTION |
|
||||||
| `history-handlers` | HISTORY (incl. `history:setFavorite`) + `stats:getSummary` |
|
| `history-handlers` | HISTORY (incl. `history:setFavorite`, `history:getAudio` → local bytes or signed URL) + `stats:getSummary` |
|
||||||
| `input-telemetry-handlers` | INPUT_TELEMETRY |
|
| `input-telemetry-handlers` | INPUT_TELEMETRY |
|
||||||
| `instruction-handlers` | INSTRUCTION |
|
| `instruction-handlers` | INSTRUCTION |
|
||||||
| `keybinding-handlers` | KEYBINDING |
|
| `keybinding-handlers` | KEYBINDING |
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
## 1. Supabase project (`server/supabase/`)
|
## 1. Supabase project (`server/supabase/`)
|
||||||
|
|
||||||
- `config.toml` — project_id `d3ro-voice`; local ports API 55321 / DB 55322 / Studio 55323 / Inbucket 55324; DB major v17; storage 50 MiB; auth `site_url` localhost:5173, redirects include `https://d3ro.chanpaca.net` and `d3ro-voice://auth-callback`; external providers Google/GitHub/Apple; per-function `verify_jwt` settings; analytics off.
|
- `config.toml` — project_id `d3ro-voice`; local ports API 55321 / DB 55322 / Studio 55323 / Inbucket 55324; DB major v17; storage 50 MiB; auth `site_url` localhost:5173, redirects include `https://d3ro.chanpaca.net` and `d3ro-voice://auth-callback`; external providers Google/GitHub/Apple; per-function `verify_jwt` settings; analytics off.
|
||||||
- `seed.sql`, `migrations/` (63), `functions/` (~27), `functions/_shared/`, `tests/`, `deno.json`.
|
- `seed.sql`, `migrations/` (65; production at `20260927000035` — it lagged at `0028` until 2026-09-27, GAP-OPS-01), `functions/` (23, all redeployed 2026-09-27), `functions/_shared/`, `tests/`, `deno.json`.
|
||||||
|
|
||||||
> Note: root `supabase/` contains only empty scaffolding (`.branches/`, `snippets/`). The real project lives under `server/supabase/`.
|
> Note: root `supabase/` contains only empty scaffolding (`.branches/`, `snippets/`). The real project lives under `server/supabase/`.
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
| Core schema + RLS + auth triggers | initial tables, profiles, triggers |
|
| Core schema + RLS + auth triggers | initial tables, profiles, triggers |
|
||||||
| Storage buckets | audio, meeting documents, exports |
|
| Storage buckets | audio, meeting documents, exports |
|
||||||
| Teams | team invites, membership, roles, `team_activities` feed (`20260913000033`) |
|
| Teams | team invites, membership, roles, `team_activities` feed (`20260913000033`) |
|
||||||
| Cross-device sync | `20260927000034`: `sync_tombstones` (AFTER DELETE triggers on 8 synced tables, owner-read RLS, realtime, `prune_sync_tombstones_v1`), `history.revision` bump on content change, `meeting_memos.updated_at`, `(user_id, updated_at, id)` cursor indexes, client-keyed `sync_upsert/delete_user_template_v1`, realtime for `devices`/`memo_tags`/`custom_instructions`/`user_templates`, and `export_account_portability` projected back onto v1 archive keys |
|
| Cross-device sync | `20260927000035`: knowledge_documents tombstones/cursor/realtime + `pg_cron` job `prune-sync-tombstones` (daily 03:17 UTC). `20260927000034`: `sync_tombstones` (AFTER DELETE triggers on 8 synced tables, owner-read RLS, realtime, `prune_sync_tombstones_v1`), `history.revision` bump on content change, `meeting_memos.updated_at`, `(user_id, updated_at, id)` cursor indexes, client-keyed `sync_upsert/delete_user_template_v1`, realtime for `devices`/`memo_tags`/`custom_instructions`/`user_templates`, and `export_account_portability` projected back onto v1 archive keys |
|
||||||
| Knowledge / RAG | `knowledge_documents`, `knowledge_chunks`, pgvector |
|
| Knowledge / RAG | `knowledge_documents`, `knowledge_chunks`, pgvector |
|
||||||
| Notifications / push | push tokens, durable outbox |
|
| Notifications / push | push tokens, durable outbox |
|
||||||
| Billing | Payple, subscriptions, payment provider events/operations (`'stripe'` provider values kept only for historical rows) |
|
| Billing | Payple, subscriptions, payment provider events/operations (`'stripe'` provider values kept only for historical rows) |
|
||||||
|
|
|
||||||
|
|
@ -58,15 +58,15 @@ Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]`
|
||||||
| MEM-02 | History detail + edit | [x] | [x] | [x] | [x] | Mobile `HistoryDetailScreen` |
|
| MEM-02 | History detail + edit | [x] | [x] | [x] | [x] | Mobile `HistoryDetailScreen` |
|
||||||
| MEM-03 | History favorites | [~] | [x] | [x] | [x] | Desktop had **no** favorites until 2026-09-27 (the old `[x]` was wrong): `history.is_favorite` column, `HistoryService.setFavorite` + `history:setFavorite` IPC, star toggle in `HistoryEntryCard`; the value is the same Supabase `history.is_favorite` mobile filters on, so it syncs both ways (`apps/desktop/tests/main/sync/SyncEngine.test.ts` (20) + `apps/desktop/tests/integration/cross-device-sync.supabase.test.ts` (5, local Supabase stack)). `[~]` until a GUI run toggles it and mobile shows it |
|
| MEM-03 | History favorites | [~] | [x] | [x] | [x] | Desktop had **no** favorites until 2026-09-27 (the old `[x]` was wrong): `history.is_favorite` column, `HistoryService.setFavorite` + `history:setFavorite` IPC, star toggle in `HistoryEntryCard`; the value is the same Supabase `history.is_favorite` mobile filters on, so it syncs both ways (`apps/desktop/tests/main/sync/SyncEngine.test.ts` (20) + `apps/desktop/tests/integration/cross-device-sync.supabase.test.ts` (5, local Supabase stack)). `[~]` until a GUI run toggles it and mobile shows it |
|
||||||
| MEM-04 | History export / share | [x] | [~] | [x] | [x] | Desktop export; web limited; mobile share sheet |
|
| 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-05 | History audio playback | [~] | [x] | [x] | [x] | Signed URLs on web/mobile. **Desktop had no playback at all** (the old `[x]` was wrong); since 2026-09-27 the history card plays this device's recording from the file (`history:getAudio` → bytes → Blob) and a phone/web recording through a 5-minute signed URL of its `audio_files` row. Desktop recordings upload to the same `audio` bucket contract (`{userId}/desktop/...`, `audio_files`, ≤50 MiB, Settings > Cloud toggle `cloudSyncAudio`) so the phone plays them too (`tests/main/sync/SyncExtensions.test.ts` (9) + integration case "지식 문서·설정·녹음" against the local Supabase stack). `[~]` until a GUI run plays both kinds |
|
||||||
| MEM-06 | Dictionary (custom vocabulary) | [x] | [x] | [x] | [x] | All surfaces CRUD; Desktop Red Team RT-02 & RT-18 fuzzed/verified |
|
| 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-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. **Desktop tags sync with mobile since 2026-09-27**: pushed through the mobile RPCs (`mobile_add/remove_memo_tag_v1`) and reconciled by natural key `historyId` + normalized tag because the server owns tag ids and renames rows (`services/sync/memo-tag-sync.ts`). Tag normalisation (trim, collapse spaces, lower-case) is now identical on both sides |
|
| MEM-08 | Memos (tags over history) | [x] | [ ] | [x] | [x] | Desktop `MemoService`; mobile `MemosScreen`; web none. **Desktop tags sync with mobile since 2026-09-27**: pushed through the mobile RPCs (`mobile_add/remove_memo_tag_v1`) and reconciled by natural key `historyId` + normalized tag because the server owns tag ids and renames rows (`services/sync/memo-tag-sync.ts`). Tag normalisation (trim, collapse spaces, lower-case) is now identical on both sides |
|
||||||
| 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-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 **Sync (2026-09-27):** desktop documents mirror `knowledge_documents` + `knowledge_chunks` as source text; each surface embeds with its own model (desktop nomic 768-d, server 1536-d), the server index is requested through `embed-chunks`. Documents synced from the phone are stored without a file and re-indexed from their stored chunks (`RAGService.applyRemoteDocument`). Chunk text is now kept even when local embedding fails |
|
||||||
| MEM-10 | Semantic search over knowledge | [x] | [x] | [x] | [x] | Web `KnowledgeSearch` calls `search-knowledge` (was mislabeled deferred); mobile + Edge `search-knowledge` |
|
| 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-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 **Sync (2026-09-27):** desktop documents mirror `knowledge_documents` + `knowledge_chunks` as source text; each surface embeds with its own model (desktop nomic 768-d, server 1536-d), the server index is requested through `embed-chunks`. Documents synced from the phone are stored without a file and re-indexed from their stored chunks (`RAGService.applyRemoteDocument`). Chunk text is now kept even when local embedding fails |
|
||||||
| MEM-12 | Voice actions (OS automation) | [x] | [x] | [x] | [x] | Desktop `VoiceActionService`; web `ActionRunner` (simulated); mobile `ActionsScreen` |
|
| 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) |
|
| 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-14 | Memo tag search | [x] | [ ] | [x] | [x] | |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -96,10 +96,11 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res
|
||||||
| GAP-SYNC-02 | Sync | **Desktop Realtime never connected.** Electron 33 main (Node 20.18) has no global `WebSocket`; supabase-js Realtime only ever logged `TIMED_OUT`/`CLOSED` (installed-app log 2026-09-16) and gave up after three retries. Also: filtered `postgres_changes` channels never deliver DELETE (Supabase limitation). | `CloudSyncService.ts`, `services/sync/realtime-transport.ts`, integration test "Realtime" case | `[x]` 2026-09-27: `ws` injected as the Realtime transport (`ws` now a declared desktop dependency, externalised in the main bundle); deletions arrive as `sync_tombstones` INSERT events; the heartbeat re-subscribes a channel that is not `joined`. Verified live against the local stack (INSERT/UPDATE + tombstone events received). |
|
| GAP-SYNC-02 | Sync | **Desktop Realtime never connected.** Electron 33 main (Node 20.18) has no global `WebSocket`; supabase-js Realtime only ever logged `TIMED_OUT`/`CLOSED` (installed-app log 2026-09-16) and gave up after three retries. Also: filtered `postgres_changes` channels never deliver DELETE (Supabase limitation). | `CloudSyncService.ts`, `services/sync/realtime-transport.ts`, integration test "Realtime" case | `[x]` 2026-09-27: `ws` injected as the Realtime transport (`ws` now a declared desktop dependency, externalised in the main bundle); deletions arrive as `sync_tombstones` INSERT events; the heartbeat re-subscribes a channel that is not `joined`. Verified live against the local stack (INSERT/UPDATE + tombstone events received). |
|
||||||
| GAP-SYNC-03 | Portability | Mobile/cloud data export failed for any account with meetings or generated documents: `export_account_portability` used `to_jsonb(row)` while the v1 archive contract (server restore + mobile `exactKeys`) is a frozen key set, and later migrations added columns. | `server/supabase/migrations/20260927000034_cross_device_sync_parity.sql` §7 | `[x]` 2026-09-27: export projects each dataset onto its v1 keys; integration test asserts the memo/meeting key sets. |
|
| GAP-SYNC-03 | Portability | Mobile/cloud data export failed for any account with meetings or generated documents: `export_account_portability` used `to_jsonb(row)` while the v1 archive contract (server restore + mobile `exactKeys`) is a frozen key set, and later migrations added columns. | `server/supabase/migrations/20260927000034_cross_device_sync_parity.sql` §7 | `[x]` 2026-09-27: export projects each dataset onto its v1 keys; integration test asserts the memo/meeting key sets. |
|
||||||
| GAP-SYNC-04 | Mobile devices | Mobile `normalizeRegisteredDevice` rejected every platform except `android`/`ios`, so the first desktop/web device on an account would have made the whole Devices screen fail to load. | `apps/mobile-rn/src/features/devices/device-service.ts`, `screens/DevicesScreen.tsx` | `[x]` 2026-09-27: accepts the server platform list (`android/ios/web/windows/macos`), card shows the platform; `__tests__/devices.test.ts` 3/3, full mobile Jest 372/372. |
|
| GAP-SYNC-04 | Mobile devices | Mobile `normalizeRegisteredDevice` rejected every platform except `android`/`ios`, so the first desktop/web device on an account would have made the whole Devices screen fail to load. | `apps/mobile-rn/src/features/devices/device-service.ts`, `screens/DevicesScreen.tsx` | `[x]` 2026-09-27: accepts the server platform list (`android/ios/web/windows/macos`), card shows the platform; `__tests__/devices.test.ts` 3/3, full mobile Jest 372/372. |
|
||||||
| GAP-SYNC-05 | Sync | **Not yet in production.** Migration `20260927000034` is applied only to the local stack; the rewritten desktop sync ships with the next desktop release. Until the migration is deployed, desktop tombstone pulls and template RPCs fail against production (they are isolated per step, other entities keep syncing). | migration file, `apps/desktop/src/main/services/sync/` | `[ ]` Deploy the migration (`supabase db push` against the production project) **before** publishing a desktop build that contains this engine; then run a GUI pass: sign in on desktop, edit/delete on the phone, confirm desktop updates within seconds and the phone lists the desktop under Devices. |
|
| GAP-SYNC-05 | Sync | **Not yet in production.** Migration `20260927000034` is applied only to the local stack; the rewritten desktop sync ships with the next desktop release. Until the migration is deployed, desktop tombstone pulls and template RPCs fail against production (they are isolated per step, other entities keep syncing). | migration file, `apps/desktop/src/main/services/sync/` | `[~]` 2026-09-27: production `supabase db push` applied `0029`–`0035` (backup dump taken first; see GAP-OPS-01), all 23 Edge Functions redeployed, v1.7.0 published (runtime/portable first, then the updater feed) and installed on the owner PC. **Remaining:** the desktop on that PC is in local mode — one Google sign-in in Settings > Cloud, then edit/delete on the phone and confirm the desktop updates within seconds and appears under Devices. |
|
||||||
| GAP-SYNC-06 | Sync | Still desktop-local (not mirrored): knowledge base (desktop local RAG embeddings vs cloud pgvector — different embedding spaces), audio (desktop WAV files vs mobile Storage uploads), `user_settings` (locale, auto-polish, active command), desktop preset-command prompt edits, meeting `transcripts` segments (desktop uses `raw_transcript`, which mobile also fills). | `docs/map/10-feature-catalog.md` MEM-09/MEM-05/SHELL rows | `[ ]` Decide per item: knowledge docs could sync as source text and be re-embedded per surface; audio needs a size/quota policy before upload. |
|
| GAP-SYNC-06 | Sync | Still desktop-local (not mirrored): knowledge base (desktop local RAG embeddings vs cloud pgvector — different embedding spaces), audio (desktop WAV files vs mobile Storage uploads), `user_settings` (locale, auto-polish, active command), desktop preset-command prompt edits, meeting `transcripts` segments (desktop uses `raw_transcript`, which mobile also fills). | `docs/map/10-feature-catalog.md` MEM-09/MEM-05/SHELL rows | `[x]` 2026-09-27: knowledge docs sync as source text and are re-embedded per surface; recordings upload to the mobile storage contract (≤50 MiB per file, opt-out toggle) and remote recordings play through signed URLs; `user_settings` maps the shared fields. Deliberately not synced (see §7.x): meeting `transcripts` segments (mobile renders `raw_transcript`/`edited_transcript`, which the desktop fills) and desktop preset-command prompt edits (server presets are immutable by RLS). Evidence: `tests/main/sync/SyncExtensions.test.ts` (9) + integration case "지식 문서·설정·녹음" against the local Supabase stack. |
|
||||||
| GAP-SYNC-07 | Sync | `sync_tombstones` grows without a schedule. `prune_sync_tombstones_v1(interval)` (service_role) exists; nothing calls it. | migration §1 | `[ ]` Call it from an existing cron (e.g. the Forgejo scheduled workflow) with the 180-day default. A client whose cursor is older than the retention must fall back to a full resync. |
|
| GAP-SYNC-07 | Sync | `sync_tombstones` grows without a schedule. `prune_sync_tombstones_v1(interval)` (service_role) exists; nothing calls it. | migration §1 | `[x]` 2026-09-27: migration `0035` enables `pg_cron` and schedules `prune-sync-tombstones` daily at 03:17 UTC (production `cron.job` active=true). A client whose cursor is older than 180 days must fall back to a full resync. |
|
||||||
|
|
||||||
|
| GAP-OPS-01 | Ops | **Production Supabase was five migrations behind** (`0029` content reporting, `0030` profile column ACL — users could raise their own `role`/`tier` through PostgREST, `0031` audit-log actor unlink, `0032` meeting-document reporting, `0033` team activity feed) and `content-report` was never deployed, while the map listed those features GREEN from local runs. Most functions still ran the 2026-08-21 build. | `server/supabase/migrations/`, Management API `schema_migrations`, `functions` list | `[x]` 2026-09-27: schema+data dump taken, `db push` applied `0029`–`0035`, all functions redeployed (unauthenticated calls return 401, i.e. live and gated); `authenticated` can no longer UPDATE `profiles.tier`; real-account export returns exactly the v1 keys. Next: add a CI check that compares `supabase migration list --linked` with the repo so production cannot silently lag again. |
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Mobile checklist roll-up (from `MOBILE_APP_COMPLETION_SSOT.md` §4)
|
## 2. Mobile checklist roll-up (from `MOBILE_APP_COMPLETION_SSOT.md` §4)
|
||||||
|
|
@ -217,6 +218,10 @@ Actionable checklist for the work started this session. Fields to fill are blank
|
||||||
- **원격 삭제가 로컬 미전송 수정보다 우선한다.** 다른 기기에서 지운 행은 데스크톱에서 편집 중이었어도 지워진다.
|
- **원격 삭제가 로컬 미전송 수정보다 우선한다.** 다른 기기에서 지운 행은 데스크톱에서 편집 중이었어도 지워진다.
|
||||||
- **업그레이드 후 첫 동기화는 예전 삭제를 되살릴 수 있다.** 이 버전 이전에는 삭제 기록이 없었으므로, 데스크톱에만 남아 있던 행(모바일에서 지웠지만 데스크톱이 몰랐던 행)은 첫 대조에서 서버로 다시 올라간다. 데이터 유실보다 되살림을 택했다.
|
- **업그레이드 후 첫 동기화는 예전 삭제를 되살릴 수 있다.** 이 버전 이전에는 삭제 기록이 없었으므로, 데스크톱에만 남아 있던 행(모바일에서 지웠지만 데스크톱이 몰랐던 행)은 첫 대조에서 서버로 다시 올라간다. 데이터 유실보다 되살림을 택했다.
|
||||||
- **데스크톱 사용 횟수(`dictionary.usage_count`)는 자주 바뀌어 매번 올리지 않는다.** 단어를 추가·수정할 때 함께 올라간다.
|
- **데스크톱 사용 횟수(`dictionary.usage_count`)는 자주 바뀌어 매번 올리지 않는다.** 단어를 추가·수정할 때 함께 올라간다.
|
||||||
|
- **회의 `transcripts` 세그먼트는 동기화하지 않는다.** 모바일은 회의 본문을 `raw_transcript`/`edited_transcript`로 그리고, 데스크톱은 이 두 컬럼을 채운다.
|
||||||
|
- **데스크톱 프리셋 명령의 프롬프트 수정은 기기 로컬이다.** 서버 프리셋(`builtin_key`)은 RLS상 클라이언트가 고칠 수 없고 두 프리셋 목록도 다르다. 사용자가 만든 명령만 동기화한다.
|
||||||
|
- **설정은 뜻이 같은 것만 맞춘다.** 언어는 모바일이 지원하는 ko/en, 테마는 시스템/라이트/다크만(데스크톱 전용 테마는 로컬), 자동 다듬기는 켬/끔, 활성 명령은 양쪽에 있는 사용자 명령일 때만. 서버에 설정 행이 이미 있으면 첫 동기화에서 서버 값이 이긴다.
|
||||||
|
- **지식 문서는 원문만 옮긴다.** 기기마다 임베딩 모델·차원이 달라(데스크톱 768, 서버 1536) 벡터는 각자 만든다. 다른 기기에서 온 문서에는 원본 파일이 없다.
|
||||||
|
|
||||||
여기 있는 항목은 고쳐야 할 갭이 아니라 **대안을 검토한 뒤 의도적으로 유지하기로 한 절충**이다.
|
여기 있는 항목은 고쳐야 할 갭이 아니라 **대안을 검토한 뒤 의도적으로 유지하기로 한 절충**이다.
|
||||||
§1 에 갭으로 재등록하지 마라.
|
§1 에 갭으로 재등록하지 마라.
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@
|
||||||
- [ ] D-001 `user_settings`, `devices`, `audio_files`, `processing_jobs` 스키마·RLS·Realtime 확정
|
- [ ] D-001 `user_settings`, `devices`, `audio_files`, `processing_jobs` 스키마·RLS·Realtime 확정
|
||||||
- [ ] D-002 모바일 기기 등록, last seen, push token, 앱 버전 갱신
|
- [ ] D-002 모바일 기기 등록, last seen, push token, 앱 버전 갱신
|
||||||
- [~] D-003 연결 기기 목록·현재 기기 표시·원격 연결 해제 — 2026-09-27: 데스크톱도 `windows`/`macos` 기기로 등록되고 모바일 목록이 이를 파싱·표시(이전엔 비모바일 플랫폼 한 행이 화면 전체 로드를 실패시킴), 모바일 해제 시 데스크톱 로그아웃. 로컬 Supabase 통합 테스트 GREEN, 운영 배포·기기 실측 대기(`docs/map/11` GAP-SYNC-04/05)
|
- [~] D-003 연결 기기 목록·현재 기기 표시·원격 연결 해제 — 2026-09-27: 데스크톱도 `windows`/`macos` 기기로 등록되고 모바일 목록이 이를 파싱·표시(이전엔 비모바일 플랫폼 한 행이 화면 전체 로드를 실패시킴), 모바일 해제 시 데스크톱 로그아웃. 로컬 Supabase 통합 테스트 GREEN, 운영 배포·기기 실측 대기(`docs/map/11` GAP-SYNC-04/05)
|
||||||
- [~] D-004 데스크톱/Web에서 생성한 history/meeting/document/dictionary/knowledge/team 데이터 조회 — 2026-09-27: 데스크톱 history(제목·즐겨찾기)·사전·회의·메모·문서·메모 태그·명령·템플릿이 서버로 올라가 모바일에서 조회됨(로컬 스택 통합 테스트). knowledge는 미동기화(GAP-SYNC-06)
|
- [~] D-004 데스크톱/Web에서 생성한 history/meeting/document/dictionary/knowledge/team 데이터 조회 — 2026-09-27: 데스크톱 history(제목·즐겨찾기)·사전·회의·메모·문서·메모 태그·명령·템플릿이 서버로 올라가 모바일에서 조회됨(로컬 스택 통합 테스트). knowledge 원문·녹음(audio_files)·공통 설정도 2026-09-27 두 번째 묶음에서 동기화, 운영 반영 완료(0035)
|
||||||
- [~] D-005 모바일 변경을 데스크톱/Web에 Realtime 또는 재조회로 반영 — 2026-09-27: 데스크톱 Realtime 복구(`ws` 전송) + 삭제 기록(`sync_tombstones`) + 5분 주기 재조회, 반영 즉시 화면 새로고침. 로컬 스택에서 생성·수정·삭제 수신 확인, 운영 배포 대기
|
- [~] D-005 모바일 변경을 데스크톱/Web에 Realtime 또는 재조회로 반영 — 2026-09-27: 데스크톱 Realtime 복구(`ws` 전송) + 삭제 기록(`sync_tombstones`) + 5분 주기 재조회, 반영 즉시 화면 새로고침. 로컬 스택에서 생성·수정·삭제 수신 확인, 운영 배포 대기
|
||||||
- [ ] D-006 최근 데이터 암호화 캐시와 로그인 사용자별 격리
|
- [ ] D-006 최근 데이터 암호화 캐시와 로그인 사용자별 격리
|
||||||
- [ ] D-007 오프라인 생성·편집 queue, 재시도 backoff, 앱 재시작 복구
|
- [ ] D-007 오프라인 생성·편집 queue, 재시도 backoff, 앱 재시작 복구
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue