teksilo-settings 0.9.0

Persistent reactive settings for Teksilo — key-value store, versioned setting files, MRU lists and window state.
Documentation

teksilo-settings — persistent, reactive user preferences for Teksilo.

Two persistence shapes share one storage backbone:

  • [SettingsStore] — dynamic, dotted-key K/V for scalar values (numbers, strings, bools, arrays of those), surfaced as Signal<T>. The QSettings analogue. Struct values aren't supported here because TOML serializes them as tables, indistinguishable from nested key paths; use [SettingsFile<T>] for those instead.
  • [SettingsFile<T>] — typed single-struct persistence with migrations.
  • [PersistedListModel<T>] — a reactive, [Keyed]-item ordered collection persisted to a single file, merging by op (upsert / remove / clear) rather than by whole-document snapshot — so a peer process's concurrent addition survives.
  • [MruList<T>] — generic dedupe + pin + cap list over an app-defined item type implementing [MruEntry]. Apps register their own via TeksiloAppBuilder::app_state(handle).
  • [WindowStateService] — per-window geometry persistence. When registered via TeksiloAppBuilder::settings(...), every WindowConfig carrying an id(...) is automatically restored on creation (sanitized against the current monitor) and recorded on every change by teksilo-app's window manager. No widget-side wiring required.

All disk writes are atomic (write-temp + rename). Every write merges against the document read fresh off disk under an exclusive lock — never a blind whole-document overwrite — so two processes sharing a file (Skribisto's one-process-per-project model shares general.toml, recents.toml, window_state.toml across every open project) cannot silently clobber each other. [Reloadable] is the matching read-side contract: a hook a file-system watcher calls to push a peer's write into this process's live signals/models — and [SettingsWatcher] + [SettingsRegistry] are the actual watcher: a notify-based directory watch (parent-directory, not file, so an atomic rename-over doesn't invalidate it) plus the path → [Reloadable] lookup a changed-file event dispatches through. TeksiloAppBuilder::settings(...) wires one up automatically (opt out via .settings_watch(false)); every service [SettingsBundle::open] opens is pre-registered into [OpenedSettings::registry], and application code can register its own ad hoc SettingsFile / PersistedListModel / MruList handles into that same registry.

use teksilo_settings::{AppPaths, SettingsStore, SettingsKey};

const FONT_SIZE: SettingsKey<f32> =
    SettingsKey::new("editor.font_size", || 14.0);

// Tests / docs use `for_testing(...)` against a tempdir so they
// never touch the user's real config tree. Production apps use
// `AppPaths::new(qualifier, organization, application)`.
let tmp = tempfile::tempdir().unwrap();
let paths = AppPaths::for_testing(tmp.path());
let store = SettingsStore::open(paths.config_file("general")).unwrap();

let font_size = store.signal_for(&FONT_SIZE);
font_size.set(18.0); // persisted on the next debounce tick