Waveterm add-config
Guide for adding new configuration settings to Wave Terminal. Use when adding a new setting to the configuration system, implementing a new config key, or adding user-customizable settings.
git clone https://github.com/wavetermdev/waveterm
T=$(mktemp -d) && git clone --depth=1 https://github.com/wavetermdev/waveterm "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.kilocode/skills/add-config" ~/.claude/skills/wavetermdev-waveterm-add-config && rm -rf "$T"
.kilocode/skills/add-config/SKILL.mdAdding a New Configuration Setting to Wave Terminal
This guide explains how to add a new configuration setting to Wave Terminal's hierarchical configuration system.
Configuration System Overview
Wave Terminal uses a hierarchical configuration system with:
- Go Struct Definitions - Type-safe configuration structure in
pkg/wconfig/settingsconfig.go - JSON Schema - Auto-generated validation schema in
schema/settings.json - Default Values - Built-in defaults in
pkg/wconfig/defaultconfig/settings.json - User Configuration - User overrides in
~/.config/waveterm/settings.json - Block Metadata - Block-level overrides in
pkg/waveobj/wtypemeta.go - Documentation - User-facing docs in
docs/docs/config.mdx
Settings cascade from defaults → user settings → connection config → block overrides.
Step-by-Step Guide
Step 1: Add to Go Struct Definition
Edit
pkg/wconfig/settingsconfig.go and add your new field to the SettingsType struct:
type SettingsType struct { // ... existing fields ... // Add your new field with appropriate JSON tag MyNewSetting string `json:"mynew:setting,omitempty"` // For different types: MyBoolSetting bool `json:"mynew:boolsetting,omitempty"` MyNumberSetting float64 `json:"mynew:numbersetting,omitempty"` MyIntSetting *int64 `json:"mynew:intsetting,omitempty"` // Use pointer for optional ints MyArraySetting []string `json:"mynew:arraysetting,omitempty"` }
Naming Conventions:
- Use namespace prefixes (e.g.,
,term:
,window:
,ai:
,web:
)app: - Use lowercase with colons as separators
- Field names should be descriptive and follow Go naming conventions
- Use
tag to exclude empty values from JSONomitempty
Type Guidelines:
- Use
and*int64
for optional numeric values*float64 - Use
for optional boolean values (or*bool
if default is false)bool - Use
for text valuesstring - Use
for arrays[]string - Use
for numbers that can be decimalsfloat64
Namespace Organization:
- Application-level settingsapp:*
- Terminal-specific settingsterm:*
- Window and UI settingswindow:*
- AI-related settingsai:*
- Web browser settingsweb:*
- Code editor settingseditor:*
- Connection settingsconn:*
Step 1.5: Add to Block Metadata (Optional)
If your setting should support block-level overrides, also add it to
pkg/waveobj/wtypemeta.go:
type MetaTSType struct { // ... existing fields ... // Add your new field with matching JSON tag and type MyNewSetting *string `json:"mynew:setting,omitempty"` // Use pointer for optional values // For different types: MyBoolSetting *bool `json:"mynew:boolsetting,omitempty"` MyNumberSetting *float64 `json:"mynew:numbersetting,omitempty"` MyIntSetting *int `json:"mynew:intsetting,omitempty"` MyArraySetting []string `json:"mynew:arraysetting,omitempty"` }
Block Metadata Guidelines:
- Use pointer types (
,*string
,*bool
,*int
) for optional overrides*float64 - JSON tags should exactly match the corresponding settings field
- This enables the hierarchical config system: block metadata → connection config → global settings
- Only add settings here that make sense to override per-block or per-connection
Step 2: Set Default Value (Optional)
If your setting should have a default value, add it to
pkg/wconfig/defaultconfig/settings.json:
{ "ai:preset": "ai@global", "ai:model": "gpt-5-mini", // ... existing defaults ... "mynew:setting": "default value", "mynew:boolsetting": true, "mynew:numbersetting": 42.5, "mynew:intsetting": 100 }
Default Value Guidelines:
- Only add defaults for settings that should have non-zero/non-empty initial values
- Ensure defaults make sense for typical user experience
- Keep defaults conservative and safe
- Boolean settings often don't need defaults if
is the correct defaultfalse
Step 3: Update Documentation
Add your new setting to the configuration table in
docs/docs/config.mdx:
| Key Name | Type | Function | | ------------------- | -------- | ----------------------------------------- | | mynew:setting | string | Description of what this setting controls | | mynew:boolsetting | bool | Enable/disable some feature | | mynew:numbersetting | float | Numeric setting for some parameter | | mynew:intsetting | int | Integer setting for some configuration | | mynew:arraysetting | string[] | Array of strings for multiple values |
Documentation Guidelines:
- Provide clear, concise descriptions
- For new settings in upcoming releases, add
<VersionBadge version="v0.14" /> - Update the default configuration example if you added defaults
- Explain what values are valid and what they do
Step 4: Regenerate Schema and TypeScript Types
Run the generate task to automatically regenerate the JSON schema and TypeScript types:
task generate
What this does:
- Runs
(automatically generates JSON schema from Go structs)task build:schema - Generates TypeScript type definitions in
frontend/types/gotypes.d.ts - Generates RPC client APIs
- Generates metadata constants
Important: The JSON schema in
schema/settings.json is automatically generated from the Go struct definitions - you don't need to edit it manually.
Step 5: Use in Frontend Code
Access your new setting in React components:
import { getOverrideConfigAtom, getSettingsKeyAtom, useAtomValue } from "@/store/global"; // In a React component const MyComponent = ({ blockId }: { blockId: string }) => { // Use override config atom for hierarchical resolution // This automatically checks: block metadata → connection config → global settings → default const mySettingAtom = getOverrideConfigAtom(blockId, "mynew:setting"); const mySetting = useAtomValue(mySettingAtom) ?? "fallback value"; // For global-only settings (no block overrides) const globalOnlySetting = useAtomValue(getSettingsKeyAtom("mynew:globalsetting")) ?? "fallback"; return <div>Setting value: {mySetting}</div>; };
Frontend Configuration Patterns:
// 1. Settings with block-level overrides (recommended for most view/display settings) const termFontSize = useAtomValue(getOverrideConfigAtom(blockId, "term:fontsize")) ?? 12; // 2. Global-only settings (app-wide settings that don't vary by block) const appGlobalHotkey = useAtomValue(getSettingsKeyAtom("app:globalhotkey")) ?? ""; // 3. Connection-specific settings const connStatus = useAtomValue(getConnStatusAtom(connectionName));
When to use each pattern:
- Use
for settings that can vary by block or connection (most UI/display settings)getOverrideConfigAtom() - Use
for app-level settings that are always globalgetSettingsKeyAtom() - Always provide a fallback value with
operator??
Step 6: Use in Backend Code
Access settings in Go code:
// Get the full config fullConfig := wconfig.GetWatcher().GetFullConfig() // Access your setting myValue := fullConfig.Settings.MyNewSetting // For optional values (pointers) if fullConfig.Settings.MyIntSetting != nil { intValue := *fullConfig.Settings.MyIntSetting // Use intValue }
Complete Examples
Example 1: Simple Boolean Setting (No Block Override)
Use case: Add a setting to hide the AI button globally
1. Go Struct (pkg/wconfig/settingsconfig.go
)
pkg/wconfig/settingsconfig.gotype SettingsType struct { // ... existing fields ... AppHideAiButton bool `json:"app:hideaibutton,omitempty"` }
2. Default Value (pkg/wconfig/defaultconfig/settings.json
)
pkg/wconfig/defaultconfig/settings.json{ "app:hideaibutton": false }
3. Documentation (docs/docs/config.mdx
)
docs/docs/config.mdx| app:hideaibutton <VersionBadge version="v0.14" /> | bool | Hide the AI button in the tab bar (defaults to false) |
4. Generate Types
task generate
5. Frontend Usage
import { getSettingsKeyAtom } from "@/store/global"; const TabBar = () => { const hideAiButton = useAtomValue(getSettingsKeyAtom("app:hideaibutton")); if (hideAiButton) { return null; // Don't render AI button } return <button>AI</button>; };
6. Usage Examples
# Set in settings file wsh setconfig app:hideaibutton=true # Or edit ~/.config/waveterm/settings.json { "app:hideaibutton": true }
Example 2: Terminal Setting with Block Override
Use case: Add a terminal bell sound setting that can be overridden per block
1. Go Struct (pkg/wconfig/settingsconfig.go
)
pkg/wconfig/settingsconfig.gotype SettingsType struct { // ... existing fields ... TermBellSound string `json:"term:bellsound,omitempty"` }
2. Block Metadata (pkg/waveobj/wtypemeta.go
)
pkg/waveobj/wtypemeta.gotype MetaTSType struct { // ... existing fields ... TermBellSound *string `json:"term:bellsound,omitempty"` // Pointer for optional override }
3. Default Value (pkg/wconfig/defaultconfig/settings.json
)
pkg/wconfig/defaultconfig/settings.json{ "term:bellsound": "default" }
4. Documentation (docs/docs/config.mdx
)
docs/docs/config.mdx| term:bellsound <VersionBadge version="v0.14" /> | string | Sound to play for terminal bell ("default", "none", or custom sound file path) |
5. Generate Types
task generate
6. Frontend Usage
import { getOverrideConfigAtom } from "@/store/global"; const TerminalView = ({ blockId }: { blockId: string }) => { // Use override config for hierarchical resolution const bellSoundAtom = getOverrideConfigAtom(blockId, "term:bellsound"); const bellSound = useAtomValue(bellSoundAtom) ?? "default"; const playBellSound = () => { if (bellSound === "none") return; // Play the bell sound }; return <div>Terminal with bell: {bellSound}</div>; };
7. Usage Examples
# Set globally in settings file wsh setconfig term:bellsound="custom.wav" # Set for current block only wsh setmeta term:bellsound="none" # Set for specific block wsh setmeta --block BLOCK_ID term:bellsound="beep" # Or edit ~/.config/waveterm/settings.json { "term:bellsound": "custom.wav" }
Configuration Patterns
Clear/Reset Pattern
Each namespace can have a "clear" field for resetting all settings in that namespace:
AppClear bool `json:"app:*,omitempty"` TermClear bool `json:"term:*,omitempty"`
Optional vs Required Settings
- Use pointer types (
,*bool
,*int64
) for truly optional settings*float64 - Use regular types for settings that should always have a value
- Provide sensible defaults for important settings
Block-Level Overrides via RPC
Settings can be overridden at the block level using metadata:
import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { WOS } from "@/store/global"; // Set block-specific override await RpcApi.SetMetaCommand(TabRpcClient, { oref: WOS.makeORef("block", blockId), meta: { "mynew:setting": "block-specific value" }, });
Common Pitfalls
1. Forgetting to Run task generate
task generateProblem: TypeScript types not updated, schema out of sync
Solution: Always run
task generate after modifying Go structs
2. Type Mismatch Between Settings and Metadata
Problem: Settings uses
string, metadata uses *int
Solution: Ensure types match (except metadata uses pointers for optionals)
3. Not Providing Fallback Values
Problem: Component breaks if setting is undefined
Solution: Always use
?? operator with fallback:
const value = useAtomValue(getSettingsKeyAtom("key")) ?? "default";
4. Using Wrong Config Atom
Problem: Using
getSettingsKeyAtom() for settings that need block overrides
Solution: Use
getOverrideConfigAtom() for any setting in MetaTSType
Best Practices
Naming
- Use descriptive names:
notterm:fontsizeterm:fs - Follow namespace conventions: Group related settings with common prefix
- Use consistent casing: Always lowercase with colons
Types
- Use
for simple on/off settings (no pointer if false is default)bool - Use
only if you need to distinguish unset from false*bool - Use
/*int64
for optional numeric values*float64 - Use
for text, paths, or enum-like valuesstring - Use
for lists[]string
Defaults
- Provide sensible defaults for settings users will commonly change
- Omit defaults for advanced/optional settings
- Keep defaults safe - don't enable experimental features by default
- Document defaults clearly in config.mdx
Block Overrides
- Enable for view/display settings: Font sizes, colors, themes, etc.
- Don't enable for app-wide settings: Global hotkeys, window behavior, etc.
- Consider the use case: Would a user want different values per block or connection?
Documentation
- Be specific: Explain what the setting does and what values are valid
- Provide examples: Show common use cases
- Add version badges: Mark new settings with
<VersionBadge version="v0.x" /> - Keep it current: Update docs when behavior changes
Quick Reference
When adding a new configuration setting:
- Add field to
inSettingsTypepkg/wconfig/settingsconfig.go - Add field to
inMetaTSType
(if block override needed)pkg/waveobj/wtypemeta.go - Add default to
(if needed)pkg/wconfig/defaultconfig/settings.json - Document in
docs/docs/config.mdx - Run
to update TypeScript typestask generate - Use appropriate atom (
orgetOverrideConfigAtom
) in frontendgetSettingsKeyAtom
Related Documentation
- User Documentation:
- User-facing configuration docsdocs/docs/config.mdx - Type Definitions:
- Go struct definitionspkg/wconfig/settingsconfig.go - Metadata Types:
- Block metadata definitionspkg/waveobj/wtypemeta.go