feat(desktop): two-way cloud sync with mobile and web
Rewrites the desktop mirror as services/sync/SyncEngine: a persistent outbox, per-account server-clock keyset cursors with paging, pulls that never overwrite unsent local edits, deletions both ways through sync_tombstones and per-row failure isolation. It now covers history titles and favorites, dictionary, every meeting's memos and documents, memo tags, user commands and dictation/meeting templates, and registers the desktop as a device that the phone can disconnect. Fixes shipped defects: the first pull after sign-in fetched nothing, only the first meeting's children were pushed, team meetings leaked into the personal database and lost team_id on re-push, and Realtime never connected because Electron's Node 20 has no global WebSocket (ws is now the transport). Anonymous local-mode records are imported into the first account that signs in. The settings sync section is translated and shows pending/rejected changes; synced screens reload on app:dataChanged.
This commit is contained in:
parent
b5c9ff9f31
commit
0a4f5aee64
49 changed files with 3940 additions and 1033 deletions
|
|
@ -66,6 +66,7 @@ import type {
|
|||
HistoryEntry,
|
||||
HistoryDeleteParams,
|
||||
HistorySearchParams,
|
||||
HistorySetFavoriteParams,
|
||||
DictionaryQueryParams,
|
||||
DictionaryPage,
|
||||
DictionaryEntry,
|
||||
|
|
@ -408,6 +409,8 @@ const electronAPI = {
|
|||
delete: (params: HistoryDeleteParams) =>
|
||||
invoke<void>(IPC_CHANNELS.HISTORY.DELETE, params),
|
||||
deleteAll: () => invoke<void>(IPC_CHANNELS.HISTORY.DELETE_ALL),
|
||||
setFavorite: (params: HistorySetFavoriteParams) =>
|
||||
invoke<HistoryEntry>(IPC_CHANNELS.HISTORY.SET_FAVORITE, params),
|
||||
search: (params: HistorySearchParams) =>
|
||||
invoke<HistoryPage>(IPC_CHANNELS.HISTORY.SEARCH, params),
|
||||
onAdded: (cb: (e: HistoryEntry) => void): Unsubscribe =>
|
||||
|
|
@ -469,8 +472,13 @@ const electronAPI = {
|
|||
|
||||
// ── App 이벤트 (실시간 UI 갱신) ────────────────────────
|
||||
app: {
|
||||
onDataChanged: (cb: (data: { type: string; activeId?: string }) => void): Unsubscribe =>
|
||||
on('app:dataChanged', cb),
|
||||
/**
|
||||
* 로컬 데이터가 바뀌었다 — 화면을 다시 불러온다.
|
||||
* type 'cloud-sync'는 다른 기기(모바일·웹)의 변경이 반영된 것이고 entities에 바뀐 종류가 온다.
|
||||
*/
|
||||
onDataChanged: (
|
||||
cb: (data: { type: string; activeId?: string; entities?: string[] }) => void
|
||||
): Unsubscribe => on(IPC_CHANNELS.APP.DATA_CHANGED, cb),
|
||||
},
|
||||
|
||||
// ── Phase 10: Memo Tags ─────────────────────────────────
|
||||
|
|
@ -802,6 +810,8 @@ const electronAPI = {
|
|||
userEmail: string | null
|
||||
lastSyncAt: number | null
|
||||
syncing: boolean
|
||||
pendingChanges: number
|
||||
failedChanges: number
|
||||
}>(IPC_CHANNELS.CLOUD_SYNC.GET_STATE),
|
||||
signIn: (params: { provider: 'google' | 'github' }) =>
|
||||
invoke<{ started: boolean }>(IPC_CHANNELS.CLOUD_SYNC.SIGN_IN, params),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue