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
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { Box, IconButton, Tooltip, Chip, Collapse } from '@mui/material'
|
||||
import { Copy, Check, Trash2, Tag, X, ScrollText, ChevronDown, Sparkles, Volume2 } from 'lucide-react'
|
||||
import { Copy, Check, Trash2, Tag, X, ScrollText, ChevronDown, Sparkles, Volume2, Star } from 'lucide-react'
|
||||
import { MetalCard, Led, TactileBadge } from '@d3ro/ui/components/ds'
|
||||
import { d3roPalette, d3roFontSans, d3roFontMono, d3roTypo, d3roRadius, d3roShadow } from '@d3ro/ui/theme'
|
||||
import { useI18n } from '@d3ro/i18n'
|
||||
|
|
@ -15,6 +15,8 @@ export interface HistoryEntryCardProps {
|
|||
entry: HistoryEntry
|
||||
onCopy?: (text: string) => void
|
||||
onDelete?: (id: string) => void
|
||||
/** 즐겨찾기 토글 — 모바일·웹과 공유되는 값 */
|
||||
onToggleFavorite?: (entry: HistoryEntry) => void
|
||||
showTags?: boolean
|
||||
onTagClick?: (tag: string) => void
|
||||
}
|
||||
|
|
@ -23,6 +25,7 @@ export function HistoryEntryCard({
|
|||
entry,
|
||||
onCopy,
|
||||
onDelete,
|
||||
onToggleFavorite,
|
||||
showTags = false,
|
||||
onTagClick,
|
||||
}: HistoryEntryCardProps): React.ReactElement {
|
||||
|
|
@ -160,6 +163,27 @@ export function HistoryEntryCard({
|
|||
|
||||
{/* Quick Action Buttons */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }}>
|
||||
{onToggleFavorite && (
|
||||
<Tooltip title={entry.isFavorite ? t('history.unfavorite') : t('history.favorite')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-pressed={entry.isFavorite}
|
||||
aria-label={entry.isFavorite ? t('history.unfavorite') : t('history.favorite')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onToggleFavorite(entry)
|
||||
}}
|
||||
sx={{
|
||||
p: 0.6,
|
||||
color: entry.isFavorite ? d3roPalette.tag.orange : d3roPalette.text.inactive,
|
||||
bgcolor: entry.isFavorite ? d3roPalette.tag.orangeBg : 'transparent',
|
||||
'&:hover': { color: d3roPalette.tag.orange, bgcolor: d3roPalette.tag.orangeBg },
|
||||
}}
|
||||
>
|
||||
<Star size={14} fill={entry.isFavorite ? 'currentColor' : 'none'} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={copied ? t('common.copied') : t('common.copy')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue