diff --git a/toju-app/src/app/domains/theme/README.md b/toju-app/src/app/domains/theme/README.md new file mode 100644 index 0000000..84dfdfa --- /dev/null +++ b/toju-app/src/app/domains/theme/README.md @@ -0,0 +1,91 @@ +# Theme Domain + +Manages the theming engine: CSS variable injection, layout grid, theme library persistence, element picker for live editing, and schema validation. Drives the theme settings UI and the `ThemeNodeDirective` used across the app. + +## Module map + +``` +theme/ +├── application/ +│ └── services/ +│ ├── element-picker.service.ts DOM element picker for live theme editing +│ ├── layout-sync.service.ts Syncs grid layout state between editor and theme document +│ ├── theme-library.service.ts Saved-theme CRUD via Electron file system +│ ├── theme-registry.service.ts Wraps static registry/container lookups as signals +│ └── theme.service.ts Core orchestrator: CSS injection, draft/active, validation +│ +├── domain/ +│ ├── constants/ +│ │ └── theme-llm-guide.constants.ts LLM prompt context for AI-assisted theme editing +│ ├── logic/ +│ │ ├── theme-defaults.logic.ts Default theme document, JSON template, legacy detection +│ │ ├── theme-registry.logic.ts Static registry of themeable elements and layout containers +│ │ ├── theme-schema.logic.ts Schema field definitions, animation starters, suggested defaults +│ │ └── theme-validation.logic.ts Theme document validation against registry + schema +│ └── models/ +│ └── theme.model.ts All domain types (ThemeDocument, ThemeElementStyles, etc.) +│ +├── infrastructure/ +│ ├── services/ +│ │ └── theme-library-storage.service.ts Electron bridge for saving/loading theme files +│ └── util/ +│ └── theme-storage.util.ts localStorage read/write for active + draft theme text +│ +├── feature/ +│ ├── settings/ +│ │ ├── theme-grid-editor.component.* Visual grid layout editor +│ │ ├── theme-json-code-editor.component.* CodeMirror JSON editor +│ │ └── theme-settings.component.* Main theme settings page +│ ├── theme-node.directive.ts Applies theme styles + picker interaction to host elements +│ └── theme-picker-overlay.component.* Floating overlay showing picked element info +│ +└── index.ts Barrel exports +``` + +## Layer composition + +```mermaid +graph TD + ThemeService[ThemeService] + Registry[ThemeRegistryService] + Library[ThemeLibraryService] + Layout[LayoutSyncService] + Picker[ElementPickerService] + LibStorage[ThemeLibraryStorageService] + Storage[theme-storage.util] + Defaults[theme-defaults.logic] + Validation[theme-validation.logic] + Schema[theme-schema.logic] + RegistryLogic[theme-registry.logic] + Models[theme.model] + + ThemeService --> Defaults + ThemeService --> Schema + ThemeService --> RegistryLogic + ThemeService --> Validation + ThemeService --> Storage + Library --> LibStorage + Library --> ThemeService + Layout --> ThemeService + Layout --> Registry + Layout --> Defaults + Registry --> RegistryLogic + Picker --> Registry + Validation --> Defaults + Validation --> RegistryLogic + Defaults --> RegistryLogic + Defaults --> Models + + click ThemeService "application/services/theme.service.ts" "Core orchestrator" _blank + click Registry "application/services/theme-registry.service.ts" "Registry wrapper" _blank + click Library "application/services/theme-library.service.ts" "Theme library CRUD" _blank + click Layout "application/services/layout-sync.service.ts" "Grid layout sync" _blank + click Picker "application/services/element-picker.service.ts" "Element picker" _blank + click LibStorage "infrastructure/services/theme-library-storage.service.ts" "Electron file I/O" _blank + click Storage "infrastructure/util/theme-storage.util.ts" "localStorage" _blank + click Defaults "domain/logic/theme-defaults.logic.ts" "Default theme" _blank + click Validation "domain/logic/theme-validation.logic.ts" "Validation" _blank + click Schema "domain/logic/theme-schema.logic.ts" "Schema fields" _blank + click RegistryLogic "domain/logic/theme-registry.logic.ts" "Static registry" _blank + click Models "domain/models/theme.model.ts" "Domain types" _blank +``` diff --git a/toju-app/src/app/domains/theme/application/element-picker.service.ts b/toju-app/src/app/domains/theme/application/services/element-picker.service.ts similarity index 98% rename from toju-app/src/app/domains/theme/application/element-picker.service.ts rename to toju-app/src/app/domains/theme/application/services/element-picker.service.ts index 563b066..4dd04e1 100644 --- a/toju-app/src/app/domains/theme/application/element-picker.service.ts +++ b/toju-app/src/app/domains/theme/application/services/element-picker.service.ts @@ -5,7 +5,7 @@ import { signal } from '@angular/core'; -import { SettingsModalService, type SettingsPage } from '../../../core/services/settings-modal.service'; +import { SettingsModalService, type SettingsPage } from '../../../../core/services/settings-modal.service'; import { ThemeRegistryService } from './theme-registry.service'; @Injectable({ providedIn: 'root' }) diff --git a/toju-app/src/app/domains/theme/application/layout-sync.service.ts b/toju-app/src/app/domains/theme/application/services/layout-sync.service.ts similarity index 93% rename from toju-app/src/app/domains/theme/application/layout-sync.service.ts rename to toju-app/src/app/domains/theme/application/services/layout-sync.service.ts index 462c266..192ca5e 100644 --- a/toju-app/src/app/domains/theme/application/layout-sync.service.ts +++ b/toju-app/src/app/domains/theme/application/services/layout-sync.service.ts @@ -8,8 +8,8 @@ import { ThemeContainerKey, ThemeGridEditorItem, ThemeGridRect -} from '../domain/theme.models'; -import { createDefaultThemeDocument } from '../domain/theme.defaults'; +} from '../../domain/models/theme.model'; +import { createDefaultThemeDocument } from '../../domain/logic/theme-defaults.logic'; import { ThemeRegistryService } from './theme-registry.service'; import { ThemeService } from './theme.service'; diff --git a/toju-app/src/app/domains/theme/application/theme-library.service.ts b/toju-app/src/app/domains/theme/application/services/theme-library.service.ts similarity index 96% rename from toju-app/src/app/domains/theme/application/theme-library.service.ts rename to toju-app/src/app/domains/theme/application/services/theme-library.service.ts index 2eabb28..750d7a6 100644 --- a/toju-app/src/app/domains/theme/application/theme-library.service.ts +++ b/toju-app/src/app/domains/theme/application/services/theme-library.service.ts @@ -4,8 +4,8 @@ import { inject, signal } from '@angular/core'; -import type { SavedThemeSummary } from '../domain/theme.models'; -import { ThemeLibraryStorageService } from '../infrastructure/theme-library.storage'; +import type { SavedThemeSummary } from '../../domain/models/theme.model'; +import { ThemeLibraryStorageService } from '../../infrastructure/services/theme-library-storage.service'; import { ThemeService } from './theme.service'; @Injectable({ providedIn: 'root' }) diff --git a/toju-app/src/app/domains/theme/application/theme-registry.service.ts b/toju-app/src/app/domains/theme/application/services/theme-registry.service.ts similarity index 95% rename from toju-app/src/app/domains/theme/application/theme-registry.service.ts rename to toju-app/src/app/domains/theme/application/services/theme-registry.service.ts index c1e739b..987435f 100644 --- a/toju-app/src/app/domains/theme/application/theme-registry.service.ts +++ b/toju-app/src/app/domains/theme/application/services/theme-registry.service.ts @@ -1,12 +1,12 @@ import { Injectable, signal } from '@angular/core'; -import { ThemeLayoutContainerDefinition, ThemeRegistryEntry } from '../domain/theme.models'; +import { ThemeLayoutContainerDefinition, ThemeRegistryEntry } from '../../domain/models/theme.model'; import { THEME_LAYOUT_CONTAINERS, THEME_REGISTRY, findThemeLayoutContainer, findThemeRegistryEntry -} from '../domain/theme.registry'; +} from '../../domain/logic/theme-registry.logic'; @Injectable({ providedIn: 'root' }) export class ThemeRegistryService { diff --git a/toju-app/src/app/domains/theme/application/theme.service.ts b/toju-app/src/app/domains/theme/application/services/theme.service.ts similarity index 97% rename from toju-app/src/app/domains/theme/application/theme.service.ts rename to toju-app/src/app/domains/theme/application/services/theme.service.ts index ab322e1..1fefe6b 100644 --- a/toju-app/src/app/domains/theme/application/theme.service.ts +++ b/toju-app/src/app/domains/theme/application/services/theme.service.ts @@ -13,20 +13,20 @@ import { ThemeDocument, ThemeElementStyleProperty, ThemeElementStyles -} from '../domain/theme.models'; +} from '../../domain/models/theme.model'; import { DEFAULT_THEME_JSON, createDefaultThemeDocument, isLegacyDefaultThemeDocument -} from '../domain/theme.defaults'; -import { createAnimationStarterDefinition } from '../domain/theme.schema'; -import { findThemeLayoutContainer } from '../domain/theme.registry'; -import { validateThemeDocument } from '../domain/theme.validation'; +} from '../../domain/logic/theme-defaults.logic'; +import { createAnimationStarterDefinition } from '../../domain/logic/theme-schema.logic'; +import { findThemeLayoutContainer } from '../../domain/logic/theme-registry.logic'; +import { validateThemeDocument } from '../../domain/logic/theme-validation.logic'; import { loadThemeStorageSnapshot, saveActiveThemeText, saveDraftThemeText -} from '../infrastructure/theme.storage'; +} from '../../infrastructure/util/theme-storage.util'; function toKebabCase(value: string): string { return value diff --git a/toju-app/src/app/domains/theme/domain/theme-llm-guide.ts b/toju-app/src/app/domains/theme/domain/constants/theme-llm-guide.constants.ts similarity index 97% rename from toju-app/src/app/domains/theme/domain/theme-llm-guide.ts rename to toju-app/src/app/domains/theme/domain/constants/theme-llm-guide.constants.ts index 179a682..27d07dc 100644 --- a/toju-app/src/app/domains/theme/domain/theme-llm-guide.ts +++ b/toju-app/src/app/domains/theme/domain/constants/theme-llm-guide.constants.ts @@ -1,10 +1,10 @@ -import { DEFAULT_THEME_DOCUMENT } from './theme.defaults'; -import { THEME_LAYOUT_CONTAINERS, THEME_REGISTRY } from './theme.registry'; +import { DEFAULT_THEME_DOCUMENT } from '../logic/theme-defaults.logic'; +import { THEME_LAYOUT_CONTAINERS, THEME_REGISTRY } from '../logic/theme-registry.logic'; import { THEME_ANIMATION_FIELDS, THEME_ELEMENT_STYLE_FIELDS, createAnimationStarterDefinition -} from './theme.schema'; +} from '../logic/theme-schema.logic'; function formatExample(example: string | number): string { return typeof example === 'number' diff --git a/toju-app/src/app/domains/theme/domain/theme.defaults.ts b/toju-app/src/app/domains/theme/domain/logic/theme-defaults.logic.ts similarity index 99% rename from toju-app/src/app/domains/theme/domain/theme.defaults.ts rename to toju-app/src/app/domains/theme/domain/logic/theme-defaults.logic.ts index 0110ffb..95b16c1 100644 --- a/toju-app/src/app/domains/theme/domain/theme.defaults.ts +++ b/toju-app/src/app/domains/theme/domain/logic/theme-defaults.logic.ts @@ -2,12 +2,12 @@ import { ThemeDocument, ThemeElementStyles, ThemeLayoutEntry -} from './theme.models'; +} from '../models/theme.model'; import { THEME_LAYOUT_CONTAINERS, THEME_REGISTRY, getLayoutEditableThemeKeys -} from './theme.registry'; +} from './theme-registry.logic'; const APP_ROOT_BASE_GRADIENT = 'radial-gradient(circle at top, ' diff --git a/toju-app/src/app/domains/theme/domain/theme.registry.ts b/toju-app/src/app/domains/theme/domain/logic/theme-registry.logic.ts similarity index 99% rename from toju-app/src/app/domains/theme/domain/theme.registry.ts rename to toju-app/src/app/domains/theme/domain/logic/theme-registry.logic.ts index 6ffa2bc..713ed19 100644 --- a/toju-app/src/app/domains/theme/domain/theme.registry.ts +++ b/toju-app/src/app/domains/theme/domain/logic/theme-registry.logic.ts @@ -1,4 +1,4 @@ -import { ThemeLayoutContainerDefinition, ThemeRegistryEntry } from './theme.models'; +import { ThemeLayoutContainerDefinition, ThemeRegistryEntry } from '../models/theme.model'; export const THEME_LAYOUT_CONTAINERS: readonly ThemeLayoutContainerDefinition[] = [ { diff --git a/toju-app/src/app/domains/theme/domain/theme.schema.ts b/toju-app/src/app/domains/theme/domain/logic/theme-schema.logic.ts similarity index 99% rename from toju-app/src/app/domains/theme/domain/theme.schema.ts rename to toju-app/src/app/domains/theme/domain/logic/theme-schema.logic.ts index ba6d500..db1a608 100644 --- a/toju-app/src/app/domains/theme/domain/theme.schema.ts +++ b/toju-app/src/app/domains/theme/domain/logic/theme-schema.logic.ts @@ -2,7 +2,7 @@ import { ThemeDocument, ThemeElementStyleProperty, ThemeSchemaField -} from './theme.models'; +} from '../models/theme.model'; const RADIAL_GRADIENT_EXAMPLE = 'radial-gradient(circle at top, rgba(255,255,255,0.12), ' diff --git a/toju-app/src/app/domains/theme/domain/theme.validation.ts b/toju-app/src/app/domains/theme/domain/logic/theme-validation.logic.ts similarity index 98% rename from toju-app/src/app/domains/theme/domain/theme.validation.ts rename to toju-app/src/app/domains/theme/domain/logic/theme-validation.logic.ts index d4305c5..933db0e 100644 --- a/toju-app/src/app/domains/theme/domain/theme.validation.ts +++ b/toju-app/src/app/domains/theme/domain/logic/theme-validation.logic.ts @@ -4,13 +4,13 @@ import { ThemeElementStyleProperty, ThemeElementStyles, ThemeValidationResult -} from './theme.models'; -import { createDefaultThemeDocument } from './theme.defaults'; +} from '../models/theme.model'; +import { createDefaultThemeDocument } from './theme-defaults.logic'; import { THEME_LAYOUT_CONTAINERS, THEME_REGISTRY, getLayoutEditableThemeKeys -} from './theme.registry'; +} from './theme-registry.logic'; const TOP_LEVEL_KEYS = [ 'meta', diff --git a/toju-app/src/app/domains/theme/domain/theme.models.ts b/toju-app/src/app/domains/theme/domain/models/theme.model.ts similarity index 100% rename from toju-app/src/app/domains/theme/domain/theme.models.ts rename to toju-app/src/app/domains/theme/domain/models/theme.model.ts diff --git a/toju-app/src/app/domains/theme/feature/settings/theme-grid-editor.component.ts b/toju-app/src/app/domains/theme/feature/settings/theme-grid-editor.component.ts index b24d3d5..5108076 100644 --- a/toju-app/src/app/domains/theme/feature/settings/theme-grid-editor.component.ts +++ b/toju-app/src/app/domains/theme/feature/settings/theme-grid-editor.component.ts @@ -14,7 +14,7 @@ import { ThemeGridEditorItem, ThemeGridRect, ThemeLayoutContainerDefinition -} from '../../domain/theme.models'; +} from '../../domain/models/theme.model'; type DragMode = 'move' | 'resize'; diff --git a/toju-app/src/app/domains/theme/feature/settings/theme-settings.component.ts b/toju-app/src/app/domains/theme/feature/settings/theme-settings.component.ts index 1caa06f..d529d8b 100644 --- a/toju-app/src/app/domains/theme/feature/settings/theme-settings.component.ts +++ b/toju-app/src/app/domains/theme/feature/settings/theme-settings.component.ts @@ -13,19 +13,19 @@ import { ThemeContainerKey, ThemeElementStyleProperty, ThemeRegistryEntry -} from '../../domain/theme.models'; +} from '../../domain/models/theme.model'; import { THEME_ANIMATION_FIELDS as THEME_ANIMATION_FIELD_HINTS, THEME_ELEMENT_STYLE_FIELDS, createAnimationStarterDefinition, getSuggestedFieldDefault -} from '../../domain/theme.schema'; -import { ElementPickerService } from '../../application/element-picker.service'; -import { LayoutSyncService } from '../../application/layout-sync.service'; -import { ThemeLibraryService } from '../../application/theme-library.service'; -import { ThemeRegistryService } from '../../application/theme-registry.service'; -import { ThemeService } from '../../application/theme.service'; -import { THEME_LLM_GUIDE } from '../../domain/theme-llm-guide'; +} from '../../domain/logic/theme-schema.logic'; +import { ElementPickerService } from '../../application/services/element-picker.service'; +import { LayoutSyncService } from '../../application/services/layout-sync.service'; +import { ThemeLibraryService } from '../../application/services/theme-library.service'; +import { ThemeRegistryService } from '../../application/services/theme-registry.service'; +import { ThemeService } from '../../application/services/theme.service'; +import { THEME_LLM_GUIDE } from '../../domain/constants/theme-llm-guide.constants'; import { ThemeGridEditorComponent } from './theme-grid-editor.component'; import { ThemeJsonCodeEditorComponent } from './theme-json-code-editor.component'; diff --git a/toju-app/src/app/domains/theme/feature/theme-node.directive.ts b/toju-app/src/app/domains/theme/feature/theme-node.directive.ts index c1d6c82..0fee665 100644 --- a/toju-app/src/app/domains/theme/feature/theme-node.directive.ts +++ b/toju-app/src/app/domains/theme/feature/theme-node.directive.ts @@ -9,9 +9,9 @@ import { } from '@angular/core'; import { ExternalLinkService } from '../../../core/platform'; -import { ElementPickerService } from '../application/element-picker.service'; -import { ThemeRegistryService } from '../application/theme-registry.service'; -import { ThemeService } from '../application/theme.service'; +import { ElementPickerService } from '../application/services/element-picker.service'; +import { ThemeRegistryService } from '../application/services/theme-registry.service'; +import { ThemeService } from '../application/services/theme.service'; function looksLikeImageReference(value: string): boolean { return value.startsWith('url(') diff --git a/toju-app/src/app/domains/theme/feature/theme-picker-overlay.component.ts b/toju-app/src/app/domains/theme/feature/theme-picker-overlay.component.ts index bc2bd9f..403f070 100644 --- a/toju-app/src/app/domains/theme/feature/theme-picker-overlay.component.ts +++ b/toju-app/src/app/domains/theme/feature/theme-picker-overlay.component.ts @@ -5,8 +5,8 @@ import { } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ElementPickerService } from '../application/element-picker.service'; -import { ThemeRegistryService } from '../application/theme-registry.service'; +import { ElementPickerService } from '../application/services/element-picker.service'; +import { ThemeRegistryService } from '../application/services/theme-registry.service'; @Component({ selector: 'app-theme-picker-overlay', diff --git a/toju-app/src/app/domains/theme/index.ts b/toju-app/src/app/domains/theme/index.ts index ef59e73..45a14e4 100644 --- a/toju-app/src/app/domains/theme/index.ts +++ b/toju-app/src/app/domains/theme/index.ts @@ -1,13 +1,13 @@ -export * from './application/theme.service'; -export * from './application/theme-library.service'; -export * from './application/theme-registry.service'; -export * from './application/element-picker.service'; -export * from './application/layout-sync.service'; -export * from './domain/theme.models'; -export * from './domain/theme.defaults'; -export * from './domain/theme.registry'; -export * from './domain/theme.schema'; -export * from './domain/theme.validation'; +export * from './application/services/theme.service'; +export * from './application/services/theme-library.service'; +export * from './application/services/theme-registry.service'; +export * from './application/services/element-picker.service'; +export * from './application/services/layout-sync.service'; +export * from './domain/models/theme.model'; +export * from './domain/logic/theme-defaults.logic'; +export * from './domain/logic/theme-registry.logic'; +export * from './domain/logic/theme-schema.logic'; +export * from './domain/logic/theme-validation.logic'; export { ThemeNodeDirective } from './feature/theme-node.directive'; export { ThemePickerOverlayComponent } from './feature/theme-picker-overlay.component'; diff --git a/toju-app/src/app/domains/theme/infrastructure/theme-library.storage.ts b/toju-app/src/app/domains/theme/infrastructure/services/theme-library-storage.service.ts similarity index 94% rename from toju-app/src/app/domains/theme/infrastructure/theme-library.storage.ts rename to toju-app/src/app/domains/theme/infrastructure/services/theme-library-storage.service.ts index e12c4b1..7a9a73f 100644 --- a/toju-app/src/app/domains/theme/infrastructure/theme-library.storage.ts +++ b/toju-app/src/app/domains/theme/infrastructure/services/theme-library-storage.service.ts @@ -1,8 +1,8 @@ import { Injectable, inject } from '@angular/core'; -import { ElectronBridgeService } from '../../../core/platform/electron/electron-bridge.service'; -import type { SavedThemeFileDescriptor } from '../../../core/platform/electron/electron-api.models'; -import type { SavedThemeSummary } from '../domain/theme.models'; -import { validateThemeDocument } from '../domain/theme.validation'; +import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service'; +import type { SavedThemeFileDescriptor } from '../../../../core/platform/electron/electron-api.models'; +import type { SavedThemeSummary } from '../../domain/models/theme.model'; +import { validateThemeDocument } from '../../domain/logic/theme-validation.logic'; const THEME_LIBRARY_REQUEST_TIMEOUT_MS = 4000; diff --git a/toju-app/src/app/domains/theme/infrastructure/theme.storage.ts b/toju-app/src/app/domains/theme/infrastructure/util/theme-storage.util.ts similarity index 96% rename from toju-app/src/app/domains/theme/infrastructure/theme.storage.ts rename to toju-app/src/app/domains/theme/infrastructure/util/theme-storage.util.ts index 2eaa855..94beab1 100644 --- a/toju-app/src/app/domains/theme/infrastructure/theme.storage.ts +++ b/toju-app/src/app/domains/theme/infrastructure/util/theme-storage.util.ts @@ -1,4 +1,4 @@ -import { STORAGE_KEY_THEME_ACTIVE, STORAGE_KEY_THEME_DRAFT } from '../../../core/constants'; +import { STORAGE_KEY_THEME_ACTIVE, STORAGE_KEY_THEME_DRAFT } from '../../../../core/constants'; export interface ThemeStorageSnapshot { activeText: string | null;