From d4821eb48616565ae59bb86b06da9286b218ec2b Mon Sep 17 00:00:00 2001 From: tianma-if Date: Sat, 29 Aug 2026 20:00:41 +0800 Subject: [PATCH] refactor: remove sync interval preference --- apps/web/src/components/SettingsPane.tsx | 6 --- apps/web/src/components/WorkspaceApp.tsx | 7 +-- .../components/settings/PreferenceCard.tsx | 45 +--------------- .../hooks/useWorkspacePreferences.test.mjs | 13 ----- apps/web/src/hooks/useWorkspacePreferences.ts | 20 -------- .../src/hooks/useWorkspaceSyncLifecycle.ts | 21 +++----- apps/web/src/lib/app-helpers.test.mjs | 51 ------------------- apps/web/src/lib/app-helpers.ts | 37 -------------- apps/web/src/lib/workspace-refresh.test.mjs | 5 ++ apps/web/src/lib/workspace-refresh.ts | 1 + packages/shared/src/i18n/en-US.ts | 3 -- packages/shared/src/i18n/zh-CN.ts | 3 -- 12 files changed, 16 insertions(+), 196 deletions(-) delete mode 100644 apps/web/src/hooks/useWorkspacePreferences.test.mjs diff --git a/apps/web/src/components/SettingsPane.tsx b/apps/web/src/components/SettingsPane.tsx index 086b6ead..5f0c0fbf 100644 --- a/apps/web/src/components/SettingsPane.tsx +++ b/apps/web/src/components/SettingsPane.tsx @@ -45,8 +45,6 @@ interface SettingsPaneProps { onOpenAiPrompts: () => void; imageCompressionEnabled: boolean; onImageCompressionChange: (enabled: boolean) => void; - syncIntervalMs: number | null; - onSyncIntervalChange: (intervalMs: number | null) => void; shortcutSettings: ShortcutSettings; onShortcutSettingsChange: (settings: ShortcutSettings) => void; editorContentAlignment: EditorContentAlignment; @@ -86,8 +84,6 @@ export const SettingsPane = ({ onOpenAiPrompts, imageCompressionEnabled, onImageCompressionChange, - syncIntervalMs, - onSyncIntervalChange, shortcutSettings, onShortcutSettingsChange, editorContentAlignment, @@ -208,8 +204,6 @@ export const SettingsPane = ({ (() => isInitialSettingsRoute @@ -1311,12 +1309,11 @@ export const WorkspaceApp = ({ }, [mobilePullToRefreshActive, refreshLatestMemos]); useWorkspaceSyncLifecycle({ - pendingSyncCount: syncSummary.total, + failedSyncCount: syncSummary.error, backgroundRefreshKey: localDataScope, refreshWorkspace: refreshWorkspaceFromServer, runQueuedSync, setOnline: setIsOnline, - syncIntervalMs, }); const selectedNotebookDescendantIds = useMemo( @@ -3100,8 +3097,6 @@ export const WorkspaceApp = ({ onOpenAiPrompts={handleOpenAiPrompts} imageCompressionEnabled={imageCompressionEnabled} onImageCompressionChange={setImageCompressionEnabled} - syncIntervalMs={syncIntervalMs} - onSyncIntervalChange={setSyncIntervalMs} shortcutSettings={shortcutSettings} onShortcutSettingsChange={setShortcutSettings} editorContentAlignment={editorContentAlignment} diff --git a/apps/web/src/components/settings/PreferenceCard.tsx b/apps/web/src/components/settings/PreferenceCard.tsx index c768154a..5b729498 100644 --- a/apps/web/src/components/settings/PreferenceCard.tsx +++ b/apps/web/src/components/settings/PreferenceCard.tsx @@ -1,7 +1,7 @@ -import { AlignHorizontalJustifyCenter, ChartNoAxesCombined, FileCode2, Image, Keyboard, Languages, MousePointerClick, Palette, RefreshCw, Sparkles } from "lucide-react"; +import { AlignHorizontalJustifyCenter, ChartNoAxesCombined, FileCode2, Image, Keyboard, Languages, MousePointerClick, Palette, Sparkles } from "lucide-react"; import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { writeSyncIntervalPreference, type EditorContentAlignment, type ShortcutSettings, type SyncIntervalPreference } from "@/lib/app-helpers"; +import type { EditorContentAlignment, ShortcutSettings } from "@/lib/app-helpers"; import { EDITOR_LINK_OPEN_MODE_CHANGED_EVENT, getStoredEditorLinkOpenMode, @@ -45,8 +45,6 @@ import { interface PreferenceCardProps { imageCompressionEnabled: boolean; onImageCompressionChange: (enabled: boolean) => void; - syncIntervalMs: number | null; - onSyncIntervalChange: (intervalMs: number | null) => void; shortcutSettings: ShortcutSettings; onShortcutSettingsChange: (settings: ShortcutSettings) => void; editorContentAlignment: EditorContentAlignment; @@ -56,8 +54,6 @@ interface PreferenceCardProps { export const PreferenceCard = ({ imageCompressionEnabled, onImageCompressionChange, - syncIntervalMs, - onSyncIntervalChange, shortcutSettings, onShortcutSettingsChange, editorContentAlignment, @@ -185,9 +181,6 @@ export const PreferenceCard = ({ void changeAppLocalePreference(preference); }; - const syncIntervalPreference: SyncIntervalPreference = - syncIntervalMs === 300_000 ? "5m" : syncIntervalMs === 900_000 ? "15m" : syncIntervalMs === 1_800_000 ? "30m" : syncIntervalMs === 3_600_000 ? "1h" : syncIntervalMs === 7_200_000 ? "2h" : "30s"; - return ( @@ -357,40 +350,6 @@ export const PreferenceCard = ({ -
-
- -
-
{t("settings.syncIntervalTitle")}
-
{t("settings.syncIntervalDescription")}
-
-
-
- -
-
-
diff --git a/apps/web/src/hooks/useWorkspacePreferences.test.mjs b/apps/web/src/hooks/useWorkspacePreferences.test.mjs deleted file mode 100644 index b7650251..00000000 --- a/apps/web/src/hooks/useWorkspacePreferences.test.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { syncIntervalMsToPreference } from "./useWorkspacePreferences.ts"; - -describe("workspace sync interval preference", () => { - test("serializes every supported interval", () => { - expect([null, 30_000, 300_000, 900_000, 1_800_000, 3_600_000, 7_200_000].map(syncIntervalMsToPreference)) - .toEqual(["off", "30s", "5m", "15m", "30m", "1h", "2h"]); - }); - - test("falls back to the default interval for an unknown value", () => { - expect(syncIntervalMsToPreference(1234)).toBe("30s"); - }); -}); diff --git a/apps/web/src/hooks/useWorkspacePreferences.ts b/apps/web/src/hooks/useWorkspacePreferences.ts index 134d99ae..1793f1a6 100644 --- a/apps/web/src/hooks/useWorkspacePreferences.ts +++ b/apps/web/src/hooks/useWorkspacePreferences.ts @@ -7,41 +7,23 @@ import { readImageCompressionPreference, readMemoListWidthPreference, readShortcutSettingsPreference, - readSyncIntervalPreference, writeDesktopFocusModePreference, writeEditorContentAlignmentPreference, writeImageCompressionPreference, writeMemoListWidthPreference, writeShortcutSettingsPreference, - writeSyncIntervalPreference, type ShortcutSettings, type EditorContentAlignment, } from "@/lib/app-helpers"; -export type SyncIntervalPreference = "off" | "30s" | "5m" | "15m" | "30m" | "1h" | "2h"; - -export const syncIntervalMsToPreference = (intervalMs: number | null): SyncIntervalPreference => { - switch (intervalMs) { - case null: return "off"; - case 300_000: return "5m"; - case 900_000: return "15m"; - case 1_800_000: return "30m"; - case 3_600_000: return "1h"; - case 7_200_000: return "2h"; - default: return "30s"; - } -}; - export const useWorkspacePreferences = () => { const [imageCompressionEnabled, setImageCompressionEnabled] = useState(readImageCompressionPreference); - const [syncIntervalMs, setSyncIntervalMs] = useState(readSyncIntervalPreference); const [desktopFocusMode, setDesktopFocusModeState] = useState(readDesktopFocusModePreference); const [editorContentAlignment, setEditorContentAlignmentState] = useState(readEditorContentAlignmentPreference); const [shortcutSettings, setShortcutSettings] = useState(readShortcutSettingsPreference); const [memoListWidth, setMemoListWidthState] = useState(readMemoListWidthPreference); useEffect(() => writeImageCompressionPreference(imageCompressionEnabled), [imageCompressionEnabled]); - useEffect(() => writeSyncIntervalPreference(syncIntervalMsToPreference(syncIntervalMs)), [syncIntervalMs]); useEffect(() => writeShortcutSettingsPreference(shortcutSettings), [shortcutSettings]); const setDesktopFocusMode = useCallback((enabled: boolean) => { @@ -75,8 +57,6 @@ export const useWorkspacePreferences = () => { setImageCompressionEnabled, setMemoListWidth, setShortcutSettings, - setSyncIntervalMs, shortcutSettings, - syncIntervalMs, }; }; diff --git a/apps/web/src/hooks/useWorkspaceSyncLifecycle.ts b/apps/web/src/hooks/useWorkspaceSyncLifecycle.ts index 0dec6bb8..2bad6642 100644 --- a/apps/web/src/hooks/useWorkspaceSyncLifecycle.ts +++ b/apps/web/src/hooks/useWorkspaceSyncLifecycle.ts @@ -6,6 +6,7 @@ import { BACKGROUND_WORKSPACE_REFRESH_INTERVAL_MS, claimBackgroundRefreshLease, createRefreshSingleFlight, + DEFERRED_MEMO_SYNC_DELAY_MS, releaseBackgroundRefreshLease, type WorkspaceRefreshMode, } from "@/lib/workspace-refresh"; @@ -13,22 +14,19 @@ import { type RefreshWorkspace = (mode: WorkspaceRefreshMode) => Promise; export const useWorkspaceSyncLifecycle = ({ - pendingSyncCount, + failedSyncCount, backgroundRefreshKey, refreshWorkspace, runQueuedSync, setOnline, - syncIntervalMs, }: { - pendingSyncCount: number; + failedSyncCount: number; backgroundRefreshKey: string; refreshWorkspace: RefreshWorkspace; runQueuedSync: () => Promise; setOnline: (online: boolean) => void; - syncIntervalMs: number | null; }) => { const deferredSyncTimerRef = useRef(null); - const deferredSyncPendingRef = useRef(false); const runQueuedSyncRef = useRef(runQueuedSync); const refreshWorkspaceRef = useRef(refreshWorkspace); const backgroundRefreshOwnerRef = useRef(createClientUuid()); @@ -108,7 +106,6 @@ export const useWorkspaceSyncLifecycle = ({ useEffect(() => { const handleQueueChanged = () => { - deferredSyncPendingRef.current = false; if (deferredSyncTimerRef.current !== null) { window.clearTimeout(deferredSyncTimerRef.current); deferredSyncTimerRef.current = null; @@ -121,21 +118,17 @@ export const useWorkspaceSyncLifecycle = ({ useEffect(() => { const scheduleDeferredSync = () => { - deferredSyncPendingRef.current = true; if (deferredSyncTimerRef.current !== null) { window.clearTimeout(deferredSyncTimerRef.current); deferredSyncTimerRef.current = null; } - if (syncIntervalMs === null) return; deferredSyncTimerRef.current = window.setTimeout(() => { deferredSyncTimerRef.current = null; - deferredSyncPendingRef.current = false; void runQueuedSyncRef.current(); - }, syncIntervalMs); + }, DEFERRED_MEMO_SYNC_DELAY_MS); }; window.addEventListener(SYNC_QUEUE_DEFERRED_EVENT, scheduleDeferredSync); - if (deferredSyncPendingRef.current) scheduleDeferredSync(); return () => { window.removeEventListener(SYNC_QUEUE_DEFERRED_EVENT, scheduleDeferredSync); if (deferredSyncTimerRef.current !== null) { @@ -143,7 +136,7 @@ export const useWorkspaceSyncLifecycle = ({ deferredSyncTimerRef.current = null; } }; - }, [syncIntervalMs]); + }, []); useEffect(() => { const leaseKey = `edgeever.background-refresh:${backgroundRefreshKey}`; @@ -201,8 +194,8 @@ export const useWorkspaceSyncLifecycle = ({ }, [backgroundRefreshKey]); useEffect(() => { - if (pendingSyncCount === 0) return; + if (failedSyncCount === 0) return; const timer = window.setInterval(() => void runQueuedSync(), 15_000); return () => window.clearInterval(timer); - }, [pendingSyncCount, runQueuedSync]); + }, [failedSyncCount, runQueuedSync]); }; diff --git a/apps/web/src/lib/app-helpers.test.mjs b/apps/web/src/lib/app-helpers.test.mjs index 6b7931fb..8b300104 100644 --- a/apps/web/src/lib/app-helpers.test.mjs +++ b/apps/web/src/lib/app-helpers.test.mjs @@ -1,25 +1,21 @@ import { afterEach, describe, expect, test } from "bun:test"; import { DEFAULT_SHORTCUT_SETTINGS, - DEFAULT_SYNC_INTERVAL_MS, DESKTOP_FOCUS_MODE_STORAGE_KEY, DESKTOP_READING_PROTECTION_STORAGE_KEY, EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY, NOTEBOOK_SORT_STORAGE_KEY, SHORTCUT_SETTINGS_STORAGE_KEY, - SYNC_INTERVAL_STORAGE_KEY, getSearchShortcutScope, getShortcutActionForEvent, getNotebookSortComparator, readEditorContentAlignmentPreference, readNotebookSortPreference, - readSyncIntervalPreference, readDesktopFocusModePreference, readDesktopReadingProtectionPreference, readShortcutSettingsPreference, writeEditorContentAlignmentPreference, writeNotebookSortPreference, - writeSyncIntervalPreference, writeDesktopFocusModePreference, writeDesktopReadingProtectionPreference, } from "./app-helpers.ts"; @@ -194,53 +190,6 @@ describe("custom notebook sorting", () => { }); }); -describe("automatic sync interval preference", () => { - test("defaults to 30 seconds", () => { - installLocalStorage(); - expect(readSyncIntervalPreference()).toBe(DEFAULT_SYNC_INTERVAL_MS); - expect(DEFAULT_SYNC_INTERVAL_MS).toBe(30_000); - }); - - test("reads and writes sync intervals", () => { - const values = installLocalStorage(); - - writeSyncIntervalPreference("30s"); - expect(values.get(SYNC_INTERVAL_STORAGE_KEY)).toBe("30s"); - expect(readSyncIntervalPreference()).toBe(30_000); - - writeSyncIntervalPreference("5m"); - expect(values.get(SYNC_INTERVAL_STORAGE_KEY)).toBe("5m"); - expect(readSyncIntervalPreference()).toBe(300_000); - }); - - test("preserves the legacy preference stored under the old key", () => { - const values = installLocalStorage(); - values.set("edgeever.autoSaveInterval", "15m"); - expect(readSyncIntervalPreference()).toBe(900_000); - }); - - test("migrates the former one-minute default to 30 seconds", () => { - const values = installLocalStorage(); - values.set(SYNC_INTERVAL_STORAGE_KEY, "1m"); - expect(readSyncIntervalPreference()).toBe(30_000); - }); - - test("falls back to the default for unknown or unavailable storage", () => { - const values = installLocalStorage(); - values.set(SYNC_INTERVAL_STORAGE_KEY, "unexpected"); - expect(readSyncIntervalPreference()).toBe(30_000); - - globalThis.window = { - localStorage: { - getItem: () => { - throw new Error("blocked"); - }, - }, - }; - expect(readSyncIntervalPreference()).toBe(30_000); - }); -}); - describe("workspace shortcut preferences", () => { test("provides navigation, AI, save, reading protection, and editor mode defaults", () => { expect(DEFAULT_SHORTCUT_SETTINGS.focusGlobalSearch).toEqual({ diff --git a/apps/web/src/lib/app-helpers.ts b/apps/web/src/lib/app-helpers.ts index 7557c052..1300aee2 100644 --- a/apps/web/src/lib/app-helpers.ts +++ b/apps/web/src/lib/app-helpers.ts @@ -129,8 +129,6 @@ export { buildNotebookTree, type NotebookNode }; export { DEFAULT_MEMO_TITLE }; export const IMAGE_COMPRESSION_STORAGE_KEY = "edgeever.imageCompressionEnabled"; -export const SYNC_INTERVAL_STORAGE_KEY = "edgeever.syncInterval"; -const LEGACY_AUTO_SAVE_INTERVAL_STORAGE_KEY = "edgeever.autoSaveInterval"; export const DESKTOP_FOCUS_MODE_STORAGE_KEY = "edgeever.desktopFocusMode"; export const DESKTOP_READING_PROTECTION_STORAGE_KEY = "edgeever.desktopReadingProtection"; export const EDITOR_CONTENT_ALIGNMENT_STORAGE_KEY = "edgeever.editorContentAlignment"; @@ -143,18 +141,6 @@ export const MIN_MEMO_LIST_WIDTH_PX = 300; export const MAX_MEMO_LIST_WIDTH_PX = 540; export const EDITOR_LOCAL_SAVE_DELAY_MS = 1_200; -export type SyncIntervalPreference = "30s" | "5m" | "15m" | "30m" | "1h" | "2h" | "off"; -export const DEFAULT_SYNC_INTERVAL_MS = 30_000; -const SYNC_INTERVAL_VALUES: Record = { - "30s": 30_000, - "5m": 300_000, - "15m": 900_000, - "30m": 1_800_000, - "1h": 3_600_000, - "2h": 7_200_000, - off: null, -}; - export const MEMO_DRAG_MIME = "application/x-edgeever-memos"; export const NOTEBOOK_DRAG_MIME = "application/x-edgeever-notebook"; @@ -337,29 +323,6 @@ export const writeImageCompressionPreference = (enabled: boolean) => { } }; -export const readSyncIntervalPreference = (): number | null => { - try { - const stored = ( - window.localStorage.getItem(SYNC_INTERVAL_STORAGE_KEY) - ?? window.localStorage.getItem(LEGACY_AUTO_SAVE_INTERVAL_STORAGE_KEY) - ); - const preference = stored === "1m" ? "30s" : stored; - return preference && preference in SYNC_INTERVAL_VALUES - ? SYNC_INTERVAL_VALUES[preference as SyncIntervalPreference] - : DEFAULT_SYNC_INTERVAL_MS; - } catch { - return DEFAULT_SYNC_INTERVAL_MS; - } -}; - -export const writeSyncIntervalPreference = (preference: SyncIntervalPreference) => { - try { - window.localStorage.setItem(SYNC_INTERVAL_STORAGE_KEY, preference); - } catch { - // Local storage can be unavailable in private or restricted browser contexts. - } -}; - export const readDesktopFocusModePreference = () => { try { return window.localStorage.getItem(DESKTOP_FOCUS_MODE_STORAGE_KEY) === "true"; diff --git a/apps/web/src/lib/workspace-refresh.test.mjs b/apps/web/src/lib/workspace-refresh.test.mjs index 45f3b746..21e6d29c 100644 --- a/apps/web/src/lib/workspace-refresh.test.mjs +++ b/apps/web/src/lib/workspace-refresh.test.mjs @@ -4,6 +4,7 @@ import { BACKGROUND_WORKSPACE_REFRESH_INTERVAL_MS, claimBackgroundRefreshLease, createRefreshSingleFlight, + DEFERRED_MEMO_SYNC_DELAY_MS, releaseBackgroundRefreshLease, refreshWorkspaceData, resolveCreatedMemoSelection, @@ -12,6 +13,10 @@ import { } from "./workspace-refresh.ts"; describe("refreshWorkspaceData", () => { + it("uses a fixed 30-second delay for uploading memo edits", () => { + assert.equal(DEFERRED_MEMO_SYNC_DELAY_MS, 30_000); + }); + it("uses a shared five-minute background refresh interval", () => { assert.equal(BACKGROUND_WORKSPACE_REFRESH_INTERVAL_MS, 5 * 60_000); }); diff --git a/apps/web/src/lib/workspace-refresh.ts b/apps/web/src/lib/workspace-refresh.ts index dc31c642..bc690b3e 100644 --- a/apps/web/src/lib/workspace-refresh.ts +++ b/apps/web/src/lib/workspace-refresh.ts @@ -1,6 +1,7 @@ export type WorkspaceRefreshMode = "background" | "manual"; export type WorkspaceMemoView = "notebook" | "trash"; +export const DEFERRED_MEMO_SYNC_DELAY_MS = 30_000; export const BACKGROUND_WORKSPACE_REFRESH_INTERVAL_MS = 5 * 60_000; export const BACKGROUND_WORKSPACE_REFRESH_LEASE_MS = BACKGROUND_WORKSPACE_REFRESH_INTERVAL_MS * 2; export const BACKGROUND_WORKSPACE_REFRESH_COALESCE_MS = 1_000; diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts index c0d6c72a..7324d887 100644 --- a/packages/shared/src/i18n/en-US.ts +++ b/packages/shared/src/i18n/en-US.ts @@ -244,9 +244,6 @@ export const enUS = { linkOpenModifierTitle: "Open web links with Ctrl/⌘ + click while editing", linkOpenModifierDescription: "On by default: hold Ctrl/⌘ and click to open web links, preventing accidental opens while editing. Note references always open with one click. Desktop only.", linkOpenModifierAria: "Require Ctrl or ⌘ click to open web links while editing", - syncIntervalTitle: "Automatic sync interval", - syncIntervalDescription: "Sync edits to the server automatically while keeping local drafts available immediately.", - syncIntervals: { "30s": "Every 30 seconds", "5m": "Every 5 minutes", "15m": "Every 15 minutes", "30m": "Every 30 minutes", "1h": "Every hour", "2h": "Every 2 hours" }, mermaidThemeTitle: "Diagram theme", mermaidThemeDescription: "Follow the app appearance or choose a fixed visual theme for diagrams.", markdownThemeTitle: "Markdown source theme", diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts index 500303d6..c2c70112 100644 --- a/packages/shared/src/i18n/zh-CN.ts +++ b/packages/shared/src/i18n/zh-CN.ts @@ -244,9 +244,6 @@ export const zhCN = { linkOpenModifierTitle: "编辑时用 Ctrl/⌘ + 点击打开网页链接", linkOpenModifierDescription: "默认开启:网页链接需按住 Ctrl/⌘ 再点击,避免编辑时误触;笔记引用始终单击打开(仅桌面)。", linkOpenModifierAria: "编辑时是否需要 Ctrl 或 ⌘ 再点击才能打开网页链接", - syncIntervalTitle: "自动同步间隔", - syncIntervalDescription: "定期将编辑内容同步到服务器,本地草稿仍会即时保留。", - syncIntervals: { "30s": "每 30 秒", "5m": "每 5 分钟", "15m": "每 15 分钟", "30m": "每 30 分钟", "1h": "每 1 小时", "2h": "每 2 小时" }, mermaidThemeTitle: "图表主题", mermaidThemeDescription: "跟随应用外观,或为笔记图表选择固定主题。", markdownThemeTitle: "Markdown 源码主题",