diff --git a/.changeset/web-storage-appearance-notification.md b/.changeset/web-storage-appearance-notification.md new file mode 100644 index 0000000000..82313689e3 --- /dev/null +++ b/.changeset/web-storage-appearance-notification.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Consolidate web client localStorage access and decouple appearance/notification state into dedicated modules. diff --git a/apps/kimi-web/src/App.vue b/apps/kimi-web/src/App.vue index 367cfec89e..0b5ea163e2 100644 --- a/apps/kimi-web/src/App.vue +++ b/apps/kimi-web/src/App.vue @@ -31,6 +31,7 @@ import { useKimiWebClient } from './composables/useKimiWebClient'; import { useIsMobile } from './composables/useIsMobile'; import type { AppConfig, ThinkingLevel } from './api/types'; import type { FilePreviewRequest, ToolMedia } from './types'; +import { safeGetString, safeSetString, STORAGE_KEYS } from './lib/storage'; const client = useKimiWebClient(); provide('resolveImage', client.resolveImageUrl); @@ -204,8 +205,8 @@ function onGlobalKeydown(e: KeyboardEvent): void { // Layout: resizable session column. ResizeHandle owns the column width (with // localStorage persistence); we mirror it here to drive the App grid. // --------------------------------------------------------------------------- -const SIDEBAR_WIDTH_KEY = 'kimi-web.sidebar-width'; -const SIDEBAR_COLLAPSED_KEY = 'kimi-web.sidebar-collapsed'; +const SIDEBAR_WIDTH_KEY = STORAGE_KEYS.sidebarWidth; +const SIDEBAR_COLLAPSED_KEY = STORAGE_KEYS.sidebarCollapsed; const SIDEBAR_DEFAULT = 270; const SIDEBAR_MIN = 170; const SIDEBAR_MAX = 420; @@ -219,7 +220,7 @@ const sideWidth = computed(() => function loadSidebarCollapsed(): void { try { - sidebarCollapsed.value = localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === 'true'; + sidebarCollapsed.value = safeGetString(SIDEBAR_COLLAPSED_KEY) === 'true'; } catch { sidebarCollapsed.value = false; } @@ -227,7 +228,7 @@ function loadSidebarCollapsed(): void { function saveSidebarCollapsed(): void { try { - localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(sidebarCollapsed.value)); + safeSetString(SIDEBAR_COLLAPSED_KEY, String(sidebarCollapsed.value)); } catch { // ignore } diff --git a/apps/kimi-web/src/api/config.ts b/apps/kimi-web/src/api/config.ts index 1051613b15..620e9eca8e 100644 --- a/apps/kimi-web/src/api/config.ts +++ b/apps/kimi-web/src/api/config.ts @@ -1,7 +1,9 @@ // apps/kimi-web/src/api/config.ts // Reads Vite env, builds REST/WS URLs, manages stable clientId. -const CLIENT_ID_KEY = 'kimi-web.client-id'; +import { safeGetString, safeSetString, STORAGE_KEYS } from '../lib/storage'; + +const CLIENT_ID_KEY = STORAGE_KEYS.clientId; const WEB_CLIENT_NAME = 'kimi-code-web'; const WEB_CLIENT_UI_MODE = 'web'; @@ -86,10 +88,10 @@ export function buildWsUrl(origin: string, clientId: string): string { } function getClientId(): string { - const stored = globalThis.localStorage?.getItem(CLIENT_ID_KEY); + const stored = safeGetString(CLIENT_ID_KEY); if (stored) return stored; const generated = `web_${globalThis.crypto?.randomUUID?.() || Math.random().toString(36).slice(2)}`; - globalThis.localStorage?.setItem(CLIENT_ID_KEY, generated); + safeSetString(CLIENT_ID_KEY, generated); return generated; } diff --git a/apps/kimi-web/src/components/Composer.vue b/apps/kimi-web/src/components/Composer.vue index dabe088a65..075c206cf2 100644 --- a/apps/kimi-web/src/components/Composer.vue +++ b/apps/kimi-web/src/components/Composer.vue @@ -10,6 +10,7 @@ import type { FileItem } from './MentionMenu.vue'; import type { ActivationBadges, ConversationStatus, PermissionMode, QueuedPromptView } from '../types'; import type { AppModel, AppSkill, ThinkingLevel } from '../api/types'; import { modelThinkingAvailability } from '../lib/modelThinking'; +import { draftStorageKey, safeGetString, safeRemove, safeSetString } from '../lib/storage'; // --------------------------------------------------------------------------- // Attachment state @@ -104,25 +105,13 @@ const { t } = useI18n(); // Unsent-draft persistence: the composer text is kept in localStorage PER // SESSION, so switching away and back (or a page refresh) restores whatever the // user was typing for that session. Cleared when the draft is sent/steered. -const DRAFT_PREFIX = 'kimi-web.draft.'; -function draftKey(sid: string | undefined): string { - return DRAFT_PREFIX + (sid && sid.length > 0 ? sid : '__new__'); -} function loadDraft(sid: string | undefined): string { - try { - return localStorage.getItem(draftKey(sid)) ?? ''; - } catch { - return ''; - } + return safeGetString(draftStorageKey(sid)) ?? ''; } function saveDraft(sid: string | undefined, value: string): void { - try { - const key = draftKey(sid); - if (value) localStorage.setItem(key, value); - else localStorage.removeItem(key); - } catch { - // localStorage unavailable (private mode / quota) — drafts just don't persist. - } + const key = draftStorageKey(sid); + if (value) safeSetString(key, value); + else safeRemove(key); } const text = ref(loadDraft(props.sessionId)); diff --git a/apps/kimi-web/src/components/ConversationPane.vue b/apps/kimi-web/src/components/ConversationPane.vue index 31920499ee..e303baba35 100644 --- a/apps/kimi-web/src/components/ConversationPane.vue +++ b/apps/kimi-web/src/components/ConversationPane.vue @@ -12,6 +12,7 @@ import Composer from './Composer.vue'; import SwarmCard from './SwarmCard.vue'; import ChatDock from './ChatDock.vue'; import { getVisibleWorkspaces } from '../lib/workspacePicker'; +import { safeRemove, STORAGE_KEYS } from '../lib/storage'; const props = defineProps<{ turns: ChatTurn[]; @@ -172,11 +173,7 @@ const { t } = useI18n(); // The align toggle was removed with its UI (6e50cb7) — reading layout is // always centered now. Drop the old persisted preference so users who once // picked 'left' aren't frozen on it with no way back. -try { - localStorage.removeItem('kimi-web.content-align'); -} catch { - // localStorage unavailable -} +safeRemove(STORAGE_KEYS.contentAlign); const chatPaneRef = ref | null>(null); const emptyComposerRef = ref<{ loadForEdit: (v: string) => void } | null>(null); diff --git a/apps/kimi-web/src/components/OpenInMenu.vue b/apps/kimi-web/src/components/OpenInMenu.vue index 35cfd38b96..27e89f135f 100644 --- a/apps/kimi-web/src/components/OpenInMenu.vue +++ b/apps/kimi-web/src/components/OpenInMenu.vue @@ -5,6 +5,7 @@