Migrate window-bounds persistence off the electron-window-state library into a WindowManager built-in `rememberBounds` capability, backed by the main-process persist cache (`window.bounds` key — its first real consumer). - New `windowBoundsTracker` free-function module: validates the stored record (including displayBounds), restores onto the display the window was last on (clamping into its work area, never resetting to primary), and snapshots at teardown via getNormalBounds + isMaximized. - Singleton-only gate (dev warning for non-singleton types). Runtime toggle `wm.setRememberBounds` (orthogonal to the registry flag; OFF drops only that type's slot) plus `wm.peekWindowBounds`. - Persist at three teardown exits: native close (singletons), before window.destroy() in destroyWindow (programmatic destroys), and a new onStop so shutdown writes land before CacheService flushes its persist map. - Wire Main + QuickAssistant. Main re-applies maximize consumer-side on its own show schedule (tray-on-launch defers to first show); remove electron-window-state and its orphaned keepers/constants/comments. Fullscreen is not persisted and old *-state.json is not migrated (one-time reset, loseable). Adds tracker/integration/persist tests, extends the main CacheService mock with persist methods, and documents the capability plus a breaking-change note.
5.2 KiB
Cache Schema Guide
How to add fixed and template keys. Aligned with Preference Schema Guide and Boot Config Schema Guide.
Schemas
| Schema | Tier | File | Default map |
|---|---|---|---|
UseCacheSchema |
Memory | src/shared/data/cache/cacheSchemas.ts |
DefaultUseCache |
SharedCacheSchema |
Shared | src/shared/data/cache/cacheSchemas.ts |
DefaultSharedCache |
RendererPersistCacheSchema |
Persist (Renderer) | src/shared/data/cache/cacheSchemas.ts |
DefaultRendererPersistCache |
MainPersistCacheSchema |
Persist (Main) | src/shared/data/cache/cacheSchemas.ts |
DefaultMainPersistCache |
Complex value types go in src/shared/data/cache/cacheValueTypes.ts and are imported via CacheValueTypes.*.
Naming Convention
Enforced by ESLint rule data-schema-key/valid-key. Pattern: /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/.
| Valid | Invalid | Why |
|---|---|---|
app.user.avatar |
userAvatar |
No dot separator |
chat.multi_select_mode |
chat.multiSelectMode |
No camelCase |
scroll.position.${topicId} |
scroll.position:${id} |
Colon not allowed |
entity.cache.${type}_${id} |
App.user |
No uppercase |
Template placeholders ${xxx} are treated as literal segments for the naming check. At runtime, the placeholder name is ignored — ${providerId} and ${foo} produce identical regex; each placeholder expands to [\w\-]+ (no dots, no colons, no non-ASCII).
Choosing Fixed / Template / Casual
| Fixed key | Template key | Casual (Memory only) | |
|---|---|---|---|
| Type inference | Automatic | Automatic | Manual generic |
| Compile-time validation | Yes | Yes | No |
| Dynamic segments | No | Yes | Yes |
| Shared tier | Yes | Yes | Not supported |
| Persist tier | Yes | Not supported | Not supported |
| Default value | Per key | Shared across instances | None |
Prefer Fixed > Template > Casual. Cross-window dynamic keys must be Template — there is no getSharedCasual.
Adding a Fixed Key
1. Add the entry
// src/shared/data/cache/cacheSchemas.ts
export type UseCacheSchema = {
// ...existing entries
'feature.my_feature.data': MyDataType
}
export const DefaultUseCache: UseCacheSchema = {
// ...existing defaults
'feature.my_feature.data': { items: [], lastUpdated: 0 }
}
2. Define complex value type (if needed)
// src/shared/data/cache/cacheValueTypes.ts
export interface MyDataType {
items: string[]
lastUpdated: number
}
3. Use it
const [data, setData] = useCache('feature.my_feature.data')
// data is MyDataType
Adding a Template Key
// cacheSchemas.ts
export type UseCacheSchema = {
'scroll.position.${topicId}': number
}
export const DefaultUseCache: UseCacheSchema = {
'scroll.position.${topicId}': 0 // shared by every concrete instance
}
Use with any string in the dynamic segment:
const [pos, setPos] = useCache(`scroll.position.${topicId}`)
// pos is number; default 0 for every topicId that hasn't been written yet
The placeholder name is documentation-only. ${topicId} and ${id} compile to the same runtime matcher. Pick a name that matches the concept (not convention).
Shared and Persist Variants
- Shared: add to
SharedCacheSchema/DefaultSharedCache. Fixed and template both supported. - Persist: add to
RendererPersistCacheSchema/DefaultRendererPersistCache. Fixed keys only. Persist values survive restart via localStorage — keep them small and typed.
Validation
| Check | Command / location |
|---|---|
| ESLint naming rule | pnpm lint (rule: data-schema-key/valid-key) |
| Template matching unit tests | src/shared/data/cache/__tests__/templateKey.test.ts |
| Schema exhaustiveness | TypeScript compiler — default map must satisfy the schema type |
See Also
- Cache Overview — design invariants, tier semantics
- Cache Usage — hooks, direct API, patterns