Files
craft-libs/docs/settings-storage.md
T
Jeremy e85bd30bb2 Document the proposed Windows installation and persistent-model policy
* docs: add the Windows installation policy; models are persistent asse…

…ts, not caches

A companion policy to settings-storage.md: dual-scope per-user-default MSIs,
the ArtCraft suite folder, what an update must preserve (folder, context, no
duplicates), standalone installability, and the platform-native boundary on
macOS, Linux and FreeBSD. Splits models out of the disposable-caches row:
downloaded models are persistent user assets that cache cleanup and app
uninstallers must never delete.

* docs: describe suite Windows installation policy

* docs: record the wizard scope and folder rules and the real-install c…

…hecks

The scope page leaves ALLUSERS unset for Only for me, and the wizard sets the new-install folder itself after the choice, because Windows Installer resolves Program Files once for the context it started in. Notes the 32-bit Program Files (x86) folder and updates the adoption table to say real per-user and all-users msiexec runs were done. Clean-machine and second-account checks are still pending.

* docs: new installs default to per-machine; only the old default folde…

…r moves

* docs: say plainly that released packages still default to per-user

The policy says new installs are per-machine, but every released package is
dual-scope with a per-user authored default, and the inventory still listed
"Per-user default". The policy now separates the two: what the policy is, what
the packages do today, who picks per-machine (the launcher, or "Everyone" in the
wizard), and the follow-up that closes the gap.

Also: Update all's one-prompt route was reverted after the antivirus incident,
so the policy no longer says it asks once; the Linux model folder names its
ArtCraft/models subfolder; repair is listed where the 26/10 payload runs
covered it; the inventory no longer says nothing was released.

* docs: the launcher and a direct MSI run match only for the same scope…

… and folder

CodeRabbit pointed out that 'produce the same result' contradicted the
documented new-install difference: the launcher sets ALLUSERS=1, while a
direct run keeps the package's per-user default.

* docs: make the legacy-folder migration exception explicit

State that exact former-default paths can migrate even when manually
chosen, subject to maintainer acceptance of the proposed policy. Preserve
other chosen folders and document the effect on custom shortcuts.

---------

Co-authored-by: Jeremy <jeremy8776@googlemail.com>
2026-11-12 10:43:51 -05:00

6.8 KiB

Settings storage across the Craft apps

How every Craft app stores its settings, and what goes where. The shared code is craft-settings; this page is the policy it implements. Where the apps install on Windows, and what an update may move, is the companion policy installation-policy.md. Discussion: storytold/craft-libs#12.

What goes where

Kind Where Why
Settings people might edit config.toml in the config folder One readable file with comments, atomic saves, backup, recovery, migrations
Window and UI state ui.ron / app.ron beside config.toml Changes every session; not worth comments or hand edits
Caches Platform cache folder (paths::cache_dir); %LOCALAPPDATA% on Windows so it does not roam Disposable, large
Downloaded models The user's chosen persistent model root (suggested %LOCALAPPDATA%\ArtCraft\models on Windows; $XDG_DATA_HOME/ArtCraft/models on Linux, or ~/.local/share/ArtCraft/models when it is unset) Persistent user assets: never deleted by cache cleanup or an app's uninstaller, never disposable (installation-policy.md)
Recovery and autosave files Platform data folder (paths::data_dir) Not settings; must survive a settings reset
Settings that belong to a library or project (LightCraft) Inside that folder, via SettingsStore::open_at Travels with the library
API keys and tokens The OS keychain Never in a plain-text file

The config folder

First match wins. The store reports which rule applied, so apps can show the path in their about or settings screen.

  1. An override passed by the app, such as a --config-dir flag
  2. <APP>_CONFIG_DIR, for example PHOTOCRAFT_CONFIG_DIR (one per app, so redirecting one app does not move the others). Set but empty means isolated: nothing is read or written anywhere, the same as DesignCraft's existing behaviour, so tests and agents can never touch a user's real settings
  3. Portable mode: a marker file next to the executable (next to the .app on macOS, since writing inside a signed bundle breaks its signature). The default is a file named portable with settings in config/ beside it; apps that already ship portable builds keep their own names (PhotoCraft: portable.txt or PhotoCraft.portable, folder PhotoCraftData)
  4. The platform default:

Apps can also open a store with Location::Memory (for a switch like DESIGNCRAFT_NO_PREFS): settings work in memory and nothing touches the disk.

Platform Folder
Linux / BSD $XDG_CONFIG_HOME/<app>/, or ~/.config/<app>/ if unset
macOS ~/Library/Application Support/<app>/
Windows %APPDATA%\<app>\ (Roaming)

<app> is the app's lowercase id (photocraft, designcraft, …) on every platform, macOS included (decided in #12). Existing Linux folder names do not change; the Launcher moves from ai.storyteller.artcraft-launcher to artcraft-launcher.

If the folder cannot be created or written (a portable marker in /Applications, an AppImage mount), the store opens read-only and says so at startup.

Files in the folder

  • config.toml: starts with schema_version = N, then one [section] per settings area. Written on first launch so people can find it. 0600 on Unix.
  • config.toml.bak: the previous version, refreshed before every save.
  • config.broken-<unix-ts>.toml: a file that was not valid TOML, moved aside and never deleted.

Saving and recovery

  • Saves happen during use, half a second after the last change, atomically through craft-atomic. A failed save leaves the old file intact and is retried with backoff; the app shows the error.
  • A file with a TOML syntax error is moved aside and defaults are used. A file that parses but has a value that no longer fits (a renamed theme, a typo in a hand edit) is not treated as broken: only that [section] falls back to defaults and the app is told which one.
  • A file written by a newer build is shown but never overwritten.
  • Comments, key order and keys the app does not know survive saves. If two instances of an app are open, each re-reads the file before writing so those survive; known keys are last-writer-wins.

Migration

  • Schema changes: Settings::migrate upgrades the document one version at a time before it is parsed. A failed step leaves the file untouched and the store read-only.
  • Older per-app files (prefs.json, ui.json, *.ron): imported on the first run that finds no config.toml, using the app's existing parser. The old file is left in place, so rolling back to an older build keeps working; apps can remove it a few releases later.

Where the apps are today

From the audit in #12 (Linux defaults, from source; overrides and portable mode change them). Before an app moves over, check where it saves on macOS and Windows so the importer finds the old files there too.

App Today Moving to craft-settings
PhotoCraft ~/.config/photocraft/preferences.json + eframe ui.ron; atomic saves with retries; portable via portable.txt/PhotoCraft.portable → PhotoCraftData/ Import preferences.json; keep ui.ron; keep its portable names via PORTABLE_MARKERS/PORTABLE_DIR
DesignCraft ~/.config/designcraft/prefs.json + ui.json; saves on exit, ignores failures; empty DESIGNCRAFT_CONFIG_DIR and DESIGNCRAFT_NO_PREFS touch nothing Import prefs.json; keep ui.json; gain save-during-use and error reporting; DESIGNCRAFT_NO_PREFS → Location::Memory
EffectCraft ~/.config/effectcraft/prefs.json, shortcuts.json, script_settings.json; atomic Import all three into sections of one config.toml
FilmCraft ~/.local/share/filmcraft/preferences.json; atomic Import from the data folder; settings move to the config folder
GridCraft ~/.config/gridcraft/prefs.json + ui.json; saves on exit, ignores failures Same as DesignCraft
LightCraft global ui.json; library prefs.json, presets.json, view.json; atomic, backups, overwrite protection Global settings via open; library settings via open_at(library_dir)
VectorCraft ~/.config/vectorcraft/ui.json with engine preferences inside; atomic, ignores failures Split engine preferences out into config.toml; keep UI state in ui.json
ArtCraft Launcher ~/.local/share/ai.storyteller.artcraft-launcher/app.ron (eframe) Import from app.ron; needs its own tick timer while in the tray

Adoption order: PhotoCraft and DesignCraft first (closest to and furthest from the target behaviour), then the rest.

Not yet

  • Reloading settings when the file changes on disk while the app runs
  • Removing imported legacy files
  • Settings on the web (wasm32), where only the trait, document helpers and error types exist