# Config `config/*` is a global, persistent, reactive key/value store — Deft's equivalent of dconf or the Windows Registry. It sits at the same layer as `pub/*` (on the Runtime, not the TUI Workspace), so it is available in **both** the standalone `deft` binary and the TUI hosts. Keys are hierarchical strings using `/` as the separator, prefixed with the owning app for namespacing: `log/max-lines`, `editor/font`, `config/auto-save`. Cross-cutting keys with no owning app live at the top level: `ui/theme`, `window/width`. ## `config/*` Stdlib functions | Function | Returns | Purpose | |---|---|---| | `[config/get "key"]` | value or nil | Read | | `[config/set "key" value]` | stored value | Write + publish change | | `[config/unset "key"]` | bool | Remove; publishes with `:value nil` | | `[config/scan "prefix"]` | map | All keys starting with prefix | | `[config/keys "prefix"]` | list | Matching key names | | `[config/count]` | number | Total key count | | `[config/load ?path]` | bool | Load `.deft/config.dft` | | `[config/save ?path]` | bool | Write entire registry | | `[config/save-overrides ?path]` | count written | Diff against defaults; write only changed | | `[config/save-map $map ?path]` | bool | Write a given map | ## Prefixing — Manual, Not Automatic Apps declare their prefix once and reuse it. No runtime magic — the "which app am I in?" question is genuinely ambiguous across CLI scripts, TUI panels hosting multiple apps, and child runtimes, so the registry never tries to answer it.
 def cfg "log/"

# Explicit prefix at each call site — no hidden resolution
config/set ($cfg + "max-lines") 1000
echo [config/get ($cfg + "max-lines")]

# Or a thin wrapper when call-site noise matters
def set-cfg {|k v| config/set ($cfg + $k) $v]}
def get-cfg {|k| config/get ($cfg + $k)]}

set-cfg "max-lines" 1000 
Cross-app reads stay explicit (`[config/get "editor/font"]` from inside `log`), and cross-cutting keys (`ui/theme`) live at the top level without contortion. ## Reactivity `config/set` and `config/unset` publish a change event on the `"config"` pub/sub topic. Subscribers receive it in the standard event map under `:data`:
 %{ :type :message :topic "config"
   :data %{ :key "log/max-lines" :value 1000 :old 500 } } 
Subscribe and filter by key prefix in the handler:
 def on-config {|ev|
    @doc "React to log config changes."
    if [starts-with? $ev~data~key "log/"] {
        echo "log config changed:" $ev~data~key "->" $ev~data~value
        [reload-logger]
    |
    }
}
pub/subscribe "config" "on-config" 
See [Pubsub](pubsub) for the broker contract. ## Values — Stringifiable Data Only Registry values must round-trip through `deft/stringify` / `deft/parse`: - Scalars (numbers, strings, booleans, nil) - Keywords - Lists, maps, sets - Tagged maps Function values are stored as **names** (strings) and resolved at the call site via `[apply $name ...]`. `apply` accepts either a string/keyword name (resolved through the namespace chain) or a direct callable value (closure, native-func, keyword, map, tagged-map, set, vector). ## Persistence `config/save` writes the entire registry to a flat map file (default `.deft/config.dft`) as `%{ "key1" v1 "key2" v2 }`. `config/load` reads such a file and replaces registry contents. The file is plain Deft data — diff-friendly, hand-editable, and version-control- friendly.
 # Per-package defaults ship as small data files (keyword keys are idiomatic):
%{
    :log/max-lines 1000
    :log/wrap true
    :log/severity :all
} 
`config/save-overrides` diffs against loaded defaults and writes only changed keys — this is the canonical save path for the config app. Load accepts both string and keyword keys; save emits string keys. ## Loading Defaults at Startup A typical package's `on-init` loads defaults if the registry is empty:
 def on-init {||
    @doc "Load log defaults on first run."
    if (empty? [config/scan "log/"]) {
        config/load "packages/log/defaults.dft"
    |
    }
    pub/subscribe "config" "on-config"
} 
## The Config App The TUI ships with a config app (see `packages/config/src/config.dft`) that: - Reads `[config/scan $prefix]` to list keys. - Lets the user toggle booleans, edit strings/numbers. - Writes back via `config/set`. - Persists changes via `config/save-overrides` — only the keys the user explicitly changed, not the full registry. ## What Lives Where - `tui/get` / `tui/set` — **ephemeral per-session UI state** (cursor position, selection, scroll). TUI-only, never persisted. Different concern; don't conflate. - `config/*` — **persistent user preferences**. All hosts. Disk-backed via `config/load` / `config/save`. ## Examples ### A simple preference
 config/set "editor/font-size" 14
echo [config/get "editor/font-size"]]           # => 14

config/unset "editor/font-size"
echo [config/get "editor/font-size"]]           # => nil 
### Scanning for a namespace
 def log-config [config/scan "log/"]
# => %{ :log/max-lines 1000 :log/wrap true :log/severity :all }

for k [keys $log-config] {
    echo "$k = $log-config~$k"
} 
### Reactive theme switching
 def apply-theme {|ev|
    if ($ev~data~key == "ui/theme") {
        echo "switching theme to $ev~data~value"
        [load-theme $ev~data~value]
    |
    }
}
pub/subscribe "config" "apply-theme"

# Anywhere:
config/set "ui/theme" "catppuccin-mocha" 
## Quick Reference | Form | Purpose | |---|---| | `[config/get "key"]` | Read | | `[config/set "key" value]` | Write + publish | | `[config/unset "key"]` | Remove + publish | | `[config/scan "prefix"]` | All matching keys | | `[config/keys "prefix"]` | Matching key names | | `[config/count]` | Total keys | | `[config/load ?path]` | Load from disk | | `[config/save ?path]` | Write all to disk | | `[config/save-overrides ?path]` | Write only diff from defaults | | `[config/save-map $map ?path]` | Write a given map |