summaryrefslogtreecommitdiff
path: root/docs/config.md
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 19:25:00 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit57b30ba3e38153a4446626449b0fed5120da954c (patch)
tree7b9381327a05791181a855bd8d4ba3cfb4df5301 /docs/config.md
parent0fd908eea63d04886b269438aa7529d3dd422256 (diff)
downloadpardes-57b30ba3e38153a4446626449b0fed5120da954c.tar.gz
pardes-57b30ba3e38153a4446626449b0fed5120da954c.zip
The docs and the 9P skill say each fact once, in the file that owns it, and say only what a live session does
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'docs/config.md')
-rw-r--r--docs/config.md790
1 files changed, 141 insertions, 649 deletions
diff --git a/docs/config.md b/docs/config.md
index 349814a6..f25c8746 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1,79 +1,20 @@
-# Startup configuration
+# Configuration
-Native pardes builds use a per-user `pardes` configuration directory. Its
-main command file is named `init`:
+## The startup file
-- Unix: `$XDG_CONFIG_HOME/pardes/init`, falling back to
- `~/.config/pardes/init`.
-- macOS: `$XDG_CONFIG_HOME/pardes/init` when that variable is set, otherwise
- `~/Library/Application Support/pardes/init`.
-- Windows: `%LOCALAPPDATA%\pardes\init`, with
- `%USERPROFILE%\AppData\Local\pardes\init` as the fallback.
-
-On the two unixes `XDG_CONFIG_HOME` counts only when it is ABSOLUTE, as the
-XDG base-directory specification requires; an empty or relative value falls
-back to the home-directory form (`config.User.path`, and the test beside
-it). Windows never consults it. An `init` that does not fit the `max_bytes`
-read limit — 1 MiB, `config.User` in `src/config.zig` — or that cannot be read at all is
-treated as no file: `load` takes the `readFileAlloc` error and keeps going. The
-path still resolves, because "nothing is there yet" is the answer `Config`
-exists to give. There is one case with no path at all: a native launch with no
-`HOME` set (or, on Windows, neither `%LOCALAPPDATA%` nor `%USERPROFILE%`),
-which `Config` reports as `no per-user config path`.
-
-`Config` (`SPC f c`, or the word executed anywhere) opens one refreshable
-`+Config` pane. It reports the startup path and every live config-like value:
-theme, colors, focus tint, syntax weight, wrapping, tag position, debug mode, the requested shell and the
-executable actually resolved at the last spawn, requested/effective GUI font
-and size, tagline scale, window opacity, ligatures (SDL GUI only), panel transition,
-scene effects, hover delay, platform, native-image support, and (on SDL) whether
-the executable uses live-built shaders or the paired prebuilt shader snapshot.
-Platform-dependent rows say `unsupported` instead of looking like an off or
-empty supported setting. The fields of `config.Runtime.Capabilities` gate
-them and are stated once as plain data in `builtins.capabilities`:
-`font_picker` is the SDL GUI and
-native macOS only, `scene_shaders` the same two, `panel_transitions` every
-hosted shell, `window_opacity` the SDL GUI and macOS, `window_blur` macOS
-only, `ligatures` the SDL GUI only, and `tagline_font_size`
-everything but the TTY and the board. `ligatures` is the exception to
-`unsupported`: where it is off, `Ligatures` is not a builtin at all and
-`Config` has no row for it. So
-the TTY reports Font, TaglineSize and the scene shaders as unsupported; the
-browser reports Font, panel transitions and the scene shaders as unsupported,
-and its TaglineSize row reads `82 (build-time only)` — tagline font size is
-its own capability precisely because GUI font SELECTION is native-only while
-the browser still applies the compiled percentage to its DOM glyphs.
-The startup path is printed whether or not a file exists — that is usually when
-it is most useful — and is ordinary selectable text, so a right click on it
-opens the file. Shell follows the same requested/effective/pending model as
-Font. `Default shell` is the one used while no `Shell` is set: `$SHELL`, the
-user's login shell, else `/bin/sh` (also when `$SHELL` names nothing
-executable); an explicit `Shell` overrides it. `Shell <name or path>` is
-refused unless it names an executable file (`Shell: shell "x" not found (...)`, or
-`Shell: not a shell: /etc is a directory`; a bare name is looked for in the
-usual bin directories), as `Tty <shell>` is, and a
-bare `Shell` goes back to the default. The root ctl reads `Shell <the one the
-next terminal runs>`. `Shell
-effective (last spawn)` is the executable the native host really chose after
-installation lookup and fallback. A changed request remains pending until a
-terminal is spawned, because the core does not resolve native executables.
+Native builds read one command file, `init`, from the per-user `pardes`
+configuration directory:
-The mutable global values live together in the plain `config.Runtime` record.
-One plain capability record gates the setting registry, leader table,
-`EffectCode`, and report; the compile-time setting table generates both setter
-builtins and their `Config` rows. Exhaustive checks require every table-backed
-toggle, transition, and scene-effect switch to occur exactly once, so those
-generated setting builtins cannot quietly lose their query row or leave a
-renderer switch unnamed. Manual pane-local actions remain with their payload (for
-example an image tag reports its renderer choices); they are not global
-configuration.
+- Unix: `$XDG_CONFIG_HOME/pardes/init` (only an absolute `XDG_CONFIG_HOME`
+ counts), else `~/.config/pardes/init`.
+- macOS: `$XDG_CONFIG_HOME/pardes/init` when set, else
+ `~/Library/Application Support/pardes/init`.
+- Windows: `%LOCALAPPDATA%\pardes\init`, else
+ `%USERPROFILE%\AppData\Local\pardes\init`.
-The browser build has no local user-config path and does not load this file.
-(Nor does it have `Font`, the effect builtins or `EffectCode`, a language
-backend, or ptys of its own — see `docs/web.md`.)
+The browser build has none. A file over 1 MiB or unreadable counts as absent.
-The format is one existing builtin command per line, using the same spelling
-and argument parsing as commands executed inside pardes:
+Each line is one builtin, spelled as it would be executed in pardes:
```text
Theme orchard
@@ -83,620 +24,171 @@ Shell zsh
Wrap
```
-A line matches a builtin whose name takes NO argument only as that whole word:
-`Kill` runs, `Kill something` does not. A builtin that takes one
-(`takes_arg` in `src/builtins.zig`, or a `settings` row whose action is
-`shell`, `theme`, `font`, `tagline_size` or `window_opacity` in `config.Runtime.settings`) takes
-everything after the name as the argument. On a native build that is `Theme`,
-`ThemeFile`, `Font`, `TaglineSize`, `Shell`, `Save`, `Restore`, `Attach`,
-`Mount`, `Unmount`, `Find`, `Grep`, `Rename`, `WsSymbols`, `Look`, `Exec`,
-`Msg` and `EffectCode`.
-The SDL GUI also has `WindowOpacity`, which takes one argument.
-(`Peek`, `Poke`, `Hexdump` and `Gpio` take one too, but they exist only where
-`builtins.Board.enabled` holds, and that build has no config file.)
-
-`Theme <name>` wants one of the names in the compiled ring. Do not derive the
-spelling — read it off `Themes` (`SPC t t`), which lists every one as the
-exact `Theme <name>` line that selects it. `slug` in `tools/gen_themes.zig`
-lowercases, folds punctuation runs to a single `_` and then TRIMS leading and
-trailing ones (`penumbra+.toml` is `penumbra`, not `penumbra_`), and every
-variant read out of a zed `.json` gets `_zed` on the end so it cannot collide
-with a helix theme of the same name — zed's "Ayu Mirage" is `ayu_mirage_zed`
-and `ayu_mirage` is helix's `ayu_mirage.toml`. The suffix goes on all of them
-rather than only the eight that clash today, so a name cannot move when either
-project gains or loses a file. A name that is not in the ring is ignored.
-
-The fifteen [native Pardes themes](themes.md) lead the ring: `orchard` (the
-default), `dusk`, `ink`, `paper`, `daybreak`, `atelier`, `forge`, `lagoon`,
-`solarium`, `spectrum`, `harvest`, `clay`, `forge_black`, `forge_soft`, and
-`orchard_black`, then `acme` and `lapis`. `Themes` lists them first under
-Pardes themes. They add coordinated
-focus, search, diagnostic and terminal colors; `ink` and `daybreak` are high
-contrast dark and light options. After them come the
-[faithful ports](themes.md#faithful-ports) of well-known themes, a section per
-family, then the legacy `helix` and `dark`, then everything imported. Where a
-port takes an imported theme's name (`dracula`), the imported one gains
-`_helix` (`dracula_helix`), as zed's carry `_zed`. `NextColor` walks the same
-ring in the same order.
-
-`FocusTint` toggles the focused pane and column tag tints; it is on by default.
-Workspace, column and pane command text can be edited directly; see
-[editable tags](tags.md) for naming and saved-workspace behavior.
-The column command row is always shown; an old `ColumnTags` init line is
-ignored with a message.
-`Placement acme` (the default) puts new panes where acme would, in the active
-column; `Placement pardes` brings back pardes's own rules, which open a first
-document in a column of its own. See [where new panes go](tags.md#where-new-panes-go).
-`BootShell keep` (the default) leaves a shell alone when a document is dragged
-into the left column beside it, whatever it holds; `BootShell replace` closes
-such a shell there when it is the column's only one and nobody has typed
-into it (no scrollback, cursor still on the first prompt line), the boot's
-placeholder giving its rows to the document. Bare `BootShell` flips it, as
-bare `Placement` does: a setting that chooses among words (`on`/`off`
-switches, `Placement`, `BootShell`, `Crt`, `Bloom`, `Vignette`, `Grain`, `Lift`, `Motion`,
-`ShaderAnimation`) steps to its
-next value when written bare, as its word clicked in a tag does, and a value
-it does not take is refused with the values it takes, which `/commands` also
-lists.
-`Verbose` toggles the message-row announcement every builtin makes of its own
-name before it runs; it is on by default, and the builtins that own the message
-row themselves (`Msg`) never announce. Turning it off leaves the row to the
-messages a builtin chooses to write.
-`MessageAnimation` toggles how a message comes and goes; it is on by default.
-A message eases down into its row, fast at first and settling at the end, its
-colour whole by half way (graphical frontends slide it out from under the
-tagline as it fades up; a terminal only fades it in), stays until the next key or click as it
-always has, then lingers for `MessageLinger` milliseconds (default 800) before
-it dissolves into the page. `MessageFall` (default 180) and `MessageDissolve`
-(default 150: leaving is quicker than arriving) set how long the fall and the dissolve take, also in
-milliseconds; each timing is at most 60000, and `Config` reports all three. `MessageLinger 0` with
-`MessageAnimation off` restores the old behaviour, a message cleared by the
-very input that follows it. An updated line on a row already showing one swaps
-its text in place rather than falling again.
-`SyntaxBold` toggles bold syntax keywords; it is off by default. These
-settings are shared by GUI and TTY, and `Config` reports their current
-states. Like `Colors` and `Wrap`, these commands take no argument and invert
-the current value. A `FocusTint` line in a fresh startup configuration disables
-the tint; a `SyntaxBold` line enables the stronger keyword weight. Those two
-appearance controls leave focus, selections and editing behavior unchanged.
-
-`Ligatures` (SDL GUI only) toggles a font's programming ligatures, such as
-Maple Mono's `->` and `!=` drawn across their cells; it is on by default.
-Off, every cell draws its own glyph, exactly as a font without ligatures does.
-`Ligatures on` and `Ligatures off` set it explicitly. The native macOS shell
-draws CoreText ligatures of its own, which this setting does not reach.
-
-`TreeContext` toggles sticky declaration headers for the current source pane.
-It is off by default and appears in the default pane tag when a tree-sitter
-grammar supports that file. `TreeContext on` and `TreeContext off` set it
-explicitly. Scrolling inside a function, type or module keeps its enclosing
-declarations above the body, with source line numbers, syntax colors and a
-subtle background tint. Clicking a header moves the cursor there without
-scrolling; scrolling upward reveals that source line as the headers recede.
-`Dump` and `Restore` preserve the setting per pane. Customized tags retain
-their text; the command can still be executed from any source pane.
-
-`TreeContextTagStyle` toggles the experimental tagline treatment for those
-headers and is on by default. In graphical frontends it uses the tagline font,
-line height and thin border. A matching separator marks gaps between the
-headers' source lines. Turning it off restores body-sized context rows.
-`TreeContext` itself still defaults off; this appearance option does not enable
-it. `Config` reports the appearance option, and dumps preserve it.
-
-Search, Grep and LSP location-result panes include `LocationsConfig` in their
-default tags. Custom tags keep their edits.
-
-Location highlighting is enabled by the command that produces a location list.
-Plain reports, including `LocationsConfig`, keep ordinary text colors even
-when their text resembles a location. `Mini` keeps its own syntax colors.
-LSP references and goto results highlight the exact symbol span supplied by
-the server, using the search-result colors. Source indentation is retained so
-those byte ranges stay aligned; context and descriptive labels remain unmarked.
-
-`LocationsConfig` prints the current settings for Search, Grep and LSP location
-results in an output pane. Execute the printed line to apply it again, or
-supply just the fields to change:
-
-```text
-LocationsConfig context:5 tscontext:on tslocations:off layout:stacked
-```
-
-- `context` is the number of source lines above and below each match (default
- `0`). Overlapping context is shown once. These neighboring lines omit their
- locations and keep their code aligned with the matching result.
-- `tscontext` includes enclosing tree-sitter declaration headers in source
- order (default `off`). Preceding neighboring context stops at the outermost
- enclosing declaration, keeping unrelated lines above it out of the result.
-- `tslocations` shows a location on the first line of each declaration header
- (default `on`); continuation lines keep the same alignment without repeating
- the location. Declaration headers use the same muted color as locations, with or
- without their locations visible.
-- `layout:stacked` (default) puts each location on its own line, followed by its
- source preview. Hidden context locations do not add an empty address line.
- Use `layout:inline` to put locations beside the source instead.
+A builtin that takes no argument matches only as the whole line (`Kill` runs,
+`Kill something` does not); one that takes an argument takes the rest of the
+line. Blank, unknown, malformed or failing lines are ignored silently and do
+not stop later ones. Text that is no builtin is not run as a shell command
+(`Exec ...` still is). Key bindings are compile-time choices in
+`src/config.zig`; `init` does not remap them.
-In inline layout, result locations share padding in groups of eight matches, so a long path only
-widens its own group. Context does not count toward the eight; at group boundaries,
-it aligns with the nearer match (ties stay with the preceding group). Source
-indentation is preserved. All visible locations start flush left; `n` and `N`
-still stop on matches.
-With `tscontext` enabled, asterisks after a match location show its declaration
-depth (for example, `main.zig:42 ***`). Ordinary neighboring source lines retain
-syntax colors; declaration headers do not.
-A declaration context line ends with ` ...` when source lines are omitted
-before the next displayed row from that file.
-Source analysis is reused across result queries while the source bytes stay the
-same. The cache retains at most 64 files and 64 MiB; it checks current buffer or
-filesystem contents on each refresh.
-Open file buffers supply context from their current edits. Missing files still
-leave the original result available. These settings apply to subsequent result
-generation and survive `Dump`/`Restore`. Invalid fields reject the entire
-update; if a field appears twice, its last value wins.
+`Config` (`SPC f c`) opens a `+Config` pane with the startup path (a right
+click opens it) and every live setting; a setting the frontend cannot show
+reads `unsupported`. The root `ctl` file reads the settings back in the
+words a write takes ([fs.md](fs.md#the-root-ctl)).
-`WindowOpacity <percent>` controls the opacity of everything in the SDL window
-except text and the cursor, which stay fully opaque. Use `WindowOpacity 85`
-to see the desktop through the editor, or `WindowOpacity 100` to restore full
-opacity (the default). The argument must be a whole number from
-0 through 100; missing or invalid values
-leave the setting unchanged. At zero only text and the cursor remain visible.
-Add the command to `init` to persist it.
+## Settings
-The same opacity applies to editor and embedded terminal backgrounds, UI
-chrome, borders, scrollbars, gutters, and images. Overlapping non-text drawing
-does not make those areas more opaque. Regular text, syntax colors, tagline
-text, terminal glyphs, and the cursor keep their normal opacity. This does
-not blend foreground colors into their cell backgrounds. The TTY
-and other non-SDL hosts do not emulate this effect; their `Config` report says
-`WindowOpacity unsupported`.
+A setting that chooses among words (`on`/`off` switches, `Placement`,
+`BootShell`, `Crt`, `Bloom`, `Vignette`, `Grain`, `Lift`, `Motion`,
+`ShaderAnimation`) steps to its next value when given bare, as its word
+clicked in a tag does; a value it does not take is refused, naming those it
+does. `/commands` lists every builtin and its values.
-On native Wayland, Pardes uses an alpha-capable transparent surface. It does
-not use whole-window opacity protocols such as `wp_alpha_modifier_v1`, because
-those would also fade the text. Presentation uses the existing GPU offscreen
-renderer followed by a readback and SDL renderer upload per presented frame,
-which adds rendering cost. Other SDL drivers can report background transparency
-as unsupported unless their rendering configuration is alpha-capable.
-If the rendering backend cannot apply the request, Pardes reports the error
-and keeps the last successfully applied opacity. `Config` reports the
-percentage and marks a request as pending until the SDL host handles it.
-
-## Crash records
-
-A panic appends to `crashes` in that same directory, beside `init`, and only
-then prints to stderr (`src/crash.zig`, wired into the panic handlers in
-`main.zig` and — because the macOS build roots there — `macos.zig`). stderr is
-the one place this program cannot keep a trace: in the TTY shell stderr IS the
-screen, so the trace lands on a grid the terminal is being reset out of; the SDL
-and AppKit shells have no terminal at all; and a `--detach` session's stderr
-goes wherever its launcher left it. The file is appended, never rewritten, and
-each record is two lines — build metadata, then the panic message:
-
-```text
-pardes 0.0.2 (a1b2c3d) 2026-09-03T11:20:44Z linux-x86_64 pid 48812
-panic: index out of bounds: index 4, len 4
-```
-
-NO STACK TRACE, and that is a measured decision rather than an omission. The
-frames stay on stderr, where `std.debug.defaultPanic` prints them. Collecting
-them here instead HANGS the process: `writeCurrentStackTrace` called from a
-panic handler before `defaultPanic` has run wedges at 0% CPU, and
-`captureCurrentStackTrace` — which looks like the safe half — takes `SelfInfo`'s
-rwlock exclusively on its first call, so a panic inside the walk leaves that
-lock held and `defaultPanic` then waits on it forever. What makes `defaultPanic`
-survive the same hazard is its own private `panic_stage`, which nothing outside
-`std.debug` can reach. A crash that becomes a hang is worse than the crash, so
-this file keeps only what it can gather without asking the process any
-questions: which build, when, where, and what it said.
-
-Everything about it is best effort and silent: no config directory (a launch
-with no `HOME`) means no file, and a directory that cannot be created or opened
-leaves the panic exactly as it was before — stderr alone. The directory itself
-is created if it does not exist, because the user who never wrote an `init` is
-as likely as any other to hit a bug. One record at a time: two threads panicking
-at once would otherwise interleave into one buffer, so the second falls straight
-through to stderr. Only panics come here; a SIGSEGV is caught one level lower
-(`main.zig`'s `debug.handleSegfault`) and unwinding one needs the signal's saved
-CPU context.
-
-## Runtime theme files
-
-`ThemeFile <path>` loads one complete theme from a `.zon` file. An absolute
-path is used as written; a relative path is resolved from the `pardes`
-configuration directory, not from the process working directory. A typical
-layout is:
-
-```text
-~/.config/pardes/
-├── init
-└── themes/
- └── mine.zon
-```
-
-and the corresponding init line is:
-
-```text
-ThemeFile themes/mine.zon
-```
-
-After a successful load, hosts with document live reload watch the path with
-the same parent-directory mechanism, so in-place writes and editor-style
-rename-over saves reload the theme live. A malformed or incomplete save does
-not replace the last valid theme; fixing and saving the file applies the next
-valid snapshot. Selecting a compiled theme with `Theme <name>` or `NextColor`
-stops the custom-file watch.
-
-Execute `DumpThemes` to write every theme compiled into the executable to:
-
-```text
-<config directory>/themes/builtin/<name>.zon
-```
-
-The command replaces those generated reference files but leaves unrelated
-files alone. Copy one into `themes/`, rename it, change its `.name`, and use it
-as the starting point for a custom theme. The dumped file is also the complete
-format, and it is `pardes.Theme` serialised by `std.zon.stringify`: the theme
-name, thirteen required RGB roles (`sel_bg`, `sel_fg`, `tag_bg`, `tag_fg`,
-`box`, `box_dim`, `kw`, `str`, `num`, `comment`, `lineno`, `scroll_track`,
-`scroll_thumb`), nullable `bg`/`fg`, and either a 16-color RGB `palette` or
-`null`. Optional nullable RGB roles extend this format:
-`tag_active_bg`, `tag_active_fg`, `tag_name_fg`, `tag_active_name_fg`, `border`, `empty_col`, `lineno_active`, `search_bg`, `search_fg`,
-`diagnostic_error`, `diagnostic_warning`, `diagnostic_info`,
-`diagnostic_hint`, `tag_sel_bg`, `tag_rule`, `box_border` and `box_dirty`;
-`sweep_bg` and `sweep_fg` take three triples, and `rule_px` and `rail_px` a
-pixel count. Missing new roles use backward-compatible defaults, so
-previously exported files remain valid. [Theme customization](themes.md)
-describes each role and its fallback. RGB values are three-byte arrays, and
-hex literals are accepted. The original fields remain required; there is no
-inheritance or partial override layer.
-
-`tag_name_fg` gives the filename at the end of a pane's path a separate
-foreground. `tag_active_name_fg` can adjust that tint for active tags; it falls
-back to `tag_name_fg`. When both are omitted or `null`, the filename uses
-the corresponding tag foreground. All canonical themes use a different hue
-at similar perceived brightness to the surrounding text in both states.
-Directory text and tag commands retain
-their regular colors; selecting filename text uses the selection colors.
-Terminal tags use this color for `Tty`, which comes immediately after the path.
-
-`Filter` in a terminal's tag projects that pane's ANSI colors through the
-active theme, and does it in two stages. The default foreground and background
-roles are mapped FIRST, because ghostty-vt generates the whole 256-color
-projection from that pair; every other color follows, by reducing it to its
-nearest canonical xterm key and reading the key back out of the projection.
-
-That reduction compares RGB triples, so it knows about hue and nothing about
-the page — and the projection's cube corners ARE the two anchors, which is how
-a foreground used to end up painted the exact color of the paper behind it
-(`\x1b[38;2;255;255;255m` on acme's `#ffffea`, and the ANSI black a shell
-writes with `\x1b[30m` on either dark theme). So a foreground additionally has
-to keep `tty_filter_min_contrast` — a WCAG ratio, `1.5` by default — against
-the mapped background. One that cannot is not mapped: it takes whichever of
-the theme's own two anchors is still visible on that background. Backgrounds
-are exempt, since a background is the page the floor is measured against. Set
-the constant to `1.0` to accept every projected color, collapses included.
-
-`Shell <name>` sets the binary that the NEXT terminal pane execs; panes
-already open keep the shell they are running. A bare name is resolved against
-the handful of directories a shell actually lives in, not `$PATH`.
-
-On Linux, `Tty9p` (`SPC n 9`) starts that shell with a private kernel 9P mount,
-asking sudo inside the new terminal. `$PARDES_MOUNT` names the mountpoint.
-The installed `pardes-v9fs` helper lives beside the editor; development builds
-can set `PARDES_V9FS_HELPER` to its absolute path. See [v9fs.md](v9fs.md).
-
-Ctrl-B switches between raw TTY and editor mode. Plain Escape at a detected
-shell prompt hops back to the previous pane. Other keys, including Ctrl-O,
-Ctrl-W and modified Escape, belong to the child. Use `Mode` in the pane tag to
-return to editor mode in place. Desktop paste events still feed the terminal.
-
-The paste chords are the exception the window keeps. Ctrl-V types the yank
-register at the program, and Ctrl-Shift-V asks the desktop for its clipboard
-and types that; both go through the program's bracketed paste when it has
-asked for one. Neither reaches the child as a keystroke, so an application
-that would otherwise answer Ctrl-V by reading the system clipboard itself
-never gets the chance to read the wrong thing.
-
-`Font` and `Fonts` exist ONLY in the SDL GUI and native macOS builds — a
-terminal's font belongs to its emulator and a browser's to the page — so a
-`Font` line is one of the silently-ignored ones everywhere else. Both builds
-resolve the name by walking the font directories on every lookup, so a face
-installed a moment ago is findable.
-
-Use `Font <name>:<size>` to change face and size together, for example
-`Font MartianMono-NrRg:18` in the startup file or an editable tag. Fractional
-sizes such as `:18.5` are supported; the accepted range is 8–72 (pixels in SDL,
-points on macOS). `Font <name>` without a suffix preserves the current size.
-Invalid sizes or unknown faces leave the current font unchanged.
-
-The SDL GUI also has a tiny optional workspace-tag companion: `Pet cat`,
-`Pet frog`, or `Pet off` (the default). Its original pixel sprite walks and
-idles in the trailing blank area of the global tag only. It hides while that
-tag is being edited, when the tag is full, or when the font leaves too little
-room; it never replaces text, receives clicks, or appears in pane/column tags.
-Animation runs at ten steps per second, uses theme ink, and requires no image
-assets or shaders. Put `Pet cat` in the startup configuration to keep it;
-`Pet off` disables the companion and its animation. Other hosts ignore the
-startup command. `Config` reports the current choice in SDL.
+| setting | default | |
+|---|---|---|
+| `Theme <name>` | `orchard` | names as `Themes` (`SPC t t`) lists them ([themes.md](themes.md)); `NextColor` walks the ring |
+| `ThemeFile <path>` | | a `.zon` theme, relative to the config directory; reloads live when saved |
+| `FocusTint` | on | tint the focused pane's and column's tags |
+| `SyntaxBold` | off | bold syntax keywords |
+| `Verbose` | on | a builtin announces its name on the message row |
+| `MessageAnimation` | on | messages ease in and dissolve |
+| `MessageLinger`, `MessageFall`, `MessageDissolve` | 800, 180, 150 | milliseconds, at most 60000 |
+| `Placement acme\|pardes` | `acme` | where new panes go ([tags.md](tags.md#where-new-panes-go)) |
+| `BootShell keep\|replace` | `keep` | `replace` closes the untouched lone shell a dragged document lands beside |
+| `LookWord search\|list` | `search` | a looked-at word selects its next place, or lists all in `+Search` |
+| `Shell <name or path>` | `$SHELL`, else the login shell, else `/bin/sh` | the shell the next terminal runs; a bare name is looked for in the usual bin directories, not `$PATH`; bare `Shell` returns to the default |
+| `DumpDir <dir>` | `$XDG_DATA_HOME/pardes`, else `~/.local/share/pardes` | where `Dump` writes; `~/` is home; bare returns to the default |
+| `TreeContext` | off | sticky declaration headers in a source pane (per pane, dumped) |
+| `TreeContextTagStyle` | on | draw those headers in the tagline style |
+| `LocationsConfig ...` | | Search, Grep and LSP result layout (below) |
+| `Wrap`, `Colors`, `Tagbottom`, `Debug` | | toggles |
+| `Font <name>[:<size>]`, `Fonts` | | SDL and macOS only; size 8-72 (pixels in SDL, points on macOS) |
+| `TaglineSize <1-100>` | 82 | tagline face, percent; SDL and macOS |
+| `WindowOpacity <0-100>` | 100 | SDL only: everything but text and the cursor |
+| `Ligatures` | on | SDL only; macOS draws CoreText's own |
+| `Pet cat\|frog\|off` | off | SDL only: a sprite in the workspace tag's blank space |
-`Font` is asynchronous at the renderer boundary. `Config` therefore keeps
-requested name/path/size, pending state, and the effective face/point-or-pixel size
-as separate facts; a failed request never gets reported as the face on screen.
-Taglines use a distinct face size in both native GUI renderers. Execute
-`TaglineSize <percent>` to change it live, for example `TaglineSize 70`; the
-accepted range is 1 through 100 and the default comes from
-`gui_tagline_font_percent` in `src/config.zig` (82). The native renderer
-remeasures both the glyph and its visible tag band while retaining body-grid
-pane geometry. Graphical tag text uses the smaller face's measured monospace
-advance and fills the available pixel width, including workspace and column
-tags. Text selection and scrolling use that same capacity; drag grips retain
-their physical body-grid width and position. The 100% ceiling is
-deliberate: a tagline remains exactly one logical grid row, so a larger face or
-band would overlap its pane body or a neighbour instead of leaving the body
-grid stable. `Config` reports the active percentage. The browser applies the
-same compiled percentage to its DOM glyphs but has no runtime setter.
+`Tty9p` (`SPC n 9`) is described in [v9fs.md](v9fs.md); the
+`PARDES_V9FS_HELPER` variable points development builds at the helper.
-Both native GUIs separate the reduced-height global and pane tagline bands with
-a `gui_topbar_pane_border_px` physical-pixel rule. Set it to zero to leave it out.
-`gui_topbar_pane_border_rgb` can pin an RGB color; its default `null` follows
-the theme's `border` role in SDL (falling back to `scroll_track` for older
-themes), and `scroll_track` in the macOS shell. Every band is centred in its
-row, so the workspace, column and pane tag text share one baseline offset and
-the anchors get the same margin above, below and to the left. With `Tagbottom`
-enabled, a tagline on the final grid row is bottom-aligned so the unused
-half-band does not show beneath it. The rule lives in the core
-(`pardes.taglineBandOffset`), and the macOS shell reaches it over the C ABI
-rather than keeping its own copy. When the window is not a whole number of cells,
-bands and rules at the right and bottom edges run on through the leftover pixels.
+### Terminals
-What happens to a codepoint the chosen face has no glyph for differs by shell.
-The SDL GUI falls back through a chain it builds itself: embedded Adwaita
-Mono, then installed `NotoSansMono-Regular`, `DejaVuSansMono`,
-`SymbolsNerdFont-Regular`, `NotoSansSymbols2-Regular`,
-`NotoSansSymbols-Regular`, and `DejaVuSans`, in that order, missing entries
-skipped; the rasterized glyphs are retained in its GPU atlas. The macOS shell
-has none of that and needs none. It embeds no font. Its default face is
-`NSFont.monospacedSystemFont`, resolved through the descriptor rather than by
-name, and `Menlo` only if that face turns out not to be fixed-pitch — a
-proportional face in a fixed grid is a broken screen, not a cosmetic problem
-(`PardesView.defaultFace`). A `Font <name>` arrives as a PATH the core already
-resolved by walking the font directories, and a file CoreText cannot measure
-leaves the face already on screen rather than substituting one, because the
-alternative is a terminal with no way back out (`PardesView.fromFile`).
-Per-codepoint fallback is CoreText's own cascade at draw time. What the shell
-caches is `CGGlyph` ids, not pixels.
+Ctrl-B switches a terminal between raw input and editor mode. Plain Escape
+at a detected shell prompt hops back to the previous pane; other keys,
+Ctrl-O, Ctrl-W and modified Escape included, go to the program. `Mode` in the
+tag returns to editor mode in place. Ctrl-V types the yank register and
+Ctrl-Shift-V the desktop clipboard, through bracketed paste when the program
+asked for it; neither reaches the program as a keystroke.
-Blank, unknown, malformed, or unsuccessful lines are ignored silently, and a
-bad line does not prevent later lines from running. Top-level text that is not
-a builtin is not sent to a shell. (`Exec ...` remains an ordinary builtin and
-therefore keeps its normal behavior.) Key bindings remain compile-time choices
-in `src/config.zig`; this startup file does not remap them.
+`Filter` in a terminal's tag maps its ANSI colours through the theme,
+keeping each foreground at least `tty_filter_min_contrast` (WCAG 1.5, in
+`src/config.zig`) against its background.
-## Panel and scene effects
+### Location results
-Exactly one panel transition is selected at a time. Executing its builtin a
-second time turns it off; selecting another replaces it:
+`LocationsConfig` with no argument prints the current settings as a line
+that can be run again; with fields it changes only those:
```text
-PanelSlide
-PanelZoom
-PanelDissolve
-PanelAscii
-PanelVertical
-PanelEdges
-PanelFall
-PanelWave
-PanelCurtain
-PanelScramble
-PanelType
+LocationsConfig context:5 tscontext:on tslocations:off layout:stacked
```
-All panel transitions start off. To disable one, execute its builtin again:
-`PanelDissolve` turns off an active dissolve, and `PanelAscii` turns off an
-active ASCII transition. `Config` shows the active command under
-`Panel transition`. Remove that command from your startup configuration to
-keep it off after restarting. Executing a different transition enables that
-one instead; these commands are toggles, not an unconditional animation-off command.
+- `context` (0): source lines shown above and below each match.
+- `tscontext` (off): include the enclosing tree-sitter declaration headers.
+- `tslocations` (on): show a location on each declaration header.
+- `layout` (`stacked`): the location on its own line; `inline` puts it
+ beside the source, padded in groups of eight matches.
-Slide uses cubic ease-out and zoom uses an
-overshooting ease-out-back. Dissolve and ASCII compare the last successfully
-presented grid with the new one. Dissolve switches visually changed cells at
-stable noise thresholds.
-ASCII walks every changed single-byte printable glyph
-from its old `u8` value to its new one, spending that byte distance as the
-frames of the walk. The walk is eased in and out: a glyph creeps at both ends
-and crosses the middle of its distance in a few large skips, inside the same
-number of frames a constant one-value-per-frame walk would have taken.
-Glyph-stable style changes
-and non-ASCII graphemes become canonical immediately. A walk is capped at
-twelve movement frames, and each pane lasts only as long as its longest walk.
-The core computes and composes that semantic diff once for every backend;
-pixel attachments, which have no character value, pass through unchanged.
+An invalid field rejects the whole line; a repeated field's last value wins.
+The settings survive Dump and Restore. Source analysis is cached for 64
+files and 64 MiB.
-Six further transitions are character *motion* over the same frozen/new grid
-pair, and are composed in the core the same way:
+### Effects
-- `PanelEdges` — whole rows slide in from alternating screen edges.
-- `PanelFall` — columns rain down into place, each with its own head start.
-- `PanelWave` — a vertical ripple travels across the pane and decays.
-- `PanelCurtain` — a wipe, left to right, behind a soft edge two cells wide.
-- `PanelScramble` — every cell churns through printable ASCII and locks onto
- its final glyph at its own stable noise threshold.
-- `PanelType` — reading-order reveal with a caret sitting on the write head.
+Panel transitions, one at a time; running the active one again turns it
+off: `PanelSlide`, `PanelZoom`, `PanelDissolve`, `PanelAscii`,
+`PanelVertical`, `PanelEdges`, `PanelFall`, `PanelWave`, `PanelCurtain`,
+`PanelScramble`, `PanelType`. All start off; the web shell has none.
-Unlike dissolve and ASCII these carry *every* glyph in the pane, changed or
-not: text flying in from a screen edge has to bring its unchanged glyphs with
-it. A cell whose glyph has not arrived shows the frozen old cell rather than a
-blank or a blend, so every intermediate frame is made of real characters. No
-cell is a valid input target until its own glyph has settled.
+Scene passes (SDL GUI, and a GUI attached to a detached session), each at a
+level 0-3 (`on` is 2), all off by default: `Crt`, `Bloom`, `Vignette`,
+`Grain`. `Shader <file.glsl>` adds a Shadertoy file written for ghostty to
+the chain (`Shader off` removes it; it recompiles when saved);
+`ShaderAnimation off|on|always` says when the chain animates by itself.
-Vertical is a pane-lifecycle effect: a newly added
-pane rises from below inside its own fixed box, and a deleted pane's frozen
-content drops back down; surviving panes are never animated. The TTY
-implementation performs its remaining geometry/dissolve operations directly
-on a copy of the core-composed presentation grid.
-The SDL and native macOS GUI implementations pass plain panel tracks to their
-GPU shaders, including native image/PDF pixels; layout itself commits
-immediately and remains the one authoritative geometry. DOM web intentionally
-does not expose these builtins: its renderer is selectable HTML/CSS and has no
-canvas or shader stage.
+The focused pane can stand off the page: `Lift shadow|rim|auto|off`,
+`InactiveDim <percent>`, `Motion off|crisp|smooth|bouncy|playful`
+(default `smooth`), `SelectionGlow`, `HoverGlow`, `Occlusion`, `Parallax`,
+`JumpTrail`, `ChipShadow`, `ThumbFlash`, `CursorBlink`, `GripWidth <50-300>`.
+Most are GUI-only; `InactiveDim` works everywhere, and `JumpTrail`,
+`ChipShadow` and `ThumbFlash` are the terminal's. No effect may lower the
+contrast of text, the selection or a focus indicator
+([effects.md](effects.md)).
-The scene effect is `Crt`, at a level from 0 (off) to 3; `on` is 2:
+`EffectCode <effect>` lists that effect's sources under `/virtual` when the
+build embeds them (`-Dembed-sources=true`). `zig build shaders` refreshes
+the committed SPIR-V with its GLSL.
-```text
-Crt
-Crt 3
-```
+### Look preview
-The SDL GUI runs it as the bundled pass of its post chain, which also takes
-Shadertoy files written for ghostty (`Shader ~/crt.glsl`, `Shader off`;
-a file compiles again when it is saved, and a save that fails to compile
-keeps the last good one and says why), and
-`ShaderAnimation off|on|always` says when the chain animates on its own. A
-GUI attached to a detached session runs the session's chain the same way. With
-the chain empty the pass is bypassed. CRT works in linear light with restrained
-scanlines, mask, bloom and vignette, and no curvature, so clicks land where
-they are drawn. Its slow hum and dither move on their own, so with Crt on the
-GUI keeps drawing while idle (under `ShaderAnimation on`, while focused);
-the other bundled passes are still and let it rest.
+Resting the pointer on text for about 32 ms (`look_preview_delay_frames`, 2
+frames) tints what a right click would look at, with no other effect. Set
+`look_preview_delay_frames` to `null` in `src/config.zig` to turn it off.
-Three more bundled passes take the same levels, each off by default and
-each costing nothing while off:
+## Themes from files
-```text
-Bloom 2 the brightest ink glows a little (only what is brighter than
- the page; a dual Kawase blur at half size and down)
-Vignette 2 the window's corners fall a little into shade
-Grain 2 the page's own ground takes a fine, still grain, like paper
-```
+`ThemeFile themes/mine.zon` loads a complete theme; a malformed save keeps
+the last good one, and `Theme <name>` stops the watch. `DumpThemes` writes
+every compiled theme to `<config dir>/themes/builtin/<name>.zon`: copy one,
+change its `.name`, and edit. The format is `pardes.Theme` as
+`std.zon.stringify` writes it; [themes.md](themes.md) describes each role.
-None of them touches a tag, a grip, a notice or the cursor, and each is
-capped per theme so text and the selection keep their own contrast or 4.5
-(docs/effects.md).
-
-The focused pane can stand off the page (SDL GUI, off by default):
-
-```text
-Lift shadow a soft drop shadow, on the other panes' bodies only
-Lift rim a hairline just above the focused tag (light, or shade on a light page)
-Lift auto a shadow on a light page; on a dark one the others recede
-Lift off
-InactiveDim 30 the unfocused panes' text fades 30% toward its ground
-Motion smooth off, crisp, smooth (the default), bouncy or playful
-SelectionGlow on a soft halo of the selection's colour round it in the body,
- fading in over 100 ms, never over a tag, a grip or the cursor
-HoverGlow on a soft glow under the word a look-hover would open, fading
- in over 80 ms
-Occlusion on pane bodies darken faintly toward their edges (2%), never
- over the cursor
-Parallax on a theme's page pattern (lapis's dots) moves with the text
- at a quarter of its speed
-JumpTrail on in a terminal, a jump of the cursor of three cells or more
- leaves a trail of a few cells that fades in 120 ms
- (truecolor terminals; a pixel shell glides instead)
-ChipShadow on in a terminal, a notice chip casts a shadow a cell right and
- down: half blocks on blank cells, a darker ground on text
-ThumbFlash on in a terminal, a pane's scroll thumb brightens as it scrolls
- and fades back over 250 ms
-CursorBlink on the cursor blinks, solid while typing and half a second
- after, eased at each edge, solid after 10 idle seconds
-GripWidth 150 the grip's button and the scrollbar under it, percent of the
- theme's rail_px (12px at a 17px tagline; 50 to 300)
-```
+## Dumps
-`InactiveDim` works everywhere (a grid shows it at once) and never takes a
-pair below its own contrast or 4.5. Under `Lift auto` on a dark page it is 30
-while unset. `Motion` sets how every such effect moves: see docs/effects.md.
+`Dump` writes `pardes-<date>-<time>.zon` (UTC) in `DumpDir`, creating the
+last directory if it is missing (a missing parent fails `no such
+directory`); `$PARDES_DUMP` overrides the file. `Restore <path>` looks for a
+relative path in `DumpDir`, then in the directory pardes started in; bare
+`Restore` takes the last dump this session wrote. `pardes -l <dump.zon>`
+starts from one.
-`EffectCode PanelAscii` or `EffectCode Crt` lists the current backend's
-build-embedded source paths under `/virtual`. Look opens each full file;
-no checkout is needed, but the build must carry them (`-Dembed-sources=true`,
-off by default); otherwise the command reports them unavailable. TTY exposes grid transitions, native GUI builds also
-expose scene shaders, and web has neither. Shared implementations share paths.
-SDL reports whether GLSL was compiled during this build or came from the
-`-Dprebuilt-shaders` snapshot paired with the committed SPIR-V.
-`zig build shaders` refreshes both files of every pair together,
-so editing live GLSL without that explicit refresh changes neither half of a
-prebuilt executable.
+A dump keeps panes, columns and their tags, each text pane's selection, the
+theme, and the settings that differ from a fresh session's; the font stays
+the frontend's. A terminal comes back with its last MiB of output as
+history, a `── restored history ──` line, and a new shell in its old
+directory; a command pane comes back finished (`exit ?` if it was running).
+Undo history and REPL bindings are not kept. A dump holds at most 6 columns.
-## Delayed Look preview
-
-The preview is enabled by default. Moving the pointer onto selectable text and
-leaving it still for `look_preview_delay_frames` — 2 animation ticks, and
-`animation.frame_ms` is 16, so about 32 ms — paints a subtle theme-derived
-preview of the exact operand a right-click Look would receive.
-Repeated motion reports in the same semantic grid cell do not restart the
-delay. The preview uses the same side-effect-free word/path expansion as Look;
-it does not focus a pane, move a cursor, install a selection, activate a PDF
-page, or execute anything. Motion to another operand, pointer leave, input,
-pane teardown, and relevant content changes cancel it.
-
-Set this compile-time option in `src/config.zig` to disable the feature:
+## Crash records
-```zig
-pub const look_preview_delay_frames: ?u16 = null;
-```
+A panic appends two lines to `crashes` beside `init` (build, time, platform,
+pid; then the panic message) before printing to stderr. There is no stack
+trace in it: collecting one from a panic handler can hang the process. The
+trace stays on stderr.
-## Build-time configuration
+## Build options
-Runtime settings live in `src/config.zig`. Build options are listed below;
-`zig build --help` lists the options available for the selected platform,
-including the standard `-Dtarget` and `-Doptimize` options.
+`zig build --help` lists the options for the selected platform.
| option | values | default |
|---|---|---|
-| `-Dplatform` | `tty`, `gui`, `web`, `macos`, `esp32p4` | absent builds the tty cli and the SDL gui together |
+| `-Dplatform` | `tty`, `gui`, `web`, `macos`, `esp32p4` | absent: the tty and SDL shells together, installed into `~/.local` |
| `-Dstatic` | bool | `false` |
-| `-Dquic` | bool; 9P over QUIC using system OpenSSL 3.6+ | `false` |
-| `-Dmupdf` | bool | on for a native target, off for web and esp32p4 |
-| `-Djpx` | bool | `true` — JPEG 2000, and with it scanned PDFs |
+| `-Dquic` | bool; 9P over QUIC with system OpenSSL 3.6+ | `false` |
+| `-Dmupdf` | bool | on natively, off for web and esp32p4 |
+| `-Djpx` | bool; JPEG 2000, and with it scanned PDFs | `true` |
| `-Dtree-sitter` | `disabled`, `zig`, `minimal`, `full` | `full` natively, `zig` for web, `disabled` for esp32p4 |
-| `-Dtheme-animation` | bool | on everywhere except `-Dplatform=esp32p4` |
-| `-Dworkspace-tag` | bool; draw the workspace tag row — the macOS shell hands its commands to the native menu bar instead | on except on `-Dplatform=macos` |
-| `-Dprebuilt-shaders` | bool | on for a bare `zig build`, off when `-Dplatform` names a shell |
-| `-Dtracy` | path to a Tracy source checkout | off |
+| `-Dembed-sources` | bool; serve the sources under `/src` | `false` |
+| `-Dstamp-commit` | bool; the git commit in `--version` and crash records | on for release builds and the `~/.local` install |
+| `-Dtheme-animation` | bool | on except for esp32p4 |
+| `-Dworkspace-tag` | bool; draw the workspace tag row | on except for macOS, whose menu bar carries it |
+| `-Dprebuilt-shaders` | bool; embed the committed SPIR-V | on for a bare `zig build`, off with `-Dplatform` |
+| `-Dtracy` | path to a Tracy checkout | off |
| `-Dmacos-identity` | codesigning identity for `pardes.app` | `-` (ad-hoc) |
| `-Ddump` | a `dump.zon` to embed in the web shell | none |
-| `-Dtest-filter` | substring; run only tests whose name contains it | none |
-| `-Dtest-rebuild` | bool; force fresh Zig test compilation, retaining cached C dependencies | `false` |
-| `-Dhelix-harness` | native reference executable for live differential tests | `HX_HARNESS`, otherwise `hx-harness` on PATH |
-| `-Desp32p4-cols` | u16, the board's grid width in cells | `56` |
-| `-Desp32p4-rows` | u16, the board's grid height in cells | `14` |
-
-The local board build emits an object. Firmware clock, serial port and profiling
-options belong to the sibling `05-zig-p4` toolchain's build.
-
-Two build inputs reach the running binary as ordinary values rather than as
-behaviour. `build.zig` reads `.version` from `build.zig.zon` through an untyped
-`@import("build.zig.zon")` — one place to bump — and `gitCommit(b)` reads the
-revision at configure time. Both land in `pardes_config` and are re-exported as
-`pardes.version` and `pardes.commit`, and `pardes --version` prints
-`pardes <version> (<commit>)`, or `pardes <version>` alone when there is no
-commit: a tarball, a container with no `git`, or a checkout outside version
-control all yield null, and the flag has to work anyway. Neither is a question
-asked at runtime — a binary that shelled out to `git` would describe whatever
-tree it was standing in rather than the one it came from.
-
-`Restore a.dump` first looks for the relative path in the dump directory:
-`DumpDir <path>` when set (a leading `~/` is your home; bare `DumpDir` returns
-to the default), else `$XDG_DATA_HOME/pardes` or `~/.local/share/pardes`.
-`Config` reports the directory in effect as `DumpDir <path>`, so the line can
-be fed back as configuration. If it is absent, Restore
-uses the argument as a path as before. Absolute paths and argument-free Restore
-retain their existing behavior. `Dump` still honors `$PARDES_DUMP` and otherwise
-writes a timestamped file in the default directory.
-
-A dump keeps each text pane's dot (its selection, or the caret), as acme's
-keeps a window's q0 and q1, and a Restore puts it back where the kept view
-shows it. It keeps the settings that differ from a fresh session's (`Placement
-pardes`, `Verbose off`, a shader), as the root ctl reads them, and a Restore
-sets them again; the theme is kept with the layout, and the font stays the
-frontend's. REPL bindings are not kept.
+| `-Dtest-filter` | run only tests whose name contains it | none |
+| `-Dtest-rebuild` | bool; fresh Zig test compilation | `false` |
+| `-Dhelix-harness` | reference executable for live differential tests | `HX_HARNESS`, else `hx-harness` on PATH |
+| `-Desp32p4-cols`, `-Desp32p4-rows` | the board's grid | 56, 14 |
-A restored terminal comes back live: its recorded output (the last MiB of it)
-is replayed as history, a dim `── restored history ──` line marks where it
-ends, and a new shell starts below it in the directory the old one was in,
-the same shell it ran (a `Tty bash` pane comes back running bash). A
-view left scrolled back stays where it was. Only the shell is new; nothing the
-old one was running is restarted. A command pane comes back finished, with
-what it printed and its `exit N`, and is not run again (a command still
-running when dumped comes back as `exit ?`). The web shell, which has no ptys, still
-shows the history alone.
+The version comes from `build.zig.zon`'s `.version`.