Skip to main content

cvkg_core/
lib.rs

1//! # CVKG Agentic Development Guidelines (v1.2)
2//!
3//! All AI agents contributing to this crate MUST follow ALL seven rules:
4//!
5//! ── Karpathy Guidelines (1–4) ────────────────────────────────────────────
6//! 1. THINK FIRST     — State assumptions. Surface ambiguity. Push back on complexity.
7//! 2. STAY SIMPLE     — Minimum code. No speculative features. No unasked-for abstractions.
8//! 3. BE SURGICAL     — Touch only what's required. Own your orphans. Don't improve neighbors.
9//! 4. VERIFY GOALS    — Turn tasks into checkable criteria. Loop until they pass. Never commit broken.
10//!
11//! ── CVKG Extended Protocols (5–7) ────────────────────────────────────────
12//! 5. TRIPLE-PASS     — Read the target, its surrounding context, and its full call graph
13//                      at least THREE TIMES before making any edit or revision.
14//! 6. COMMENT ALL     — Every major pub fn, unsafe block, and non-trivial algorithm in
15//                      every .rs/.ts/.h/.wgsl file MUST have a descriptive doc comment.
16//                      Comments describe WHY and WHAT CONTRACT, not HOW mechanically.
17//! 7. MONITOR LOOPS   — Check every tool call / command for progress every 30 seconds.
18//                      After 3 consecutive identical failures, stop, write BLOCKED.md,
19//                      and move to unblocked work. Never silently accept a broken state.
20//!
21//! Sources:
22//   Karpathy: https://github.com/multica-ai/andrej-karpathy-skills
23//   CVKG Extended: Section 2 of the CVKG Design Specification
24
25//! The View trait is the fundamental building block of CVKG. Every UI element — from a plain text label
26//! to a complex navigation controller — is a View. The trait is intentionally minimal; complexity emerges
27//! through modifier composition.
28//!
29//! # Conformance rules:
30//! 1. `body()` must be pure and side-effect free
31//! 2. Primitive views use `Never` as `Body` and register a `PaintCommand` directly with the scene graph
32//! 3. `View` types must implement `Send` but not necessarily `Sync`, enabling safe multi-threaded layout passes
33
34use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::str::FromStr;
37
38pub mod error_types;
39
40pub mod security;
41
42/// Error state for fault isolation at the component level.
43#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
44pub struct ComponentErrorState {
45    pub has_error: bool,
46    pub error_message: Option<String>,
47    pub error_location: Option<String>,
48}
49impl ComponentErrorState {
50    pub fn clear() -> Self {
51        Self::default()
52    }
53
54    pub fn error(message: impl Into<String>, location: impl Into<String>) -> Self {
55        Self {
56            has_error: true,
57            error_message: Some(message.into()),
58            error_location: Some(location.into()),
59        }
60    }
61}
62
63/// Knowledge state for the agentic memory system.
64#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
65pub struct KnowledgeState {
66    pub thoughts: Vec<String>,
67    pub actions: Vec<String>,
68    pub context: HashMap<String, String>,
69    pub last_query_results: Vec<KnowledgeId>,
70    #[serde(alias = "items")]
71    pub fragments: std::collections::HashMap<KnowledgeId, KnowledgeFragment>,
72    /// The Temporal Graph nodes
73    pub nodes: Vec<TemporalNode>,
74    /// The Temporal Graph edges
75    pub edges: Vec<TemporalEdge>,
76    /// The current operational Realm (Midgard/Asgard)
77    pub realm: Realm,
78    /// Last known pointer position (X, Y)
79    pub last_pointer_pos: [f32; 2],
80    /// Resolved pointer velocity (pixels per frame)
81    pub pointer_velocity: [f32; 2],
82    /// The current 'Focus' node ID (Odin's Eye focus)
83    pub odin_focus: Option<String>,
84    /// Agent attention heatmap (node_id -> intensity)
85    pub agent_attention: HashMap<String, f32>,
86    // Component state storage for dynamic state
87    #[serde(skip)]
88    pub component_states: HashMap<u64, Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>>>,
89    /// Global undo/redo manager tracking document and input states.
90    #[serde(skip)]
91    pub undo_manager: UndoManager,
92    /// Active notification list.
93    #[serde(default)]
94    pub notifications: Vec<Notification>,
95    /// Flag indicating whether the notification center panel is visible.
96    #[serde(default)]
97    pub notification_center_visible: bool,
98    /// Modifier key state: shift key pressed.
99    #[serde(default)]
100    pub modifiers_shift: bool,
101    /// Modifier key state: control key pressed.
102    #[serde(default)]
103    pub modifiers_ctrl: bool,
104    /// Modifier key state: alt/option key pressed.
105    #[serde(default)]
106    pub modifiers_alt: bool,
107    /// Modifier key state: logo/command/windows key pressed.
108    #[serde(default)]
109    pub modifiers_logo: bool,
110    /// Whether the performance profiling overlay (Cmd+Shift+P) is currently visible.
111    #[serde(default)]
112    pub performance_overlay_visible: bool,
113}
114
115impl KnowledgeState {
116    /// Apply activation decay to all temporal nodes and evolving components.
117    /// Nodes with weight below a threshold drift out of the primary context.
118    /// Components lose vitality (Fafnir's Decay) if not actively 'fed'.
119    pub fn apply_decay(&mut self, decay_factor: f32) {
120        for node in &mut self.nodes {
121            node.weight *= decay_factor;
122        }
123
124        // Fafnir's Decay: Components naturally revert to base state over time
125        for state in self.component_states.values() {
126            if let Ok(mut lock) = state.write()
127                && let Some(v) = lock.downcast_mut::<f32>()
128            {
129                *v = (*v * decay_factor).max(1.0);
130            }
131        }
132    }
133
134    /// Increase the importance weight of nodes associated with a successful task.
135    pub fn reinforce(&mut self, node_ids: &[String], boost: f32) {
136        for node in &mut self.nodes {
137            if node_ids.contains(&node.id) {
138                node.weight += boost;
139            }
140        }
141    }
142
143    /// Update pointer kinematics based on a new position.
144    pub fn update_pointer(&mut self, new_pos: [f32; 2]) {
145        self.pointer_velocity = [
146            new_pos[0] - self.last_pointer_pos[0],
147            new_pos[1] - self.last_pointer_pos[1],
148        ];
149        self.last_pointer_pos = new_pos;
150    }
151}
152// Knowledge System Types
153/// Unique identifier for knowledge fragments
154pub type KnowledgeId = String;
155
156/// A knowledge fragment stored in the memory system
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct KnowledgeFragment {
159    /// Unique identifier for this fragment
160    pub id: String,
161    /// Short summary for prompt injection and quick search
162    pub summary: String,
163    /// Reference source (e.g. filename, URL, or conversation ID)
164    pub source: String,
165    /// Frame number or timestamp of creation
166    pub created_at: u64,
167    /// Number of times this fragment has been retrieved
168    pub accessed_count: u32,
169    /// Full content (optional, can be loaded on-demand)
170    pub content: Option<String>,
171}
172
173impl KnowledgeFragment {
174    pub fn new(id: String, summary: String, source: String) -> Self {
175        Self {
176            id,
177            summary,
178            source,
179            created_at: 0,
180            accessed_count: 0,
181            content: None,
182        }
183    }
184}
185
186/// Memory layers for the layered cognitive engine
187#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
188pub enum MemoryLayer {
189    /// Raw mission events (short-term)
190    Episodic,
191    /// Extracted facts and tactical intelligence (long-term)
192    Semantic,
193    /// Successful command sequences and tool chains
194    Procedural,
195}
196
197/// The operational Realm of the UI.
198/// Midgard: Classic, functional, 2D tactical UI for mortals.
199/// Asgard: High-fidelity, cognitive, shader-heavy UI for the Singularity.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
201pub enum Realm {
202    Midgard,
203    #[default]
204    Asgard,
205}
206
207/// A node in the Temporal Graph representing a cognitive anchor
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct TemporalNode {
210    /// Unique identifier for this node
211    pub id: String,
212    /// ID of the underlying knowledge fragment
213    pub fragment_id: KnowledgeId,
214    /// Timestamp of the event
215    pub timestamp: u64,
216    /// The memory layer this node belongs to
217    pub layer: MemoryLayer,
218    /// Importance weight for activation decay and retrieval
219    pub weight: f32,
220}
221
222/// An edge in the Temporal Graph representing a relationship between nodes
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct TemporalEdge {
225    /// Source node ID
226    pub source: String,
227    /// Target node ID
228    pub target: String,
229    /// Type of relationship (e.g. "causal", "semantic", "temporal")
230    pub relation: String,
231    /// Weight/strength of the connection
232    pub weight: f32,
233}
234
235/// A single action group representing an undo/redo step.
236pub struct UndoGroup {
237    /// Descriptive label of the action (e.g. "Type", "Delete").
238    pub label: String,
239    /// Time when the action was recorded, in seconds.
240    pub timestamp: f32,
241    /// Closure to revert the action.
242    pub undo: Arc<dyn Fn() + Send + Sync>,
243    /// Closure to re-apply the action.
244    pub redo: Arc<dyn Fn() + Send + Sync>,
245}
246
247impl Clone for UndoGroup {
248    /// Clone the undo/redo group. The closures are shared via Arc.
249    fn clone(&self) -> Self {
250        Self {
251            label: self.label.clone(),
252            timestamp: self.timestamp,
253            undo: Arc::clone(&self.undo),
254            redo: Arc::clone(&self.redo),
255        }
256    }
257}
258
259impl std::fmt::Debug for UndoGroup {
260    /// Debug format helper to avoid printing closures.
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        f.debug_struct("UndoGroup")
263            .field("label", &self.label)
264            .field("timestamp", &self.timestamp)
265            .finish()
266    }
267}
268
269/// Unified manager for undo and redo stacks.
270/// Supports grouping of actions, max undo depth clamping, and coalescing.
271pub struct UndoManager {
272    /// History stack of undo/redo groups.
273    stack: Vec<UndoGroup>,
274    /// Current position/index in the stack.
275    position: usize,
276    /// Maximum allowed undo steps before discarding oldest.
277    max_depth: usize,
278    /// Time window in seconds to coalesce consecutive actions of the same type.
279    coalesce_window: f32,
280}
281
282impl Default for UndoManager {
283    /// Create a default UndoManager with a depth of 100 and a 0.5s coalesce window.
284    fn default() -> Self {
285        Self {
286            stack: Vec::new(),
287            position: 0,
288            max_depth: 100,
289            coalesce_window: 0.5,
290        }
291    }
292}
293
294impl Clone for UndoManager {
295    /// Clone the undo manager, preserving stacks and position.
296    fn clone(&self) -> Self {
297        Self {
298            stack: self.stack.clone(),
299            position: self.position,
300            max_depth: self.max_depth,
301            coalesce_window: self.coalesce_window,
302        }
303    }
304}
305
306impl std::fmt::Debug for UndoManager {
307    /// Debug format helper for UndoManager.
308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309        f.debug_struct("UndoManager")
310            .field("stack_len", &self.stack.len())
311            .field("position", &self.position)
312            .field("max_depth", &self.max_depth)
313            .field("coalesce_window", &self.coalesce_window)
314            .finish()
315    }
316}
317
318impl UndoManager {
319    /// Create a new UndoManager with custom settings.
320    pub fn new(max_depth: usize, coalesce_window: f32) -> Self {
321        Self {
322            stack: Vec::new(),
323            position: 0,
324            max_depth,
325            coalesce_window,
326        }
327    }
328
329    /// Push a new undo/redo group to the stack, clearing any forward redo history.
330    pub fn push(
331        &mut self,
332        label: &str,
333        undo: impl Fn() + Send + Sync + 'static,
334        redo: impl Fn() + Send + Sync + 'static,
335    ) {
336        if self.position < self.stack.len() {
337            self.stack.truncate(self.position);
338        }
339
340        let timestamp = std::time::SystemTime::now()
341            .duration_since(std::time::UNIX_EPOCH)
342            .unwrap_or_default()
343            .as_secs_f32();
344
345        self.stack.push(UndoGroup {
346            label: label.to_string(),
347            timestamp,
348            undo: Arc::new(undo),
349            redo: Arc::new(redo),
350        });
351
352        if self.stack.len() > self.max_depth {
353            self.stack.remove(0);
354        }
355        self.position = self.stack.len();
356    }
357
358    /// Perform the undo action if possible, moving the position back.
359    /// Returns the undo closure to be executed outside of any state lock.
360    pub fn undo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
361        if self.can_undo() {
362            self.position -= 1;
363            Some(Arc::clone(&self.stack[self.position].undo))
364        } else {
365            None
366        }
367    }
368
369    /// Perform the redo action if possible, moving the position forward.
370    /// Returns the redo closure to be executed outside of any state lock.
371    pub fn redo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
372        if self.can_redo() {
373            let group = &self.stack[self.position];
374            self.position += 1;
375            Some(Arc::clone(&group.redo))
376        } else {
377            None
378        }
379    }
380
381    /// Returns true if there is an action that can be undone.
382    pub fn can_undo(&self) -> bool {
383        self.position > 0
384    }
385
386    /// Returns true if there is an action that can be redone.
387    pub fn can_redo(&self) -> bool {
388        self.position < self.stack.len()
389    }
390
391    /// Clear all undo/redo history.
392    pub fn clear(&mut self) {
393        self.stack.clear();
394        self.position = 0;
395    }
396
397    /// Push a new coalesceable action. If the last action in the stack matches the label,
398    /// is within the coalesce window, and the position is at the end of the stack, their undo/redo
399    /// functions will be combined instead of creating a new group.
400    pub fn push_coalesceable(
401        &mut self,
402        label: &str,
403        undo: impl Fn() + Send + Sync + 'static,
404        redo: impl Fn() + Send + Sync + 'static,
405    ) {
406        let now = std::time::SystemTime::now()
407            .duration_since(std::time::UNIX_EPOCH)
408            .unwrap_or_default()
409            .as_secs_f32();
410
411        if self.position == self.stack.len() && !self.stack.is_empty() {
412            let last_idx = self.stack.len() - 1;
413            let last = &self.stack[last_idx];
414            if last.label == label && (now - last.timestamp).abs() <= self.coalesce_window {
415                let old_undo = Arc::clone(&last.undo);
416                let old_redo = Arc::clone(&last.redo);
417                let new_undo = Arc::new(undo);
418                let new_redo = Arc::new(redo);
419
420                self.stack[last_idx].undo = Arc::new(move || {
421                    new_undo();
422                    old_undo();
423                });
424                self.stack[last_idx].redo = Arc::new(move || {
425                    old_redo();
426                    new_redo();
427                });
428                self.stack[last_idx].timestamp = now;
429                return;
430            }
431        }
432
433        self.push(label, undo, redo);
434    }
435}
436
437/// Unique identifier for a window instance.
438#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
439pub struct WindowId(pub u64);
440
441/// Specifies the layering behavior of the window relative to other windows.
442#[derive(
443    Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
444)]
445pub enum WindowLevel {
446    /// Standard window.
447    #[default]
448    Normal,
449    /// Window stays above all standard windows.
450    AlwaysOnTop,
451    /// Menu or pop-up level window.
452    PopUpMenu,
453}
454
455/// Configuration settings for creating a new window.
456#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
457pub struct WindowConfig {
458    /// The window title bar text.
459    pub title: String,
460    /// Default width and height of the window.
461    pub size: (f32, f32),
462    /// Minimum allowed dimensions.
463    pub min_size: Option<(f32, f32)>,
464    /// Maximum allowed dimensions.
465    pub max_size: Option<(f32, f32)>,
466    /// Whether the window can be resized by the user.
467    pub resizable: bool,
468    /// Whether the window background is transparent.
469    pub transparent: bool,
470    /// Whether the window title bar and border decorations are drawn.
471    pub decorations: bool,
472    /// The window level layer.
473    pub level: WindowLevel,
474}
475
476impl Default for WindowConfig {
477    /// Create a standard default window configuration.
478    fn default() -> Self {
479        Self {
480            title: "CVKG Window".to_string(),
481            size: (800.0, 600.0),
482            min_size: None,
483            max_size: None,
484            resizable: true,
485            transparent: false,
486            decorations: true,
487            level: WindowLevel::Normal,
488        }
489    }
490}
491
492/// Abstract trait representing a platform-native window.
493/// Implementations delegate calls back to the platform renderers and events.
494pub trait Window: Send + Sync {
495    /// Request closing of the window.
496    fn close(&self);
497    /// Change the title bar text of the window.
498    fn set_title(&self, title: &str);
499    /// Update the window's physical dimensions.
500    fn set_size(&self, width: f32, height: f32);
501    /// Check if the window currently has keyboard focus.
502    fn is_key(&self) -> bool;
503    /// Check if this is the primary main application window.
504    fn is_main(&self) -> bool;
505    /// Check if the window is currently visible/mapped.
506    fn is_visible(&self) -> bool;
507    /// Hide or show the window.
508    fn set_visible(&self, visible: bool);
509    /// Bring the window to the front and focus it.
510    fn bring_to_front(&self);
511}
512
513/// A handle to a native window that can be used by application code.
514#[derive(Clone)]
515pub struct WindowHandle {
516    /// The unique identifier of this window.
517    pub id: WindowId,
518    /// Reference to the underlying platform window.
519    pub inner: Arc<dyn Window>,
520}
521
522impl std::fmt::Debug for WindowHandle {
523    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524        f.debug_struct("WindowHandle")
525            .field("id", &self.id)
526            .finish()
527    }
528}
529
530impl WindowHandle {
531    /// Create a new WindowHandle.
532    pub fn new(id: WindowId, inner: Arc<dyn Window>) -> Self {
533        Self { id, inner }
534    }
535    /// Request the window to close.
536    pub fn close(self) {
537        self.inner.close();
538    }
539    /// Set the title text of the window.
540    pub fn set_title(&self, title: &str) {
541        self.inner.set_title(title);
542    }
543    /// Resize the window.
544    pub fn set_size(&self, width: f32, height: f32) {
545        self.inner.set_size(width, height);
546    }
547    /// Returns true if this window has key focus.
548    pub fn is_key(&self) -> bool {
549        self.inner.is_key()
550    }
551    /// Returns true if this is the main application window.
552    pub fn is_main(&self) -> bool {
553        self.inner.is_main()
554    }
555    /// Returns true if the window is visible.
556    pub fn is_visible(&self) -> bool {
557        self.inner.is_visible()
558    }
559    /// Set visibility of the window.
560    pub fn set_visible(&self, visible: bool) {
561        self.inner.set_visible(visible);
562    }
563    /// Bring this window to the foreground.
564    pub fn bring_to_front(&self) {
565        self.inner.bring_to_front();
566    }
567}
568
569/// Action to take when a window close request event is received.
570#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
571pub enum WindowCloseAction {
572    /// Close the window immediately.
573    Allow,
574    /// Request confirmation from the user (e.g. show dialog).
575    Confirm,
576    /// Ignore the close request.
577    Deny,
578}
579
580#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
581pub struct AssetKey(pub String);
582
583impl EnvKey for AssetKey {
584    type Value = Arc<dyn AssetManager>;
585    fn default_value() -> Self::Value {
586        Arc::new(DefaultAssetManager::new())
587    }
588}
589
590/// Asset state for async resource loading.
591#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
592pub enum AssetState<T> {
593    Loading,
594    Ready(T),
595    Error(String),
596}
597
598/// Design token value that can adapt to light/dark mode
599#[derive(Debug, Clone, Serialize, Deserialize)]
600#[serde(untagged)]
601pub enum TokenValue {
602    /// Single value (same for light and dark)
603    Single { value: String },
604    /// Different values for light and dark mode
605    Adaptive { light: String, dark: String },
606}
607
608/// YggdrasilTokens is the authoritative container for all design tokens in the CVKG ecosystem.
609#[derive(Debug, Clone, Serialize, Deserialize)]
610pub struct YggdrasilTokens {
611    pub color: HashMap<String, TokenValue>,
612    pub font: HashMap<String, TokenValue>,
613    pub spacing: HashMap<String, TokenValue>,
614    pub radius: HashMap<String, TokenValue>,
615    pub shadow: HashMap<String, TokenValue>,
616    pub border: HashMap<String, TokenValue>,
617    pub anim: HashMap<String, TokenValue>,
618    pub bifrost: HashMap<String, TokenValue>,
619    pub gungnir: HashMap<String, TokenValue>,
620    pub mjolnir: HashMap<String, TokenValue>,
621    pub accessibility: HashMap<String, TokenValue>,
622}
623
624impl Default for YggdrasilTokens {
625    fn default() -> Self {
626        Self::new()
627    }
628}
629
630impl YggdrasilTokens {
631    pub fn new() -> Self {
632        Self {
633            color: HashMap::new(),
634            font: HashMap::new(),
635            spacing: HashMap::new(),
636            radius: HashMap::new(),
637            shadow: HashMap::new(),
638            border: HashMap::new(),
639            anim: HashMap::new(),
640            bifrost: HashMap::new(),
641            gungnir: HashMap::new(),
642            mjolnir: HashMap::new(),
643            accessibility: HashMap::new(),
644        }
645    }
646
647    /// Get a color token value for the current mode
648    pub fn get_color(&self, key: &str, is_dark: bool) -> Option<String> {
649        self.color.get(key).map(|token| match token {
650            TokenValue::Single { value } => value.clone(),
651            TokenValue::Adaptive { light, dark } => {
652                if is_dark {
653                    dark.clone()
654                } else {
655                    light.clone()
656                }
657            }
658        })
659    }
660
661    /// Get a token value of any type and parse it into the target type
662    pub fn get<T: FromStr>(&self, category: &str, key: &str, is_dark: bool) -> Option<T> {
663        let map = match category {
664            "color" => &self.color,
665            "font" => &self.font,
666            "spacing" => &self.spacing,
667            "radius" => &self.radius,
668            "shadow" => &self.shadow,
669            "border" => &self.border,
670            "anim" => &self.anim,
671            "bifrost" => &self.bifrost,
672            "gungnir" => &self.gungnir,
673            "mjolnir" => &self.mjolnir,
674            "accessibility" => &self.accessibility,
675            _ => return None,
676        };
677
678        map.get(key).and_then(|token| match token {
679            TokenValue::Single { value } => value.parse().ok(),
680            TokenValue::Adaptive { light, dark } => {
681                let value = if is_dark { dark } else { light };
682                value.parse().ok()
683            }
684        })
685    }
686}
687
688pub trait View: Sized + Send {
689    /// The concrete type produced after applying modifiers.
690    /// For primitive views this is Self.
691    type Body: View;
692
693    fn body(self) -> Self::Body;
694
695    /// Render this view into the provided renderer at the specified bounds.
696    /// Primitive views override this to perform drawing operations.
697    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
698
699    /// Calculate the natural (intrinsic) size of this view given proposed constraints.
700    /// This allows views like Buttons or Labels to inform the layout engine of their needs.
701    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
702        Size::ZERO
703    }
704
705    /// Optionally provide a layout implementation for this view.
706    fn layout(&self) -> Option<&dyn layout::LayoutView> {
707        None
708    }
709
710    /// Returns the flex weight of this view for proportional distribution in stacks.
711    fn flex_weight(&self) -> f32 {
712        0.0
713    }
714
715    /// Returns the grid placement configuration for this view if it is laid out in a Grid.
716    fn get_grid_placement(&self) -> Option<GridPlacement> {
717        None
718    }
719
720    /// Provided modifier entry point
721    fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
722        ModifiedView::new(self, m)
723    }
724
725    /// Apply a Bifrost (Frosted Glass) effect to the view
726    fn bifrost(
727        self,
728        blur: f32,
729        saturation: f32,
730        opacity: f32,
731    ) -> ModifiedView<Self, BifrostModifier> {
732        self.modifier(BifrostModifier {
733            blur,
734            saturation,
735            opacity,
736        })
737    }
738
739    /// Apply a Gungnir (Neon Glow) effect to the view
740    fn gungnir(
741        self,
742        color: impl Into<String>,
743        radius: f32,
744        intensity: f32,
745    ) -> ModifiedView<Self, GungnirModifier> {
746        self.modifier(GungnirModifier {
747            color: color.into(),
748            radius,
749            intensity,
750        })
751    }
752
753    /// Apply a Mjolnir Slice (Geometric cut) to the view
754    fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, MjolnirSliceModifier> {
755        self.modifier(MjolnirSliceModifier { angle, offset })
756    }
757
758    /// Apply a Mjolnir Shatter (Fragmented transition) to the view
759    fn mjolnir_shatter(
760        self,
761        pieces: u32,
762        force: f32,
763    ) -> ModifiedView<Self, MjolnirShatterModifier> {
764        self.modifier(MjolnirShatterModifier { pieces, force })
765    }
766
767    /// Mark this view as a Bifrost Bridge (Shared Element) for cross-view persistence
768    fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, BifrostBridgeModifier> {
769        self.modifier(BifrostBridgeModifier { id: id.into() })
770    }
771
772    /// Add a background color to this view
773    fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
774        self.modifier(BackgroundModifier { color })
775    }
776
777    /// Add padding to this view
778    fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
779        self.modifier(PaddingModifier { amount })
780    }
781
782    /// Set the opacity (alpha) of this view in the range [0.0, 1.0].
783    fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
784        self.modifier(OpacityModifier {
785            opacity: opacity.clamp(0.0, 1.0),
786        })
787    }
788
789    /// Override the foreground (text / icon) color of this view.
790    fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
791        self.modifier(ForegroundColorModifier { color })
792    }
793
794    /// Constrain this view to an explicit width and/or height.
795    /// Constrains the size of this view using fixed width/height values.
796    fn frame(self, width: Option<f32>, height: Option<f32>) -> ModifiedView<Self, FrameModifier> {
797        self.modifier(FrameModifier {
798            width,
799            height,
800            min_width: None,
801            max_width: None,
802            min_height: None,
803            max_height: None,
804            alignment: Alignment::Center,
805        })
806    }
807
808    /// Give this view a flex weight for proportional space distribution in stacks.
809    fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
810        self.modifier(FlexModifier { weight })
811    }
812
813    /// Specify the grid placement configuration (column, row, column_span, row_span) for this view.
814    fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
815        self.modifier(GridPlacementModifier { placement })
816    }
817
818    /// Overlay a view on top of this view, aligned and offset relative to it.
819    fn overlay<O: View + Clone + 'static>(
820        self,
821        overlay: O,
822        alignment: Alignment,
823        offset: [f32; 2],
824        on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
825    ) -> ModifiedView<Self, OverlayModifier> {
826        self.modifier(OverlayModifier {
827            overlay: overlay.erase(),
828            alignment,
829            offset,
830            on_dismiss,
831        })
832    }
833
834    /// Automatically add padding to avoid overlapping with platform safe areas (notches, bars).
835    fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
836        self.modifier(SafeAreaModifier { ignores: false })
837    }
838
839    /// Explicitly ignore platform safe areas and draw into the margins.
840    fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
841        self.modifier(SafeAreaModifier { ignores: true })
842    }
843
844    /// Clip all child drawing to this view's bounds.
845    fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
846        self.modifier(ClipModifier)
847    }
848
849    /// Draw a colored border around this view.
850    fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
851        self.modifier(BorderModifier { color, width })
852    }
853
854    /// Add elevation (shadow) to the view. Level determines the shadow depth.
855    fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
856        self.modifier(ElevationModifier { level })
857    }
858
859    /// Add a magnetic effect that pulls the view towards the cursor.
860    fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticModifier> {
861        self.modifier(MagneticModifier { radius, intensity })
862    }
863
864    /// Add a ManiGlow (Lunar Illuminator) effect that glows near the cursor.
865    fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, ManiGlowModifier> {
866        self.modifier(ManiGlowModifier { color, radius })
867    }
868
869    /// Theme this view based on a specific memory layer.
870    fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, BifrostLayerModifier> {
871        self.modifier(BifrostLayerModifier { layer })
872    }
873
874    /// Enable Fafnir's Evolution: The component grows and glows as it is used.
875    fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, FafnirModifier> {
876        self.modifier(FafnirModifier { id })
877    }
878
879    /// Enable Mimir's Intent: The component anticipates user interaction via pointer kinematics.
880    fn mimir_intent(self) -> ModifiedView<Self, MimirIntentModifier> {
881        self.modifier(MimirIntentModifier)
882    }
883
884    /// Enable Kvasir's Vibes: Subconscious telemetry representing cognitive complexity.
885    fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, KvasirVibeModifier> {
886        self.modifier(KvasirVibeModifier { complexity })
887    }
888
889    /// Bestow Odin's Eye: Global omniscient observability layer.
890    fn odins_eye(self) -> ModifiedView<Self, OdinsEyeModifier> {
891        self.modifier(OdinsEyeModifier)
892    }
893
894    /// Trigger an action when the view appears
895    fn on_appear<F: Fn() + Send + Sync + 'static>(
896        self,
897        action: F,
898    ) -> ModifiedView<Self, LifecycleModifier> {
899        self.modifier(LifecycleModifier {
900            on_appear: Some(Arc::new(action)),
901            on_disappear: None,
902        })
903    }
904
905    /// Trigger an action when the view disappears
906    fn on_disappear<F: Fn() + Send + Sync + 'static>(
907        self,
908        action: F,
909    ) -> ModifiedView<Self, LifecycleModifier> {
910        self.modifier(LifecycleModifier {
911            on_appear: None,
912            on_disappear: Some(Arc::new(action)),
913        })
914    }
915
916    /// Trigger an action when the view is clicked
917    fn on_click<F: Fn() + Send + Sync + 'static>(
918        self,
919        action: F,
920    ) -> ModifiedView<Self, OnClickModifier> {
921        self.modifier(OnClickModifier {
922            action: Arc::new(action),
923        })
924    }
925
926    /// Trigger an action when the pointer enters the view bounds
927    fn on_pointer_enter<F: Fn() + Send + Sync + 'static>(
928        self,
929        action: F,
930    ) -> ModifiedView<Self, OnPointerEnterModifier> {
931        self.modifier(OnPointerEnterModifier {
932            action: Arc::new(action),
933        })
934    }
935
936    /// Trigger an action when the pointer leaves the view bounds
937    fn on_pointer_leave<F: Fn() + Send + Sync + 'static>(
938        self,
939        action: F,
940    ) -> ModifiedView<Self, OnPointerLeaveModifier> {
941        self.modifier(OnPointerLeaveModifier {
942            action: Arc::new(action),
943        })
944    }
945
946    /// Trigger an action when the pointer moves inside the view bounds
947    fn on_pointer_move<F: Fn(f32, f32) + Send + Sync + 'static>(
948        self,
949        action: F,
950    ) -> ModifiedView<Self, OnPointerMoveModifier> {
951        self.modifier(OnPointerMoveModifier {
952            action: Arc::new(action),
953        })
954    }
955
956    /// Trigger an action when the pointer is pressed down
957    fn on_pointer_down<F: Fn() + Send + Sync + 'static>(
958        self,
959        action: F,
960    ) -> ModifiedView<Self, OnPointerDownModifier> {
961        self.modifier(OnPointerDownModifier {
962            action: Arc::new(action),
963        })
964    }
965
966    /// Trigger an action when the pointer is released
967    fn on_pointer_up<F: Fn() + Send + Sync + 'static>(
968        self,
969        action: F,
970    ) -> ModifiedView<Self, OnPointerUpModifier> {
971        self.modifier(OnPointerUpModifier {
972            action: Arc::new(action),
973        })
974    }
975
976    /// Type-erase this view into AnyView
977    fn erase(self) -> AnyView
978    where
979        Self: Clone + 'static,
980    {
981        AnyView::new(self)
982    }
983
984    // =============================================================================
985    // ACCESSIBILITY
986    // =============================================================================
987
988    /// Return accessibility properties for this view.
989    /// Override to expose semantic role, label, state to assistive technology.
990    /// Default returns `None` (view is not explicitly accessible).
991    fn aria_properties(&self) -> Option<AriaProperties> {
992        None
993    }
994
995    /// Handle a keyboard navigation event.
996    /// Return true if consumed, false to bubble.
997    fn on_key_event(&self, _key: &str, _modifiers: KeyModifiers) -> bool {
998        false
999    }
1000
1001    /// Return keyboard shortcuts this view responds to.
1002    fn key_shortcuts(&self) -> Vec<KeyShortcut> {
1003        vec![]
1004    }
1005}
1006
1007// =============================================================================
1008// ARIA PROPERTIES
1009// =============================================================================
1010
1011/// Semantic role for assistive technology (WCAG 2.1 §4.1.2).
1012#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1013pub enum AriaRole {
1014    Alert,
1015    Alertdialog,
1016    Article,
1017    Banner,
1018    Button,
1019    Checkbox,
1020    Columnheader,
1021    Combobox,
1022    Complementary,
1023    Contentinfo,
1024    Dialog,
1025    Form,
1026    Grid,
1027    Gridcell,
1028    Heading,
1029    Img,
1030    Link,
1031    List,
1032    Listbox,
1033    Listitem,
1034    Main,
1035    Menu,
1036    Menubar,
1037    Menuitem,
1038    Menuitemcheckbox,
1039    Menuitemradio,
1040    Navigation,
1041    None,
1042    Note,
1043    Option,
1044    Presentation,
1045    Progressbar,
1046    Radio,
1047    Radiogroup,
1048    Region,
1049    Row,
1050    Rowgroup,
1051    Rowheader,
1052    Search,
1053    Separator,
1054    Slider,
1055    Spinbutton,
1056    Status,
1057    Switch,
1058    Tab,
1059    Table,
1060    Tablist,
1061    Tabpanel,
1062    Textbox,
1063    Toolbar,
1064    Tooltip,
1065    Tree,
1066    Treeitem,
1067}
1068
1069/// Accessible properties for a view, describing its semantic role and state.
1070#[derive(Debug, Clone, Serialize, Deserialize)]
1071pub struct AriaProperties {
1072    pub role: AriaRole,
1073    pub label: String,
1074    pub description: Option<String>,
1075    pub value: Option<String>,
1076    pub pressed: Option<bool>,
1077    pub checked: Option<bool>,
1078    pub expanded: Option<bool>,
1079    pub disabled: bool,
1080    pub hidden: bool,
1081    pub level: Option<u8>,
1082    pub shortcut: Option<String>,
1083    pub focused: bool,
1084    pub live: Option<String>,
1085    pub atomic: bool,
1086}
1087
1088impl AriaProperties {
1089    pub fn new(role: AriaRole, label: impl Into<String>) -> Self {
1090        Self {
1091            role,
1092            label: label.into(),
1093            description: None,
1094            value: None,
1095            pressed: None,
1096            checked: None,
1097            expanded: None,
1098            disabled: false,
1099            hidden: false,
1100            level: None,
1101            shortcut: None,
1102            focused: false,
1103            live: None,
1104            atomic: false,
1105        }
1106    }
1107
1108    pub fn description(mut self, d: impl Into<String>) -> Self {
1109        self.description = Some(d.into());
1110        self
1111    }
1112    pub fn value(mut self, v: impl Into<String>) -> Self {
1113        self.value = Some(v.into());
1114        self
1115    }
1116    pub fn checked(mut self, c: bool) -> Self {
1117        self.checked = Some(c);
1118        self
1119    }
1120    pub fn disabled(mut self, d: bool) -> Self {
1121        self.disabled = d;
1122        self
1123    }
1124    pub fn expanded(mut self, e: bool) -> Self {
1125        self.expanded = Some(e);
1126        self
1127    }
1128    pub fn level(mut self, l: u8) -> Self {
1129        self.level = Some(l.clamp(1, 6));
1130        self
1131    }
1132    pub fn shortcut(mut self, s: impl Into<String>) -> Self {
1133        self.shortcut = Some(s.into());
1134        self
1135    }
1136    pub fn focused(mut self, f: bool) -> Self {
1137        self.focused = f;
1138        self
1139    }
1140}
1141
1142// =============================================================================
1143// KEYBOARD NAVIGATION
1144// =============================================================================
1145
1146/// Modifier keys for keyboard events.
1147#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1148pub struct KeyModifiers {
1149    pub shift: bool,
1150    pub ctrl: bool,
1151    pub alt: bool,
1152    pub meta: bool,
1153}
1154
1155/// A keyboard shortcut binding.
1156#[derive(Debug, Clone, Serialize, Deserialize)]
1157pub struct KeyShortcut {
1158    pub key: String,
1159    pub modifiers: KeyModifiers,
1160    pub description: String,
1161}
1162
1163impl KeyShortcut {
1164    pub fn new(key: impl Into<String>, desc: impl Into<String>) -> Self {
1165        Self {
1166            key: key.into(),
1167            modifiers: KeyModifiers::default(),
1168            description: desc.into(),
1169        }
1170    }
1171    pub fn with_ctrl(mut self) -> Self {
1172        self.modifiers.ctrl = true;
1173        self
1174    }
1175    pub fn with_shift(mut self) -> Self {
1176        self.modifiers.shift = true;
1177        self
1178    }
1179    pub fn with_alt(mut self) -> Self {
1180        self.modifiers.alt = true;
1181        self
1182    }
1183    pub fn with_meta(mut self) -> Self {
1184        self.modifiers.meta = true;
1185        self
1186    }
1187}
1188
1189// =============================================================================
1190// FOCUS MANAGEMENT
1191// =============================================================================
1192
1193/// Unique ID for a focusable element.
1194#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1195pub struct FocusableId(String);
1196
1197impl From<&str> for FocusableId {
1198    fn from(s: &str) -> Self {
1199        Self(s.to_string())
1200    }
1201}
1202impl From<String> for FocusableId {
1203    fn from(s: String) -> Self {
1204        Self(s)
1205    }
1206}
1207
1208/// Focus trap for confining Tab navigation (e.g., modals).
1209#[derive(Debug, Clone)]
1210pub struct FocusTrap {
1211    pub id: FocusableId,
1212    pub order: Vec<FocusableId>,
1213    pub wrap: bool,
1214}
1215
1216impl FocusTrap {
1217    pub fn new(id: impl Into<FocusableId>, order: Vec<FocusableId>) -> Self {
1218        Self {
1219            id: id.into(),
1220            order,
1221            wrap: true,
1222        }
1223    }
1224}
1225
1226/// Manages focus order, Tab/Shift+Tab navigation, and focus traps.
1227#[derive(Debug, Default)]
1228pub struct FocusManager {
1229    order: Vec<FocusableId>,
1230    focused: Option<FocusableId>,
1231    traps: Vec<FocusTrap>,
1232}
1233
1234impl FocusManager {
1235    pub fn new() -> Self {
1236        Self::default()
1237    }
1238
1239    pub fn register(&mut self, id: impl Into<FocusableId>) {
1240        let id = id.into();
1241        if !self.order.contains(&id) {
1242            self.order.push(id);
1243        }
1244    }
1245
1246    pub fn unregister(&mut self, id: &FocusableId) {
1247        self.order.retain(|x| x != id);
1248        if self.focused.as_ref() == Some(id) {
1249            self.focused = None;
1250        }
1251    }
1252
1253    pub fn focused(&self) -> Option<&FocusableId> {
1254        self.focused.as_ref()
1255    }
1256
1257    pub fn focus(&mut self, id: impl Into<FocusableId>) -> bool {
1258        let id = id.into();
1259        if self.order.contains(&id) || self.traps.iter().any(|t| t.order.contains(&id)) {
1260            self.focused = Some(id);
1261            true
1262        } else {
1263            false
1264        }
1265    }
1266
1267    pub fn focus_next(&mut self) -> Option<&FocusableId> {
1268        let order = self.effective_order();
1269        if order.is_empty() {
1270            return None;
1271        }
1272        let idx = self
1273            .focused
1274            .as_ref()
1275            .and_then(|f| order.iter().position(|x| x == f));
1276        let next = match idx {
1277            Some(i) if i + 1 < order.len() => &order[i + 1],
1278            _ => &order[0],
1279        };
1280        self.focused = Some(next.clone());
1281        self.focused.as_ref()
1282    }
1283
1284    pub fn focus_prev(&mut self) -> Option<&FocusableId> {
1285        let order = self.effective_order();
1286        if order.is_empty() {
1287            return None;
1288        }
1289        let idx = self
1290            .focused
1291            .as_ref()
1292            .and_then(|f| order.iter().position(|x| x == f));
1293        let prev = match idx {
1294            Some(i) if i > 0 => &order[i - 1],
1295            _ => &order[order.len() - 1],
1296        };
1297        self.focused = Some(prev.clone());
1298        self.focused.as_ref()
1299    }
1300
1301    pub fn push_trap(&mut self, trap: FocusTrap) -> FocusableId {
1302        let id = trap.id.clone();
1303        self.traps.push(trap);
1304        id
1305    }
1306
1307    pub fn pop_trap(&mut self) {
1308        self.traps.pop();
1309    }
1310    pub fn trap_count(&self) -> usize {
1311        self.traps.len()
1312    }
1313
1314    fn effective_order(&self) -> &[FocusableId] {
1315        self.traps
1316            .last()
1317            .map(|t| t.order.as_slice())
1318            .unwrap_or(&self.order)
1319    }
1320}
1321
1322// =============================================================================
1323// REDUCED MOTION
1324// =============================================================================
1325
1326/// Detects OS-level reduced motion preference.
1327pub fn is_reduced_motion() -> bool {
1328    std::env::var("GTK_THEME")
1329        .map(|v| v.to_lowercase().contains("reduced"))
1330        .unwrap_or(false)
1331        || std::env::var("NO_ANIMATIONS")
1332            .map(|v| v == "1" || v.to_lowercase() == "true")
1333            .unwrap_or(false)
1334        || std::env::var("ACCESSIBILITY_REDUCED_MOTION")
1335            .map(|v| v == "1" || v.to_lowercase() == "true")
1336            .unwrap_or(false)
1337}
1338
1339/// Returns effective animation duration (0.0 if reduced motion is active).
1340pub fn effective_duration(secs: f32) -> f32 {
1341    if is_reduced_motion() { 0.0 } else { secs }
1342}
1343
1344/// An object-safe version of the View trait for type erasure.
1345pub trait ErasedView: Send {
1346    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect);
1347    fn name(&self) -> &'static str;
1348    fn flex_weight_erased(&self) -> f32;
1349    fn layout_erased(&self) -> Option<&dyn layout::LayoutView>;
1350    fn grid_placement_erased(&self) -> Option<GridPlacement>;
1351    fn clone_box(&self) -> Box<dyn ErasedView>;
1352}
1353
1354impl<V: View + Clone + 'static> ErasedView for V {
1355    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect) {
1356        self.render(renderer, rect);
1357    }
1358
1359    fn name(&self) -> &'static str {
1360        std::any::type_name::<V>()
1361    }
1362
1363    fn flex_weight_erased(&self) -> f32 {
1364        self.flex_weight()
1365    }
1366
1367    fn layout_erased(&self) -> Option<&dyn layout::LayoutView> {
1368        self.layout()
1369    }
1370
1371    fn grid_placement_erased(&self) -> Option<GridPlacement> {
1372        self.get_grid_placement()
1373    }
1374
1375    fn clone_box(&self) -> Box<dyn ErasedView> {
1376        Box::new(self.clone())
1377    }
1378}
1379
1380/// A view that memoizes its rendering based on a stable ID and data hash.
1381/// The renderer can use this to skip re-rendering the sub-tree if the data hasn't changed.
1382pub struct MemoView<V, F> {
1383    id: u64,
1384    data_hash: u64,
1385    builder: F,
1386    _v: std::marker::PhantomData<V>,
1387}
1388
1389impl<V: View, F: Fn() -> V + Send + Sync> MemoView<V, F> {
1390    /// Create a new MemoView with a stable ID and a data hash.
1391    pub fn new(id: u64, data_hash: u64, builder: F) -> Self {
1392        Self {
1393            id,
1394            data_hash,
1395            builder,
1396            _v: std::marker::PhantomData,
1397        }
1398    }
1399}
1400
1401impl<V: View + 'static, F: Fn() -> V + Send + Sync + 'static> View for MemoView<V, F> {
1402    type Body = Never;
1403    fn body(self) -> Self::Body {
1404        unreachable!("MemoView does not have a body")
1405    }
1406
1407    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1408        renderer.memoize(self.id, self.data_hash, &|r| {
1409            let view = (self.builder)();
1410            view.render(r, rect);
1411        });
1412    }
1413}
1414
1415/// A type-erased View wrapper.
1416pub struct AnyView {
1417    inner: Box<dyn ErasedView>,
1418}
1419
1420impl Clone for AnyView {
1421    fn clone(&self) -> Self {
1422        Self {
1423            inner: self.inner.clone_box(),
1424        }
1425    }
1426}
1427
1428impl AnyView {
1429    pub fn new<V: View + Clone + 'static>(view: V) -> Self {
1430        Self {
1431            inner: Box::new(view),
1432        }
1433    }
1434}
1435
1436impl View for AnyView {
1437    type Body = Never;
1438    fn body(self) -> Self::Body {
1439        unreachable!()
1440    }
1441
1442    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1443        renderer.push_vnode(rect, self.inner.name());
1444        self.inner.render_erased(renderer, rect);
1445        renderer.pop_vnode();
1446    }
1447
1448    fn flex_weight(&self) -> f32 {
1449        self.inner.flex_weight_erased()
1450    }
1451
1452    fn layout(&self) -> Option<&dyn layout::LayoutView> {
1453        self.inner.layout_erased()
1454    }
1455
1456    fn get_grid_placement(&self) -> Option<GridPlacement> {
1457        self.inner.grid_placement_erased()
1458    }
1459}
1460
1461/// BifrostBridgeModifier enables shared-element transitions.
1462/// When two views share the same Bifrost Bridge ID, the Sleipnir solver will
1463/// interpolate their geometry and effects (blur, glow) during the transition.
1464#[derive(Debug, Clone, PartialEq)]
1465pub struct BifrostBridgeModifier {
1466    pub id: String,
1467}
1468
1469impl ViewModifier for BifrostBridgeModifier {
1470    fn modify<V: View>(self, content: V) -> impl View {
1471        ModifiedView::new(content, self)
1472    }
1473
1474    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1475        // Register this element with the renderer for shared-element transition logic
1476        renderer.register_shared_element(&self.id, rect);
1477    }
1478}
1479
1480/// MjolnirSliceModifier implements the "Geometric Slice" aesthetic.
1481/// It uses a signed distance field (SDF) to clip the view along a sharp angled line.
1482#[derive(Debug, Clone, Copy, PartialEq)]
1483pub struct MjolnirSliceModifier {
1484    pub angle: f32,
1485    pub offset: f32,
1486}
1487
1488impl ViewModifier for MjolnirSliceModifier {
1489    fn modify<V: View>(self, content: V) -> impl View {
1490        ModifiedView::new(content, self)
1491    }
1492
1493    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1494        renderer.push_mjolnir_slice(self.angle, self.offset);
1495    }
1496
1497    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1498        renderer.pop_mjolnir_slice();
1499    }
1500}
1501
1502/// MjolnirShatterModifier implements the "Shattering" effect.
1503/// It breaks the view into discrete geometric fragments that can be animated.
1504#[derive(Debug, Clone, Copy, PartialEq)]
1505pub struct MjolnirShatterModifier {
1506    pub pieces: u32,
1507    pub force: f32,
1508}
1509
1510impl ViewModifier for MjolnirShatterModifier {
1511    fn modify<V: View>(self, content: V) -> impl View {
1512        ModifiedView::new(content, self)
1513    }
1514
1515    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1516        // RADIAL SHATTER: Fragment the view into wedges
1517        let pieces = self.pieces.max(1);
1518        for i in 0..pieces {
1519            let progress = i as f32 / pieces as f32;
1520            let next_progress = (i + 1) as f32 / pieces as f32;
1521
1522            let angle_start = progress * 360.0;
1523            let angle_end = next_progress * 360.0;
1524
1525            // Wedge slice: intersection of two half-planes
1526            renderer.push_mjolnir_slice(angle_start, 0.0);
1527            renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
1528
1529            // Apply radial force offset
1530            let mid_angle = (angle_start + angle_end) / 2.0;
1531            let rad = mid_angle.to_radians();
1532            let dx = rad.cos() * self.force;
1533            let dy = rad.sin() * self.force;
1534
1535            let shard_rect = Rect {
1536                x: rect.x + dx,
1537                y: rect.y + dy,
1538                ..rect
1539            };
1540
1541            view.render(renderer, shard_rect);
1542
1543            renderer.pop_mjolnir_slice();
1544            renderer.pop_mjolnir_slice();
1545        }
1546    }
1547}
1548
1549/// BifrostModifier implements the Cyberpunk "Frosted Glass" aesthetic.
1550/// It triggers backdrop blurring and light scattering in the render pipeline.
1551#[derive(Debug, Clone, Copy, PartialEq)]
1552pub struct BifrostModifier {
1553    pub blur: f32,
1554    pub saturation: f32,
1555    pub opacity: f32,
1556}
1557
1558impl ViewModifier for BifrostModifier {
1559    fn modify<V: View>(self, content: V) -> impl View {
1560        ModifiedView::new(content, self)
1561    }
1562
1563    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1564        if renderer.is_over_budget() {
1565            // Degrade: Use lower quality (half blur) if over budget
1566            renderer.bifrost(rect, self.blur * 0.5, self.saturation, self.opacity);
1567        } else {
1568            renderer.bifrost(rect, self.blur, self.saturation, self.opacity);
1569        }
1570    }
1571}
1572
1573/// A modifier that adds a background color to a view.
1574#[derive(Debug, Clone, Copy, PartialEq)]
1575pub struct BackgroundModifier {
1576    pub color: [f32; 4],
1577}
1578
1579impl ViewModifier for BackgroundModifier {
1580    fn modify<V: View>(self, content: V) -> impl View {
1581        ModifiedView::new(content, self)
1582    }
1583
1584    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1585        renderer.fill_rect(rect, self.color);
1586    }
1587}
1588
1589/// A modifier that adds padding to a view.
1590#[derive(Debug, Clone, Copy, PartialEq)]
1591pub struct PaddingModifier {
1592    pub amount: f32,
1593}
1594
1595impl ViewModifier for PaddingModifier {
1596    fn modify<V: View>(self, content: V) -> impl View {
1597        ModifiedView::new(content, self)
1598    }
1599
1600    fn transform_rect(&self, rect: Rect) -> Rect {
1601        Rect {
1602            x: rect.x + self.amount,
1603            y: rect.y + self.amount,
1604            width: (rect.width - 2.0 * self.amount).max(0.0),
1605            height: (rect.height - 2.0 * self.amount).max(0.0),
1606        }
1607    }
1608
1609    fn transform_proposal(&self, mut proposal: SizeProposal) -> SizeProposal {
1610        if let Some(w) = proposal.width {
1611            proposal.width = Some((w - 2.0 * self.amount).max(0.0));
1612        }
1613        if let Some(h) = proposal.height {
1614            proposal.height = Some((h - 2.0 * self.amount).max(0.0));
1615        }
1616        proposal
1617    }
1618
1619    fn transform_size(&self, mut size: Size) -> Size {
1620        size.width += 2.0 * self.amount;
1621        size.height += 2.0 * self.amount;
1622        size
1623    }
1624}
1625
1626/// GungnirModifier implements the "Neon Glow" aesthetic.
1627/// It uses additive blending and multi-pass blurring to simulate glowing light.
1628#[derive(Debug, Clone, PartialEq)]
1629pub struct GungnirModifier {
1630    pub color: String,
1631    pub radius: f32,
1632    pub intensity: f32,
1633}
1634
1635impl ViewModifier for GungnirModifier {
1636    fn modify<V: View>(self, content: V) -> impl View {
1637        ModifiedView::new(content, self)
1638    }
1639
1640    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1641        // Neon Glow using Mode 1 in the Surtr pipeline
1642        renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1643    }
1644}
1645
1646/// GungnirPulseModifier implements a "breathing" neon effect.
1647#[derive(Debug, Clone, Copy, PartialEq)]
1648pub struct GungnirPulseModifier {
1649    pub color: [f32; 4],
1650    pub radius: f32,
1651    pub speed: f32,
1652}
1653
1654impl ViewModifier for GungnirPulseModifier {
1655    fn modify<V: View>(self, content: V) -> impl View {
1656        ModifiedView::new(content, self)
1657    }
1658
1659    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1660        let time = std::time::SystemTime::now()
1661            .duration_since(std::time::UNIX_EPOCH)
1662            .unwrap_or_default()
1663            .as_secs_f32();
1664
1665        // Mode 19: Dashed Border
1666        // Mode 20: 9-Slice / Patch Scaling
1667        let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1668        let mut color = self.color;
1669        color[3] *= intensity;
1670
1671        // Mode 1 neon glow with dynamic intensity
1672        renderer.stroke_rect(rect, color, self.radius);
1673    }
1674}
1675
1676/// MagneticModifier makes a view "magnetic", subtly leaning towards or pulling the cursor.
1677#[derive(Debug, Clone, Copy, PartialEq)]
1678pub struct MagneticModifier {
1679    pub radius: f32,
1680    pub intensity: f32,
1681}
1682
1683impl ViewModifier for MagneticModifier {
1684    fn modify<V: View>(self, content: V) -> impl View {
1685        ModifiedView::new(content, self)
1686    }
1687
1688    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1689        let [px, py] = renderer.get_pointer_position();
1690        let center_x = rect.x + rect.width / 2.0;
1691        let center_y = rect.y + rect.height / 2.0;
1692
1693        let dx = px - center_x;
1694        let dy = py - center_y;
1695        let dist = (dx * dx + dy * dy).sqrt();
1696
1697        let mut offset_x = 0.0;
1698        let mut offset_y = 0.0;
1699
1700        if dist < self.radius && dist > 0.0 {
1701            let force = (1.0 - dist / self.radius) * self.intensity;
1702            offset_x = dx * force;
1703            offset_y = dy * force;
1704        }
1705
1706        let magnetic_rect = Rect {
1707            x: rect.x + offset_x,
1708            y: rect.y + offset_y,
1709            ..rect
1710        };
1711
1712        view.render(renderer, magnetic_rect);
1713    }
1714}
1715
1716/// ManiGlowModifier adds a soft, lunar-like cursor glow to a view.
1717/// Named after Máni, the personification of the Moon.
1718#[derive(Debug, Clone, Copy, PartialEq)]
1719pub struct ManiGlowModifier {
1720    pub color: [f32; 4],
1721    pub radius: f32,
1722}
1723
1724impl ViewModifier for ManiGlowModifier {
1725    fn modify<V: View>(self, content: V) -> impl View {
1726        ModifiedView::new(content, self)
1727    }
1728
1729    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1730        if crate::load_system_state().realm == Realm::Asgard {
1731            renderer.mani_glow(rect, self.color, self.radius);
1732        }
1733        view.render(renderer, rect);
1734    }
1735}
1736
1737/// BifrostLayerModifier themes a view based on its cognitive memory layer.
1738/// Episodic: Shifting aurora clouds.
1739/// Semantic: Crystalline gold.
1740/// Procedural: Heavy obsidian stone.
1741#[derive(Debug, Clone, Copy, PartialEq)]
1742pub struct BifrostLayerModifier {
1743    pub layer: MemoryLayer,
1744}
1745
1746impl ViewModifier for BifrostLayerModifier {
1747    fn modify<V: View>(self, content: V) -> impl View {
1748        ModifiedView::new(content, self)
1749    }
1750
1751    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1752        let realm = crate::load_system_state().realm;
1753        match self.layer {
1754            MemoryLayer::Episodic => {
1755                if realm == Realm::Asgard {
1756                    renderer.bifrost(rect, 40.0, 1.2, 0.7);
1757                } else {
1758                    renderer.fill_rect(rect, [0.1, 0.12, 0.15, 0.8]);
1759                }
1760            }
1761            MemoryLayer::Semantic => {
1762                if realm == Realm::Asgard {
1763                    renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0, 0.6);
1764                } else {
1765                    renderer.stroke_rect(rect, [0.4, 0.4, 0.4, 1.0], 1.5);
1766                }
1767            }
1768            MemoryLayer::Procedural => {
1769                renderer.fill_rect(rect, [0.05, 0.05, 0.07, 0.95]);
1770                let stroke_color = if realm == Realm::Asgard {
1771                    [0.3, 0.3, 0.3, 1.0]
1772                } else {
1773                    [0.2, 0.2, 0.2, 1.0]
1774                };
1775                renderer.stroke_rect(rect, stroke_color, 2.0);
1776            }
1777        }
1778        view.render(renderer, rect);
1779    }
1780}
1781
1782/// FafnirModifier enables self-evolving UI capabilities.
1783/// Named after Fafnir, the dragon who grows in power based on the gold he hoards.
1784/// In CVKG, 'Gold' is user attention/interaction.
1785#[derive(Debug, Clone, Copy, PartialEq)]
1786pub struct FafnirModifier {
1787    /// Unique ID for tracking this component's vitality across frames.
1788    pub id: u64,
1789}
1790
1791impl ViewModifier for FafnirModifier {
1792    fn modify<V: View>(self, content: V) -> impl View {
1793        ModifiedView::new(content, self)
1794    }
1795
1796    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1797        let state = crate::load_system_state();
1798        let vitality = state
1799            .get_component_state::<f32>(self.id)
1800            .map(|v| *v.read().unwrap())
1801            .unwrap_or(1.0);
1802
1803        // Calculate evolutionary growth factors
1804        // Max growth at vitality 5.0 (50% scale increase, strong glow)
1805        let growth = (vitality - 1.0).clamp(0.0, 4.0);
1806        let scale = 1.0 + growth * 0.12;
1807        let glow_intensity = growth * 0.25;
1808
1809        // Feed Fafnir: Register interaction to boost vitality
1810        let id = self.id;
1811        renderer.register_handler(
1812            "pointermove",
1813            std::sync::Arc::new(move |_| {
1814                crate::update_system_state(|s| {
1815                    let mut s = s.clone();
1816                    let v = s
1817                        .get_component_state::<f32>(id)
1818                        .map(|v| *v.read().unwrap())
1819                        .unwrap_or(1.0);
1820                    s.set_component_state(id, (v + 0.05).min(5.0)); // Cap at 5.0
1821                    s
1822                });
1823            }),
1824        );
1825
1826        if scale > 1.01 {
1827            renderer.push_transform([0.0, 0.0], [scale, scale], 0.0);
1828        }
1829
1830        if glow_intensity > 0.1 && state.realm == Realm::Asgard {
1831            renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0 * vitality, glow_intensity);
1832        }
1833
1834        view.render(renderer, rect);
1835
1836        if scale > 1.01 {
1837            renderer.pop_transform();
1838        }
1839    }
1840}
1841
1842/// MimirIntentModifier anticipates user movement and manifests holographic ghosts.
1843#[derive(Debug, Clone, Copy, PartialEq)]
1844pub struct MimirIntentModifier;
1845
1846impl ViewModifier for MimirIntentModifier {
1847    fn modify<V: View>(self, content: V) -> impl View {
1848        ModifiedView::new(content, self)
1849    }
1850
1851    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1852        let state = crate::load_system_state();
1853        let pos = state.last_pointer_pos;
1854        let vel = state.pointer_velocity;
1855
1856        // Calculate if the cursor is moving towards this rect
1857        let center = [rect.x + rect.width / 2.0, rect.y + rect.height / 2.0];
1858        let dx = center[0] - pos[0];
1859        let dy = center[1] - pos[1];
1860
1861        // Dot product of velocity and direction to center
1862        let dot = vel[0] * dx + vel[1] * dy;
1863        let speed_sq = vel[0] * vel[0] + vel[1] * vel[1];
1864        let dist_sq = dx * dx + dy * dy;
1865
1866        if dot > 0.0 && dist_sq < 250.0 * 250.0 && speed_sq > 0.5 && state.realm == Realm::Asgard {
1867            // Intent detected: render a subtle "ghost" reveal
1868            let intent_strength = (dot / (speed_sq.sqrt() * dist_sq.sqrt())).clamp(0.0, 1.0);
1869            renderer.stroke_rect(rect, [0.0, 0.9, 1.0, 0.3 * intent_strength], 1.5);
1870        }
1871
1872        view.render(renderer, rect);
1873    }
1874}
1875
1876/// KvasirVibeModifier renders a cognitive telemetry cloud representing agent complexity.
1877#[derive(Debug, Clone, Copy, PartialEq)]
1878pub struct KvasirVibeModifier {
1879    pub complexity: f32,
1880}
1881
1882impl ViewModifier for KvasirVibeModifier {
1883    fn modify<V: View>(self, content: V) -> impl View {
1884        ModifiedView::new(content, self)
1885    }
1886
1887    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1888        if crate::load_system_state().realm == Realm::Asgard {
1889            let t = renderer.elapsed_time();
1890            let c = self.complexity.clamp(0.0, 1.0);
1891
1892            // 1. Core Cognitive Cloud (Bifrost)
1893            // Turbulence increases with complexity
1894            let blur = 20.0 + c * 40.0;
1895            let turbulence_x = (t * (1.0 + c * 2.0)).sin() * 8.0 * c;
1896            let turbulence_y = (t * (0.8 + c * 1.5)).cos() * 5.0 * c;
1897            renderer.bifrost(
1898                rect.offset(turbulence_x, turbulence_y),
1899                blur,
1900                0.8 + c * 0.4,
1901                0.25,
1902            );
1903
1904            // 2. Synaptic Discharge (Gungnir pulses)
1905            if c > 0.2 {
1906                let pulse = (t * (3.0 + c * 5.0)).sin().abs() * c;
1907                let color = [0.0, 0.9, 1.0, 0.4 * pulse]; // Cyan synaptic pulse
1908                renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1909            }
1910
1911            // 3. Unstable Resonance (Magenta/Red shift for high complexity)
1912            if c > 0.7 {
1913                let instability = (t * 15.0).cos().abs() * (c - 0.7) * 3.3;
1914                let warning_color = [1.0, 0.0, 0.4, 0.12 * instability];
1915                renderer.fill_rect(rect, warning_color);
1916                renderer.stroke_rect(rect, [1.0, 0.0, 0.2, 0.45 * instability], 1.8);
1917            }
1918        }
1919        view.render(renderer, rect);
1920    }
1921}
1922
1923/// OdinsEyeModifier bestows omniscient observability over the entire scene graph.
1924#[derive(Debug, Clone, Copy, PartialEq)]
1925pub struct OdinsEyeModifier;
1926
1927impl ViewModifier for OdinsEyeModifier {
1928    fn modify<V: View>(self, content: V) -> impl View {
1929        ModifiedView::new(content, self)
1930    }
1931
1932    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1933        let state = crate::load_system_state();
1934        let t = renderer.elapsed_time();
1935
1936        // 1. Render Background content
1937        view.render(renderer, rect);
1938
1939        if state.realm == Realm::Asgard {
1940            // 2. Bestow Odin's Eye (Atmospheric Overlay)
1941            // Soft, large circular pulse representing the 'Eye'
1942            let eye_pulse = (t * 0.5).sin().abs() * 0.05;
1943            renderer.draw_radial_gradient(
1944                rect,
1945                [0.0, 0.6, 0.8, 0.08 + eye_pulse], // Inner Cyan
1946                [0.0, 0.0, 0.0, 0.0],              // Outer Black
1947            );
1948
1949            // 3. Hugin (Thought) Telemetry - Left Side
1950            let hugin_rect = Rect {
1951                x: rect.x + 20.0,
1952                y: rect.y + 40.0,
1953                width: 200.0,
1954                height: rect.height - 80.0,
1955            };
1956            renderer.draw_text(
1957                "HUGIN: THOUGHT",
1958                hugin_rect.x,
1959                hugin_rect.y,
1960                10.0,
1961                [0.0, 1.0, 1.0, 0.6],
1962            );
1963            for (i, thought) in state.thoughts.iter().rev().take(10).enumerate() {
1964                renderer.draw_text(
1965                    thought,
1966                    hugin_rect.x,
1967                    hugin_rect.y + 20.0 + i as f32 * 14.0,
1968                    9.0,
1969                    [1.0, 1.0, 1.0, 0.4],
1970                );
1971            }
1972
1973            // 4. Munin (Memory) Telemetry - Right Side
1974            let munin_rect = Rect {
1975                x: rect.x + rect.width - 220.0,
1976                y: rect.y + 40.0,
1977                width: 200.0,
1978                height: rect.height - 80.0,
1979            };
1980            renderer.draw_text(
1981                "MUNIN: MEMORY",
1982                munin_rect.x,
1983                munin_rect.y,
1984                10.0,
1985                [1.0, 0.84, 0.0, 0.6],
1986            );
1987            for (i, node) in state.nodes.iter().take(10).enumerate() {
1988                let opacity = (node.weight.min(1.0)) * 0.5;
1989                renderer.draw_text(
1990                    &node.id,
1991                    munin_rect.x,
1992                    munin_rect.y + 20.0 + i as f32 * 14.0,
1993                    9.0,
1994                    [1.0, 1.0, 1.0, opacity],
1995                );
1996            }
1997
1998            // 5. Omniscient Focus Beams (Gungnir Beams)
1999            if let Some(focus_id) = &state.odin_focus {
2000                // Visualize causal links to the focus node
2001                renderer.draw_text(
2002                    &format!("EYE FOCUS: {}", focus_id),
2003                    rect.x + rect.width / 2.0 - 50.0,
2004                    rect.y + 20.0,
2005                    12.0,
2006                    [0.0, 1.0, 1.0, 0.8],
2007                );
2008
2009                // In a real implementation, we would find the rect of the focus_id component.
2010                // For the 'Eye', we manifest a central beam of wisdom.
2011                renderer.gungnir(
2012                    Rect {
2013                        x: rect.x + rect.width / 2.0 - 1.0,
2014                        y: rect.y,
2015                        width: 2.0,
2016                        height: rect.height,
2017                    },
2018                    [0.0, 1.0, 1.0, 1.0],
2019                    20.0,
2020                    0.4,
2021                );
2022            }
2023        }
2024    }
2025}
2026
2027/// Sleipnir spring parameters for the physics solver
2028#[derive(Debug, Clone, Copy, PartialEq)]
2029pub struct SleipnirParams {
2030    pub stiffness: f32,
2031    pub damping: f32,
2032    pub mass: f32,
2033}
2034
2035impl SleipnirParams {
2036    pub fn snappy() -> Self {
2037        Self {
2038            stiffness: 230.0,
2039            damping: 22.0,
2040            mass: 1.0,
2041        }
2042    }
2043    pub fn fluid() -> Self {
2044        Self {
2045            stiffness: 170.0,
2046            damping: 26.0,
2047            mass: 1.0,
2048        }
2049    }
2050    pub fn heavy() -> Self {
2051        Self {
2052            stiffness: 90.0,
2053            damping: 20.0,
2054            mass: 1.0,
2055        }
2056    }
2057    pub fn bouncy() -> Self {
2058        Self {
2059            stiffness: 190.0,
2060            damping: 14.0,
2061            mass: 1.0,
2062        }
2063    }
2064}
2065
2066impl Default for SleipnirParams {
2067    fn default() -> Self {
2068        Self::fluid()
2069    }
2070}
2071
2072#[derive(Debug, Clone, Copy, PartialEq)]
2073struct SolverState {
2074    x: f32,
2075    v: f32,
2076}
2077
2078/// SleipnirSolver implements a 4th-order Runge-Kutta (RK4) integration for springs.
2079/// This provides superior stability for high-fidelity interactive motion.
2080#[derive(Debug, Clone, Copy, PartialEq)]
2081pub struct SleipnirSolver {
2082    params: SleipnirParams,
2083    target: f32,
2084    state: SolverState,
2085}
2086
2087impl SleipnirSolver {
2088    /// Create a new solver with a target value and starting state.
2089    pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
2090        Self {
2091            params,
2092            target,
2093            state: SolverState { x: current, v: 0.0 },
2094        }
2095    }
2096
2097    /// Advance the simulation by dt seconds using RK4 integration.
2098    pub fn tick(&mut self, dt: f32) -> f32 {
2099        if dt <= 0.0 {
2100            return self.state.x;
2101        }
2102
2103        // Use a fixed time step for stability if dt is too large
2104        let mut remaining = dt;
2105        let step = 1.0 / 120.0;
2106
2107        while remaining > 0.0 {
2108            let d = remaining.min(step);
2109            self.step(d);
2110            remaining -= d;
2111        }
2112
2113        self.state.x
2114    }
2115
2116    fn step(&mut self, dt: f32) {
2117        let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
2118        let b = self.evaluate(self.state, dt * 0.5, a);
2119        let c = self.evaluate(self.state, dt * 0.5, b);
2120        let d = self.evaluate(self.state, dt, c);
2121
2122        let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
2123        let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
2124
2125        self.state.x += dxdt * dt;
2126        self.state.v += dvdt * dt;
2127    }
2128
2129    fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
2130        let state = SolverState {
2131            x: initial.x + d.x * dt,
2132            v: initial.v + d.v * dt,
2133        };
2134        let force =
2135            -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
2136        let mass = self.params.mass.max(0.001);
2137        SolverState {
2138            x: state.v,
2139            v: force / mass,
2140        }
2141    }
2142
2143    pub fn is_settled(&self) -> bool {
2144        (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
2145    }
2146
2147    pub fn set_target(&mut self, target: f32) {
2148        self.target = target;
2149    }
2150
2151    pub fn current_value(&self) -> f32 {
2152        self.state.x
2153    }
2154}
2155
2156/// SleipnirModifier handles physics-based animations via the Sleipnir RK4 solver.
2157#[derive(Debug, Clone, PartialEq)]
2158pub struct SleipnirModifier {
2159    pub id: u64,
2160    pub target: f32,
2161    pub params: SleipnirParams,
2162}
2163
2164impl ViewModifier for SleipnirModifier {
2165    fn modify<V: View>(self, content: V) -> impl View {
2166        ModifiedView::new(content, self)
2167    }
2168
2169    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2170        let state = load_system_state();
2171
2172        // Try to fetch the solver from persistent state.
2173        let solver_lock_opt = state.get_component_state::<SleipnirSolver>(self.id);
2174
2175        let current_val;
2176
2177        if let Some(lock) = solver_lock_opt {
2178            // Found a solver. Tick it.
2179            let mut solver = lock.write().unwrap();
2180            solver.set_target(self.target);
2181            current_val = solver.tick(renderer.delta_time());
2182
2183            // If the solver hasn't settled yet, request another frame.
2184            if !solver.is_settled() {
2185                renderer.request_redraw();
2186            }
2187        } else {
2188            // First time seeing this ID. Initialize solver state.
2189            let solver = SleipnirSolver::new(
2190                self.params,
2191                self.target,
2192                self.target, // Initialize at target to avoid jump on first frame
2193            );
2194
2195            // Insert into registry for next frame.
2196            get_system_state().rcu(|old| {
2197                let mut new_state = (**old).clone();
2198                new_state.set_component_state(self.id, solver);
2199                new_state
2200            });
2201
2202            current_val = self.target;
2203        }
2204
2205        // Apply the solved value as a vertical translation.
2206        renderer.push_transform([0.0, current_val], [1.0, 1.0], 0.0);
2207        view.render(renderer, rect);
2208        renderer.pop_transform();
2209    }
2210}
2211
2212/// TransformModifier applies a 2D transform (translation, scale, rotation) to its child.
2213/// This modifier is "layout-neutral" and can be animated without re-running the layout engine.
2214#[derive(Debug, Clone, Copy, PartialEq)]
2215pub struct TransformModifier {
2216    pub translation: [f32; 2],
2217    pub scale: [f32; 2],
2218    pub rotation: f32,
2219}
2220
2221impl Default for TransformModifier {
2222    fn default() -> Self {
2223        Self::new()
2224    }
2225}
2226
2227impl TransformModifier {
2228    pub fn new() -> Self {
2229        Self {
2230            translation: [0.0, 0.0],
2231            scale: [1.0, 1.0],
2232            rotation: 0.0,
2233        }
2234    }
2235
2236    pub fn translate(mut self, x: f32, y: f32) -> Self {
2237        self.translation = [x, y];
2238        self
2239    }
2240
2241    pub fn scale(mut self, x: f32, y: f32) -> Self {
2242        self.scale = [x, y];
2243        self
2244    }
2245
2246    pub fn rotate(mut self, radians: f32) -> Self {
2247        self.rotation = radians;
2248        self
2249    }
2250}
2251
2252impl ViewModifier for TransformModifier {
2253    fn modify<V: View>(self, content: V) -> impl View {
2254        ModifiedView::new(content, self)
2255    }
2256
2257    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2258        renderer.push_transform(self.translation, self.scale, self.rotation);
2259        view.render(renderer, rect);
2260        renderer.pop_transform();
2261    }
2262}
2263
2264/// LifecycleModifier handles on_appear and on_disappear hooks.
2265
2266#[derive(Clone)]
2267pub struct LifecycleModifier {
2268    pub on_appear: Option<Arc<dyn Fn() + Send + Sync>>,
2269    pub on_disappear: Option<Arc<dyn Fn() + Send + Sync>>,
2270}
2271
2272impl ViewModifier for LifecycleModifier {
2273    fn modify<V: View>(self, content: V) -> impl View {
2274        ModifiedView::new(content, self)
2275    }
2276}
2277
2278/// OpacityModifier fades this view and all its descendants to the given alpha.
2279/// The renderer is expected to honour `push_opacity`/`pop_opacity` on the Renderer trait.
2280#[derive(Debug, Clone, Copy, PartialEq)]
2281pub struct OpacityModifier {
2282    pub opacity: f32,
2283}
2284
2285impl ViewModifier for OpacityModifier {
2286    fn modify<V: View>(self, content: V) -> impl View {
2287        ModifiedView::new(content, self)
2288    }
2289
2290    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2291        renderer.push_opacity(self.opacity);
2292    }
2293
2294    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2295        renderer.pop_opacity();
2296    }
2297}
2298
2299/// OnClickModifier registers a click handler for this view.
2300#[derive(Clone)]
2301pub struct OnClickModifier {
2302    pub action: Arc<dyn Fn() + Send + Sync>,
2303}
2304
2305impl ViewModifier for OnClickModifier {
2306    fn modify<V: View>(self, content: V) -> impl View {
2307        ModifiedView::new(content, self)
2308    }
2309
2310    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2311        let action = self.action.clone();
2312        renderer.register_handler(
2313            "pointerclick",
2314            std::sync::Arc::new(move |event| {
2315                if let Event::PointerClick { .. } = event {
2316                    (action)();
2317                }
2318            }),
2319        );
2320    }
2321}
2322
2323/// OnPointerEnterModifier registers a pointer enter handler.
2324#[derive(Clone)]
2325pub struct OnPointerEnterModifier {
2326    pub action: Arc<dyn Fn() + Send + Sync>,
2327}
2328
2329impl ViewModifier for OnPointerEnterModifier {
2330    fn modify<V: View>(self, content: V) -> impl View {
2331        ModifiedView::new(content, self)
2332    }
2333
2334    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2335        let action = self.action.clone();
2336        renderer.register_handler(
2337            "pointerenter",
2338            std::sync::Arc::new(move |event| {
2339                if let Event::PointerEnter = event {
2340                    (action)();
2341                }
2342            }),
2343        );
2344    }
2345}
2346
2347/// OnPointerLeaveModifier registers a pointer leave handler.
2348#[derive(Clone)]
2349pub struct OnPointerLeaveModifier {
2350    pub action: Arc<dyn Fn() + Send + Sync>,
2351}
2352
2353impl ViewModifier for OnPointerLeaveModifier {
2354    fn modify<V: View>(self, content: V) -> impl View {
2355        ModifiedView::new(content, self)
2356    }
2357
2358    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2359        let action = self.action.clone();
2360        renderer.register_handler(
2361            "pointerleave",
2362            std::sync::Arc::new(move |event| {
2363                if let Event::PointerLeave = event {
2364                    (action)();
2365                }
2366            }),
2367        );
2368    }
2369}
2370
2371/// OnPointerMoveModifier registers a pointer move handler.
2372#[derive(Clone)]
2373pub struct OnPointerMoveModifier {
2374    pub action: Arc<dyn Fn(f32, f32) + Send + Sync>,
2375}
2376
2377impl ViewModifier for OnPointerMoveModifier {
2378    fn modify<V: View>(self, content: V) -> impl View {
2379        ModifiedView::new(content, self)
2380    }
2381
2382    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2383        let action = self.action.clone();
2384        renderer.register_handler(
2385            "pointermove",
2386            std::sync::Arc::new(move |event| {
2387                if let Event::PointerMove { x, y, .. } = event {
2388                    (action)(x, y);
2389                }
2390            }),
2391        );
2392    }
2393}
2394
2395/// OnPointerDownModifier registers a pointer down handler.
2396#[derive(Clone)]
2397pub struct OnPointerDownModifier {
2398    pub action: Arc<dyn Fn() + Send + Sync>,
2399}
2400
2401impl ViewModifier for OnPointerDownModifier {
2402    fn modify<V: View>(self, content: V) -> impl View {
2403        ModifiedView::new(content, self)
2404    }
2405
2406    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2407        let action = self.action.clone();
2408        renderer.register_handler(
2409            "pointerdown",
2410            std::sync::Arc::new(move |event| {
2411                if let Event::PointerDown { .. } = event {
2412                    (action)();
2413                }
2414            }),
2415        );
2416    }
2417}
2418
2419/// OnPointerUpModifier registers a pointer up handler.
2420#[derive(Clone)]
2421pub struct OnPointerUpModifier {
2422    pub action: Arc<dyn Fn() + Send + Sync>,
2423}
2424
2425impl ViewModifier for OnPointerUpModifier {
2426    fn modify<V: View>(self, content: V) -> impl View {
2427        ModifiedView::new(content, self)
2428    }
2429
2430    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2431        let action = self.action.clone();
2432        renderer.register_handler(
2433            "pointerup",
2434            std::sync::Arc::new(move |event| {
2435                if let Event::PointerUp { .. } = event {
2436                    (action)();
2437                }
2438            }),
2439        );
2440    }
2441}
2442
2443/// ForegroundColorModifier overrides the foreground (text / icon) color inherited
2444/// by all descendants until another ForegroundColorModifier is encountered.
2445#[derive(Debug, Clone, Copy, PartialEq)]
2446pub struct ForegroundColorModifier {
2447    pub color: [f32; 4],
2448}
2449
2450impl ViewModifier for ForegroundColorModifier {
2451    fn modify<V: View>(self, content: V) -> impl View {
2452        ModifiedView::new(content, self)
2453    }
2454}
2455
2456/// ClipModifier restricts all child drawing to the view's layout rectangle.
2457/// The renderer must support `push_clip_rect`/`pop_clip_rect`.
2458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2459pub struct ClipModifier;
2460
2461impl ViewModifier for ClipModifier {
2462    fn modify<V: View>(self, content: V) -> impl View {
2463        ModifiedView::new(content, self)
2464    }
2465
2466    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2467        renderer.push_clip_rect(rect);
2468    }
2469
2470    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2471        renderer.pop_clip_rect();
2472    }
2473}
2474
2475/// BorderModifier draws a solid-color border around the view bounds.
2476#[derive(Debug, Clone, Copy, PartialEq)]
2477pub struct BorderModifier {
2478    pub color: [f32; 4],
2479    pub width: f32,
2480}
2481
2482impl ViewModifier for BorderModifier {
2483    fn modify<V: View>(self, content: V) -> impl View {
2484        ModifiedView::new(content, self)
2485    }
2486
2487    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2488        renderer.stroke_rect(rect, self.color, self.width);
2489    }
2490}
2491
2492// Primitive (leaf) views implement Never as body
2493#[doc(hidden)]
2494pub enum Never {}
2495
2496impl View for Never {
2497    type Body = Never;
2498    fn body(self) -> Never {
2499        unreachable!()
2500    }
2501}
2502
2503/// EmptyView - A view that renders nothing and takes up no space.
2504#[derive(Debug, Clone, Copy, Default)]
2505pub struct EmptyView;
2506
2507impl View for EmptyView {
2508    type Body = Never;
2509    fn body(self) -> Self::Body {
2510        unreachable!()
2511    }
2512    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2513    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
2514        Size {
2515            width: 0.0,
2516            height: 0.0,
2517        }
2518    }
2519}
2520
2521/// A view that has been transformed by a modifier.
2522/// Section 4.3: "Each modifier implements ViewModifier and produces a ModifiedView<Inner, Self>."
2523#[derive(Clone)]
2524pub struct ModifiedView<V, M> {
2525    view: V,
2526    modifier: M,
2527}
2528
2529impl<V: View, M: ViewModifier> ModifiedView<V, M> {
2530    #[doc(hidden)]
2531    pub fn new(view: V, modifier: M) -> Self {
2532        Self { view, modifier }
2533    }
2534}
2535
2536impl<V: View, M: ViewModifier> View for ModifiedView<V, M> {
2537    type Body = ModifiedView<V::Body, M>;
2538
2539    fn body(self) -> Self::Body {
2540        ModifiedView {
2541            view: self.view.body(),
2542            modifier: self.modifier.clone(),
2543        }
2544    }
2545
2546    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2547        self.modifier.render_view(&self.view, renderer, rect);
2548    }
2549
2550    fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
2551        self.modifier.measure_view(&self.view, renderer, proposal)
2552    }
2553
2554    fn flex_weight(&self) -> f32 {
2555        self.modifier.child_flex_weight(&self.view)
2556    }
2557
2558    fn layout(&self) -> Option<&dyn layout::LayoutView> {
2559        self.modifier.layout().or_else(|| self.view.layout())
2560    }
2561
2562    fn get_grid_placement(&self) -> Option<GridPlacement> {
2563        self.modifier
2564            .get_grid_placement()
2565            .or_else(|| self.view.get_grid_placement())
2566    }
2567}
2568
2569pub trait ViewModifier: Send + Clone {
2570    fn modify<V: View>(self, content: V) -> impl View;
2571
2572    /// Returns the grid placement configuration if this modifier defines one.
2573    fn get_grid_placement(&self) -> Option<GridPlacement> {
2574        None
2575    }
2576
2577    /// Core rendering hook called before child views.
2578    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2579
2580    /// Cleanup hook called after child views.
2581    fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2582
2583    /// Allows a modifier to completely override or wrap the rendering of its child.
2584    /// Default implementation performs a standard push -> transform -> render child -> pop sequence.
2585    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2586        self.render(renderer, rect);
2587        let child_rect = self.transform_rect(rect);
2588        view.render(renderer, child_rect);
2589        self.post_render(renderer, rect);
2590    }
2591
2592    fn transform_rect(&self, rect: Rect) -> Rect {
2593        rect
2594    }
2595
2596    /// Allows a modifier to transform the layout proposal before it reaches the child.
2597    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2598        proposal
2599    }
2600
2601    /// Allows a modifier to transform the resulting size from the child.
2602    fn transform_size(&self, size: Size) -> Size {
2603        size
2604    }
2605
2606    /// Measure hook that coordinates size propagation.
2607    fn measure_view<V: View>(
2608        &self,
2609        view: &V,
2610        renderer: &mut dyn Renderer,
2611        proposal: SizeProposal,
2612    ) -> Size {
2613        let child_proposal = self.transform_proposal(proposal);
2614        let child_size = view.intrinsic_size(renderer, child_proposal);
2615        self.transform_size(child_size)
2616    }
2617
2618    /// Allows a modifier to override or pass through the child's flex weight.
2619    fn child_flex_weight<V: View>(&self, view: &V) -> f32 {
2620        view.flex_weight()
2621    }
2622
2623    fn layout(&self) -> Option<&dyn layout::LayoutView> {
2624        None
2625    }
2626}
2627
2628/// TelemetryData tracks real-time performance metrics for the GPU renderer.
2629#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2630pub struct TelemetryData {
2631    pub frame_time_ms: f32,
2632    /// 99th percentile frame time over the last window, used to detect tail latency.
2633    pub p99_frame_time_ms: f32,
2634    /// Statistical jitter (variance in frame timing).
2635    pub frame_jitter_ms: f32,
2636    /// Indicates if a hardware stall (DRAM refresh, thermal spike) was detected.
2637    pub hardware_stall_detected: bool,
2638
2639    // Pass timing
2640    pub input_time_ms: f32,
2641    pub state_flush_time_ms: f32,
2642    pub layout_time_ms: f32,
2643    pub draw_time_ms: f32,
2644    pub gpu_submit_time_ms: f32,
2645
2646    pub draw_calls: u32,
2647    pub vertices: u32,
2648
2649    /// Global Berserker Pipeline Intensity (0.0 - 1.0+)
2650    pub berserker_rage: f32,
2651
2652    // Memory breakdown
2653    pub vram_usage_mb: f32,
2654    pub vram_textures_mb: f32,
2655    pub vram_buffers_mb: f32,
2656    pub vram_pipelines_mb: f32,
2657    /// Indicates if the Mega-Atlas or VRAM pools are at capacity.
2658    pub vram_exhausted: bool,
2659}
2660
2661/// Configuration for render-loop frame timing and degradation strategies.
2662#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2663pub struct FrameBudget {
2664    /// Target frame time in milliseconds (default: 16.0 for 60FPS)
2665    pub target_ms: f32,
2666    /// If true, the renderer is allowed to dynamically skip non-critical effects
2667    /// (like heavy blurs or complex shadows) when the budget is exceeded.
2668    pub allow_degradation: bool,
2669}
2670
2671impl Default for FrameBudget {
2672    fn default() -> Self {
2673        Self {
2674            target_ms: 16.0,
2675            allow_degradation: true,
2676        }
2677    }
2678}
2679
2680/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2681/// This trait is object-safe and used by the View::render system.
2682/// # Implementation Requirements
2683/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2684/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2685/// 3. All operations must be batchable by the underlying backend.
2686///    Trait providing timing information for the render loop.
2687pub trait ElapsedTime {
2688    /// Returns the cumulative time since the renderer started in seconds.
2689    fn elapsed_time(&self) -> f32;
2690
2691    /// Returns the time elapsed since the last frame in seconds.
2692    fn delta_time(&self) -> f32;
2693}
2694
2695/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2696/// This trait is object-safe and used by the View::render system.
2697/// # Implementation Requirements
2698/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2699/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2700/// 3. All operations must be batchable by the underlying backend.
2701pub trait Renderer: ElapsedTime + Send {
2702    /// Requests that the renderer redraws as soon as possible.
2703    /// Used for continuous animations.
2704    fn request_redraw(&mut self) {}
2705
2706    /// Returns true if the current frame is over the time budget.
2707    /// This can be used to skip expensive visual effects.
2708    fn is_over_budget(&self) -> bool {
2709        false
2710    }
2711
2712    // ── Filled shapes ────────────────────────────────────────────────────
2713    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
2714    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
2715    /// Fill an ellipse/circle that fits inside `rect`.
2716    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2717
2718    /// Draw a high-fidelity 3D cube inside the given rectangle using specialized shader logic.
2719    /// `rotation` is [pitch, yaw, roll] in radians.
2720    fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2721
2722    // ── Stroked shapes ───────────────────────────────────────────────────
2723    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2724    fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
2725    /// Stroke an ellipse/circle that fits inside `rect`.
2726    fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2727    /// Draw a straight line from (x1,y1) to (x2,y2).
2728    fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2729    /// Fill a polygon defined by a set of vertices.
2730    fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2731    /// Stroke a polygon defined by a set of vertices.
2732    fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2733
2734    // ── Text ─────────────────────────────────────────────────────────────
2735    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
2736    /// Measure the width and height of the specified text.
2737    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32);
2738
2739    fn shape_rich_text(
2740        &mut self,
2741        _spans: &[cvkg_runic_text::TextSpan],
2742        _max_width: Option<f32>,
2743        _align: cvkg_runic_text::TextAlign,
2744        _overflow: cvkg_runic_text::TextOverflow,
2745    ) -> Option<cvkg_runic_text::ShapedText> {
2746        None
2747    }
2748
2749    fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2750
2751    // ── Images & textures ────────────────────────────────────────────────
2752    /// Draw a texture (GPU-side) at the specified rect.
2753    fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2754    /// Draw an image asset by name or path.
2755    fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2756    /// Load an image asset from memory.
2757    fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2758    /// Pre-warm the renderer with assets. Implementations can use this
2759    /// to populate texture atlases or warm up shader caches.
2760    fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2761
2762    /// Get the current pointer (mouse/touch) position.
2763    fn get_pointer_position(&self) -> [f32; 2] {
2764        [0.0, 0.0]
2765    }
2766
2767    // ── Data Visualization ───────────────────────────────────────────────
2768    /// Upload raw float data as a GPU texture for heatmap rendering.
2769    fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2770    /// Draw a heatmap using a previously uploaded data texture.
2771    fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2772
2773    // ── 3D Objects ───────────────────────────────────────────────────────
2774    /// Draw a 3D mesh.
2775    fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2776
2777    // ── Advanced Visual Effects ──────────────────────────────────────────
2778    /// Draw a linear gradient between two colors at the specified angle.
2779    fn draw_linear_gradient(
2780        &mut self,
2781        _rect: Rect,
2782        _start_color: [f32; 4],
2783        _end_color: [f32; 4],
2784        _angle: f32,
2785    ) {
2786    }
2787    /// Draw a radial gradient between two colors.
2788    fn draw_radial_gradient(
2789        &mut self,
2790        _rect: Rect,
2791        _inner_color: [f32; 4],
2792        _outer_color: [f32; 4],
2793    ) {
2794    }
2795    /// Draw a high-fidelity drop shadow for a rounded rectangle.
2796    fn draw_drop_shadow(
2797        &mut self,
2798        _rect: Rect,
2799        _radius: f32,
2800        _color: [f32; 4],
2801        _blur: f32,
2802        _spread: f32,
2803    ) {
2804    }
2805    /// Draw a dashed border for a rounded rectangle.
2806    fn stroke_dashed_rounded_rect(
2807        &mut self,
2808        _rect: Rect,
2809        _radius: f32,
2810        _color: [f32; 4],
2811        _width: f32,
2812        _dash: f32,
2813        _gap: f32,
2814    ) {
2815    }
2816    /// Draw a 9-slice / patch scaled image.
2817    fn draw_9slice(
2818        &mut self,
2819        _image_name: &str,
2820        _rect: Rect,
2821        _left: f32,
2822        _top: f32,
2823        _right: f32,
2824        _bottom: f32,
2825    ) {
2826    }
2827
2828    // ── Clipping ─────────────────────────────────────────────────────────
2829    /// Push a clip rectangle.  All subsequent drawing is clipped to `rect`.
2830    /// Implementations that do not support clipping may ignore this call.
2831    fn push_clip_rect(&mut self, _rect: Rect) {}
2832    /// Pop the most recently pushed clip rectangle.
2833    fn pop_clip_rect(&mut self) {}
2834    /// Get the current clip rectangle in screen coordinates.
2835    /// Returns a rect covering the entire screen if no clip is active.
2836    fn current_clip_rect(&self) -> Rect {
2837        Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2838    }
2839
2840    // ── Global opacity ───────────────────────────────────────────────────
2841    /// Set a global opacity multiplier applied to all subsequent draw calls
2842    /// until `pop_opacity` is called.  `opacity` is in [0.0, 1.0].
2843    fn push_opacity(&mut self, _opacity: f32) {}
2844    /// Restore the previous opacity level.
2845    fn pop_opacity(&mut self) {}
2846
2847    // ── Berserker Pipeline State ─────────────────────────────────────────
2848    fn set_theme(&mut self, _theme: ColorTheme) {}
2849    fn set_rage(&mut self, _rage: f32) {}
2850    fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2851    fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2852    /// Set the desktop scene preset (Aurora, Void, Nebula, Glitch, Yggdrasil).
2853    fn set_scene(&mut self, _scene: &str) {}
2854
2855    // ── Export & Print ───────────────────────────────────────────────────
2856    /// Capture the current frame as a PNG byte buffer.
2857    fn capture_png(&mut self) -> Vec<u8> {
2858        Vec::new()
2859    }
2860    /// Trigger a native print dialog or spooling operation.
2861    fn print(&mut self) {}
2862
2863    fn set_scene_preset(&mut self, _preset: u32) {}
2864
2865    // ── Cyberpunk Effects ────────────────────────────────────────────────
2866    /// Apply a Bifrost (Frosted Glass) effect to the specified rect.
2867    fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2868    /// Apply a Gungnir (Neon Glow) effect to the specified rect.
2869    fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2870    /// Apply a ManiGlow (Lunar Illuminator) effect.
2871    fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2872    /// Push a Mjolnir Slice (geometric clipping).
2873    fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2874    fn pop_mjolnir_slice(&mut self) {}
2875    /// Execute a render function with memoization.
2876    /// If the renderer supports caching and the `id` + `data_hash` match a previous run,
2877    /// it may replay cached commands instead of executing the function.
2878    fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2879    /// Apply a Mjolnir Shatter effect (fragmentation) to the specified rect.
2880    fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2881    fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2882    /// Draw a Mjolnir Bolt (lightning strike) between two points.
2883    fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2884
2885    // ── Accessibility (ShieldWall) ───────────────────────────────────────
2886    fn set_aria_role(&mut self, _role: &str) {}
2887    fn set_aria_label(&mut self, _label: &str) {}
2888
2889    /// Register a shared element for Bifrost Bridge transitions.
2890    fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2891
2892    /// Set a unique key for the current VDOM node to ensure stable identity during diffing.
2893    fn set_key(&mut self, _key: &str) {}
2894
2895    // ── Telemetry ────────────────────────────────────────────────────────
2896    /// Get real-time performance telemetry.
2897    fn get_telemetry(&self) -> TelemetryData {
2898        TelemetryData::default()
2899    }
2900
2901    // ── GPU State Management ─────────────────────────────────────────────
2902    /// Push a shadow state to the stack. All following draw calls will have this shadow.
2903    fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2904    /// Pop the last shadow state from the stack.
2905    fn pop_shadow(&mut self) {}
2906
2907    // ── VDOM & Scene Graph ───────────────────────────────────────────────
2908    /// Push a Virtual DOM node onto the stack for hierarchy tracking.
2909    fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2910    /// Pop the current Virtual DOM node from the stack.
2911    fn pop_vnode(&mut self) {}
2912    /// Register an event handler for the current VDOM node.
2913    fn register_handler(
2914        &mut self,
2915        _event_type: &str,
2916        _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2917    ) {
2918    }
2919
2920    // ── Z-Index & Depth ──────────────────────────────────────────────────
2921    /// Set the current Z-index for depth sorting.
2922    /// Higher values appear closer to the viewer.
2923    fn set_z_index(&mut self, _z: f32) {}
2924    /// Get the current Z-index.
2925    fn get_z_index(&self) -> f32 {
2926        0.0
2927    }
2928
2929    // ── Vector Graphics ──────────────────────────────────────────────────
2930    /// Load an SVG model from raw bytes.
2931    fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2932    /// Draw a pre-loaded SVG model.
2933    fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2934    /// Serialize a pre-loaded SVG model back to SVG XML markup.
2935    /// Returns the serialized SVG string, or an error if the model is not loaded
2936    /// or serialization is not supported by this renderer.
2937    fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2938        Err("SVG serialization not supported by this renderer".into())
2939    }
2940    /// Apply an SVG filter to a pre-loaded SVG model by filter element ID.
2941    /// The filter is evaluated and the result composited back into the SVG.
2942    /// Returns the filtered SVG as XML, or an error if not supported.
2943    fn apply_svg_filter(
2944        &mut self,
2945        _name: &str,
2946        _filter_id: &str,
2947        _region: Rect,
2948    ) -> Result<String, String> {
2949        Err("SVG filter not supported by this renderer".into())
2950    }
2951
2952    // ── GPU Transformations ──────────────────────────────────────────────
2953    /// Push a 2D transform (translation, scale, rotation) onto the stack.
2954    /// This transform should be applied to all subsequent draw calls until popped.
2955    /// Transform-only animations use this to avoid re-triggering the layout engine.
2956    fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2957    /// Push a raw 2D affine transform matrix [a, b, c, d, e, f] corresponding to
2958    /// [m11, m12, m21, m22, tx, ty].
2959    fn push_affine(&mut self, _transform: [f32; 6]) {}
2960    /// Pop the last 2D transform from the stack.
2961    fn pop_transform(&mut self) {}
2962    /// Return the resolved layout bounds for a specific node ID if it exists.
2963    fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
2964        None
2965    }
2966    /// Enable or disable the layout debug overlay (bounds, padding, margin).
2967    fn set_debug_layout(&mut self, _enabled: bool) {}
2968    /// Check if the layout debug overlay is currently enabled.
2969    fn get_debug_layout(&self) -> bool {
2970        false
2971    }
2972
2973    // ── Material Routing ─────────────────────────────────────────────────
2974    /// Set the active material for subsequent draw calls.
2975    /// Controls which pass a draw call is routed to in the multi-pass pipeline.
2976    fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
2977    /// Return the currently active material (defaults to Opaque).
2978    fn current_material(&self) -> crate::material::DrawMaterial {
2979        crate::material::DrawMaterial::Opaque
2980    }
2981
2982    // ── Vili Interaction Paradigm ──────────────────────────────────────────
2983    /// Compute the user's velocity/intent vector.
2984    fn mimir_intent(&self) -> [f32; 2] {
2985        [0.0, 0.0]
2986    }
2987    /// Calculate magnetic coordinate warp towards an anchor.
2988    fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
2989        if strength <= 0.0 {
2990            return pointer;
2991        }
2992        let cx = anchor_rect.x + anchor_rect.width / 2.0;
2993        let cy = anchor_rect.y + anchor_rect.height / 2.0;
2994        let dx = pointer[0] - cx;
2995        let dy = pointer[1] - cy;
2996        let dist = (dx * dx + dy * dy).sqrt();
2997        let radius = 120.0;
2998        if dist < radius && dist > 0.0 {
2999            let force = (1.0 - dist / radius) * strength;
3000            [pointer[0] - dx * force, pointer[1] - dy * force]
3001        } else {
3002            pointer
3003        }
3004    }
3005    /// Calculate kinematic glow intensity based on proximity.
3006    fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3007        let cx = bounds.x + bounds.width / 2.0;
3008        let cy = bounds.y + bounds.height / 2.0;
3009        let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3010        if dist < radius {
3011            (1.0 - dist / radius).clamp(0.0, 1.0)
3012        } else {
3013            0.0
3014        }
3015    }
3016    /// Calculate dynamic element attention (scaling/morphing) statelessly per frame.
3017    fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3018        let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3019        1.0 + (max_scale - 1.0) * prox
3020    }
3021    /// Sets the precise Vili SDF Shape boundary for hit-testing.
3022    fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3023}
3024
3025/// Utility for accessibility compliance (WCAG 2.1).
3026pub mod accessibility {
3027    /// Calculate the relative luminance of an sRGB color.
3028    pub fn relative_luminance(color: [f32; 4]) -> f32 {
3029        let f = |c: f32| {
3030            if c <= 0.03928 {
3031                c / 12.92
3032            } else {
3033                ((c + 0.055) / 1.055).powf(2.4)
3034            }
3035        };
3036        0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3037    }
3038
3039    /// Calculate the contrast ratio between two colors.
3040    pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3041        let l1 = relative_luminance(c1);
3042        let l2 = relative_luminance(c2);
3043        let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3044        (light + 0.05) / (dark + 0.05)
3045    }
3046}
3047/// Defines the hardware acceleration tier and feature set available to the renderer.
3048#[derive(
3049    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3050)]
3051pub enum RenderTier {
3052    /// High-performance GPU path (WebGPU / Vulkan / Metal / DX12) with full shader support.
3053    Tier1GPU = 0,
3054    /// Mid-tier GPU path (WebGL2 / OpenGL 3.3) with standard shader support.
3055    Tier2GPU = 1,
3056    /// Fallback software or basic hardware path (Canvas 2D / GDI+) with limited effects.
3057    Tier3Fallback = 2,
3058}
3059// =============================================================================
3060// BERSERKER UNIFORMS
3061// =============================================================================
3062use bytemuck::{Pod, Zeroable};
3063/// Fully themeable color palette for the Berserker pipeline.
3064#[repr(C)]
3065#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3066pub struct ColorTheme {
3067    pub primary_neon: [f32; 4], // (R, G, B, intensity)
3068    pub shatter_neon: [f32; 4],
3069    pub glass_base: [f32; 4],
3070    pub glass_edge: [f32; 4],
3071    pub rune_glow: [f32; 4],
3072    pub ember_core: [f32; 4],
3073    pub background_deep: [f32; 4],
3074    pub mani_glow: [f32; 4], // (R, G, B, radius)
3075    pub glass_blur_strength: f32,
3076    pub shatter_edge_width: f32,
3077    pub neon_bloom_radius: f32,
3078    pub rune_opacity: f32,
3079}
3080impl ColorTheme {
3081    /// Asgard Mode: The high-fidelity "Cyberpunk Viking" aesthetic.
3082    pub fn asgard() -> Self {
3083        Self {
3084            primary_neon: [0.0, 1.0, 0.95, 1.2],
3085            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3086            glass_base: [0.04, 0.04, 0.06, 0.82],
3087            glass_edge: [0.0, 0.45, 0.55, 0.6],
3088            rune_glow: [0.75, 0.98, 1.0, 0.9],
3089            ember_core: [0.95, 0.12, 0.12, 1.0],
3090            background_deep: [0.01, 0.01, 0.03, 1.0],
3091            mani_glow: [0.7, 0.9, 1.0, 0.05],
3092            glass_blur_strength: 0.6,
3093            shatter_edge_width: 1.8,
3094            neon_bloom_radius: 0.022,
3095            rune_opacity: 0.55,
3096        }
3097    }
3098
3099    /// Midgard Mode: A clean, functional tactical HUD for standard operations.
3100    pub fn midgard() -> Self {
3101        Self {
3102            primary_neon: [0.2, 0.4, 0.6, 1.0], // Muted blue
3103            shatter_neon: [0.5, 0.5, 0.5, 1.0], // Neutral gray
3104            glass_base: [0.1, 0.12, 0.15, 1.0], // Solid slate
3105            glass_edge: [0.3, 0.35, 0.4, 1.0],  // Subtle border
3106            rune_glow: [0.8, 0.8, 0.8, 0.0],    // Runes disabled
3107            ember_core: [0.5, 0.5, 0.5, 1.0],
3108            background_deep: [0.05, 0.05, 0.07, 1.0],
3109            mani_glow: [0.0, 0.0, 0.0, 0.0], // No cursor glow
3110            glass_blur_strength: 0.0,        // No blur
3111            shatter_edge_width: 1.0,
3112            neon_bloom_radius: 0.0,
3113            rune_opacity: 0.0,
3114        }
3115    }
3116
3117    pub fn cyberpunk_viking() -> Self {
3118        Self::asgard()
3119    }
3120    pub fn vibrant_glass() -> Self {
3121        Self {
3122            primary_neon: [0.0, 1.0, 0.95, 1.2],
3123            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3124            glass_base: [0.55, 0.6, 0.7, 0.08], // Luminous cool tint
3125            glass_edge: [0.7, 0.85, 1.0, 0.45], // Subtle blue-white rim
3126            rune_glow: [0.75, 0.98, 1.0, 0.9],
3127            ember_core: [1.0, 0.4, 0.1, 1.0],
3128            background_deep: [0.05, 0.05, 0.1, 1.0],
3129            mani_glow: [0.7, 0.9, 1.0, 0.05],
3130            glass_blur_strength: 0.9,
3131            shatter_edge_width: 1.8,
3132            neon_bloom_radius: 0.022,
3133            rune_opacity: 0.55,
3134        }
3135    }
3136}
3137impl Default for ColorTheme {
3138    fn default() -> Self {
3139        Self::vibrant_glass()
3140    }
3141}
3142/// Per-frame scene state for the Berserker pipeline.
3143#[repr(C)]
3144#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3145pub struct SceneUniforms {
3146    pub view: glam::Mat4,
3147    pub proj: glam::Mat4,
3148    pub time: f32,
3149    pub delta_time: f32,
3150    pub resolution: [f32; 2],
3151    pub mouse: [f32; 2],
3152    pub mouse_velocity: [f32; 2],
3153    pub shatter_origin: [f32; 2],
3154    pub shatter_time: f32,
3155    pub shatter_force: f32,
3156    pub berzerker_rage: f32,
3157    pub berzerker_mode: u32,
3158    pub scroll_offset: f32,
3159    pub scale_factor: f32,
3160    pub scene_type: u32,
3161    pub _pad: [f32; 3], // Align to 16 bytes if needed, but current struct is 4x16 + 4x16 + 4x16 + ...
3162}
3163
3164pub const SCENE_AURORA: u32 = 0;
3165pub const SCENE_VOID: u32 = 1;
3166pub const SCENE_NEBULA: u32 = 2;
3167pub const SCENE_GLITCH: u32 = 3;
3168pub const SCENE_YGGDRASIL: u32 = 4;
3169
3170impl SceneUniforms {
3171    pub fn new(width: f32, height: f32) -> Self {
3172        Self {
3173            view: glam::Mat4::IDENTITY,
3174            proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3175            time: 0.0,
3176            delta_time: 0.016,
3177            resolution: [width, height],
3178            mouse: [0.5, 0.5],
3179            mouse_velocity: [0.0, 0.0],
3180            shatter_origin: [0.5, 0.5],
3181            shatter_time: -100.0,
3182            shatter_force: 0.0,
3183            berzerker_rage: 0.0,
3184            berzerker_mode: 0,
3185            scroll_offset: 0.0,
3186            scale_factor: 1.0,
3187            scene_type: SCENE_AURORA,
3188            _pad: [0.0; 3],
3189        }
3190    }
3191}
3192/// A 3D mesh containing vertex and index data.
3193#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3194pub struct Mesh {
3195    pub vertices: Vec<[f32; 3]>,
3196    pub normals: Vec<[f32; 3]>,
3197    pub indices: Vec<u32>,
3198}
3199impl Mesh {
3200    pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3201        let mut cursor = std::io::Cursor::new(data);
3202        let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3203            Ok((Vec::new(), Default::default()))
3204        })?;
3205        let mut meshes = Vec::new();
3206        for m in models {
3207            let mesh = m.mesh;
3208            let vertices: Vec<[f32; 3]> = mesh
3209                .positions
3210                .chunks(3)
3211                .map(|c| [c[0], c[1], c[2]])
3212                .collect();
3213            let normals = if mesh.normals.is_empty() {
3214                vec![[0.0, 0.0, 1.0]; vertices.len()]
3215            } else {
3216                mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3217            };
3218            meshes.push(Mesh {
3219                vertices,
3220                normals,
3221                indices: mesh.indices,
3222            });
3223        }
3224        Ok(meshes)
3225    }
3226    pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3227        let mut cursor = std::io::Cursor::new(data);
3228        let stl = stl_io::read_stl(&mut cursor)?;
3229        let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3230        let mut indices = Vec::new();
3231        for face in stl.faces {
3232            indices.push(face.vertices[0] as u32);
3233            indices.push(face.vertices[1] as u32);
3234            indices.push(face.vertices[2] as u32);
3235        }
3236        let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3237        Ok(Mesh {
3238            vertices,
3239            normals,
3240            indices,
3241        })
3242    }
3243}
3244/// FrameRenderer extends Renderer with frame lifecycle management.
3245/// It is typically implemented by the host windowing/rendering environment.
3246pub trait FrameRenderer<E = ()>: Renderer {
3247    fn begin_frame(&mut self) -> E;
3248    fn render_frame(&mut self) {
3249        // Default implementation does nothing - override for custom frame rendering
3250    }
3251    fn end_frame(&mut self, encoder: E);
3252}
3253use std::sync::Arc;
3254type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3255/// State wrapper that owns a value and notifies subscribers when changed
3256#[derive(Clone)]
3257pub struct State<T: Clone + Send + Sync + 'static> {
3258    swap: Arc<arc_swap::ArcSwap<T>>,
3259    metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3260    #[cfg(not(target_arch = "wasm32"))]
3261    tvar: Arc<stm::TVar<T>>,
3262    #[cfg(not(target_arch = "wasm32"))]
3263    metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3264    subscribers: SubscriberList<T>,
3265    version: Arc<std::sync::atomic::AtomicU64>,
3266    resolution: agents::ConflictResolution,
3267}
3268impl<T: Clone + Send + Sync + 'static> State<T> {
3269    /// Create a new State with initial value
3270    pub fn new(value: T) -> Self {
3271        #[cfg(not(target_arch = "wasm32"))]
3272        let tvar = Arc::new(stm::TVar::new(value.clone()));
3273        #[cfg(not(target_arch = "wasm32"))]
3274        let metadata_tvar = Arc::new(stm::TVar::new(None));
3275        Self {
3276            swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3277            metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3278            #[cfg(not(target_arch = "wasm32"))]
3279            tvar,
3280            #[cfg(not(target_arch = "wasm32"))]
3281            metadata_tvar,
3282            subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3283            version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3284            resolution: agents::ConflictResolution::default(),
3285        }
3286    }
3287    /// Set the conflict resolution strategy for this state.
3288    pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3289        self.resolution = resolution;
3290        self
3291    }
3292    /// Get the current value
3293    pub fn get(&self) -> T {
3294        (**self.swap.load()).clone()
3295    }
3296    /// Set a new value, notifying all subscribers. Applies conflict resolution if agents are present.
3297    pub fn set(&self, value: T) {
3298        #[cfg(not(target_arch = "wasm32"))]
3299        let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3300            let new_meta = agents::get_current_mutation_metadata();
3301            let existing_meta = self.metadata_tvar.read(tx)?;
3302            let mut skip = false;
3303            if self.resolution == agents::ConflictResolution::PriorityWins
3304                && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3305                && new_m.priority < old_m.priority
3306            {
3307                skip = true;
3308            }
3309            if !skip {
3310                self.tvar.write(tx, value.clone())?;
3311                self.metadata_tvar.write(tx, new_meta)?;
3312                Ok((false, value.clone(), new_meta))
3313            } else {
3314                Ok((true, self.tvar.read(tx)?, existing_meta))
3315            }
3316        });
3317        #[cfg(target_arch = "wasm32")]
3318        let (was_skipped, final_val, final_meta) =
3319            (false, value, agents::get_current_mutation_metadata());
3320        if was_skipped {
3321            if let (Some(new_m), Some(old_m)) =
3322                (agents::get_current_mutation_metadata(), final_meta)
3323            {
3324                agents::notify_conflict(agents::ConflictEvent {
3325                    agent_id: new_m.agent_id,
3326                    priority: new_m.priority,
3327                    existing_agent_id: old_m.agent_id,
3328                    existing_priority: old_m.priority,
3329                    timestamp_ms: new_m.timestamp_ms,
3330                });
3331            }
3332            return;
3333        }
3334        self.swap.store(Arc::new(final_val.clone()));
3335        self.metadata_swap.store(Arc::new(final_meta));
3336        self.version
3337            .fetch_add(1, std::sync::atomic::Ordering::Release);
3338        let subs = Arc::clone(&self.subscribers);
3339        if crate::is_batching() {
3340            crate::enqueue_batch_task(Box::new(move || {
3341                let s = subs.lock().unwrap();
3342                for cb in s.iter() {
3343                    cb(&final_val);
3344                }
3345            }));
3346        } else {
3347            let s = subs.lock().unwrap();
3348            for cb in s.iter() {
3349                cb(&final_val);
3350            }
3351        }
3352    }
3353    pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3354        #[cfg(not(target_arch = "wasm32"))]
3355        {
3356            let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3357                let new_meta = agents::get_current_mutation_metadata();
3358                let existing_meta = self.metadata_tvar.read(tx)?;
3359                let mut skip = false;
3360                if self.resolution == agents::ConflictResolution::PriorityWins
3361                    && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3362                    && new_m.priority < old_m.priority
3363                {
3364                    skip = true;
3365                }
3366                if !skip {
3367                    let current = self.tvar.read(tx)?;
3368                    let next = f(&current);
3369                    self.tvar.write(tx, next.clone())?;
3370                    self.metadata_tvar.write(tx, new_meta)?;
3371                    Ok((false, next, new_meta))
3372                } else {
3373                    Ok((true, self.tvar.read(tx)?, existing_meta))
3374                }
3375            });
3376            if was_skipped {
3377                if let (Some(new_m), Some(old_m)) =
3378                    (agents::get_current_mutation_metadata(), final_meta)
3379                {
3380                    agents::notify_conflict(agents::ConflictEvent {
3381                        agent_id: new_m.agent_id,
3382                        priority: new_m.priority,
3383                        existing_agent_id: old_m.agent_id,
3384                        existing_priority: old_m.priority,
3385                        timestamp_ms: new_m.timestamp_ms,
3386                    });
3387                }
3388                return;
3389            }
3390            self.swap.store(Arc::new(final_val.clone()));
3391            self.metadata_swap.store(Arc::new(final_meta));
3392            self.version
3393                .fetch_add(1, std::sync::atomic::Ordering::Release);
3394            let subs = Arc::clone(&self.subscribers);
3395            if crate::is_batching() {
3396                crate::enqueue_batch_task(Box::new(move || {
3397                    let s = subs.lock().unwrap();
3398                    for cb in s.iter() {
3399                        cb(&final_val);
3400                    }
3401                }));
3402            } else {
3403                let s = subs.lock().unwrap();
3404                for cb in s.iter() {
3405                    cb(&final_val);
3406                }
3407            }
3408        }
3409        #[cfg(target_arch = "wasm32")]
3410        {
3411            self.set(f(&self.get()));
3412        }
3413    }
3414    /// Get current version
3415    pub fn version(&self) -> u64 {
3416        self.version.load(std::sync::atomic::Ordering::Acquire)
3417    }
3418    /// Subscribe to state changes
3419    pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3420        self.subscribers.lock().unwrap().push(Box::new(callback));
3421    }
3422}
3423use crate::runtime::NodeStateSnapshot;
3424use std::sync::OnceLock;
3425use std::sync::atomic::{AtomicBool, Ordering};
3426/// Global application state registry.
3427pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3428#[cfg(not(target_arch = "wasm32"))]
3429static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3430static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3431pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3432pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3433type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3434static BATCH_QUEUE: BatchQueue = OnceLock::new();
3435/// Global write lock to serialize updates to SYSTEM_STATE and KNOWLEDGE_TVAR,
3436/// preventing parallel race conditions between STM transactions and the lock-free reader state.
3437static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3438/// Returns true if state updates are currently being batched.
3439pub fn is_batching() -> bool {
3440    IS_BATCHING.load(Ordering::Acquire)
3441}
3442/// Returns true if the system is currently in the render phase.
3443pub fn is_rendering() -> bool {
3444    IS_RENDERING.load(Ordering::Acquire)
3445}
3446/// Signals the start of the render phase. Mutations during this phase trigger warnings.
3447pub fn begin_render_phase() {
3448    IS_RENDERING.store(true, Ordering::Release);
3449}
3450/// Signals the end of the render phase.
3451pub fn end_render_phase() {
3452    IS_RENDERING.store(false, Ordering::Release);
3453}
3454/// Enqueues a notification task to be run when the current batch flushes.
3455pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3456    let mut queue = BATCH_QUEUE
3457        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3458        .lock()
3459        .unwrap();
3460    queue.push(task);
3461}
3462/// Executes multiple state updates in a single batch, deferring all subscriber
3463/// notifications until the closure completes. This prevents layout thrashing
3464/// and redundant render cycles when modifying multiple independent states.
3465pub fn batch<F: FnOnce()>(f: F) {
3466    if IS_BATCHING.swap(true, Ordering::AcqRel) {
3467        // Already inside a batch, just execute
3468        f();
3469        return;
3470    }
3471    f();
3472    IS_BATCHING.store(false, Ordering::Release);
3473    let mut queue = BATCH_QUEUE
3474        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3475        .lock()
3476        .unwrap();
3477    let tasks: Vec<_> = queue.drain(..).collect();
3478    drop(queue);
3479    for task in tasks {
3480        task();
3481    }
3482}
3483/// Get a reference to the global system state.
3484pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3485    SYSTEM_STATE
3486        .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3487        .clone()
3488}
3489pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3490    get_system_state().load()
3491}
3492pub fn update_system_state<F>(f: F)
3493where
3494    F: FnOnce(&KnowledgeState) -> KnowledgeState,
3495{
3496    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3497    if is_rendering() {
3498        log::warn!(
3499            "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3500        );
3501    }
3502    LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3503    let swap = get_system_state();
3504    let current = swap.load();
3505    let new_state = Arc::new(f(&current));
3506    swap.store(Arc::clone(&new_state));
3507    #[cfg(not(target_arch = "wasm32"))]
3508    {
3509        let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3510        stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3511    }
3512}
3513pub fn transact_system_state<F>(f: F)
3514where
3515    F: Fn(&KnowledgeState) -> KnowledgeState,
3516{
3517    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3518    #[cfg(not(target_arch = "wasm32"))]
3519    {
3520        if is_rendering() {
3521            log::warn!(
3522                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3523            );
3524        }
3525        let tvar = KNOWLEDGE_TVAR
3526            .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3527            .clone();
3528        let new_state = stm::atomically(move |tx| {
3529            let current = tvar.read(tx)?;
3530            let next = f(&current);
3531            tvar.write(tx, next.clone())?;
3532            Ok(next)
3533        });
3534        get_system_state().store(Arc::new(new_state));
3535    }
3536    #[cfg(target_arch = "wasm32")]
3537    {
3538        if is_rendering() {
3539            log::warn!(
3540                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3541            );
3542        }
3543        update_system_state(f);
3544    }
3545}
3546impl KnowledgeState {
3547    /// Create a new empty KnowledgeState.
3548    pub fn new() -> Self {
3549        Self::default()
3550    }
3551    /// Set a component's internal state.
3552    pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3553        self.component_states
3554            .insert(id, Arc::new(std::sync::RwLock::new(state)));
3555    }
3556    /// Get a reference to a component's internal state.
3557    pub fn get_component_state<T: 'static + Send + Sync>(
3558        &self,
3559        id: u64,
3560    ) -> Option<Arc<std::sync::RwLock<T>>> {
3561        let lock = self.component_states.get(&id)?;
3562        // Attempt to clone the Arc and downcast the inner RwLock<dyn Any> to RwLock<T>
3563        // We use a two-step approach: check if the inner type matches via Any, then transmute the Arc
3564        // SAFETY: We verify the type via Any::is::<T> before transmuting
3565        let any_ref = lock.read().ok()?;
3566        if any_ref.is::<T>() {
3567            // Type matches — safe to transmute the Arc
3568            drop(any_ref);
3569            let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3570            // Transmute Arc<RwLock<dyn Any>> to Arc<RwLock<T>>
3571            // This is safe because we just verified the inner type is T
3572            Some(unsafe {
3573                let raw = Arc::into_raw(cloned);
3574                Arc::from_raw(raw as *const std::sync::RwLock<T>)
3575            })
3576        } else {
3577            None
3578        }
3579    }
3580    /// Add a new fragment to memory.
3581    pub fn remember(&mut self, fragment: KnowledgeFragment) {
3582        self.fragments.insert(fragment.id.clone(), fragment);
3583    }
3584    /// Process a search query against the local knowledge base.
3585    pub fn process_query(&mut self, query: &str) {
3586        let query_lower = query.to_lowercase();
3587        let mut results: Vec<(f32, String)> = self
3588            .fragments
3589            .iter()
3590            .map(|(id, frag)| {
3591                let mut score = 0.0;
3592                if frag.summary.to_lowercase().contains(&query_lower) {
3593                    score += 1.0;
3594                }
3595                if frag.source.to_lowercase().contains(&query_lower) {
3596                    score += 0.5;
3597                }
3598                (score, id.clone())
3599            })
3600            .filter(|(score, _)| *score > 0.0)
3601            .collect();
3602        // Sort by relevance score
3603        results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3604        self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3605    }
3606    /// Captures a snapshot of the current state for debugging and hot-reloading.
3607    pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3608        let mut snapshots = Vec::new();
3609        // Snapshots of agentic fragments
3610        for frag in self.fragments.values() {
3611            if let Ok(val) = serde_json::to_value(frag) {
3612                snapshots.push(NodeStateSnapshot { id: 0, state: val });
3613            }
3614        }
3615        snapshots
3616    }
3617}
3618/// A read/write projection into a `State<T>` owned elsewhere.
3619#[derive(Clone)]
3620pub struct Binding<T: Clone + Send + Sync + 'static> {
3621    swap: Arc<arc_swap::ArcSwap<T>>,
3622    #[cfg(not(target_arch = "wasm32"))]
3623    tvar: Arc<stm::TVar<T>>,
3624    version: Arc<std::sync::atomic::AtomicU64>,
3625}
3626impl<T: Clone + Send + Sync + 'static> Binding<T> {
3627    /// Create a binding from a State
3628    pub fn from_state(state: &State<T>) -> Self {
3629        Self {
3630            swap: Arc::clone(&state.swap),
3631            #[cfg(not(target_arch = "wasm32"))]
3632            tvar: Arc::clone(&state.tvar),
3633            version: Arc::clone(&state.version),
3634        }
3635    }
3636    /// Get the current value
3637    pub fn get(&self) -> T {
3638        (**self.swap.load()).clone()
3639    }
3640    /// Set a new value
3641    pub fn set(&self, value: T) {
3642        self.swap.store(Arc::new(value.clone()));
3643        #[cfg(not(target_arch = "wasm32"))]
3644        {
3645            let tvar = Arc::clone(&self.tvar);
3646            let v = value.clone();
3647            stm::atomically(move |tx| tvar.write(tx, v.clone()));
3648        }
3649        self.version
3650            .fetch_add(1, std::sync::atomic::Ordering::Release);
3651    }
3652    /// Get current version
3653    pub fn version(&self) -> u64 {
3654        self.version.load(std::sync::atomic::Ordering::Acquire)
3655    }
3656}
3657#[cfg(not(target_arch = "wasm32"))]
3658pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3659where
3660    A: Clone + Send + Sync + 'static,
3661    B: Clone + Send + Sync + 'static,
3662    F: Fn(&A, &B) -> (A, B),
3663{
3664    let tvar_a = Arc::clone(&state_a.tvar);
3665    let tvar_b = Arc::clone(&state_b.tvar);
3666    let (new_a, new_b) = stm::atomically(move |tx| {
3667        let a = tvar_a.read(tx)?;
3668        let b = tvar_b.read(tx)?;
3669        let (na, nb) = f(&a, &b);
3670        tvar_a.write(tx, na.clone())?;
3671        tvar_b.write(tx, nb.clone())?;
3672        Ok((na, nb))
3673    });
3674    state_a.swap.store(Arc::new(new_a.clone()));
3675    state_b.swap.store(Arc::new(new_b.clone()));
3676    state_a
3677        .version
3678        .fetch_add(1, std::sync::atomic::Ordering::Release);
3679    state_b
3680        .version
3681        .fetch_add(1, std::sync::atomic::Ordering::Release);
3682    let subs_a = Arc::clone(&state_a.subscribers);
3683    let subs_b = Arc::clone(&state_b.subscribers);
3684    if crate::is_batching() {
3685        crate::enqueue_batch_task(Box::new(move || {
3686            {
3687                let s = subs_a.lock().unwrap();
3688                for cb in s.iter() {
3689                    cb(&new_a);
3690                }
3691            }
3692            {
3693                let s = subs_b.lock().unwrap();
3694                for cb in s.iter() {
3695                    cb(&new_b);
3696                }
3697            }
3698        }));
3699    } else {
3700        {
3701            let s = subs_a.lock().unwrap();
3702            for cb in s.iter() {
3703                cb(&new_a);
3704            }
3705        }
3706        {
3707            let s = subs_b.lock().unwrap();
3708            for cb in s.iter() {
3709                cb(&new_b);
3710            }
3711        }
3712    }
3713}
3714use std::any::TypeId;
3715use std::sync::Mutex;
3716/// Global environment storage using TypeId as keys.
3717pub(crate) static ENVIRONMENT: OnceLock<
3718    Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3719> = OnceLock::new();
3720/// Environment key type for accessing ambient values
3721/// Implement this trait to define a new environment key.
3722pub trait EnvKey: 'static + Send + Sync {
3723    /// The type of value stored in the environment
3724    type Value: Clone + Send + Sync + 'static;
3725    /// Get a default value for this key
3726    fn default_value() -> Self::Value;
3727}
3728/// Key for accessing the Yggdrasil design token tree
3729pub struct YggdrasilKey;
3730impl EnvKey for YggdrasilKey {
3731    type Value = YggdrasilTokens;
3732    fn default_value() -> Self::Value {
3733        default_tokens()
3734    }
3735}
3736// Duplicate AssetKey removed - original definition at line 63
3737/// System appearance (Light/Dark mode)
3738#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3739pub enum Appearance {
3740    Light,
3741    Dark,
3742}
3743/// Orientation for layouts
3744#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3745pub enum Orientation {
3746    Horizontal,
3747    Vertical,
3748}
3749/// Placement configuration for placing a view within a Grid layout.
3750#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3751pub struct GridPlacement {
3752    /// 0-based column index. Negative values count from the end of columns.
3753    pub column: i32,
3754    /// Number of columns the view spans (default is 1).
3755    pub column_span: u32,
3756    /// 0-based row index. Negative values count from the end of rows.
3757    pub row: i32,
3758    /// Number of rows the view spans (default is 1).
3759    pub row_span: u32,
3760}
3761/// Cross-axis alignment for layout containers.
3762#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3763pub enum Alignment {
3764    #[default]
3765    Center,
3766    Leading,
3767    Trailing,
3768    Top,
3769    Bottom,
3770}
3771/// Main-axis distribution for linear layout containers.
3772#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3773pub enum Distribution {
3774    #[default]
3775    Fill,
3776    Center,
3777    Leading,
3778    Trailing,
3779    SpaceBetween,
3780    SpaceAround,
3781    SpaceEvenly,
3782}
3783/// A color represented by RGBA components in the [0.0, 1.0] range.
3784#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
3785pub struct Color {
3786    pub r: f32,
3787    pub g: f32,
3788    pub b: f32,
3789    pub a: f32,
3790}
3791impl Color {
3792    pub const BLACK: Color = Color {
3793        r: 0.0,
3794        g: 0.0,
3795        b: 0.0,
3796        a: 1.0,
3797    };
3798    pub const WHITE: Color = Color {
3799        r: 1.0,
3800        g: 1.0,
3801        b: 1.0,
3802        a: 1.0,
3803    };
3804    pub const TRANSPARENT: Color = Color {
3805        r: 0.0,
3806        g: 0.0,
3807        b: 0.0,
3808        a: 0.0,
3809    };
3810    pub const RED: Color = Color {
3811        r: 1.0,
3812        g: 0.0,
3813        b: 0.0,
3814        a: 1.0,
3815    };
3816    pub const GREEN: Color = Color {
3817        r: 0.0,
3818        g: 1.0,
3819        b: 0.0,
3820        a: 1.0,
3821    };
3822    pub const BLUE: Color = Color {
3823        r: 0.0,
3824        g: 0.0,
3825        b: 1.0,
3826        a: 1.0,
3827    };
3828    pub const VIKING_GOLD: Color = Color {
3829        r: 1.0,
3830        g: 0.84,
3831        b: 0.0,
3832        a: 1.0,
3833    };
3834    pub const MAGENTA_LIQUID: Color = Color {
3835        r: 1.0,
3836        g: 0.0,
3837        b: 1.0,
3838        a: 1.0,
3839    };
3840    pub const TACTICAL_OBSIDIAN: Color = Color {
3841        r: 0.05,
3842        g: 0.05,
3843        b: 0.07,
3844        a: 1.0,
3845    };
3846    /// Calculate the relative luminance of the color as defined by WCAG 2.x
3847    pub fn relative_luminance(&self) -> f32 {
3848        fn res(c: f32) -> f32 {
3849            if c <= 0.03928 {
3850                c / 12.92
3851            } else {
3852                ((c + 0.055) / 1.055).powf(2.4)
3853            }
3854        }
3855        0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
3856    }
3857    /// Calculate the contrast ratio between this color and another color
3858    pub fn contrast_ratio(&self, other: &Color) -> f32 {
3859        let l1 = self.relative_luminance();
3860        let l2 = other.relative_luminance();
3861        if l1 > l2 {
3862            (l1 + 0.05) / (l2 + 0.05)
3863        } else {
3864            (l2 + 0.05) / (l1 + 0.05)
3865        }
3866    }
3867    pub const CYAN: Color = Color {
3868        r: 0.0,
3869        g: 1.0,
3870        b: 1.0,
3871        a: 1.0,
3872    };
3873    pub const YELLOW: Color = Color {
3874        r: 1.0,
3875        g: 1.0,
3876        b: 0.0,
3877        a: 1.0,
3878    };
3879    pub const MAGENTA: Color = Color {
3880        r: 1.0,
3881        g: 0.0,
3882        b: 1.0,
3883        a: 1.0,
3884    };
3885    pub const GRAY: Color = Color {
3886        r: 0.5,
3887        g: 0.5,
3888        b: 0.5,
3889        a: 1.0,
3890    };
3891    /// Create a new color from RGBA components.
3892    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
3893        Self { r, g, b, a }
3894    }
3895    /// Convert the color to a [r, g, b, a] array.
3896    pub fn as_array(&self) -> [f32; 4] {
3897        [self.r, self.g, self.b, self.a]
3898    }
3899
3900    /// Return a new color with lightness increased by `amount`.
3901    ///
3902    /// Adds `amount` to each RGB channel and clamps to [0.0, 1.0].
3903    /// This is a simple sRGB lightness adjustment, not perceptually uniform.
3904    /// For perceptually uniform adjustments, use OKLCH via cvkg-themes.
3905    pub fn lighten(&self, amount: f32) -> Self {
3906        Self {
3907            r: (self.r + amount).clamp(0.0, 1.0),
3908            g: (self.g + amount).clamp(0.0, 1.0),
3909            b: (self.b + amount).clamp(0.0, 1.0),
3910            a: self.a,
3911        }
3912    }
3913
3914    /// Return a new color with lightness decreased by `amount`.
3915    pub fn darken(&self, amount: f32) -> Self {
3916        Self {
3917            r: (self.r - amount).clamp(0.0, 1.0),
3918            g: (self.g - amount).clamp(0.0, 1.0),
3919            b: (self.b - amount).clamp(0.0, 1.0),
3920            a: self.a,
3921        }
3922    }
3923}
3924impl View for Color {
3925    type Body = Never;
3926    fn body(self) -> Self::Body {
3927        unreachable!()
3928    }
3929    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
3930        renderer.fill_rect(rect, self.as_array());
3931    }
3932}
3933/// Key for accessing the current system appearance
3934pub struct AppearanceKey;
3935impl EnvKey for AppearanceKey {
3936    type Value = Appearance;
3937    fn default_value() -> Self::Value {
3938        Appearance::Dark // Default to Dark (Ginnungagap) for Berserker aesthetic
3939    }
3940}
3941/// StyleResolver provides high-level access to themed values from the environment.
3942pub struct StyleResolver;
3943impl StyleResolver {
3944    /// Resolve a color from the current environment
3945    pub fn color(key: &str) -> String {
3946        let tokens = Environment::<YggdrasilKey>::new().get();
3947        let appearance = Environment::<AppearanceKey>::new().get();
3948        let is_dark = appearance == Appearance::Dark;
3949        tokens
3950            .get_color(key, is_dark)
3951            .unwrap_or_else(|| "#FF00FF".to_string()) // Default to MuspelMagenta on failure
3952    }
3953    /// Resolve a generic token value
3954    pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
3955        let tokens = Environment::<YggdrasilKey>::new().get();
3956        let appearance = Environment::<AppearanceKey>::new().get();
3957        let is_dark = appearance == Appearance::Dark;
3958        tokens.get(category, key, is_dark)
3959    }
3960    /// Resolve a color from the current environment as a [f32; 4] RGBA array.
3961    /// Returns the color value for the current appearance (light/dark).
3962    /// Falls back to magenta (#FF00FF) if the key is not found.
3963    pub fn color_array(key: &str) -> [f32; 4] {
3964        let hex = Self::color(key);
3965        parse_hex_color(&hex)
3966    }
3967}
3968
3969/// Parse a hex color string (#RRGGBB or #RRGGBBAA) into [f32; 4] RGBA.
3970fn parse_hex_color(hex: &str) -> [f32; 4] {
3971    let hex = hex.trim_start_matches('#');
3972    if hex.len() >= 6 {
3973        let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
3974        let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
3975        let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
3976        let a = if hex.len() >= 8 {
3977            u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
3978        } else {
3979            1.0
3980        };
3981        [r, g, b, a]
3982    } else {
3983        [1.0, 0.0, 1.0, 1.0] // Magenta fallback
3984    }
3985}
3986
3987/// The authoritative Cyberpunk Viking default tokens
3988pub fn default_tokens() -> YggdrasilTokens {
3989    let mut tokens = YggdrasilTokens::new();
3990    // Core Norse Colorways
3991    tokens.color.insert(
3992        "background".to_string(),
3993        TokenValue::Single {
3994            value: "#000000".to_string(), // Ginnungagap (The Void)
3995        },
3996    );
3997    tokens.color.insert(
3998        "primary".to_string(),
3999        TokenValue::Single {
4000            value: "#00FFFF".to_string(), // NiflCyan (Aesir Primary)
4001        },
4002    );
4003    tokens.color.insert(
4004        "secondary".to_string(),
4005        TokenValue::Single {
4006            value: "#FF00FF".to_string(), // MuspelMagenta (Berserker Secondary)
4007        },
4008    );
4009    tokens.color.insert(
4010        "surface".to_string(),
4011        TokenValue::Adaptive {
4012            light: "#FFFFFF".to_string(),
4013            dark: "#121212".to_string(),
4014        },
4015    );
4016    tokens.color.insert(
4017        "text".to_string(),
4018        TokenValue::Adaptive {
4019            light: "#000000".to_string(),
4020            dark: "#FFFFFF".to_string(),
4021        },
4022    );
4023    // Semantic component tokens
4024    tokens.color.insert(
4025        "surface_elevated".to_string(),
4026        TokenValue::Adaptive {
4027            light: "#FFFFFF".to_string(),
4028            dark: "#1A1A24".to_string(),
4029        },
4030    );
4031    tokens.color.insert(
4032        "surface_overlay".to_string(),
4033        TokenValue::Adaptive {
4034            light: "#FFFFFF".to_string(),
4035            dark: "#1E1E2E".to_string(),
4036        },
4037    );
4038    tokens.color.insert(
4039        "border".to_string(),
4040        TokenValue::Adaptive {
4041            light: "#D0D0D8".to_string(),
4042            dark: "#2A2A3A".to_string(),
4043        },
4044    );
4045    tokens.color.insert(
4046        "border_strong".to_string(),
4047        TokenValue::Adaptive {
4048            light: "#A0A0B0".to_string(),
4049            dark: "#3A3A50".to_string(),
4050        },
4051    );
4052    tokens.color.insert(
4053        "text_muted".to_string(),
4054        TokenValue::Adaptive {
4055            light: "#606070".to_string(),
4056            dark: "#8080A0".to_string(),
4057        },
4058    );
4059    tokens.color.insert(
4060        "text_dim".to_string(),
4061        TokenValue::Adaptive {
4062            light: "#9090A0".to_string(),
4063            dark: "#505070".to_string(),
4064        },
4065    );
4066    tokens.color.insert(
4067        "accent".to_string(),
4068        TokenValue::Single {
4069            value: "#00FFFF".to_string(), // NiflCyan
4070        },
4071    );
4072    tokens.color.insert(
4073        "accent_hover".to_string(),
4074        TokenValue::Single {
4075            value: "#33FFFF".to_string(),
4076        },
4077    );
4078    tokens.color.insert(
4079        "success".to_string(),
4080        TokenValue::Single {
4081            value: "#00E676".to_string(),
4082        },
4083    );
4084    tokens.color.insert(
4085        "warning".to_string(),
4086        TokenValue::Single {
4087            value: "#FFB300".to_string(),
4088        },
4089    );
4090    tokens.color.insert(
4091        "error".to_string(),
4092        TokenValue::Single {
4093            value: "#FF5252".to_string(),
4094        },
4095    );
4096    tokens.color.insert(
4097        "info".to_string(),
4098        TokenValue::Single {
4099            value: "#448AFF".to_string(),
4100        },
4101    );
4102    tokens.color.insert(
4103        "hover".to_string(),
4104        TokenValue::Adaptive {
4105            light: "#F0F0F5".to_string(),
4106            dark: "#252535".to_string(),
4107        },
4108    );
4109    tokens.color.insert(
4110        "active".to_string(),
4111        TokenValue::Adaptive {
4112            light: "#E0E0EB".to_string(),
4113            dark: "#303045".to_string(),
4114        },
4115    );
4116    tokens.color.insert(
4117        "disabled".to_string(),
4118        TokenValue::Adaptive {
4119            light: "#E8E8F0".to_string(),
4120            dark: "#1A1A28".to_string(),
4121        },
4122    );
4123    tokens.color.insert(
4124        "disabled_text".to_string(),
4125        TokenValue::Adaptive {
4126            light: "#B0B0C0".to_string(),
4127            dark: "#404060".to_string(),
4128        },
4129    );
4130    tokens.color.insert(
4131        "focus_ring".to_string(),
4132        TokenValue::Single {
4133            value: "#00FFFF".to_string(),
4134        },
4135    );
4136    tokens.color.insert(
4137        "shadow".to_string(),
4138        TokenValue::Adaptive {
4139            light: "#00000020".to_string(),
4140            dark: "#00000060".to_string(),
4141        },
4142    );
4143    tokens.color.insert(
4144        "code_bg".to_string(),
4145        TokenValue::Adaptive {
4146            light: "#F5F5FA".to_string(),
4147            dark: "#0D0D18".to_string(),
4148        },
4149    );
4150    // Bifrost (Glassmorphism) - Frosted Style
4151    tokens.bifrost.insert(
4152        "blur".to_string(),
4153        TokenValue::Single {
4154            value: "25.0".to_string(),
4155        },
4156    );
4157    tokens.bifrost.insert(
4158        "saturation".to_string(),
4159        TokenValue::Single {
4160            value: "1.2".to_string(),
4161        },
4162    );
4163    tokens.bifrost.insert(
4164        "opacity".to_string(),
4165        TokenValue::Single {
4166            value: "0.65".to_string(),
4167        },
4168    );
4169    // Gungnir (Neon Glow)
4170    tokens.gungnir.insert(
4171        "intensity".to_string(),
4172        TokenValue::Single {
4173            value: "1.0".to_string(),
4174        },
4175    );
4176    tokens.gungnir.insert(
4177        "radius".to_string(),
4178        TokenValue::Single {
4179            value: "15.0".to_string(),
4180        },
4181    );
4182    // Mjolnir (Sharp Geometry)
4183    tokens.mjolnir.insert(
4184        "clip_angle".to_string(),
4185        TokenValue::Single {
4186            value: "12.0".to_string(),
4187        },
4188    );
4189    tokens.mjolnir.insert(
4190        "border_width".to_string(),
4191        TokenValue::Single {
4192            value: "2.0".to_string(),
4193        },
4194    );
4195    // Sleipnir (Spring Animation)
4196    tokens.anim.insert(
4197        "stiffness".to_string(),
4198        TokenValue::Single {
4199            value: "170.0".to_string(),
4200        },
4201    );
4202    tokens.anim.insert(
4203        "damping".to_string(),
4204        TokenValue::Single {
4205            value: "26.0".to_string(),
4206        },
4207    );
4208    tokens.anim.insert(
4209        "mass".to_string(),
4210        TokenValue::Single {
4211            value: "1.0".to_string(),
4212        },
4213    );
4214    // Accessibility
4215    tokens.accessibility.insert(
4216        "reduce_motion".to_string(),
4217        TokenValue::Single {
4218            value: "false".to_string(),
4219        },
4220    );
4221    tokens
4222}
4223/// Environment wrapper for accessing ambient values
4224pub struct Environment<K: EnvKey> {
4225    _marker: std::marker::PhantomData<K>,
4226}
4227impl<K: EnvKey> Default for Environment<K> {
4228    fn default() -> Self {
4229        Self::new()
4230    }
4231}
4232impl<K: EnvKey> Environment<K> {
4233    /// Create a new Environment
4234    pub fn new() -> Self {
4235        Self {
4236            _marker: std::marker::PhantomData,
4237        }
4238    }
4239    /// Get the current value from the environment
4240    pub fn get(&self) -> K::Value {
4241        if let Some(env_store) = ENVIRONMENT.get() {
4242            let env_lock = env_store.lock().unwrap();
4243            if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4244                if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4245                    return typed_val.clone();
4246                } else {
4247                    log::warn!(
4248                        "Environment: Downcast failed for key type {:?}",
4249                        std::any::type_name::<K>()
4250                    );
4251                }
4252            } else {
4253                log::debug!(
4254                    "Environment: Key not found: {:?}. Returning default.",
4255                    std::any::type_name::<K>()
4256                );
4257            }
4258        } else {
4259            log::debug!(
4260                "Environment: Store not initialized. Key: {:?}. Returning default.",
4261                std::any::type_name::<K>()
4262            );
4263        }
4264        K::default_value()
4265    }
4266}
4267/// Ambient environment management
4268pub mod env {
4269    /// Insert a value into the environment
4270    pub fn insert<K: super::EnvKey>(value: K::Value) {
4271        let store = super::ENVIRONMENT
4272            .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4273        let mut env_map = store.lock().unwrap();
4274        env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4275    }
4276    /// Remove a value from the environment.
4277    pub fn remove<K: super::EnvKey>() {
4278        if let Some(store) = super::ENVIRONMENT.get() {
4279            let mut env_map = store.lock().unwrap();
4280            env_map.remove(&std::any::TypeId::of::<K>());
4281        }
4282    }
4283}
4284/// Geometry modifiers
4285/// Size of the view in logical pixels
4286#[derive(Debug, Clone, Copy, PartialEq)]
4287pub struct Size {
4288    pub width: f32,
4289    pub height: f32,
4290}
4291
4292impl Size {
4293    pub const ZERO: Self = Self {
4294        width: 0.0,
4295        height: 0.0,
4296    };
4297
4298    pub fn new(width: f32, height: f32) -> Self {
4299        Self { width, height }
4300    }
4301}
4302
4303/// Insets for padding
4304#[derive(Debug, Clone, Copy, PartialEq)]
4305pub struct EdgeInsets {
4306    pub top: f32,
4307    pub leading: f32,
4308    pub bottom: f32,
4309    pub trailing: f32,
4310}
4311
4312impl EdgeInsets {
4313    /// Equal insets on all edges
4314    pub fn all(value: f32) -> Self {
4315        Self {
4316            top: value,
4317            leading: value,
4318            bottom: value,
4319            trailing: value,
4320        }
4321    }
4322
4323    /// Vertical insets (top and bottom)
4324    pub fn vertical(value: f32) -> Self {
4325        Self {
4326            top: value,
4327            leading: 0.0,
4328            bottom: value,
4329            trailing: 0.0,
4330        }
4331    }
4332
4333    /// Horizontal insets (leading and trailing)
4334    pub fn horizontal(value: f32) -> Self {
4335        Self {
4336            top: 0.0,
4337            leading: value,
4338            bottom: 0.0,
4339            trailing: value,
4340        }
4341    }
4342}
4343
4344/// Modifier to set the size and alignment constraints of a view.
4345/// This determines the proposal size passed to the child and how the child is aligned
4346/// within the layout rect allocated to the frame.
4347#[derive(Debug, Clone, Copy, PartialEq)]
4348pub struct FrameModifier {
4349    /// Exact width to assign to the child view.
4350    pub width: Option<f32>,
4351    /// Exact height to assign to the child view.
4352    pub height: Option<f32>,
4353    /// Minimum width constraint for the view.
4354    pub min_width: Option<f32>,
4355    /// Maximum width constraint for the view.
4356    pub max_width: Option<f32>,
4357    /// Minimum height constraint for the view.
4358    pub min_height: Option<f32>,
4359    /// Maximum height constraint for the view.
4360    pub max_height: Option<f32>,
4361    /// The alignment strategy for positioning the child view within the frame.
4362    pub alignment: Alignment,
4363}
4364
4365impl Default for FrameModifier {
4366    /// Returns the default frame configuration which has no constraints and center alignment.
4367    fn default() -> Self {
4368        Self::new()
4369    }
4370}
4371
4372impl FrameModifier {
4373    /// Creates a new FrameModifier with all dimensions unspecified and center alignment.
4374    pub fn new() -> Self {
4375        Self {
4376            width: None,
4377            height: None,
4378            min_width: None,
4379            max_width: None,
4380            min_height: None,
4381            max_height: None,
4382            alignment: Alignment::Center,
4383        }
4384    }
4385
4386    /// Sets the fixed width of the frame.
4387    pub fn width(mut self, width: f32) -> Self {
4388        self.width = Some(width);
4389        self
4390    }
4391
4392    /// Sets the fixed height of the frame.
4393    pub fn height(mut self, height: f32) -> Self {
4394        self.height = Some(height);
4395        self
4396    }
4397
4398    /// Sets both the fixed width and height of the frame.
4399    pub fn size(mut self, width: f32, height: f32) -> Self {
4400        self.width = Some(width);
4401        self.height = Some(height);
4402        self
4403    }
4404
4405    /// Sets the minimum width constraint.
4406    pub fn min_width(mut self, min_width: f32) -> Self {
4407        self.min_width = Some(min_width);
4408        self
4409    }
4410
4411    /// Sets the maximum width constraint.
4412    pub fn max_width(mut self, max_width: f32) -> Self {
4413        self.max_width = Some(max_width);
4414        self
4415    }
4416
4417    /// Sets the minimum height constraint.
4418    pub fn min_height(mut self, min_height: f32) -> Self {
4419        self.min_height = Some(min_height);
4420        self
4421    }
4422
4423    /// Sets the maximum height constraint.
4424    pub fn max_height(mut self, max_height: f32) -> Self {
4425        self.max_height = Some(max_height);
4426        self
4427    }
4428
4429    /// Sets the alignment strategy for the child within the frame's layout bounds.
4430    pub fn alignment(mut self, alignment: Alignment) -> Self {
4431        self.alignment = alignment;
4432        self
4433    }
4434}
4435
4436impl ViewModifier for FrameModifier {
4437    /// Wraps the child view in a ModifiedView using this frame modifier.
4438    fn modify<V: View>(self, content: V) -> impl View {
4439        ModifiedView::new(content, self)
4440    }
4441
4442    /// Transforms the layout size proposal offered to the child to comply with frame constraints.
4443    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4444        let w = if let Some(width) = self.width {
4445            Some(width)
4446        } else {
4447            proposal.width.map(|pw| {
4448                pw.clamp(
4449                    self.min_width.unwrap_or(0.0),
4450                    self.max_width.unwrap_or(f32::INFINITY),
4451                )
4452            })
4453        };
4454        let h = if let Some(height) = self.height {
4455            Some(height)
4456        } else {
4457            proposal.height.map(|ph| {
4458                ph.clamp(
4459                    self.min_height.unwrap_or(0.0),
4460                    self.max_height.unwrap_or(f32::INFINITY),
4461                )
4462            })
4463        };
4464        SizeProposal {
4465            width: w,
4466            height: h,
4467        }
4468    }
4469
4470    /// Constraints and transforms the child's resulting size to fit the frame's bounds.
4471    fn transform_size(&self, child_size: Size) -> Size {
4472        let w = if let Some(width) = self.width {
4473            width
4474        } else {
4475            child_size.width.clamp(
4476                self.min_width.unwrap_or(0.0),
4477                self.max_width.unwrap_or(f32::INFINITY),
4478            )
4479        };
4480        let h = if let Some(height) = self.height {
4481            height
4482        } else {
4483            child_size.height.clamp(
4484                self.min_height.unwrap_or(0.0),
4485                self.max_height.unwrap_or(f32::INFINITY),
4486            )
4487        };
4488        Size {
4489            width: w,
4490            height: h,
4491        }
4492    }
4493
4494    /// Renders the frame's child view aligned within the layout rect.
4495    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4496        self.render(renderer, rect);
4497        let child_proposal =
4498            self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4499        let child_size = view.intrinsic_size(renderer, child_proposal);
4500
4501        let mut child_x = rect.x;
4502        let mut child_y = rect.y;
4503
4504        match self.alignment {
4505            Alignment::Leading => {
4506                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4507            }
4508            Alignment::Trailing => {
4509                child_x = rect.x + rect.width - child_size.width;
4510                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4511            }
4512            Alignment::Top => {
4513                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4514            }
4515            Alignment::Bottom => {
4516                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4517                child_y = rect.y + rect.height - child_size.height;
4518            }
4519            Alignment::Center => {
4520                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4521                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4522            }
4523        }
4524
4525        let child_rect = Rect {
4526            x: child_x,
4527            y: child_y,
4528            width: child_size.width,
4529            height: child_size.height,
4530        };
4531
4532        view.render(renderer, child_rect);
4533        self.post_render(renderer, rect);
4534    }
4535}
4536
4537/// Modifier to set the flex weight of a view
4538#[derive(Debug, Clone, Copy, PartialEq)]
4539pub struct FlexModifier {
4540    pub weight: f32,
4541}
4542
4543impl ViewModifier for FlexModifier {
4544    fn modify<V: View>(self, content: V) -> impl View {
4545        ModifiedView::new(content, self)
4546    }
4547
4548    fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4549        self.weight
4550    }
4551}
4552
4553/// Modifier that specifies the column and row placement of a view inside a Grid layout.
4554#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4555pub struct GridPlacementModifier {
4556    /// The grid placement settings containing column/row indexes and spans.
4557    pub placement: GridPlacement,
4558}
4559
4560impl ViewModifier for GridPlacementModifier {
4561    /// Wraps the child view in a ModifiedView using this modifier.
4562    fn modify<V: View>(self, content: V) -> impl View {
4563        ModifiedView::new(content, self)
4564    }
4565
4566    /// Exposes the grid placement metadata to parent layout engines.
4567    fn get_grid_placement(&self) -> Option<GridPlacement> {
4568        Some(self.placement)
4569    }
4570}
4571
4572/// Modifier to render a popover, tooltip, or menu view overlaying an anchored view.
4573/// It supports alignment positioning and outside-click dismissal.
4574#[derive(Clone)]
4575pub struct OverlayModifier {
4576    /// The overlay content view.
4577    pub overlay: AnyView,
4578    /// Where the overlay is aligned relative to the anchored view.
4579    pub alignment: Alignment,
4580    /// Additional offset in logical pixels.
4581    pub offset: [f32; 2],
4582    /// Optional dismissal callback triggered by click-outside events.
4583    pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4584}
4585
4586impl ViewModifier for OverlayModifier {
4587    /// Wraps the child view in a ModifiedView using this overlay modifier.
4588    fn modify<V: View>(self, content: V) -> impl View {
4589        ModifiedView::new(content, self)
4590    }
4591
4592    /// Renders the overlay content positioned above the child view.
4593    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4594        // 1. Render primary anchored view
4595        view.render(renderer, rect);
4596
4597        // 2. Measure overlay content
4598        let overlay_size = self
4599            .overlay
4600            .intrinsic_size(renderer, SizeProposal::unspecified());
4601
4602        // 3. Align overlay rect relative to anchored rect
4603        let mut overlay_x;
4604        let mut overlay_y;
4605
4606        match self.alignment {
4607            Alignment::Leading => {
4608                overlay_x = rect.x - overlay_size.width;
4609                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4610            }
4611            Alignment::Trailing => {
4612                overlay_x = rect.x + rect.width;
4613                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4614            }
4615            Alignment::Top => {
4616                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4617                overlay_y = rect.y - overlay_size.height;
4618            }
4619            Alignment::Bottom => {
4620                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4621                overlay_y = rect.y + rect.height;
4622            }
4623            Alignment::Center => {
4624                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4625                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4626            }
4627        }
4628
4629        overlay_x += self.offset[0];
4630        overlay_y += self.offset[1];
4631
4632        let overlay_rect = Rect {
4633            x: overlay_x,
4634            y: overlay_y,
4635            width: overlay_size.width,
4636            height: overlay_size.height,
4637        };
4638
4639        // 4. Handle click-outside dismissal
4640        if let Some(on_dismiss) = &self.on_dismiss {
4641            let dismiss = on_dismiss.clone();
4642            renderer.register_handler(
4643                "pointerdown",
4644                Arc::new(move |event| {
4645                    if let Event::PointerDown { x, y, .. } = event {
4646                        let click_inside = x >= overlay_rect.x
4647                            && x <= overlay_rect.x + overlay_rect.width
4648                            && y >= overlay_rect.y
4649                            && y <= overlay_rect.y + overlay_rect.height;
4650                        if !click_inside {
4651                            dismiss();
4652                        }
4653                    }
4654                }),
4655            );
4656        }
4657
4658        // 5. Render overlay view
4659        self.overlay.render(renderer, overlay_rect);
4660    }
4661}
4662
4663/// Modifier to offset a view
4664#[derive(Debug, Clone, Copy, PartialEq)]
4665pub struct OffsetModifier {
4666    pub x: f32,
4667    pub y: f32,
4668}
4669
4670impl OffsetModifier {
4671    pub fn new(x: f32, y: f32) -> Self {
4672        Self { x, y }
4673    }
4674}
4675
4676impl ViewModifier for OffsetModifier {
4677    fn modify<V: View>(self, content: V) -> impl View {
4678        ModifiedView::new(content, self)
4679    }
4680}
4681
4682/// Modifier to set the z-index of a view
4683#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4684pub struct ZIndexModifier {
4685    pub z_index: i32,
4686}
4687
4688impl ZIndexModifier {
4689    pub fn new(z_index: i32) -> Self {
4690        Self { z_index }
4691    }
4692}
4693
4694impl ViewModifier for ZIndexModifier {
4695    fn modify<V: View>(self, content: V) -> impl View {
4696        ModifiedView::new(content, self)
4697    }
4698}
4699
4700/// Layout constraints for views
4701#[derive(Debug, Clone, Copy, PartialEq, Default)]
4702pub struct LayoutConstraints {
4703    pub min_width: Option<f32>,
4704    pub max_width: Option<f32>,
4705    pub min_height: Option<f32>,
4706    pub max_height: Option<f32>,
4707}
4708
4709/// Modifier to set layout constraints
4710#[derive(Debug, Clone, Copy, PartialEq)]
4711pub struct LayoutModifier {
4712    pub constraints: LayoutConstraints,
4713}
4714
4715impl LayoutModifier {
4716    pub fn new(constraints: LayoutConstraints) -> Self {
4717        Self { constraints }
4718    }
4719}
4720
4721impl ViewModifier for LayoutModifier {
4722    fn modify<V: View>(self, content: V) -> impl View {
4723        ModifiedView::new(content, self)
4724    }
4725}
4726
4727/// Modifier to handle platform safe areas
4728#[derive(Debug, Clone, Copy, PartialEq)]
4729pub struct SafeAreaModifier {
4730    pub ignores: bool,
4731}
4732
4733impl ViewModifier for SafeAreaModifier {
4734    fn modify<V: View>(self, content: V) -> impl View {
4735        ModifiedView::new(content, self)
4736    }
4737}
4738
4739/// Modifier to add elevation (shadow) to a view
4740#[derive(Debug, Clone, Copy, PartialEq)]
4741pub struct ElevationModifier {
4742    pub level: f32,
4743}
4744
4745impl ViewModifier for ElevationModifier {
4746    fn modify<V: View>(self, content: V) -> impl View {
4747        ModifiedView::new(content, self)
4748    }
4749
4750    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4751        if self.level > 0.0 {
4752            let radius = self.level * 2.0;
4753            let offset_y = self.level * 0.5;
4754            let shadow_color = [0.0, 0.0, 0.0, 0.3];
4755            renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
4756            view.render(renderer, rect);
4757            renderer.pop_shadow();
4758        } else {
4759            view.render(renderer, rect);
4760        }
4761    }
4762}
4763
4764// Layout subsystem
4765pub mod layout {
4766    use super::*;
4767
4768    /// Key used to identify a cached layout entry.
4769    /// Combines a view hash with a generation counter for cache invalidation.
4770    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4771    pub struct LayoutKey {
4772        pub view_hash: u64,
4773        pub generation: u64,
4774    }
4775
4776    // Layout pass scratch space
4777    pub struct LayoutCache {
4778        pub safe_area: SafeArea,
4779        size_cache: HashMap<(u64, u32, u32), Size>, // (ViewHash, ProposalW, ProposalH)
4780        /// Monotonically increasing generation counter for cache invalidation.
4781        /// When a view tree changes, bumping the generation causes stale entries
4782        /// to be treated as invalid without eagerly clearing the entire cache.
4783        generation: u64,
4784    }
4785
4786    impl Default for LayoutCache {
4787        fn default() -> Self {
4788            Self::new()
4789        }
4790    }
4791
4792    impl LayoutCache {
4793        pub fn new() -> Self {
4794            Self {
4795                safe_area: SafeArea::default(),
4796                size_cache: HashMap::new(),
4797                generation: 0,
4798            }
4799        }
4800
4801        /// Returns the current generation counter.
4802        pub fn generation(&self) -> u64 {
4803            self.generation
4804        }
4805
4806        /// Bump the generation counter, logically invalidating all cached entries
4807        /// without eagerly clearing them. Subsequent lookups with the old generation
4808        /// will miss until re-populated.
4809        pub fn invalidate(&mut self) {
4810            self.generation = self.generation.wrapping_add(1);
4811        }
4812
4813        /// Check whether a cached entry for the given key is still valid
4814        /// against the current generation.
4815        pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
4816            key.generation == current_gen && key.generation == self.generation
4817        }
4818
4819        pub fn clear(&mut self) {
4820            self.safe_area = SafeArea::default();
4821            self.size_cache.clear();
4822        }
4823
4824        pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
4825            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
4826            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
4827            self.size_cache.get(&(view_hash, pw, ph)).copied()
4828        }
4829
4830        pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
4831            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
4832            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
4833            self.size_cache.insert((view_hash, pw, ph), size);
4834        }
4835
4836        /// Remove all cached size entries for a specific view hash.
4837        pub fn invalidate_view(&mut self, view_hash: u64) {
4838            self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
4839        }
4840    }
4841
4842    /// Proposed size from parent view
4843    #[derive(Debug, Clone, Copy, PartialEq)]
4844    pub struct SizeProposal {
4845        pub width: Option<f32>,
4846        pub height: Option<f32>,
4847    }
4848
4849    impl SizeProposal {
4850        pub fn unspecified() -> Self {
4851            Self {
4852                width: None,
4853                height: None,
4854            }
4855        }
4856
4857        pub fn width(width: f32) -> Self {
4858            Self {
4859                width: Some(width),
4860                height: None,
4861            }
4862        }
4863
4864        pub fn height(height: f32) -> Self {
4865            Self {
4866                width: None,
4867                height: Some(height),
4868            }
4869        }
4870
4871        pub fn tight(width: f32, height: f32) -> Self {
4872            Self {
4873                width: Some(width),
4874                height: Some(height),
4875            }
4876        }
4877
4878        pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
4879            Self { width, height }
4880        }
4881    }
4882
4883    /// A view that can participate in layout
4884    pub trait LayoutView: Send {
4885        /// Propose a size for this view given the available space
4886        fn size_that_fits(
4887            &self,
4888            proposal: SizeProposal,
4889            subviews: &[&dyn LayoutView],
4890            cache: &mut LayoutCache,
4891        ) -> Size;
4892
4893        /// Place subviews within the given bounds
4894        fn place_subviews(
4895            &self,
4896            bounds: Rect,
4897            subviews: &mut [&mut dyn LayoutView],
4898            cache: &mut LayoutCache,
4899        );
4900
4901        /// Returns the flex weight of this view (default is 0.0, which means fixed/intrinsic)
4902        fn flex_weight(&self) -> f32 {
4903            0.0
4904        }
4905
4906        /// Return a debug representation of this layout subtree.
4907        /// The `indent` parameter controls the indentation level for nested display.
4908        fn debug_layout(&self, indent: usize) -> String {
4909            let prefix = " ".repeat(indent);
4910            format!("{}LayoutView", prefix)
4911        }
4912    }
4913    /// Edge insets for padding, margins, and safe areas
4914    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
4915    pub struct EdgeInsets {
4916        pub top: f32,
4917        pub leading: f32,
4918        pub bottom: f32,
4919        pub trailing: f32,
4920    }
4921
4922    impl EdgeInsets {
4923        pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
4924            Self {
4925                top,
4926                leading,
4927                bottom,
4928                trailing,
4929            }
4930        }
4931
4932        pub fn all(value: f32) -> Self {
4933            Self {
4934                top: value,
4935                leading: value,
4936                bottom: value,
4937                trailing: value,
4938            }
4939        }
4940    }
4941
4942    /// SafeArea constraints provided by the platform
4943    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
4944    pub struct SafeArea {
4945        pub insets: EdgeInsets,
4946    }
4947
4948    /// SDF Shape definitions for Vili Interaction Paradigm hit-testing.
4949    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4950    pub enum SdfShape {
4951        Rect(Rect),
4952        RoundedRect { rect: Rect, radius: f32 },
4953        Circle { center: [f32; 2], radius: f32 },
4954    }
4955
4956    /// Rectangle in logical pixels
4957    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4958    pub struct Rect {
4959        pub x: f32,
4960        pub y: f32,
4961        pub width: f32,
4962        pub height: f32,
4963    }
4964
4965    impl Rect {
4966        pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
4967            Self {
4968                x,
4969                y,
4970                width,
4971                height,
4972            }
4973        }
4974
4975        pub fn inset(&self, amount: f32) -> Self {
4976            Self {
4977                x: self.x + amount,
4978                y: self.y + amount,
4979                width: (self.width - amount * 2.0).max(0.0),
4980                height: (self.height - amount * 2.0).max(0.0),
4981            }
4982        }
4983
4984        pub fn offset(&self, dx: f32, dy: f32) -> Self {
4985            Self {
4986                x: self.x + dx,
4987                y: self.y + dy,
4988                ..*self
4989            }
4990        }
4991
4992        pub fn zero() -> Self {
4993            Self {
4994                x: 0.0,
4995                y: 0.0,
4996                width: 0.0,
4997                height: 0.0,
4998            }
4999        }
5000
5001        pub fn contains(&self, x: f32, y: f32) -> bool {
5002            x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5003        }
5004
5005        pub fn size(&self) -> Size {
5006            Size {
5007                width: self.width,
5008                height: self.height,
5009            }
5010        }
5011
5012        /// Split the rect horizontally into N equal pieces
5013        pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5014            if n == 0 {
5015                return vec![];
5016            }
5017            let item_width = self.width / n as f32;
5018            (0..n)
5019                .map(|i| Rect {
5020                    x: self.x + i as f32 * item_width,
5021                    y: self.y,
5022                    width: item_width,
5023                    height: self.height,
5024                })
5025                .collect()
5026        }
5027
5028        /// Split the rect vertically into N equal pieces
5029        pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5030            if n == 0 {
5031                return vec![];
5032            }
5033            let item_height = self.height / n as f32;
5034            (0..n)
5035                .map(|i| Rect {
5036                    x: self.x,
5037                    y: self.y + i as f32 * item_height,
5038                    width: self.width,
5039                    height: item_height,
5040                })
5041                .collect()
5042        }
5043    }
5044}
5045
5046// Re-export layout items for convenience
5047pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5048// Size and FrameRenderer are pub items in this module; no re-export alias needed.
5049
5050pub mod agents;
5051pub mod animation;
5052pub mod gpu;
5053pub mod material;
5054pub mod runtime;
5055pub mod scene_graph;
5056pub mod sdf_shadow;
5057
5058pub use material::DrawMaterial;
5059pub use scene_graph::{NodeId, bifrost_registry};
5060
5061// Duplicate AssetState removed - original definition at line 67
5062
5063/// AssetManager defines the interface for loading and caching external resources.
5064pub trait AssetManager: Send + Sync {
5065    /// Request an image asset. Returns the current state (Loading, Ready, or Error).
5066    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5067
5068    /// Pre-load an image into the cache.
5069    fn preload_image(&self, url: &str);
5070}
5071
5072/// The phase of a touch or gesture event in its lifecycle.
5073#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5074pub enum TouchPhase {
5075    /// The touch/gesture has just begun.
5076    Began,
5077    /// The touch/gesture is moving.
5078    Moved,
5079    /// The touch/gesture has ended normally.
5080    Ended,
5081    /// The touch/gesture was cancelled (e.g., by the system).
5082    Cancelled,
5083}
5084
5085/// User input event types
5086#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5087pub enum Event {
5088    PointerDown {
5089        x: f32,
5090        y: f32,
5091        button: u32,
5092        proximity_field: f32,
5093        tilt: Option<f32>,
5094        azimuth: Option<f32>,
5095        pressure: Option<f32>,
5096        barrel_rotation: Option<f32>,
5097    },
5098    PointerUp {
5099        x: f32,
5100        y: f32,
5101        button: u32,
5102        tilt: Option<f32>,
5103        azimuth: Option<f32>,
5104        pressure: Option<f32>,
5105        barrel_rotation: Option<f32>,
5106    },
5107    PointerMove {
5108        x: f32,
5109        y: f32,
5110        proximity_field: f32,
5111        tilt: Option<f32>,
5112        azimuth: Option<f32>,
5113        pressure: Option<f32>,
5114        barrel_rotation: Option<f32>,
5115    },
5116    PointerClick {
5117        x: f32,
5118        y: f32,
5119        button: u32,
5120        tilt: Option<f32>,
5121        azimuth: Option<f32>,
5122        pressure: Option<f32>,
5123        barrel_rotation: Option<f32>,
5124    },
5125    PointerEnter,
5126    PointerLeave,
5127    /// Mouse wheel / trackpad scroll event.
5128    /// `delta_x` is the horizontal scroll amount, `delta_y` is the vertical scroll amount (positive = scroll down).
5129    PointerWheel {
5130        x: f32,
5131        y: f32,
5132        delta_x: f32,
5133        delta_y: f32,
5134    },
5135    /// Double-click event (rapid successive clicks).
5136    PointerDoubleClick {
5137        x: f32,
5138        y: f32,
5139        button: u32,
5140    },
5141    /// Drag-and-drop: drag started (pointer moved while button held past threshold).
5142    DragStart {
5143        x: f32,
5144        y: f32,
5145        button: u32,
5146    },
5147    /// Drag-and-drop: drag in progress.
5148    DragMove {
5149        x: f32,
5150        y: f32,
5151    },
5152    /// Drag-and-drop: drag ended (pointer released).
5153    DragEnd {
5154        x: f32,
5155        y: f32,
5156    },
5157    KeyDown {
5158        key: String,
5159    },
5160    KeyUp {
5161        key: String,
5162    },
5163    /// Focus gained by a node.
5164    FocusIn,
5165    /// Focus lost by a node.
5166    FocusOut,
5167    /// Clipboard copy event.
5168    Copy,
5169    /// Clipboard cut event.
5170    Cut,
5171    /// Clipboard paste event with the pasted text content.
5172    Paste(String),
5173    /// Input Method Editor event (e.g. CJK character composition)
5174    Ime(String),
5175    /// Touch began at the given position.
5176    TouchStart {
5177        x: f32,
5178        y: f32,
5179        touch_id: u64,
5180    },
5181    /// Touch moved to a new position.
5182    TouchMove {
5183        x: f32,
5184        y: f32,
5185        touch_id: u64,
5186    },
5187    /// Touch ended at the given position.
5188    TouchEnd {
5189        x: f32,
5190        y: f32,
5191        touch_id: u64,
5192    },
5193    /// Touch cancelled.
5194    TouchCancel {
5195        touch_id: u64,
5196    },
5197    /// Multi-touch pinch gesture.
5198    /// `center` is the gesture anchor point in device-independent pixels.
5199    /// `scale` is the relative pinch scale (>1 = expand, <1 = contract).
5200    /// `velocity` is the instantaneous velocity of the pinch.
5201    /// `phase` indicates the current phase of the gesture lifecycle.
5202    GesturePinch {
5203        center: [f32; 2],
5204        scale: f32,
5205        velocity: f32,
5206        phase: TouchPhase,
5207    },
5208    /// Multi-touch swipe/pan gesture.
5209    /// `direction` is the normalized direction vector [dx, dy].
5210    /// `velocity` is the instantaneous velocity of the swipe.
5211    /// `phase` indicates the current phase of the gesture lifecycle.
5212    GestureSwipe {
5213        direction: [f32; 2],
5214        velocity: f32,
5215        phase: TouchPhase,
5216    },
5217    /// Drag-and-drop: external file dropped onto window.
5218    FileDrop {
5219        path: String,
5220    },
5221}
5222
5223impl Event {
5224    /// Returns the canonical string name of the event for lookup in handler maps.
5225    pub fn name(&self) -> &'static str {
5226        match self {
5227            Self::PointerDown { .. } => "pointerdown",
5228            Self::PointerUp { .. } => "pointerup",
5229            Self::PointerMove { .. } => "pointermove",
5230            Self::PointerClick { .. } => "pointerclick",
5231            Self::PointerEnter => "pointerenter",
5232            Self::PointerLeave => "pointerleave",
5233            Self::PointerWheel { .. } => "pointerwheel",
5234            Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5235            Self::DragStart { .. } => "dragstart",
5236            Self::DragMove { .. } => "dragmove",
5237            Self::DragEnd { .. } => "dragend",
5238            Self::KeyDown { .. } => "keydown",
5239            Self::KeyUp { .. } => "keyup",
5240            Self::FocusIn => "focusin",
5241            Self::FocusOut => "focusout",
5242            Self::Copy => "copy",
5243            Self::Cut => "cut",
5244            Self::Paste(_) => "paste",
5245            Self::Ime(_) => "ime",
5246            Self::TouchStart { .. } => "touchstart",
5247            Self::TouchMove { .. } => "touchmove",
5248            Self::TouchEnd { .. } => "touchend",
5249            Self::TouchCancel { .. } => "touchcancel",
5250            Self::GesturePinch { .. } => "gesturepinch",
5251            Self::GestureSwipe { .. } => "gestureswipe",
5252            Self::FileDrop { .. } => "filedrop",
5253        }
5254    }
5255}
5256
5257/// Response from an event handler
5258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5259pub enum EventResponse {
5260    Handled,
5261    Ignored,
5262}
5263
5264/// A basic implementation of AssetManager that can be overridden by platform backends.
5265pub struct DefaultAssetManager {
5266    cache: AssetCache,
5267}
5268type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5269
5270impl Default for DefaultAssetManager {
5271    fn default() -> Self {
5272        Self::new()
5273    }
5274}
5275
5276impl DefaultAssetManager {
5277    pub fn new() -> Self {
5278        Self {
5279            cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5280        }
5281    }
5282}
5283
5284impl AssetManager for DefaultAssetManager {
5285    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5286        if let Some(state) = self.cache.load().get(url) {
5287            return state.clone();
5288        }
5289
5290        self.cache.rcu(|map| {
5291            let mut m = (**map).clone();
5292            m.entry(url.to_string()).or_insert(AssetState::Loading);
5293            m
5294        });
5295        AssetState::Loading
5296    }
5297
5298    fn preload_image(&self, _url: &str) {}
5299}
5300
5301use std::future::Future;
5302
5303/// Suspense wrapper for asynchronous state management.
5304/// Integrates with State<T> to provide loading/error/ready states for async operations.
5305pub struct Suspense<T: Clone + Send + Sync + 'static> {
5306    inner: State<AssetState<T>>,
5307}
5308
5309impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5310    fn default() -> Self {
5311        Self::new()
5312    }
5313}
5314
5315impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5316    pub fn new() -> Self {
5317        Self {
5318            inner: State::new(AssetState::Loading),
5319        }
5320    }
5321
5322    pub fn new_async<F>(future: F) -> Self
5323    where
5324        F: Future<Output = Result<T, String>> + Send + 'static,
5325    {
5326        let suspense = Self::new();
5327        let suspense_clone = suspense.clone();
5328
5329        #[cfg(not(target_arch = "wasm32"))]
5330        {
5331            // Try to use an existing tokio runtime, or fallback to a dedicated thread
5332            if let Ok(handle) = tokio::runtime::Handle::try_current() {
5333                handle.spawn(async move {
5334                    let result = future.await;
5335                    match result {
5336                        Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5337                        Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5338                    }
5339                });
5340            } else {
5341                std::thread::spawn(move || {
5342                    let rt = tokio::runtime::Builder::new_current_thread()
5343                        .enable_all()
5344                        .build()
5345                        .unwrap();
5346                    rt.block_on(async {
5347                        let result = future.await;
5348                        match result {
5349                            Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5350                            Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5351                        }
5352                    });
5353                });
5354            }
5355        }
5356        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5357        {
5358            wasm_bindgen_futures::spawn_local(async move {
5359                let result = future.await;
5360                match result {
5361                    Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5362                    Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5363                }
5364            });
5365        }
5366
5367        suspense
5368    }
5369
5370    pub fn ready(value: T) -> Self {
5371        Self {
5372            inner: State::new(AssetState::Ready(value)),
5373        }
5374    }
5375
5376    pub fn error(message: impl Into<String>) -> Self {
5377        Self {
5378            inner: State::new(AssetState::Error(message.into())),
5379        }
5380    }
5381
5382    pub fn get(&self) -> AssetState<T> {
5383        self.inner.get()
5384    }
5385
5386    pub fn get_ref(&self) -> AssetState<T> {
5387        self.inner.get()
5388    }
5389
5390    pub fn is_loading(&self) -> bool {
5391        matches!(self.get(), AssetState::Loading)
5392    }
5393
5394    pub fn is_ready(&self) -> bool {
5395        matches!(self.get(), AssetState::Ready(_))
5396    }
5397
5398    pub fn is_error(&self) -> bool {
5399        matches!(self.get(), AssetState::Error(_))
5400    }
5401
5402    pub fn ready_value(&self) -> Option<T> {
5403        match self.get() {
5404            AssetState::Ready(value) => Some(value),
5405            _ => None,
5406        }
5407    }
5408
5409    pub fn error_message(&self) -> Option<String> {
5410        match self.get() {
5411            AssetState::Error(message) => Some(message),
5412            _ => None,
5413        }
5414    }
5415
5416    pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5417        self.inner.subscribe(callback)
5418    }
5419
5420    pub fn inner_state(&self) -> &State<AssetState<T>> {
5421        &self.inner
5422    }
5423}
5424
5425impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5426    fn clone(&self) -> Self {
5427        Self {
5428            inner: self.inner.clone(),
5429        }
5430    }
5431}
5432
5433impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5434    fn from(value: T) -> Self {
5435        Self::ready(value)
5436    }
5437}
5438
5439impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5440    fn from(result: Result<T, String>) -> Self {
5441        match result {
5442            Ok(value) => Self::ready(value),
5443            Err(error) => Self::error(error),
5444        }
5445    }
5446}
5447
5448#[cfg(test)]
5449mod phase1_test;
5450
5451/// Berserker mode states for the rendering pipeline.
5452#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5453pub enum BerserkerMode {
5454    Normal,
5455    Rage,    // Red tint, slight shake
5456    Frenzy,  // Heavy red tint, motion blur, aggressive shake
5457    GodMode, // Golden aura, lightning arcs
5458}
5459
5460/// Seer trait for AI-assisted UI components.
5461/// Allows components to receive "prophecies" (predictions) from an AI backend.
5462pub trait Seer: Send + Sync {
5463    /// Provide a prediction for the next user action or content.
5464    fn predict(&self, context: &str) -> String;
5465    /// Stream real-time "whispers" (transcriptions/intent).
5466    fn whispers(&self) -> Vec<String>;
5467}
5468
5469#[cfg(test)]
5470mod vili_tests {
5471    use super::*;
5472
5473    struct DummyRenderer;
5474    impl ElapsedTime for DummyRenderer {
5475        fn elapsed_time(&self) -> f32 {
5476            0.0
5477        }
5478        fn delta_time(&self) -> f32 {
5479            0.0
5480        }
5481    }
5482    impl Renderer for DummyRenderer {
5483        fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5484        fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5485        fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5486        fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5487        fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5488        fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5489        fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5490        fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5491        fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5492            (0.0, 0.0)
5493        }
5494        fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5495    }
5496
5497    #[test]
5498    fn test_magnetic_warp() {
5499        let renderer = DummyRenderer;
5500        let anchor = Rect {
5501            x: 100.0,
5502            y: 100.0,
5503            width: 50.0,
5504            height: 50.0,
5505        };
5506        // Pointer is near the anchor (distance < 120)
5507        let pointer = [125.0, 50.0];
5508        // distance from center (125, 125) is 75.
5509        // force = (1.0 - 75/120) * strength
5510        let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5511        // It should pull closer to (125, 125), so Y should be > 50
5512        assert!(warp[1] > 50.0);
5513
5514        // Out of range pointer should remain unchanged
5515        let far_pointer = [500.0, 500.0];
5516        let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5517        assert_eq!(far_pointer, far_warp);
5518    }
5519
5520    #[test]
5521    fn test_mani_glow() {
5522        let renderer = DummyRenderer;
5523        let bounds = Rect {
5524            x: 0.0,
5525            y: 0.0,
5526            width: 100.0,
5527            height: 100.0,
5528        };
5529        let pointer_inside = [50.0, 50.0];
5530        let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5531        assert_eq!(glow_max, 1.0);
5532
5533        let pointer_edge = [50.0, -10.0];
5534        let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5535        assert!(glow_partial > 0.0 && glow_partial < 1.0);
5536    }
5537
5538    #[test]
5539    fn test_fafnir_evolve() {
5540        let renderer = DummyRenderer;
5541        let bounds = Rect {
5542            x: 0.0,
5543            y: 0.0,
5544            width: 100.0,
5545            height: 100.0,
5546        };
5547        let pointer_inside = [50.0, 50.0];
5548        let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5549        assert_eq!(scale, 1.2); // Full scale when hovering center
5550    }
5551
5552    #[test]
5553    fn test_undo_manager_basic() {
5554        let mut manager = UndoManager::new(3, 0.5);
5555        let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5556
5557        let v1 = val.clone();
5558        let v2 = val.clone();
5559        manager.push(
5560            "Add",
5561            move || *v1.lock().unwrap() -= 1,
5562            move || *v2.lock().unwrap() += 1,
5563        );
5564
5565        assert!(manager.can_undo());
5566        assert!(!manager.can_redo());
5567
5568        let undo = manager.undo().unwrap();
5569        undo();
5570        assert_eq!(*val.lock().unwrap(), -1);
5571        assert!(!manager.can_undo());
5572        assert!(manager.can_redo());
5573
5574        let redo = manager.redo().unwrap();
5575        redo();
5576        assert_eq!(*val.lock().unwrap(), 0);
5577    }
5578
5579    #[test]
5580    fn test_undo_manager_depth_limit() {
5581        let mut manager = UndoManager::new(2, 0.5);
5582        manager.push("1", || {}, || {});
5583        manager.push("2", || {}, || {});
5584        manager.push("3", || {}, || {});
5585
5586        assert_eq!(manager.stack.len(), 2);
5587        assert_eq!(manager.position, 2);
5588    }
5589
5590    #[test]
5591    fn test_undo_manager_coalescing() {
5592        let mut manager = UndoManager::new(10, 1.0);
5593        let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5594
5595        let c = count.clone();
5596        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5597
5598        let c = count.clone();
5599        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5600
5601        assert_eq!(manager.stack.len(), 1);
5602
5603        let undo = manager.undo().unwrap();
5604        undo();
5605        assert_eq!(*count.lock().unwrap(), -2);
5606    }
5607}
5608
5609// =============================================================================
5610// THEME CONTEXT — Thread-local theme access for components
5611// =============================================================================
5612//
5613// Components call `use_theme()` to get the current SemanticColors.
5614// The native renderer sets this via `set_current_theme()` before each frame.
5615// Falls back to dark theme defaults if no theme has been set.
5616//
5617// We store SemanticColors directly (not the full Theme) to avoid depending
5618// on cvkg-themes from cvkg-core. The colors are cloned into thread-local storage.
5619
5620use std::cell::RefCell;
5621
5622thread_local! {
5623    /// Thread-local semantic colors for the current frame.
5624    static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5625}
5626
5627/// Semantic colors extracted from the theme for use by components.
5628/// This is a standalone type defined in cvkg-core so cvkg-components
5629/// can use it without depending on cvkg-themes.
5630///
5631/// Components should access these via `use_theme()` rather than hardcoding RGBA.
5632pub use color::SemanticColors;
5633
5634/// Set the current semantic colors for this thread.
5635/// Called by the native renderer before each frame.
5636pub fn set_current_theme(colors: color::SemanticColors) {
5637    THEME_CONTEXT.with(|cell| {
5638        *cell.borrow_mut() = Some(colors);
5639    });
5640}
5641
5642/// Clear the current theme. Called after each frame.
5643pub fn clear_current_theme() {
5644    THEME_CONTEXT.with(|cell| {
5645        *cell.borrow_mut() = None;
5646    });
5647}
5648
5649/// Access the current semantic colors from within a component's `render()` method.
5650///
5651/// Returns the colors set by the most recent `set_current_theme()` call.
5652/// Falls back to dark theme defaults if no theme has been set.
5653///
5654/// # Example
5655/// ```no_run
5656/// use cvkg_core::{use_theme, Renderer, Rect};
5657///
5658/// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
5659///     let colors = use_theme();
5660///     renderer.fill_rounded_rect(rect, 8.0, [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
5661/// }
5662/// ```
5663pub fn use_theme() -> color::SemanticColors {
5664    THEME_CONTEXT.with(|cell| {
5665        cell.borrow()
5666            .clone()
5667            .unwrap_or_else(color::SemanticColors::dark)
5668    })
5669}
5670
5671// =============================================================================
5672// COLOR MODULE — Standalone semantic colors type
5673// =============================================================================
5674//
5675// This module provides `SemanticColors`, a self-contained color palette that
5676// components can use without depending on `cvkg-themes`. The `use_theme()`
5677// function returns the current `SemanticColors` from thread-local storage.
5678
5679pub mod color {
5680    use super::Color;
5681
5682    /// A complete set of semantic colors for UI components.
5683    ///
5684    /// Each color serves a specific role in the UI. Components should reference
5685    /// these semantic roles rather than hardcoding RGBA values.
5686    ///
5687    /// # Example
5688    /// ```no_run
5689    /// use cvkg_core::{use_theme, Renderer, Rect};
5690    ///
5691    /// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
5692    ///     let colors = use_theme();
5693    ///     // Use accent color for the button background
5694    ///     renderer.fill_rounded_rect(rect, 8.0,
5695    ///         [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
5696    /// }
5697    /// ```
5698    #[derive(Debug, Clone)]
5699    pub struct SemanticColors {
5700        /// Primary brand color — used for key interactive elements.
5701        pub primary: Color,
5702        /// Secondary color — used for less prominent interactive elements.
5703        pub secondary: Color,
5704        /// Accent color — used for highlights, focus rings, CTAs.
5705        pub accent: Color,
5706        /// Page/window background color.
5707        pub background: Color,
5708        /// Surface color — used for cards, panels, sheets.
5709        pub surface: Color,
5710        /// Error color — used for destructive actions, error messages.
5711        pub error: Color,
5712        /// Warning color — used for caution indicators.
5713        pub warning: Color,
5714        /// Success color — used for positive feedback.
5715        pub success: Color,
5716        /// Primary text color.
5717        pub text: Color,
5718        /// Dimmed/disabled text color.
5719        pub text_dim: Color,
5720    }
5721
5722    impl SemanticColors {
5723        /// Dark theme semantic colors (default fallback).
5724        pub fn dark() -> Self {
5725            Self {
5726                primary: Color::new(1.0, 0.84, 0.0, 1.0),      // Viking Gold
5727                secondary: Color::new(1.0, 0.0, 1.0, 1.0),     // Magenta Liquid
5728                accent: Color::new(1.0, 0.0, 0.4, 1.0),        // Crimson Flash
5729                background: Color::new(0.02, 0.02, 0.05, 1.0), // Deep Void
5730                surface: Color::new(0.05, 0.05, 0.07, 1.0),    // Tactical Obsidian
5731                error: Color::new(1.0, 0.2, 0.2, 1.0),         // Red
5732                warning: Color::new(1.0, 0.8, 0.0, 1.0),       // Yellow
5733                success: Color::new(0.0, 1.0, 0.5, 1.0),       // Green
5734                text: Color::new(0.95, 0.95, 1.0, 1.0),        // Near-white
5735                text_dim: Color::new(0.6, 0.6, 0.7, 1.0),      // Gray
5736            }
5737        }
5738
5739        /// Light theme semantic colors.
5740        pub fn light() -> Self {
5741            Self {
5742                primary: Color::new(0.35, 0.30, 0.70, 1.0),
5743                secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5744                accent: Color::new(0.30, 0.35, 0.75, 1.0),
5745                background: Color::new(0.97, 0.97, 0.98, 1.0),
5746                surface: Color::new(0.93, 0.93, 0.95, 1.0),
5747                error: Color::new(0.75, 0.15, 0.15, 1.0),
5748                warning: Color::new(0.80, 0.60, 0.0, 1.0),
5749                success: Color::new(0.15, 0.65, 0.30, 1.0),
5750                text: Color::new(0.08, 0.08, 0.10, 1.0),
5751                text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5752            }
5753        }
5754
5755        /// Convert the accent color semantic color into interactive state colors.
5756        ///
5757        /// This provides hover/active/focus/disabled variants derived from the
5758        /// accent color, matching the pattern that `cvkg-themes::StateColors` uses.
5759        pub fn accent_states(&self) -> InteractiveColorStates {
5760            InteractiveColorStates::from_color(self.accent)
5761        }
5762
5763        /// Convert the primary color into interactive state colors.
5764        pub fn primary_states(&self) -> InteractiveColorStates {
5765            InteractiveColorStates::from_color(self.primary)
5766        }
5767
5768        /// Convert the error color into interactive state colors.
5769        pub fn error_states(&self) -> InteractiveColorStates {
5770            InteractiveColorStates::from_color(self.error)
5771        }
5772
5773        /// Convert the success color into interactive state colors.
5774        pub fn success_states(&self) -> InteractiveColorStates {
5775            InteractiveColorStates::from_color(self.success)
5776        }
5777    }
5778
5779    /// Interactive state colors derived from a single base color.
5780    ///
5781    /// Provides hover/active/focus/disabled variants for any color,
5782    /// derived via simple lightness adjustments in sRGB space.
5783    #[derive(Debug, Clone)]
5784    pub struct InteractiveColorStates {
5785        pub default: Color,
5786        pub hover: Color,
5787        pub active: Color,
5788        pub focus: Color,
5789        pub disabled: Color,
5790        pub focus_ring: Color,
5791    }
5792
5793    impl InteractiveColorStates {
5794        /// Derive interactive state colors from a base sRGB color.
5795        ///
5796        /// Uses simple lightness adjustments:
5797        /// - Hover: +15% lightness
5798        /// - Active: -15% lightness
5799        /// - Focus: same as default
5800        /// - Disabled: 40% opacity
5801        /// - Focus ring: base color at 70% opacity
5802        pub fn from_color(base: Color) -> Self {
5803            Self {
5804                default: base,
5805                hover: base.lighten(0.15),
5806                active: base.darken(0.15),
5807                focus: base,
5808                disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
5809                focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
5810            }
5811        }
5812
5813        /// Get the color for a specific interactive state.
5814        pub fn color_for(&self, state: InteractiveState) -> Color {
5815            match state {
5816                InteractiveState::Default => self.default,
5817                InteractiveState::Hover => self.hover,
5818                InteractiveState::Active => self.active,
5819                InteractiveState::Focus => self.focus,
5820                InteractiveState::Disabled => self.disabled,
5821            }
5822        }
5823    }
5824
5825    /// Interactive state for a component.
5826    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5827    pub enum InteractiveState {
5828        Default,
5829        Hover,
5830        Active,
5831        Focus,
5832        Disabled,
5833    }
5834}
5835
5836// =============================================================================
5837// USE_STATE HOOK — Local component state with automatic re-render
5838// =============================================================================
5839//
5840// Components call `use_state(id, initial)` to get a `(getter, setter)` pair.
5841// The setter updates the global system state and triggers a re-render.
5842//
5843// This is the minimal state primitive needed for interactive components.
5844// For complex state, use the global `KnowledgeState` directly.
5845
5846/// Local state hook for components.
5847///
5848/// Returns a `(getter, setter)` pair:
5849/// - `getter()` returns the current value of type `T`
5850/// - `setter(value)` updates the value and triggers a re-render
5851///
5852/// The `id` must be unique per component instance (use a hash of the
5853/// component's label or a generated UUID).
5854pub fn use_state<T: Clone + Send + Sync + 'static>(
5855    id: u64,
5856    initial: T,
5857) -> (impl Fn() -> T, impl Fn(T)) {
5858    // Initialize the state if not already present
5859    let already_exists = load_system_state().get_component_state::<T>(id).is_some();
5860    if !already_exists {
5861        update_system_state(|s| {
5862            let mut ns = s.clone();
5863            ns.set_component_state(id, initial.clone());
5864            ns
5865        });
5866    }
5867
5868    let getter = move || -> T {
5869        load_system_state()
5870            .get_component_state::<T>(id)
5871            .map(|arc_lock| {
5872                arc_lock
5873                    .read()
5874                    .ok()
5875                    .map(|guard| (*guard).clone())
5876                    .unwrap_or_else(|| initial.clone())
5877            })
5878            .unwrap_or_else(|| initial.clone())
5879    };
5880
5881    let setter = {
5882        move |value| {
5883            update_system_state(|s| {
5884                let mut ns = s.clone();
5885                ns.set_component_state(id, value);
5886                ns
5887            });
5888        }
5889    };
5890
5891    (getter, setter)
5892}
5893
5894/// Generate a stable hash ID from a string key.
5895///
5896/// Use this to create unique IDs for `use_state` based on component labels
5897/// or other stable identifiers.
5898///
5899/// # Example
5900/// ```no_run
5901/// use cvkg_core::{use_state, use_state_hash};
5902/// let id = use_state_hash("my-checkbox");
5903/// let (value, set_value) = use_state(id, false);
5904/// ```
5905pub fn use_state_hash(key: &str) -> u64 {
5906    use std::hash::{Hash, Hasher};
5907    let mut s = std::collections::hash_map::DefaultHasher::new();
5908    key.hash(&mut s);
5909    s.finish()
5910}
5911
5912// =============================================================================
5913// ACCESSIBILITY PREFERENCES — System accessibility settings
5914// =============================================================================
5915//
5916// Components and the renderer query these to adapt behavior:
5917// - Reduce Motion: disable non-essential animations
5918// - Reduce Transparency: replace glass materials with opaque surfaces
5919// - Increase Contrast: make borders visible, minimum alpha 0.5
5920
5921thread_local! {
5922    /// Thread-local accessibility preferences.
5923    /// Defaults to no restrictions (all false).
5924    static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
5925        std::cell::RefCell::new(AccessibilityPreferences::default());
5926}
5927
5928/// System accessibility preferences that components and the renderer must honor.
5929///
5930/// These map to macOS System Settings > Accessibility:
5931/// - `reduce_motion`: Disables non-essential animations (spring, bounce, etc.)
5932/// - `reduce_transparency`: Replaces glass/transparent materials with opaque surfaces
5933/// - `increase_contrast`: Makes all borders visible, minimum alpha 0.5 for all elements
5934#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5935pub struct AccessibilityPreferences {
5936    /// User prefers reduced motion. Animations should be instant or very short.
5937    pub reduce_motion: bool,
5938    /// User prefers reduced transparency. Glass materials should be opaque.
5939    pub reduce_transparency: bool,
5940    /// User prefers increased contrast. Borders must be visible, min alpha 0.5.
5941    pub increase_contrast: bool,
5942}
5943
5944impl AccessibilityPreferences {
5945    /// Detect system accessibility preferences (macOS).
5946    ///
5947    /// On non-macOS platforms, returns defaults (all false).
5948    /// In a production implementation, this would query the OS APIs.
5949    pub fn detect_from_system() -> Self {
5950        #[cfg(target_os = "macos")]
5951        {
5952            // Try to read macOS accessibility preferences via defaults command
5953            let reduce_motion = std::process::Command::new("defaults")
5954                .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
5955                .output()
5956                .ok()
5957                .and_then(|o| String::from_utf8(o.stdout).ok())
5958                .map(|s| s.trim() == "1")
5959                .unwrap_or(false);
5960
5961            let reduce_transparency = std::process::Command::new("defaults")
5962                .args([
5963                    "read",
5964                    "-g",
5965                    "com.apple.universalaccess",
5966                    "reduceTransparency",
5967                ])
5968                .output()
5969                .ok()
5970                .and_then(|o| String::from_utf8(o.stdout).ok())
5971                .map(|s| s.trim() == "1")
5972                .unwrap_or(false);
5973
5974            let increase_contrast = std::process::Command::new("defaults")
5975                .args([
5976                    "read",
5977                    "-g",
5978                    "com.apple.universalaccess",
5979                    "increaseContrast",
5980                ])
5981                .output()
5982                .ok()
5983                .and_then(|o| String::from_utf8(o.stdout).ok())
5984                .map(|s| s.trim() == "1")
5985                .unwrap_or(false);
5986
5987            Self {
5988                reduce_motion,
5989                reduce_transparency,
5990                increase_contrast,
5991            }
5992        }
5993        #[cfg(not(target_os = "macos"))]
5994        {
5995            Self::default()
5996        }
5997    }
5998
5999    /// Apply a minimum alpha constraint for increase-contrast mode.
6000    pub fn min_alpha(&self, requested: f32) -> f32 {
6001        if self.increase_contrast {
6002            requested.max(0.5)
6003        } else {
6004            requested
6005        }
6006    }
6007
6008    /// Returns true if glass effects should be replaced with opaque surfaces.
6009    pub fn should_disable_glass(&self) -> bool {
6010        self.reduce_transparency
6011    }
6012
6013    /// Returns true if animations should be instant.
6014    pub fn should_reduce_motion(&self) -> bool {
6015        self.reduce_motion
6016    }
6017
6018    /// Returns true if borders should be made visible.
6019    pub fn should_increase_contrast(&self) -> bool {
6020        self.increase_contrast
6021    }
6022}
6023
6024/// Get the current accessibility preferences for this thread.
6025pub fn accessibility_preferences() -> AccessibilityPreferences {
6026    ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6027}
6028
6029/// Set the accessibility preferences for this thread.
6030///
6031/// The native renderer should call this on startup and when system
6032/// preferences change (via `detect_from_system()`).
6033pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6034    ACCESSIBILITY_PREFS.with(|p| {
6035        *p.borrow_mut() = prefs;
6036    });
6037}
6038
6039// =============================================================================
6040// CLIPBOARD — System clipboard access
6041// =============================================================================
6042
6043/// Trait for clipboard operations.
6044///
6045/// The native renderer implements this via `arboard` on desktop platforms.
6046/// On WASM, it uses the browser Clipboard API.
6047pub trait ClipboardProvider: Send + Sync {
6048    /// Read text from the system clipboard.
6049    fn read_text(&self) -> Option<String>;
6050    /// Write text to the system clipboard.
6051    fn write_text(&self, text: &str);
6052}
6053
6054/// Default clipboard implementation using `arboard`.
6055/// Note: This is only available when the `arboard` feature is enabled.
6056/// The renderer provides the concrete implementation.
6057#[cfg(not(target_arch = "wasm32"))]
6058pub struct SystemClipboard;
6059
6060#[cfg(not(target_arch = "wasm32"))]
6061impl ClipboardProvider for SystemClipboard {
6062    fn read_text(&self) -> Option<String> {
6063        use std::process::Command;
6064        // Fallback: try pbpaste on macOS
6065        Command::new("pbpaste")
6066            .output()
6067            .ok()
6068            .and_then(|o| String::from_utf8(o.stdout).ok())
6069    }
6070
6071    fn write_text(&self, text: &str) {
6072        use std::process::Command;
6073        // Fallback: try pbcopy on macOS
6074        if let Ok(mut child) = Command::new("pbcopy")
6075            .stdin(std::process::Stdio::piped())
6076            .spawn()
6077        {
6078            if let Some(stdin) = child.stdin.as_mut() {
6079                use std::io::Write;
6080                let _ = stdin.write_all(text.as_bytes());
6081            }
6082            let _ = child.wait();
6083        }
6084    }
6085}
6086
6087// =============================================================================
6088// TEXT INPUT — Direction enum for cursor movement
6089// =============================================================================
6090
6091/// Direction for cursor movement in text input.
6092#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6093pub enum TextDirection {
6094    Forward,
6095    Backward,
6096    Up,
6097    Down,
6098    LineStart,
6099    LineEnd,
6100    WordForward,
6101    WordBackward,
6102}
6103
6104/// Text input state managed by the renderer.
6105///
6106/// Components don't store this directly — the renderer maintains it
6107/// and components query/modify it through the Renderer trait methods.
6108#[derive(Debug, Clone, Default)]
6109pub struct TextInputState {
6110    /// The full text content.
6111    pub text: String,
6112    /// Cursor position as byte offset into the text.
6113    pub cursor_pos: usize,
6114    /// Selection anchor. If Some, the selection is from anchor to cursor.
6115    /// If None, there is no selection.
6116    pub selection_anchor: Option<usize>,
6117    /// Whether the input is focused (shows cursor, accepts keyboard).
6118    pub focused: bool,
6119    /// Whether the caret is currently visible (for blinking).
6120    pub caret_visible: bool,
6121    /// Last edit timestamp for undo coalescing.
6122    pub last_edit_time: f32,
6123}
6124
6125impl TextInputState {
6126    /// Create a new TextInputState with the given initial text.
6127    pub fn new(text: impl Into<String>) -> Self {
6128        let text = text.into();
6129        let cursor_pos = text.len();
6130        Self {
6131            text,
6132            cursor_pos,
6133            selection_anchor: None,
6134            focused: false,
6135            caret_visible: true,
6136            last_edit_time: 0.0,
6137        }
6138    }
6139
6140    /// Get the selection range as (start, end) byte offsets.
6141    /// Returns None if there is no selection.
6142    pub fn selection_range(&self) -> Option<(usize, usize)> {
6143        self.selection_anchor.map(|anchor| {
6144            if anchor <= self.cursor_pos {
6145                (anchor, self.cursor_pos)
6146            } else {
6147                (self.cursor_pos, anchor)
6148            }
6149        })
6150    }
6151
6152    /// Get the selected text, or empty string if no selection.
6153    pub fn selected_text(&self) -> String {
6154        self.selection_range()
6155            .map(|(start, end)| self.text[start..end].to_string())
6156            .unwrap_or_default()
6157    }
6158
6159    /// Insert text at the current cursor position, replacing any selection.
6160    pub fn insert(&mut self, new_text: &str) {
6161        if let Some((start, end)) = self.selection_range() {
6162            self.text.replace_range(start..end, new_text);
6163            self.cursor_pos = start + new_text.len();
6164        } else {
6165            self.text.insert_str(self.cursor_pos, new_text);
6166            self.cursor_pos += new_text.len();
6167        }
6168        self.selection_anchor = None;
6169    }
6170
6171    /// Delete characters. If there's a selection, delete it.
6172    /// Otherwise delete `count` characters backward (backspace) or forward (delete).
6173    pub fn delete(&mut self, backward: bool, count: usize) -> String {
6174        if let Some((start, end)) = self.selection_range() {
6175            let deleted = self.text[start..end].to_string();
6176            self.text.replace_range(start..end, "");
6177            self.cursor_pos = start;
6178            self.selection_anchor = None;
6179            return deleted;
6180        }
6181
6182        if backward && self.cursor_pos > 0 {
6183            let start = self.cursor_pos.saturating_sub(count);
6184            let deleted = self.text[start..self.cursor_pos].to_string();
6185            self.text.replace_range(start..self.cursor_pos, "");
6186            self.cursor_pos = start;
6187            deleted
6188        } else if !backward && self.cursor_pos < self.text.len() {
6189            let end = (self.cursor_pos + count).min(self.text.len());
6190            let deleted = self.text[self.cursor_pos..end].to_string();
6191            self.text.replace_range(self.cursor_pos..end, "");
6192            deleted
6193        } else {
6194            String::new()
6195        }
6196    }
6197
6198    /// Move the cursor in the given direction.
6199    pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6200        if !extend_selection {
6201            self.selection_anchor = None;
6202        } else if self.selection_anchor.is_none() {
6203            self.selection_anchor = Some(self.cursor_pos);
6204        }
6205
6206        match direction {
6207            TextDirection::Forward if self.cursor_pos < self.text.len() => {
6208                // Move to next character boundary (UTF-8 safe)
6209                let next = self.text[self.cursor_pos..]
6210                    .char_indices()
6211                    .nth(1)
6212                    .map(|(i, _)| self.cursor_pos + i)
6213                    .unwrap_or(self.text.len());
6214                self.cursor_pos = next;
6215            }
6216            TextDirection::Backward if self.cursor_pos > 0 => {
6217                let prev = self.text[..self.cursor_pos]
6218                    .char_indices()
6219                    .next_back()
6220                    .map(|(i, _)| i)
6221                    .unwrap_or(0);
6222                self.cursor_pos = prev;
6223            }
6224            TextDirection::LineStart => {
6225                self.cursor_pos = 0;
6226            }
6227            TextDirection::LineEnd => {
6228                self.cursor_pos = self.text.len();
6229            }
6230            TextDirection::WordForward => {
6231                // Find next word boundary
6232                let rest = &self.text[self.cursor_pos..];
6233                // Skip current word chars
6234                let after_word = rest
6235                    .char_indices()
6236                    .find(|(_, c)| !c.is_alphanumeric())
6237                    .map(|(i, _)| i)
6238                    .unwrap_or(rest.len());
6239                // Skip whitespace
6240                let after_space = rest[after_word..]
6241                    .char_indices()
6242                    .find(|(_, c)| !c.is_whitespace())
6243                    .map(|(i, _)| after_word + i)
6244                    .unwrap_or(rest.len());
6245                self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6246            }
6247            TextDirection::WordBackward => {
6248                let before = &self.text[..self.cursor_pos];
6249                // Skip whitespace going backward
6250                let before_word = before
6251                    .char_indices()
6252                    .rev()
6253                    .find(|(_, c)| !c.is_whitespace())
6254                    .map(|(i, _)| i)
6255                    .unwrap_or(0);
6256                // Skip word chars going backward
6257                let word_start = before[..before_word]
6258                    .char_indices()
6259                    .rev()
6260                    .find(|(_, c)| !c.is_alphanumeric())
6261                    .map(|(i, _)| i)
6262                    .unwrap_or(0);
6263                self.cursor_pos = word_start;
6264            }
6265            _ => {} // Up/Down handled by multi-line components
6266        }
6267
6268        if !extend_selection {
6269            self.selection_anchor = None;
6270        }
6271    }
6272
6273    /// Select all text.
6274    pub fn select_all(&mut self) {
6275        self.cursor_pos = self.text.len();
6276        self.selection_anchor = Some(0);
6277    }
6278
6279    /// Get the byte offset of the cursor.
6280    pub fn cursor_byte_pos(&self) -> usize {
6281        self.cursor_pos
6282    }
6283}
6284
6285/// Action details for interactive buttons inside a notification.
6286#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6287pub struct NotificationAction {
6288    /// Unique identifier of the action.
6289    pub id: String,
6290    /// The text label to display on the action button.
6291    pub title: String,
6292    /// Indicates whether the action performs a destructive task (e.g. Delete).
6293    pub is_destructive: bool,
6294}
6295
6296/// Priority tier of a notification.
6297#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6298pub enum NotificationPriority {
6299    /// Placed silently into the notification center without visual alerts.
6300    Passive,
6301    /// Triggers a visual alert (toast) but does not interrupt focus.
6302    #[default]
6303    Active,
6304    /// Important alert that bypasses standard DND/Focus bounds.
6305    TimeSensitive,
6306}
6307
6308/// A structured notification representation.
6309#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6310pub struct Notification {
6311    /// Unique identifier for this notification.
6312    pub id: String,
6313    /// App or source identifier spawning this notification.
6314    pub app_name: Option<String>,
6315    /// The bold heading/title text.
6316    pub title: String,
6317    /// The detailed descriptive body text.
6318    pub body: String,
6319    /// Optional URI or path to an icon asset.
6320    pub icon: Option<String>,
6321    /// Optional sound identifier to play when posting.
6322    pub sound: Option<String>,
6323    /// Interactive actions available on this notification.
6324    pub actions: Vec<NotificationAction>,
6325    /// Timer duration in seconds after which the toast auto-dismisses.
6326    pub timeout: Option<f32>,
6327    /// Priority level for delivery logic.
6328    pub priority: NotificationPriority,
6329    /// Time (in seconds since renderer startup) when this notification was posted.
6330    pub timestamp: f32,
6331    /// Whether the notification has been dismissed/read.
6332    pub dismissed: bool,
6333}
6334
6335/// Error type indicating a failure in generating or posting a notification.
6336#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6337pub enum NotificationError {
6338    /// Permissions denied.
6339    #[error("Notification permission denied")]
6340    PermissionDenied,
6341    /// Failed to post the notification.
6342    #[error("Failed to post notification")]
6343    PostFailed,
6344}
6345
6346/// State of notification permissions.
6347#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6348pub enum NotificationPermission {
6349    /// Explicitly allowed.
6350    Granted,
6351    /// Explicitly blocked.
6352    Denied,
6353    /// Prompt has not been shown or decided yet.
6354    #[default]
6355    NotDetermined,
6356}
6357
6358/// Core interface for routing and dispatching notification events.
6359pub trait NotificationHandler: Send + Sync {
6360    /// Posts a new notification.
6361    fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6362    /// Dismisses a notification by ID.
6363    fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6364    /// Requests delivery permission.
6365    fn request_permission(&self) -> NotificationPermission;
6366}
6367
6368static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6369    std::sync::atomic::AtomicUsize::new(1);
6370
6371/// Default in-app notification handler that writes state to KnowledgeState.
6372#[derive(Clone, Copy, Debug, Default)]
6373pub struct DefaultNotificationHandler;
6374
6375impl NotificationHandler for DefaultNotificationHandler {
6376    /// Save the notification to the global system state (history) and auto-assign an ID if empty.
6377    fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6378        let mut notif = notification;
6379        if notif.id.is_empty() {
6380            let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6381            notif.id = format!("notif_{}", id);
6382        }
6383        update_system_state(|state| {
6384            let mut new_state = state.clone();
6385            new_state.notifications.push(notif.clone());
6386            new_state
6387        });
6388        Ok(())
6389    }
6390
6391    /// Mark a notification as dismissed/read in the global system state.
6392    fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6393        update_system_state(|state| {
6394            let mut new_state = state.clone();
6395            for notif in &mut new_state.notifications {
6396                if notif.id == id {
6397                    notif.dismissed = true;
6398                }
6399            }
6400            new_state
6401        });
6402        Ok(())
6403    }
6404
6405    /// Returns the permission state (always Granted for internal in-app notifications).
6406    fn request_permission(&self) -> NotificationPermission {
6407        NotificationPermission::Granted
6408    }
6409}
6410
6411static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6412    once_cell::sync::OnceCell::new();
6413
6414/// Sets the global notification handler.
6415pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6416    let _ = NOTIFICATION_HANDLER.set(handler);
6417}
6418
6419/// Gets the global notification handler, fallback to DefaultNotificationHandler.
6420pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6421    NOTIFICATION_HANDLER
6422        .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6423        .clone()
6424}
6425
6426/// Filter mapping name to extension list for a file dialog.
6427#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6428pub struct FileFilter {
6429    /// Friendly name of the filter (e.g. "Images").
6430    pub name: String,
6431    /// List of file extensions (e.g. ["png", "jpg"]).
6432    pub extensions: Vec<String>,
6433}
6434
6435/// The mode/purpose of the file dialog.
6436#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6437pub enum FileDialogMode {
6438    /// Pick a single or multiple files to open.
6439    #[default]
6440    OpenFile,
6441    /// Pick a directory path.
6442    OpenDirectory,
6443    /// Prompt for a location/name to save a file.
6444    SaveFile,
6445}
6446
6447/// Dialog options for picking files or directories.
6448#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6449pub struct FileDialog {
6450    /// Title displayed in the dialog window.
6451    pub title: String,
6452    /// Optional starting directory path.
6453    pub default_path: Option<String>,
6454    /// Extensions used to filter selection.
6455    pub filters: Vec<FileFilter>,
6456    /// Open/save mode.
6457    pub mode: FileDialogMode,
6458    /// Allows selecting multiple files if in OpenFile mode.
6459    pub allow_multiple: bool,
6460}
6461
6462/// Errors returned by the file dialog.
6463#[derive(Debug, thiserror::Error)]
6464pub enum FileDialogError {
6465    /// The user closed the dialog without selecting anything.
6466    #[error("File dialog cancelled")]
6467    Cancelled,
6468    /// An input/output error occurred.
6469    #[error("I/O error: {0}")]
6470    Io(#[from] std::io::Error),
6471    /// Platform-specific error.
6472    #[error("Platform error: {0}")]
6473    Platform(String),
6474}
6475
6476impl FileDialog {
6477    /// Creates a new FileDialog with the given mode.
6478    pub fn new(mode: FileDialogMode) -> Self {
6479        Self {
6480            mode,
6481            ..Default::default()
6482        }
6483    }
6484
6485    /// Sets the dialog title.
6486    pub fn title(mut self, title: impl Into<String>) -> Self {
6487        self.title = title.into();
6488        self
6489    }
6490
6491    /// Adds a file filter.
6492    pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6493        self.filters.push(FileFilter {
6494            name: name.to_string(),
6495            extensions: extensions.iter().map(|s| s.to_string()).collect(),
6496        });
6497        self
6498    }
6499
6500    /// Sets the default starting directory path.
6501    pub fn default_path(mut self, path: impl Into<String>) -> Self {
6502        self.default_path = Some(path.into());
6503        self
6504    }
6505
6506    /// Sets whether selecting multiple files is allowed.
6507    pub fn allow_multiple(mut self, allow: bool) -> Self {
6508        self.allow_multiple = allow;
6509        self
6510    }
6511}
6512
6513#[cfg(not(target_arch = "wasm32"))]
6514impl FileDialog {
6515    /// Pick file(s) or folder based on current mode configuration.
6516    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6517        let mut dialog = rfd::FileDialog::new();
6518        dialog = dialog.set_title(&self.title);
6519        if let Some(path) = &self.default_path {
6520            dialog = dialog.set_directory(path);
6521        }
6522        for filter in &self.filters {
6523            let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6524            dialog = dialog.add_filter(&filter.name, &refs);
6525        }
6526
6527        match self.mode {
6528            FileDialogMode::OpenFile => {
6529                if self.allow_multiple {
6530                    dialog.pick_files().ok_or(FileDialogError::Cancelled)
6531                } else {
6532                    Ok(dialog.pick_file().into_iter().collect())
6533                }
6534            }
6535            FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6536            FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6537        }
6538    }
6539
6540    /// Helper to pick a single file/directory, returning None if cancelled.
6541    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6542        let results = self.pick()?;
6543        Ok(results.into_iter().next())
6544    }
6545}
6546
6547#[cfg(target_arch = "wasm32")]
6548impl FileDialog {
6549    /// Pick is unsupported/mocked on WASM.
6550    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6551        Err(FileDialogError::Platform(
6552            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6553        ))
6554    }
6555
6556    /// Helper to pick a single file/directory, returning None if cancelled.
6557    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6558        Err(FileDialogError::Platform(
6559            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6560        ))
6561    }
6562}
6563
6564/// Error type representing a failure in Document load/save/parse operations.
6565#[derive(Debug, thiserror::Error)]
6566pub enum DocumentError {
6567    /// An input/output error occurred.
6568    #[error("I/O error: {0}")]
6569    Io(#[from] std::io::Error),
6570    /// Failure during deserialization or parsing.
6571    #[error("Parse error: {0}")]
6572    Parse(String),
6573    /// Failure during serialization.
6574    #[error("Serialization error: {0}")]
6575    Serialize(String),
6576}
6577
6578/// A document interface mapping to local filesystem persistence.
6579pub trait Document: Send + Sync {
6580    /// Loads the document from the specified path.
6581    fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6582    where
6583        Self: Sized;
6584
6585    /// Saves the document to the specified path.
6586    fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6587
6588    /// Returns true if the document has unsaved modifications.
6589    fn is_dirty(&self) -> bool;
6590
6591    /// Marks the document as clean/saved.
6592    fn mark_clean(&mut self);
6593}
6594
6595/// Periodic auto-save coordinator for open Documents.
6596pub struct AutoSaveManager {
6597    /// Time interval in seconds between auto-saves.
6598    pub interval: f32,
6599    /// Elapsed timer tracker.
6600    pub timer: f32,
6601    /// Registered open documents under management.
6602    pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6603}
6604
6605impl AutoSaveManager {
6606    /// Creates a new AutoSaveManager with the specified check interval.
6607    pub fn new(interval: f32) -> Self {
6608        Self {
6609            interval,
6610            timer: 0.0,
6611            documents: Vec::new(),
6612        }
6613    }
6614
6615    /// Register a document with its current file path.
6616    pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6617        self.documents.push((path, doc));
6618    }
6619
6620    /// Advance the timer and auto-save any dirty documents when the interval is reached.
6621    pub fn tick(&mut self, dt: f32) {
6622        self.timer += dt;
6623        if self.timer >= self.interval {
6624            self.timer = 0.0;
6625            for (path, doc) in &mut self.documents {
6626                if doc.is_dirty() {
6627                    match doc.write_to(path) {
6628                        Ok(()) => {
6629                            doc.mark_clean();
6630                            log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6631                        }
6632                        Err(e) => {
6633                            log::error!(
6634                                "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6635                                path,
6636                                e
6637                            );
6638                        }
6639                    }
6640                }
6641            }
6642        }
6643    }
6644}
6645
6646// ── Menu Bar API ──────────────────────────────────────────────────────────────
6647
6648/// Keyboard modifier flags used by [`KeyboardShortcut`].
6649///
6650/// On macOS, `cmd` maps to the Command (⌘) key.
6651/// On all other platforms, `cmd` maps to the Control key.
6652/// This is enforced at the renderer level, not here; the data model is OS-agnostic.
6653#[derive(Debug, Clone, PartialEq, Eq, Default)]
6654pub struct Modifiers {
6655    /// Command on macOS, Control on Windows/Linux.
6656    pub cmd: bool,
6657    /// Shift key.
6658    pub shift: bool,
6659    /// Alt/Option key.
6660    pub alt: bool,
6661    /// Control key (distinct from cmd on all platforms).
6662    pub ctrl: bool,
6663}
6664
6665/// A keyboard shortcut binding to a menu action.
6666#[derive(Debug, Clone)]
6667pub struct KeyboardShortcut {
6668    /// The key character or name, e.g. `"s"`, `"z"`, `"Return"`.
6669    pub key: String,
6670    /// The required modifier combination.
6671    pub modifiers: Modifiers,
6672}
6673
6674impl KeyboardShortcut {
6675    /// Convenience constructor: cmd (or ctrl on non-macOS) + `key`.
6676    pub fn cmd(key: impl Into<String>) -> Self {
6677        Self {
6678            key: key.into(),
6679            modifiers: Modifiers {
6680                cmd: true,
6681                ..Default::default()
6682            },
6683        }
6684    }
6685
6686    /// Convenience constructor: cmd+Shift + `key`.
6687    pub fn cmd_shift(key: impl Into<String>) -> Self {
6688        Self {
6689            key: key.into(),
6690            modifiers: Modifiers {
6691                cmd: true,
6692                shift: true,
6693                ..Default::default()
6694            },
6695        }
6696    }
6697}
6698
6699/// A single entry in a [`MenuBar`].
6700///
6701/// Actions hold a callback that is invoked when the user activates the item
6702/// (either via the menu UI or via the associated keyboard shortcut).
6703/// Separators provide visual grouping. Submenus allow hierarchical menus.
6704pub enum MenuItem {
6705    /// An activatable menu entry with an optional shortcut and enabled/disabled state.
6706    Action {
6707        label: String,
6708        shortcut: Option<KeyboardShortcut>,
6709        action: std::sync::Arc<dyn Fn() + Send + Sync>,
6710        enabled: bool,
6711    },
6712    /// A nested submenu.
6713    Submenu { label: String, items: Vec<MenuItem> },
6714    /// A visual separator line between groups of items.
6715    Separator,
6716}
6717
6718impl std::fmt::Debug for MenuItem {
6719    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6720        match self {
6721            Self::Action { label, enabled, .. } => f
6722                .debug_struct("Action")
6723                .field("label", label)
6724                .field("enabled", enabled)
6725                .finish(),
6726            Self::Submenu { label, items } => f
6727                .debug_struct("Submenu")
6728                .field("label", label)
6729                .field("items", items)
6730                .finish(),
6731            Self::Separator => write!(f, "Separator"),
6732        }
6733    }
6734}
6735
6736/// A top-level menu bar containing [`MenuItem`]s.
6737///
6738/// The menu bar is a data model only; rendering it into an OS-native menu is
6739/// handled by the platform renderer (`cvkg-render-native`).
6740pub struct MenuBar {
6741    /// Ordered list of top-level menu items.
6742    pub items: Vec<MenuItem>,
6743}
6744
6745impl MenuBar {
6746    /// Create an empty menu bar.
6747    pub fn new() -> Self {
6748        Self { items: Vec::new() }
6749    }
6750
6751    /// Append a menu item to the bar.
6752    pub fn add_item(&mut self, item: MenuItem) {
6753        self.items.push(item);
6754    }
6755
6756    /// Build the standard CVKG menu structure with all conventional shortcuts.
6757    ///
6758    /// The `cmd` modifier maps to ⌘ on macOS and Ctrl on Windows/Linux — this
6759    /// translation is enforced by the renderer, not here.
6760    ///
6761    /// Menus included:
6762    /// - **File**: New, Open, Save, Close
6763    /// - **Edit**: Undo, Redo, Cut, Copy, Paste, Select All, Find
6764    /// - **View**: Zoom In, Zoom Out, Fullscreen
6765    /// - **Window**: Minimize, Zoom, Bring All to Front
6766    /// - **Help**: Search Help
6767    #[allow(clippy::too_many_arguments)]
6768    pub fn standard(
6769        new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6770        open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6771        save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6772        close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6773        quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6774        undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6775        redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6776        cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6777        copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6778        paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6779        select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6780        find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6781    ) -> Self {
6782        let mut bar = Self::new();
6783
6784        // ── File ──────────────────────────────────────────────────────────────
6785        bar.add_item(MenuItem::Submenu {
6786            label: "File".to_string(),
6787            items: vec![
6788                MenuItem::Action {
6789                    label: "New".to_string(),
6790                    shortcut: Some(KeyboardShortcut::cmd("n")),
6791                    action: new_fn,
6792                    enabled: true,
6793                },
6794                MenuItem::Action {
6795                    label: "Open…".to_string(),
6796                    shortcut: Some(KeyboardShortcut::cmd("o")),
6797                    action: open_fn,
6798                    enabled: true,
6799                },
6800                MenuItem::Separator,
6801                MenuItem::Action {
6802                    label: "Save".to_string(),
6803                    shortcut: Some(KeyboardShortcut::cmd("s")),
6804                    action: save_fn,
6805                    enabled: true,
6806                },
6807                MenuItem::Separator,
6808                MenuItem::Action {
6809                    label: "Close".to_string(),
6810                    shortcut: Some(KeyboardShortcut::cmd("w")),
6811                    action: close_fn,
6812                    enabled: true,
6813                },
6814                MenuItem::Separator,
6815                MenuItem::Action {
6816                    label: "Quit".to_string(),
6817                    shortcut: Some(KeyboardShortcut::cmd("q")),
6818                    action: quit_fn,
6819                    enabled: true,
6820                },
6821            ],
6822        });
6823
6824        // ── Edit ──────────────────────────────────────────────────────────────
6825        bar.add_item(MenuItem::Submenu {
6826            label: "Edit".to_string(),
6827            items: vec![
6828                MenuItem::Action {
6829                    label: "Undo".to_string(),
6830                    shortcut: Some(KeyboardShortcut::cmd("z")),
6831                    action: undo_fn,
6832                    enabled: true,
6833                },
6834                MenuItem::Action {
6835                    label: "Redo".to_string(),
6836                    shortcut: Some(KeyboardShortcut::cmd_shift("z")),
6837                    action: redo_fn,
6838                    enabled: true,
6839                },
6840                MenuItem::Separator,
6841                MenuItem::Action {
6842                    label: "Cut".to_string(),
6843                    shortcut: Some(KeyboardShortcut::cmd("x")),
6844                    action: cut_fn,
6845                    enabled: true,
6846                },
6847                MenuItem::Action {
6848                    label: "Copy".to_string(),
6849                    shortcut: Some(KeyboardShortcut::cmd("c")),
6850                    action: copy_fn,
6851                    enabled: true,
6852                },
6853                MenuItem::Action {
6854                    label: "Paste".to_string(),
6855                    shortcut: Some(KeyboardShortcut::cmd("v")),
6856                    action: paste_fn,
6857                    enabled: true,
6858                },
6859                MenuItem::Separator,
6860                MenuItem::Action {
6861                    label: "Select All".to_string(),
6862                    shortcut: Some(KeyboardShortcut::cmd("a")),
6863                    action: select_all_fn,
6864                    enabled: true,
6865                },
6866                MenuItem::Separator,
6867                MenuItem::Action {
6868                    label: "Find…".to_string(),
6869                    shortcut: Some(KeyboardShortcut::cmd("f")),
6870                    action: find_fn,
6871                    enabled: true,
6872                },
6873            ],
6874        });
6875
6876        // ── View ──────────────────────────────────────────────────────────────
6877        // View items carry no application-level callbacks at the model layer;
6878        // zoom and fullscreen are handled by the renderer directly.
6879        let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
6880        bar.add_item(MenuItem::Submenu {
6881            label: "View".to_string(),
6882            items: vec![
6883                MenuItem::Action {
6884                    label: "Zoom In".to_string(),
6885                    shortcut: Some(KeyboardShortcut::cmd("=")),
6886                    action: noop.clone(),
6887                    enabled: true,
6888                },
6889                MenuItem::Action {
6890                    label: "Zoom Out".to_string(),
6891                    shortcut: Some(KeyboardShortcut::cmd("-")),
6892                    action: noop.clone(),
6893                    enabled: true,
6894                },
6895                MenuItem::Separator,
6896                MenuItem::Action {
6897                    label: "Toggle Fullscreen".to_string(),
6898                    shortcut: Some(KeyboardShortcut {
6899                        key: "f".to_string(),
6900                        modifiers: Modifiers {
6901                            ctrl: true,
6902                            ..Default::default()
6903                        },
6904                    }),
6905                    action: noop.clone(),
6906                    enabled: true,
6907                },
6908            ],
6909        });
6910
6911        // ── Window ────────────────────────────────────────────────────────────
6912        bar.add_item(MenuItem::Submenu {
6913            label: "Window".to_string(),
6914            items: vec![
6915                MenuItem::Action {
6916                    label: "Minimize".to_string(),
6917                    shortcut: Some(KeyboardShortcut::cmd("m")),
6918                    action: noop.clone(),
6919                    enabled: true,
6920                },
6921                MenuItem::Action {
6922                    label: "Zoom".to_string(),
6923                    shortcut: None,
6924                    action: noop.clone(),
6925                    enabled: true,
6926                },
6927                MenuItem::Separator,
6928                MenuItem::Action {
6929                    label: "Bring All to Front".to_string(),
6930                    shortcut: None,
6931                    action: noop.clone(),
6932                    enabled: true,
6933                },
6934            ],
6935        });
6936
6937        // ── Help ──────────────────────────────────────────────────────────────
6938        bar.add_item(MenuItem::Submenu {
6939            label: "Help".to_string(),
6940            items: vec![MenuItem::Action {
6941                label: "Search Help".to_string(),
6942                shortcut: None,
6943                action: noop,
6944                enabled: true,
6945            }],
6946        });
6947
6948        bar
6949    }
6950}
6951
6952impl Default for MenuBar {
6953    fn default() -> Self {
6954        Self::new()
6955    }
6956}
6957
6958// =============================================================================
6959// LOCALIZATION — Item 12: Localization / Internationalization
6960// =============================================================================
6961// OS-agnostic: works on all platforms. No platform-specific string loading.
6962
6963use std::sync::RwLock;
6964
6965/// Layout direction for UI elements (LTR or RTL).
6966#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
6967pub enum Direction {
6968    #[default]
6969    LTR,
6970    RTL,
6971    Auto,
6972}
6973
6974impl Direction {
6975    pub fn is_rtl(self) -> bool {
6976        matches!(self, Direction::RTL)
6977    }
6978}
6979#[derive(Clone, Debug)]
6980pub struct L10nBundle {
6981    pub locale: String,
6982    pub strings: HashMap<String, String>,
6983    pub is_rtl: bool,
6984}
6985
6986impl L10nBundle {
6987    pub fn new(locale: impl Into<String>) -> Self {
6988        Self {
6989            locale: locale.into(),
6990            strings: HashMap::new(),
6991            is_rtl: false,
6992        }
6993    }
6994
6995    pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
6996        self.strings.insert(key.into(), value.into());
6997        self
6998    }
6999
7000    pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7001        let mut bundle = Self::new(locale);
7002        for line in input.lines() {
7003            let line = line.trim();
7004            if line.is_empty() || line.starts_with("//") {
7005                continue;
7006            }
7007            if let Some(eq_pos) = line.find(" = ") {
7008                let key = line[..eq_pos].trim_matches('"').to_string();
7009                let val = line[eq_pos + 3..]
7010                    .trim_end_matches(';')
7011                    .trim_matches('"')
7012                    .to_string();
7013                bundle.strings.insert(key, val);
7014            }
7015        }
7016        bundle
7017    }
7018    /// Get a translated string by key. Returns the key itself if not found.
7019    pub fn t(&self, key: &str) -> String {
7020        self.strings
7021            .get(key)
7022            .map(|s| s.to_string())
7023            .unwrap_or_else(|| key.to_string())
7024    }
7025
7026    /// Translate with interpolation. Replaces {0}, {1}, etc. with args.
7027    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7028        let mut result = self.t(key);
7029        for (i, arg) in args.iter().enumerate() {
7030            result = result.replace(&format!("{{{}}}", i), arg);
7031        }
7032        result
7033    }
7034}
7035
7036/// Global localization manager.
7037pub struct L10n {
7038    bundles: HashMap<String, L10nBundle>,
7039    current: String,
7040}
7041
7042impl L10n {
7043    pub fn new(default_locale: &str) -> Self {
7044        Self {
7045            bundles: HashMap::new(),
7046            current: default_locale.to_string(),
7047        }
7048    }
7049
7050    pub fn add_bundle(&mut self, bundle: L10nBundle) {
7051        self.bundles.insert(bundle.locale.clone(), bundle);
7052    }
7053
7054    pub fn set_locale(&mut self, locale: &str) {
7055        self.current = locale.to_string();
7056    }
7057    pub fn current_locale(&self) -> &str {
7058        &self.current
7059    }
7060
7061    pub fn is_rtl(&self) -> bool {
7062        self.bundles
7063            .get(self.current.as_str())
7064            .map(|b| b.is_rtl)
7065            .unwrap_or(false)
7066    }
7067
7068    pub fn t(&self, key: &str) -> String {
7069        self.bundles
7070            .get(self.current.as_str())
7071            .map(|b| b.t(key))
7072            .unwrap_or_else(|| key.to_string())
7073    }
7074
7075    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7076        let mut result = self.t(key);
7077        for (i, arg) in args.iter().enumerate() {
7078            result = result.replace(&format!("{{{}}}", i), arg);
7079        }
7080        result
7081    }
7082
7083    pub fn direction(&self) -> Direction {
7084        if self.is_rtl() {
7085            Direction::RTL
7086        } else {
7087            Direction::LTR
7088        }
7089    }
7090}
7091
7092static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7093    once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7094
7095pub fn init_l10n(l10n: L10n) {
7096    if let Ok(mut guard) = L10N.write() {
7097        *guard = l10n;
7098    }
7099}
7100
7101pub fn l10n() -> Arc<RwLock<L10n>> {
7102    L10N.clone()
7103}
7104
7105pub fn t(key: &str) -> String {
7106    L10N.read()
7107        .map(|g| g.t(key).to_string())
7108        .unwrap_or_else(|_| key.to_string())
7109}
7110
7111pub fn tf(key: &str, args: &[&str]) -> String {
7112    L10N.read()
7113        .map(|g| g.tf(key, args))
7114        .unwrap_or_else(|_| key.to_string())
7115}
7116
7117pub fn set_locale(locale: &str) {
7118    if let Ok(mut guard) = L10N.write() {
7119        guard.set_locale(locale);
7120    }
7121}
7122
7123pub fn current_locale() -> String {
7124    L10N.read()
7125        .map(|g| g.current_locale().to_string())
7126        .unwrap_or_else(|_| "en".to_string())
7127}
7128
7129pub fn is_rtl() -> bool {
7130    L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7131}
7132
7133// =============================================================================
7134// SYSTEM THEME DETECTION — Dark/Light mode detection
7135// =============================================================================
7136//
7137// OS-agnostic theme detection. Checks the CVKG_THEME environment variable first,
7138// then falls back to dark mode (safe default).
7139//
7140// Platform backends may override this with native OS queries (e.g.,
7141// dark-light crate on desktop, prefers-color-scheme on web).
7142
7143/// The detected system theme.
7144#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7145pub enum SystemTheme {
7146    /// Dark mode (default).
7147    #[default]
7148    Dark,
7149    /// Light mode.
7150    Light,
7151}
7152
7153/// Detect the current system theme.
7154///
7155/// Checks `CVKG_THEME` environment variable first:
7156/// - `"dark"` → `SystemTheme::Dark`
7157/// - `"light"` → `SystemTheme::Light`
7158/// - unset or any other value → `SystemTheme::Dark` (default)
7159///
7160/// Platform backends can call this and override with native detection
7161/// (e.g., `dark-light` crate on desktop, `prefers-color-scheme` on web).
7162pub fn detect_system_theme() -> SystemTheme {
7163    std::env::var("CVKG_THEME")
7164        .ok()
7165        .and_then(|v| match v.as_str() {
7166            "light" => Some(SystemTheme::Light),
7167            "dark" => Some(SystemTheme::Dark),
7168            _ => None,
7169        })
7170        .unwrap_or(SystemTheme::Dark)
7171}
7172
7173// =============================================================================
7174// AUDIO / HAPTIC — Item 14: Spatial Audio / Haptic Feedback
7175// =============================================================================
7176// OS-agnostic: pure trait abstractions. Platform backends via cfg in renderer.
7177
7178pub mod audio_haptic;
7179pub use audio_haptic::{
7180    AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7181    haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7182    set_haptic_engine, sounds,
7183};