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

1""" 

2Theme Schema - Data structures for theme metadata. 

3""" 

4 

5from dataclasses import dataclass 

6from pathlib import Path 

7from typing import Literal 

8 

9 

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] 

26 

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] 

40 

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] 

57 

58# Valid theme groups 

59ThemeGroup = Literal["core", "nature", "dev", "research", "system", "other"] 

60 

61# Valid theme types (for luminance classification) 

62ThemeType = Literal["dark", "light"] 

63 

64 

65@dataclass 

66class ThemeMetadata: 

67 """Theme metadata extracted from CSS frontmatter.""" 

68 

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 ) 

79 

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 } 

88 

89 

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}