mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add distributed settings and workspace import/export (M5)
This commit is contained in:
@@ -227,10 +227,10 @@ export async function saveSnippet(s: Snippet): Promise<void> {
|
||||
|
||||
Small, frequently-read structured records live in `localStorage`, not IndexedDB: **UserSettings** (one record) and **app/UI preferences** (snippet sort, panel layout). Why split them from `UserSettings`? UI prefs change often (drag a panel divider, toggle a sort) and shouldn't force a rewrite of the whole settings blob on every interaction.
|
||||
|
||||
The pattern is **load-with-fallback, write-through on change.**
|
||||
The pattern is **load-with-fallback, per-slice write-through merge.**
|
||||
|
||||
- **Load-with-fallback:** merge the parsed stored object over a complete `DEFAULTS` constant. Missing keys (a setting added in a later build) and malformed JSON silently fall back to defaults — the app always gets a fully-populated object and never `undefined`-crashes on a new field.
|
||||
- **Write-through:** every update reads current, applies the change, and writes the whole record back immediately. No dirty-tracking, no flush step.
|
||||
- **Load-with-fallback:** merge the parsed stored object over a complete `DEFAULTS` constant. Missing keys (a setting added in a later build) and malformed JSON silently fall back to defaults — the app always gets a fully-populated object and never `undefined`-crashes on a new field. For `UserSettings`, the normalization is **pure and lives in `@core/settings` (`loadSettings(raw)` / `defaultSettings()`)** — it clamps ranges and validates enums; the infra adapter is just the thin localStorage reader (`loadUserSettings()` = `loadSettings(readRaw())`).
|
||||
- **Per-slice write-through merge:** every update reads current, merges in **just its slice**, and writes back. **The one `astrolabe:settings` record has multiple independent writers**, because settings are distributed and live-applied (spec §07 — no central save, no Apply step): the header theme toggle writes `ui.theme`, the preview Fit control writes `ui.previewFitMode`, and the per-pane settings clusters write `editor`/`performance`/`formatting`. **A writer that replaced the whole record would clobber the slices it doesn't own** — so each must field-merge. (There is deliberately no whole-record `saveSettings`.)
|
||||
- **Environment-guarded:** `localStorage` is absent or throws in some test/SSR contexts; guard access and degrade to defaults rather than throwing.
|
||||
|
||||
```ts
|
||||
@@ -307,16 +307,18 @@ export function loadSettings(): UserSettings {
|
||||
}
|
||||
}
|
||||
|
||||
export function saveSettings(s: UserSettings): void {
|
||||
if (!available()) return;
|
||||
try {
|
||||
localStorage.setItem(KEY, JSON.stringify({ ...s, version: CURRENT_SETTINGS_VERSION }));
|
||||
} catch (err) {
|
||||
console.warn('[settings] failed to save', err);
|
||||
}
|
||||
}
|
||||
// No whole-record save — each live control merges only its slice into the shared
|
||||
// record, so the others survive (see the per-slice rule above):
|
||||
// saveUiTheme(theme) -> { ...current, ui: { ...current.ui, theme } }
|
||||
// savePreviewFitMode(mode) -> { ...current, ui: { ...current.ui, previewFitMode } }
|
||||
// saveManagedSettings(managed) -> { ...current, editor, performance, formatting }
|
||||
```
|
||||
|
||||
> The above sketch keeps the `DEFAULTS`/merge shape inline for illustration, but
|
||||
> the **authoritative** defaults + normalization now live in `@core/settings`
|
||||
> (pure); the infra adapter calls them and owns only the localStorage IO + the
|
||||
> slice writers. Keep the two in sync via that one core source, not a second copy here.
|
||||
|
||||
App/UI prefs follow the identical guard+fallback pattern, but live in **one
|
||||
record under their own key**, `astrolabe:ux-prefs` (the snippet sort and the
|
||||
panel layout together — the plan's "ux-prefs for sort + panel layout", §09D):
|
||||
|
||||
Reference in New Issue
Block a user