diff --git a/docs/README.md b/docs/README.md index 2d38f3ea..8d3112d6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,10 @@ Index of Querya Desktop documentation, grouped by audience. ## Planning - [Roadmap](roadmap.md) — current direction and follow-ups. +- [Custom theme parser requirements](scheme-parcer.md) — JSON theme format and scaling spec. +- [Theme parser implementation plan](theme-parser-implementation-tasks.md) — task breakdown and architecture. +- [Theme parser GitHub issues](theme-parser-github-issues.md) — issue templates for epic #96–#125. +- [Marketplace extensions spec](market-tech.md) — extensions manager and marketplace integration. ## Archive diff --git a/docs/market-tech.md b/docs/market-tech.md new file mode 100644 index 00000000..2b1ceb18 --- /dev/null +++ b/docs/market-tech.md @@ -0,0 +1,103 @@ +Это потрясающая новость! Разработка собственного маркетплейса параллельно с клиентом — это переход от создания просто "инструмента" к созданию полноценной экосистемы (как у VS Code или Obsidian). Это невероятно мощный драйвер для роста сообщества и получения звезд на GitHub. + +Чтобы Querya Desktop оставалась легковесной, функционал маркетплейса должен быть реализован архитектурно грамотно: ядро ничего не знает о логике плагинов, оно лишь предоставляет интерфейс (API) для их загрузки и применения. + +Вот подробное техническое задание (ТЗ) на создание менеджера расширений и задел для интеграции с твоим будущим маркетом. +ТЗ 3: Встроенный Менеджер Расширений и Интеграция с Маркетплейсом + +Цель: Создать в интерфейсе Querya Desktop выделенный раздел для управления дополнениями (темами, UI-твиками, коннекторами) и заложить сетевую/файловую архитектуру для связи с внешним API маркетплейса. +1. UI/UX: Раздел «Extensions» (В стиле VS Code) + +В интерфейсе приложения (например, в левом боковом меню) появляется новая иконка (🧩 Пазл). + +Структура раздела: + + Левая панель (Навигация и Поиск): + + Строка поиска (с debounce-задержкой, чтобы не спамить API твоего маркета). + + Вкладки-фильтры: Installed (Установленные), Explore (Поиск по маркету), Updates (Доступные обновления). + + Центральная панель (Список): + + Карточки расширений с использованием компонентов shadcn_flutter. + + На карточке: Иконка, Название, Автор, Рейтинг (⭐), Бейдж типа (Theme, Plugin, Driver) и кнопка Install / Uninstall. + + Правая панель (Детали - Markdown View): + + При клике на карточку справа открывается подробное описание (парсится из README расширения), скриншоты и Changelog. + +2. Архитектура: Задел под Маркетплейс (Сетевой слой) + +В директории lib/core/ необходимо создать новый модуль market/, который будет отвечать за связь с твоим бэкендом. + +Ожидаемые контракты (Интерфейсы для будущего API): +Мобильный/десктопный клиент должен общаться с маркетом через четкие модели данных. Тебе нужно заложить класс ExtensionManifest, который клиент будет ожидать от бэкенда: +Dart + +class ExtensionManifest { + final String id; // e.g., 'reei.cyberpunk-theme' + final String name; // 'Cyberpunk 2077 Theme' + final String type; // 'theme', 'sql-formatter', 'visualizer' + final String version; // '1.0.2' + final String downloadUrl; // Ссылка на .zip или .json в твоем хранилище + final String sha256Checksum; // КРИТИЧНО: Хэш для проверки целостности +} + +Абстракция клиента (MarketplaceClient): +Сделай интерфейс, чтобы сейчас его можно было замокать (Mock), а потом просто подставить реальный HTTP-клиент: + + Future> fetchTrending() + + Future> search(String query) + + Future downloadExtension(String downloadUrl) + +3. Файловая система и Безопасность (Локальный слой) + +Querya Desktop — это клиент базы данных, поэтому безопасность (особенно при скачивании сторонних файлов) — приоритет №1. + + Директории: При старте приложение должно проверять и создавать папки в домашней директории пользователя: + + Linux/macOS: ~/.querya/extensions/themes/ и ~/.querya/extensions/plugins/ + + Windows: %APPDATA%\Querya\extensions\ + + Процесс установки (Флоу): + + Пользователь жмет Install. + + Приложение скачивает файл во временную папку. + + Сверяет sha256 скачанного файла с тем, что отдал API маркета. + + Распаковывает в нужную папку внутри ~/.querya/extensions/. + + Обновляет локальную базу данных SQLite (таблица installed_extensions). + + Изоляция (Sandboxing): На первом этапе (для тем) это просто JSON файлы, они безопасны. Но в ТЗ нужно указать, что исполняемые плагины в будущем должны загружаться как изолированные модули (например, через Dart Isolates или WASM), чтобы плагин не мог украсть креды от БД из ОС. + +4. Стейт-менеджмент (Управление состояниями) + +Для бесшовного опыта нужно создать ExtensionProvider (или использовать Bloc/Riverpod — в зависимости от того, что у вас в lib/core/). + +Отслеживаемые состояния: + + isMarketReachable: Проверка, доступен ли сервер маркета (если нет — показываем только вкладку Installed с заглушкой "Marketplace offline"). + + downloadProgress: Мапа Map для отображения прогресс-баров загрузки на кнопках Install. + + requireRestart: Флаг. Некоторым темам (или сложным плагинам) может потребоваться перезапуск приложения или сброс кэша редактора. Если флаг true, показываем всплывающий Toast (через shadcn_flutter). + +Маркетинговый совет для GitHub (Как использовать маркетплейс для звезд): + +Когда ты сделаешь этот раздел, добавь в README.md красивый бейдж: +[🔌 Querya Extension Market: Live] + +И напиши блок: + + Build your own tools for Querya + Querya Desktop features a built-in Marketplace. Don't like our UI? Download a new theme. Need a specific data visualizer? Write a plugin and publish it to the Querya Market in 5 minutes. + +Как тебе такой план? Если концепция ясна, мы можем углубиться в то, как именно ThemeParser (из предыдущего ТЗ) будет автоматически подхватывать свежескачанные JSON-файлы из папки ~/.querya/extensions/themes/ без перезагрузки приложения! \ No newline at end of file diff --git a/docs/scheme-parcer.md b/docs/scheme-parcer.md new file mode 100644 index 00000000..761a1ce1 --- /dev/null +++ b/docs/scheme-parcer.md @@ -0,0 +1,54 @@ +ТЗ 1: Разработка парсера кастомных JSON-тем + +Цель: Реализовать утилиту, которая динамически считывает .json файлы (например, пресеты cyberpunk ) и конвертирует их в объекты ThemeData (для shadcn_flutter) и ThemeExtension (для уникальных элементов). + +1. Архитектура и расположение + + Локация: Вся логика парсинга должна находиться в lib/core/ (например, lib/core/theme/theme_parser.dart). + + Интеграция: Применение распарсенной темы происходит в lib/app/. + +2. Требования к JSON-структуре +Файл темы должен быть разделен на две логические части: + + shadcn_colors: базовые токены для кнопок, фонов и инпутов (соответствуют палитре shadcn_flutter ). + + editor_colors: кастомные токены для подсветки синтаксиса и сайдбаров (базовых цветов для этого не хватит ). + +3. Функционал парсера + + Десериализация: Чтение JSON и безопасное извлечение строковых значений HEX-цветов (например, #1E1E1E или 1E1E1E). + + Конвертер HEX -> Color: Утилита для преобразования строковых HEX-значений в объекты Color фреймворка Flutter. + + Маппинг: Генерация объекта ColorScheme (для shadcn_flutter) и пользовательского EditorThemeExtension. + +4. Обработка ошибок (Фолбэк) + + Если JSON файл поврежден или отсутствуют обязательные ключи, парсер должен тихо (без краша приложения) откатываться к дефолтной темной теме приложения. + +ТЗ 2: Аудит и масштабирование системы тем (Подготовка к 50+ темам) + +Цель: Обеспечить плавную работу UI, отсутствие утечек памяти и удобный UX при наличии большого количества кастомных тем. + +1. Оптимизация UI выбора тем (Preferences) + + Проблема: Если тем станет много, простой список вызовет проблемы с отрисовкой и перекрытием окна. + + Решение: Выпадающий список выбора темы должен использовать MenuAnchor. Обязательно внедрить жесткое ограничение высоты (например, maxHeight: 300.0) и внутренний скроллбар. + + Предпросмотр (Live Preview): При наведении на название темы в списке (состояние hover ), интерфейс не должен полностью перестраиваться, если тема еще не применена окончательно (избегаем лагов). + +2. Управление состоянием и хранение + + Кэширование: Парсинг JSON-файлов — это ресурсоемкая операция. Распарсенные объекты ThemeData должны кэшироваться в памяти (например, в Map), чтобы повторное переключение происходило мгновенно. + + Персистентность: Сохранять выбранный ID темы (или путь к файлу) необходимо в локальную базу данных SQLite, которая уже используется в проекте для метаданных. + +3. Интеграция с нативными элементами окна + + Синхронизация рамок: Приложение использует bitsdojo_window для отрисовки кастомных заголовков. При смене темы через парсер, цвета кнопок управления окном (свернуть/развернуть/закрыть) и цвет самого заголовка должны динамически перекрашиваться в цвет background новой темы. + +4. Динамическая загрузка из файловой системы + + Необходимо заложить возможность сканирования определенной папки в ОС пользователя (например, ~/.querya/themes/) при старте приложения, чтобы подтягивать не только встроенные themes/samples/, но и скачанные пользователями файлы. \ No newline at end of file diff --git a/docs/theme-parser-github-issues.md b/docs/theme-parser-github-issues.md new file mode 100644 index 00000000..58dc4b9c --- /dev/null +++ b/docs/theme-parser-github-issues.md @@ -0,0 +1,1626 @@ +# GitHub Issues: кастомные JSON-темы и масштабирование theme system + +Источник: [`theme-parser-implementation-tasks.md`](theme-parser-implementation-tasks.md). +Формат ниже рассчитан на перенос в GitHub Issues: каждый блок можно заводить как отдельный issue. + +## Labels + +Рекомендуемые labels: + +- `theme` +- `frontend` +- `performance` +- `parser` +- `settings` +- `docs` +- `tests` +- `good first issue` — только для изолированных docs/fixtures/test задач + +## Milestones / Epics + +- **Epic A — Custom theme parser core** +- **Epic B — Theme registry and caching** +- **Epic C — Preferences theme picker for 50+ themes** +- **Epic D — Built-in themes, filesystem loading, docs** +- **Epic E — Window chrome theme sync** + +## Recommended order + +1. TP-01 → TP-07: parser core. +2. TP-08 → TP-14: registry, persistence, cache. +3. TP-15 → TP-20: Preferences UI and import flow. +4. TP-21 → TP-24: built-in assets, filesystem folder, docs. +5. TP-25 → TP-27: window chrome sync and final hardening. +6. TP-28 → TP-30: QA, regression tests, release checklist. + +--- + +## TP-01 — Document Querya custom theme JSON schema + +**Labels:** `theme`, `docs`, `good first issue` +**Epic:** A +**Depends on:** none + +### Goal + +Create public documentation for the new Querya custom theme JSON format (`querya.theme.v1`) before implementing the parser. + +### Context + +Current `docs/theme-import.md` describes VS Code theme import. The new format must be documented separately so parser behavior is clear and testable. + +### Implementation + +Create `docs/theme-custom-json.md` with: + +- purpose of the Querya custom format; +- required root fields: + - `schema` + - `id` + - `name` + - `type` + - `shadcn_colors` + - `editor_colors` +- optional root fields: + - `tokenColors` + - `description` + - `author` + - `version` +- accepted `type` values: `dark`, `light`; +- accepted color formats: + - `#RRGGBB` + - `RRGGBB` + - `#AARRGGBB` + - `AARRGGBB` + - optionally `#RGB` / `#RGBA` if supported by existing parser; +- fallback rules: + - missing optional colors fallback to `QueryaTheme.darkDefault` / `QueryaTheme.lightDefault`; + - invalid optional color is ignored; + - missing required root field fails parsing; + - broken selected theme falls back to Querya Dark on startup; +- difference between VS Code JSON/JSONC and Querya custom JSON; +- one minimal working example and one full example. + +Update `docs/theme-import.md` with a short link to `docs/theme-custom-json.md`. + +### Acceptance Criteria + +- `docs/theme-custom-json.md` exists. +- It includes a valid copy-pastable JSON example. +- It explicitly says VS Code import remains supported. +- It documents fallback/error behavior. + +### Tests + +Docs only. No automated tests required. + +--- + +## TP-02 — Add custom theme JSON fixtures + +**Labels:** `theme`, `tests`, `good first issue` +**Epic:** A +**Depends on:** TP-01 + +### Goal + +Add stable fixtures for parser and factory tests. + +### Files + +- `test/fixtures/themes/querya_custom_dark.json` +- `test/fixtures/themes/querya_custom_light.json` +- `test/fixtures/themes/querya_custom_minimal.json` +- `test/fixtures/themes/querya_custom_invalid_missing_id.json` +- `test/fixtures/themes/querya_custom_invalid_color.json` +- `test/fixtures/themes/querya_custom_jsonc.jsonc` + +### Implementation + +Add fixtures: + +- `querya_custom_dark.json` + - full dark theme with `shadcn_colors`, `editor_colors`, and sample `tokenColors`; +- `querya_custom_light.json` + - full light theme; +- `querya_custom_minimal.json` + - only required root fields and a small set of colors; +- `querya_custom_invalid_missing_id.json` + - no `id`; +- `querya_custom_invalid_color.json` + - one optional invalid color and enough valid fields to test skip/failure policy; +- `querya_custom_jsonc.jsonc` + - comments and trailing commas. + +### Acceptance Criteria + +- Fixtures are small enough to read in tests. +- Dark/light/minimal fixtures use distinct values so tests can assert mapping. +- Invalid fixtures target one failure mode each. + +### Tests + +No parser tests in this issue. Fixtures are consumed by later issues. + +--- + +## TP-03 — Add `QueryaThemeManifest` model + +**Labels:** `theme`, `parser` +**Epic:** A +**Depends on:** TP-02 + +### Goal + +Create an immutable model for Querya custom theme manifests without converting colors to Flutter `Color` yet. + +### Files + +- `lib/core/theme/parser/querya_theme_manifest.dart` +- `test/core/theme/parser/querya_theme_manifest_test.dart` + +### Implementation + +Add: + +```dart +enum QueryaThemeType { dark, light } + +class QueryaThemeManifest { + const QueryaThemeManifest({ + required this.schema, + required this.id, + required this.name, + required this.type, + required this.shadcnColors, + required this.editorColors, + this.tokenColors = const [], + this.description, + this.author, + this.version, + }); +} + +class QueryaThemeManifestParseException implements Exception { + const QueryaThemeManifestParseException(this.message); + final String message; +} +``` + +Fields: + +- `schema: String` +- `id: String` +- `name: String` +- `type: QueryaThemeType` +- `shadcnColors: Map` +- `editorColors: Map` +- `tokenColors: List` +- optional metadata fields. + +Add `QueryaThemeManifest.fromJsonString(String raw)`. + +Use existing: + +- `stripJsonc` +- `TokenColorRule` / existing token color parser path where possible. + +### Performance Notes + +- Do not create `Color`, `QueryaTheme`, or `ThemeData` here. +- Keep maps unmodifiable. +- Parsing should be pure and synchronous for a single file; async belongs in services. + +### Acceptance Criteria + +- Valid dark/light fixtures parse. +- JSONC fixture parses. +- Missing required fields throw `QueryaThemeManifestParseException`. +- Unknown fields are ignored. +- Returned maps are immutable or safely copied. + +### Tests + +Cover: + +- valid full dark; +- valid full light; +- minimal manifest; +- JSONC comments/trailing commas; +- missing `id`; +- invalid `type`; +- empty `shadcn_colors` / `editor_colors` behavior according to docs. + +--- + +## TP-04 — Add Querya theme color parser wrapper + +**Labels:** `theme`, `parser`, `tests` +**Epic:** A +**Depends on:** TP-03 + +### Goal + +Support Querya custom HEX formats without duplicating incompatible color parsing logic. + +### Files + +- `lib/core/theme/parser/color_parser.dart` +- `test/core/theme/parser/color_parser_test.dart` + +### Implementation + +Add: + +```dart +Color parseQueryaThemeColor(String raw) +``` + +Behavior: + +- trim whitespace; +- accept `#RRGGBB`; +- accept `RRGGBB`; +- accept `#AARRGGBB`; +- accept `AARRGGBB`; +- delegate to `parseVsCodeColor` where possible; +- throw `FormatException` for invalid input. + +If current `parseVsCodeColor` already supports all formats, implement this wrapper as normalization + delegate. + +### Acceptance Criteria + +- Wrapper exists and is used by custom theme factory. +- No second unrelated parser implementation. +- Error messages mention the invalid value. + +### Tests + +Cases: + +- `#1E1E1E` +- `1E1E1E` +- `#FF1E1E1E` +- `FF1E1E1E` +- lowercase hex +- invalid length +- invalid characters +- empty string + +--- + +## TP-05 — Map `shadcn_colors` to `ColorScheme` + +**Labels:** `theme`, `parser` +**Epic:** A +**Depends on:** TP-04 + +### Goal + +Convert custom `shadcn_colors` into `shadcn_flutter.ColorScheme` with fallback values. + +### Files + +- `lib/core/theme/parser/querya_theme_color_scheme.dart` +- `test/core/theme/parser/querya_theme_color_scheme_test.dart` + +### Implementation + +Add pure function: + +```dart +ColorScheme colorSchemeFromQueryaThemeColors({ + required Map colors, + required QueryaTheme fallback, +}); +``` + +Map these keys: + +- `background` +- `foreground` +- `card` +- `cardForeground` +- `popover` +- `popoverForeground` +- `primary` +- `primaryForeground` +- `secondary` +- `secondaryForeground` +- `muted` +- `mutedForeground` +- `accent` +- `accentForeground` +- `destructive` +- `destructiveForeground` +- `border` +- `input` +- `ring` +- `chart1` +- `chart2` +- `chart3` +- `chart4` +- `chart5` + +Fallback: + +- missing key -> fallback `colorScheme` value; +- invalid optional color -> fallback value; +- debug log invalid optional key if useful. + +### Acceptance Criteria + +- Function is pure. +- Missing optional keys preserve fallback. +- Full fixture maps distinct expected values. +- Brightness comes from fallback theme, not from colors map. + +### Tests + +- full map uses custom values; +- missing keys use fallback; +- invalid optional color uses fallback; +- chart colors fallback correctly. + +--- + +## TP-06 — Map `editor_colors` to `QueryaEditorTheme` + +**Labels:** `theme`, `parser` +**Epic:** A +**Depends on:** TP-04 + +### Goal + +Convert custom editor color tokens into `QueryaEditorTheme`. + +### Files + +- `lib/core/theme/parser/querya_editor_theme_from_manifest.dart` +- `test/core/theme/parser/querya_editor_theme_from_manifest_test.dart` + +### Implementation + +Add: + +```dart +QueryaEditorTheme editorThemeFromQueryaColors({ + required Map colors, + required QueryaEditorTheme fallback, +}); +``` + +Support keys matching current `QueryaEditorTheme` fields. At minimum: + +- `background` +- `foreground` +- `selection` +- `lineNumber` +- `bracketMatch` +- `widgetBorder` + +If `QueryaEditorTheme` has additional fields, include them explicitly. + +Fallback: + +- missing/invalid key -> fallback field. + +### Acceptance Criteria + +- Full fixture changes editor background/foreground/selection. +- Minimal fixture falls back for missing fields. +- Invalid optional color does not crash. + +### Tests + +- full custom values; +- fallback behavior; +- invalid optional value. + +--- + +## TP-07 — Map `editor_colors` to `QueryaWorkbenchTheme` + +**Labels:** `theme`, `parser` +**Epic:** A +**Depends on:** TP-04 + +### Goal + +Convert workbench-related custom tokens into `QueryaWorkbenchTheme`. + +### Files + +- `lib/core/theme/parser/querya_workbench_theme_from_manifest.dart` +- `test/core/theme/parser/querya_workbench_theme_from_manifest_test.dart` + +### Implementation + +Add: + +```dart +QueryaWorkbenchTheme workbenchThemeFromQueryaColors({ + required Map colors, + required QueryaWorkbenchTheme fallback, +}); +``` + +Support keys: + +- `sidebarBackground` +- `canvas` +- `surface` +- `editorBackground` +- `mutedForeground` +- `accent` +- `onAccent` +- `borderSubtle` +- `destructive` +- `gitModified` +- `gitUntracked` + +If the docs use `background`, map it deliberately: + +- `background` -> `canvas` and/or `editorBackground` only if explicit in docs. + +### Acceptance Criteria + +- Mapping is documented in code comments or docs table. +- Missing keys use fallback. +- Invalid optional colors use fallback. + +### Tests + +- full custom workbench mapping; +- minimal fallback; +- invalid optional value. + +--- + +## TP-08 — Build `QueryaTheme` from custom manifest + +**Labels:** `theme`, `parser` +**Epic:** A +**Depends on:** TP-05, TP-06, TP-07 + +### Goal + +Provide one factory that converts `QueryaThemeManifest` into the existing app-level `QueryaTheme`. + +### Files + +- `lib/core/theme/parser/querya_theme_from_manifest.dart` +- `test/core/theme/parser/querya_theme_from_manifest_test.dart` + +### Implementation + +Add: + +```dart +QueryaTheme queryaThemeFromManifest(QueryaThemeManifest manifest) +``` + +Algorithm: + +1. Pick fallback: + - dark -> `QueryaTheme.darkDefault` + - light -> `QueryaTheme.lightDefault` +2. Build `ColorScheme` from `shadcn_colors`. +3. Build `QueryaEditorTheme` from `editor_colors`. +4. Build `QueryaWorkbenchTheme` from `editor_colors`. +5. Return `fallback.copyWith(...)`. +6. Preserve `tokenColors`. + +### Acceptance Criteria + +- Full dark fixture creates dark `QueryaTheme`. +- Full light fixture creates light `QueryaTheme`. +- `tokenColors` are preserved. +- No `ThemeData` is created. + +### Tests + +- dark brightness; +- light brightness; +- shadcn color mapping; +- editor/workbench mapping; +- token colors preserved; +- minimal fixture fallback. + +--- + +## TP-09 — Add typed theme load result + +**Labels:** `theme`, `parser`, `error-handling` +**Epic:** A +**Depends on:** TP-08 + +### Goal + +Introduce result types for loading/parsing themes so UI and startup can handle failures without exceptions leaking. + +### Files + +- `lib/core/theme/theme_load_result.dart` + +### Implementation + +Add sealed result: + +```dart +sealed class ThemeLoadResult { + const ThemeLoadResult(); +} + +class ThemeLoadSuccess extends ThemeLoadResult { + const ThemeLoadSuccess({ + required this.definition, + required this.theme, + }); +} + +class ThemeLoadFailure extends ThemeLoadResult { + const ThemeLoadFailure({ + required this.definition, + required this.message, + this.error, + }); +} +``` + +Use this result in later registry APIs. + +### Acceptance Criteria + +- Result can represent success/failure without throwing. +- Failure keeps enough data to show user-facing error and debug logs. + +### Tests + +No direct tests required unless lint coverage demands it. Later registry tests will cover usage. + +--- + +## TP-10 — Add `ThemeDefinition` + +**Labels:** `theme`, `registry` +**Epic:** B +**Depends on:** TP-03 + +### Goal + +Represent lightweight theme metadata for lists/pickers without full parsing. + +### Files + +- `lib/core/theme/theme_definition.dart` +- `test/core/theme/theme_definition_test.dart` + +### Implementation + +Add: + +```dart +enum ThemeSource { builtin, imported, filesystem, legacyImported } +enum ThemeFormat { queryaCustom, vscode } + +class ThemeDefinition { + const ThemeDefinition({ + required this.id, + required this.name, + required this.source, + required this.format, + required this.isDark, + this.path, + this.lastModified, + this.contentHash, + }); +} +``` + +Add helpers: + +- `bool get isFileBacked` +- `String get stableCacheKey` + +### Acceptance Criteria + +- `ThemeDefinition` is immutable. +- `stableCacheKey` changes when `contentHash` changes. +- Does not depend on Flutter widgets. + +### Tests + +- file-backed vs builtin; +- cache key includes id/hash/source; +- equality if implemented. + +--- + +## TP-11 — Add theme paths helper + +**Labels:** `theme`, `filesystem` +**Epic:** B +**Depends on:** TP-10 + +### Goal + +Centralize app theme directories and avoid path logic scattered across services. + +### Files + +- `lib/core/theme/theme_paths.dart` +- `test/core/theme/theme_paths_test.dart` if path provider can be faked easily. + +### Implementation + +Add: + +```dart +abstract final class ThemePaths { + static Future userThemesDirectory(); + static Future importedThemesDirectory(); +} +``` + +Rules: + +- Primary user dir: app support directory + `themes`. +- Imported dir: app support directory + `themes/imported`. +- Optionally expose `legacyDotQueryaThemesDirectory()` for later `~/.querya/themes`. + +### Acceptance Criteria + +- Directories are not created by path getter unless method name says `ensure`. +- Separate `ensureUserThemesDirectory()` can create it. + +### Tests + +- If existing test support fakes path provider, assert paths. +- Otherwise cover through registry tests. + +--- + +## TP-12 — Implement filesystem theme scan + +**Labels:** `theme`, `filesystem`, `performance` +**Epic:** B +**Depends on:** TP-10, TP-11 + +### Goal + +Scan app support theme folder and return lightweight `ThemeDefinition` objects. + +### Files + +- `lib/core/theme/theme_registry_service.dart` +- `test/core/theme/theme_registry_service_test.dart` + +### Implementation + +Add: + +```dart +class ThemeRegistryService { + Future> loadThemeDefinitions(); +} +``` + +For `.json` and `.jsonc` files: + +1. Read file async. +2. Detect format: + - if root `schema == querya.theme.v1` -> custom; + - otherwise try VS Code manifest. +3. Extract only metadata: + - id + - name + - type/isDark + - format + - path + - lastModified + - contentHash +4. Skip broken files from list or return a disabled/error definition. Prefer disabled/error definition if UI should show it later. + +### Performance Notes + +- Do not construct `QueryaTheme`. +- Do not construct `ThemeData`. +- Hash file content once during scan. +- Async file IO only. + +### Acceptance Criteria + +- Valid custom files appear. +- Valid VS Code files appear. +- Broken file does not crash scan. +- Only `.json` / `.jsonc` are considered. + +### Tests + +- temp dir with 2 valid themes and 1 broken; +- stable ordering by name; +- content hash changes when file changes. + +--- + +## TP-13 — Load selected theme by definition + +**Labels:** `theme`, `registry` +**Epic:** B +**Depends on:** TP-12, TP-09 + +### Goal + +Given a `ThemeDefinition`, parse the full theme and return `ThemeLoadResult`. + +### Files + +- `lib/core/theme/theme_registry_service.dart` +- `test/core/theme/theme_registry_service_test.dart` + +### Implementation + +Add: + +```dart +Future loadTheme(ThemeDefinition definition) +``` + +Behavior: + +- `ThemeFormat.queryaCustom` -> `QueryaThemeManifest.fromJsonString` -> `queryaThemeFromManifest`. +- `ThemeFormat.vscode` -> existing `VsCodeThemeManifest` -> existing `queryaThemeFromVsCode`. +- failure -> `ThemeLoadFailure`. +- missing file -> `ThemeLoadFailure`. + +### Acceptance Criteria + +- Custom definition loads to `QueryaTheme`. +- VS Code definition still loads. +- Missing/deleted file returns failure. +- No app crash on parse failure. + +### Tests + +- custom success; +- VS Code success using existing fixture; +- deleted file failure; +- invalid file failure. + +--- + +## TP-14 — Add LRU cache for parsed themes + +**Labels:** `theme`, `performance`, `registry` +**Epic:** B +**Depends on:** TP-13 + +### Goal + +Avoid repeated file reads and parsing when switching between themes. + +### Files + +- `lib/core/theme/theme_registry_service.dart` +- `test/core/theme/theme_registry_cache_test.dart` + +### Implementation + +Inside `ThemeRegistryService`: + +- cache `QueryaTheme` by `definition.stableCacheKey`; +- max entries: 12 or 20; +- on cache hit, return cached theme; +- on content hash change, key changes naturally; +- expose `clearCache()` for tests/reset. + +If current project has no LRU helper, implement tiny private LRU using `LinkedHashMap`. + +### Acceptance Criteria + +- Loading same definition twice parses once. +- Loading changed file parses again. +- Cache evicts oldest entry after limit. + +### Tests + +- fake parser counter or temp file mutation; +- cache hit; +- cache invalidation by hash; +- eviction. + +--- + +## TP-15 — Persist selected theme id/path in AppSettings + +**Labels:** `theme`, `storage` +**Epic:** B +**Depends on:** TP-10 + +### Goal + +Persist selected registry theme across restarts without storing heavy objects. + +### Files + +- `lib/core/storage/app_settings.dart` +- `test/core/storage/app_settings_test.dart` + +### Implementation + +Add keys: + +- `theme_selected_id` +- `theme_selected_source` +- `theme_selected_path` + +Add methods: + +```dart +Future getSelectedThemeId(); +Future setSelectedThemeId(String? id); +Future getSelectedThemeSource(); +Future setSelectedThemeSource(String? source); +Future getSelectedThemePath(); +Future setSelectedThemePath(String? path); +``` + +Keep existing preset/imported settings unchanged. + +### Acceptance Criteria + +- Settings roundtrip. +- Clearing selected theme works. +- No SQL workspace revision bump unless existing theme settings already do that intentionally. + +### Tests + +- id/source/path roundtrip; +- clear values; +- existing theme preset tests still pass. + +--- + +## TP-16 — Migrate legacy imported theme into registry + +**Labels:** `theme`, `migration`, `compatibility` +**Epic:** B +**Depends on:** TP-12, TP-15 + +### Goal + +Users with existing imported VS Code themes should keep them after registry lands. + +### Files + +- `lib/core/theme/theme_registry_service.dart` +- `lib/core/theme/theme_import_service.dart` +- `lib/core/theme/theme_controller.dart` +- tests as needed. + +### Implementation + +During registry load: + +- check existing persisted import path/name/colors; +- if found, add a `ThemeDefinition`: + - `id: legacy-imported` or `imported`; + - `source: legacyImported`; + - `format: vscode`; + - `path: stored import file`; + - `name: importedThemeName ?? "Imported theme"`. + +Do not delete old settings. + +### Acceptance Criteria + +- Existing `QueryaThemePreset.imported` still applies. +- Legacy imported theme appears in new picker. +- Missing legacy file falls back gracefully. + +### Tests + +- fake old imported path -> registry definition exists; +- selected legacy imported theme loads; +- missing old file does not crash. + +--- + +## TP-17 — Integrate registry into ThemeController load + +**Labels:** `theme`, `controller` +**Epic:** B +**Depends on:** TP-13, TP-15, TP-16 + +### Goal + +Make `ThemeController` aware of registry themes while preserving existing presets. + +### Files + +- `lib/core/theme/theme_controller.dart` +- `lib/core/theme/querya_theme_preset.dart` +- tests for theme controller. + +### Implementation + +Add state: + +- `_availableThemes: List` +- `_selectedThemeId: String?` +- `_selectedThemePath: String?` +- `_selectedThemeLoadError: String?` + +Add getters: + +- `availableThemes` +- `selectedThemeId` +- `selectedThemeLoadError` + +Add methods: + +```dart +Future loadAvailableThemes(); +Future setThemeById(String id); +Future previewThemeById(String id); +``` + +Behavior: + +- `load()` first loads existing mode/preset. +- Then load registry definitions async. +- If stored selected id exists, try load it. +- On failure, apply Querya Dark fallback but keep error visible. + +### Performance Notes + +- Do not call `notifyListeners()` for every discovered file. +- Batch registry load and notify once. +- `previewThemeById` must not mutate active app theme. + +### Acceptance Criteria + +- Existing `setPreset()` behavior still works. +- New `setThemeById()` applies registry theme. +- Broken selected theme falls back to Querya Dark. +- `previewThemeById()` returns theme/result without notifying app listeners. + +### Tests + +- old presets still pass; +- select by id persists setting; +- preview does not change `activeTheme`; +- broken selected id fallback. + +--- + +## TP-18 — Add `ThemePickerButton` widget shell + +**Labels:** `theme`, `settings`, `frontend` +**Epic:** C +**Depends on:** TP-10 + +### Goal + +Introduce a dedicated picker UI for many themes instead of overloading a small dropdown. + +### Files + +- `lib/features/settings/theme_picker_button.dart` +- `test/features/settings/theme_picker_button_test.dart` + +### Implementation + +Create widget: + +```dart +class ThemePickerButton extends StatelessWidget { + const ThemePickerButton({ + required this.themes, + required this.selectedThemeId, + required this.onSelected, + this.isLoading = false, + }); +} +``` + +Use: + +- `MenuAnchor`; +- fixed/max popup height 300-360px; +- `Scrollbar`; +- `ListView.builder`; +- row shows: + - name; + - source badge; + - dark/light icon or label. + +### Acceptance Criteria + +- Opens menu with 50+ fake themes without overflow. +- Uses builder list, not `Column(children: themes.map(...))`. +- Does not parse or apply theme during build. + +### Tests + +- pump with 60 definitions; +- open menu; +- visible rows render; +- no exception/overflow in test logs if test harness supports it; +- tap row triggers `onSelected(id)`. + +--- + +## TP-19 — Add search/filter to ThemePickerButton + +**Labels:** `theme`, `settings`, `frontend` +**Epic:** C +**Depends on:** TP-18 + +### Goal + +Make 50+ themes easy to navigate. + +### Files + +- `lib/features/settings/theme_picker_button.dart` +- `test/features/settings/theme_picker_button_test.dart` + +### Implementation + +Inside popup: + +- small search input at top; +- local `TextEditingController`; +- filter by lowercase `name`, `id`, `source`; +- debounce not strictly required for 50 items, but avoid parsing/building themes; +- dispose controller. + +### Acceptance Criteria + +- Typing filters list. +- Empty result shows small message. +- Search does not call `ThemeController.setThemeById`. + +### Tests + +- filter by theme name; +- filter no results; +- clear input restores list. + +--- + +## TP-20 — Add safe preview card without applying theme on hover + +**Labels:** `theme`, `settings`, `performance` +**Epic:** C +**Depends on:** TP-18, TP-17 + +### Goal + +Optional visual preview for hovered/selected theme without rebuilding the whole app. + +### Files + +- `lib/features/settings/theme_preview_card.dart` +- `lib/features/settings/theme_picker_button.dart` +- tests as needed. + +### Implementation + +Add `ThemePreviewCard`: + +- accepts `QueryaTheme` or lightweight preview colors; +- displays: + - background; + - surface; + - primary/accent; + - sample text; + - editor background strip. + +In picker: + +- hover selects preview target id locally; +- debounce 100-150ms before calling `previewThemeById`; +- preview result stored in local state only; +- never call `setThemeById` on hover. + +### Acceptance Criteria + +- Hovering row does not change app theme. +- Preview card updates after debounce. +- Broken preview shows non-blocking error in card. + +### Tests + +- hover/callback does not call `onSelected`; +- preview future resolves and card updates; +- broken preview shows fallback/error. + +--- + +## TP-21 — Wire ThemePickerButton into Preferences + +**Labels:** `theme`, `settings`, `frontend` +**Epic:** C +**Depends on:** TP-17, TP-18 + +### Goal + +Replace or extend current Color preset dropdown with registry-backed theme selection. + +### Files + +- `lib/features/settings/preferences_appearance_section.dart` +- `lib/features/settings/theme_picker_button.dart` + +### Implementation + +In Appearance: + +- keep `Theme mode`; +- replace `Color preset` row with `Theme` row using `ThemePickerButton`; +- include existing Querya Dark/Light as built-in definitions; +- show current imported/registry selected theme; +- call `ThemeController.setThemeById(id)` on select; +- keep old `Import theme…` and `Reset appearance` buttons. + +### Compatibility + +- If registry unavailable/empty, fallback to current preset dropdown behavior or show Querya Dark/Light only. + +### Acceptance Criteria + +- Querya Dark/Light selectable. +- Legacy imported theme selectable if present. +- Selecting registry theme applies immediately. +- Existing reset returns to Querya Dark. + +### Tests + +- widget shows built-in themes; +- selecting theme calls controller hook or fake callback; +- reset remains visible; +- import button remains visible. + +--- + +## TP-22 — Add refresh themes action in Preferences + +**Labels:** `theme`, `settings`, `filesystem` +**Epic:** C +**Depends on:** TP-17, TP-21 + +### Goal + +Let users refresh filesystem themes without restarting the app. + +### Files + +- `lib/features/settings/preferences_appearance_section.dart` +- `lib/core/theme/theme_controller.dart` + +### Implementation + +Add button near import/reset: + +- `Refresh themes` +- calls `ThemeController.loadAvailableThemes()`; +- shows small loading state; +- preserves active theme if still available; +- if active theme changed on disk, optionally reload when selected again, not immediately. + +### Acceptance Criteria + +- Refresh updates list. +- Broken files do not break Preferences. +- Loading state does not block whole dialog. + +### Tests + +- fake controller list changes after refresh; +- button disabled while refreshing. + +--- + +## TP-23 — Import custom themes into user themes directory + +**Labels:** `theme`, `filesystem`, `settings` +**Epic:** D +**Depends on:** TP-12, TP-21 + +### Goal + +Make `Import theme…` add themes to registry instead of only overwriting one `imported.json`. + +### Files + +- `lib/core/theme/theme_registry_service.dart` +- `lib/core/theme/theme_import_service.dart` +- `lib/features/settings/preferences_appearance_section.dart` + +### Implementation + +Add: + +```dart +Future importThemeFile(String sourcePath) +``` + +Behavior: + +- detect Querya custom vs VS Code; +- validate; +- copy into app support themes directory; +- filename should be stable and safe: + - `${id}.json` for custom; + - slugified name for VS Code; +- avoid overwrite: + - if same id/hash exists, reuse; + - if same id different hash, append suffix or replace only after explicit policy; +- return new `ThemeDefinition`. + +### Acceptance Criteria + +- Importing custom theme adds it to picker. +- Importing VS Code theme still works. +- Multiple imported themes can coexist. +- Old single imported flow still works until fully migrated. + +### Tests + +- import custom; +- import VS Code; +- duplicate import same hash; +- duplicate id different content. + +--- + +## TP-24 — Add built-in theme assets + +**Labels:** `theme`, `assets`, `docs` +**Epic:** D +**Depends on:** TP-12 + +### Goal + +Ship built-in sample themes in release builds, not only as repository files. + +### Files + +- `assets/themes/` +- `pubspec.yaml` +- `lib/core/theme/theme_registry_service.dart` +- tests as feasible. + +### Implementation + +Move/copy curated themes to: + +- `assets/themes/cyberpunk-neon.json` +- any other approved built-in themes. + +Update `pubspec.yaml`: + +```yaml +flutter: + assets: + - assets/themes/ +``` + +Registry: + +- load built-in asset manifest; +- create `ThemeDefinition(source: ThemeSource.builtin)`; +- load full theme from asset when selected. + +### Acceptance Criteria + +- Built-in themes show in picker in release/profile builds. +- App does not depend on repo `themes/samples/` path at runtime. +- Existing `themes/samples/` can remain for docs/manual testing. + +### Tests + +- Asset loading if test environment supports bundle. +- Otherwise unit-test parsing with same file content. + +--- + +## TP-25 — Add user theme folder docs and open-folder affordance + +**Labels:** `theme`, `docs`, `settings` +**Epic:** D +**Depends on:** TP-11, TP-21 + +### Goal + +Make filesystem themes discoverable. + +### Files + +- `docs/theme-custom-json.md` +- `docs/theme-import.md` +- `lib/features/settings/preferences_appearance_section.dart` + +### Implementation + +Docs: + +- show actual app support path behavior; +- mention accepted extensions; +- mention refresh/restart. + +UI: + +- show hint text: + - "Themes are loaded from app support themes folder." +- optional button: + - `Open themes folder` + - can be follow-up if cross-platform opening helper does not exist. + +### Acceptance Criteria + +- User can understand where to put downloaded themes. +- UI does not promise watcher/live reload if not implemented. + +### Tests + +Docs only unless adding button. + +--- + +## TP-26 — Sync custom window chrome with active theme + +**Labels:** `theme`, `frontend`, `bitsdojo` +**Epic:** E +**Depends on:** TP-17 + +### Goal + +Ensure title bar / window controls follow custom theme background/canvas. + +### Files + +- `lib/main.dart` +- `lib/features/main_screen/main_screen.dart` +- any title bar/window button widgets. + +### Implementation + +Find where `bitsdojo_window` title area and window controls are styled. + +Use: + +- `QueryaThemeScope.of(context).workbench.canvas` +- `QueryaThemeScope.of(context).workbench.surface` +- `QueryaThemeScope.of(context).workbench.mutedForeground` + +Avoid: + +- direct singleton reads inside deep widgets when inherited theme is available; +- app-wide notify on hover. + +### Acceptance Criteria + +- Switching theme updates title bar background. +- Window buttons remain readable. +- Hover states use theme tokens. + +### Tests + +- Widget test if title bar is testable. +- Otherwise manual smoke checklist in PR body: + - dark; + - light; + - custom dark; + - custom light. + +--- + +## TP-27 — Startup fallback for missing/broken selected theme + +**Labels:** `theme`, `error-handling`, `stability` +**Epic:** E +**Depends on:** TP-17 + +### Goal + +Prevent broken custom themes from breaking app startup. + +### Files + +- `lib/core/theme/theme_controller.dart` +- tests for controller. + +### Implementation + +On `ThemeController.load()`: + +1. Read selected theme id/path. +2. Try registry load. +3. If failure: + - set active theme to Querya Dark; + - keep `selectedThemeLoadError`; + - do not crash; + - do not delete user setting automatically. +4. Preferences can show: + - "Selected theme failed to load. Using Querya Dark." + +### Acceptance Criteria + +- Missing selected file starts app with Querya Dark. +- Invalid selected file starts app with Querya Dark. +- Error visible in Preferences. +- User can choose another theme and clear error. + +### Tests + +- missing file; +- invalid file; +- subsequent valid selection clears error. + +--- + +## TP-28 — Performance test: 50+ themes in picker + +**Labels:** `theme`, `performance`, `tests` +**Epic:** E +**Depends on:** TP-18, TP-21 + +### Goal + +Prevent regression where many themes make Preferences slow or overflow. + +### Files + +- `test/features/settings/theme_picker_button_test.dart` +- maybe `test/features/settings/preferences_appearance_section_test.dart` + +### Implementation + +Create 60 fake `ThemeDefinition` objects. + +Test: + +- picker opens; +- only visible subset is built if measurable; +- no overflow exception; +- scroll to bottom works; +- select last item works. + +If exact build count is hard to assert, assert behavior and no exceptions. + +### Acceptance Criteria + +- Test fails if picker uses unbounded `Column` and overflows. +- Test passes with `ListView.builder`. + +--- + +## TP-29 — End-to-end theme import test + +**Labels:** `theme`, `tests`, `integration` +**Epic:** E +**Depends on:** TP-21, TP-23 + +### Goal + +Cover the full import/select path with a fake filesystem theme. + +### Files + +- `test/features/settings/theme_import_flow_test.dart` + +### Implementation + +Use fake/temp app support path if project test support allows it. + +Flow: + +1. Put custom JSON in temp source. +2. Import through service/controller. +3. Registry list includes it. +4. Select it. +5. `ThemeController.activeTheme` changes expected token. +6. Restart-like reload preserves selection. + +### Acceptance Criteria + +- Custom theme can be imported, selected, and restored. +- Test does not depend on real user home directory. + +--- + +## TP-30 — Release docs and QA checklist for custom themes + +**Labels:** `theme`, `docs`, `qa` +**Epic:** E +**Depends on:** TP-01 through TP-29 + +### Goal + +Prepare the feature for release and manual verification. + +### Files + +- `docs/theme-custom-json.md` +- `docs/theme-import.md` +- `docs/release-checklist.md` +- `CHANGELOG.md` when release branch is prepared. + +### Implementation + +Add QA checklist: + +- import valid custom dark; +- import valid custom light; +- import VS Code JSONC; +- select among 50+ fake themes or test pack; +- restart app and verify selected theme persists; +- delete selected theme file and restart; +- verify fallback + Preferences error; +- verify title bar/window controls colors; +- verify SQL/JSON highlighting still uses tokenColors. + +### Acceptance Criteria + +- Release checklist includes custom theme scenarios. +- Docs include troubleshooting for invalid colors/missing fields. +- CHANGELOG entry can be written from completed issues. + +--- + +## Optional follow-up issues + +These are intentionally out of the first implementation pass. + +### TP-F1 — File watcher for user themes folder + +Use a filesystem watcher to auto-refresh themes after files are added/removed. Keep as follow-up because watchers differ by OS and can introduce lifecycle bugs. + +### TP-F2 — Theme marketplace metadata + +Support metadata fields like preview image, tags, homepage, license. Useful only after custom theme format is stable. + +### TP-F3 — Visual theme editor + +Allow editing theme colors in Preferences and export to `querya.theme.v1`. This is larger than parser/import support. + +### TP-F4 — Remote theme install + +Install theme from URL. Requires network, trust/security decisions, and probably signature/checksum policy. + +## Master checklist + +- [ ] TP-01 docs schema +- [ ] TP-02 fixtures +- [ ] TP-03 manifest model +- [ ] TP-04 color parser wrapper +- [ ] TP-05 shadcn color scheme mapping +- [ ] TP-06 editor theme mapping +- [ ] TP-07 workbench theme mapping +- [ ] TP-08 QueryaTheme factory +- [ ] TP-09 load result types +- [ ] TP-10 ThemeDefinition +- [ ] TP-11 theme paths +- [ ] TP-12 filesystem scan +- [ ] TP-13 load selected definition +- [ ] TP-14 parsed theme cache +- [ ] TP-15 AppSettings selected theme +- [ ] TP-16 legacy imported migration +- [ ] TP-17 ThemeController registry integration +- [ ] TP-18 ThemePickerButton shell +- [ ] TP-19 picker search/filter +- [ ] TP-20 safe preview card +- [ ] TP-21 Preferences integration +- [ ] TP-22 refresh themes action +- [ ] TP-23 multi-theme import +- [ ] TP-24 built-in theme assets +- [ ] TP-25 user theme folder docs +- [ ] TP-26 window chrome sync +- [ ] TP-27 startup fallback +- [ ] TP-28 50+ themes performance test +- [ ] TP-29 end-to-end import test +- [ ] TP-30 release QA docs diff --git a/docs/theme-parser-implementation-tasks.md b/docs/theme-parser-implementation-tasks.md new file mode 100644 index 00000000..ef34773a --- /dev/null +++ b/docs/theme-parser-implementation-tasks.md @@ -0,0 +1,591 @@ +# План реализации парсера кастомных JSON-тем + +Исходное ТЗ: [`scheme-parcer.md`](scheme-parcer.md). +Цель этого документа — разбить работу на маленькие задачи так, чтобы реализация хорошо ложилась на текущую архитектуру Querya и не ухудшала производительность при 50+ темах. + +## Текущее состояние + +В проекте уже есть большая часть инфраструктуры тем: + +- `lib/core/theme/querya_theme.dart` — главный объект темы: `QueryaWorkbenchTheme`, `QueryaEditorTheme`, `ColorScheme`, `tokenColors`. +- `lib/core/theme/theme_controller.dart` — singleton-контроллер темы, кэширует `QueryaTheme`, `ThemeData`, Material theme. +- `lib/core/theme/theme_import_service.dart` — импорт одного VS Code JSON/JSONC файла в app support. +- `lib/core/theme/parser/` — парсинг VS Code colors/tokenColors, JSONC, color parsing. +- `lib/features/settings/preferences_appearance_section.dart` — UI выбора темы и импорта. +- `lib/app/app.dart` — применение темы через `ShadcnApp` и `QueryaThemeScope`. + +Поэтому не нужно создавать параллельную систему `ThemeData` с нуля. Лучше добавить новый слой: **реестр тем + парсер кастомного формата**, который на выходе дает существующий `QueryaTheme`. + +## Целевая архитектура + +```text +themes/*.json / ~/.querya/themes/*.json + | + v +ThemeRegistryService + - сканирует директории + - хранит легкие manifest-метаданные + - лениво парсит выбранную тему + | + v +QueryaThemeManifestParser + - custom Querya JSON + - VS Code JSON/JSONC compatibility + | + v +QueryaThemeFactory + - manifest -> QueryaTheme + - fallback на QueryaTheme.darkDefault/lightDefault + | + v +ThemeController + - selectedThemeId + - cache + - persist в AppSettings/SQLite + | + v +ShadcnApp + QueryaThemeScope + bitsdojo window colors +``` + +## JSON-формат + +Поддержать новый формат с версией схемы, но сохранить совместимость с текущим VS Code import. + +Минимальная структура: + +```json +{ + "schema": "querya.theme.v1", + "id": "cyberpunk-neon", + "name": "Cyberpunk Neon", + "type": "dark", + "shadcn_colors": { + "background": "#09090B", + "foreground": "#F8FAFC", + "card": "#111113", + "cardForeground": "#F8FAFC", + "popover": "#111113", + "popoverForeground": "#F8FAFC", + "primary": "#22D3EE", + "primaryForeground": "#020617", + "secondary": "#18181B", + "secondaryForeground": "#F8FAFC", + "muted": "#18181B", + "mutedForeground": "#94A3B8", + "accent": "#27272A", + "accentForeground": "#F8FAFC", + "destructive": "#EF4444", + "destructiveForeground": "#F8FAFC", + "border": "#27272A", + "input": "#27272A", + "ring": "#22D3EE" + }, + "editor_colors": { + "background": "#09090B", + "foreground": "#E5E7EB", + "selection": "#155E75", + "lineNumber": "#64748B", + "bracketMatch": "#164E63", + "widgetBorder": "#22D3EE", + "sidebarBackground": "#020617", + "surface": "#111113", + "accent": "#22D3EE" + }, + "tokenColors": [] +} +``` + +Правила: + +- `schema`, `id`, `name`, `type` — обязательные. +- `type`: `dark` или `light`. +- Все цвета можно писать как `#RRGGBB`, `RRGGBB`, `#AARRGGBB`, `AARRGGBB`, короткие `#RGB/#RGBA` лучше поддержать только если это уже легко переиспользуется из `parseVsCodeColor`. +- Отсутствующие необязательные ключи добираются из `QueryaTheme.darkDefault` / `QueryaTheme.lightDefault`. +- Неизвестные ключи игнорируются, но в debug можно логировать. +- Поврежденный файл не должен ломать запуск приложения. + +## Мини-задачи + +### 1. Зафиксировать формат и тестовые fixtures + +**Файлы:** + +- `docs/theme-parser-implementation-tasks.md` +- `test/fixtures/themes/querya_custom_dark.json` +- `test/fixtures/themes/querya_custom_light.json` +- `test/fixtures/themes/querya_custom_invalid.json` + +**Что сделать:** + +- Добавить 2 валидных custom JSON темы и 1 битую. +- Описать обязательные/необязательные поля в `docs/theme-import.md` или отдельном `docs/theme-custom-json.md`. +- Явно указать, что текущий VS Code import остается поддержанным. + +**Definition of Done:** + +- Есть fixtures для dark/light/invalid. +- В документации есть пример структуры и fallback-правила. + +### 2. Добавить модели manifest для custom themes + +**Файлы:** + +- `lib/core/theme/parser/querya_theme_manifest.dart` + +**Что сделать:** + +- Создать immutable-модель: + - `QueryaThemeManifest` + - `QueryaThemeType` + - `QueryaThemeParseException` +- Поля: + - `schema` + - `id` + - `name` + - `isDark` + - `shadcnColors: Map` + - `editorColors: Map` + - `tokenColors: List` +- Метод `fromJsonString(String raw)`. +- Для JSONC использовать существующий `stripJsonc`. + +**Производительность:** + +- Не создавать `Color`/`ThemeData` на этапе чтения списка тем. +- Manifest-метаданные должны быть легкими. + +**Definition of Done:** + +- Парсер возвращает manifest без зависимости от Flutter widget layer. +- Ошибки возвращаются контролируемо через exception/result, без краша. + +### 3. Унифицировать HEX parsing + +**Файлы:** + +- `lib/core/theme/parser/color_parser.dart` + +**Что сделать:** + +- Проверить, покрывает ли `parseVsCodeColor` все нужные форматы. +- Если нет — добавить wrapper: + - `parseQueryaThemeColor(String raw)` + - принимает `#RRGGBB`, `RRGGBB`, `#AARRGGBB`, `AARRGGBB` + - нормализует ошибки в `FormatException` +- Не плодить второй несовместимый парсер. + +**Тесты:** + +- `test/core/theme/parser/color_parser_test.dart` +- Валидные и невалидные HEX. + +**Definition of Done:** + +- Все color formats из документации покрыты тестами. +- Invalid color не валит всю тему, если ключ необязательный. + +### 4. Маппинг custom manifest -> QueryaTheme + +**Файлы:** + +- `lib/core/theme/parser/querya_theme_from_manifest.dart` + +**Что сделать:** + +- Реализовать pure-функцию: + +```dart +QueryaTheme queryaThemeFromManifest(QueryaThemeManifest manifest) +``` + +- Базовый fallback: + - `manifest.isDark ? QueryaTheme.darkDefault : QueryaTheme.lightDefault` +- `shadcn_colors` маппить в `ColorScheme`. +- `editor_colors` маппить в: + - `QueryaWorkbenchTheme` + - `QueryaEditorTheme` +- Для пересечения ключей (`background`, `accent`, `border`) выбрать единый источник: + - UI/shadcn берет `shadcn_colors` + - editor/workbench берет `editor_colors` +- `tokenColors` передать в `QueryaTheme.tokenColors`. + +**Важно:** + +- Не возвращать напрямую `ThemeData`. Внутри приложения единый источник истины — `QueryaTheme`, а `ThemeData` создается через `toShadcnThemeData()`. + +**Definition of Done:** + +- Custom manifest можно превратить в `QueryaTheme`. +- Missing optional fields берутся из fallback. +- Required missing fields дают controlled failure. + +### 5. Результаты парсинга и fallback без крашей + +**Файлы:** + +- `lib/core/theme/theme_parse_result.dart` или рядом с сервисом + +**Что сделать:** + +- Ввести result-типы: + - `ThemeLoadSuccess` + - `ThemeLoadFailure` +- Для UI показывать failure message. +- Для старта приложения: + - если выбранная тема сломана/удалена — тихо применить Querya Dark + - сохранить в лог/debug причину + - не перезаписывать пользовательские настройки сразу, чтобы файл можно было восстановить + +**Definition of Done:** + +- Поврежденный JSON не ломает запуск. +- Preferences показывает понятную ошибку при ручном импорте. + +### 6. Реестр тем вместо одного imported.json + +**Файлы:** + +- `lib/core/theme/theme_registry_service.dart` +- `lib/core/theme/theme_definition.dart` + +**Что сделать:** + +- Добавить `ThemeDefinition`: + - `id` + - `name` + - `source` (`builtin`, `imported`, `filesystem`) + - `path` + - `isDark` + - `format` (`queryaCustom`, `vscode`) + - `lastModified` + - `contentHash` +- `ThemeRegistryService.loadThemeDefinitions()`: + - встроенные темы из `themes/samples/` или будущего `assets/themes/` + - persisted imported + - пользовательская папка +- На первом этапе можно не делать asset bundle, а начать с app support + manual import. + +**Производительность:** + +- Сканирование читает только первые KB/manifest, а не строит `ThemeData`. +- Полный парсинг только при выборе/preview. +- Если 50+ файлов, UI получает список `ThemeDefinition`, а не тяжелые темы. + +**Definition of Done:** + +- Можно получить список доступных тем. +- Список не парсит каждую тему полностью. + +### 7. Кэш parsed theme и ThemeData + +**Файлы:** + +- `lib/core/theme/theme_controller.dart` +- `lib/core/theme/theme_registry_service.dart` + +**Что сделать:** + +- Кэшировать минимум: + - `Map _themeCache` + - `Map _shadcnThemeCache` +- Ключ кэша: + - `themeId + contentHash + brightness` +- При изменении файла: + - обновить `contentHash` + - инвалидировать только эту тему. +- Ограничить кэш, например LRU на 12-20 тем. + +**Производительность:** + +- Повторное переключение на уже открытую тему не читает файл и не парсит JSON. +- `ThemeController._invalidateThemeCache()` не должен сбрасывать весь registry без причины. + +**Definition of Done:** + +- Повторный выбор темы мгновенный. +- Тест проверяет, что один и тот же файл не парсится повторно без изменения hash. + +### 8. Persist выбранной темы + +**Файлы:** + +- `lib/core/storage/app_settings.dart` + +**Что сделать:** + +- Добавить настройки: + - `theme_selected_id` + - `theme_selected_source` + - `theme_selected_path` для filesystem themes +- Для совместимости: + - текущий `QueryaThemePreset.imported` продолжает работать + - при наличии old imported theme создать `ThemeDefinition` с id `imported` + +**SQLite vs settings key-value:** + +- Для выбранной темы достаточно текущего key-value слоя `AppSettings`. +- Для списка импортированных тем лучше отдельная таблица позже: + - `theme_id` + - `name` + - `path` + - `format` + - `last_modified` + - `content_hash` + +**Definition of Done:** + +- После перезапуска выбранная тема восстанавливается. +- Старые imported themes не ломаются. + +### 9. Динамическая папка тем + +**Файлы:** + +- `lib/core/theme/theme_registry_service.dart` +- `lib/core/theme/theme_paths.dart` + +**Что сделать:** + +- Определить папку: + - Linux/macOS: `${appSupport}/themes/` + - можно дополнительно поддержать `~/.querya/themes/`, но лучше app support как основной путь. +- Методы: + - `Future userThemesDirectory()` + - `Future> scanThemeFiles()` +- Поддержать расширения: + - `.json` + - `.jsonc` +- Не использовать watcher на первом этапе. Достаточно кнопки `Refresh themes`. + +**Производительность:** + +- Сканировать async. +- Не блокировать startup дольше 50-100ms: если файлов много, загрузить built-in/default сразу, список пользовательских тем догрузить после первого кадра. + +**Definition of Done:** + +- Файлы, добавленные в папку, появляются после refresh/restart. +- Битый файл не ломает список. + +### 10. Preferences UI для 50+ тем + +**Файлы:** + +- `lib/features/settings/preferences_appearance_section.dart` +- `lib/shared/widgets/querya_dropdown.dart` + +**Что сделать:** + +- Текущий `QueryaDropdown` уже построен на `MenuAnchor`, имеет `menuMaxHeight`. +- Для 50+ тем лучше сделать отдельный `ThemePickerButton`: + - trigger показывает текущую тему + - popup max height 300-360px + - `ListView.builder` + - scrollbar + - search/filter по названию + - source badge: Built-in / Imported / File +- Не строить превью каждой темы в списке. +- Для каждой строки использовать только `ThemeDefinition`. + +**Live preview:** + +- Hover не должен применять тему ко всему app. +- Если нужен preview: + - показывать справа маленькую карточку-превью + - парсить тему debounce 100-150ms + - не вызывать `ThemeController.setTheme(...)` на hover +- Полное применение — только click/select. + +**Definition of Done:** + +- 50+ тем открываются без лагов. +- Hover по списку не перестраивает `ShadcnApp`. +- Popup не выходит за экран и скроллится. + +### 11. Интеграция в ThemeController + +**Файлы:** + +- `lib/core/theme/theme_controller.dart` +- `lib/core/theme/querya_theme_preset.dart` + +**Что сделать:** + +- Не раздувать enum preset под каждую тему. +- Добавить понятие `selectedThemeId`. +- Сохранить старые preset-значения: + - `queryaDark` + - `queryaLight` + - `imported` как legacy/single import +- Новый путь: + - `ThemeController.loadAvailableThemes()` + - `ThemeController.setThemeById(String id)` + - `ThemeController.previewThemeById(String id)` только для preview-card, не для app. +- `activeTheme` должен брать тему из cache/registry. + +**Definition of Done:** + +- Старые тесты на presets проходят. +- Новые темы выбираются по id. +- Нет полного reparse при каждом rebuild. + +### 12. Синхронизация bitsdojo_window + +**Файлы:** + +- `lib/main.dart` +- место, где настраивается окно/кнопки bitsdojo +- возможно `lib/features/main_screen/main_screen.dart` + +**Что сделать:** + +- Найти текущую точку отрисовки title bar и window buttons. +- Использовать `QueryaThemeScope.of(context).workbench.canvas/background`. +- Цвет кнопок/hover должен зависеть от текущей темы. +- Не обращаться к `ThemeController.instance.activeTheme` глубоко в виджетах, если можно получить тему из `QueryaThemeScope`. + +**Производительность:** + +- Title bar должен перестраиваться только при смене темы, не при scale preview/обычных workspace state changes. + +**Definition of Done:** + +- При смене темы title bar и кнопки окна меняют цвет. +- На hover кнопок нет лишнего app-wide rebuild. + +### 13. Built-in themes и packaging + +**Файлы:** + +- `themes/samples/` +- возможно `assets/themes/` +- `pubspec.yaml` + +**Что сделать:** + +- Решить, shipped themes — это: + - dev-only samples (`themes/samples/`) + - или bundled assets (`assets/themes/`) для пользователей. +- Для релизной функциональности лучше `assets/themes/`. +- Добавить в `pubspec.yaml` assets: + +```yaml +flutter: + assets: + - assets/themes/ +``` + +- `ThemeRegistryService` должен читать built-in themes через `AssetManifest`. + +**Definition of Done:** + +- В релизной сборке встроенные темы доступны без файловой системы проекта. +- Samples остаются для docs/tests. + +### 14. Тесты парсера и registry + +**Файлы:** + +- `test/core/theme/querya_theme_manifest_test.dart` +- `test/core/theme/querya_theme_from_manifest_test.dart` +- `test/core/theme/theme_registry_service_test.dart` +- `test/features/settings/theme_picker_test.dart` + +**Что покрыть:** + +- Валидный dark custom JSON. +- Валидный light custom JSON. +- Missing optional keys fallback. +- Missing required keys failure. +- Invalid HEX skipped/failure по правилам. +- JSONC comments/trailing commas. +- 50 fake definitions в picker без overflow. +- Cache hit: повторный выбор не вызывает parse повторно. +- Broken persisted selected theme falls back to Querya Dark. + +**Definition of Done:** + +- `flutter analyze` clean. +- `flutter test` green. +- Есть тест на производительный сценарий 50+ themes. + +### 15. Миграция текущего imported theme + +**Что сделать:** + +- При `ThemeController.load()`: + - если есть старые `theme_import_*` настройки — создать legacy `ThemeDefinition`. + - `QueryaThemePreset.imported` продолжает работать. +- Не удалять `ThemeImportService` сразу. +- После внедрения registry можно постепенно заменить `ThemeImportService.importFromPath` на `ThemeRegistryService.importTheme`. + +**Definition of Done:** + +- Пользователь, который уже импортировал VS Code theme, не теряет тему после обновления. + +### 16. Документация для пользователей + +**Файлы:** + +- `docs/theme-import.md` +- новый `docs/theme-custom-json.md` +- `README.md` короткая ссылка при необходимости + +**Что описать:** + +- Куда класть темы. +- Формат custom JSON. +- Отличие VS Code JSON от Querya custom JSON. +- Как работает fallback. +- Как импортировать через Preferences. + +## Рекомендуемый порядок PR + +1. **Parser core only** + - manifest model + - color parser wrapper + - manifest -> QueryaTheme + - fixtures/tests + +2. **Registry + cache** + - `ThemeDefinition` + - scan app support themes + - LRU/cache by hash + - persistence selected id + +3. **Preferences UI** + - theme picker with max height / search / scrollbar + - no app-wide preview on hover + - import/refresh folder actions + +4. **Built-in assets + docs** + - package built-in themes + - docs and samples + +5. **Window chrome sync** + - title bar/window button colors from `QueryaThemeScope` + - focused tests/manual smoke + +## Performance rules + +- Никогда не строить `ThemeData` для всех тем при открытии Preferences. +- Не применять тему на hover. +- Не читать все файлы синхронно в `build()`. +- Не хранить `ThemeData` в SQLite; хранить только id/path/hash. +- Полный parse делать async и только для выбранной/preview темы. +- Кэшировать `QueryaTheme` и `ThemeData`. +- Для 50+ тем UI должен работать на `ThemeDefinition`, а не на parsed theme. +- Любая ошибка файла темы должна превращаться в fallback или UI error, но не в crash. + +## Acceptance checklist + +- [ ] Querya custom JSON импортируется и применяется. +- [ ] VS Code JSON/JSONC import продолжает работать. +- [ ] 50+ тем в Preferences не вызывают overflow и заметные лаги. +- [ ] Hover в списке не перестраивает весь app. +- [ ] Повторное переключение на уже открытую тему мгновенное. +- [ ] Сломанная выбранная тема не ломает запуск приложения. +- [ ] Выбранная тема сохраняется после рестарта. +- [ ] Window title bar синхронизирован с background/canvas темы. +- [ ] `flutter analyze` clean. +- [ ] `flutter test` green.