# 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 (**shipped in 0.4.2**).
**Shipped in 0.4.3** ([milestone](https://github.com/QueryaHub/Querya-Desktop/milestone/2), epic **#159**) see [planned-0.4.3.md](planned-0.4.3.md).
### TP-F1 File watcher for user themes folder ([#160](https://github.com/QueryaHub/Querya-Desktop/issues/160))
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 ([#161](https://github.com/QueryaHub/Querya-Desktop/issues/161))
Support metadata fields like preview image, tags, homepage, license. Useful only after custom theme format is stable.
### TP-F3 Visual theme editor ([#162](https://github.com/QueryaHub/Querya-Desktop/issues/162))
Allow editing theme colors in Preferences and export to `querya.theme.v1`. This is larger than parser/import support.
### TP-F4 Remote theme install ([#163](https://github.com/QueryaHub/Querya-Desktop/issues/163))
Install theme from URL. Requires network, trust/security decisions, and probably signature/checksum policy.
## Master checklist
- [x] TP-01 docs schema
- [x] TP-02 fixtures
- [x] TP-03 manifest model
- [x] TP-04 color parser wrapper
- [x] TP-05 shadcn color scheme mapping
- [x] TP-06 editor theme mapping
- [x] TP-07 workbench theme mapping
- [x] TP-08 QueryaTheme factory
- [x] TP-09 load result types
- [x] TP-10 ThemeDefinition
- [x] TP-11 theme paths
- [x] TP-12 filesystem scan
- [x] TP-13 load selected definition
- [x] TP-14 parsed theme cache
- [x] TP-15 AppSettings selected theme
- [x] TP-16 legacy imported migration
- [x] TP-17 ThemeController registry integration
- [x] TP-18 ThemePickerButton shell
- [x] TP-19 picker search/filter
- [x] TP-20 safe preview card
- [x] TP-21 Preferences integration
- [x] TP-22 refresh themes action
- [x] TP-23 multi-theme import
- [x] TP-24 built-in theme assets
- [x] TP-25 user theme folder docs
- [x] TP-26 window chrome sync
- [x] TP-27 startup fallback
- [x] TP-28 50+ themes performance test
- [x] TP-29 end-to-end import test
- [x] TP-30 release QA docs