fix(canvas): 主题存储 - 修复非法 theme 值导致 /create 崩溃 (#155)

useThemeStore persist 无 merge 校验,坏 session/后端同步失败时可能将
非法 theme 值写入 localStorage,导致 canvasThemes[非法值] 为 undefined,
渲染时访问 theme.node 抛出 "Cannot read properties of undefined
(reading 'node')",/create 页面崩溃。

4 层防线(只加校验,不回退代码):
- useThemeStore persist merge 白名单校验,非法值回退 dark
- useThemeStore setTheme 写入前校验,防止同步路径绕过 merge
- normalizeConfigSnapshot 入口兜底 undefined/partial 快照
- ModelPicker 与 CanvasResourceMentionTextarea 消费端加 fallback

Closes #154

Co-authored-by: sikongyue <3155157671@qq.com>
This commit is contained in:
爱笑的毛毛虫
2026-08-06 08:32:27 +08:00
committed by GitHub
parent 6aa793bbd2
commit a7b6cf9967
4 changed files with 25 additions and 7 deletions

View File

@@ -279,8 +279,9 @@ export const useConfigStore = create<ConfigStore>()(
),
);
export function normalizeConfigSnapshot(snapshot: ConfigStoreSnapshot) {
const persistedConfig = (snapshot.config || {}) as Partial<AiConfig>;
export function normalizeConfigSnapshot(snapshot: ConfigStoreSnapshot | undefined = {}) {
// 坏存储/旧版本快照可能是 undefined 或缺 config,兜底为 defaultConfig,保证渲染不崩溃
const persistedConfig = (snapshot?.config || {}) as Partial<AiConfig>;
const config = { ...defaultConfig, ...persistedConfig };
const hasPersistedChannels = Array.isArray(persistedConfig.channels);
if (!hasPersistedChannels) config.channels = [];

View File

@@ -8,12 +8,26 @@ type ThemeStore = {
setTheme: (theme: ThemeName) => void;
};
const VALID_THEMES: ThemeName[] = ["light", "dark"];
export const useThemeStore = create<ThemeStore>()(
persist(
(set) => ({
theme: "dark",
setTheme: (theme) => set({ theme }),
// 写入前校验:非法 theme 直接忽略,防止 syncRemoteUserData 等路径绕过 merge 写入坏值
setTheme: (next) => set((state) => (VALID_THEMES.includes(next) ? { theme: next } : state)),
}),
{ name: "infinite-canvas:theme_store" },
{
name: "infinite-canvas:theme_store",
// 持久化恢复校验:旧版本/坏 session 写入的非法值回退到 dark,
// 避免 canvasThemes[非法值] = undefined 触发 "reading 'node'" 崩溃
merge: (persisted, current) => {
const stored = (persisted || {}) as Partial<ThemeStore>;
const theme = VALID_THEMES.includes(stored.theme as ThemeName)
? (stored.theme as ThemeName)
: "dark";
return { ...current, theme };
},
},
),
);