| 1 | # ADR 0028: Пользовательские настройки — `settings.toml`, каталог `%LocalAppData%\CascadeIDE\`, секреты отдельно |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-04-08 |
| 5 | **Обновлено:** 2026-04-13 — LSP-пресеты в `settings.toml` ([0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md)). Подробности — [§ История](#adr0028-history). |
| 6 | |
| 7 | ## Связанные ADR |
| 8 | |
| 9 | | ADR | Роль | |
| 10 | |-----|------| |
| 11 | | [0010](0010-ui-modes-toml-configuration.md) | TOML для **бандла режимов** и **репозиторного** `workspace.toml` — другой слой, не путать с пользовательским файлом | |
| 12 | | [0013](0013-command-surface-and-discoverability.md) | `hotkeys.toml` рядом с `settings.toml` — задумано, не обязательно реализовано | |
| 13 | | [0026](0026-markdown-preview-surfaces-and-placement.md) | часть пресетов в **merged** `workspace.toml`; пользовательский override размещения — не только `settings.toml` | |
| 14 | | [0027](0027-small-team-focus-vs-public-maturity.md) | предсказуемые пути конфигурации | |
| 15 | | [0029](0029-configuration-toml-canonical-ui-facade.md) | TOML как канон; UI — фасад над тем же файлом | |
| 16 | | [0017](0017-multi-window-workspace-and-agent-surfaces.md) | `presentation` — прежде всего **`settings.toml`**, не репо | |
| 17 | | [0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md) | C#/Markdown LSP: пресеты, опциональные ключи `executable`/`arguments`, опционально окружение | |
| 18 | |
| 19 | ### Снимок реализации |
| 20 | |
| 21 | | Элемент | Значение | |
| 22 | |---------|----------| |
| 23 | | Сервис | `SettingsService` | |
| 24 | | Каталог | `%LocalAppData%\CascadeIDE\` | |
| 25 | | Настройки | `settings.toml` (Tomlyn, snake_case) | |
| 26 | | Секреты | `ai-keys.toml` (отдельно от настроек) | |
| 27 | |
| 28 | ## Резюме |
| 29 | |
| 30 | - Пользовательский канон: **`%LocalAppData%\CascadeIDE\settings.toml`** (Tomlyn). |
| 31 | - Секреты — **`ai-keys.toml`**, не в основном файле настроек. |
| 32 | - Отдельно от бандла `UiModes/` и merge `.cascade/workspace.toml` ([0010](0010-ui-modes-toml-configuration.md)). |
| 33 | - LSP-пресеты — [0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md). |
| 34 | |
| 35 | --- |
| 36 | ## Контекст |
| 37 | |
| 38 | В продукте уже есть **несколько слоёв** конфигурации: шипнутый бандл `UiModes/`, merge с `.cascade/workspace.toml` в открытом репозитории ([0010](0010-ui-modes-toml-configuration.md)), глобальные пользовательские предпочтения (AI, MCP, LSP, видимость панелей, локаль UI и т.д.). При этом **отдельного ADR**, который фиксирует **именно пользовательский** канал (путь на диске, формат, что куда кладём), не было — ссылки размазаны по [0010](0010-ui-modes-toml-configuration.md), [0015](0015-editor-toml-syntax-highlighting.md), UX-докам. |
| 39 | |
| 40 | Нужна **одна точка канона**: где лежит «мой компьютер», что в TOML, что нельзя класть в TOML, как соотносится с репозиторным слоем. |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## Решение |
| 45 | |
| 46 | ### 1. Каталог пользовательских данных CascadeIDE |
| 47 | |
| 48 | - **Базовый путь:** `%LocalAppData%\CascadeIDE\` (через `Environment.SpecialFolder.LocalApplicationData` + сегмент `CascadeIDE`). |
| 49 | - Каталог **создаётся** при первом обращении, если отсутствует (`SettingsService.GetSettingsDirectory()`). |
| 50 | - Этот каталог — **не** каталог решения и **не** `AppContext.BaseDirectory`; он **привязан к пользователю Windows** и установке приложения на машине. |
| 51 | |
| 52 | ### 2. Основной файл настроек: `settings.toml` |
| 53 | |
| 54 | - **Полный путь:** `%LocalAppData%\CascadeIDE\settings.toml`. |
| 55 | - **Модель:** `CascadeIdeSettings` — единственный источник правды по **набору полей** и смыслам (AI-провайдеры, MCP, LSP C#/Markdown, Kroki, видимость панелей, `UiMode`, локаль UI и т.д.). |
| 56 | - **Сериализация:** Tomlyn через `CascadeTomlSerializer`: ключи в файле — **snake_case**, свойства C# — PascalCase (`TomlSerializerOptions` с `JsonNamingPolicy.SnakeCaseLower`). |
| 57 | - **Загрузка:** при старте `SettingsService.Load()`; при ошибке чтения/парсинга — **модель по умолчанию** из кода, без падения IDE. |
| 58 | - **Сохранение:** `SettingsService.Save(CascadeIdeSettings)` — перезапись целого файла; ошибки записи **глотаются** (текущая политика реализации — не блокировать UI). |
| 59 | |
| 60 | **До публичного релиза:** не наращивать в `SettingsService` автоматические миграции при переименовании/переносе ключей TOML — см. подраздел **«До публичного релиза»** в [корневом README](../../README.md#до-публичного-релиза). После появления массовых установок — отдельное решение (версия файла, одноразовый мигратор, changelog). |
| 61 | |
| 62 | ### 3. Исторически: JSON больше не поддерживается |
| 63 | |
| 64 | - Ранее в коде была однократная миграция **`settings.json` → `settings.toml`**; по состоянию на принятие этого ADR **поддерживаемых старых установок нет**, ветка миграции **удалена** из `SettingsService.Load()`. |
| 65 | - **Канон** — только **`settings.toml`**; при отсутствии файла — merge заводских дефолтов из встроенного **`Settings/defaults-settings.toml`** (EmbeddedResource), затем пользовательский overlay. Пользовательский **`settings.json`** в `%LocalAppData%\CascadeIDE\` **не** читается (если файл остался вручную — его нужно перенести в TOML самостоятельно или удалить). |
| 66 | |
| 67 | ### 4. Секреты: не в `settings.toml` |
| 68 | |
| 69 | - **API-ключи** (Anthropic, OpenAI, DeepSeek и т.п.) хранятся в **`%LocalAppData%\CascadeIDE\ai-keys.toml`**, отдельная модель `AiKeys`, сериализация через **`CascadeTomlSerializer`** (как у `settings.toml`: ключи в файле — **snake_case**), `AiKeysStorage.Load` / `Save`. |
| 70 | - **Причины разделения:** не светить секреты в том же файле, что удобно копировать/показывать в логах; проще политика бэкапа и `.gitignore` для пользователя; соответствует направлению [0013](0013-command-surface-and-discoverability.md) (не смешивать всё в одном «комке»). Формат **TOML**, а не отдельный JSON — один стек сериализации с пользовательскими настройками. |
| 71 | - **Не коммитить** `ai-keys.toml`; в документации для разработчиков предполагается только локальная машина. Файл **`ai-keys.json`** не читается (если остался руками — перенести в TOML или удалить). |
| 72 | |
| 73 | ### 5. Что этот ADR *не* описывает (явное разграничение с [0010](0010-ui-modes-toml-configuration.md)) |
| 74 | |
| 75 | | Слой | Где | Назначение | |
| 76 | |------|-----|------------| |
| 77 | | Пользователь | `%LocalAppData%\CascadeIDE\settings.toml` | Предпочтения **пользователя** на этой машине (модель `CascadeIdeSettings`). | |
| 78 | | Бандл приложения | `UiModes/` рядом с exe | Режимы UI, индекс, **шипнутый** `workspace.toml` бандла. | |
| 79 | | Репозиторий | `<solution>/.cascade/workspace.toml` | Overlay команды/проекта поверх бандла (merge в [0010](0010-ui-modes-toml-configuration.md)). | |
| 80 | |
| 81 | #### Имя `workspace.toml`: один контракт merge, не «два смысла» — и где файла нет |
| 82 | |
| 83 | - **Хром и пресеты режимов ([0010](0010-ui-modes-toml-configuration.md)):** одна модель данных (`UiWorkspaceToml`), два **источника** одного имени файла — шипнутый **`UiModes/workspace.toml`** и при открытом решении опционально **`<repo>/.cascade/workspace.toml`**. Это **цепочка merge** (бандл → overlay репо), а не два разных продукта с одним именем. |
| 84 | - **`%LocalAppData%\CascadeIDE\`:** файла **`workspace.toml` нет** и в текущем каноне **не задуман** — глобальные пользовательские настройки живут в **`settings.toml`**. Типичная путаница при чтении доков: искать «user workspace» рядом с `settings.toml` под тем же именем. |
| 85 | |
| 86 | **Переименование** `workspace.toml` в бандле/репо (например в `ui-workspace.toml`) **не делаем** только ради уникальности имени на диске: затронет сборку, merge, ключи в [0026](0026-markdown-preview-surfaces-and-placement.md), примеры и ожидания «workspace = раскладка IDE». Если когда-то понадобится — **отдельный ADR** + миграция путей и версии бандла. |
| 87 | |
| 88 | Состояние, привязанное к **конкретному решению** (например выбранный стартовый проект для отладки: `Services/StartupProjectStore` → `<каталог .sln>/.cascade-ide/startup-project.json`), **не** лежит в `%LocalAppData%\CascadeIDE\` — это отдельный канал «рядом с репо», не глобальные пользовательские настройки. |
| 89 | |
| 90 | ### 6. Будущие файлы в том же каталоге |
| 91 | |
| 92 | - **`hotkeys.toml`** — по намерению [0013](0013-command-surface-and-discoverability.md), рядом с `settings.toml`, только переопределения; до реализации этот ADR не обязует их наличие. |
| 93 | - Раскладка по **физическим дисплеям** (`presentation` / `zone_screen_layout` и токены грамматики по [0017](0017-multi-window-workspace-and-agent-surfaces.md)) — **персональный** слой, **прежде всего** поля в **`settings.toml`**, а не обязательство команды через репозиторный `workspace.toml` ([0017 § слой хранения](0017-multi-window-workspace-and-agent-surfaces.md#adr0017-presentation-grammar)). |
| 94 | - Дополнительные пользовательские файлы (например состояние окон по [0017](0017-multi-window-workspace-and-agent-surfaces.md)) — **отдельным ADR** при появлении договорённости, с явной привязкой к этому каталогу или к новому ключу в `settings.toml`. |
| 95 | |
| 96 | --- |
| 97 | |
| 98 | ## Последствия |
| 99 | |
| 100 | - Документация и агенты: при слове «настройки пользователя» — путь к **`settings.toml`** и модель **`CascadeIdeSettings`**; при «секретах API» — **`ai-keys.toml`**. |
| 101 | - Новые поля в пользовательских настройках добавляются в **`CascadeIdeSettings`** + при необходимости UI/сохранение; при смене смысла слоя — **обновлять этот ADR** или ссылаться на новый. |
| 102 | - Тесты могут подменять загрузку через существующие механизмы или экземпляры модели без записи на диск — без изменения канона путей. |
| 103 | |
| 104 | --- |
| 105 | |
| 106 | ## Отклонённые альтернативы |
| 107 | |
| 108 | - **Держать пользовательские настройки в JSON как основной формат** — отклонено: канон — **TOML**; автоматическая миграция с `settings.json` **снята** с кода при отсутствии поддерживаемых legacy-профилей. |
| 109 | - **Хранить API-ключи в `settings.toml`** — отклонено: смешение секретов с переносимым/редактируемым конфигом и риск утечки при обмене файлами. |
| 110 | - **Держать секреты в отдельном JSON (`ai-keys.json`)** — отклонено в пользу **`ai-keys.toml`**: единый формат с `settings.toml` и тот же `CascadeTomlSerializer`; отдельный файл по-прежнему изолирует секреты от «обычного» конфига. |
| 111 | - **Один ADR на все TOML** — отклонено: [0010](0010-ui-modes-toml-configuration.md) остаётся про режимы и merge; пользовательский файл — отдельный контракт пути и содержимого. |
| 112 | - **Переименовать `workspace.toml` в бандле/репо только из‑за совпадения имени с ожиданиями** — отклонено без отдельного ADR и миграции (см. §5.1). |
| 113 | |
| 114 | --- |
| 115 | |
| 116 | ## История изменений |
| 117 | |
| 118 | <a id="adr0028-history"></a> |
| 119 | |
| 120 | | Дата | Изменение | |
| 121 | |------|-----------| |
| 122 | | 2026-04-08 | Канон: `settings.toml`, каталог LocalAppData; `workspace.toml` в merge-слое ([0010](0010-ui-modes-toml-configuration.md)). | |
| 123 | | 2026-04-08 | Удалена миграция `settings.json` → TOML; legacy не поддерживается. | |
| 124 | | 2026-04-08 | Секреты: `ai-keys.toml` вместо JSON; миграции с JSON нет. | |
| 125 | | 2026-04-11 | До публичного релиза — без auto-migrate схемы ([README § До публичного релиза](../../README.md#до-публичного-релиза)). | |
| 126 | | 2026-04-11 | `presentation` → [0017](0017-multi-window-workspace-and-agent-surfaces.md). | |
| 127 | | 2026-04-13 | LSP в `settings.toml` → [0040](0040-lsp-launch-line-settings-toml-presets-and-environment.md). | |
| 128 | |