Skip to main content

coding_agent_search/html_export/
mod.rs

1//! HTML export module for self-contained session exports.
2//!
3//! This module generates standalone HTML files from coding agent session transcripts.
4//! The exported files are:
5//! - **Self-contained**: All critical CSS/JS inlined for offline operation
6//! - **Progressive enhancement**: CDN resources enhance but don't break offline view
7//! - **Encrypted (optional)**: Web Crypto compatible encryption for sensitive content
8//! - **Accessible**: Semantic HTML with proper ARIA attributes
9//!
10//! # Architecture
11//!
12//! ```text
13//! html_export/
14//! ├── mod.rs           # Module facade (this file)
15//! ├── template.rs      # Core HTML template generation
16//! ├── styles.rs        # CSS (critical inline + Tailwind CDN fallback)
17//! ├── scripts.rs       # JS (decryption, search, theme toggle)
18//! ├── renderer.rs      # Conversation -> HTML rendering
19//! ├── filename.rs      # Smart filename generation
20//! └── encryption.rs    # Web Crypto compatible encryption
21//! ```
22//!
23//! # Usage
24//!
25//! ```rust,ignore
26//! use cass::html_export::{HtmlExporter, ExportOptions};
27//!
28//! let exporter = HtmlExporter::new();
29//! let html = exporter.export_session(&session, ExportOptions::default())?;
30//! std::fs::write("session.html", html)?;
31//! ```
32
33mod encryption;
34mod filename;
35mod renderer;
36mod scripts;
37mod styles;
38mod template;
39
40// Re-export public API
41pub use encryption::{EncryptedContent, EncryptionError, EncryptionParams, encrypt_content};
42pub use filename::{
43    FilenameMetadata, FilenameOptions, agent_slug, datetime_slug, extract_topic, generate_filename,
44    generate_filepath, generate_full_filename, get_downloads_dir, is_valid_filename,
45    normalize_topic, unique_filename, workspace_slug,
46};
47pub use renderer::{
48    Message, MessageGroup, MessageGroupType, RenderError, RenderOptions, ToolCall,
49    ToolCallWithResult, ToolResult, ToolStatus, agent_css_class, agent_display_name,
50    render_message, render_message_groups,
51};
52pub use scripts::{ScriptBundle, generate_scripts};
53pub use styles::{StyleBundle, generate_styles};
54pub use template::{ExportOptions, HtmlExporter, HtmlTemplate, TemplateError, TemplateMetadata};
55
56/// Color palette matching TUI theme.rs for visual consistency.
57///
58/// These CSS custom properties are injected into the HTML template,
59/// ensuring exported files match the TUI aesthetics.
60pub mod colors {
61    /// Deep background - primary canvas color (#1a1b26)
62    pub const BG_DEEP: &str = "#1a1b26";
63
64    /// Elevated surface - cards, modals, popups (#24283b)
65    pub const BG_SURFACE: &str = "#24283b";
66
67    /// Subtle surface - hover states, selected items (#292e42)
68    pub const BG_HIGHLIGHT: &str = "#292e42";
69
70    /// Border color - subtle separators (#3b4261)
71    pub const BORDER: &str = "#3b4261";
72
73    /// Border accent - focused/active elements (#7d91c8)
74    pub const BORDER_FOCUS: &str = "#7d91c8";
75
76    /// Primary text - headings, important content (#c0caf5)
77    pub const TEXT_PRIMARY: &str = "#c0caf5";
78
79    /// Secondary text - body content (#a9b1d6)
80    pub const TEXT_SECONDARY: &str = "#a9b1d6";
81
82    /// Muted text - hints, placeholders, timestamps (#696e9e)
83    pub const TEXT_MUTED: &str = "#696e9e";
84
85    /// Disabled/inactive text (#444b6a)
86    pub const TEXT_DISABLED: &str = "#444b6a";
87
88    /// Primary accent - main actions, links (#7aa2f7)
89    pub const ACCENT_PRIMARY: &str = "#7aa2f7";
90
91    /// Secondary accent - complementary highlights (#bb9af7)
92    pub const ACCENT_SECONDARY: &str = "#bb9af7";
93
94    /// Tertiary accent - subtle highlights (#7dcfff)
95    pub const ACCENT_TERTIARY: &str = "#7dcfff";
96
97    /// User messages - soft sage green (#9ece6a)
98    pub const ROLE_USER: &str = "#9ece6a";
99
100    /// Agent/Assistant messages - primary accent (#7aa2f7)
101    pub const ROLE_AGENT: &str = "#7aa2f7";
102
103    /// Tool invocations - warm peach (#ff9e64)
104    pub const ROLE_TOOL: &str = "#ff9e64";
105
106    /// System messages - soft amber (#e0af68)
107    pub const ROLE_SYSTEM: &str = "#e0af68";
108
109    /// Success states (#73daca)
110    pub const STATUS_SUCCESS: &str = "#73daca";
111
112    /// Warning states (#e0af68)
113    pub const STATUS_WARNING: &str = "#e0af68";
114
115    /// Error states (#f7768e)
116    pub const STATUS_ERROR: &str = "#f7768e";
117
118    /// Info states (#7dcfff)
119    pub const STATUS_INFO: &str = "#7dcfff";
120
121    /// User message background tint (#1a201e)
122    pub const ROLE_USER_BG: &str = "#1a201e";
123
124    /// Agent message background tint (#1a1c24)
125    pub const ROLE_AGENT_BG: &str = "#1a1c24";
126
127    /// Tool invocation background tint (#201c1a)
128    pub const ROLE_TOOL_BG: &str = "#201c1a";
129
130    /// System message background tint (#201e1a)
131    pub const ROLE_SYSTEM_BG: &str = "#201e1a";
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_colors_are_valid_hex() {
140        // Verify all color constants are valid 7-char hex colors
141        let all_colors = [
142            // Backgrounds
143            colors::BG_DEEP,
144            colors::BG_SURFACE,
145            colors::BG_HIGHLIGHT,
146            // Borders
147            colors::BORDER,
148            colors::BORDER_FOCUS,
149            // Text
150            colors::TEXT_PRIMARY,
151            colors::TEXT_SECONDARY,
152            colors::TEXT_MUTED,
153            colors::TEXT_DISABLED,
154            // Accents
155            colors::ACCENT_PRIMARY,
156            colors::ACCENT_SECONDARY,
157            colors::ACCENT_TERTIARY,
158            // Roles
159            colors::ROLE_USER,
160            colors::ROLE_AGENT,
161            colors::ROLE_TOOL,
162            colors::ROLE_SYSTEM,
163            // Role backgrounds
164            colors::ROLE_USER_BG,
165            colors::ROLE_AGENT_BG,
166            colors::ROLE_TOOL_BG,
167            colors::ROLE_SYSTEM_BG,
168            // Status
169            colors::STATUS_SUCCESS,
170            colors::STATUS_WARNING,
171            colors::STATUS_ERROR,
172            colors::STATUS_INFO,
173        ];
174
175        for color in all_colors {
176            assert!(
177                color.starts_with('#') && color.len() == 7,
178                "Invalid color format: {}",
179                color
180            );
181            // Verify hex chars
182            assert!(
183                color[1..].chars().all(|c| c.is_ascii_hexdigit()),
184                "Invalid hex in color: {}",
185                color
186            );
187        }
188    }
189}