| themerequired | ThemeChoice | theme= in preferences.conf, and the terminal row's theme=.
Matches koru_draw::theme::ThemePreference: case-sensitive, because that is what the existing parser does and a file written in another case has never been produced by any build. |
|---|
| icon_pack | IconPackChoice or null | None follows the build's default artwork, now and later; it is persisted as no key, like a font role that follows the app.
Default null |
|---|
| caret_colorrequired | CaretColorChoice | caret_color=, and the terminal row's cursor_color=.
|
|---|
| caret_widthrequired | CaretWidthChoice | caret_width=.
x4/x8 are accepted on read and never written - an asymmetry inherited from koru_draw::theme::CaretWidth::parse, kept because files carrying them exist.
|
|---|
| caret_heightrequired | CaretHeightChoice | |
|---|
| ui_fontrequired | FontSetting | What a font role is set to. "Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either. |
|---|
| body_fontrequired | FontSetting | What a font role is set to. "Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either. |
|---|
| headings_fontrequired | FontSetting | What a font role is set to. "Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either. |
|---|
| code_fontrequired | FontSetting | What a font role is set to. "Follow the app default" is a state, not a value. The alternative - writing today's default into the file - froze the default forever: v1 files wrote every font key on every save, and nobody saw a new default font until their file was deleted. So FollowDefault persists as no key at all, and a chosen family is always written even when it happens to equal the current default. That last part is a deliberate change from the pre-SP-6 writer, which omitted a key whose value matched the default and so could not tell a choice from a habit either. |
|---|
| chat_font | FontSetting | The typeface the AI chat is set in - its messages and the box a person types in. follow_default follows the app font (ui_font), now and later, rather than a build default of its own: a person who never chooses sees the chat exactly as before, and a later change to the app font carries the chat with it. Persisted as no key while it follows, like every font role. #[serde(default)] so a section from a build without it reads as following the app font. Default "follow_default" |
|---|
| document_text_size | DocTextSizeSetting | The document body size. Follows the build's default until chosen. Default "follow_default" |
|---|
| base_color_light | RgbColor or null | The window's base color - the frame around the panes - for the light theme, as #rrggbb. null follows the theme, now and later, and is persisted as no key, like a font role that follows the app. Default null |
|---|
| base_color_dark | RgbColor or null | The same for the dark theme. Each theme keeps its own; neither is derived from the other. Default null |
|---|
| accent_color_light | RgbColor or null | The accent - the colour a primary action wears - for the light theme, as #rrggbb; null follows the theme (2026-11-10). Default null |
|---|
| accent_color_dark | RgbColor or null | The same for the dark theme, kept apart for the base colour's reason. Default null |
|---|
| densityrequired | DensityChoice | density=. Case-sensitive, matching koru_draw::theme::Density.
|
|---|
| scroll_speed | ScrollSpeedChoice | Absent from a file written before it existed, which reads as the default. Default "2x" |
|---|
| zoomrequired | Zoom | A zoom factor, valid only within the range the app has always accepted. Out of range is ignored rather than clamped - the guarded-geometry stance: a file carrying nonsense keeps the default rather than snapping to a surprising value the person never chose. |
|---|
| default_view_moderequired | ViewModeChoice | view_mode= - the default view mode for a newly opened document.
Narrow until somebody chooses (the user's call, 2026-10-31): a readable column is what a new page should look like. Unknown input becomes the default too. |
|---|
| default_canvas_backgroundrequired | CanvasBackground | The canvas background a new document opens with. Auto persists as the literal auto; a color persists as #rrggbb in lowercase. Alpha is not stored and is always opaque, exactly as today.
|
|---|
| pinned_show_titlerequired | boolean | |
|---|
| tabs_per_workspace | boolean | Workspace tabs: each workspace keeps its own tabs (true, Per workspace) or the open tabs stay as you switch (false, Shared, the default). Absent from an older file, which reads as Shared. Default false |
|---|
| popout_closerequired | PopoutCloseChoice | popout_close=. Note RestoreTab persists as restore.
|
|---|
| emoji_skin_tonerequired | string | |
|---|
| dictation_gesturerequired | DictationGesture | dictation_toggle= as a named gesture rather than a bare flag.
The disk spelling and the API spelling are deliberately different. On disk it is 0/1, because that is the byte already written in every preferences.conf and three parsers read it. Over the API it is push_to_talk/toggle, because "dictation_gesture": "1" is a riddle at the call site - and the two parsers that read dictation_toggle disagree about true (one accepts it, one does not), which is exactly the kind of question a typed value should stop anybody having to ask. |
|---|
| dictation_languagerequired | LanguageChoice | A language preference: automatic, or one BCP 47 tag. The authority validates the shape of a tag and never resolves it: the closed set of languages lives with each consumer (koru-i18n for the UI, the speech engine for dictation, the spell engine for spelling), and pinning a copy of any of them here would be a second list to keep true. An unrecognized tag resolves to automatic at the consumer. Grew out of the dictation preference when spell checking needed exactly the same shape; [DictationLanguage] remains as an alias so nothing renames on the wire or at a call site. |
|---|
| dictation_tierrequired | DictationTier | dictation_quality=. koru_voice::ModelTier's two tiers.
|
|---|
| dictation_inputrequired | DictationInput | Which microphone dictation opens. The device name roams with the person rather than staying on the machine - ADR-252's recorded reasoning, kept deliberately. |
|---|
| spellcheck_enabledrequired | boolean | |
|---|
| spellcheck_languagerequired | LanguageChoice | A language preference: automatic, or one BCP 47 tag. The authority validates the shape of a tag and never resolves it: the closed set of languages lives with each consumer (koru-i18n for the UI, the speech engine for dictation, the spell engine for spelling), and pinning a copy of any of them here would be a second list to keep true. An unrecognized tag resolves to automatic at the consumer. Grew out of the dictation preference when spell checking needed exactly the same shape; [DictationLanguage] remains as an alias so nothing renames on the wire or at a call site. |
|---|
| favorite_models | array of FavoriteModel | The models starred in the chat's model picker, by agent key and model id, in the order they were starred; each pair once. Roaming, like the rest of how Koru looks to this person: a favorite made on one computer is a favorite on the next. An agent turned off on a machine keeps its favorites; the picker hides them there. #[serde(default)] so a section from a build without it still reads. Default [] |
|---|
| close_behaviorrequired | CloseBehaviorChoice | close_behavior in desktop-v1.json, whose variants are kebab-case.
|
|---|
| global_shortcutrequired | GlobalShortcut | The chord that raises global search. Currently writerless - no surface changes it - but the registry defines it anyway, because a setting the app reads and nothing can write is exactly the hole this authority exists to close. |
|---|
| global_dictation_enabled | boolean or null | Whether Agent may open a global microphone session and insert into an external application. Off until the person accepts the Voice notice. None means the first-use notice has never been accepted; Some remembers onboarding independently of the current enabled value. |
|---|
| global_dictation_shortcutrequired | DictationShortcut | The person's preferred global Dictation chord. Agent reports the registered fallback separately; this is intent, not availability. |
|---|
| start_on_loginrequired | boolean | |
|---|
| reminder_start_on_login_explainedrequired | boolean | |
|---|
| automatic_update_checksrequired | boolean | |
|---|
| delegate_temporary_root | string or null | Where Koru makes a folder for a delegated run that belongs to no project (ADR-365). None means the platform's temporary directory - %TMP% on Windows, /tmp on Unix - which is [crate::paths::delegate_temporary_base]'s answer and the only place the default is spelled. Device-local because it names a directory on this machine: the same person's other computer has different disks, and a path that travelled would send a run to a folder that is not there. A string rather than a PathBuf because it crosses JSON and the wire, and because an unreadable value has to survive a round trip rather than fail a load - the consumer falls back to the default instead. |
|---|
| recordingrequired | RecordingPrefs | The internal session recorder's prefill. Device-local - see the type. |
|---|
| disabled_agents | array of string | The coding agents turned off on this machine, by agent key (koru_session_profiles::ProfileLabel::agent_key: claude, codex, …), sorted and without repeats. An agent named here is hidden from the chat's agent picker, the terminal's Agents › menu and every new session choice; its chats and sessions are kept and keep working. The off list, not the on list, so empty is the default - every agent on - and an agent a later Koru adds is on until someone turns it off. Device-local because the agents installed differ per machine. #[serde(default)] so a section from a build without it still reads. Default [] |
|---|
| default_permission | AgentPermission | The permission a new chat with a supported agent starts in - an agent that has an auto mode (Claude, Codex). Manual by default. The chat's own mode is still the chat's: a change inside a chat applies to that chat only, and this names only where the next new chat starts. Device-local, beside disabled_agents. Default "manual" |
|---|
| default_permission_other | AgentPermission | The same for every other agent - one with no auto mode (OpenCode): Manual or Yolo, never Auto. Manual by default. Default "manual" |
|---|
| default_agent | string or null | The agent a new chat starts with, by agent key, or None for Auto - the last agent used, which is what a chat did before this existed. An agent that is turned off or not installed falls back to Auto where a chat is made; the key is kept, so turning it back on brings the choice back. Default null |
|---|
| agent_order | array of string | The order the coding agents are listed in, by agent key, as the person dragged them on Agent providers: the chat's agent rail, the terminal's Agents › menu, the Default agent chips and the delegate dialog all follow it. Empty is the built-in order (Claude first); an agent it does not name - one a later Koru adds - follows the named ones in the built-in order. Each key once. Default [] |
|---|