apps/web/src/store/ 里的 Zustand store 里。每个 store 都用 Immer middleware,写 reducer 时像直接 mutate 一样。IndexedDB 是持久化层(通过 src/lib/api/IndexDBClient.ts 访问 MagicResumeDB)。
Store 列表
主角:useResumeStore
它持有当前简历,并维护一个 syncStatus 状态机:
- 更新内存中的状态(Immer)。
- 渲染后同步写入 IndexedDB。
- 如果是云端模式 且
useSettingStore.cloudSync打开,安排一次防抖的 push 到 Core API。
云端同步这条副作用只有同时满足
APP_MODE === 'cloud' 和用户在 Settings 里手动打开同步时才会触发。自托管用户永远不会因 store 操作发起网络请求。IndexedDB 层
src/lib/api/IndexDBClient.ts 是 idb-keyval 上的薄包装。Key 按 store 命名(如 resume:current、resume:history:<id>)。
新增持久化字段时,如果形状会变,记得加版本迁移。形状静默漂移会让旧浏览器 tab 在几周后突然报错。
为什么是 Zustand?
三个原因:- 没有 Provider 模板代码。 Store 直接 import,没有
<Provider>链。 - Selector 订阅。 组件订阅切片,不订阅整个 store,改一个字段不会让整棵树重渲染。
- 方便连接非 React 代码。 MCP 包、e2e helper、AI 流程都重用
@magic-resume/resume-schema里的Resume类型,不需要把 React 拉进来。
