Coverage for src/local_deep_research/web/themes/schema.py: 100%
17 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
1"""
2Theme Schema - Data structures for theme metadata.
3"""
5from dataclasses import dataclass
6from pathlib import Path
7from typing import Literal
10# Required CSS variables that every theme must define
11REQUIRED_VARIABLES: list[str] = [
12 "--bg-primary",
13 "--bg-secondary",
14 "--bg-tertiary",
15 "--accent-primary",
16 "--accent-secondary",
17 "--accent-tertiary",
18 "--text-primary",
19 "--text-secondary",
20 "--text-muted",
21 "--border-color",
22 "--success-color",
23 "--warning-color",
24 "--error-color",
25]
27# Light palettes darken the status colors so they read as text on pale
28# surfaces, so each must also say which ink sits on a filled accent, a filled
29# warning, a filled error and a filled success, and how strong an input edge
30# is. Dark
31# palettes inherit the styles.css defaults (the shipped ones also set
32# --text-on-accent, since white is too faint on their light accents).
33LIGHT_THEME_REQUIRED_VARIABLES: list[str] = [
34 "--control-border",
35 "--text-on-accent",
36 "--text-on-warning",
37 "--text-on-error",
38 "--text-on-success",
39]
41# RGB variants for rgba() usage
42REQUIRED_RGB_VARIANTS: list[str] = [
43 "--bg-primary-rgb",
44 "--bg-secondary-rgb",
45 "--bg-tertiary-rgb",
46 "--accent-primary-rgb",
47 "--accent-secondary-rgb",
48 "--accent-tertiary-rgb",
49 "--text-primary-rgb",
50 "--text-secondary-rgb",
51 "--text-muted-rgb",
52 "--border-color-rgb",
53 "--success-color-rgb",
54 "--warning-color-rgb",
55 "--error-color-rgb",
56]
58# Valid theme groups
59ThemeGroup = Literal["core", "nature", "dev", "research", "system", "other"]
61# Valid theme types (for luminance classification)
62ThemeType = Literal["dark", "light"]
65@dataclass
66class ThemeMetadata:
67 """Theme metadata extracted from CSS frontmatter."""
69 id: str # kebab-case identifier (e.g., "nord", "dracula")
70 label: str # Display label for UI (e.g., "Nord", "Dracula")
71 icon: str # FontAwesome icon class (e.g., "fa-snowflake")
72 group: ThemeGroup # Category for grouping in UI
73 type: ThemeType = "dark" # dark/light for luminance classification
74 description: str = "" # Optional description
75 author: str = "" # Optional author attribution
76 css_path: Path | None = (
77 None # Path to CSS file (None for virtual themes like "system")
78 )
80 def to_dict(self) -> dict:
81 """Convert to dictionary for JSON serialization."""
82 return {
83 "label": self.label,
84 "icon": self.icon,
85 "group": self.group,
86 "type": self.type,
87 }
90# Group labels for UI display
91GROUP_LABELS: dict[str, str] = {
92 "core": "Core Themes",
93 "nature": "Nature",
94 "dev": "Developer Themes",
95 "research": "Research & Reading",
96 "system": "System",
97 "other": "Other",
98}