name: t3-settings-workflow description: Use when adding, changing, wiring, or debugging settings in the T3 Code repo, especially when a setting must flow through contracts, useSettings, the settings UI, and a consumer like the sidebar, chat view, or server config.
T3 Settings Workflow
Use this skill when a task involves app settings in t3code-vxapp.
This repo already has a real settings architecture. Do not add one-off local state if the behavior is meant to be configurable.
First Principles
- Decide whether the setting is
client-onlyorserver-authoritative. - Prefer extending existing settings flow over adding parallel config paths.
- Keep defaults stable and explicit.
- Update restore/defaults surfaces so the setting is not "half integrated".
- Validate with:
bun fmtbun lintbun typecheck
- Never run
bun test. If tests are needed, usebun run test.
Settings Split
There are two settings buckets:
- Client settings:
- Persisted in local storage.
- Defined in packages/contracts/src/settings.ts in
ClientSettingsSchema. - Merged into app settings by apps/web/src/hooks/useSettings.ts.
- Server settings:
- Persisted by the server.
- Defined in packages/contracts/src/settings.ts in
ServerSettings. - Routed automatically by
splitPatch()in apps/web/src/hooks/useSettings.ts.
Rule of thumb:
- Use
clientfor UI preferences, sidebar behavior, local confirmations, display toggles, local editor UX. - Use
serverfor provider config, defaults that should follow the user across clients, or settings the server must know to behave correctly.
Normal Edit Path
When adding a new setting, check these files in roughly this order.
1. Schema and default
Edit packages/contracts/src/settings.ts.
Typical work:
- Add the field to
ClientSettingsSchemaorServerSettings. - Add a decoding default.
- Export a named default constant if the default is important or reused.
- Keep the unified type working through
DEFAULT_CLIENT_SETTINGS,DEFAULT_SERVER_SETTINGS, andDEFAULT_UNIFIED_SETTINGS. - For mutually exclusive UI modes, prefer a
Schema.Literals([...])enum plus a named default instead of multiple booleans.
Notes:
- Use existing schema helpers from packages/contracts/src/baseSchemas.ts when possible.
- For numeric settings, prefer validated schema types over raw numbers.
2. Migration and patch routing
Edit apps/web/src/hooks/useSettings.ts.
Typical work:
- Add legacy migration support in
buildLegacyClientSettingsMigrationPatch()if the repo already had older local storage keys for similar behavior. - Do not bypass
useUpdateSettings()unless there is a very strong reason. - If the setting is server-side, confirm the key is part of
ServerSettingssosplitPatch()routes it correctly. - If a new client setting has a decoding default, add the same fallback behavior in
buildLegacyClientSettingsMigrationPatch()when the legacy key is missing. Otherwise legacy users can silently miss the new defaulted field during one-shot migration.
3. Settings UI
Edit apps/web/src/components/settings/SettingsPanels.tsx.
Typical work:
- Add a
SettingsRow. - Use
SettingResetButtonso the setting can be reset individually. - Add the setting to
useSettingsRestore()so "Restore defaults" includes it in the dirty-state list. - Keep descriptions concrete and behavior-oriented.
- For enum settings, add a local
*_LABELSmap inSettingsPanels.tsxand useSelect,SelectTrigger,SelectPopup, andSelectItemrather than ad hoc button groups. - If the setting belongs to a specific feature cluster such as chat/thread presentation, prefer adding or extending a dedicated
SettingsSectionlikeChat Viewinstead of dropping it into an unrelated group.
Common pattern:
- Number input:
- Keep a small local string state for the input if needed.
- Clamp/sanitize on blur or commit.
- Persist through
updateSettings().
- Boolean input:
- Use
Switch.
- Use
4. Consumer wiring
Update the feature that actually uses the setting.
Examples:
- Sidebar behavior:
- Thread/chat behavior:
- Diff behavior:
Prefer pushing reusable behavior into *.logic.ts when it is testable and shared.
Consumer guidance:
- Distinguish between a setting that defines automatic policy and local component state that reflects an explicit user override.
- Example: a chat-view scrolling mode can control what happens automatically while scrolled, while a separate local toggle still lets the user manually hide/show the composer.
- Do not let the setting erase an explicit user action unless that reset is deliberate and obvious.
- If a control needs to remain discoverable across UI states, prefer one persistent location over duplicating the same control in different places.
5. Tests
Add or update focused tests near the touched area.
Usual places:
- apps/web/src/hooks/useSettings.test.ts
- apps/web/src/components/Sidebar.logic.test.ts
- Feature-specific browser or logic tests beside the consumer
Test the rule, not just the UI control.
Additional test guidance:
- For client settings, add a focused
useSettings.test.tscase for the new default and for legacy migration behavior. - For browser-level behavior, seed
t3code:client-settings:v1inlocalStorageusingDEFAULT_CLIENT_SETTINGSplus the overridden key so tests stay resilient to unrelated defaults. - For enum behavior settings, cover every mode at least once in the consumer tests (
hide/show/compact-style matrices).
Good Workflow For New Settings
- Decide client vs server.
- Find the closest existing setting with similar persistence and UX.
- Add schema and default.
- Add migration if needed.
- Add settings panel row and reset/default coverage.
- Wire the consumer.
- Add settings-level tests for default + migration behavior.
- Add consumer tests for the actual behavior.
- Run
bun fmt,bun lint,bun typecheck.
Known Repo Patterns
Provider settings pattern
For provider-backed settings, follow the existing path used by providers.codex,
providers.claudeAgent, and now providers.ollamaLocal.
Read these together:
packages/contracts/src/settings.tsapps/server/src/serverSettings.tsapps/web/src/components/settings/SettingsPanels.tsx- the provider consumer layer, for example
apps/server/src/provider/Layers/OllamaAdapter.ts
What to keep aligned:
- schema defaults and patch shape
- settings-panel controls
- dirty/open-state behavior in the provider card
- provider snapshot or runtime consumer that actually uses the value
- focused server tests for normalization and deep-merge behavior
For server-authoritative provider config, prefer one derived runtime helper when multiple server files need the same normalized value. Example: local-model providers should derive base URL or default model once in a small server helper instead of reassembling strings in every adapter/provider file.
Client setting examples
Look at these for copyable patterns:
confirmThreadArchiveconfirmThreadDeletediffWordWrapchatViewInputWhenScrollingsidebarProjectSortOrdersidebarThreadSortOrder
These are all defined in packages/contracts/src/settings.ts and surfaced in apps/web/src/components/settings/SettingsPanels.tsx.
Enum setting pattern
For a new client enum setting, mirror this full path:
- Add
Schema.Literals([...]), exported type, andDEFAULT_*constant in packages/contracts/src/settings.ts. - Add the field to
ClientSettingsSchemawithSchema.withDecodingDefault(...). - Add legacy migration handling in apps/web/src/hooks/useSettings.ts.
- Add a labels map plus
SelectUI in apps/web/src/components/settings/SettingsPanels.tsx. - Add restore-default dirty tracking in
useSettingsRestore(). - Add both settings-layer tests and consumer behavior tests.
Sidebar-specific settings
For sidebar behavior, inspect:
- apps/web/src/components/Sidebar.tsx
- apps/web/src/components/Sidebar.logic.ts
- apps/web/src/components/Sidebar.logic.test.ts
If the new setting affects visibility, folding, sorting, or derived display state, put the core rule in Sidebar.logic.ts and test it there.
Footguns
- Do not add a setting to the schema and forget
useSettingsRestore(). - Do not add a decoding default in
ClientSettingsSchemaand forget to mirror the fallback inbuildLegacyClientSettingsMigrationPatch(). - Do not add a settings row without a reset path if other comparable rows have one.
- Do not hard-code a magic number in a consumer when it should be configurable.
- Do not use a stringly-typed raw number if a schema helper already exists.
- Do not forget that
DEFAULT_UNIFIED_SETTINGSdrives reset behavior. - Do not forget that browser tests or local-storage-backed tests may seed
DEFAULT_CLIENT_SETTINGS. - Do not collapse policy state and local override state into one variable when the consumer supports both automatic behavior and explicit user toggles.
- Do not duplicate the same control in multiple UI locations unless there is a clear reason; a single persistent location is usually easier to understand and test.
- Do not keep provider-backed endpoint or model configuration hardcoded in an adapter once it is meant to be configurable through settings.
- Do not treat provider settings as "done" if the settings UI exists but the provider snapshot or runtime consumer still ignores the values.
- Do not run
bun test.
If You Are Dropped Into This Codebase Cold
Start here:
- Read packages/contracts/src/settings.ts.
- Read apps/web/src/hooks/useSettings.ts.
- Read apps/web/src/components/settings/SettingsPanels.tsx.
- Read the consumer you are modifying.
- Read the closest existing test file.
If the task is "add a new toggle/number input for X", this is usually enough context to implement it safely.
Completion Standard
Do not consider the task complete until all of these pass:
bun fmtbun lintbun typecheck
If tests are relevant, use bun run test, never bun test.