decisions/configuration.md

Configuration

Status: Miri settings decisions and proposals. The release scope and ADR 0007 define the accepted configuration contract. Implementation and test status lives in PROGRESS.md, not in this document.

Expose settings through controls in a native window. Apply valid changes immediately, without a Save button or restart. Ordinary setup must not require file editing. Advanced users can edit ~/Library/Application Support/Miri/config.toml.

Miri reuses Paneru's TOML key names and stores app-only settings in a [miri] section. Miri passes the configuration path explicitly and leaves standalone Paneru's files untouched. The app watches the file for external edits. If the file is invalid or cannot be written, the engine keeps the last valid settings. Miri shows the error in Settings and the menu bar. Miri never overwrites a file it could not read correctly. This is Miri's required isolation contract, not a verified capability of unmodified Paneru. The pinned-engine assessment identifies standalone configuration fallback and Lua discovery that Miri must prevent.

Configuration upgrades

Configuration upgrades follow the accepted migration rule in Release scope. General schema migrations remain engineering work. Default shortcut upgrades save the original file in config.toml.backups/ beside config.toml before recording a new default version. Each backup has a unique filename, and a backup failure leaves the configuration file untouched. The initial default installation also backs up the file before adding its version marker.

Accepted layout defaults

Product settingMiri defaultEngine mapping
Width presets[0.25, 0.33, 0.5, 0.66, 0.75][options].preset_column_widths, cycled by Control-Option-R.
Inner gap8 points[miri].inner_gap. Miri writes half of it as per-window padding in [windows.miri-gaps].
Outer gap8 points[miri].outer_gap. Miri writes it minus the per-window padding to each [padding] side.
Stack limit5 windows[options].stack_limit, a number from 1 or "fit". The engine caps it at what fits on each display.
Maximized stacked window opensRight of its stack[options].maximized_window_side, "left" or "right".
Show window contents on edges next to another displayOff[options].shared_edge_slices, true or false. Absent means off.
New window width0.5 of the display's tiling area[options].new_window_width, a ratio above 0 and up to 1, of the width inside the display padding, as the width presets use. Miri writes it when the key is absent. A [windows] rule's width wins.

Paneru v0.5.2 has no gap_inner or gap_outer configuration keys. Its outer padding has separate top, right, bottom, and left fields. Its per-window padding contributes on both sides of adjacent windows. The accepted eight-point gap behavior remains unchanged. The app implements this mapping. A live run on 15 October 2026 showed 8-point gaps on all sides and between windows on the built-in display. A native check for #28 on 18 October 2026 measured 8-point gaps between four windows. The app provisionally accepts gaps from 0 to 32 points. The final Miri validation range is not yet selected.

The preset list is editable. The fourth width preset is 0.66. Release scope defines the accepted new-window width and resize increment. The engine option [options].new_window_width applies only to windows that open while the engine runs, so windows found at startup and windows a restored session places keep their widths. Miri binds the engine's window_step_grow and window_step_shrink, which change the width by window_resize_step of the display, 0.05 by default.

Layout behavior and remaining controls

Focus reveal and Dock reservation follow V1 interactions. The stack limit is an engine option, [options].stack_limit, which holds a number from 1 or "fit" and defaults to 5. Settings writes it, and the engine caps it at what fits on each display. The side where a maximized stacked window opens is an engine option, [options].maximized_window_side, which holds "left" or "right" and defaults to "right". Settings writes it as Maximized stacked window opens, and Maximizing a window records the behavior. The switch Show window contents on edges next to another display writes [options].shared_edge_slices, as Sam decided in #150. Turning it on asks for Screen Recording permission, and the switch stays off without it. If Miri finds the permission gone when it rechecks its setup, it turns the switch off and says why. With it on, the engine shows a photo of each hidden window's slice on an edge next to another display, and a click or drag on that photo reaches the window. Alternative centering modes, never and always, remain proposals rather than accepted v1 controls. The allowed gap range remains unresolved. Fixed-pixel width presets are deferred to v2. Confirm each additional key against the pinned engine before adding it to Miri's schema.

Focus and pointer controls

The engine defaults [options].focus_follows_mouse and [options].mouse_follows_focus to false. A configuration without these keys therefore keeps pointer-driven focus and pointer movement off after a restart or an upgrade. focus_follows_mouse = true is a hidden key that turns pointer-driven focus back on. Settings does not show it, and Miri preserves it when Settings writes the file. The engine also accepts mouse_follows_focus = true, which moves the pointer onto a window when a Miri command focuses it. Such commands include focus shortcuts, Centre column, display focus, display moves, and virtual workspace switches. A click, Cmd-Tab, an app bringing its own window forward, focus-follows-mouse, a new or closed window, and engine startup leave the pointer in place. Sam chose this rule, niri's, on 7 November 2026 in #339. Settings > General offers the key as the switch Move the pointer to windows that Miri focuses, which is off by default. Sam moved this switch into v1 on 8 November 2026 in #360.

Focus borders and a focus-follows-mouse control are deferred to v2. The following option names and defaults remain proposals for that future work, pending verification against the pinned engine.

Proposed optionProposed defaultBehavior
focus_borderfalseDraw an overlay around the focused window.
focus_border_widthUnresolvedBorder width.
focus_border_colorUnresolvedColor in #RRGGBB or #RRGGBBAA format.
focus_border_radiusUnresolvedBorder corner radius.
dim_inactivefalseDim the area behind the focused window.
dim_inactive_opacityUnresolvedDimming strength.

Keep pointer movement opt-in.

A focus border is excluded from v1. Its width, color, and corner radius need defaults and validation rules before implementation. Inactive-window dimming is deferred to v2. Its opacity remains unresolved.

Miri dock

V2 includes a custom Miri dock with an enable or disable control. The Miri dock is enabled by default. When inactive-window dimming is enabled, the Miri dock dims too. The dock's configuration key remains unresolved. The Miri dock plan defines stack representation and the handling of several windows from the same app.

Gesture controls

Supported finger counts, defaults, direction, conflict handling, and retesting follow Strip gestures.

The fields below are source-backed Paneru v0.5.2 configuration fields. They do not by themselves encode Miri's verification status or conflict policy.

Engine fieldAccepted Miri behaviorSource behavior
[swipe.gesture].directionFollow finger movement by default, with a persistent reversal setting.Source values are "Natural" and "Reversed".
[swipe.gesture].fingers_countThree fingers, four fingers, or Off, subject to the accepted enablement policy.Exact-count interception starts at three fingers. Omission or zero disables it.
[swipe.gesture].verticalStrip swipes are horizontal only.Vertical interception defaults to enabled.
[swipe.gesture].window_landingOff for Continuous scrolling, on for Window by window scrolling.The strip follows the fingers, then lands one column on from where the swipe began, once the fingers have moved it 5% of the display width or flicked, and never glides on with momentum. haptics, on by default, clicks the trackpad each time that column changes.
[swipe.gesture].targetThe strip under the pointer by default, or the focused window's strip.A swipe or modifier-scroll picks its strip when it starts and drives that strip until it lands, even if focus or the active display changes. "pointer" is the default.
[swipe.scroll].window_stepOff for Continuous scrolling, on for Window by window scrolling.Each modifier-scroll wheel input focuses one adjacent column. Momentum adds no step, and repeats in the same direction within 180 ms are dropped. The modifier defaults to Option.

swipe_natural is absent from this release. Legacy [options].swipe_gesture_fingers and [options].swipe_gesture_direction are deprecated. Source acceptance of a five-finger count does not add five-finger support to Miri v1.

Miri stores the choice in [miri].strip_gesture as "off", "three", or "four", and the reversal in [miri].reverse_swipe. It always writes [swipe.gesture].direction from the reversal and sets [swipe.gesture].vertical to false. It writes [swipe.gesture].fingers_count only while the selected swipe is enabled or a gesture test is running, and removes it otherwise. It removes the legacy [options] swipe keys, because the engine falls back to them and would keep a swipe on that Miri turned off. Miri stores the scrolling choice in [miri].strip_scrolling as "continuous" or "window", and writes [swipe.gesture].window_landing and [swipe.scroll].window_step from it. A file without the key keeps Continuous scrolling. Miri does not write haptics or [swipe.scroll].modifier, so the engine defaults of pulses on and Option apply, as Sam decided on 11 November 2026 (#281). Miri removes [swipe.gesture].paging, because paging moves focus in steps while the strip stays still under the fingers, and it overrides window_landing. Sam tried paging on 11 November 2026 and found it unnatural, since the windows do not move during the swipe. The gesture test result is app state outside the file, so a file edit cannot mark a swipe as tested. The effect of vertical = false on Mission Control and App Exposé swipes still needs hardware verification.

The accepted strip target for trackpad scrolling and keyboard commands is in Multiple monitors. Miri stores the target in [miri].swipe_target as "pointer" or "focused", and writes [swipe.gesture].target from it (#212). Settings > Gestures offers it as Swipe moves, with Under pointer and Focused window.

The setup experience is recorded in Setup.

Animation controls

Strip motion should follow the display refresh rate. A keyboard slide in v1 uses the exponential ease-out recorded in V1 interactions. Configurable spring duration and damping are deferred to v2. V1 follows the Reduce Motion and disable-animation behavior in V1 interactions. The Animate window movement switch in Settings > Layout writes [miri].animations. When the switch is off or Reduce Motion is on, Miri removes [options].animation_speed, and the engine then moves windows instantly. The following option names, values, and behavior require validation in Miri before adoption.

Proposed optionProposed defaultBehavior
animation_duration_ms500Perceived spring duration in milliseconds. 0 disables animation.
animation_damping0.91.0 settles without overshoot. Lower values produce bounce.

Window rules and displays

Advanced window rules are deferred to v2. Proposed rules match a bundle ID, a title regular expression, or both. An app picker could create rules for floating a window or assigning a strip position. Rule precedence and the file schema remain unresolved.

Automatic floating for fixed-size windows and Miri's settings follows V1 interactions. This default does not add advanced per-app rules to v1. The file field for a fixed strip slot remains unresolved.

Excluding individual displays from management is deferred to v2. The proposed file key is ignored_displays. The proposed Configuration > Displays control lets users turn management off for individual displays. The display identifier format remains unresolved. Windows on excluded displays would retain manual positioning.

Each managed display and native Space retains its own strip. macOS System Settings controls display arrangement, scaling, resolution, and Spaces. Display changes follow the accepted behavior in Release scope. The engine keeps a sliver of each hidden column at a free display edge, as Setup describes.

Shortcut configuration

Use the settings recorder for ordinary edits. Allow several bindings per action and preserve user bindings across upgrades. Conflict handling during recording follows Binding customization. The selected defaults are in Keybinding reference. Actions not covered there have no default shortcut, because Paneru's default configuration binds no keys.

Miri writes the default set to [bindings], with the engine's command names as keys and its chord syntax as values. The engine reads each key as command words split on _, so window_focus_3 means window focus 3. An unknown command name makes the engine reject the whole file, so Miri writes only names the pinned engine parses. A binding whose key name the engine does not know does nothing, and a bad modifier or an extra dash makes the engine reject the whole file. Miri reports each of these as a configuration error in Settings and the menu bar, and changes nothing in the file until the user fixes it. A short excerpt of the written table follows.

toml
1[bindings]
2window_focus_west = ["ctrl+alt-leftarrow", "ctrl+alt-h", "ctrl+alt-a"]
3window_focus_3 = "ctrl+alt-3"
4window_step_grow = "ctrl+alt-equal"
5window_stack_west = "ctrl+alt-leftbracket"
6window_manage = "ctrl+alt-t"

Sources/MiriCore/Bindings.swift holds the full table, and Engine mapping lists the differences. [miri].default_bindings_version records which defaults Miri has offered. Miri adds a default only when the action has no binding and every chord it adds is free, and it never changes a user binding. Miri offers each default once, so a binding the user deletes stays deleted. The proposed window_tabbed, window_wider, window_narrower, window_taller, and window_shorter names are not engine commands.

App settings and lifecycle

Store app-only settings, including launch at login and updates, in [miri]. Launch at login is enabled by default. [miri].show_in_dock stores the Show in Dock switch, and it is true by default. An explicit stopped state persists across launches. Window recovery after management ends follows App controls and window recovery.

Miri checks for updates daily and asks before installing. If users enable automatic updates, Miri installs updates silently in the background at startup.

The engine restores its layout after a start or crash restart. The minimap, tabbed columns, dimming, and advanced rules remain v2 work.

Open configuration decisions

Define validation ranges and error behavior for each additional setting before exposing it. Confirm proposed defaults against Miri's engine and hardware checks. Document app-only file keys when their schema is selected.