# Prelude The **prelude** is Deft's project-level configuration file. It lives at `.deft/prelude.dft` and is auto-loaded by every runtime in the project — the scripting binary, the TUI hosts (`retro`/`retra`), and any child runtimes you spawn. It's a plain Deft data file whose top-level form is a single `%Prelude{...}` tagged map.
 %Prelude{
    :mounts   %{ "stdext"  ".deft/packages/deft/stdext/0.2.0/src"
                 "launcher" ".deft/packages/deft/stdext/0.2.0/src" }
    :pkgs     @{ ".deft/packages" }
    :registry "https://packages.deft-lang.org"
    :themes   %{ :default "Dracula" }
    :font     %{ :family "JetBrains Mono" :size 16 }
    :window   %{ :width 2200 :height 1200 }
    :workspace %{ :default "ide" }
} 
`deft init` writes a template prelude for you. Edit it to customise mounts, package roots, registry, theme, and TUI defaults. ## When the Prelude Loads The host runtime loads the prelude very early — before any user code runs and before the TUI workspace is built: 1. Runtime starts; `settle()` registers built-in stdlib functions. 2. `loadPrelude()` reads `.deft/prelude.dft` from the current working directory. If absent or unparseable, the prelude is `nil` and every field falls back to its built-in default. 3. Mounts from `:mounts` and package roots from `:pkgs` are registered on the VM. 4. `:registry` configures the package plugin's URL. 5. The config registry autoloads per-package defaults from each `:pkgs` root, then layers `.deft/config.dft` on top. 6. **TUI only:** `:window` is read to size the window and `:font` is resolved to a font file (before the Landlock sandbox applies, so the font directories can be scanned); `:themes` and `:workspace` are stashed for the workspace builder. 7. User code runs (the launched file, REPL input, or workspace spec eval). Silent no-op semantics: a missing or malformed prelude is not an error. The host proceeds with defaults. This means scripts work even when run outside an initialised project. ## Fields ### `:mounts` — mount aliases A map from mount name → target. Each entry registers a **mount**: `import "/..."` (and any `:source` path that begins with `/`) is rewritten to the real path before resolution. Mount names may be multi-segment — package references like `"deft/stdext"` work, and imports match the longest mount prefix first. **Value type decides** what a mount means: - **string value** — an import mount (the classic form): the name maps to a local directory for `import` and `:source` resolution. - **map value** (or `%Local{}` / `%Ssh{}` tagged map) — a **VFS mount**: `fs/*` paths whose leading segment(s) name the mount route to its driver (a local anchor directory or a remote host over ssh). Never an import mount.
 :mounts %{
    "deft/stdext" ".deft/packages/deft/stdext/0.2.0/src"
    "apps"        "packages"
    "themes"      "${HOME}/.config/deft/themes"

    # VFS mounts — fs/* paths under the name route to the driver
    :docs %Local{ :root "/usr/share/doc" }
    :prod %{ :kind :ssh :host "10.0.0.5" :root "/srv/app" :user "root" }
} 
 [fs/read "docs/misc.txt"]          # → /usr/share/doc/misc.txt
[fs/read "prod/etc/hostname"]      # → /srv/app/etc/hostname on 10.0.0.5 
After the above, both `import "deft/stdext/html"` from Deft code and a workspace config's `:source "apps/editor/src/editor.dft"` resolve through the configured directories. VFS mounts instead serve the `fs/` namespace — see [VFS](vfs) for path semantics, the ssh backend, and the runtime `[vfs/mount]` API. Keys may be strings or keywords (`"deft/stdext"` and `:stdext` both work); values must be strings (import mounts) or maps (VFS mounts). Path literals are taken as-is — there's no `$HOME` expansion at the language level, but the host's standard `[fs/expand-path]` runs on mount registration. VFS mounts seeded from the prelude land in a process-global table, so app threads that skip the prelude load see them too; note that `:kind :ssh` mounts need subprocess and network access, which the Landlock sandbox denies (a warning is printed when the sandbox is active). Explicit `:mounts` entries always win over the package auto-scan (next section) — use them to pin a package to a specific checkout. `deft init` scaffolds a `launcher` mount (used to resolve the TUI launcher — see [`:launcher`](#launcher) below); the stdext package mounts need no prelude entries. ### `:pkgs` — package search roots A vector of directories to scan for packages. The **first** entry is the install target used by `[pkg/install]`. Entries may be relative (resolved against the current working directory).
 :pkgs @{ ".deft/packages" } 
A single string is also accepted:
 :pkgs ".deft/packages" 
At startup every root is scanned and each package found (dev checkout or installed version) is auto-mounted under `owner/name`, plus `owner/name@version` pins and the `@latest` alias — no `:mounts` entries needed. Roots are scanned in listed order; the first root providing a package wins. This lets you layer a project-local packages dir over a shared system one. Explicit `:mounts` entries override the scan. ### `:registry` — package registry URL When set to an `http://` or `https://` URL, the `pkg/*` stdlib functions (`pkg/install`, `pkg/fetch`, `pkg/info`, `pkg/available`) talk to that registry instead of the built-in local virt-pkg tree. Anything other than an HTTP(S) URL is ignored.
 :registry "https://packages.deft-lang.org" 
Use this to point a project at a private registry (typically nginx-terminated, serving static package tarballs). ### `:themes` — TUI theme default A map with a `:default` key naming the theme to apply when the loaded workspace config doesn't specify its own `:theme`.
 :themes %{ :default "Dracula" } 
The TUI host searches the conventional theme locations (user overrides, stdext defaults, legacy `./themes/`) for a file matching the name. Workspace-level `:theme` overrides this; see [Workspace configs](#workspace) below and the TUI docs. ### `:window` — TUI initial window size Pixel dimensions for the TUI window. Honoured only by the `retro` build (the OpenGL host); `retra` is constrained by the terminal emulator.
 :window %{ :width 2200 :height 1200 } 
Both keys are optional; missing dimensions fall back to the binary's compiled-in default. ### `:font` — TUI font (GL hosts) The monospaced font behind the GL hosts' workspace cell grid, panel windows, and surface text (`draw/text-px`). Honoured by `retro`, `retrs`, and `hydra`; `retra` renders through the terminal emulator's font and ignores this key.
 :font %{ :family "JetBrains Mono" :size 16 }     # resolve by family name
:font %{ :path "/home/u/.fonts/FiraCode.ttf" :size 16 }  # exact file
:font "Source Code Pro"                          # family shorthand 
- **`:family`** scans the conventional font directories (`$XDG_DATA_HOME/fonts` — else `~/.local/share/fonts` — then `~/.fonts`, `/usr/local/share/fonts`, `/usr/share/fonts`; recursive, `.ttf`/`.otf`/`.ttc`) and picks the best match by normalised file name, preferring `Regular` cuts. **`:path`** uses an exact file (relative paths resolve against the working directory) and wins over `:family` when both are present. - Candidates must be **effectively monospaced**: declared fixed-width, or with uniform glyph advances (some genuinely mono fonts — Google's Noto Mono TTFs, for one — don't declare the flag). The probe also rasterizes probe glyphs and requires each bitmap to fit its cell and map to real distinct Latin glyphs, so proportional fonts (and Latin-less script fonts, which would render as boxes) are rejected with a warning; the cell grid advances by a fixed cell width, so they would mis-render. - **`:size`** is the pixel size, clamped 6–128 (default 18). - Failures warn on stderr (`font: ...`) and fall back to DejaVu Sans Mono. A successful resolution logs `font: using (prelude :font ...)` so the choice is confirmable from the terminal. - The CLI flags `--font` / `--size` win per-field — the prelude is the project default, flags are per-invocation overrides. **Any character, no rebuild.** The rasterized repertoire is NOT baked in: besides the startup set (ASCII + box-drawing + the UI symbol chrome), every other codepoint rasterizes **on demand** the first time it appears — Cyrillic, Greek, CJK, emoji, any symbol — and stays cached in growable atlas pages. Two optional keys tune that:
 :font %{
    :family "JetBrains Mono" :size 16
    :fallback @{ "/usr/share/fonts/.../NotoSansMonoCJK-VF.ttc" }  # faces tried for chars the primary lacks
    :ranges   @{ "0x0400-0x04FF" "0x4E00-0x9FFF" } # pre-rasterize hot scripts
} 
- **`:fallback`** (string or vector of family names / file paths, up to 4) registers faces consulted in order when the primary font lacks a codepoint — the typical shape is a Latin mono primary plus a CJK/symbol fallback. The monospace probe does not apply to fallback paths; only the primary drives the cell grid. Note the family matcher scores FILE STEMS, not font metadata — CJK collections with style-suffixed filenames (`NotoSansMonoCJK-VF.ttc`) resolve more reliably by exact `:path`. - **`:ranges`** (string or vector of `first-last` hex with optional `0x` prefix, or bare codepoints) pre-rasterizes ranges into the glyph cache at startup so first use doesn't pay a small rasterization hitch mid-frame. Purely an optimization — everything renders on demand regardless. - Both keys always come from the prelude (CLI `--font` picks the primary face only), and both feed the sandbox's read-only grants. - East Asian Wide scripts (CJK, kana, Hangul, fullwidth) occupy two cells per glyph — measure display width with `[width $s]` instead of `len` when laying out mixed-script text. **Interactive zoom.** In `retro` and `hydra`, `Ctrl+=` / `Ctrl+-` (also keypad `+`/`-`, and `Ctrl+Shift+=` for the `+` glyph) step the font size, and `Ctrl+0` resets it to the startup size — browser-style, global over dialogs and apps. `retro` zooms the grid and every popped-out panel window together; `hydra` zooms the whole instance (new windows inherit the zoomed size). The zoom is session-only — persist a size with `:size` here. **Applying from code.** Fonts also change live via stdlib verbs (same resolution and monospace rules as this key):
 [tui/set-font "JetBrains Mono" 16]              # family (or file path) + size
[tui/set-font %{:family "JetBrains Mono" :size 16}]
[tui/set-font %{:size 20}]                      # size-only (keeps the font)
[tui/font]                                      # => %{:path "..." :size 16}
[tui/font-list]                                 # => @("Adwaita Mono" "Deja Vu Sans Mono" ...) 
Resolution and the fixed-width check run immediately (the call returns `false` when the family doesn't resolve); the rebuild lands on the next host frame — grid, popped windows, and surface text all follow. Pair `tui/font-list` with `dialog/search` for a font picker in your own apps. Like the zoom, applies are session-only — persist a choice with `:font` here. GL hosts only (`retro`/`hydra`; `retra` renders through the terminal emulator). `tui/set-font` changes the primary face and size; `:fallback`/`:ranges` are prelude-configured and survive applies. ### `:workspace` — default workspace (TUI) When the TUI host starts without an explicit `--workspace` CLI flag, it looks here for the workspace config to auto-load.
 :workspace %{ :default "ide" } 
This loads `.deft/workspaces/ide.dft` (see [TUI: App Platform](../06-TUI/app-platform) for the workspace spec format). ### `:sandbox` — landlock sandbox path lists When the Linux sandbox is enabled (`DEFT_SANDBOX=1`), the `:sandbox` map's read-only and writable path lists bound the process's filesystem access via Landlock:
 :sandbox %{
    :readonly @{ "/etc" "/usr/bin" "/usr/share" "/proc" }
    :writable @{ "~/.config" "~/.local" }
} 
`~` is expanded to `$HOME` when the sandbox is applied. Edit the lists by hand or with `deft sandbox add/remove/list`. `deft init` writes a template block. See [Sandbox](sandbox) for the full story — what's always granted, kernel requirements, and limits. ### `:launcher` — launcher app (TUI) The launcher is the app that opens in newly created panels — splits (`Ctrl+B v` / `Ctrl+B h`), group adds, and **Workspace → New** on the bottom taskbar's Workspace menu. The TUI hosts launch the source path `launcher/launcher.dft`, resolved through the `launcher` mount in `:mounts`:
 :mounts %{
    "stdext"  ".deft/packages/deft/stdext/0.2.0/src"
    "launcher" ".deft/packages/deft/stdext/0.2.0/src"
} 
`deft init` scaffolds both mounts pointing at the installed stdext, so the built-in picker is found without further configuration. Resolution order when the launcher is launched: 1. A top-level `:launcher "path/to/app.dft"` field, when present — a full source path, resolved through `:mounts` mounts like any other. 2. `launcher/launcher.dft` through the `launcher` mount, when the file exists. 3. Fallback: `stdext/launcher.dft` through the `stdext` mount (the built-in picker). **Replacing the launcher.** The launcher is itself just a Deft app, so you can swap it for your own — a domain-specific dashboard, a kiosk mode, a company-branded picker. Two ways:
 # 1. Repoint the launcher mount at your app's directory
#    (convention: the file is <dir>/launcher.dft)
:mounts %{ "launcher" "myapp" }

# 2. Or set the explicit field for a full source path
:launcher "myapp/picker.dft" 
The explicit field wins; the mount is the idiomatic form since the same alias serves `import "launcher/..."` from Deft code. See [TUI: App Platform](../06-TUI/app-platform) for building a custom launcher. ## Runtime Access The loaded prelude is available at runtime via the `prelude` stdlib function:
 echo [prelude]
# => %Prelude{ :mounts {...} :pkgs [...] :registry "..." ... }

# Read a single field
echo [prelude]~registry                # => "https://packages.deft-lang.org"
echo [prelude]~themes~default          # => "Dracula"

# Returns nil when no prelude is loaded (running outside a project)
echo [prelude]                         # => nil 
This is the canonical way for a script or app to discover project configuration without re-reading the file. ## What the Prelude Is Not - **Not user settings.** Per-user preferences (theme overrides at the *user* level, key bindings, editor options) belong in `[config/*]` — see [Config](config). - **Not secrets.** The prelude is plain Deft data, typically checked into version control. Don't put API keys or passwords here. - **Not environment-specific.** The prelude ships with the project; if you need machine-specific overrides (CI vs. laptop, prod vs. dev), express them via `os/env` lookups in your app code, or use the `config/*` registry layered on top of `config/load`. ## See Also - [Runtimes](runtimes) — every runtime loads the prelude at startup - [VFS](vfs) — map-valued `:mounts` entries declare VFS mounts - [Config](config) — runtime key/value preferences, layered on top - [Sandbox](sandbox) — the `:sandbox` key configures the Landlock path lists - [TUI: App Platform](../06-TUI/app-platform) — TUI workspace configs - [Tooling: Packages](../07-Tooling/packages) — `pkg/*` stdlib functions and the registry